/vue-socket.io-extended

:v::zap: Socket.io bindings for Vue.js and Vuex (inspired by Vue-Socket.io)

Primary LanguageJavaScriptMIT LicenseMIT

Vue-Socket.io-Extended

Build Status Version Downloads License Vue.js 2.x compatible Minified library size Code coverage (codecov) Join us Gitter

Socket.io bindings for Vue.js and Vuex (inspired by Vue-Socket.io)

🍒 Features

  • Listen and emit socket.io events inside components
  • Dispatch actions and mutations in Vuex store on socket.io events
  • Support namespaced Vuex modules out-of-the-box
  • Listen for one server event from the multiple stores at the same time
  • Support for multiple arguments from the server (when more then one argument passed - payload is wrapped to array automatically)
  • Possibility to define socket.io listeners in components dynamically
  • Options support - tweak the library to better fit your project needs
  • And many other...

✔️ Browser Support

-- Chrome Firefox Safari Opera Edge IE
Basic support * 38+ ✔️ 13+ ✔️ 8+ ✔️ 25+ ✔️ 12+ ✔️ 11+ ✔️
Full support 49+ ✔️ 18+ ✔️ 10+ ✔️ 36+ ✔️ 12+ ✔️

🌱 Motivation

I was using Vue-Socket.io for few months. I've liked the idea, but the more I used it the more I faced with bugs, outdated documentation, lack of support, absence of tests and a huge amount of issues 😞. That slowed down development of the product I was working on. So I ended up with a decision to create my own fork with all the desirable stuff (features/fixes/tests/support/CI checks etc). That's how vue-socket.io-extended was born.

If you'd like to help - create an issue or PR. I will be glad to see any contribution. Let's make the world a better place ❤️

Requirements

💿 Installation

npm install vue-socket.io-extended socket.io-client

🏁 Initialization

ES2015 (Webpack/Rollup/Browserify/Parcel/etc)

import VueSocketio from 'vue-socket.io-extended';
import io from 'socket.io-client';

Vue.use(VueSocketio, io('http://socketserver.com:1923'));

Note: you have to pass instance of socket.io-client as second argument to prevent library duplication. Read more here.

UMD (Browser)

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/socket.io-client/dist/socket.io.slim.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-socket.io-extended"></script>
<script>
  Vue.use(VueSocketIOExt, io('http://socketserver.com:1923'));
</script>

🚀 Usage

On Vue.js component

Define your listeners under sockets section and they will listen coresponding socket.io events automatically.

new Vue({
  sockets: {
    connect() {
      console.log('socket connected')
    },
    customEmit(val) {
      console.log('this method was fired by the socket server. eg: io.emit("customEmit", data)')
    }
  },
  methods: {
    clickButton(val) {
      // this.$socket is `socket.io-client` instance
      this.$socket.emit('emit_method', val);
    }
  }
})

Note: Don't use arrow functions for methods or listeners if you are going to emit socket.io events inside. You will end up with using incorrect this. More info about this here

Dynamic socket event listeners

Create a new listener

this.$options.sockets.event_name = (data) => {
  console.log(data)
}

Remove existing listener

delete this.$options.sockets.event_name;

Note: This feature supported only in browsers with native Proxy API support (e.g. IE11 is not supported)

🌲 Vuex Store integration

To enable Vuex integration just pass the store as the third argument, e.g.:

import store from './store'

Vue.use(VueSocketio, io('http://socketserver.com:1923'), { store });

The main idea behind the integration is that mutations and actions are dispatched/committed automatically on Vuex store when server socket event arrives. Not every mutation and action is invoked. It should follow special formatting convention, so the plugin can easily determine which one should be called.

  • a mutation should start with SOCKET_ prefix and continue with an uppercase version of the event
  • an action should start with socket_ prefix and continue with camelcase version of the event
Server Event Mutation Action
chat message SOCKET_CHAT MESSAGE socket_chatMessage
chat_message SOCKET_CHAT_MESSAGE socket_chatMessage
chatMessage SOCKET_CHATMESSAGE socket_chatMessage
CHAT_MESSAGE SOCKET_CHAT_MESSAGE socket_chatMessage

