I recently had to find out a way to have two distinct socket connections each one pointing to a different endpoint and handle both in the frontend. So, depending on whether the user is accessing public or private content we would use a different connection. This approach enabled us to have the flexibility of scaling the microservices separately whenever needed without impacting one another.

With Quasar framework, is fairly simple to implement it, instead of putting in our main.js in a traditional Vuejs app, we need to create a boot file to initialize the socket instance.

quasar new boot socketio

Then, in the quasar.conf file, under boot property, we add our new boot file to the array:

module.exports = function(ctx) {
  return {
    // app boot file (/src/boot)
    // --> boot files are part of "main.js"
    boot: [
      'socketio',
    ]
  // more stuff here
  }
 }

in our new socketio.js boot file, we need to create two SocketIO instances and bind then to the Vue.prototype object.

We were already using socket.io to handle the authenticated connection, so we only needed to add another one and bind it to a property in Vue.prototype.

import socketIo from 'socket.io-client';


const privateSocket = socketIo(process.env.WSENDPOINT);
const publicSocket = socketIo(process.env.WSPUBLICENDPOINT);

export default async ({ Vue, store }) => {
  Vue.prototype.$socket = privateSocket;
  Vue.prototype.$public = publicSocket;
};

Check socket.io for options you can pass as parameters. In our case, we wanted to control when each socket should be connected. For public pages, the public socket should connect, but soon as you are logged in and authenticated the private connection should take the lead.

Because we are adding the instances in the Vue.prototype object, all our Vue components will have access to both connections.

So we can just make the connection in the respective component, for instance:

export default {
  name: 'App',
  created() {
	this.$public.connect();
  }
};

Handling socket messages in Vuex mutations and actions

One important thing for us was the possibility of catching all socket events via Vuex mutations and in some cases in Vuex actions.

With this, we would just have to manage all the events in one single place and update the data directly in the store.


To implement this, we need to bind a listener to all events that come from the backend and from that trigger a commit or dispatch using our store object that quasar provides in our boot file.

So, in our boot file we add a register function that listens to all socket events and calls a onEvent function

function register(socket, store, mutationPrefix, actionPrefix) {
  socket.onevent = (packet) => {
    let [event, ...args] = packet.data;
    if (args.length === 1) args = args[0];
    onEvent(event, args, store, mutationPrefix, actionPrefix);
  };
}

To distinguish between socket handlers and normal mutations and actions is interesting to add a prefix for the functions so we know which ones are being triggered by the socket event. So for this example, we will be using PUBLIC_ as a mutation prefix and public_ as an action prefix for the public socket and PRIVATE_ and private_ respectively for the private socket.

In our onEvent, we make use of these parameters:

function onEvent(event, args, store, mutationPrefix, actionPrefix) {
  let prefixed_event = (mutationPrefix + event).toUpperCase();
  if (store._mutations) {
    for (const key of store._mutations) {
      // in case you are using namespaced vuex modules, takes the module name out
      const mutation = key.split('/').pop();
      if (mutation === prefixed_event) {
        console.log(`Commiting Mutation: ${key}, Data:`, args);
        store.commit(key, args);
      }
    }
  }
  prefixed_event = actionPrefix + event;
  if (store._actions) {
    for (const key of store._actions) {
      // in case you are using namespaced vuex modules, takes the module name out
      const action = key.split('/').pop();
      if (action === prefixed_event) {
        console.log(`Dispatching Action: ${key}, Data:`, args);
        store.dispatch(key, args);
      }
    }
  }
}

Now, we need to bind the events to the socket instances. The only thing we need to do is call our register function for each socket passing the parameters for mutationPrefix and actionPrefix

register(privateSocket, store, 'PRIVATE_', 'private_');
register(publicSocket, store, 'PUBLIC_', 'public_');

Our final socketio.js boot file will look like:

import socketIo from 'socket.io-client';


const privateSocket = socketIo(process.env.WSENDPOINT);
const publicSocket = socketIo(process.env.WSPUBLICENDPOINT);

export default async ({ Vue, store }) => {
  register(privateSocket, store, 'PRIVATE_', 'private_');
  register(publicSocket, store, 'PUBLIC_', 'public_');

  Vue.prototype.$socket = privateSocket;
  Vue.prototype.$public = publicSocket;
};

function register(socket, store, mutationPrefix, actionPrefix) {
  socket.onevent = (packet) => {
    let [event, ...args] = packet.data;
    if (args.length === 1) args = args[0];
    onEvent(event, args, store, mutationPrefix, actionPrefix);
  };
}

function onEvent(event, args, store, mutationPrefix, actionPrefix) {
  let prefixed_event = (mutationPrefix + event).toUpperCase();
  if (store._mutations) {
    for (const key of store._mutations) {
      const mutation = key.split('/').pop();
      if (mutation === prefixed_event) {
        console.log(`Commiting Mutation: ${key}, Data:`, args);
        store.commit(key, args);
      }
    }
  }
  prefixed_event = actionPrefix + event;
  if (store._actions) {
    for (const key of store._actions) {
      const action = key.split('/').pop();
      if (action === prefixed_event) {
        console.log(`Dispatching Action: ${key}, Data:`, args);
        store.dispatch(key, args);
      }
    }
  }
}

Just remembering that, regardless if you are using a framework or pure Vuejs app, you can implement it in your main.js file. What quasar does is just creating the main.js file behind the scenes so we can have a more structured way of dealing with third parties libraries in our code.

Now we just need to create our handlers in our mutation files:

const mutations = {
 return {
   PUBLIC_SOMEEVENT(state, data) {
     state.publicData = data;
   },
   PRIVATE_ANOTHEREVENT(state, data) {
     state.privateData = data;
   }
 };

export default mutations;

If you are new to Vuex, take a look at Vuex amazing documentation at Vuex Docs

I hope this post can help you achieve the same results. If you have implemented something similar, please let me know in the comments how you did it.