I'd like to write a vue-plugin to get handy WebSocket methods like connect() and subscribe() in my Vue application. I've got a problem with connecting to WebSocket, it only works when I call connect() method in the mounted hook and load the whole page (like with the browser refresh button). In another case, when I first load the page and then call the connect() method explicitly by the button click, the connection isn't established.
My vue-plugin code:
import SockJS from "sockjs-client";
import Stomp from "webstomp-client";
const WebSocketTester = {
install(Vue, options) {
console.log("websocket tester launched");
let connected = false;
const ws = {
connected: () => connected
};
const stompClient = getStompClient("http://localhost:8080/ws");
const connect = () => {
return new Promise((resolve, reject) => {
if (connected) {
reject("Already connected !");
return;
}
console.log("trying to connect websocket");
stompClient.connect({}, frame => {
console.log("got websocket frame:");
console.log(frame);
if (frame.command == "CONNECTED") {
connected = true;
resolve();
} else {
reject("Could not connect with " + url);
}
});
});
};
ws.connect = () => {
return connect();
};
Vue.prototype.$ws = ws;
}
};
const getStompClient = webSocketUrl => {
const socket = new SockJS(webSocketUrl);
return Stomp.over(socket);
};
export default WebSocketTester;
My vue component:
<template>
<div class="hello">
<button #click="connect">Connect with websocket</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
},
methods: {
connect() {
console.log("connecting...");
this.$ws.connect().catch(error => {
console.log("could not connect by click");
console.log(error);
});
}
},
mounted() {
// this works well
// this.$ws.connect().catch(error => {
// console.log("could not connect in mounted");
// console.log(error);
// });
}
};
</script>
In the case, I uncomment the mounted hook, after page load I see the console log like this:
websocket tester launched
trying to connect websocket
Opening Web Socket...
Web Socket Opened...
DEPRECATED: undefined is not a recognized STOMP version. In next major client version, this will close the connection.
>>> CONNECT
>>> length 52
<<< CONNECTED
connected to server undefined
got websocket frame:
Frame {command: "CONNECTED", headers: {…}, body: ""}
And everything works correct. But, if I comment the mounted hook and want to connect with the WebSocket by the button click, the console log looks like this:
websocket tester launched
connecting...
trying to connect websocket
Opening Web Socket...
and that's it, the connection isn't established. Why this happens and how to fix it?
OK I figured it out. The problem line was const stompClient = getStompClient("http://localhost:8080/ws"); in the plugin. I've moved it to the connect method and store as ws.object.
if (connected) {
reject("Already connected !");
return;
}
ws.stompClient = getStompClient("http://localhost:8080/ws");
console.log("trying to connect websocket");
ws.stompClient.connect({}, frame => {
Later, I use ws.stompClient and it works fine.
Related
Based on the official documentation, i am able to get the subscribed messages. When i simply run the javascript code, it runs without any error.
const WebSocket = require('ws');
const { WebPubSubServiceClient } = require('#azure/web-pubsub');
async function main() {
const hub = "hub1";
let service = new WebPubSubServiceClient(process.env.WebPubSubConnectionString, hub);
let token = await service.getClientAccessToken();
let ws = new WebSocket(token.url);
ws.on('open', () => console.log('connected'));
ws.on('message', data => console.log('Message received: %s', data));
}
main();
But when i try to do this within React class's, componentDidMount() function, facing error.
import React from "react";
// == Azure WebPuSub
// import { WebPubSubServiceClient } from '#azure/web-pubsub';
// import { WebSocket } from 'ws';
const { WebPubSubServiceClient } = require('#azure/web-pubsub');
const WebSocket = require('ws');
class AzurePubSubTest extends React.Component {
constructor(_props, _context) {
super(_props, _context);
this.connectToPubSub = this.connectToPubSub.bind(this);
this.state = {
}
}
async componentDidMount() {
console.log("===Mounting....")
await this.connectToPubSub();
}
componentWillUnmount() {
console.log("Unmounting....")
}
async connectToPubSub() {
const hub = "hub1";
let endpoint;
// endpoint = process.env.WebPubSubConnectionString;
endpoint = "Endpoint=https://***check.webpubsub.azure.com;AccessKey=***;Version=1.0;"
// endpoint = "wss://***check.webpubsub.azure.com/client/hubs/Hub?access_token=***";
console.log("process.env.WebPubSubConnectionString");
console.log(endpoint);
let service = new WebPubSubServiceClient(endpoint, hub);
let token = await service.getClientAccessToken();
let ws = new WebSocket(token.url);
ws.on('open', () => console.log('connected'));
ws.on('message', data => console.log('Message received: %s', data));
}
render() {
const user = { username: "Check" };
let testMessages = [];
if (testMessages === undefined || testMessages === null) {
testMessages = [];
}
return (
<div>Testing....</div>
)
}
}
export default AzurePubSubTest;
× Unhandled Rejection (TypeError): Right-hand side of 'instanceof' is
not an object
Stacktrace 1
Stacktrace 2
Stacktrace 3
The issue here is with the Jsonwebtoken package which is used with the websocket.
Jsonwebtoken is predominantly build for NodeJS to be run on a web server so it doesn't fully work with the client-side rendering of the react apps
try installing the latest version of jsonwebtoken , otherwise the ideal way of working would be with an intermediary between the react app and azure pub sub.
One workaround with this approach would be with azure function with azure web pub sub input/output bindings. and then use a WebSocket in the react app to connect to the azure function.
Here you will need a HTTP trigger with the input bindings of the azure pub sub . This trigger will return the URL which you can use in web sockets of your react app.
function.json (for http trigger) :
{
"bindings":[
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req"
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "webPubSubConnection",
"name": "connection",
"hub": "notification",
"direction": "in"
}
]
}
Here I am sending the message using a time trigger and in a simple HTML file I created a WebSocket the html file which is served using different HTTP trigger. Thus after every interval of time I will get messages
I am getting the event emitter leak after using my code 10 times essentially. I understand the default of event emitter auto sending out a warning in the console. My question is what in this code is directly creating the event listeners? Is it poor coding on my part or is it just how the websockets are stacked onto each other?
I'll explain the code a bit. I have one websocket within another and I figured it would serve the data to a web page essentially flowing from Twitch to a localhost site. However, if I use the keywords more than 10 times, I get the error. I do not understand enough about WebSockets to really understand why my code creates a new listener with each msg.text received so anyone with a bit more understanding please help!
I believe me issue to be similar to this though I am having a hard time conceptualizing my own code here
const { paintballShot } = require('./JavaScript/paintballGunFire');
const { readPin } = require('./JavaScript/readPin');
const ws = require('ws');
const express = require('express');
const app = express();
//CONNECT TO TWITCH
let client = new ChatClient({
connection: {
type: "websocket",
secure: true,
}
});
//connected?
client.on("ready", () => console.log("Successfully connected to chat"));
client.on("close", (error) => {
if (error != null) {
console.error("Client closed due to error", error);
}
});
//create headless websocket
const wsServer = new ws.Server({ noServer: true });
wsServer.on('connection', function connection(socket) {
//call other websocket connected to Twitch from inside the new websocket
client.on("PRIVMSG", (msg, error) => {
if (msg.messageText === "right") {
socket.send(JSON.stringify(`${msg.displayName}: ${msg.messageText}`));
}
if (msg.messageText === "left") {
socket.send(JSON.stringify(`${msg.displayName}: ${msg.messageText}`));
}
if (msg.messageText === "fire") {
socket.send(JSON.stringify(`${msg.displayName}: ${msg.messageText}`));
paintballShot();
}
if (msg.messageText === "pin") {
readPin();
}
process.on('uncaughtException', function (err) {
console.log(err);
});
});
client.connect();
client.join("channel");
socket.on('message', message => console.log(message));
});
// `server` is a vanilla Node.js HTTP server
const server = app.listen(3000);
server.on('upgrade', (request, socket, head) => {
wsServer.handleUpgrade(request, socket, head, socket => {
wsServer.emit('connection', socket, request);
});
});
process.on('uncaughtException', function (err) {
console.log(err);
});
To wrap this up, the library I am using (Dank TwitchIRC) does have a connection rate limiter that seems to work if you add it to your chat client in the beginning. If I set it low enough, depending on the messages received from Twitch, it will end connections just as fast, meaning no memory leak.
I'm a beginner in vue and created my first application. I use vuex and I have a plugin to manage a webSocket to the server. Everything works fine as long as I only dealt with messages sent from the server to the browser.
I now would like to add a function to send messages through the socket if connected, but I'm not able to access the exported function. I'm also a beginner in Javascript programming.
Here is the plugin code:
var store = null;
var ws = null;
function startWebsocket() {
ws = new WebSocket(process.env.VUE_APP_WEBSOCKET_URL)
ws.onmessage = function (event) {
console.log("webSocket: on message: ", event.data);
store.dispatch('remoteMessage', event.data);
}
ws.onopen = function (event) {
console.log("webSocket: on open: ", event)
store.dispatch('connectionOpened');
}
ws.onclose = function (event) {
console.log("webSocket: on close: ", event)
store.dispatch('connectionClosed');
ws = null
setTimeout(startWebsocket, 5000)
}
ws.onerror = function (event) {
console.log("webSocket: on error: ", event)
}
}
export default function createWebSocketPlugin() {
return store_param => {
store = store_param;
startWebsocket();
};
}
I would like to add the following function to the plugin so that I can call it from a vuex action function.
export function sendWebSocketMsg(msg) {
if (ws) {
ws.sendMsg(msg)
}
}
In the vuex index.js file I have this:
. . .
import webSocket from '../plugins/webSocket'
. . .
export default new Vuex.Store({
. . .
actions: {
connectionOpened({ commit }) {
commit('SET_CONNECTION', true);
},
connectionClosed({ commit }) {
commit('SET_CONNECTION', false);
},
connectionError({ commit }, error) {
commit('SET_ERROR', error);
},
remoteMessage({commit}, message) {
commit('SET_MESSAGE', message);
},
pause() {
sendWebSocketMsg('{"pause":true}')
},
play() {
sendWebSocketMsg('{"pause":false}')
}
}
}
The webSocket works well and reconnects automatically.
The only thing that I'm missing is the ability to send a webSocket message.
How do I have to modify the webSocket plugin ?
I answer my question since I found the solution. It is partly given in the tutorial I followed.
I wasn't aware of it, but the plugin is a vuex plugin.
The solution is to subscribe to a vuex method. I added the empty method SEND_MESSAGE to the vuex mutations.
mutations: {
SET_ERROR(state, errStr) {
state.error = errStr;
},
SET_CONNECTION(state, status) {
state.connected = status;
},
SET_MESSAGE(state, message) {
let msg = JSON.parse(message);
. . .
},
SEND_MESSAGE() {
},
},
I also added the application specific actions:
pause({commit}) {
commit('SEND_MESSAGE', '{"pause":true}');
},
play({commit}) {
commit('SEND_MESSAGE', '{"pause":false}');
},
I call the store actions from my components like this:
methods: {
pause() {
this.$store.dispatch("pause");
},
play() {
this.$store.dispatch("play");
}
},
The only change left to do is in the plugin. I subscribe a method to call to the SEND_MESSAGE mutation. This is how it is done:
export default function createWebSocketPlugin() {
return store_param => {
store = store_param;
startWebsocket();
store.subscribe((mutation, state) => {
if (state.connected && mutation.type === 'SEND_MESSAGE' && ws) {
console.log("webSocket send "+mutation.payload);
ws.send(mutation.payload);
}
});
};
}
I added the store.subscribe instruction. We only perform the operation when the mutation is of the right type and the web socket is connected.
ws variable is local to the module it was defined, this requires to modify plugin module in order for a function to access ws, e.g.:
export function sendWebSocketMsg(msg) {
if (ws) {
ws.sendMsg(msg)
}
}
export default function createWebSocketPlugin() {...}
Then named export can be imported in module where it's used:
import webSocket, {sendWebSocketMsg} from '../plugins/webSocket'
I'm new to react native moved from ReactJS I thought I can use same packages as my previous pure Reactjs app but I was wrong.
What I'm trying to do is to make a websocket connection.
I'm recently using autobahnJS package WAMP2 in my ReactJS app but when I moved to react native it seems autobahnJS doesn't support react-native
connectToSocketFunction = () =>{ // autobahn code
let connection = new autobahn.Connection({ url: 'wss://api.example.com/websocket/', realm: 'Realm1', authmethods: ['jwt'] });
connection.onopen = (session, detalis) => {
session.subscribe('ChannelName', (data)=>console.log(data));
};
Anyone know how does react native make socket connection based on my code?
I have tried react-native-autobahnjs doesn't work
The React Native documentation mentions support for WebSocket connections:
var ws = new WebSocket('ws://host.com/path');
ws.onopen = () => {
// connection opened
ws.send('something'); // send a message
};
ws.onmessage = (e) => {
// a message was received
console.log(e.data);
};
ws.onerror = (e) => {
// an error occurred
console.log(e.message);
};
ws.onclose = (e) => {
// connection closed
console.log(e.code, e.reason);
};
https://facebook.github.io/react-native/docs/network.html#websocket-support
You will most likely need to use other React Native based frameworks to fill in the gaps that Autobahn provided, e.g. session support and JWT authentication.
I have used create-react-app to scaffold the initial react application.
My DashBoard component:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import $ from 'jquery';
import 'signalr';
class Dashboard extends Component {
constructor(props) {
super(props);
var connection = $.hubConnection('http://[address]:[port]');
var proxy = connection.createHubProxy('[hubname]');
// atempt connection, and handle errors
connection.start()
.done(function(){ console.log('Now connected, connection ID=' + connection.id); })
.fail(function(){ console.log('Could not connect'); });
}
render() {
return (...);
}
}
export default Dashboard;
Now I get the below error from SignalR saying jQuery is not added, but I have imported it in the line above:
Error: jQuery was not found. Please ensure jQuery is referenced before
the SignalR client JavaScript file.
If I comment out import "signalr"; jQuery gets loaded correctly and i can access the $ inside the module. Why does this happen?
This is how we do it now (year 2020) with the new package #microsoft/signalr.
We use Redux, but you don't have to use Redux to be able to utilize this method.
If you are using #microsoft/signalr package instead of #aspnet/signalr, then this is how you can set it up. This is our working code in prod:
import {
JsonHubProtocol,
HubConnectionState,
HubConnectionBuilder,
LogLevel
} from '#microsoft/signalr';
const isDev = process.env.NODE_ENV === 'development';
const startSignalRConnection = async connection => {
try {
await connection.start();
console.assert(connection.state === HubConnectionState.Connected);
console.log('SignalR connection established');
} catch (err) {
console.assert(connection.state === HubConnectionState.Disconnected);
console.error('SignalR Connection Error: ', err);
setTimeout(() => startSignalRConnection(connection), 5000);
}
};
// Set up a SignalR connection to the specified hub URL, and actionEventMap.
// actionEventMap should be an object mapping event names, to eventHandlers that will
// be dispatched with the message body.
export const setupSignalRConnection = (connectionHub, actionEventMap = {}, getAccessToken) => (dispatch, getState) => {
const options = {
logMessageContent: isDev,
logger: isDev ? LogLevel.Warning : LogLevel.Error,
accessTokenFactory: () => getAccessToken(getState())
};
// create the connection instance
// withAutomaticReconnect will automatically try to reconnect
// and generate a new socket connection if needed
const connection = new HubConnectionBuilder()
.withUrl(connectionHub, options)
.withAutomaticReconnect()
.withHubProtocol(new JsonHubProtocol())
.configureLogging(LogLevel.Information)
.build();
// Note: to keep the connection open the serverTimeout should be
// larger than the KeepAlive value that is set on the server
// keepAliveIntervalInMilliseconds default is 15000 and we are using default
// serverTimeoutInMilliseconds default is 30000 and we are using 60000 set below
connection.serverTimeoutInMilliseconds = 60000;
// re-establish the connection if connection dropped
connection.onclose(error => {
console.assert(connection.state === HubConnectionState.Disconnected);
console.log('Connection closed due to error. Try refreshing this page to restart the connection', error);
});
connection.onreconnecting(error => {
console.assert(connection.state === HubConnectionState.Reconnecting);
console.log('Connection lost due to error. Reconnecting.', error);
});
connection.onreconnected(connectionId => {
console.assert(connection.state === HubConnectionState.Connected);
console.log('Connection reestablished. Connected with connectionId', connectionId);
});
startSignalRConnection(connection);
connection.on('OnEvent', res => {
const eventHandler = actionEventMap[res.eventType];
eventHandler && dispatch(eventHandler(res));
});
return connection;
};
Then you would call like the following. Please note that this a pseudo code. You may have to call it differently depending on your project setup.
import { setupSignalRConnection } from 'fileAbove.js';
const connectionHub = '/hub/service/url/events';
export const setupEventsHub = setupSignalRConnection(connectionHub, {
onMessageEvent: someMethod
}, getAccessToken);
export default () => dispatch => {
dispatch(setupEventsHub); // dispatch is coming from Redux
};
Let me know if it helped by up-voting. Thank you
UPDATE: Please note that if you are using Redux in your ReactJS app, the solution below is not necessarily the best solution. It is better to implement signalR as a middleware. You can find the best answer here.
If you are not using Redux, or you still want to implement it in a React component, then read on:
For people that are using the latest version of signalR (core v2.1), since jQuery is not a dependency of signalR any more, you can import it like:
import * as signalR from '#aspnet/signalr';
NOTE: there is now a newer version of signalr available (#microsoft/signalr) that requires a different setup. This solution only works with #aspnet/signalr. (UPDATE June 2020)
And then use it like:
signalR.HubConnectionBuilder()
Here is an example:
import React, { PureComponent } from 'react';
import { string } from 'prop-types';
import * as signalR from '#aspnet/signalr';
class SignalR extends PureComponent {
constructor (props) {
super(props);
this.connection = null;
this.onNotifReceived = this.onNotifReceived.bind(this);
}
componentDidMount () {
const protocol = new signalR.JsonHubProtocol();
const transport = signalR.HttpTransportType.WebSockets;
const options = {
transport,
logMessageContent: true,
logger: signalR.LogLevel.Trace,
accessTokenFactory: () => this.props.accessToken,
};
// create the connection instance
this.connection = new signalR.HubConnectionBuilder()
.withUrl(this.props.connectionHub, options)
.withHubProtocol(protocol)
.build();
this.connection.on('DatabaseOperation', this.onNotifReceived);
this.connection.on('DownloadSession', this.onNotifReceived);
this.connection.on('UploadSession', this.onNotifReceived);
this.connection.start()
.then(() => console.info('SignalR Connected'))
.catch(err => console.error('SignalR Connection Error: ', err));
}
componentWillUnmount () {
this.connection.stop();
}
onNotifReceived (res) {
console.info('Yayyyyy, I just received a notification!!!', res);
}
render () {
return <span />;
};
};
SignalR.propTypes = {
connectionHub: string.isRequired,
accessToken: string.isRequired
};
export default SignalR;
UPDATE: in 2020, you can use "withAutomaticReconnect()":
const connection = new HubConnectionBuilder()
.withUrl(connectionHub, options)
.withAutomaticReconnect()
.withHubProtocol(new JsonHubProtocol())
.configureLogging(LogLevel.Information)
.build();
What I figured out Signalr has dependency on jQuery. For some reason import $ from 'jquery' doesn't set window.jQuery. That's why need to do it explicitly.
I solved the issue this way:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import $ from 'jquery';
window.jQuery = $;
require('signalr');
class Dashboard extends Component {
// .....
}
export default Dashboard;
Check out SignalR no jQuery
npm i -D signalr-no-jquery
import { hubConnection } from 'signalr-no-jquery';
const connection = hubConnection('http://[address]:[port]', options);
const hubProxy = connection.createHubProxy('hubNameString');
// set up event listeners i.e. for incoming "message" event
hubProxy.on('message', function(message) {
console.log(message);
});
// connect
connection.start({ jsonp: true })
.done(function(){ console.log('Now connected, connection ID=' + connection.id); })
.fail(function(){ console.log('Could not connect'); });
https://www.npmjs.com/package/signalr-no-jquery