2
0
mirror of https://github.com/tenrok/vue-native-websocket.git synced 2026-06-23 02:00:34 +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) native websocket implementation for Vuejs 2 and Vuex
![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
## Install ## Install
``` bash ``` bash
npm install vue-socket.io --save yarn add vue-native-websocket
# or
npm install vue-native-websocket --save
``` ```
## Usage ## Usage
#### Configuration #### Configuration
Automatic socket connection from an URL string Automatic socket connection from an URL string
``` js ``` js
import VueSocketio from 'vue-socket.io'; import VueNativeSock from 'vue-native-websocket'
Vue.use(VueSocketio, 'http://socketserver.com:1923'); Vue.use(VueNativeSock, 'ws://localhost:9090')
``` ```
Bind custom socket.io-client instance Enable Vuex integration, where `'./store'` is your local apps store:
``` js ``` 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 ``` js
import store from './yourstore' import store from './store'
Vue.use(VueSocketio, socketio('http://socketserver.com:1923'), store); Vue.use(VueNativeSock, 'ws://localhost:9090', store, {format: 'json'})
``` ```
#### On Vuejs instance usage #### On Vuejs instance usage
@@ -67,11 +66,25 @@ delete this.$options.sockets.event_name;
#### Vuex Store integration #### 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 ``` js
import Vue from 'vue' import Vue from 'vue'
@@ -80,35 +93,50 @@ import Vuex from 'vuex'
Vue.use(Vuex); Vue.use(Vuex);
export default new Vuex.Store({ export default new Vuex.Store({
state: { state: {
connect: false, socket: {
message: null isConnected: false,
}, message: '',
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);
}
...
}
} }
},
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 ##### With `format: 'json'` enabled
[Realtime Car Tracker System](http://metinseylan.com/)
[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", "name": "vue-native-websocket",
"version": "2.1.1-a", "version": "1.0.0",
"description": "socket.io implemantation for vuejs and vuex", "description": "native websocket implemantation for vuejs and vuex",
"main": "dist/build.js", "main": "dist/build.js",
"scripts": { "scripts": {
"build": "webpack --progress --hide-modules" "build": "eslint --ext .js src && webpack --progress --hide-modules",
"lint": "eslint --ext .js src"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/MetinSeylan/Vue-Socket.io.git" "url": "git+https://github.com/nathantsoi/vue-native-websocket.git"
}, },
"keywords": [ "keywords": [
"vuejs", "vuejs",
"socket", "socket",
"vue", "vue",
"socket.io",
"websocket", "websocket",
"socket.io-client",
"realtime", "realtime",
"flux", "flux",
"vuex", "vuex",
"redux" "redux"
], ],
"author": "Metin Seylan", "author": "Nathan Tsoi",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {
"url": "https://github.com/MetinSeylan/Vue-Socket.io/issues" "url": "https://github.com/nathantsoi/vue-native-websocket/issues"
},
"homepage": "https://github.com/MetinSeylan/Vue-Socket.io#readme",
"dependencies": {
"socket.io-client": "^1.4.6"
}, },
"homepage": "https://github.com/nathantsoi/vue-native-websocket#readme",
"devDependencies": { "devDependencies": {
"babel-cli": "^6.11.4", "babel-cli": "^6.24.1",
"babel-loader": "^6.2.5", "babel-eslint": "^7.2.3",
"babel-preset-es2015": "^6.3.13", "babel-loader": "^7.0.0",
"webpack": "^2.2.0-rc.3" "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 { class Emitter {
constructor() { constructor () {
this.listeners = new Map(); 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) { removeListener (label, callback, vm) {
if(typeof callback == 'function'){ let listeners = this.listeners.get(label)
this.listeners.has(label) || this.listeners.set(label, []); let index
this.listeners.get(label).push({callback: callback, vm: vm});
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) { emit (label, ...args) {
let listeners = this.listeners.get(label), let listeners = this.listeners.get(label)
index;
if (listeners && listeners.length) { if (listeners && listeners.length) {
index = listeners.reduce((i, listener, index) => { listeners.forEach((listener) => {
return (typeof listener.callback == 'function' && listener.callback === callback && listener.vm == vm) ? listener.callback.call(listener.vm, ...args)
i = index : })
i; return true
}, -1);
if (index > -1) {
listeners.splice(index, 1);
this.listeners.set(label, listeners);
return true;
}
}
return false;
} }
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 { 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({ this.$options.sockets = new Proxy({}, {
created(){ set (target, key, value) {
let sockets = this.$options['sockets'] Emitter.addListener(key, value, this)
target[key] = value
this.$options.sockets = new Proxy({}, { return true
set: (target, key, value) => { },
Emitter.addListener(key, value, this) deleteProperty (target, key) {
target[key] = value Emitter.removeListener(key, this.$options.sockets[key], this)
return true; delete target.key
}, 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]
});
}
}
}) })
} 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 Emitter from './Emitter'
import Socket from 'socket.io-client'
export default class{ export default class {
constructor (connectionUrl, store, opts = {}) {
constructor(connection, store) { this.format = opts.format && opts.format.toLowerCase()
this.connect(connectionUrl)
if(typeof connection == 'string'){ if (store) { this.store = store }
this.Socket = Socket(connection); this.onEvent()
}else{ }
this.Socket = connection
}
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(){ onEvent () {
this.Socket.onevent = (packet) => { ['onmessage', 'onclose', 'onerror', 'onopen'].forEach((eventType) => {
Emitter.emit(packet.data[0], packet.data[1]); 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)]) passToStore (eventName, event) {
}; if (!eventName.startsWith('SOCKET_')) { return }
if (this.format === 'json' && event.data) {
let _this = this; let msg = JSON.parse(event.data)
let target = msg.namespace || ''
["connect", "error", "disconnect", "reconnect", "reconnect_attempt", "reconnecting", "reconnect_error", "reconnect_failed", "connect_error", "connect_timeout", "connecting", "ping", "pong"] if (msg.mutation) {
.forEach((value) => { this.store.commit([target, msg.mutation].join('/'), msg)
_this.Socket.on(value, (data) => { }
Emitter.emit(value, data); if (msg.action) {
if(_this.store) _this.passToStore('SOCKET_'+value, data) this.store.dispatch([target, msg.action].join('/'), msg)
}) }
}) } else {
} // default mutation
this.store.commit(eventName.toUpperCase(), event)
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)
}
} }
}
} }