mikudos-socketio-app
Version:
mikudos-socketio-app for connection and call methods and sync events on mikudos-socketio-app server, which is provided only for socket.io connection.
250 lines (226 loc) • 8.28 kB
text/typescript
import config from 'config';
import socket from 'socket.io';
const redisAdapter = require('socket.io-redis');
import _ from 'lodash';
import { JSON_RPC_HANDLER } from './common/json-rpc/json-rpc-handler';
import { Authentication, AuthenticationRequest } from './authentication.class';
import { CHAT_HANDLER, DUPLEX_HANDLER } from './common';
import { PUSHER_HANDLER } from './common/pusher/pusher';
import { mikudos } from './namespace';
import Debug from 'debug';
const debug = Debug('mikudos:app');
export class Application {
settings: any;
io: socket.Namespace;
json_rpc_services?: JSON_RPC_HANDLER;
chat_services?: CHAT_HANDLER;
rootNamespace?: string;
publishFilter?: (
app: Application,
io: socket.Namespace
) => Promise<string[]>;
authentication?: Authentication;
duplex_services?: DUPLEX_HANDLER;
pusher?: PUSHER_HANDLER;
constructor(
public rootIo: socket.Server,
{
rootNamespace,
redisConfig,
}: {
rootNamespace?: string;
redisConfig?: { host: string; port: number };
} = {}
) {
rootNamespace = rootNamespace || '/';
debug('booting mikudos at rootNamespace %o', rootNamespace);
this.settings = _.merge({}, config);
this.enabled('redisAdaptered') &&
(redisConfig = this.get('redisConfig'));
if (redisConfig) {
debug('redisAdapter activated');
this.enable('redisAdaptered');
rootIo.adapter(redisAdapter(redisConfig));
}
this.rootNamespace = rootNamespace;
this.io = rootIo.of(rootNamespace);
}
init() {
this.socketInit();
}
get(name: string) {
return _.get(this.settings, name);
}
set(name: string, value: any) {
_.set(this.settings, name, value);
return this;
}
disable(name: string) {
_.set(this.settings, name, false);
return this;
}
disabled(name: string) {
return !_.get(this.settings, name);
}
enable(name: string) {
_.set(this.settings, name, true);
return this;
}
enabled(name: string) {
return !!_.get(this.settings, name);
}
configure(fn: mikudos.ConfigFunc): Application {
fn.call(this, this);
return this;
}
socketInit() {
this.io.on('connection', (socket: mikudos.Socket) => {
debug('io connected with socket: %o', socket.id);
socket.mikudos = {
app: this,
provider: 'socketio',
headers: socket.handshake.headers,
remoteAddress: socket.conn.remoteAddress,
user: null,
};
socket.use((reqData: any, next) => {
this.parseRequset(reqData, socket);
next();
});
this.authentication &&
this.authentication.register(socket, (authResult: any) => {
debug('register all authenticated handlers');
this.pusher && this.pusher.register(socket);
this.json_rpc_services &&
this.json_rpc_services.authenticated &&
this.json_rpc_services.register(socket);
this.chat_services &&
this.chat_services.authenticated &&
this.chat_services.register(socket);
this.duplex_services &&
this.duplex_services.authenticated &&
this.duplex_services.register(socket);
});
debug('register all unauthenticated handlers');
this.json_rpc_services &&
!this.json_rpc_services.authenticated &&
this.json_rpc_services.register(socket);
this.chat_services &&
!this.chat_services.authenticated &&
this.chat_services.register(socket);
this.duplex_services &&
!this.duplex_services.authenticated &&
this.duplex_services.register(socket);
socket.once('disconnect', () => {
debug('socket disconnected %o', socket.id);
socket.leaveAll();
debug('all rooms leaved');
if (this.duplex_services) {
debug('canncel processing duplex service');
this.duplex_services.cancelAllOnSocket(socket);
}
/* … */
});
// socket.on('disconnecting', reason => {
// let rooms = Object.keys(socket.rooms);
// // ...
// });
});
}
// use customized publishFilter
async publishEvent(response: any) {
if (!this.publishFilter) return;
debug(
'publishFilter method provided, publish event to all filtered socket'
);
const rooms = await this.publishFilter(this, this.io);
rooms.map((clientRoom) => {
this.io.to(clientRoom).emit('rpc-call event', response);
});
}
parseRequset(request: any, socket: mikudos.Socket) {
if (request.length === 1) return;
if (!request[1].jsonrpc) return;
// if request is jsonrpc request then parse the request
debug('parse request for json-rpc method');
request[1] = _.pick(request[1], ['jsonrpc', 'id', 'method', 'params']);
request[1].socket = socket;
}
/**
* Join the remote Room
* @param socketId
* @param room
*/
async remoteJoin(socketId: string, room: string) {
if (!this.enabled('redisAdaptered')) return;
debug('remoteJoin with redisAdapter');
await new Promise((resolve, reject) => {
(this.io.adapter as any).remoteJoin(socketId, room, (err: any) => {
if (err) reject(err);
resolve();
});
});
}
/**
* Leave the remote room
* @param socketId
* @param room
*/
async remoteLeave(socketId: string, room: string) {
if (!this.enabled('redisAdaptered')) return;
debug('remoteLeave with redisAdapter');
await new Promise((resolve, reject) => {
(this.io.adapter as any).remoteLeave(socketId, room, (err: any) => {
if (err) reject(err);
resolve();
});
});
}
async clientRooms(socket: mikudos.Socket): Promise<string[]> {
if (this.enabled('redisAdaptered')) {
return await new Promise((resolve, reject) => {
(this.io.adapter as any).clientRooms(
socket.id,
(err: any, rooms: string[]) => {
if (err) reject(err);
resolve(rooms); // return an array containing every room socketId has joined.
}
);
});
} else {
return Object.keys(socket.rooms);
}
}
async isIORoomEmpty(room: string) {
return await new Promise((resolve, reject) => {
this.io.in(room).clients((error: Error, clients: string[]) => {
if (error || clients.length == 0) resolve(true);
resolve(false);
});
});
}
async allRooms() {
if (!this.enabled('redisAdaptered')) return;
return await new Promise((resolve, reject) => {
(this.io.adapter as any).allRooms((err: any, rooms: string[]) => {
if (err || !rooms)
reject(err || Error('get no rooms, remote error'));
resolve(rooms);
});
});
}
async remoteDisconnect(socketId: String, close: Boolean = true) {
if (!this.enabled('redisAdaptered')) return;
debug('set socket %o disconnect remote server', socketId);
await new Promise((resolve, reject) => {
(this.io.adapter as any).remoteDisconnect(
socketId,
close,
(err: any) => {
if (err) reject(err);
resolve();
}
);
});
}
}