VueSocketIO offer fallback connection url - vue.js

I am using Vuetify together with Vuex and VueSocketIO for my WebApp and here is an example Part of the code:
Vue.use(new VueSocketIO({
reconnection: true,
debug: true,
connection: SocketIO(`http://${process.env.ip}:4000`),
vuex: {
store,
actionPrefix: 'SOCKET_',
},
}));
If I understand it correctly, using Vuex and VueSocketIO together makes it only possible to use one Socket like this at the same time.
In some cases Vue might not be able to connect to the socket specified at connection.
I was wondering if there was a possibility to first let Vue try to connect to one socket (also with some number of reconnection attempts) but to switch to another connection value and try with that one afterwards as a fallback?
Thank you in advance!
Final solution
const options = {
reconnection: true,
reconnectionAttempts: 2,
reconnectionDelay: 10,
reconnectionDelayMax: 1,
timeout: 300,
};
let connection = new SocketIO(`http://${process.env.ip}:4000`, options);
const instance = new VueSocketIO({
debug: true,
connection,
vuex: {
store,
actionPrefix: 'SOCKET_',
},
options,
});
const options2 = {
reconnection: true,
reconnectionAttempts: 4,
};
connection.on('reconnect_failed', () => {
connection = new SocketIO(`http://${process.env.fallback}:4000`, options2);
instance.listener.io = connection;
instance.listener.register();
Vue.prototype.$socket = connection;
});

To specify the number of reconnection attempts you can set reconnectionAttempts option.
Example Code:
const url = `http://${process.env.ip}:4000`
const options = {
reconnectionAttempts: 3
}
Vue.use(new VueSocketIO({
debug: true,
connection: new SocketIO(url, options),
vuex: { ... }
}))
But switching to another connection is not easy as both of vue-socket.io and socket.io-client it was not designed for that.
First we have to listen on reconnect_failed event which will fires when reconnection attempts is exceeded.
Then we have to create a new connection to connect to the fallback url.
The VueSocketIO instance have two important properties which emitter and listener we cannot create the new emitter since it might already used in some components (with subscribe function) so we have to use old emitter but new listener.
Unfortunately, we cannot import Listener class directly from vue-socket.io package. So we have to use old listener but change the io property to the new connection then manually call register method.
Binding Vue.prototype.$socket to the new connection for the future use.
Example Code:
const url = `http://${process.env.ip}:4000`
const fallbackUrl = `http://${process.env.ip}:4001`
const options = {
reconnectionAttempts: 3
}
const connection = new SocketIO(url, options)
const instance = new VueSocketIO({
debug: true,
connection,
vuex: {
store,
actionPrefix: 'SOCKET_'
},
options
})
connection.on('reconnect_failed', error => {
const connection = new SocketIO(fallbackUrl, options)
instance.listener.io = connection
instance.listener.register()
Vue.prototype.$socket = connection;
})
Vue.use(instance)

Related

vue-socket.io fail to trigger event when url route is present

I have a Python Flask backend that listens at port 8000 (at '/') and keeps emitting digest event after receiving a connection. At the Vue frontend, I setup the connection as
Vue.use(
new VueSocketIO({
debug: false,
connection: "http://localhost:8000",
})
);
and in a component, I have
sockets: {
connect() {
console.log('connected');
},
afterConnect(data) {
console.log(data);
},
// get digest from backend
digest(data) {
console.log(data)
},
}
The digest function here will be triggered correctly.
However, when I change the setup to
Vue.use(
new VueSocketIO({
debug: false,
connection: "http://localhost:8000/abc", // add whatever route here
})
);
I can still receive correct data packets through the socket, but digest function is simply not triggered. The abc in the route can be anything, it won't break the connection.
Any help is appreciated! Thanks!

Vue-Socket Mutation Listener

