4
packages/ws/.gitignore
vendored
Normal file
4
packages/ws/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/node_modules
|
||||
/dist
|
||||
/package-lock.json
|
||||
/yarn.lock
|
||||
22
packages/ws/.npmignore
Normal file
22
packages/ws/.npmignore
Normal file
@@ -0,0 +1,22 @@
|
||||
src
|
||||
test
|
||||
typings
|
||||
bundled
|
||||
build
|
||||
coverage
|
||||
docs
|
||||
wiki
|
||||
gulpfile.js
|
||||
bower.json
|
||||
karma.conf.js
|
||||
tsconfig.json
|
||||
typings.json
|
||||
CONTRIBUTING.md
|
||||
ISSUE_TEMPLATE.md
|
||||
PULL_REQUEST_TEMPLATE.md
|
||||
tslint.json
|
||||
wallaby.js
|
||||
.travis.yml
|
||||
.gitignore
|
||||
.vscode
|
||||
type_definitions
|
||||
11
packages/ws/README.md
Normal file
11
packages/ws/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# `cc-server-ws`
|
||||
|
||||
> TODO: description
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
const ccServerIoc = require('cc-server-ws');
|
||||
|
||||
// TODO: DEMONSTRATE API
|
||||
```
|
||||
33
packages/ws/package.json
Normal file
33
packages/ws/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@cc-server/ws",
|
||||
"version": "0.3.3",
|
||||
"description": "> TODO: description",
|
||||
"author": "MiaoWoo <admin@yumc.pw>",
|
||||
"homepage": "https://faas.yumc.pw",
|
||||
"license": "ISC",
|
||||
"main": "dist/index.js",
|
||||
"publishConfig": {
|
||||
"registry": "https://repo.yumc.pw/repository/npm-hosted/"
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "npx tsc --watch",
|
||||
"build": "rimraf dist && npx tsc",
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/502647092/cc-server-parent.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"inversify": "^5.0.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"socket.io": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/socket.io": "^2.1.2",
|
||||
"mocha": "^6.1.4",
|
||||
"rimraf": "^2.6.3",
|
||||
"typescript": "^3.5.1"
|
||||
},
|
||||
"gitHead": "7d84393a3cb6be6be9ed51d71f12677d2d7d0728"
|
||||
}
|
||||
92
packages/ws/src/builder.ts
Normal file
92
packages/ws/src/builder.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import "reflect-metadata";
|
||||
import { Container } from 'inversify'
|
||||
import { interfaces, BroadcastMessage } from './interfaces'
|
||||
import { TYPE } from './constants'
|
||||
import { getNamespaces, getNamespaceMetadata, getNamespaceListenerMetadata } from './utils'
|
||||
import * as io from 'socket.io'
|
||||
|
||||
export function buildWebSocket(container: Container, server: io.Server) {
|
||||
let constructors = getNamespaces();
|
||||
if (!constructors.length) { return; }
|
||||
|
||||
registryNamespace(container, constructors);
|
||||
|
||||
// get all namespaces
|
||||
let namespaces = container.getAll<interfaces.Namespace>(TYPE.Namespace)
|
||||
for (const namespace of namespaces) {
|
||||
let namespaceMetadata = getNamespaceMetadata(namespace);
|
||||
let namespaceEventMetadata = getNamespaceListenerMetadata(namespace);
|
||||
let ns = server.of(namespaceMetadata.name);
|
||||
namespace.constructor.prototype.nsp = ns;
|
||||
applyNamespaceMiddleware(namespaceMetadata, ns);
|
||||
ns.on('connection', async (socket: io.Socket) => {
|
||||
let namespaceInstance = container.getNamed<interfaces.Namespace>(TYPE.Namespace, namespace.constructor.name);
|
||||
await applyEvent(namespaceInstance, socket);
|
||||
await applyMiddlewares(namespaceEventMetadata, socket);
|
||||
await applyListeners(namespaceEventMetadata, socket, namespaceInstance);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function registryNamespace(container: Container, constructors: any[]) {
|
||||
constructors.forEach((constructor) => {
|
||||
const name = constructor.name;
|
||||
if (container.isBoundNamed(TYPE.Namespace, name)) {
|
||||
throw new Error(`DUPLICATED_NAMESPACE(${name})`);
|
||||
}
|
||||
container.bind(TYPE.Namespace)
|
||||
.to(constructor)
|
||||
.whenTargetNamed(name);
|
||||
});
|
||||
}
|
||||
function applyNamespaceMiddleware(namespaceMetadata: interfaces.NamespaceMetadata, ns: io.Namespace) {
|
||||
for (const middleware of namespaceMetadata.middleware) {
|
||||
ns.use(middleware);
|
||||
}
|
||||
}
|
||||
|
||||
function flatten(arr: Array<any>) {
|
||||
while (arr.some(item => Array.isArray(item))) {
|
||||
arr = [].concat(...arr);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function applyMiddlewares(namespaceEventMetadata: interfaces.ListenerMetadata[], socket: io.Socket) {
|
||||
// socket.use((packet: io.Packet, next: (err?: any) => void) => {
|
||||
// Reflect.defineMetadata(TYPE.SocketContext, socket, packet);
|
||||
// next();
|
||||
// })
|
||||
let middlewares = [...new Set(flatten(namespaceEventMetadata.map((data) => data.middleware)))];
|
||||
for (const middleware of middlewares) {
|
||||
socket.use((packet: io.Packet, next: (err?: any) => void) => { middleware(socket, packet, next); });
|
||||
}
|
||||
}
|
||||
|
||||
async function applyEvent(namespaceInstance: interfaces.Namespace, socket: io.Socket) {
|
||||
if (namespaceInstance.connection) {
|
||||
let result = await namespaceInstance.connection(socket);
|
||||
if (result != undefined) {
|
||||
socket.send(result);
|
||||
}
|
||||
}
|
||||
if (namespaceInstance.disconnect) {
|
||||
socket.on('disconnect', async () => await namespaceInstance.disconnect(socket));
|
||||
}
|
||||
}
|
||||
|
||||
function applyListeners(namespaceEventMetadata: interfaces.ListenerMetadata[], socket: io.Socket, namespaceInstance: interfaces.Namespace) {
|
||||
for (const event of namespaceEventMetadata) {
|
||||
socket.on(event.name, async data => {
|
||||
let result = await namespaceInstance[event.key](socket, data);
|
||||
if (result != undefined) {
|
||||
if (result instanceof BroadcastMessage) {
|
||||
socket.broadcast.emit(event.name, result.message);
|
||||
}
|
||||
else {
|
||||
socket.emit(event.name, result);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
9
packages/ws/src/constants.ts
Normal file
9
packages/ws/src/constants.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const TYPE = {
|
||||
Namespace: Symbol.for('namespace'),
|
||||
SocketContext: Symbol.for('context')
|
||||
}
|
||||
|
||||
export const METADATA_KEY = {
|
||||
namespace: "@cc-server/ws:namespace",
|
||||
listener: "@cc-server/ws:listener"
|
||||
};
|
||||
40
packages/ws/src/decorators.ts
Normal file
40
packages/ws/src/decorators.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { inject, injectable, decorate } from "inversify";
|
||||
import { interfaces } from './interfaces'
|
||||
import { METADATA_KEY } from './constants'
|
||||
import { getNamespaceListenerMetadata, getNamespacesMetadata } from './utils'
|
||||
|
||||
/**
|
||||
* Socket.io Namespace
|
||||
* @param name namespace name default is '/'
|
||||
* @param middleware middleware array
|
||||
*/
|
||||
export function namespace(name?: string, ...middleware: interfaces.Middleware[]) {
|
||||
return function(target: any) {
|
||||
let currentMetadata: interfaces.NamespaceMetadata = {
|
||||
name: name || '/',
|
||||
middleware: middleware,
|
||||
target: target
|
||||
};
|
||||
decorate(injectable(), target);
|
||||
Reflect.defineMetadata(METADATA_KEY.namespace, currentMetadata, target);
|
||||
const previousMetadata: interfaces.NamespaceMetadata[] = getNamespacesMetadata();
|
||||
Reflect.defineMetadata(METADATA_KEY.namespace, [currentMetadata, ...previousMetadata], Reflect);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Socket.io listner
|
||||
* @param name event name
|
||||
*/
|
||||
export function listener(name?: string, ...middleware: interfaces.ListenerMiddleware[]) {
|
||||
return function(target: any, key: string, value: any) {
|
||||
let currentMetadata: interfaces.ListenerMetadata = {
|
||||
name: name || key,
|
||||
middleware: middleware,
|
||||
key: key,
|
||||
target: target
|
||||
};
|
||||
const previousMetadata: interfaces.ListenerMetadata[] = getNamespaceListenerMetadata(target)
|
||||
Reflect.defineMetadata(METADATA_KEY.listener, [currentMetadata, ...previousMetadata], target.constructor);
|
||||
};
|
||||
}
|
||||
7
packages/ws/src/index.ts
Normal file
7
packages/ws/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import * as io from 'socket.io'
|
||||
|
||||
export * from './builder'
|
||||
export * from './decorators'
|
||||
export * from './interfaces'
|
||||
export { getSocketContext } from './utils'
|
||||
export { io }
|
||||
64
packages/ws/src/interfaces.ts
Normal file
64
packages/ws/src/interfaces.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import * as io from 'socket.io'
|
||||
import { injectable } from 'inversify';
|
||||
|
||||
export class Message {
|
||||
constructor(public message: any) { }
|
||||
}
|
||||
export class BroadcastMessage {
|
||||
constructor(public message: any) { }
|
||||
}
|
||||
|
||||
export namespace interfaces {
|
||||
@injectable()
|
||||
export class Namespace {
|
||||
/**
|
||||
* @see io.Namespace
|
||||
*/
|
||||
public nsp?: io.Namespace;
|
||||
/**
|
||||
* The event fired when we get a new connection
|
||||
* @param socket socket
|
||||
* @return return data will send use socket.send(data)
|
||||
*/
|
||||
public connection?(socket: io.Socket): any;
|
||||
/**
|
||||
* The event fired when socket is close
|
||||
* @param socket socket
|
||||
*/
|
||||
public disconnect?(socket: io.Socket): void;
|
||||
/**
|
||||
* broadcast message on this namespace
|
||||
*/
|
||||
public broadcast(message: any): BroadcastMessage {
|
||||
return new BroadcastMessage(message);
|
||||
}
|
||||
/**
|
||||
* Event Listener
|
||||
* @param data event data
|
||||
* @return return data will send use socket.emit(key, data)
|
||||
*/
|
||||
[key: string]: ((data: any, socket: io.Socket) => any) | any;
|
||||
}
|
||||
|
||||
export interface Middleware {
|
||||
(socket: io.Socket, next: (err?: any) => void): void;
|
||||
}
|
||||
export interface ListenerMiddleware {
|
||||
(socket: io.Socket, packet: io.Packet, next: (err?: any) => void): void;
|
||||
}
|
||||
|
||||
export interface NamespaceMetadata {
|
||||
name: string;
|
||||
middleware?: Middleware[];
|
||||
target: any;
|
||||
}
|
||||
export interface ListenerMetadata {
|
||||
name: string;
|
||||
key: string;
|
||||
/**
|
||||
* Socket Listener Middleware will share all event listener
|
||||
*/
|
||||
middleware?: ListenerMiddleware[];
|
||||
target: any;
|
||||
}
|
||||
}
|
||||
42
packages/ws/src/utils.ts
Normal file
42
packages/ws/src/utils.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { METADATA_KEY, TYPE } from './constants'
|
||||
import { interfaces } from './interfaces'
|
||||
|
||||
function getNamespaces() {
|
||||
return getNamespacesMetadata().map((target) => target.target);
|
||||
}
|
||||
|
||||
function getNamespacesMetadata() {
|
||||
let namespaceMetadata: interfaces.NamespaceMetadata[] = Reflect.getMetadata(
|
||||
METADATA_KEY.namespace,
|
||||
Reflect
|
||||
) || [];
|
||||
return namespaceMetadata;
|
||||
}
|
||||
|
||||
function getNamespaceMetadata(target: any) {
|
||||
let namespaceMetadata: interfaces.NamespaceMetadata = Reflect.getMetadata(
|
||||
METADATA_KEY.namespace,
|
||||
target.constructor
|
||||
) || {};
|
||||
return namespaceMetadata;
|
||||
}
|
||||
|
||||
function getNamespaceListenerMetadata(target: any) {
|
||||
let eventMetadata: interfaces.ListenerMetadata[] = Reflect.getMetadata(
|
||||
METADATA_KEY.listener,
|
||||
target.constructor
|
||||
) || [];
|
||||
return eventMetadata;
|
||||
}
|
||||
|
||||
function getSocketContext(packet: any) {
|
||||
return Reflect.getMetadata(TYPE.SocketContext, packet);
|
||||
}
|
||||
|
||||
export {
|
||||
getNamespaces,
|
||||
getNamespaceMetadata,
|
||||
getNamespacesMetadata,
|
||||
getNamespaceListenerMetadata,
|
||||
getSocketContext
|
||||
}
|
||||
7
packages/ws/tsconfig.json
Normal file
7
packages/ws/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"outDir": "dist"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user