feat: complate socket.io base framework
Signed-off-by: MiaoWoo <admin@yumc.pw>
This commit is contained in:
parent
15f09abce0
commit
6c9ea9fb74
@ -27,7 +27,9 @@
|
|||||||
"typescript": "^3.8.3"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ms/nashorn": "^0.3.1"
|
"@ms/container": "^0.3.2",
|
||||||
|
"@ms/nashorn": "^0.3.1",
|
||||||
|
"socket.io-parser": "^3.4.0"
|
||||||
},
|
},
|
||||||
"gitHead": "781524f83e52cad26d7c480513e3c525df867121"
|
"gitHead": "781524f83e52cad26d7c480513e3c525df867121"
|
||||||
}
|
}
|
||||||
|
@ -1 +1,3 @@
|
|||||||
/// <reference types="@ms/nashorn" />
|
/// <reference types="@ms/nashorn" />
|
||||||
|
|
||||||
|
export * from './socket-io'
|
@ -2,17 +2,18 @@ const TypeParameterMatcher = Java.type('io.netty.util.internal.TypeParameterMatc
|
|||||||
const SimpleChannelInboundHandler = Java.type('io.netty.channel.SimpleChannelInboundHandler')
|
const SimpleChannelInboundHandler = Java.type('io.netty.channel.SimpleChannelInboundHandler')
|
||||||
const FullHttpRequestMatcher = TypeParameterMatcher.get(base.getClass('io.netty.handler.codec.http.FullHttpRequest'))
|
const FullHttpRequestMatcher = TypeParameterMatcher.get(base.getClass('io.netty.handler.codec.http.FullHttpRequest'))
|
||||||
|
|
||||||
export default abstract class HttpRequestHandlerAdapter {
|
export abstract class HttpRequestHandlerAdapter {
|
||||||
private _Handler;
|
private _Handler;
|
||||||
constructor() {
|
constructor() {
|
||||||
this._Handler == Java.extend(SimpleChannelInboundHandler, {
|
let HttpRequestHandlerAdapterImpl = Java.extend(SimpleChannelInboundHandler, {
|
||||||
acceptInboundMessage: (msg: any) => {
|
acceptInboundMessage: (msg: any) => {
|
||||||
return FullHttpRequestMatcher.match(msg)
|
return FullHttpRequestMatcher.match(msg)
|
||||||
},
|
},
|
||||||
channelRead0: this.channelRead0
|
channelRead0: this.channelRead0.bind(this)
|
||||||
})
|
})
|
||||||
|
this._Handler = new HttpRequestHandlerAdapterImpl();
|
||||||
}
|
}
|
||||||
abstract channelRead0(ctx: any, msg: any);
|
abstract channelRead0(ctx: any, request: any);
|
||||||
getHandler() {
|
getHandler() {
|
||||||
return this._Handler;
|
return this._Handler;
|
||||||
}
|
}
|
||||||
|
3
packages/websocket/src/netty/index.ts
Normal file
3
packages/websocket/src/netty/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './text_websocket_frame'
|
||||||
|
export * from './websocket'
|
||||||
|
export * from './httprequest'
|
@ -2,16 +2,17 @@ const TypeParameterMatcher = Java.type('io.netty.util.internal.TypeParameterMatc
|
|||||||
const TextWebSocketFrameMatcher = TypeParameterMatcher.get(base.getClass('io.netty.handler.codec.http.websocketx.TextWebSocketFrame'))
|
const TextWebSocketFrameMatcher = TypeParameterMatcher.get(base.getClass('io.netty.handler.codec.http.websocketx.TextWebSocketFrame'))
|
||||||
const SimpleChannelInboundHandler = Java.type('io.netty.channel.SimpleChannelInboundHandler')
|
const SimpleChannelInboundHandler = Java.type('io.netty.channel.SimpleChannelInboundHandler')
|
||||||
|
|
||||||
export default abstract class TextWebSocketFrameHandlerAdapter {
|
export abstract class TextWebSocketFrameHandlerAdapter {
|
||||||
private _Handler;
|
private _Handler;
|
||||||
constructor() {
|
constructor() {
|
||||||
this._Handler == Java.extend(SimpleChannelInboundHandler, {
|
let TextWebSocketFrameHandlerAdapterImpl = Java.extend(SimpleChannelInboundHandler, {
|
||||||
userEventTriggered: this.userEventTriggered,
|
userEventTriggered: this.userEventTriggered.bind(this),
|
||||||
acceptInboundMessage: (msg: any) => {
|
acceptInboundMessage: (msg: any) => {
|
||||||
return TextWebSocketFrameMatcher.match(msg)
|
return TextWebSocketFrameMatcher.match(msg)
|
||||||
},
|
},
|
||||||
channelRead0: this.channelRead0
|
channelRead0: this.channelRead0.bind(this)
|
||||||
})
|
})
|
||||||
|
this._Handler = new TextWebSocketFrameHandlerAdapterImpl();
|
||||||
}
|
}
|
||||||
abstract userEventTriggered(ctx: any, evt: any);
|
abstract userEventTriggered(ctx: any, evt: any);
|
||||||
abstract channelRead0(ctx: any, msg: any);
|
abstract channelRead0(ctx: any, msg: any);
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
const MiaoWebSocket = 'miaowebsocket'
|
|
||||||
const CharsetUtil = Java.type('io.netty.util.CharsetUtil')
|
|
||||||
const ChannelInboundHandlerAdapter = Java.type('io.netty.channel.ChannelInboundHandlerAdapter')
|
const ChannelInboundHandlerAdapter = Java.type('io.netty.channel.ChannelInboundHandlerAdapter')
|
||||||
|
|
||||||
export default abstract class WebSocketHandlerAdapter {
|
export abstract class WebSocketHandlerAdapter {
|
||||||
private _Handler;
|
private _Handler;
|
||||||
constructor() {
|
constructor() {
|
||||||
this._Handler = Java.extend(ChannelInboundHandlerAdapter, {
|
let ChannelInboundHandlerAdapterImpl = Java.extend(ChannelInboundHandlerAdapter, {
|
||||||
channelRead: this.channelRead
|
channelRead: this.channelRead.bind(this)
|
||||||
})
|
})
|
||||||
|
this._Handler = new ChannelInboundHandlerAdapterImpl()
|
||||||
}
|
}
|
||||||
abstract channelRead(ctx: any, msg: any);
|
abstract channelRead(ctx: any, channel: any);
|
||||||
getHandler() {
|
getHandler() {
|
||||||
return this._Handler;
|
return this._Handler;
|
||||||
}
|
}
|
||||||
|
27
packages/websocket/src/server/client.ts
Normal file
27
packages/websocket/src/server/client.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { EventEmitter } from 'events'
|
||||||
|
|
||||||
|
const TextWebSocketFrame = Java.type('io.netty.handler.codec.http.websocketx.TextWebSocketFrame')
|
||||||
|
|
||||||
|
export class NettyClient {
|
||||||
|
private event: EventEmitter
|
||||||
|
|
||||||
|
private _id: string;
|
||||||
|
private channel: any
|
||||||
|
constructor(channel: any) {
|
||||||
|
this.channel = channel;
|
||||||
|
this._id = channel.id();
|
||||||
|
this.event = new EventEmitter();
|
||||||
|
}
|
||||||
|
get id() {
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
on(event: string, callback: (...args: any[]) => void) {
|
||||||
|
this.event.on(event, callback);
|
||||||
|
}
|
||||||
|
emit(event: string, text: string) {
|
||||||
|
this.event.emit(event, text);
|
||||||
|
}
|
||||||
|
send(text: string) {
|
||||||
|
this.channel.writeAndFlush(new TextWebSocketFrame(text))
|
||||||
|
}
|
||||||
|
}
|
13
packages/websocket/src/server/constants.ts
Normal file
13
packages/websocket/src/server/constants.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export enum ServerEvent {
|
||||||
|
detect = 'detect',
|
||||||
|
connect = 'connect',
|
||||||
|
connection = 'connection',
|
||||||
|
message = 'message',
|
||||||
|
disconnect = 'disconnect'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Keys {
|
||||||
|
Detect = "miao_detect",
|
||||||
|
Handler = "miaowebsocket",
|
||||||
|
Default = "DefaultChannelPipeline"
|
||||||
|
}
|
66
packages/websocket/src/server/httprequest.ts
Normal file
66
packages/websocket/src/server/httprequest.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { HttpRequestHandlerAdapter } from '../netty'
|
||||||
|
|
||||||
|
const DefaultHttpResponse = Java.type('io.netty.handler.codec.http.DefaultHttpResponse')
|
||||||
|
const DefaultFullHttpResponse = Java.type('io.netty.handler.codec.http.DefaultFullHttpResponse')
|
||||||
|
const HttpHeaders = Java.type('io.netty.handler.codec.http.HttpHeaders')
|
||||||
|
const HttpVersion = Java.type('io.netty.handler.codec.http.HttpVersion')
|
||||||
|
const HttpResponseStatus = Java.type('io.netty.handler.codec.http.HttpResponseStatus')
|
||||||
|
const LastHttpContent = Java.type('io.netty.handler.codec.http.LastHttpContent')
|
||||||
|
|
||||||
|
const File = Java.type('java.io.File')
|
||||||
|
const Runnable = Java.type('java.lang.Runnable')
|
||||||
|
const RandomAccessFile = Java.type('java.io.RandomAccessFile')
|
||||||
|
const DefaultFileRegion = Java.type('io.netty.channel.DefaultFileRegion')
|
||||||
|
const ChannelFutureListener = Java.type('io.netty.channel.ChannelFutureListener')
|
||||||
|
|
||||||
|
export type HttpRequestConfig = {
|
||||||
|
root?: string;
|
||||||
|
ws?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HttpRequestHandler extends HttpRequestHandlerAdapter {
|
||||||
|
private ws: string;
|
||||||
|
private root: string;
|
||||||
|
constructor(config: HttpRequestConfig = {
|
||||||
|
root: root + '/wwwroot',
|
||||||
|
ws: '/ws'
|
||||||
|
}) {
|
||||||
|
super()
|
||||||
|
this.root = config.root;
|
||||||
|
this.ws = config.ws;
|
||||||
|
}
|
||||||
|
channelRead0(ctx: any, request: any) {
|
||||||
|
if (request.getUri().startsWith(this.ws)) {
|
||||||
|
ctx.fireChannelRead(request.retain())
|
||||||
|
} else {
|
||||||
|
ctx.executor().execute(new Runnable({
|
||||||
|
run: () => {
|
||||||
|
if (HttpHeaders.is100ContinueExpected(request)) {
|
||||||
|
ctx.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE))
|
||||||
|
}
|
||||||
|
let filename = request.getUri().split('?')[0].substr(1)
|
||||||
|
let file = new File(this.root, filename || 'index.html')
|
||||||
|
if (!file.exists() || !file.isFile()) {
|
||||||
|
ctx.write(new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.NOT_FOUND))
|
||||||
|
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT).addListener(ChannelFutureListener.CLOSE)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let response = new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.OK)
|
||||||
|
response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html charset=UTF-8")
|
||||||
|
let raf = new RandomAccessFile(file, 'r')
|
||||||
|
let keepAlive = HttpHeaders.isKeepAlive(request)
|
||||||
|
if (keepAlive) {
|
||||||
|
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, file.length())
|
||||||
|
response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE)
|
||||||
|
}
|
||||||
|
ctx.write(response)
|
||||||
|
ctx.write(new DefaultFileRegion(raf.getChannel(), 0, raf.length()))
|
||||||
|
let future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT)
|
||||||
|
if (!keepAlive) {
|
||||||
|
future.addListener(ChannelFutureListener.CLOSE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
packages/websocket/src/server/index.ts
Normal file
53
packages/websocket/src/server/index.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { EventEmitter } from 'events'
|
||||||
|
|
||||||
|
import { ServerEvent, Keys } from './constants'
|
||||||
|
import { WebSocketDetect } from './websocket_detect'
|
||||||
|
import { WebSocketHandler } from './websocket_handler'
|
||||||
|
import { NettyClient } from './client'
|
||||||
|
|
||||||
|
interface NettyWebSocketServerOptions {
|
||||||
|
path?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NettyWebSocketServer {
|
||||||
|
private event: EventEmitter
|
||||||
|
private pipeline: any;
|
||||||
|
private allClients: { [key: string]: NettyClient };
|
||||||
|
|
||||||
|
constructor(pipeline: any, options: NettyWebSocketServerOptions) {
|
||||||
|
this.event = new EventEmitter();
|
||||||
|
this.allClients = {};
|
||||||
|
this.pipeline = pipeline;
|
||||||
|
let connectEvent = new EventEmitter();
|
||||||
|
this.pipeline.addFirst(Keys.Detect, new WebSocketDetect(connectEvent).getHandler())
|
||||||
|
connectEvent.on(ServerEvent.detect, (ctx, channel) => {
|
||||||
|
channel.pipeline().addFirst(Keys.Handler, new WebSocketHandler(connectEvent).getHandler())
|
||||||
|
ctx.fireChannelRead(channel)
|
||||||
|
})
|
||||||
|
connectEvent.on(ServerEvent.connect, (ctx) => {
|
||||||
|
let nettyClient = new NettyClient(ctx.channel());
|
||||||
|
this.allClients[nettyClient.id] = nettyClient;
|
||||||
|
this.event.emit(ServerEvent.connect, nettyClient);
|
||||||
|
})
|
||||||
|
connectEvent.on(ServerEvent.message, (ctx, msg) => {
|
||||||
|
let channel = ctx.channel();
|
||||||
|
this.allClients[channel.id()]?.emit(ServerEvent.message, msg.text())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
if (this.pipeline.names().contains(Keys.Detect)) {
|
||||||
|
this.pipeline.remove(Keys.Detect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
on(event: string, listener: (...args: any[]) => void) {
|
||||||
|
this.event.on(event, listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
NettyWebSocketServer,
|
||||||
|
ServerEvent,
|
||||||
|
NettyClient
|
||||||
|
};
|
19
packages/websocket/src/server/text_websocket_frame.ts
Normal file
19
packages/websocket/src/server/text_websocket_frame.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { TextWebSocketFrameHandlerAdapter } from '../netty'
|
||||||
|
import { EventEmitter } from 'events'
|
||||||
|
import { ServerEvent } from './constants'
|
||||||
|
|
||||||
|
export class TextWebSocketFrameHandler extends TextWebSocketFrameHandlerAdapter {
|
||||||
|
private event: EventEmitter;
|
||||||
|
constructor(event: EventEmitter) {
|
||||||
|
super()
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
userEventTriggered(ctx: any, evt: any) {
|
||||||
|
if (evt == 'HANDSHAKE_COMPLETE') {
|
||||||
|
this.event.emit(ServerEvent.connect, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channelRead0(ctx: any, msg: any) {
|
||||||
|
this.event.emit(ServerEvent.message, ctx, msg)
|
||||||
|
}
|
||||||
|
}
|
14
packages/websocket/src/server/websocket_detect.ts
Normal file
14
packages/websocket/src/server/websocket_detect.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { EventEmitter } from 'events'
|
||||||
|
import { WebSocketHandlerAdapter } from "../netty"
|
||||||
|
import { ServerEvent } from './constants'
|
||||||
|
|
||||||
|
export class WebSocketDetect extends WebSocketHandlerAdapter {
|
||||||
|
private event: EventEmitter;
|
||||||
|
constructor(event: EventEmitter) {
|
||||||
|
super()
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
channelRead(ctx: any, channel: any) {
|
||||||
|
this.event.emit(ServerEvent.detect, ctx, channel);
|
||||||
|
}
|
||||||
|
}
|
42
packages/websocket/src/server/websocket_handler.ts
Normal file
42
packages/websocket/src/server/websocket_handler.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { EventEmitter } from 'events'
|
||||||
|
|
||||||
|
import { Keys } from './constants'
|
||||||
|
import { WebSocketHandlerAdapter } from "../netty"
|
||||||
|
import { HttpRequestHandler } from './httprequest'
|
||||||
|
import { TextWebSocketFrameHandler } from './text_websocket_frame'
|
||||||
|
|
||||||
|
const CharsetUtil = Java.type('io.netty.util.CharsetUtil')
|
||||||
|
|
||||||
|
const HttpServerCodec = Java.type('io.netty.handler.codec.http.HttpServerCodec')
|
||||||
|
const ChunkedWriteHandler = Java.type('io.netty.handler.stream.ChunkedWriteHandler')
|
||||||
|
const HttpObjectAggregator = Java.type('io.netty.handler.codec.http.HttpObjectAggregator')
|
||||||
|
const WebSocketServerProtocolHandler = Java.type('io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler')
|
||||||
|
|
||||||
|
export class WebSocketHandler extends WebSocketHandlerAdapter {
|
||||||
|
private event: EventEmitter;
|
||||||
|
constructor(event: EventEmitter) {
|
||||||
|
super()
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
channelRead(ctx: any, msg: any) {
|
||||||
|
msg.markReaderIndex()
|
||||||
|
let message: string = msg.toString(CharsetUtil.UTF_8)
|
||||||
|
let channel = ctx.channel()
|
||||||
|
let pipeline = channel.pipeline()
|
||||||
|
if (message.indexOf('HTTP/1.1') > 0) {
|
||||||
|
pipeline.names().forEach(f => {
|
||||||
|
if (f == Keys.Handler || f.indexOf(Keys.Default) > -1) { return }
|
||||||
|
pipeline.remove(f)
|
||||||
|
})
|
||||||
|
pipeline.addLast('http', new HttpServerCodec())
|
||||||
|
pipeline.addLast('chunk', new ChunkedWriteHandler())
|
||||||
|
pipeline.addLast('httpobj', new HttpObjectAggregator(64 * 1024))
|
||||||
|
pipeline.addLast('http_request', new HttpRequestHandler().getHandler())
|
||||||
|
pipeline.addLast('websocket', new WebSocketServerProtocolHandler("/ws", true))
|
||||||
|
pipeline.addLast('websocket_handler', new TextWebSocketFrameHandler(this.event).getHandler())
|
||||||
|
}
|
||||||
|
pipeline.remove(Keys.Handler)
|
||||||
|
msg.resetReaderIndex()
|
||||||
|
ctx.fireChannelRead(msg)
|
||||||
|
}
|
||||||
|
}
|
84
packages/websocket/src/socket-io/client.ts
Normal file
84
packages/websocket/src/socket-io/client.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { EventEmitter } from 'events'
|
||||||
|
import { Parser } from './parser'
|
||||||
|
import { Packet } from './packet';
|
||||||
|
import { PacketTypes, SubPacketTypes } from './types';
|
||||||
|
import { ServerEvent, NettyClient } from '../server';
|
||||||
|
import { SocketIO } from './interfaces'
|
||||||
|
|
||||||
|
const parser = new Parser();
|
||||||
|
|
||||||
|
export class SocketIOClient implements SocketIO.Client {
|
||||||
|
private nettyClient: NettyClient;
|
||||||
|
private event: EventEmitter
|
||||||
|
private _id: string;
|
||||||
|
|
||||||
|
server: SocketIO.Server;
|
||||||
|
conn: SocketIO.EngineSocket;
|
||||||
|
request: any;
|
||||||
|
sockets: { [id: string]: SocketIO.Socket; };
|
||||||
|
nsps: { [nsp: string]: SocketIO.Socket; };
|
||||||
|
|
||||||
|
constructor(nettyClient: NettyClient) {
|
||||||
|
this.event = new EventEmitter()
|
||||||
|
this.nettyClient = nettyClient;
|
||||||
|
this._id = this.nettyClient.id;
|
||||||
|
this.event.emit('connect', this);
|
||||||
|
this.nettyClient.on(ServerEvent.message, (text) => this.process(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
connect() {
|
||||||
|
this.packet({
|
||||||
|
type: PacketTypes.OPEN,
|
||||||
|
data: {
|
||||||
|
sid: this._id,
|
||||||
|
upgrades: [],
|
||||||
|
pingInterval: 25000,
|
||||||
|
pingTimeout: 5000
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.packet({
|
||||||
|
type: PacketTypes.MESSAGE,
|
||||||
|
sub_type: SubPacketTypes.CONNECT
|
||||||
|
})
|
||||||
|
}
|
||||||
|
emit(event: string, data: any) {
|
||||||
|
this.packet({
|
||||||
|
type: PacketTypes.MESSAGE,
|
||||||
|
sub_type: SubPacketTypes.EVENT,
|
||||||
|
name: event,
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
send(data: any) {
|
||||||
|
this.emit("message", data);
|
||||||
|
}
|
||||||
|
packet(packet: Packet) {
|
||||||
|
this.nettyClient.send(parser.encode(packet))
|
||||||
|
}
|
||||||
|
|
||||||
|
private process(text: string) {
|
||||||
|
let packet = parser.decode(text);
|
||||||
|
switch (packet.type) {
|
||||||
|
case PacketTypes.OPEN:
|
||||||
|
break;
|
||||||
|
case PacketTypes.PING:
|
||||||
|
this.packet({
|
||||||
|
type: PacketTypes.PONG
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case PacketTypes.MESSAGE:
|
||||||
|
switch (packet.sub_type) {
|
||||||
|
case SubPacketTypes.CONNECT:
|
||||||
|
this.nettyClient.send(text);
|
||||||
|
break;
|
||||||
|
case SubPacketTypes.EVENT:
|
||||||
|
this.event.emit(packet.name, packet.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
118
packages/websocket/src/socket-io/index.ts
Normal file
118
packages/websocket/src/socket-io/index.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { EventEmitter } from 'events'
|
||||||
|
|
||||||
|
import { NettyWebSocketServer } from '../server'
|
||||||
|
import { ServerEvent } from '../server/constants';
|
||||||
|
|
||||||
|
import { Namespace } from './namespace';
|
||||||
|
import { SocketIOClient } from './client';
|
||||||
|
import { SocketIO } from './interfaces'
|
||||||
|
|
||||||
|
export class SocketIOServer implements SocketIO.Server {
|
||||||
|
private event: EventEmitter;
|
||||||
|
private nettyServer: NettyWebSocketServer;
|
||||||
|
private allClients: { [key: string]: SocketIOClient };
|
||||||
|
private namespaces: { [key: string]: Namespace };
|
||||||
|
|
||||||
|
engine: { ws: any; };
|
||||||
|
nsps: { [namespace: string]: SocketIO.Namespace; };
|
||||||
|
sockets: SocketIO.Namespace;
|
||||||
|
json: SocketIO.Server;
|
||||||
|
volatile: SocketIO.Server;
|
||||||
|
local: SocketIO.Server;
|
||||||
|
|
||||||
|
constructor(pipeline: any, options: SocketIO.ServerOptions) {
|
||||||
|
if (!pipeline) { throw new Error('Netty Pipeline can\'t be undefiend!') }
|
||||||
|
this.event = new EventEmitter();
|
||||||
|
this.allClients = {};
|
||||||
|
this.namespaces = {};
|
||||||
|
this.nettyServer = new NettyWebSocketServer(pipeline, {
|
||||||
|
|
||||||
|
});
|
||||||
|
this.nettyServer.on(ServerEvent.connect, (nettyClient) => {
|
||||||
|
let client = new SocketIOClient(nettyClient);
|
||||||
|
this.allClients[client.id] = client;
|
||||||
|
client.connect();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRequest(req: any, fn: (err: any, success: boolean) => void): void {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
serveClient(): boolean;
|
||||||
|
serveClient(v: boolean): SocketIO.Server;
|
||||||
|
serveClient(v?: any): boolean | SocketIO.Server {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
path(): string;
|
||||||
|
path(v: string): SocketIO.Server;
|
||||||
|
path(v?: any): string | SocketIO.Server {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
adapter();
|
||||||
|
adapter(v: any): SocketIO.Server;
|
||||||
|
adapter(v?: any): SocketIO.Server {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
origins(): string | string[];
|
||||||
|
origins(v: string | string[]): SocketIO.Server;
|
||||||
|
origins(fn: (origin: string, callback: (error: string, success: boolean) => void) => void): SocketIO.Server;
|
||||||
|
origins(fn?: any): string | string[] | SocketIO.Server {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
attach(srv: any, opts?: SocketIO.ServerOptions): SocketIO.Server;
|
||||||
|
attach(port: number, opts?: SocketIO.ServerOptions): SocketIO.Server;
|
||||||
|
attach(port: any, opts?: any): SocketIO.Server {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
listen(srv: any, opts?: SocketIO.ServerOptions): SocketIO.Server;
|
||||||
|
listen(port: number, opts?: SocketIO.ServerOptions): SocketIO.Server;
|
||||||
|
listen(port: any, opts?: any): SocketIO.Server {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
bind(srv: any): SocketIO.Server {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
onconnection(socket: any): SocketIO.Server {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
of(nsp: string | Function | RegExp): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
close(fn?: () => void): void {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
on(event: "connection", listener: (socket: SocketIO.Socket) => void): SocketIO.Namespace;
|
||||||
|
on(event: "connect", listener: (socket: SocketIO.Socket) => void): SocketIO.Namespace;
|
||||||
|
on(event: string, listener: Function): SocketIO.Namespace;
|
||||||
|
on(event: any, listener: any): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
to(room: string): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
in(room: string): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
use(fn: (socket: SocketIO.Socket, fn: (err?: any) => void) => void): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
emit(event: string, ...args: any[]): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
send(...args: any[]): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
write(...args: any[]): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
clients(...args: any[]): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
compress(...args: any[]): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
this.nettyServer.disable();
|
||||||
|
}
|
||||||
|
}
|
824
packages/websocket/src/socket-io/interfaces.ts
Normal file
824
packages/websocket/src/socket-io/interfaces.ts
Normal file
@ -0,0 +1,824 @@
|
|||||||
|
export declare namespace SocketIO {
|
||||||
|
interface Server {
|
||||||
|
engine: { ws: any };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dictionary of all the namespaces currently on this Server
|
||||||
|
*/
|
||||||
|
nsps: { [namespace: string]: Namespace };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default '/' Namespace
|
||||||
|
*/
|
||||||
|
sockets: Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the 'json' flag when emitting an event
|
||||||
|
*/
|
||||||
|
json: Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a modifier for a subsequent event emission that the event data may be lost if the clients are not ready to receive messages
|
||||||
|
*/
|
||||||
|
volatile: Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node
|
||||||
|
*/
|
||||||
|
local: Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server request verification function, that checks for allowed origins
|
||||||
|
* @param req The http.IncomingMessage request
|
||||||
|
* @param fn The callback to be called. It should take one parameter, err,
|
||||||
|
* which will be null if there was no problem, and one parameter, success,
|
||||||
|
* of type boolean
|
||||||
|
*/
|
||||||
|
checkRequest(req: any, fn: (err: any, success: boolean) => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets whether we're serving the client.js file or not
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
serveClient(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether we're serving the client.js file or not
|
||||||
|
* @param v True if we want to serve the file, false otherwise
|
||||||
|
* @default true
|
||||||
|
* @return This Server
|
||||||
|
*/
|
||||||
|
serveClient(v: boolean): Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the client serving path
|
||||||
|
* @default '/socket.io'
|
||||||
|
*/
|
||||||
|
path(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the client serving path
|
||||||
|
* @param v The path to serve the client file on
|
||||||
|
* @default '/socket.io'
|
||||||
|
* @return This Server
|
||||||
|
*/
|
||||||
|
path(v: string): Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the adapter that we're going to use for handling rooms
|
||||||
|
* @default typeof Adapter
|
||||||
|
*/
|
||||||
|
adapter(): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the adapter (class) that we're going to use for handling rooms
|
||||||
|
* @param v The class for the adapter to create
|
||||||
|
* @default typeof Adapter
|
||||||
|
* @return This Server
|
||||||
|
*/
|
||||||
|
adapter(v: any): Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the allowed origins for requests
|
||||||
|
* @default "*:*"
|
||||||
|
*/
|
||||||
|
origins(): string | string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the allowed origins for requests
|
||||||
|
* @param v The allowed origins, in host:port form
|
||||||
|
* @default "*:*"
|
||||||
|
* return This Server
|
||||||
|
*/
|
||||||
|
origins(v: string | string[]): Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a function taking two arguments origin:String
|
||||||
|
* and callback(error, success), where success is a boolean
|
||||||
|
* value indicating whether origin is allowed or not. If
|
||||||
|
* success is set to false, error must be provided as a string
|
||||||
|
* value that will be appended to the server response, e.g. “Origin not allowed”.
|
||||||
|
* @param fn The function that will be called to check the origin
|
||||||
|
* return This Server
|
||||||
|
*/
|
||||||
|
origins(fn: (origin: string, callback: (error: string | null, success: boolean) => void) => void): Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches socket.io to a server
|
||||||
|
* @param srv The http.Server that we want to attach to
|
||||||
|
* @param opts An optional parameters object
|
||||||
|
* @return This Server
|
||||||
|
*/
|
||||||
|
attach(srv: any, opts?: ServerOptions): Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches socket.io to a port
|
||||||
|
* @param port The port that we want to attach to
|
||||||
|
* @param opts An optional parameters object
|
||||||
|
* @return This Server
|
||||||
|
*/
|
||||||
|
attach(port: number, opts?: ServerOptions): Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see attach( srv, opts )
|
||||||
|
*/
|
||||||
|
listen(srv: any, opts?: ServerOptions): Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see attach( port, opts )
|
||||||
|
*/
|
||||||
|
listen(port: number, opts?: ServerOptions): Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds socket.io to an engine.io instance
|
||||||
|
* @param src The Engine.io (or compatible) server to bind to
|
||||||
|
* @return This Server
|
||||||
|
*/
|
||||||
|
bind(srv: any): Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called with each incoming connection
|
||||||
|
* @param socket The Engine.io Socket
|
||||||
|
* @return This Server
|
||||||
|
*/
|
||||||
|
onconnection(socket: any): Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up/creates a Namespace
|
||||||
|
* @param nsp The name of the NameSpace to look up/create. Should start
|
||||||
|
* with a '/'
|
||||||
|
* @return The Namespace
|
||||||
|
*/
|
||||||
|
of(nsp: string | RegExp | Function): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the server connection
|
||||||
|
*/
|
||||||
|
close(fn?: () => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event fired when we get a new connection
|
||||||
|
* @param event The event being fired: 'connection'
|
||||||
|
* @param listener A listener that should take one parameter of type Socket
|
||||||
|
* @return The default '/' Namespace
|
||||||
|
*/
|
||||||
|
on(event: 'connection', listener: (socket: Socket) => void): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see on( 'connection', listener )
|
||||||
|
*/
|
||||||
|
on(event: 'connect', listener: (socket: Socket) => void): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base 'on' method to add a listener for an event
|
||||||
|
* @param event The event that we want to add a listener for
|
||||||
|
* @param listener The callback to call when we get the event. The parameters
|
||||||
|
* for the callback depend on the event
|
||||||
|
* @return The default '/' Namespace
|
||||||
|
*/
|
||||||
|
on(event: string, listener: Function): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Targets a room when emitting to the default '/' Namespace
|
||||||
|
* @param room The name of the room that we're targeting
|
||||||
|
* @return The default '/' Namespace
|
||||||
|
*/
|
||||||
|
to(room: string): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see to( room )
|
||||||
|
*/
|
||||||
|
in(room: string): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a middleware function, which is a function that gets executed
|
||||||
|
* for every incoming Socket, on the default '/' Namespace
|
||||||
|
* @param fn The function to call when we get a new incoming socket. It should
|
||||||
|
* take one parameter of type Socket, and one callback function to call to
|
||||||
|
* execute the next middleware function. The callback can take one optional
|
||||||
|
* parameter, err, if there was an error. Errors passed to middleware callbacks
|
||||||
|
* are sent as special 'error' packets to clients
|
||||||
|
* @return The default '/' Namespace
|
||||||
|
*/
|
||||||
|
use(fn: (socket: Socket, fn: (err?: any) => void) => void): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits an event to the default Namespace
|
||||||
|
* @param event The event that we want to emit
|
||||||
|
* @param args Any number of optional arguments to pass with the event. If the
|
||||||
|
* last argument is a function, it will be called as an ack. The ack should
|
||||||
|
* take whatever data was sent with the packet
|
||||||
|
* @return The default '/' Namespace
|
||||||
|
*/
|
||||||
|
emit(event: string, ...args: any[]): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a 'message' event
|
||||||
|
* @see emit( event, ...args )
|
||||||
|
* @return The default '/' Namespace
|
||||||
|
*/
|
||||||
|
send(...args: any[]): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see send( ...args )
|
||||||
|
*/
|
||||||
|
write(...args: any[]): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of clients
|
||||||
|
* @return The default '/' Namespace
|
||||||
|
*/
|
||||||
|
clients(...args: any[]): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the compress flag
|
||||||
|
* @return The default '/' Namespace
|
||||||
|
*/
|
||||||
|
compress(...args: any[]): Namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options to pass to our server when creating it
|
||||||
|
*/
|
||||||
|
interface ServerOptions {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to server the client file to
|
||||||
|
* @default '/socket.io'
|
||||||
|
*/
|
||||||
|
path?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should we serve the client file?
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
serveClient?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The adapter to use for handling rooms. NOTE: this should be a class,
|
||||||
|
* not an object
|
||||||
|
* @default typeof Adapter
|
||||||
|
*/
|
||||||
|
adapter?: Adapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepted origins
|
||||||
|
* @default '*:*'
|
||||||
|
*/
|
||||||
|
origins?: string | string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many milliseconds without a pong packed to consider the connection closed (engine.io)
|
||||||
|
* @default 60000
|
||||||
|
*/
|
||||||
|
pingTimeout?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many milliseconds before sending a new ping packet (keep-alive) (engine.io)
|
||||||
|
* @default 25000
|
||||||
|
*/
|
||||||
|
pingInterval?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many bytes or characters a message can be when polling, before closing the session
|
||||||
|
* (to avoid Dos) (engine.io)
|
||||||
|
* @default 10E7
|
||||||
|
*/
|
||||||
|
maxHttpBufferSize?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that receives a given handshake or upgrade request as its first parameter,
|
||||||
|
* and can decide whether to continue or not. The second argument is a function that needs
|
||||||
|
* to be called with the decided information: fn( err, success ), where success is a boolean
|
||||||
|
* value where false means that the request is rejected, and err is an error code (engine.io)
|
||||||
|
* @default null
|
||||||
|
*/
|
||||||
|
allowRequest?: (request: any, callback: (err: number, success: boolean) => void) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transports to allow connections to (engine.io)
|
||||||
|
* @default ['polling','websocket']
|
||||||
|
*/
|
||||||
|
transports?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to allow transport upgrades (engine.io)
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
allowUpgrades?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parameters of the WebSocket permessage-deflate extension (see ws module).
|
||||||
|
* Set to false to disable (engine.io)
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
perMessageDeflate?: Object | boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters of the http compression for the polling transports (see zlib).
|
||||||
|
* Set to false to disable, or set an object with parameter "threshold:number"
|
||||||
|
* to only compress data if the byte size is above this value (1024) (engine.io)
|
||||||
|
* @default true|1024
|
||||||
|
*/
|
||||||
|
httpCompression?: Object | boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the HTTP cookie that contains the client sid to send as part of
|
||||||
|
* handshake response headers. Set to false to not send one (engine.io)
|
||||||
|
* @default "io"
|
||||||
|
*/
|
||||||
|
cookie?: string | boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to let engine.io handle the OPTIONS requests.
|
||||||
|
* You can also pass a custom function to handle the requests
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
handlePreflightRequest?: ((req: any, res: any) => void) | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Namespace, sandboxed environments for sockets, each connection
|
||||||
|
* to a Namespace requires a new Socket
|
||||||
|
*/
|
||||||
|
interface Namespace extends NodeJS.EventEmitter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the NameSpace
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The controller Server for this Namespace
|
||||||
|
*/
|
||||||
|
server: Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dictionary of all the Sockets connected to this Namespace, where
|
||||||
|
* the Socket ID is the key
|
||||||
|
*/
|
||||||
|
sockets: { [id: string]: Socket };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dictionary of all the Sockets connected to this Namespace, where
|
||||||
|
* the Socket ID is the key
|
||||||
|
*/
|
||||||
|
connected: { [id: string]: Socket };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Adapter that we're using to handle dealing with rooms etc
|
||||||
|
*/
|
||||||
|
adapter: Adapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the 'json' flag when emitting an event
|
||||||
|
*/
|
||||||
|
json: Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a middleware function, which is a function that gets executed
|
||||||
|
* for every incoming Socket
|
||||||
|
* @param fn The function to call when we get a new incoming socket. It should
|
||||||
|
* take one parameter of type Socket, and one callback function to call to
|
||||||
|
* execute the next middleware function. The callback can take one optional
|
||||||
|
* parameter, err, if there was an error. Errors passed to middleware callbacks
|
||||||
|
* are sent as special 'error' packets to clients
|
||||||
|
* @return This Namespace
|
||||||
|
*/
|
||||||
|
use(fn: (socket: Socket, fn: (err?: any) => void) => void): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Targets a room when emitting
|
||||||
|
* @param room The name of the room that we're targeting
|
||||||
|
* @return This Namespace
|
||||||
|
*/
|
||||||
|
to(room: string): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see to( room )
|
||||||
|
*/
|
||||||
|
in(room: string): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a 'message' event
|
||||||
|
* @see emit( event, ...args )
|
||||||
|
* @return This Namespace
|
||||||
|
*/
|
||||||
|
send(...args: any[]): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see send( ...args )
|
||||||
|
*/
|
||||||
|
write(...args: any[]): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event fired when we get a new connection
|
||||||
|
* @param event The event being fired: 'connection'
|
||||||
|
* @param listener A listener that should take one parameter of type Socket
|
||||||
|
* @return This Namespace
|
||||||
|
*/
|
||||||
|
on(event: 'connection', listener: (socket: Socket) => void): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see on( 'connection', listener )
|
||||||
|
*/
|
||||||
|
on(event: 'connect', listener: (socket: Socket) => void): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base 'on' method to add a listener for an event
|
||||||
|
* @param event The event that we want to add a listener for
|
||||||
|
* @param listener The callback to call when we get the event. The parameters
|
||||||
|
* for the callback depend on the event
|
||||||
|
* @ This Namespace
|
||||||
|
*/
|
||||||
|
on(event: string, listener: Function): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of clients.
|
||||||
|
* @return This Namespace
|
||||||
|
*/
|
||||||
|
clients(fn: Function): Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the compress flag.
|
||||||
|
* @param compress If `true`, compresses the sending data
|
||||||
|
* @return This Namespace
|
||||||
|
*/
|
||||||
|
compress(compress: boolean): Namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Packet extends Array<any> {
|
||||||
|
/**
|
||||||
|
* Event name
|
||||||
|
*/
|
||||||
|
[0]: string;
|
||||||
|
/**
|
||||||
|
* Packet data
|
||||||
|
*/
|
||||||
|
[1]: any;
|
||||||
|
/**
|
||||||
|
* Ack function
|
||||||
|
*/
|
||||||
|
[2]: (...args: any[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The socket, which handles our connection for a namespace. NOTE: while
|
||||||
|
* we technically extend NodeJS.EventEmitter, we're not putting it here
|
||||||
|
* as we have a problem with the emit() event (as it's overridden with a
|
||||||
|
* different return)
|
||||||
|
*/
|
||||||
|
interface Socket extends NodeJS.EventEmitter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The namespace that this socket is for
|
||||||
|
*/
|
||||||
|
nsp: Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Server that our namespace is in
|
||||||
|
*/
|
||||||
|
server: Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Adapter that we use to handle our rooms
|
||||||
|
*/
|
||||||
|
adapter: Adapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unique ID for this Socket. Regenerated at every connection. This is
|
||||||
|
* also the name of the room that the Socket automatically joins on connection
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The http.IncomingMessage request sent with the connection. Useful
|
||||||
|
* for recovering headers etc
|
||||||
|
*/
|
||||||
|
request: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Client associated with this Socket
|
||||||
|
*/
|
||||||
|
client: Client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The underlying Engine.io Socket instance
|
||||||
|
*/
|
||||||
|
conn: EngineSocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of rooms that this Socket is currently in, where
|
||||||
|
* the ID the the room ID
|
||||||
|
*/
|
||||||
|
rooms: { [id: string]: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the Socket currently connected?
|
||||||
|
*/
|
||||||
|
connected: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the Socket currently disconnected?
|
||||||
|
*/
|
||||||
|
disconnected: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object used when negociating the handshake
|
||||||
|
*/
|
||||||
|
handshake: Handshake;
|
||||||
|
/**
|
||||||
|
* Sets the 'json' flag when emitting an event
|
||||||
|
*/
|
||||||
|
json: Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the 'volatile' flag when emitting an event. Volatile messages are
|
||||||
|
* messages that can be dropped because of network issues and the like. Use
|
||||||
|
* for high-volume/real-time messages where you don't need to receive *all*
|
||||||
|
* of them
|
||||||
|
*/
|
||||||
|
volatile: Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the 'broadcast' flag when emitting an event. Broadcasting an event
|
||||||
|
* will send it to all the other sockets in the namespace except for yourself
|
||||||
|
*/
|
||||||
|
broadcast: Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Targets a room when broadcasting
|
||||||
|
* @param room The name of the room that we're targeting
|
||||||
|
* @return This Socket
|
||||||
|
*/
|
||||||
|
to(room: string): Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see to( room )
|
||||||
|
*/
|
||||||
|
in(room: string): Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a middleware, which is a function that gets executed for every incoming Packet and receives as parameter the packet and a function to optionally defer execution to the next registered middleware.
|
||||||
|
*
|
||||||
|
* Errors passed to middleware callbacks are sent as special error packets to clients.
|
||||||
|
*/
|
||||||
|
use(fn: (packet: Packet, next: (err?: any) => void) => void): Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a 'message' event
|
||||||
|
* @see emit( event, ...args )
|
||||||
|
*/
|
||||||
|
send(...args: any[]): Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see send( ...args )
|
||||||
|
*/
|
||||||
|
write(...args: any[]): Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Joins a room. You can join multiple rooms, and by default, on connection,
|
||||||
|
* you join a room with the same name as your ID
|
||||||
|
* @param name The name of the room that we want to join
|
||||||
|
* @param fn An optional callback to call when we've joined the room. It should
|
||||||
|
* take an optional parameter, err, of a possible error
|
||||||
|
* @return This Socket
|
||||||
|
*/
|
||||||
|
join(name: string | string[], fn?: (err?: any) => void): Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leaves a room
|
||||||
|
* @param name The name of the room to leave
|
||||||
|
* @param fn An optional callback to call when we've left the room. It should
|
||||||
|
* take on optional parameter, err, of a possible error
|
||||||
|
*/
|
||||||
|
leave(name: string, fn?: Function): Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leaves all the rooms that we've joined
|
||||||
|
*/
|
||||||
|
leaveAll(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects this Socket
|
||||||
|
* @param close If true, also closes the underlying connection
|
||||||
|
* @return This Socket
|
||||||
|
*/
|
||||||
|
disconnect(close?: boolean): Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the callbacks for a particular event
|
||||||
|
* @param event The event that we're looking for the callbacks of
|
||||||
|
* @return An array of callback Functions, or an empty array if we don't have any
|
||||||
|
*/
|
||||||
|
listeners(event: string): Function[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the compress flag
|
||||||
|
* @param compress If `true`, compresses the sending data
|
||||||
|
* @return This Socket
|
||||||
|
*/
|
||||||
|
compress(compress: boolean): Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the error
|
||||||
|
* @param err Error message=
|
||||||
|
*/
|
||||||
|
error(err: any): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Handshake {
|
||||||
|
/**
|
||||||
|
* The headers passed along with the request. e.g. 'host',
|
||||||
|
* 'connection', 'accept', 'referer', 'cookie'
|
||||||
|
*/
|
||||||
|
headers: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current time, as a string
|
||||||
|
*/
|
||||||
|
time: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The remote address of the connection request
|
||||||
|
*/
|
||||||
|
address: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a cross-domain request?
|
||||||
|
*/
|
||||||
|
xdomain: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a secure request?
|
||||||
|
*/
|
||||||
|
secure: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp for when this was issued
|
||||||
|
*/
|
||||||
|
issued: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request url
|
||||||
|
*/
|
||||||
|
url: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any query string parameters in the request url
|
||||||
|
*/
|
||||||
|
query: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface describing a room
|
||||||
|
*/
|
||||||
|
interface Room {
|
||||||
|
sockets: { [id: string]: boolean };
|
||||||
|
length: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface describing a dictionary of rooms
|
||||||
|
* Where room is the name of the room
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface Rooms {
|
||||||
|
[room: string]: Room;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface used when dealing with rooms etc
|
||||||
|
*/
|
||||||
|
interface Adapter extends NodeJS.EventEmitter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The namespace that this adapter is for
|
||||||
|
*/
|
||||||
|
nsp: Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dictionary of all the rooms that we have in this namespace
|
||||||
|
*/
|
||||||
|
rooms: Rooms;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dictionary of all the socket ids that we're dealing with, and all
|
||||||
|
* the rooms that the socket is currently in
|
||||||
|
*/
|
||||||
|
sids: { [id: string]: { [room: string]: boolean } };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a socket to a room. If the room doesn't exist, it's created
|
||||||
|
* @param id The ID of the socket to add
|
||||||
|
* @param room The name of the room to add the socket to
|
||||||
|
* @param callback An optional callback to call when the socket has been
|
||||||
|
* added. It should take an optional parameter, error, if there was a problem
|
||||||
|
*/
|
||||||
|
add(id: string, room: string, callback?: (err?: any) => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a socket from a room. If there are no more sockets in the room,
|
||||||
|
* the room is deleted
|
||||||
|
* @param id The ID of the socket that we're removing
|
||||||
|
* @param room The name of the room to remove the socket from
|
||||||
|
* @param callback An optional callback to call when the socket has been
|
||||||
|
* removed. It should take on optional parameter, error, if there was a problem
|
||||||
|
*/
|
||||||
|
del(id: string, room: string, callback?: (err?: any) => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a socket from all the rooms that it's joined
|
||||||
|
* @param id The ID of the socket that we're removing
|
||||||
|
*/
|
||||||
|
delAll(id: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts a packet
|
||||||
|
* @param packet The packet to broadcast
|
||||||
|
* @param opts Any options to send along:
|
||||||
|
* - rooms: An optional list of rooms to broadcast to. If empty, the packet is broadcast to all sockets
|
||||||
|
* - except: A list of Socket IDs to exclude
|
||||||
|
* - flags: Any flags that we want to send along ('json', 'volatile', 'broadcast')
|
||||||
|
*/
|
||||||
|
broadcast(packet: any, opts: { rooms?: string[]; except?: string[]; flags?: { [flag: string]: boolean } }): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The client behind each socket (can have multiple sockets)
|
||||||
|
*/
|
||||||
|
interface Client {
|
||||||
|
/**
|
||||||
|
* The Server that this client belongs to
|
||||||
|
*/
|
||||||
|
server: Server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The underlying Engine.io Socket instance
|
||||||
|
*/
|
||||||
|
conn: EngineSocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID for this client. Regenerated at every connection
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The http.IncomingMessage request sent with the connection. Useful
|
||||||
|
* for recovering headers etc
|
||||||
|
*/
|
||||||
|
request: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dictionary of sockets currently connect via this client (i.e. to different
|
||||||
|
* namespaces) where the Socket ID is the key
|
||||||
|
*/
|
||||||
|
sockets: { [id: string]: Socket };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dictionary of all the namespaces for this client, with the Socket that
|
||||||
|
* deals with that namespace
|
||||||
|
*/
|
||||||
|
nsps: { [nsp: string]: Socket };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to the underlying engine.io Socket connection.
|
||||||
|
*/
|
||||||
|
interface EngineSocket extends NodeJS.EventEmitter {
|
||||||
|
/**
|
||||||
|
* The ID for this socket - matches Client.id
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Engine.io Server for this socket
|
||||||
|
*/
|
||||||
|
server: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ready state for the client. Either 'opening', 'open', 'closing', or 'closed'
|
||||||
|
*/
|
||||||
|
readyState: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The remote IP for this connection
|
||||||
|
*/
|
||||||
|
remoteAddress: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* whether the transport has been upgraded
|
||||||
|
*/
|
||||||
|
upgraded: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (http.IncomingMessage): request that originated the Socket
|
||||||
|
*/
|
||||||
|
request: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Transport): transport reference
|
||||||
|
*/
|
||||||
|
transport: any;
|
||||||
|
}
|
||||||
|
}
|
96
packages/websocket/src/socket-io/namespace.ts
Normal file
96
packages/websocket/src/socket-io/namespace.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { EventEmitter } from 'events'
|
||||||
|
|
||||||
|
import { SocketIOClient } from './client'
|
||||||
|
import { SocketIO } from './interfaces';
|
||||||
|
|
||||||
|
export class Namespace implements SocketIO.Namespace {
|
||||||
|
private event: EventEmitter;
|
||||||
|
private allClients: { [key: string]: SocketIOClient };
|
||||||
|
private roomClients: { [key: string]: Set<string> };
|
||||||
|
private clientRooms: { [key: string]: Set<string> };
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
server: SocketIO.Server;
|
||||||
|
sockets: { [id: string]: SocketIO.Socket; };
|
||||||
|
connected: { [id: string]: SocketIO.Socket; };
|
||||||
|
adapter: SocketIO.Adapter;
|
||||||
|
json: SocketIO.Namespace;
|
||||||
|
|
||||||
|
constructor(name: string) {
|
||||||
|
this.name = name;
|
||||||
|
this.event = new EventEmitter();
|
||||||
|
this.allClients = {};
|
||||||
|
this.roomClients = {};
|
||||||
|
this.clientRooms = {};
|
||||||
|
}
|
||||||
|
use(fn: (socket: SocketIO.Socket, fn: (err?: any) => void) => void): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
to(room: string): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
in(room: string): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
send(...args: any[]): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
write(...args: any[]): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
on(event: "connection", listener: (socket: SocketIO.Socket) => void): this;
|
||||||
|
on(event: "connect", listener: (socket: SocketIO.Socket) => void): this;
|
||||||
|
on(event: string, listener: Function): this;
|
||||||
|
on(event: any, listener: any) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
clients(fn: Function): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
compress(compress: boolean): SocketIO.Namespace {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
addListener(event: string | symbol, listener: (...args: any[]) => void): this {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
once(event: string | symbol, listener: (...args: any[]) => void): this {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
removeListener(event: string | symbol, listener: (...args: any[]) => void): this {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
off(event: string | symbol, listener: (...args: any[]) => void): this {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
removeAllListeners(event?: string | symbol): this {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
setMaxListeners(n: number): this {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
getMaxListeners(): number {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
listeners(event: string | symbol): Function[] {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
rawListeners(event: string | symbol): Function[] {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
emit(event: string | symbol, ...args: any[]): boolean {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
listenerCount(type: string | symbol): number {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
prependListener(event: string | symbol, listener: (...args: any[]) => void): this {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
eventNames(): (string | symbol)[] {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
packages/websocket/src/socket-io/packet.ts
Normal file
11
packages/websocket/src/socket-io/packet.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { PacketTypes, SubPacketTypes } from './types'
|
||||||
|
|
||||||
|
export interface Packet {
|
||||||
|
type: PacketTypes;
|
||||||
|
sub_type?: SubPacketTypes;
|
||||||
|
nsp?: string;
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
data?: any;
|
||||||
|
attachments?: any;
|
||||||
|
}
|
160
packages/websocket/src/socket-io/parser.ts
Normal file
160
packages/websocket/src/socket-io/parser.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import { Packet } from "./packet";
|
||||||
|
import { PacketTypes, SubPacketTypes } from "./types";
|
||||||
|
|
||||||
|
export class Parser {
|
||||||
|
encode(packet: Packet): string {
|
||||||
|
// first is type
|
||||||
|
var str = '' + packet.type;
|
||||||
|
if (packet.type == PacketTypes.PONG) {
|
||||||
|
if (packet.data) { str += packet.data };
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
if (packet.sub_type != undefined) {
|
||||||
|
str += packet.sub_type;
|
||||||
|
}
|
||||||
|
// attachments if we have them
|
||||||
|
if ([SubPacketTypes.BINARY_EVENT, SubPacketTypes.BINARY_ACK].includes(packet.sub_type)) {
|
||||||
|
str += packet.attachments + '-';
|
||||||
|
}
|
||||||
|
// if we have a namespace other than `/`
|
||||||
|
// we append it followed by a comma `,`
|
||||||
|
if (packet.nsp && '/' !== packet.nsp) {
|
||||||
|
str += packet.nsp + ',';
|
||||||
|
}
|
||||||
|
// immediately followed by the id
|
||||||
|
if (null != packet.id) {
|
||||||
|
str += packet.id;
|
||||||
|
}
|
||||||
|
if (packet.sub_type == SubPacketTypes.EVENT) {
|
||||||
|
packet.data = [packet.name, packet.data]
|
||||||
|
}
|
||||||
|
// json data
|
||||||
|
if (null != packet.data) {
|
||||||
|
var payload = this.tryStringify(packet.data);
|
||||||
|
if (payload !== false) {
|
||||||
|
str += payload;
|
||||||
|
} else {
|
||||||
|
return '4"encode error"'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.debug(`encoded ${JSON.stringify(packet)} as ${str}`);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
tryStringify(str) {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(str);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decode(str: string): Packet {
|
||||||
|
var i = 0;
|
||||||
|
// ignore parse binary
|
||||||
|
// if ((frame.getByte(0) == 'b' && frame.getByte(1) == '4')
|
||||||
|
// || frame.getByte(0) == 4 || frame.getByte(0) == 1) {
|
||||||
|
// return parseBinary(head, frame);
|
||||||
|
// }
|
||||||
|
// look up type
|
||||||
|
var p: Packet = {
|
||||||
|
type: Number(str.charAt(i))
|
||||||
|
};
|
||||||
|
if (null == PacketTypes[p.type]) {
|
||||||
|
return this.error('unknown packet type ' + p.type);
|
||||||
|
}
|
||||||
|
// if str empty return
|
||||||
|
if (str.length == i + 1) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
// if is ping packet read data and return
|
||||||
|
if (PacketTypes.PING == p.type) {
|
||||||
|
p.data = str.substr(++i);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
// look up sub type
|
||||||
|
p.sub_type = Number(str.charAt(++i));
|
||||||
|
if (null == PacketTypes[p.sub_type]) {
|
||||||
|
return this.error('unknown sub packet type ' + p.type);
|
||||||
|
}
|
||||||
|
// look up attachments if type binary
|
||||||
|
if ([SubPacketTypes.BINARY_ACK, SubPacketTypes.BINARY_EVENT].includes(p.sub_type)) {
|
||||||
|
var buf = '';
|
||||||
|
while (str.charAt(++i) !== '-') {
|
||||||
|
buf += str.charAt(i);
|
||||||
|
if (i == str.length) break;
|
||||||
|
}
|
||||||
|
if (buf != `${Number(buf)}` || str.charAt(i) !== '-') {
|
||||||
|
throw new Error('Illegal attachments');
|
||||||
|
}
|
||||||
|
p.attachments = Number(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// look up namespace (if any)
|
||||||
|
if ('/' === str.charAt(i + 1)) {
|
||||||
|
p.nsp = '';
|
||||||
|
while (++i) {
|
||||||
|
var c = str.charAt(i);
|
||||||
|
if (',' === c) break;
|
||||||
|
p.nsp += c;
|
||||||
|
if (i === str.length) break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.nsp = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
// look up id
|
||||||
|
var next = str.charAt(i + 1);
|
||||||
|
if ('' !== next && Number.isNaN(Number(next))) {
|
||||||
|
var id = ''
|
||||||
|
while (++i) {
|
||||||
|
var c = str.charAt(i);
|
||||||
|
if (null == c || Number.isNaN(Number(c))) {
|
||||||
|
--i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
id += str.charAt(i);
|
||||||
|
if (i === str.length) break;
|
||||||
|
}
|
||||||
|
p.id = Number(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// look up packet name
|
||||||
|
if (p.sub_type == SubPacketTypes.EVENT) {
|
||||||
|
|
||||||
|
}
|
||||||
|
// ignore binary packet
|
||||||
|
if (p.sub_type == SubPacketTypes.BINARY_EVENT) {
|
||||||
|
return this.error('not support binary parse...')
|
||||||
|
}
|
||||||
|
|
||||||
|
// look up json data
|
||||||
|
if (str.charAt(++i)) {
|
||||||
|
var payload = this.tryParse(str.substr(i));
|
||||||
|
var isPayloadValid = payload !== false && (p.sub_type == SubPacketTypes.ERROR || Array.isArray(payload));
|
||||||
|
if (isPayloadValid) {
|
||||||
|
p.name = payload[0];
|
||||||
|
p.data = payload[1];
|
||||||
|
} else {
|
||||||
|
return this.error('invalid payload');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug(`decoded ${str} as ${JSON.stringify(p)}`);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
tryParse(str) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(str);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error(error: string): Packet {
|
||||||
|
return {
|
||||||
|
type: PacketTypes.MESSAGE,
|
||||||
|
sub_type: SubPacketTypes.ERROR,
|
||||||
|
data: 'parser error: ' + error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
18
packages/websocket/src/socket-io/types.ts
Normal file
18
packages/websocket/src/socket-io/types.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export enum PacketTypes {
|
||||||
|
OPEN,
|
||||||
|
CLOSE,
|
||||||
|
PING,
|
||||||
|
PONG,
|
||||||
|
MESSAGE,
|
||||||
|
UPGRADE,
|
||||||
|
NOOP,
|
||||||
|
}
|
||||||
|
export enum SubPacketTypes {
|
||||||
|
CONNECT,
|
||||||
|
DISCONNECT,
|
||||||
|
EVENT,
|
||||||
|
ACK,
|
||||||
|
ERROR,
|
||||||
|
BINARY_EVENT,
|
||||||
|
BINARY_ACK
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user