I recently changed over to connecting to a namespace on the socket.
Previously the connection was working fine and my Vuex mutations were catching the emission from the server.
After switching to the namespace I have verified that the connection is being made and the correct event is firing on the socket, but my Vuex mutation is no longer firing.
Here is the applicable code:
Server Side
const complianceNamespace = IO.of("/compliance");
complianceNamespace.on("connection", function (socket) {
socket.on("UPDATE_REQUEST_SERVICE", function (requestService) {
IO.emit("UPDATE_REQUEST_SERVICE", requestService);
});
socket.on("ADD_REQUEST_SERVICE", function (requestService) {
IO.emit("ADD_REQUEST_SERVICE", requestService)
});
});
Client Side
const SocketInstance = process.env.NODE_ENV === 'development' ? socketio('http://localhost:3001/compliance') : socketio('https://real.address.com/compliance');
Vue.use(new VueSocketIO({
debug: false,
connection: SocketInstance,
vuex: {
store,
actionPrefix: 'SOCKET_',
mutationPrefix: 'SOCKET_'
}
}));
// Mutation
SOCKET_UPDATE_REQUEST_SERVICE(state, requestService) {
// some code runs
}
Again, I have verified that the socket is getting to UPDATE_REQUEST_SERVICE, but the mutation is never being run when the socket emits, it worked when not using namespace.
I am thinking that the prefix is altered or doesn't work when using a namespace?
Answer: mistake within the namespace connection event listeners.
I was setting up a connection with the namespace (complianceNamespace) then emitting events to a different connection (IO).
Correction code:
complianceNamespace.on("connection", (socket) => {
socket.on("SOME_EVENT", function(user) {
complianceNamespace.emit("SOME_EVENT", user);
}
});

Where do I add this websocket code in the new nuxt.js setup since it does not have server?

I am using the new version of Nuxt which does not come with a server folder
In the old version, you had a server folder and an index.js which contained the app
I want to add the websocket WS library with the new version of NuxtJS
The library requires an instance of server which you create by calling http.createServer(app) meaning an instance of the running express app. You would use this server instance now to listen to 3000 in the index.js file. Also create a sessionHandler which you obtain by calling session({}) with the express-session library
const WebSocket = require('ws')
function websocket({ server, sessionHandler, logger }) {
// https://github.com/websockets/ws/blob/master/examples/express-session-parse/index.js, if you pass a server instance here 'upgrade' handler will crash
const wss = new WebSocket.Server({ noServer: true })
function noop() {}
function heartbeat() {
this.isAlive = true
}
wss.on('connection', (ws, request, client) => {
ws.isAlive = true
ws.on('pong', heartbeat)
ws.on('message', (msg) => {
logger.info(`Received message ${msg} from user ${client}`)
ws.send(true)
})
})
server.on('upgrade', (request, socket, head) => {
sessionHandler(request, {}, () => {
logger.info(`${JSON.stringify(request.session)} WEBSOCKET SESSION PARSED`)
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request)
})
})
})
// TODO use a setTimeout here instead of a setInterval
setInterval(function ping() {
// wss.clients => Set
wss.clients.forEach(function each(ws) {
if (ws.isAlive === false) return ws.terminate()
ws.isAlive = false
ws.ping(noop)
})
}, 30000)
return wss
}
module.exports = websocket
Does anyone know how I can make this work on the new Nuxt setup without the server folder
You can create a custom module and use nuxt hooks to get a server instance on listen event.
Create modules/ws.js:
const WebSocket = require('ws')
const wss = new WebSocket.Server({ noServer: true })
wss.on('connection', ws => {
ws.on('message', message => {
console.log('received: %s', message);
})
ws.send('Hello')
})
export default function () {
this.nuxt.hook('listen', server => {
server.on('upgrade', (request, socket, head) => {
wss.handleUpgrade(request, socket, head, ws => {
wss.emit('connection', ws);
})
})
})
}
And register the module in nuxt.config.js:
export default {
modules: [
'~/modules/ws'
]
}
In your case you could create a module directory instead of a single file to store multiple related files.
Create modules/ws/index.js
const websocket = require('./websocket') // this is your file
export default function () {
this.nuxt.hook('listen', server => {
websocket({
server,
sessionHandler (request, _, cb) { // example
cb()
},
logger: console // example
})
})
}
Then copy your file to modules/ws/websocket.js. You can use module.exports which is CommonJS format or change it into ES Module format, Nuxt(Webpack) can handle that.
In your code I notice that ws.send(true) cause an error TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received type boolean (true) which basically mean you cannot send boolean.

