feat(): Multiple namespaced connections

pull/288/head
Sherzod Aripjanov 4 years ago
parent 2f63eebb70
commit 35b3591b45

@ -89,8 +89,8 @@ new Vue({
debug|Boolean|`false`|Optional|Enable logging for debug debug|Boolean|`false`|Optional|Enable logging for debug
connection|String/Socket.io-client|`null`|Required|Websocket server url or socket.io-client instance connection|String/Socket.io-client|`null`|Required|Websocket server url or socket.io-client instance
vuex.store|Vuex|`null`|Optional|Vuex store instance vuex.store|Vuex|`null`|Optional|Vuex store instance
vuex.actionPrefix|String|`null`|Optional|Prefix for emitting server side vuex actions vuex.actionPrefix|String/Function|`null`|Optional|Prefix for emitting server side vuex actions
vuex.mutationPrefix|String |`null`|Optional|Prefix for emitting server side vuex mutations vuex.mutationPrefix|String/Function|`null`|Optional|Prefix for emitting server side vuex mutations
#### 🌈 Component Level Usage #### 🌈 Component Level Usage
@ -180,6 +180,109 @@ export default new Vuex.Store({
}) })
``` ```
#### 🏆 Connection Namespace
``` javascript
import Vue from 'vue'
import store from './store'
import App from './App.vue'
import VueSocketIO from 'vue-socket.io'
const app = SocketIO('http://localhost:1090', {
useConnectionNamespace: true,
});
const chat = SocketIO('http://localhost:1090/chat', {
useConnectionNamespace: true,
autoConnect: false,
});
Vue.use(new VueSocketIO({
debug: true,
connection: {
app,
chat
},
vuex: {
store,
actionPrefix: eventName => {
return (`SOCKET_` + eventName).toUpperCase();
},
mutationPrefix: eventName => {
return (`SOCKET_` + eventName).toUpperCase();
},
},
options: { path: "/my-app/" } //Optional options
}))
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
```
Then use it like this:
``` javascript
new Vue({
sockets: {
app: {
connect: function() {
console.log('socket connected');
},
customEmit: function(data) {
console.log('this method was fired by the socket server');
},
},
chat: {
connect: function() {
console.log('socket connected');
},
customEmit: function(data) {
console.log('this method was fired by the socket server');
},
},
},
methods: {
clickAppButton: function(data) {
// $socket.app is socket.io-client instance
this.$socket.app.emit('emit_method', data);
},
clickChatButton: function(data) {
// $socket.chat is socket.io-client instance
this.$socket.chat.emit('emit_method', data);
},
},
});
```
vuex
``` javascript
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},
mutations: {
"<MUTATION_PREFIX>_<MY_NAMESPACE>_<EVENT_NAME>"() {
// do something
}
},
actions: {
"<ACTION_PREFIX>_<MY_NAMESPACE>_<EVENT_NAME>"() {
// do something
}
}
})
```
## Stargazers over time ## Stargazers over time
[![Stargazers over time](https://starcharts.herokuapp.com/MetinSeylan/Vue-Socket.io.svg)](https://starcharts.herokuapp.com/MetinSeylan/Vue-Socket.io) [![Stargazers over time](https://starcharts.herokuapp.com/MetinSeylan/Vue-Socket.io.svg)](https://starcharts.herokuapp.com/MetinSeylan/Vue-Socket.io)

File diff suppressed because one or more lines are too long

14
index.d.ts vendored

@ -40,18 +40,24 @@ declare module 'vue/types/options' {
} }
} }
type prefixFunc = (eventName: string) => string;
type connectionSocket = {
[key: string]: SocketIOClient.Socket
}
export interface VueSocketOptions { export interface VueSocketOptions {
debug?: boolean; debug?: boolean;
connection: string | SocketIOClient.Socket, connection: string | SocketIOClient.Socket | connectionSocket,
vuex?: { vuex?: {
store?: Store<any>, store?: Store<any>,
actionPrefix?: string, actionPrefix?: string | prefixFunc,
mutationPrefix?: string, mutationPrefix?: string | prefixFunc,
options?: { options?: {
useConnectionNamespace?: boolean useConnectionNamespace?: boolean
} }
}, },
// type declarations for optional options // type declarations for optional options
options?:{ options?:{
path?: string; path?: string;
} }

@ -1,136 +1,113 @@
import Logger from './logger'; import Logger from './logger';
export default class EventEmitter{ export default class EventEmitter {
constructor(vuex = {}) {
constructor(vuex = {}){ Logger.info(vuex ? `Vuex adapter enabled` : `Vuex adapter disabled`);
Logger.info(vuex ? `Vuex adapter enabled` : `Vuex adapter disabled`); Logger.info(vuex.mutationPrefix ? `Vuex socket mutations enabled` : `Vuex socket mutations disabled`);
Logger.info(vuex.mutationPrefix ? `Vuex socket mutations enabled` : `Vuex socket mutations disabled`); Logger.info(vuex ? `Vuex socket actions enabled` : `Vuex socket actions disabled`);
Logger.info(vuex ? `Vuex socket actions enabled` : `Vuex socket actions disabled`); this.store = vuex.store;
this.store = vuex.store; this.actionPrefix = vuex.actionPrefix ? vuex.actionPrefix : 'SOCKET_';
this.actionPrefix = vuex.actionPrefix ? vuex.actionPrefix : 'SOCKET_'; this.mutationPrefix = vuex.mutationPrefix ? vuex.mutationPrefix : 'SOCKET_';
this.mutationPrefix = vuex.mutationPrefix; this.listeners = new Map();
this.listeners = new Map(); }
/**
* register new event listener with vuejs component instance
* @param event
* @param callback
* @param component
*/
addListener(event, callback, component) {
if (typeof callback === 'function') {
if (!this.listeners.has(event)) this.listeners.set(event, []);
this.listeners.get(event).push({ callback, component });
Logger.info(`#${event} subscribe, component: ${component.$options.name}`);
} else {
throw new Error(`callback must be a function`);
} }
}
/**
* register new event listener with vuejs component instance /**
* @param event * remove a listenler
* @param callback * @param event
* @param component * @param component
*/ */
addListener(event, callback, component){ removeListener(event, component) {
if (this.listeners.has(event)) {
if(typeof callback === 'function'){ const listeners = this.listeners.get(event).filter(listener => listener.component !== component);
if (!this.listeners.has(event)) this.listeners.set(event, []); if (listeners.length > 0) {
this.listeners.get(event).push({ callback, component }); this.listeners.set(event, listeners);
} else {
Logger.info(`#${event} subscribe, component: ${component.$options.name}`); this.listeners.delete(event);
}
} else {
Logger.info(`#${event} unsubscribe, component: ${component.$options.name}`);
throw new Error(`callback must be a function`);
}
} }
}
/**
* remove a listenler /**
* @param event * broadcast incoming event to components
* @param component * @param event
*/ * @param args
removeListener(event, component){ */
emit(event, args) {
if(this.listeners.has(event)){ if (this.listeners.has(event)) {
Logger.info(`Broadcasting: #${event}, Data:`, args);
const listeners = this.listeners.get(event).filter(listener => (
listener.component !== component this.listeners.get(event).forEach(listener => {
)); listener.callback.call(listener.component, args);
});
if (listeners.length > 0) {
this.listeners.set(event, listeners);
} else {
this.listeners.delete(event);
}
Logger.info(`#${event} unsubscribe, component: ${component.$options.name}`);
}
} }
/** if (event !== 'ping' && event !== 'pong') {
* broadcast incoming event to components this.dispatchStore(event, args);
* @param event }
* @param args }
*/
emit(event, args){ /**
* dispatching vuex actions
if(this.listeners.has(event)){ * @param event
* @param args
Logger.info(`Broadcasting: #${event}, Data:`, args); */
dispatchStore(event, args) {
this.listeners.get(event).forEach((listener) => { if (this.store && this.store._actions) {
listener.callback.call(listener.component, args); let prefixedEvent;
}); if (typeof this.actionPrefix === 'function') {
prefixedEvent = this.actionPrefix(event);
} } else {
prefixedEvent = this.actionPrefix + event;
if(event !== 'ping' && event !== 'pong') { }
this.dispatchStore(event, args);
for (let key in this.store._actions) {
let action = key.split('/').pop();
if (action === prefixedEvent) {
Logger.info(`Dispatching Action: ${key}, Data:`, args);
this.store.dispatch(key, args);
} }
}
} }
if (this.store && this.store._mutations) {
let prefixedEvent;
if (typeof this.mutationPrefix === 'function') {
prefixedEvent = this.mutationPrefix(event);
} else {
prefixedEvent = (this.mutationPrefix || '') + event;
}
/** for (let key in this.store._mutations) {
* dispatching vuex actions let mutation = key.split('/').pop();
* @param event
* @param args
*/
dispatchStore(event, args){
if(this.store && this.store._actions){
let prefixed_event = this.actionPrefix + event;
for (let key in this.store._actions) {
let action = key.split('/').pop();
if(action === prefixed_event) {
Logger.info(`Dispatching Action: ${key}, Data:`, args);
this.store.dispatch(key, args);
}
}
if(this.mutationPrefix) { if (mutation === prefixedEvent) {
Logger.info(`Commiting Mutation: ${key}, Data:`, args);
let prefixed_event = this.mutationPrefix + event;
for (let key in this.store._mutations) {
let mutation = key.split('/').pop();
if(mutation === prefixed_event) {
Logger.info(`Commiting Mutation: ${key}, Data:`, args);
this.store.commit(key, args);
}
}
}
this.store.commit(key, args);
} }
}
} }
}
} }

@ -5,63 +5,49 @@ import Emitter from './emitter';
import SocketIO from 'socket.io-client'; import SocketIO from 'socket.io-client';
export default class VueSocketIO { export default class VueSocketIO {
/**
/** * lets take all resource
* lets take all resource * @param io
* @param io * @param vuex
* @param vuex * @param debug
* @param debug * @param options
* @param options */
*/ constructor({ connection, vuex, debug, options }) {
constructor({connection, vuex, debug, options}){ Logger.debug = debug;
this.io = this.connect(connection, options);
Logger.debug = debug; this.emitter = new Emitter(vuex);
this.io = this.connect(connection, options); this.listener = new Listener(this.io, this.emitter);
this.emitter = new Emitter(vuex); }
this.listener = new Listener(this.io, this.emitter);
/**
} * Vue.js entry point
* @param Vue
/** */
* Vue.js entry point install(Vue) {
* @param Vue Vue.prototype.$socket = this.io;
*/ Vue.prototype.$vueSocketIo = this;
install(Vue){
Vue.mixin(Mixin);
Vue.prototype.$socket = this.io;
Vue.prototype.$vueSocketIo = this; Logger.info('Vue-Socket.io plugin enabled');
Vue.mixin(Mixin); }
Logger.info('Vue-Socket.io plugin enabled'); /**
* registering SocketIO instance
} * @param connection
* @param options
*/
/** connect(connection, options) {
* registering SocketIO instance if (connection && typeof connection === 'object') {
* @param connection Logger.info('Received socket.io-client instance');
* @param options
*/ return connection;
connect(connection, options){ } else if (typeof connection === 'string') {
Logger.info('Received connection string');
if(connection && typeof connection === 'object'){
return (this.io = SocketIO(connection, options));
Logger.info('Received socket.io-client instance'); } else {
throw new Error('Unsupported connection type');
return connection;
} else if(typeof connection === 'string'){
Logger.info('Received connection string');
return this.io = SocketIO(connection, options);
} else {
throw new Error('Unsupported connection type');
}
} }
}
} }

@ -1,27 +1,25 @@
export default class VueSocketIOListener { export default class VueSocketIOListener {
/** /**
* socket.io-client reserved event keywords * socket.io-client reserved event keywords
* @type {string[]} * @type {string[]}
*/ */
static staticEvents = [ static staticEvents = [
'connect', 'connect',
'error', 'error',
'disconnect', 'disconnect',
'reconnect', 'reconnect',
'reconnect_attempt', 'reconnect_attempt',
'reconnecting', 'reconnecting',
'reconnect_error', 'reconnect_error',
'reconnect_failed', 'reconnect_failed',
'connect_error', 'connect_error',
'connect_timeout', 'connect_timeout',
'connecting', 'connecting',
'ping', 'ping',
'pong' 'pong',
]; ];
constructor(io, emitter){ constructor(io, emitter) {
this.io = io; this.io = io;
this.register(); this.register();
this.emitter = emitter; this.emitter = emitter;
@ -30,22 +28,39 @@ export default class VueSocketIOListener {
/** /**
* Listening all socket.io events * Listening all socket.io events
*/ */
register(){ register() {
this.io.onevent = (packet) => { if (this.io.constructor.name === 'Object') {
Object.keys(this.io).forEach(namespace => {
this.registerEvents(this.io[namespace], namespace);
});
} else {
this.registerEvents(this.io);
}
}
registerEvents(io, namespace) {
io.onevent = packet => {
let [event, ...args] = packet.data; let [event, ...args] = packet.data;
if(args.length === 1) args = args[0]; if (args.length === 1) args = args[0];
this.onEvent(event, args) this.onEvent(this.getEventName(event, namespace), args);
}; };
VueSocketIOListener.staticEvents.forEach(event => this.io.on(event, args => this.onEvent(event, args)))
VueSocketIOListener.staticEvents.forEach(event =>
io.on(event, args => this.onEvent(this.getEventName(event, namespace), args)),
);
}
getEventName(event, namespace) {
return namespace ? namespace + '_' + event : event;
} }
/** /**
* Broadcast all events to vuejs environment * Broadcast all events to vuejs environment
*/ */
onEvent(event, args){ onEvent(event, args) {
console.log('onEvent', event);
this.emitter.emit(event, args); this.emitter.emit(event, args);
} }
} }

@ -1,35 +1,27 @@
/** /**
* shitty logger class * shitty logger class
*/ */
export default new class VueSocketIOLogger { export default new (class VueSocketIOLogger {
constructor() {
constructor() { this.debug = false;
this.debug = false; this.prefix = '%cVue-Socket.io: ';
this.prefix = '%cVue-Socket.io: '; }
}
info(text, data = '') {
info(text, data = '') { if (this.debug)
window.console.info(this.prefix + `%c${text}`, 'color: blue; font-weight: 600', 'color: #333333', data);
if(this.debug) window.console.info(this.prefix+`%c${text}`, 'color: blue; font-weight: 600', 'color: #333333', data); }
} error() {
if (this.debug) window.console.error(this.prefix, ...arguments);
error() { }
if(this.debug) window.console.error(this.prefix, ...arguments); warn() {
if (this.debug) window.console.warn(this.prefix, ...arguments);
} }
warn() { event(text, data = '') {
if (this.debug)
if(this.debug) window.console.warn(this.prefix, ...arguments); window.console.info(this.prefix + `%c${text}`, 'color: blue; font-weight: 600', 'color: #333333', data);
}
} })();
event(text, data = ''){
if(this.debug) window.console.info(this.prefix+`%c${text}`, 'color: blue; font-weight: 600', 'color: #333333', data);
}
}

@ -1,56 +1,64 @@
export default { export default {
/**
/** * Assign runtime callbacks
* Assign runtime callbacks */
*/ beforeCreate() {
beforeCreate(){ if (!this.sockets) this.sockets = {};
if(!this.sockets) this.sockets = {}; this.sockets.subscribe = (event, callback) => {
this.$vueSocketIo.emitter.addListener(event, callback, this);
this.sockets.subscribe = (event, callback) => { };
this.$vueSocketIo.emitter.addListener(event, callback, this);
}; this.sockets.unsubscribe = event => {
this.$vueSocketIo.emitter.removeListener(event, this);
this.sockets.unsubscribe = (event) => { };
this.$vueSocketIo.emitter.removeListener(event, this); },
};
/**
}, * Register all socket events
*/
/** mounted() {
* Register all socket events const { sockets } = this.$options;
*/ const { emitter } = this.$vueSocketIo;
mounted(){
if (sockets) {
if(this.$options.sockets){ Object.keys(sockets).forEach(eventOrNamespace => {
const functionOrObject = sockets[eventOrNamespace];
Object.keys(this.$options.sockets).forEach(event => { if (
typeof functionOrObject === 'function' &&
if(event !== 'subscribe' && event !== 'unsubscribe') { eventOrNamespace !== 'subscribe' &&
this.$vueSocketIo.emitter.addListener(event, this.$options.sockets[event], this); eventOrNamespace !== 'unsubscribe'
} ) {
emitter.addListener(eventOrNamespace, functionOrObject, this);
}); } else {
Object.keys(functionOrObject).forEach(event => {
if (event !== 'subscribe' && event !== 'unsubscribe') {
emitter.addListener(`${eventOrNamespace}_${event}`, functionOrObject[event], this);
}
});
} }
});
}, }
},
/**
* unsubscribe when component unmounting /**
*/ * unsubscribe when component unmounting
beforeDestroy(){ */
beforeDestroy() {
if(this.$options.sockets){ const { sockets } = this.$options;
const { emitter } = this.$vueSocketIo;
Object.keys(this.$options.sockets).forEach(event => {
if (sockets) {
this.$vueSocketIo.emitter.removeListener(event, this); Object.keys(sockets).forEach(eventOrNamespace => {
const functionOrObject = sockets[eventOrNamespace];
}); if (typeof functionOrObject === 'function') {
emitter.removeListener(eventOrNamespace, functionOrObject, this);
} else {
Object.keys(functionOrObject).forEach(event => {
emitter.removeListener(event, functionOrObject[event], this);
});
} }
});
} }
},
} };

Loading…
Cancel
Save