2
0
mirror of https://github.com/tenrok/vue-native-websocket.git synced 2026-05-17 05:09:39 +03:00

native websocket with json parsing, vuex integration and namespacing

This commit is contained in:
nathan
2017-05-16 22:58:01 -07:00
parent d44a480584
commit ace980d539
6 changed files with 227 additions and 192 deletions
+23
View File
@@ -0,0 +1,23 @@
// http://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
env: {
browser: true,
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: 'standard',
// add your custom rules here
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
}
+74 -46
View File
@@ -1,36 +1,35 @@
# Vue-Socket.io
# vue-native-websocket
[![NPM version](https://img.shields.io/npm/v/vue-socket.io.svg)](https://www.npmjs.com/package/vue-socket.io)
![VueJS v2 compatible](https://img.shields.io/badge/Vuejs%202-compatible-green.svg)
<a href="https://www.npmjs.com/package/vue-socket.io"><img src="https://img.shields.io/npm/dt/vue-socket.io.svg" alt="Downloads"></a>
<img id="dependency_badge" src="https://www.versioneye.com/javascript/metinseylan:vue-socket.io/2.0.1/badge.svg" alt="Dependency Badge" rel="nofollow">
<a href="https://www.npmjs.com/package/vue-socket.io"><img src="https://img.shields.io/npm/l/vue-socket.io.svg" alt="License"></a>
socket.io implementation for Vuejs 2 and Vuex
native websocket implementation for Vuejs 2 and Vuex
## Install
``` bash
npm install vue-socket.io --save
yarn add vue-native-websocket
# or
npm install vue-native-websocket --save
```
## Usage
#### Configuration
Automatic socket connection from an URL string
``` js
import VueSocketio from 'vue-socket.io';
Vue.use(VueSocketio, 'http://socketserver.com:1923');
import VueNativeSock from 'vue-native-websocket'
Vue.use(VueNativeSock, 'ws://localhost:9090')
```
Bind custom socket.io-client instance
Enable Vuex integration, where `'./store'` is your local apps store:
``` js
Vue.use(VueSocketio, socketio('http://socketserver.com:1923'));
import store from './store'
Vue.use(VueNativeSock, 'ws://localhost:9090', store)
```
Enable Vuex integration
Optionally enable JSON message passing:
``` js
import store from './yourstore'
Vue.use(VueSocketio, socketio('http://socketserver.com:1923'), store);
import store from './store'
Vue.use(VueNativeSock, 'ws://localhost:9090', store, {format: 'json'})
```
#### On Vuejs instance usage
@@ -67,11 +66,25 @@ delete this.$options.sockets.event_name;
#### Vuex Store integration
Socket **mutations** always have `SOCKET_` prefix.
Vuex integration works differently depending on if you've enabled a format
Socket **actions** always have `socket_` prefix and the socket event name is `camelCased` (ex. `SOCKET_USER_MESSAGE` => `socket_userMessage`)
##### Without a format enabled
You can use either one or another or both in your store. Namespaced modules are supported.
Socket events will commit mutations on the root store corresponding to the following events
`SOCKET_ONOPEN`
`SOCKET_ONCLOSE`
`SOCKET_ONERROR`
`SOCKET_ONMESSAGE`
Each callback is passed the raw websocket event object
Update state in the open, close and error callbacks. You can also check the socket state directly with the `this.$socket` object on the main Vue object.
Handle all the data in the `SOCKET_ONMESSAGE` mutation.
``` js
import Vue from 'vue'
@@ -80,35 +93,50 @@ import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
connect: false,
message: null
},
mutations:{
SOCKET_CONNECT: (state, status ) => {
state.connect = true;
},
SOCKET_USER_MESSAGE: (state, message) => {
state.message = message;
}
},
actions: {
otherAction: (context, type) => {
return true;
},
socket_userMessage: (context, message) => {
context.dispatch('newMessage', message);
context.commit('NEW_MESSAGE_RECEIVED', message);
if (message.is_important) {
context.dispatch('alertImportantMessage', message);
}
...
}
state: {
socket: {
isConnected: false,
message: '',
}
},
mutations:{
SOCKET_ONOPEN (state, event) {
state.socket.isConnected = true
},
SOCKET_ONCLOSE (state, event) {
state.socket.isConnected = false
},
SOCKET_ONERROR (state, event) {
console.error(state, event)
},
// default handler called for all methods
SOCKET_ONMESSAGE (event, message) {
state.message = message
}
}
})
```
## Example
[Realtime Car Tracker System](http://metinseylan.com/)
##### With `format: 'json'` enabled
[Simple Chat App](http://metinseylan.com/vuesocketio/)
All data passed through the websocket is expected to be JSON.
Each message is `JSON.parse`d if there is a data (content) response.
If there is no data, the fallback `SOCKET_ON*` mutation is called with the original event data, as above.
If there is a `.namespace` on the data, the message is sent to this `namespaced: true` store (be sure to turn this on in the store module).
If there is a `.mutation` value in the response data, the corresponding mutation is called with the name `SOCKET_[mutation value]`
If there is an `.action` value in the response data, the corresponding action is called with the name `SOCKET_[action value]`
Use the `.sendObj({some: data})` method on the `$socket` object to send stringified json messages.
## Examples
TODO: post your example here!
## Credits
Derived from https://github.com/MetinSeylan/Vue-Socket.io
+20 -17
View File
@@ -1,40 +1,43 @@
{
"name": "vue-socket.io",
"version": "2.1.1-a",
"description": "socket.io implemantation for vuejs and vuex",
"name": "vue-native-websocket",
"version": "1.0.0",
"description": "native websocket implemantation for vuejs and vuex",
"main": "dist/build.js",
"scripts": {
"build": "webpack --progress --hide-modules"
"build": "eslint --ext .js src && webpack --progress --hide-modules",
"lint": "eslint --ext .js src"
},
"repository": {
"type": "git",
"url": "git+https://github.com/MetinSeylan/Vue-Socket.io.git"
"url": "git+https://github.com/nathantsoi/vue-native-websocket.git"
},
"keywords": [
"vuejs",
"socket",
"vue",
"socket.io",
"websocket",
"socket.io-client",
"realtime",
"flux",
"vuex",
"redux"
],
"author": "Metin Seylan",
"author": "Nathan Tsoi",
"license": "MIT",
"bugs": {
"url": "https://github.com/MetinSeylan/Vue-Socket.io/issues"
},
"homepage": "https://github.com/MetinSeylan/Vue-Socket.io#readme",
"dependencies": {
"socket.io-client": "^1.4.6"
"url": "https://github.com/nathantsoi/vue-native-websocket/issues"
},
"homepage": "https://github.com/nathantsoi/vue-native-websocket#readme",
"devDependencies": {
"babel-cli": "^6.11.4",
"babel-loader": "^6.2.5",
"babel-preset-es2015": "^6.3.13",
"webpack": "^2.2.0-rc.3"
"babel-cli": "^6.24.1",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.0.0",
"babel-preset-es2015": "^6.24.1",
"eslint": "^3.19.0",
"eslint-config-standard": "^10.2.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-node": "^4.2.2",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1",
"webpack": "^2.5.1"
}
}
+39 -39
View File
@@ -1,49 +1,49 @@
export default new class {
constructor() {
this.listeners = new Map();
class Emitter {
constructor () {
this.listeners = new Map()
}
addListener (label, callback, vm) {
if (typeof callback === 'function') {
this.listeners.has(label) || this.listeners.set(label, [])
this.listeners.get(label).push({callback: callback, vm: vm})
return true
}
return false
}
addListener(label, callback, vm) {
if(typeof callback == 'function'){
this.listeners.has(label) || this.listeners.set(label, []);
this.listeners.get(label).push({callback: callback, vm: vm});
removeListener (label, callback, vm) {
let listeners = this.listeners.get(label)
let index
return true
if (listeners && listeners.length) {
index = listeners.reduce((i, listener, index) => {
if (typeof listener.callback === 'function' && listener.callback === callback && listener.vm === vm) {
i = index
}
return i
}, -1)
return false
if (index > -1) {
listeners.splice(index, 1)
this.listeners.set(label, listeners)
return true
}
}
return false
}
removeListener(label, callback, vm) {
let listeners = this.listeners.get(label),
index;
emit (label, ...args) {
let listeners = this.listeners.get(label)
if (listeners && listeners.length) {
index = listeners.reduce((i, listener, index) => {
return (typeof listener.callback == 'function' && listener.callback === callback && listener.vm == vm) ?
i = index :
i;
}, -1);
if (index > -1) {
listeners.splice(index, 1);
this.listeners.set(label, listeners);
return true;
}
}
return false;
if (listeners && listeners.length) {
listeners.forEach((listener) => {
listener.callback.call(listener.vm, ...args)
})
return true
}
return false
}
}
emit(label, ...args) {
let listeners = this.listeners.get(label);
if (listeners && listeners.length) {
listeners.forEach((listener) => {
listener.callback.call(listener.vm,...args)
});
return true;
}
return false;
}
}
export default new Emitter()
+34 -39
View File
@@ -3,50 +3,45 @@ import Emitter from './Emitter'
export default {
install(Vue, connection, store){
install (Vue, connection, store, opts = {}) {
if (!connection) { throw new Error('[vue-native-socket] cannot locate connection') }
if(!connection) throw new Error("[Vue-Socket.io] cannot locate connection")
let observer = new Observer(connection, store, opts)
let observer = new Observer(connection, store)
Vue.prototype.$socket = observer.WebSocket
Vue.prototype.$socket = observer.Socket;
Vue.mixin({
created () {
let sockets = this.$options['sockets']
Vue.mixin({
created(){
let sockets = this.$options['sockets']
this.$options.sockets = new Proxy({}, {
set: (target, key, value) => {
Emitter.addListener(key, value, this)
target[key] = value
return true;
},
deleteProperty: (target, key) => {
Emitter.removeListener(key, this.$options.sockets[key], this)
delete target.key;
return true
}
})
if(sockets){
Object.keys(sockets).forEach((key) => {
this.$options.sockets[key] = sockets[key];
});
}
},
beforeDestroy(){
let sockets = this.$options['sockets']
if(sockets){
Object.keys(sockets).forEach((key) => {
delete this.$options.sockets[key]
});
}
}
this.$options.sockets = new Proxy({}, {
set (target, key, value) {
Emitter.addListener(key, value, this)
target[key] = value
return true
},
deleteProperty (target, key) {
Emitter.removeListener(key, this.$options.sockets[key], this)
delete target.key
return true
}
})
}
if (sockets) {
Object.keys(sockets).forEach((key) => {
this.$options.sockets[key] = sockets[key]
})
}
},
beforeDestroy () {
let sockets = this.$options['sockets']
if (sockets) {
Object.keys(sockets).forEach((key) => {
delete this.$options.sockets[key]
})
}
}
})
}
}
+37 -51
View File
@@ -1,59 +1,45 @@
import Emitter from './Emitter'
import Socket from 'socket.io-client'
export default class{
constructor(connection, store) {
if(typeof connection == 'string'){
this.Socket = Socket(connection);
}else{
this.Socket = connection
}
if(store) this.store = store;
this.onEvent()
export default class {
constructor (connectionUrl, store, opts = {}) {
this.format = opts.format && opts.format.toLowerCase()
this.connect(connectionUrl)
if (store) { this.store = store }
this.onEvent()
}
connect (connectionUrl) {
this.WebSocket = new WebSocket(connectionUrl)
if (this.format === 'json') {
if (!('sendObj' in this.WebSocket)) {
this.WebSocket.sendObj = (obj) => this.WebSocket.send(JSON.stringify(obj))
}
}
}
onEvent(){
this.Socket.onevent = (packet) => {
Emitter.emit(packet.data[0], packet.data[1]);
onEvent () {
['onmessage', 'onclose', 'onerror', 'onopen'].forEach((eventType) => {
this.WebSocket[eventType] = (event) => {
Emitter.emit(eventType, event)
if (this.store) { this.passToStore('SOCKET_' + eventType, event) }
}
})
}
if(this.store) this.passToStore('SOCKET_'+packet.data[0], [ ...packet.data.slice(1)])
};
let _this = this;
["connect", "error", "disconnect", "reconnect", "reconnect_attempt", "reconnecting", "reconnect_error", "reconnect_failed", "connect_error", "connect_timeout", "connecting", "ping", "pong"]
.forEach((value) => {
_this.Socket.on(value, (data) => {
Emitter.emit(value, data);
if(_this.store) _this.passToStore('SOCKET_'+value, data)
})
})
}
passToStore(event, payload){
if(!event.startsWith('SOCKET_')) return
for(let namespaced in this.store._mutations) {
let mutation = namespaced.split('/').pop()
if(mutation === event.toUpperCase()) this.store.commit(namespaced, payload)
}
for(let namespaced in this.store._actions) {
let action = namespaced.split('/').pop()
if(!action.startsWith('socket_')) continue
let camelcased = 'socket_'+event
.replace('SOCKET_', '')
.replace(/^([A-Z])|[\W\s_]+(\w)/g, (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase())
if(action === camelcased) this.store.dispatch(namespaced, payload)
}
passToStore (eventName, event) {
if (!eventName.startsWith('SOCKET_')) { return }
if (this.format === 'json' && event.data) {
let msg = JSON.parse(event.data)
let target = msg.namespace || ''
if (msg.mutation) {
this.store.commit([target, msg.mutation].join('/'), msg)
}
if (msg.action) {
this.store.dispatch([target, msg.action].join('/'), msg)
}
} else {
// default mutation
this.store.commit(eventName.toUpperCase(), event)
}
}
}