Where should be the location for coturn or ice setting for sipjs 0.11.0?

I am moving from sipjs 0.7x to sipjs 0.11
After reading the Git issue https://github.com/onsip/SIP.js/pull/426#issuecomment-312065734
and
https://sipjs.com/api/0.8.0/sessionDescriptionHandler/
I have found that the ice options (coturn, turn, stun) is not in User Agent anymore,
but the problem is that I am not quite understand where should I use the
setDescription(sessionDescription, options, modifiers)
I have seen that the ice is set in options, using
options.peerConnectionOptions.rtcConfiguration.iceServers
below is what I haved tried
session.on('trackAdded', function () {
// We need to check the peer connection to determine which track was added
var modifierArray = [
SIP.WebRTC.Modifiers.stripTcpCandidates,
SIP.WebRTC.Modifiers.stripG722,
SIP.WebRTC.Modifiers.stripTelephoneEvent
];
var options = {
peerConnectionOptions:{
rtcConfiguration:{
iceServers : {
[{urls: 'turn:35.227.67.199:3478',
username: 'leon',
credential: 'leon_pass'}]
}
}
}
}
session.setDescription('trackAdded', options,modifierArray);
var pc = session.sessionDescriptionHandler.peerConnection;
// Gets remote tracks
var remoteStream = new MediaStream();
pc.getReceivers().forEach(function (receiver) {
remoteStream.addTrack(receiver.track);
});
remoteAudio.srcObject = remoteStream;
remoteAudio.play();
// Gets local tracks
// var localStream = new MediaStream();
// pc.getSenders().forEach(function(sender) {
// localStream.addTrack(sender.track);
// });
// localVideo.srcObject = localStream;
// localVideo.play();
});
}
I have tried this and it seems that the traffic is not going to the coturn server.
I have used Trickle Ice "https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/" to test and it is fine, but I have found there is not traffic going through the coturn server. You could also use this one and I do not mind.
There is even no demo on the official website to show how could we use the setDescription(sessionDescription, options, modifiers). In this case can I please ask some recommendations?
Configure STUN/TURN servers in the parameters passed to new UserAgent.
Here is sample, it seems to be working on v0.17.1:
const userAgentOptions = {
...
sessionDescriptionHandlerFactoryOptions: {
peerConnectionConfiguration: {
iceServers: [{
urls: "stun:stun.l.google.com:19302"
}, {
urls: "turn:TURN_SERVER_HOST:PORT",
username: "USERNAME",
credential: "PASSWORD"
}]
},
},
...
};
const userAgent = new SIP.UserAgent(userAgentOptions);
When using SimpleUser - pass it inside SimpleUserOptions:
const simpleUser = new Web.SimpleUser(url, { userAgentOptions })

Nuxt Ava End-to-End Testing Store Configuration