Check Configuration section if you'd like to use custom transformation.

Note: different server events can commit/dispatch the same mutation or/and the same action. So try to use only one naming convention to avoid possible bugs. In any case, this behavior is going to be changed soon and considered as problematic.

You can use either mutation or action or even both in your store. Don't forget that mutations are synchronous transactions. If you have any async operations inside, it's better to use actions instead. Learn more about Vuex here.

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    isConnected: false,
    message: null,
  },
  mutations: {
    SOCKET_CONNECT(state, status) {
      state.isConnected = true;
    },
    SOCKET_USER_MESSAGE(state, message) {
      state.message = message;
    },
  },
  actions: {
    otherAction(context, type) {
      return true;
    },
    socket_userMessage({ commit, dispatch }, message) {
      dispatch('newMessage', message);
      commit('NEW_MESSAGE_RECEIVED', message);
      if (message.is_important) {
        dispatch('alertImportantMessage', message);
      }
      // ...
    },
  },
})

Namespaced vuex modules

Namespaced modules are supported out-of-the-box when plugin initialized with Vuex store. You can easily divide your store into modules without worrying that mutation or action will not be called. The plugin checks all your modules for mutation and action that are formatted by convention described above and call them all. That means you can listen for the same event from multiple stores with no issue.

Check the following example:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);

const messages = {
  state: {
    messages: []
  },
  mutations: {
    SOCKET_CHAT_MESSAGE(state, message) {
      state.messages.push(message);
    }
  },
  actions: {
    socket_chatMessage() {
      console.log('this action will be called');
    }
  },
};

const notifications = {
  state: {
    notifications: []
  },
  mutations: {
    SOCKET_CHAT_MESSAGE(state, message) {
      state.notifications.push({ type: 'message', payload: message });
    }
  },
}

export default new Vuex.Store({
  modules: {
    messages,
    notifications,
  }
})

That's what will happen, on chat_message from the server:

  • SOCKET_CHAT_MESSAGE mutation commited on messages module
  • SOCKET_CHAT_MESSAGE mutation commited on notification module
  • socket_chatMessage action dispatched on messages module

🚵 Usage with Nuxt.js

The key point here is to disable SSR for the plugin as it will crash otherwise. It's a well-know issue and we are going to fix it. Thanks @ll931217 for investigation.

1. Create plugin:

// ~/plugins/socket.io.js
import Vue from 'vue';
import io from 'socket.io-client';
import VueSocketIO from 'vue-socket.io-extended';

export default ({ store }) => {
  Vue.use(VueSocketIO, io('http://localhost:3000'), { store });
}

2. Then register it:

// nuxt.config.js
module.exports = {
  ...,
  plugins: [
    ...,
    { 
      src: '~/plugins/socket.io.js',
      ssr: false,                    // <-- this line is required
    },
  ]
}

⚙️ Configuration

In addition to store instance, vue-socket.io-extended accepts other options. Here they are:

Option Type Default Description
store Object undefined Vuex store instance, enables vuex integration
actionPrefix String 'socket_' Prepend to event name while converting event to action. Empty string disables prefixing
mutationPrefix String 'SOCKET_' Prepend to event name while converting event to mutation. Empty string disables prefixing
eventToMutationTransformer Function string => string uppercase function Determines how event name converted to mutation
eventToActionTransformer Function string => string camelcase function Determines how event name converted to action

FYI: You can always access default plugin options if you need it (e.g. re-use default eventToActionTransformer function):

import VueSocketIOExt from 'vue-socket.io-extended';
VueSocketIOExt.defaults // -> { actionPrefix: '...', mutationPrefix: '...', ... }

FAQ

Semantic Versioning Policy

This plugin follows semantic versioning.

📰 Changelog

We're using GitHub Releases.

🍻 Contribution

We're more than happy to see potential contributions, so don't hesitate. If you have any suggestions, ideas or problems feel free to add new issue, but first please make sure your question does not repeat previous ones.

🔒 License

See the LICENSE file for license rights and limitations (MIT).

FOSSA Status