Given the example official Nuxt end-to-end test example using Ava:
import test from 'ava'
import { Nuxt, Builder } from 'nuxt'
import { resolve } from 'path'
// We keep a reference to Nuxt so we can close
// the server at the end of the test
let nuxt = null
// Init Nuxt.js and start listening on localhost:4000
test.before('Init Nuxt.js', async t => {
const rootDir = resolve(__dirname, '..')
let config = {}
try { config = require(resolve(rootDir, 'nuxt.config.js')) } catch (e) {}
config.rootDir = rootDir // project folder
config.dev = false // production build
config.mode = 'universal' // Isomorphic application
nuxt = new Nuxt(config)
await new Builder(nuxt).build()
nuxt.listen(4000, 'localhost')
})
// Example of testing only generated html
test('Route / exits and render HTML', async t => {
let context = {}
const { html } = await nuxt.renderRoute('/', context)
t.true(html.includes('<h1 class="red">Hello world!</h1>'))
})
// Close the Nuxt server
test.after('Closing server', t => {
nuxt.close()
})
How can you use Nuxt or Builder to configure/access the applications Vuex store? The example Vuex store would look like:
import Vuex from "vuex";
const createStore = () => {
return new Vuex.Store({
state: () => ({
todo: null
}),
mutations: {
receiveTodo(state, todo) {
state.todo = todo;
}
},
actions: {
async nuxtServerInit({ commit }, { app }) {
console.log(app);
const todo = await app.$axios.$get(
"https://jsonplaceholder.typicode.com/todos/1"
);
commit("receiveTodo", todo);
}
}
});
};
export default createStore;
Currently trying to run the provided Ava test, leads to an error attempting to access #nuxtjs/axios method $get:
TypeError {
message: 'Cannot read property \'$get\' of undefined',
}
I'd be able to mock $get and even $axios available on app in Vuex store method nuxtServerInit, I just need to understand how to access app in the test configuration.
Thank you for any help you can provide.
Just encountered this and after digging so many tutorial, I pieced together a solution.
You have essentially import your vuex store into Nuxt when using it programmatically. This is done by:
Importing Nuxt's config file
Adding to the config to turn off everything else but enable store
Load the Nuxt instance and continue your tests
Here's a working code (assuming your ava and dependencies are set up)
// For more info on why this works, check this aweomse guide by this post in getting this working
// https://medium.com/#brandonaaskov/how-to-test-nuxt-stores-with-jest-9a5d55d54b28
import test from 'ava'
import jsdom from 'jsdom'
import { Nuxt, Builder } from 'nuxt'
import nuxtConfig from '../nuxt.config' // your nuxt.config
// these boolean switches turn off the build for all but the store
const resetConfig = {
loading: false,
loadingIndicator: false,
fetch: {
client: false,
server: false
},
features: {
store: true,
layouts: false,
meta: false,
middleware: false,
transitions: false,
deprecations: false,
validate: false,
asyncData: false,
fetch: false,
clientOnline: false,
clientPrefetch: false,
clientUseUrl: false,
componentAliases: false,
componentClientOnly: false
},
build: {
indicator: false,
terser: false
}
}
// We keep a reference to Nuxt so we can close
// the server at the end of the test
let nuxt = null
// Init Nuxt.js and start listening on localhost:5000 BEFORE running your tests. We are combining our config file with our resetConfig using Object.assign into an empty object {}
test.before('Init Nuxt.js', async (t) => {
t.timeout(600000)
const config = Object.assign({}, nuxtConfig, resetConfig, {
srcDir: nuxtConfig.srcDir, // don't worry if its not in your nuxt.config file. it has a default
ignore: ['**/components/**/*', '**/layouts/**/*', '**/pages/**/*']
})
nuxt = new Nuxt(config)
await new Builder(nuxt).build()
nuxt.listen(5000, 'localhost')
})
// Then run our tests using the nuxt we defined initially
test.serial('Route / exists and renders correct HTML', async (t) => {
t.timeout(600000) // Sometimes nuxt's response is slow. We increase the timeont to give it time to render
const context = {}
const { html } = await nuxt.renderRoute('/', context)
t.true(html.includes('preload'))
// t.true(true)
})
test.serial('Route / exits and renders title', async (t) => {
t.timeout(600000)
const { html } = await nuxt.renderRoute('/', {})
const { JSDOM } = jsdom // this was the only way i could get JSDOM to work. normal import threw a functione error
const { document } = (new JSDOM(html)).window
t.true(document.title !== null && document.title !== undefined) // simple test to check if site has a title
})
Doing this should work. HOWEVER, You may still get some errors
✖ Timed out while running tests. If you get this you're mostly out of luck. I thought the problem was with Ava given that it didn't give a descriptive error (and removing any Nuxt method seemed to fix it), but so far even with the above snippet sometimes it works and sometimes it doesn't.
My best guess at this time is that there is a delay on Nuxt's side using either renderRouter or renderAndGetWindow that ava doesn't wait for, but on trying any of these methods ava almost immediately "times out" despite the t.timeout being explicitly set for each test. So far my research has lead me to checking the timeout for renderAndGetWindow (if it exists, but the docs doesn't indicate such).
That's all i've got.