@@ -23,7 +23,7 @@ export class WebSocketManager {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const managers = new WebSocketManager()
 | 
			
		||||
export const manager = new WebSocketManager()
 | 
			
		||||
 | 
			
		||||
export class WebSocket extends EventEmitter {
 | 
			
		||||
    public static CONNECTING = 0
 | 
			
		||||
@@ -31,6 +31,7 @@ export class WebSocket extends EventEmitter {
 | 
			
		||||
    public static CLOSING = 2
 | 
			
		||||
    public static CLOSED = 3
 | 
			
		||||
    public binaryType: 'blob' | 'arraybuffer'
 | 
			
		||||
    protected manager: WebSocketManager
 | 
			
		||||
 | 
			
		||||
    protected _url: string
 | 
			
		||||
    protected _headers: WebSocketHeader = {}
 | 
			
		||||
@@ -39,6 +40,7 @@ export class WebSocket extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
    constructor(url: string, subProtocol: string = '', headers: WebSocketHeader = {}) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.manager = manager
 | 
			
		||||
        this._url = url
 | 
			
		||||
        this._headers = headers
 | 
			
		||||
        try {
 | 
			
		||||
@@ -51,12 +53,12 @@ export class WebSocket extends EventEmitter {
 | 
			
		||||
        }
 | 
			
		||||
        this.client.on('open', (event) => {
 | 
			
		||||
            this.onopen?.(event)
 | 
			
		||||
            managers.add(this)
 | 
			
		||||
            manager.add(this)
 | 
			
		||||
        })
 | 
			
		||||
        this.client.on('message', (event) => this.onmessage?.(event))
 | 
			
		||||
        this.client.on('close', (event) => {
 | 
			
		||||
            this.onclose?.(event)
 | 
			
		||||
            managers.del(this)
 | 
			
		||||
            manager.del(this)
 | 
			
		||||
        })
 | 
			
		||||
        this.client.on('error', (event) => this.onerror?.(event))
 | 
			
		||||
        setTimeout(() => this.client.connect(), 20)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								packages/websocket/src/engine.io-parser/commons.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								packages/websocket/src/engine.io-parser/commons.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
const PACKET_TYPES = Object.create(null) // no Map = no polyfill
 | 
			
		||||
PACKET_TYPES["open"] = "0"
 | 
			
		||||
PACKET_TYPES["close"] = "1"
 | 
			
		||||
PACKET_TYPES["ping"] = "2"
 | 
			
		||||
PACKET_TYPES["pong"] = "3"
 | 
			
		||||
PACKET_TYPES["message"] = "4"
 | 
			
		||||
PACKET_TYPES["upgrade"] = "5"
 | 
			
		||||
PACKET_TYPES["noop"] = "6"
 | 
			
		||||
 | 
			
		||||
const PACKET_TYPES_REVERSE = Object.create(null)
 | 
			
		||||
Object.keys(PACKET_TYPES).forEach(key => {
 | 
			
		||||
    PACKET_TYPES_REVERSE[PACKET_TYPES[key]] = key
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const ERROR_PACKET = { type: "error", data: "parser error" }
 | 
			
		||||
 | 
			
		||||
export = {
 | 
			
		||||
    PACKET_TYPES,
 | 
			
		||||
    PACKET_TYPES_REVERSE,
 | 
			
		||||
    ERROR_PACKET
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								packages/websocket/src/engine.io-parser/decodePacket.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								packages/websocket/src/engine.io-parser/decodePacket.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
const { PACKET_TYPES_REVERSE, ERROR_PACKET } = require("./commons")
 | 
			
		||||
 | 
			
		||||
export const decodePacket = (encodedPacket, binaryType) => {
 | 
			
		||||
    if (typeof encodedPacket !== "string") {
 | 
			
		||||
        return {
 | 
			
		||||
            type: "message",
 | 
			
		||||
            data: mapBinary(encodedPacket, binaryType)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    const type = encodedPacket.charAt(0)
 | 
			
		||||
    if (type === "b") {
 | 
			
		||||
        const buffer = Buffer.from(encodedPacket.substring(1), "base64")
 | 
			
		||||
        return {
 | 
			
		||||
            type: "message",
 | 
			
		||||
            data: mapBinary(buffer, binaryType)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (!PACKET_TYPES_REVERSE[type]) {
 | 
			
		||||
        return ERROR_PACKET
 | 
			
		||||
    }
 | 
			
		||||
    return encodedPacket.length > 1
 | 
			
		||||
        ? {
 | 
			
		||||
            type: PACKET_TYPES_REVERSE[type],
 | 
			
		||||
            data: encodedPacket.substring(1)
 | 
			
		||||
        }
 | 
			
		||||
        : {
 | 
			
		||||
            type: PACKET_TYPES_REVERSE[type]
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mapBinary = (data, binaryType) => {
 | 
			
		||||
    switch (binaryType) {
 | 
			
		||||
        case "arraybuffer":
 | 
			
		||||
            return Buffer.isBuffer(data) ? toArrayBuffer(data) : data
 | 
			
		||||
        case "nodebuffer":
 | 
			
		||||
        default:
 | 
			
		||||
            return data // assuming the data is already a Buffer
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const toArrayBuffer = buffer => {
 | 
			
		||||
    const arrayBuffer = new ArrayBuffer(buffer.length)
 | 
			
		||||
    const view = new Uint8Array(arrayBuffer)
 | 
			
		||||
    for (let i = 0; i < buffer.length; i++) {
 | 
			
		||||
        view[i] = buffer[i]
 | 
			
		||||
    }
 | 
			
		||||
    return arrayBuffer
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								packages/websocket/src/engine.io-parser/encodePacket.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								packages/websocket/src/engine.io-parser/encodePacket.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
const { PACKET_TYPES } = require("./commons")
 | 
			
		||||
 | 
			
		||||
export const encodePacket = ({ type, data }, supportsBinary, callback) => {
 | 
			
		||||
    console.trace('encodePacket', type, JSON.stringify(data))
 | 
			
		||||
    if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
 | 
			
		||||
        const buffer = toBuffer(data)
 | 
			
		||||
        return callback(encodeBuffer(buffer, supportsBinary))
 | 
			
		||||
    }
 | 
			
		||||
    // plain string
 | 
			
		||||
    return callback(PACKET_TYPES[type] + (data || ""))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const toBuffer = data => {
 | 
			
		||||
    if (Buffer.isBuffer(data)) {
 | 
			
		||||
        return data
 | 
			
		||||
    } else if (data instanceof ArrayBuffer) {
 | 
			
		||||
        return Buffer.from(data)
 | 
			
		||||
    } else {
 | 
			
		||||
        return Buffer.from(data.buffer, data.byteOffset, data.byteLength)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// only 'message' packets can contain binary, so the type prefix is not needed
 | 
			
		||||
const encodeBuffer = (data, supportsBinary) => {
 | 
			
		||||
    return supportsBinary ? data : "b" + data.toString("base64")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								packages/websocket/src/engine.io-parser/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								packages/websocket/src/engine.io-parser/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
import { encodePacket } from "./encodePacket"
 | 
			
		||||
import { decodePacket } from "./decodePacket"
 | 
			
		||||
 | 
			
		||||
const SEPARATOR = String.fromCharCode(30) // see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text
 | 
			
		||||
 | 
			
		||||
const encodePayload = (packets, callback) => {
 | 
			
		||||
    // some packets may be added to the array while encoding, so the initial length must be saved
 | 
			
		||||
    const length = packets.length
 | 
			
		||||
    const encodedPackets = new Array(length)
 | 
			
		||||
    let count = 0
 | 
			
		||||
 | 
			
		||||
    packets.forEach((packet, i) => {
 | 
			
		||||
        // force base64 encoding for binary packets
 | 
			
		||||
        encodePacket(packet, false, encodedPacket => {
 | 
			
		||||
            encodedPackets[i] = encodedPacket
 | 
			
		||||
            if (++count === length) {
 | 
			
		||||
                callback(encodedPackets.join(SEPARATOR))
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const decodePayload = (encodedPayload, binaryType) => {
 | 
			
		||||
    const encodedPackets = encodedPayload.split(SEPARATOR)
 | 
			
		||||
    const packets = []
 | 
			
		||||
    for (let i = 0; i < encodedPackets.length; i++) {
 | 
			
		||||
        const decodedPacket = decodePacket(encodedPackets[i], binaryType)
 | 
			
		||||
        packets.push(decodedPacket)
 | 
			
		||||
        if (decodedPacket.type === "error") {
 | 
			
		||||
            break
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return packets
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    protocol: 4,
 | 
			
		||||
    encodePacket,
 | 
			
		||||
    encodePayload,
 | 
			
		||||
    decodePacket,
 | 
			
		||||
    decodePayload
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								packages/websocket/src/engine.io/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								packages/websocket/src/engine.io/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Module dependencies.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import * as server from "../server"
 | 
			
		||||
// const http = require("http")
 | 
			
		||||
// const Server = require("./server")
 | 
			
		||||
import { Server } from './server'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Captures upgrade requests for a http.Server.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {http.Server} server
 | 
			
		||||
 * @param {Object} options
 | 
			
		||||
 * @return {Server} engine server
 | 
			
		||||
 * @api public
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
function attach(srv, options) {
 | 
			
		||||
    const engine = new Server(options)
 | 
			
		||||
    engine.attach(server.attach(srv, options), options)
 | 
			
		||||
    return engine
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export = {
 | 
			
		||||
    attach
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										690
									
								
								packages/websocket/src/engine.io/server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										690
									
								
								packages/websocket/src/engine.io/server.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,690 @@
 | 
			
		||||
const qs = require("querystring")
 | 
			
		||||
const parse = require("url").parse
 | 
			
		||||
// const base64id = require("base64id")
 | 
			
		||||
import transports from './transports'
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
// const EventEmitter = require("events").EventEmitter
 | 
			
		||||
import { Socket } from './socket'
 | 
			
		||||
// const debug = require("debug")("engine")
 | 
			
		||||
const debug = function (...args) { }
 | 
			
		||||
// const cookieMod = require("cookie")
 | 
			
		||||
 | 
			
		||||
// const DEFAULT_WS_ENGINE = require("ws").Server;
 | 
			
		||||
import { WebSocketServer } from '../server'
 | 
			
		||||
import { Transport } from './transport'
 | 
			
		||||
const DEFAULT_WS_ENGINE = WebSocketServer
 | 
			
		||||
 | 
			
		||||
import { Request } from '../server/request'
 | 
			
		||||
import { WebSocketClient } from '../server/client'
 | 
			
		||||
 | 
			
		||||
export class Server extends EventEmitter {
 | 
			
		||||
    public static errors = {
 | 
			
		||||
        UNKNOWN_TRANSPORT: 0,
 | 
			
		||||
        UNKNOWN_SID: 1,
 | 
			
		||||
        BAD_HANDSHAKE_METHOD: 2,
 | 
			
		||||
        BAD_REQUEST: 3,
 | 
			
		||||
        FORBIDDEN: 4,
 | 
			
		||||
        UNSUPPORTED_PROTOCOL_VERSION: 5
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static errorMessages = {
 | 
			
		||||
        0: "Transport unknown",
 | 
			
		||||
        1: "Session ID unknown",
 | 
			
		||||
        2: "Bad handshake method",
 | 
			
		||||
        3: "Bad request",
 | 
			
		||||
        4: "Forbidden",
 | 
			
		||||
        5: "Unsupported protocol version"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private clients = {}
 | 
			
		||||
    private clientsCount = 0
 | 
			
		||||
    public opts: any
 | 
			
		||||
 | 
			
		||||
    private corsMiddleware: any
 | 
			
		||||
 | 
			
		||||
    private ws: any
 | 
			
		||||
    private perMessageDeflate: any
 | 
			
		||||
 | 
			
		||||
    constructor(opts: any = {}) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.opts = Object.assign(
 | 
			
		||||
            {
 | 
			
		||||
                wsEngine: DEFAULT_WS_ENGINE,
 | 
			
		||||
                pingTimeout: 20000,
 | 
			
		||||
                pingInterval: 25000,
 | 
			
		||||
                upgradeTimeout: 10000,
 | 
			
		||||
                maxHttpBufferSize: 1e6,
 | 
			
		||||
                transports: Object.keys(transports),
 | 
			
		||||
                allowUpgrades: true,
 | 
			
		||||
                httpCompression: {
 | 
			
		||||
                    threshold: 1024
 | 
			
		||||
                },
 | 
			
		||||
                cors: false,
 | 
			
		||||
                allowEIO3: false
 | 
			
		||||
            },
 | 
			
		||||
            opts
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        // if (opts.cookie) {
 | 
			
		||||
        //     this.opts.cookie = Object.assign(
 | 
			
		||||
        //         {
 | 
			
		||||
        //             name: "io",
 | 
			
		||||
        //             path: "/",
 | 
			
		||||
        //             httpOnly: opts.cookie.path !== false,
 | 
			
		||||
        //             sameSite: "lax"
 | 
			
		||||
        //         },
 | 
			
		||||
        //         opts.cookie
 | 
			
		||||
        //     )
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        // if (this.opts.cors) {
 | 
			
		||||
        //     this.corsMiddleware = require("cors")(this.opts.cors)
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        // if (opts.perMessageDeflate) {
 | 
			
		||||
        //     this.opts.perMessageDeflate = Object.assign(
 | 
			
		||||
        //         {
 | 
			
		||||
        //             threshold: 1024
 | 
			
		||||
        //         },
 | 
			
		||||
        //         opts.perMessageDeflate
 | 
			
		||||
        //     )
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        // this.init()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * Initialize websocket server
 | 
			
		||||
    //  *
 | 
			
		||||
    //  * @api private
 | 
			
		||||
    //  */
 | 
			
		||||
    // init() {
 | 
			
		||||
    //     if (!~this.opts.transports.indexOf("websocket")) return
 | 
			
		||||
 | 
			
		||||
    //     if (this.ws) this.ws.close()
 | 
			
		||||
 | 
			
		||||
    //     this.ws = new this.opts.wsEngine({
 | 
			
		||||
    //         noServer: true,
 | 
			
		||||
    //         clientTracking: false,
 | 
			
		||||
    //         perMessageDeflate: this.opts.perMessageDeflate,
 | 
			
		||||
    //         maxPayload: this.opts.maxHttpBufferSize
 | 
			
		||||
    //     })
 | 
			
		||||
 | 
			
		||||
    //     if (typeof this.ws.on === "function") {
 | 
			
		||||
    //         this.ws.on("headers", (headersArray, req) => {
 | 
			
		||||
    //             // note: 'ws' uses an array of headers, while Engine.IO uses an object (response.writeHead() accepts both formats)
 | 
			
		||||
    //             // we could also try to parse the array and then sync the values, but that will be error-prone
 | 
			
		||||
    //             const additionalHeaders = {}
 | 
			
		||||
 | 
			
		||||
    //             const isInitialRequest = !req._query.sid
 | 
			
		||||
    //             if (isInitialRequest) {
 | 
			
		||||
    //                 this.emit("initial_headers", additionalHeaders, req)
 | 
			
		||||
    //             }
 | 
			
		||||
 | 
			
		||||
    //             this.emit("headers", additionalHeaders, req)
 | 
			
		||||
 | 
			
		||||
    //             Object.keys(additionalHeaders).forEach(key => {
 | 
			
		||||
    //                 headersArray.push(`${key}: ${additionalHeaders[key]}`)
 | 
			
		||||
    //             })
 | 
			
		||||
    //         })
 | 
			
		||||
    //     }
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a list of available transports for upgrade given a certain transport.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Array}
 | 
			
		||||
     * @api public
 | 
			
		||||
     */
 | 
			
		||||
    upgrades(transport): Array<any> {
 | 
			
		||||
        if (!this.opts.allowUpgrades) return []
 | 
			
		||||
        return transports[transport].upgradesTo || []
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * Verifies a request.
 | 
			
		||||
    //  *
 | 
			
		||||
    //  * @param {http.IncomingMessage}
 | 
			
		||||
    //  * @return {Boolean} whether the request is valid
 | 
			
		||||
    //  * @api private
 | 
			
		||||
    //  */
 | 
			
		||||
    // verify(req, upgrade, fn) {
 | 
			
		||||
    //     // transport check
 | 
			
		||||
    //     const transport = req._query.transport
 | 
			
		||||
    //     if (!~this.opts.transports.indexOf(transport)) {
 | 
			
		||||
    //         debug('unknown transport "%s"', transport)
 | 
			
		||||
    //         return fn(Server.errors.UNKNOWN_TRANSPORT, { transport })
 | 
			
		||||
    //     }
 | 
			
		||||
 | 
			
		||||
    //     // 'Origin' header check
 | 
			
		||||
    //     const isOriginInvalid = checkInvalidHeaderChar(req.headers.origin)
 | 
			
		||||
    //     if (isOriginInvalid) {
 | 
			
		||||
    //         const origin = req.headers.origin
 | 
			
		||||
    //         req.headers.origin = null
 | 
			
		||||
    //         debug("origin header invalid")
 | 
			
		||||
    //         return fn(Server.errors.BAD_REQUEST, {
 | 
			
		||||
    //             name: "INVALID_ORIGIN",
 | 
			
		||||
    //             origin
 | 
			
		||||
    //         })
 | 
			
		||||
    //     }
 | 
			
		||||
 | 
			
		||||
    //     // sid check
 | 
			
		||||
    //     const sid = req._query.sid
 | 
			
		||||
    //     if (sid) {
 | 
			
		||||
    //         if (!this.clients.hasOwnProperty(sid)) {
 | 
			
		||||
    //             debug('unknown sid "%s"', sid)
 | 
			
		||||
    //             return fn(Server.errors.UNKNOWN_SID, {
 | 
			
		||||
    //                 sid
 | 
			
		||||
    //             })
 | 
			
		||||
    //         }
 | 
			
		||||
    //         const previousTransport = this.clients[sid].transport.name
 | 
			
		||||
    //         if (!upgrade && previousTransport !== transport) {
 | 
			
		||||
    //             debug("bad request: unexpected transport without upgrade")
 | 
			
		||||
    //             return fn(Server.errors.BAD_REQUEST, {
 | 
			
		||||
    //                 name: "TRANSPORT_MISMATCH",
 | 
			
		||||
    //                 transport,
 | 
			
		||||
    //                 previousTransport
 | 
			
		||||
    //             })
 | 
			
		||||
    //         }
 | 
			
		||||
    //     } else {
 | 
			
		||||
    //         // handshake is GET only
 | 
			
		||||
    //         if ("GET" !== req.method) {
 | 
			
		||||
    //             return fn(Server.errors.BAD_HANDSHAKE_METHOD, {
 | 
			
		||||
    //                 method: req.method
 | 
			
		||||
    //             })
 | 
			
		||||
    //         }
 | 
			
		||||
 | 
			
		||||
    //         if (!this.opts.allowRequest) return fn()
 | 
			
		||||
 | 
			
		||||
    //         return this.opts.allowRequest(req, (message, success) => {
 | 
			
		||||
    //             if (!success) {
 | 
			
		||||
    //                 return fn(Server.errors.FORBIDDEN, {
 | 
			
		||||
    //                     message
 | 
			
		||||
    //                 })
 | 
			
		||||
    //             }
 | 
			
		||||
    //             fn()
 | 
			
		||||
    //         })
 | 
			
		||||
    //     }
 | 
			
		||||
 | 
			
		||||
    //     fn()
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Prepares a request by processing the query string.
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    prepare(req) {
 | 
			
		||||
        // try to leverage pre-existing `req._query` (e.g: from connect)
 | 
			
		||||
        if (!req._query) {
 | 
			
		||||
            req._query = ~req.url.indexOf("?") ? qs.parse(parse(req.url).query) : {}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Closes all clients.
 | 
			
		||||
     *
 | 
			
		||||
     * @api public
 | 
			
		||||
     */
 | 
			
		||||
    close() {
 | 
			
		||||
        debug("closing all open clients")
 | 
			
		||||
        for (let i in this.clients) {
 | 
			
		||||
            if (this.clients.hasOwnProperty(i)) {
 | 
			
		||||
                this.clients[i].close(true)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (this.ws) {
 | 
			
		||||
            debug("closing webSocketServer")
 | 
			
		||||
            this.ws.close()
 | 
			
		||||
            // don't delete this.ws because it can be used again if the http server starts listening again
 | 
			
		||||
        }
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * Handles an Engine.IO HTTP request.
 | 
			
		||||
    //  *
 | 
			
		||||
    //  * @param {http.IncomingMessage} request
 | 
			
		||||
    //  * @param {http.ServerResponse|http.OutgoingMessage} response
 | 
			
		||||
    //  * @api public
 | 
			
		||||
    //  */
 | 
			
		||||
    // handleRequest(req, res) {
 | 
			
		||||
    //     debug('handling "%s" http request "%s"', req.method, req.url)
 | 
			
		||||
    //     this.prepare(req)
 | 
			
		||||
    //     req.res = res
 | 
			
		||||
 | 
			
		||||
    //     const callback = (errorCode, errorContext) => {
 | 
			
		||||
    //         if (errorCode !== undefined) {
 | 
			
		||||
    //             this.emit("connection_error", {
 | 
			
		||||
    //                 req,
 | 
			
		||||
    //                 code: errorCode,
 | 
			
		||||
    //                 message: Server.errorMessages[errorCode],
 | 
			
		||||
    //                 context: errorContext
 | 
			
		||||
    //             })
 | 
			
		||||
    //             abortRequest(res, errorCode, errorContext)
 | 
			
		||||
    //             return
 | 
			
		||||
    //         }
 | 
			
		||||
 | 
			
		||||
    //         if (req._query.sid) {
 | 
			
		||||
    //             debug("setting new request for existing client")
 | 
			
		||||
    //             this.clients[req._query.sid].transport.onRequest(req)
 | 
			
		||||
    //         } else {
 | 
			
		||||
    //             const closeConnection = (errorCode, errorContext) =>
 | 
			
		||||
    //                 abortRequest(res, errorCode, errorContext)
 | 
			
		||||
    //             this.handshake(req._query.transport, req, closeConnection)
 | 
			
		||||
    //         }
 | 
			
		||||
    //     }
 | 
			
		||||
 | 
			
		||||
    //     if (this.corsMiddleware) {
 | 
			
		||||
    //         this.corsMiddleware.call(null, req, res, () => {
 | 
			
		||||
    //             this.verify(req, false, callback)
 | 
			
		||||
    //         })
 | 
			
		||||
    //     } else {
 | 
			
		||||
    //         this.verify(req, false, callback)
 | 
			
		||||
    //     }
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * generate a socket id.
 | 
			
		||||
     * Overwrite this method to generate your custom socket id
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} request object
 | 
			
		||||
     * @api public
 | 
			
		||||
     */
 | 
			
		||||
    generateId(req) {
 | 
			
		||||
        return req.id
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handshakes a new client.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} transport name
 | 
			
		||||
     * @param {Object} request object
 | 
			
		||||
     * @param {Function} closeConnection
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    // @java-patch sync handshake
 | 
			
		||||
    handshake(transportName, req, closeConnection: (code: number) => void) {
 | 
			
		||||
        console.debug('engine.io server handshake transport', transportName, 'from', req.url)
 | 
			
		||||
        const protocol = req._query.EIO === "4" ? 4 : 3 // 3rd revision by default
 | 
			
		||||
        if (protocol === 3 && !this.opts.allowEIO3) {
 | 
			
		||||
            debug("unsupported protocol version")
 | 
			
		||||
            this.emit("connection_error", {
 | 
			
		||||
                req,
 | 
			
		||||
                code: Server.errors.UNSUPPORTED_PROTOCOL_VERSION,
 | 
			
		||||
                message:
 | 
			
		||||
                    Server.errorMessages[Server.errors.UNSUPPORTED_PROTOCOL_VERSION],
 | 
			
		||||
                context: {
 | 
			
		||||
                    protocol
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            closeConnection(Server.errors.UNSUPPORTED_PROTOCOL_VERSION)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let id
 | 
			
		||||
        try {
 | 
			
		||||
            id = this.generateId(req)
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            console.debug("error while generating an id")
 | 
			
		||||
            this.emit("connection_error", {
 | 
			
		||||
                req,
 | 
			
		||||
                code: Server.errors.BAD_REQUEST,
 | 
			
		||||
                message: Server.errorMessages[Server.errors.BAD_REQUEST],
 | 
			
		||||
                context: {
 | 
			
		||||
                    name: "ID_GENERATION_ERROR",
 | 
			
		||||
                    error: e
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            closeConnection(Server.errors.BAD_REQUEST)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        console.debug('engine.io server handshaking client "' + id + '"')
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            var transport: Transport = new transports[transportName](req)
 | 
			
		||||
            if ("websocket" !== transportName) {
 | 
			
		||||
                throw new Error('Unsupport polling at MiaoScript!')
 | 
			
		||||
            }
 | 
			
		||||
            // if ("polling" === transportName) {
 | 
			
		||||
            //     transport.maxHttpBufferSize = this.opts.maxHttpBufferSize
 | 
			
		||||
            //     transport.httpCompression = this.opts.httpCompression
 | 
			
		||||
            // } else if ("websocket" === transportName) {
 | 
			
		||||
            transport.perMessageDeflate = this.opts.perMessageDeflate
 | 
			
		||||
            // }
 | 
			
		||||
 | 
			
		||||
            if (req._query && req._query.b64) {
 | 
			
		||||
                transport.supportsBinary = false
 | 
			
		||||
            } else {
 | 
			
		||||
                transport.supportsBinary = true
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            console.ex(e)
 | 
			
		||||
            this.emit("connection_error", {
 | 
			
		||||
                req,
 | 
			
		||||
                code: Server.errors.BAD_REQUEST,
 | 
			
		||||
                message: Server.errorMessages[Server.errors.BAD_REQUEST],
 | 
			
		||||
                context: {
 | 
			
		||||
                    name: "TRANSPORT_HANDSHAKE_ERROR",
 | 
			
		||||
                    error: e
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            closeConnection(Server.errors.BAD_REQUEST)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        console.debug(`engine.io server create socket ${id} from transport ${transport.name} protocol ${protocol}`)
 | 
			
		||||
        const socket = new Socket(id, this, transport, req, protocol)
 | 
			
		||||
 | 
			
		||||
        transport.on("headers", (headers, req) => {
 | 
			
		||||
            const isInitialRequest = !req._query.sid
 | 
			
		||||
 | 
			
		||||
            if (isInitialRequest) {
 | 
			
		||||
                if (this.opts.cookie) {
 | 
			
		||||
                    headers["Set-Cookie"] = [
 | 
			
		||||
                        // cookieMod.serialize(this.opts.cookie.name, id, this.opts.cookie)
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
                this.emit("initial_headers", headers, req)
 | 
			
		||||
            }
 | 
			
		||||
            this.emit("headers", headers, req)
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        transport.onRequest(req)
 | 
			
		||||
 | 
			
		||||
        this.clients[id] = socket
 | 
			
		||||
        this.clientsCount++
 | 
			
		||||
 | 
			
		||||
        socket.once("close", () => {
 | 
			
		||||
            delete this.clients[id]
 | 
			
		||||
            this.clientsCount--
 | 
			
		||||
        })
 | 
			
		||||
        this.emit("connection", socket)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * Handles an Engine.IO HTTP Upgrade.
 | 
			
		||||
    //  *
 | 
			
		||||
    //  * @api public
 | 
			
		||||
    //  */
 | 
			
		||||
    // handleUpgrade(req, socket, upgradeHead) {
 | 
			
		||||
    //     this.prepare(req)
 | 
			
		||||
 | 
			
		||||
    //     this.verify(req, true, (errorCode, errorContext) => {
 | 
			
		||||
    //         if (errorCode) {
 | 
			
		||||
    //             this.emit("connection_error", {
 | 
			
		||||
    //                 req,
 | 
			
		||||
    //                 code: errorCode,
 | 
			
		||||
    //                 message: Server.errorMessages[errorCode],
 | 
			
		||||
    //                 context: errorContext
 | 
			
		||||
    //             })
 | 
			
		||||
    //             abortUpgrade(socket, errorCode, errorContext)
 | 
			
		||||
    //             return
 | 
			
		||||
    //         }
 | 
			
		||||
 | 
			
		||||
    //         const head = Buffer.from(upgradeHead) // eslint-disable-line node/no-deprecated-api
 | 
			
		||||
    //         upgradeHead = null
 | 
			
		||||
 | 
			
		||||
    //         // delegate to ws
 | 
			
		||||
    //         this.ws.handleUpgrade(req, socket, head, websocket => {
 | 
			
		||||
    //             this.onWebSocket(req, socket, websocket)
 | 
			
		||||
    //         })
 | 
			
		||||
    //     })
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon a ws.io connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {ws.Socket} websocket
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    onWebSocket(req: Request, socket, websocket: WebSocketClient) {
 | 
			
		||||
        websocket.on("error", onUpgradeError)
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
            transports[req._query.transport] !== undefined &&
 | 
			
		||||
            !transports[req._query.transport].prototype.handlesUpgrades
 | 
			
		||||
        ) {
 | 
			
		||||
            console.debug("transport doesnt handle upgraded requests")
 | 
			
		||||
            websocket.close()
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // get client id
 | 
			
		||||
        const id = req._query.sid
 | 
			
		||||
 | 
			
		||||
        // keep a reference to the ws.Socket
 | 
			
		||||
        req.websocket = websocket
 | 
			
		||||
 | 
			
		||||
        if (id) {
 | 
			
		||||
            const client = this.clients[id]
 | 
			
		||||
            if (!client) {
 | 
			
		||||
                console.debug("upgrade attempt for closed client")
 | 
			
		||||
                websocket.close()
 | 
			
		||||
            } else if (client.upgrading) {
 | 
			
		||||
                console.debug("transport has already been trying to upgrade")
 | 
			
		||||
                websocket.close()
 | 
			
		||||
            } else if (client.upgraded) {
 | 
			
		||||
                console.debug("transport had already been upgraded")
 | 
			
		||||
                websocket.close()
 | 
			
		||||
            } else {
 | 
			
		||||
                console.debug("upgrading existing transport")
 | 
			
		||||
 | 
			
		||||
                // transport error handling takes over
 | 
			
		||||
                websocket.removeListener("error", onUpgradeError)
 | 
			
		||||
 | 
			
		||||
                const transport = new transports[req._query.transport](req)
 | 
			
		||||
                if (req._query && req._query.b64) {
 | 
			
		||||
                    transport.supportsBinary = false
 | 
			
		||||
                } else {
 | 
			
		||||
                    transport.supportsBinary = true
 | 
			
		||||
                }
 | 
			
		||||
                transport.perMessageDeflate = this.perMessageDeflate
 | 
			
		||||
                client.maybeUpgrade(transport)
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // transport error handling takes over
 | 
			
		||||
            websocket.removeListener("error", onUpgradeError)
 | 
			
		||||
 | 
			
		||||
            // const closeConnection = (errorCode, errorContext) =>
 | 
			
		||||
            //     abortUpgrade(socket, errorCode, errorContext)
 | 
			
		||||
            this.handshake(req._query.transport, req, () => { })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function onUpgradeError() {
 | 
			
		||||
            console.debug("websocket error before upgrade")
 | 
			
		||||
            // websocket.close() not needed
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Captures upgrade requests for a http.Server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {http.Server} server
 | 
			
		||||
     * @param {Object} options
 | 
			
		||||
     * @api public
 | 
			
		||||
     */
 | 
			
		||||
    attach(server, options: any = {}) {
 | 
			
		||||
        // let path = (options.path || "/engine.io").replace(/\/$/, "")
 | 
			
		||||
 | 
			
		||||
        // const destroyUpgradeTimeout = options.destroyUpgradeTimeout || 1000
 | 
			
		||||
 | 
			
		||||
        // // normalize path
 | 
			
		||||
        // path += "/"
 | 
			
		||||
 | 
			
		||||
        // function check(req) {
 | 
			
		||||
        //     return path === req.url.substr(0, path.length)
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        // cache and clean up listeners
 | 
			
		||||
        // const listeners = server.listeners("request").slice(0)
 | 
			
		||||
        // server.removeAllListeners("request")
 | 
			
		||||
        server.on("close", this.close.bind(this))
 | 
			
		||||
        // server.on("listening", this.init.bind(this))
 | 
			
		||||
        // @java-patch transfer to Netty Server
 | 
			
		||||
        server.on("connect", (request: Request, websocket: WebSocketClient) => {
 | 
			
		||||
            console.debug('Engine.IO connect client from', request.url)
 | 
			
		||||
            this.prepare(request)
 | 
			
		||||
            this.onWebSocket(request, undefined, websocket)
 | 
			
		||||
        })
 | 
			
		||||
        // set server as ws server
 | 
			
		||||
        this.ws = server
 | 
			
		||||
 | 
			
		||||
        // // add request handler
 | 
			
		||||
        // server.on("request", (req, res) => {
 | 
			
		||||
        //     if (check(req)) {
 | 
			
		||||
        //         debug('intercepting request for path "%s"', path)
 | 
			
		||||
        //         this.handleRequest(req, res)
 | 
			
		||||
        //     } else {
 | 
			
		||||
        //         let i = 0
 | 
			
		||||
        //         const l = listeners.length
 | 
			
		||||
        //         for (; i < l; i++) {
 | 
			
		||||
        //             listeners[i].call(server, req, res)
 | 
			
		||||
        //         }
 | 
			
		||||
        //     }
 | 
			
		||||
        // })
 | 
			
		||||
 | 
			
		||||
        // if (~this.opts.transports.indexOf("websocket")) {
 | 
			
		||||
        //     server.on("upgrade", (req, socket, head) => {
 | 
			
		||||
        //         if (check(req)) {
 | 
			
		||||
        //             this.handleUpgrade(req, socket, head)
 | 
			
		||||
        //         } else if (false !== options.destroyUpgrade) {
 | 
			
		||||
        //             // default node behavior is to disconnect when no handlers
 | 
			
		||||
        //             // but by adding a handler, we prevent that
 | 
			
		||||
        //             // and if no eio thing handles the upgrade
 | 
			
		||||
        //             // then the socket needs to die!
 | 
			
		||||
        //             setTimeout(function () {
 | 
			
		||||
        //                 if (socket.writable && socket.bytesWritten <= 0) {
 | 
			
		||||
        //                     return socket.end()
 | 
			
		||||
        //                 }
 | 
			
		||||
        //             }, destroyUpgradeTimeout)
 | 
			
		||||
        //         }
 | 
			
		||||
        //     })
 | 
			
		||||
        // }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// /**
 | 
			
		||||
//  * Close the HTTP long-polling request
 | 
			
		||||
//  *
 | 
			
		||||
//  * @param res - the response object
 | 
			
		||||
//  * @param errorCode - the error code
 | 
			
		||||
//  * @param errorContext - additional error context
 | 
			
		||||
//  *
 | 
			
		||||
//  * @api private
 | 
			
		||||
//  */
 | 
			
		||||
 | 
			
		||||
// function abortRequest(res, errorCode, errorContext) {
 | 
			
		||||
//     const statusCode = errorCode === Server.errors.FORBIDDEN ? 403 : 400
 | 
			
		||||
//     const message =
 | 
			
		||||
//         errorContext && errorContext.message
 | 
			
		||||
//             ? errorContext.message
 | 
			
		||||
//             : Server.errorMessages[errorCode]
 | 
			
		||||
 | 
			
		||||
//     res.writeHead(statusCode, { "Content-Type": "application/json" })
 | 
			
		||||
//     res.end(
 | 
			
		||||
//         JSON.stringify({
 | 
			
		||||
//             code: errorCode,
 | 
			
		||||
//             message
 | 
			
		||||
//         })
 | 
			
		||||
//     )
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// /**
 | 
			
		||||
//  * Close the WebSocket connection
 | 
			
		||||
//  *
 | 
			
		||||
//  * @param {net.Socket} socket
 | 
			
		||||
//  * @param {string} errorCode - the error code
 | 
			
		||||
//  * @param {object} errorContext - additional error context
 | 
			
		||||
//  *
 | 
			
		||||
//  * @api private
 | 
			
		||||
//  */
 | 
			
		||||
 | 
			
		||||
// function abortUpgrade(socket, errorCode, errorContext: any = {}) {
 | 
			
		||||
//     socket.on("error", () => {
 | 
			
		||||
//         debug("ignoring error from closed connection")
 | 
			
		||||
//     })
 | 
			
		||||
//     if (socket.writable) {
 | 
			
		||||
//         const message = errorContext.message || Server.errorMessages[errorCode]
 | 
			
		||||
//         const length = Buffer.byteLength(message)
 | 
			
		||||
//         socket.write(
 | 
			
		||||
//             "HTTP/1.1 400 Bad Request\r\n" +
 | 
			
		||||
//             "Connection: close\r\n" +
 | 
			
		||||
//             "Content-type: text/html\r\n" +
 | 
			
		||||
//             "Content-Length: " +
 | 
			
		||||
//             length +
 | 
			
		||||
//             "\r\n" +
 | 
			
		||||
//             "\r\n" +
 | 
			
		||||
//             message
 | 
			
		||||
//         )
 | 
			
		||||
//     }
 | 
			
		||||
//     socket.destroy()
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// module.exports = Server
 | 
			
		||||
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
 | 
			
		||||
// /**
 | 
			
		||||
//  * From https://github.com/nodejs/node/blob/v8.4.0/lib/_http_common.js#L303-L354
 | 
			
		||||
//  *
 | 
			
		||||
//  * True if val contains an invalid field-vchar
 | 
			
		||||
//  *  field-value    = *( field-content / obs-fold )
 | 
			
		||||
//  *  field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
 | 
			
		||||
//  *  field-vchar    = VCHAR / obs-text
 | 
			
		||||
//  *
 | 
			
		||||
//  * checkInvalidHeaderChar() is currently designed to be inlinable by v8,
 | 
			
		||||
//  * so take care when making changes to the implementation so that the source
 | 
			
		||||
//  * code size does not exceed v8's default max_inlined_source_size setting.
 | 
			
		||||
//  **/
 | 
			
		||||
// // prettier-ignore
 | 
			
		||||
// const validHdrChars = [
 | 
			
		||||
//     0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0 - 15
 | 
			
		||||
//     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 - 47
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 - 63
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 - 95
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 - 127
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 ...
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
			
		||||
//     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1  // ... 255
 | 
			
		||||
// ]
 | 
			
		||||
 | 
			
		||||
// function checkInvalidHeaderChar(val) {
 | 
			
		||||
//     val += ""
 | 
			
		||||
//     if (val.length < 1) return false
 | 
			
		||||
//     if (!validHdrChars[val.charCodeAt(0)]) {
 | 
			
		||||
//         debug('invalid header, index 0, char "%s"', val.charCodeAt(0))
 | 
			
		||||
//         return true
 | 
			
		||||
//     }
 | 
			
		||||
//     if (val.length < 2) return false
 | 
			
		||||
//     if (!validHdrChars[val.charCodeAt(1)]) {
 | 
			
		||||
//         debug('invalid header, index 1, char "%s"', val.charCodeAt(1))
 | 
			
		||||
//         return true
 | 
			
		||||
//     }
 | 
			
		||||
//     if (val.length < 3) return false
 | 
			
		||||
//     if (!validHdrChars[val.charCodeAt(2)]) {
 | 
			
		||||
//         debug('invalid header, index 2, char "%s"', val.charCodeAt(2))
 | 
			
		||||
//         return true
 | 
			
		||||
//     }
 | 
			
		||||
//     if (val.length < 4) return false
 | 
			
		||||
//     if (!validHdrChars[val.charCodeAt(3)]) {
 | 
			
		||||
//         debug('invalid header, index 3, char "%s"', val.charCodeAt(3))
 | 
			
		||||
//         return true
 | 
			
		||||
//     }
 | 
			
		||||
//     for (let i = 4; i < val.length; ++i) {
 | 
			
		||||
//         if (!validHdrChars[val.charCodeAt(i)]) {
 | 
			
		||||
//             debug('invalid header, index "%i", char "%s"', i, val.charCodeAt(i))
 | 
			
		||||
//             return true
 | 
			
		||||
//         }
 | 
			
		||||
//     }
 | 
			
		||||
//     return false
 | 
			
		||||
// }
 | 
			
		||||
							
								
								
									
										530
									
								
								packages/websocket/src/engine.io/socket.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										530
									
								
								packages/websocket/src/engine.io/socket.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,530 @@
 | 
			
		||||
import { EventEmitter } from "events"
 | 
			
		||||
import { Server } from "./server"
 | 
			
		||||
import { Transport } from "./transport"
 | 
			
		||||
import type { Request } from "../server/request"
 | 
			
		||||
// const debug = require("debug")("engine:socket")
 | 
			
		||||
 | 
			
		||||
export class Socket extends EventEmitter {
 | 
			
		||||
    public id: string
 | 
			
		||||
    private server: Server
 | 
			
		||||
    private upgrading = false
 | 
			
		||||
    private upgraded = false
 | 
			
		||||
    public readyState = "opening"
 | 
			
		||||
    private writeBuffer = []
 | 
			
		||||
    private packetsFn = []
 | 
			
		||||
    private sentCallbackFn = []
 | 
			
		||||
    private cleanupFn = []
 | 
			
		||||
    public request: Request
 | 
			
		||||
    public protocol: number
 | 
			
		||||
    public remoteAddress: any
 | 
			
		||||
    public transport: Transport
 | 
			
		||||
 | 
			
		||||
    private checkIntervalTimer: NodeJS.Timeout
 | 
			
		||||
    private upgradeTimeoutTimer: NodeJS.Timeout
 | 
			
		||||
    private pingTimeoutTimer: NodeJS.Timeout
 | 
			
		||||
    private pingIntervalTimer: NodeJS.Timeout
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Client class (abstract).
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    constructor(id: string, server: Server, transport: Transport, req: Request, protocol: number) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.id = id
 | 
			
		||||
        this.server = server
 | 
			
		||||
        this.request = req
 | 
			
		||||
        this.protocol = protocol
 | 
			
		||||
 | 
			
		||||
        // Cache IP since it might not be in the req later
 | 
			
		||||
        if (req.websocket && req.websocket._socket) {
 | 
			
		||||
            this.remoteAddress = req.websocket._socket.remoteAddress
 | 
			
		||||
        } else {
 | 
			
		||||
            this.remoteAddress = req.connection.remoteAddress
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.checkIntervalTimer = null
 | 
			
		||||
        this.upgradeTimeoutTimer = null
 | 
			
		||||
        this.pingTimeoutTimer = null
 | 
			
		||||
        this.pingIntervalTimer = null
 | 
			
		||||
 | 
			
		||||
        this.setTransport(transport)
 | 
			
		||||
        this.onOpen()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon transport considered open.
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    onOpen() {
 | 
			
		||||
        this.readyState = "open"
 | 
			
		||||
 | 
			
		||||
        // sends an `open` packet
 | 
			
		||||
        this.transport.sid = this.id
 | 
			
		||||
        this.sendPacket(
 | 
			
		||||
            "open",
 | 
			
		||||
            JSON.stringify({
 | 
			
		||||
                sid: this.id,
 | 
			
		||||
                upgrades: this.getAvailableUpgrades(),
 | 
			
		||||
                pingInterval: this.server.opts.pingInterval,
 | 
			
		||||
                pingTimeout: this.server.opts.pingTimeout
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if (this.server.opts.initialPacket) {
 | 
			
		||||
            this.sendPacket("message", this.server.opts.initialPacket)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.emit("open")
 | 
			
		||||
 | 
			
		||||
        if (this.protocol === 3) {
 | 
			
		||||
            // in protocol v3, the client sends a ping, and the server answers with a pong
 | 
			
		||||
            this.resetPingTimeout(
 | 
			
		||||
                this.server.opts.pingInterval + this.server.opts.pingTimeout
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            // in protocol v4, the server sends a ping, and the client answers with a pong
 | 
			
		||||
            this.schedulePing()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon transport packet.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} packet
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    onPacket(packet: { type: any; data: any }) {
 | 
			
		||||
        if ("open" !== this.readyState) {
 | 
			
		||||
            console.debug("packet received with closed socket")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        // export packet event
 | 
			
		||||
        // debug(`received packet ${packet.type}`)
 | 
			
		||||
        this.emit("packet", packet)
 | 
			
		||||
 | 
			
		||||
        // Reset ping timeout on any packet, incoming data is a good sign of
 | 
			
		||||
        // other side's liveness
 | 
			
		||||
        this.resetPingTimeout(
 | 
			
		||||
            this.server.opts.pingInterval + this.server.opts.pingTimeout
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        switch (packet.type) {
 | 
			
		||||
            case "ping":
 | 
			
		||||
                if (this.transport.protocol !== 3) {
 | 
			
		||||
                    this.onError("invalid heartbeat direction")
 | 
			
		||||
                    return
 | 
			
		||||
                }
 | 
			
		||||
                // debug("got ping")
 | 
			
		||||
                this.sendPacket("pong")
 | 
			
		||||
                this.emit("heartbeat")
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            case "pong":
 | 
			
		||||
                if (this.transport.protocol === 3) {
 | 
			
		||||
                    this.onError("invalid heartbeat direction")
 | 
			
		||||
                    return
 | 
			
		||||
                }
 | 
			
		||||
                // debug("got pong")
 | 
			
		||||
                this.schedulePing()
 | 
			
		||||
                this.emit("heartbeat")
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            case "error":
 | 
			
		||||
                this.onClose("parse error")
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            case "message":
 | 
			
		||||
                this.emit("data", packet.data)
 | 
			
		||||
                this.emit("message", packet.data)
 | 
			
		||||
                break
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon transport error.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Error} error object
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    onError(err: string) {
 | 
			
		||||
        // debug("transport error")
 | 
			
		||||
        this.onClose("transport error", err)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Pings client every `this.pingInterval` and expects response
 | 
			
		||||
     * within `this.pingTimeout` or closes connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    schedulePing() {
 | 
			
		||||
        clearTimeout(this.pingIntervalTimer)
 | 
			
		||||
        this.pingIntervalTimer = setTimeout(() => {
 | 
			
		||||
            // debug(
 | 
			
		||||
            //     "writing ping packet - expecting pong within %sms",
 | 
			
		||||
            //     this.server.opts.pingTimeout
 | 
			
		||||
            // )
 | 
			
		||||
            this.sendPacket("ping")
 | 
			
		||||
            this.resetPingTimeout(this.server.opts.pingTimeout)
 | 
			
		||||
        }, this.server.opts.pingInterval)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Resets ping timeout.
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    resetPingTimeout(timeout: number) {
 | 
			
		||||
        clearTimeout(this.pingTimeoutTimer)
 | 
			
		||||
        this.pingTimeoutTimer = setTimeout(() => {
 | 
			
		||||
            if (this.readyState === "closed") return
 | 
			
		||||
            this.onClose("ping timeout")
 | 
			
		||||
        }, timeout)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attaches handlers for the given transport.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Transport} transport
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    setTransport(transport: Transport) {
 | 
			
		||||
        console.debug(`engine.io socket ${this.id} set transport ${transport.name}`)
 | 
			
		||||
        const onError = this.onError.bind(this)
 | 
			
		||||
        const onPacket = this.onPacket.bind(this)
 | 
			
		||||
        const flush = this.flush.bind(this)
 | 
			
		||||
        const onClose = this.onClose.bind(this, "transport close")
 | 
			
		||||
 | 
			
		||||
        this.transport = transport
 | 
			
		||||
        this.transport.once("error", onError)
 | 
			
		||||
        this.transport.on("packet", onPacket)
 | 
			
		||||
        this.transport.on("drain", flush)
 | 
			
		||||
        this.transport.once("close", onClose)
 | 
			
		||||
        // this function will manage packet events (also message callbacks)
 | 
			
		||||
        this.setupSendCallback()
 | 
			
		||||
 | 
			
		||||
        this.cleanupFn.push(function () {
 | 
			
		||||
            transport.removeListener("error", onError)
 | 
			
		||||
            transport.removeListener("packet", onPacket)
 | 
			
		||||
            transport.removeListener("drain", flush)
 | 
			
		||||
            transport.removeListener("close", onClose)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Upgrades socket to the given transport
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Transport} transport
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    maybeUpgrade(transport: Transport) {
 | 
			
		||||
        console.debug(
 | 
			
		||||
            'might upgrade socket transport from "', this.transport.name, '" to "', transport.name, '"'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        this.upgrading = true
 | 
			
		||||
 | 
			
		||||
        // set transport upgrade timer
 | 
			
		||||
        this.upgradeTimeoutTimer = setTimeout(() => {
 | 
			
		||||
            console.debug("client did not complete upgrade - closing transport")
 | 
			
		||||
            cleanup()
 | 
			
		||||
            if ("open" === transport.readyState) {
 | 
			
		||||
                transport.close()
 | 
			
		||||
            }
 | 
			
		||||
        }, this.server.opts.upgradeTimeout)
 | 
			
		||||
 | 
			
		||||
        const onPacket = (packet: { type: string; data: string }) => {
 | 
			
		||||
            if ("ping" === packet.type && "probe" === packet.data) {
 | 
			
		||||
                transport.send([{ type: "pong", data: "probe" }])
 | 
			
		||||
                this.emit("upgrading", transport)
 | 
			
		||||
                clearInterval(this.checkIntervalTimer)
 | 
			
		||||
                this.checkIntervalTimer = setInterval(check, 100)
 | 
			
		||||
            } else if ("upgrade" === packet.type && this.readyState !== "closed") {
 | 
			
		||||
                // debug("got upgrade packet - upgrading")
 | 
			
		||||
                cleanup()
 | 
			
		||||
                this.transport.discard()
 | 
			
		||||
                this.upgraded = true
 | 
			
		||||
                this.clearTransport()
 | 
			
		||||
                this.setTransport(transport)
 | 
			
		||||
                this.emit("upgrade", transport)
 | 
			
		||||
                this.flush()
 | 
			
		||||
                if (this.readyState === "closing") {
 | 
			
		||||
                    transport.close(() => {
 | 
			
		||||
                        this.onClose("forced close")
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                cleanup()
 | 
			
		||||
                transport.close()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // we force a polling cycle to ensure a fast upgrade
 | 
			
		||||
        const check = () => {
 | 
			
		||||
            if ("polling" === this.transport.name && this.transport.writable) {
 | 
			
		||||
                // debug("writing a noop packet to polling for fast upgrade")
 | 
			
		||||
                this.transport.send([{ type: "noop" }])
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const cleanup = () => {
 | 
			
		||||
            this.upgrading = false
 | 
			
		||||
 | 
			
		||||
            clearInterval(this.checkIntervalTimer)
 | 
			
		||||
            this.checkIntervalTimer = null
 | 
			
		||||
 | 
			
		||||
            clearTimeout(this.upgradeTimeoutTimer)
 | 
			
		||||
            this.upgradeTimeoutTimer = null
 | 
			
		||||
 | 
			
		||||
            transport.removeListener("packet", onPacket)
 | 
			
		||||
            transport.removeListener("close", onTransportClose)
 | 
			
		||||
            transport.removeListener("error", onError)
 | 
			
		||||
            this.removeListener("close", onClose)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const onError = (err: string) => {
 | 
			
		||||
            // debug("client did not complete upgrade - %s", err)
 | 
			
		||||
            cleanup()
 | 
			
		||||
            transport.close()
 | 
			
		||||
            transport = null
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const onTransportClose = () => {
 | 
			
		||||
            onError("transport closed")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const onClose = () => {
 | 
			
		||||
            onError("socket closed")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        transport.on("packet", onPacket)
 | 
			
		||||
        transport.once("close", onTransportClose)
 | 
			
		||||
        transport.once("error", onError)
 | 
			
		||||
 | 
			
		||||
        this.once("close", onClose)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clears listeners and timers associated with current transport.
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    clearTransport() {
 | 
			
		||||
        let cleanup: () => void
 | 
			
		||||
 | 
			
		||||
        const toCleanUp = this.cleanupFn.length
 | 
			
		||||
 | 
			
		||||
        for (let i = 0; i < toCleanUp; i++) {
 | 
			
		||||
            cleanup = this.cleanupFn.shift()
 | 
			
		||||
            cleanup()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // silence further transport errors and prevent uncaught exceptions
 | 
			
		||||
        this.transport.on("error", function () {
 | 
			
		||||
            // debug("error triggered by discarded transport")
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        // ensure transport won't stay open
 | 
			
		||||
        this.transport.close()
 | 
			
		||||
 | 
			
		||||
        clearTimeout(this.pingTimeoutTimer)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon transport considered closed.
 | 
			
		||||
     * Possible reasons: `ping timeout`, `client error`, `parse error`,
 | 
			
		||||
     * `transport error`, `server close`, `transport close`
 | 
			
		||||
     */
 | 
			
		||||
    onClose(reason: string, description?: string) {
 | 
			
		||||
        if ("closed" !== this.readyState) {
 | 
			
		||||
            this.readyState = "closed"
 | 
			
		||||
 | 
			
		||||
            // clear timers
 | 
			
		||||
            clearTimeout(this.pingIntervalTimer)
 | 
			
		||||
            clearTimeout(this.pingTimeoutTimer)
 | 
			
		||||
 | 
			
		||||
            clearInterval(this.checkIntervalTimer)
 | 
			
		||||
            this.checkIntervalTimer = null
 | 
			
		||||
            clearTimeout(this.upgradeTimeoutTimer)
 | 
			
		||||
            // clean writeBuffer in next tick, so developers can still
 | 
			
		||||
            // grab the writeBuffer on 'close' event
 | 
			
		||||
            process.nextTick(() => {
 | 
			
		||||
                this.writeBuffer = []
 | 
			
		||||
            })
 | 
			
		||||
            this.packetsFn = []
 | 
			
		||||
            this.sentCallbackFn = []
 | 
			
		||||
            this.clearTransport()
 | 
			
		||||
            this.emit("close", reason, description)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Setup and manage send callback
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    setupSendCallback() {
 | 
			
		||||
        // the message was sent successfully, execute the callback
 | 
			
		||||
        const onDrain = () => {
 | 
			
		||||
            if (this.sentCallbackFn.length > 0) {
 | 
			
		||||
                const seqFn = this.sentCallbackFn.splice(0, 1)[0]
 | 
			
		||||
                if ("function" === typeof seqFn) {
 | 
			
		||||
                    // debug("executing send callback")
 | 
			
		||||
                    seqFn(this.transport)
 | 
			
		||||
                } else if (Array.isArray(seqFn)) {
 | 
			
		||||
                    // debug("executing batch send callback")
 | 
			
		||||
                    const l = seqFn.length
 | 
			
		||||
                    let i = 0
 | 
			
		||||
                    for (; i < l; i++) {
 | 
			
		||||
                        if ("function" === typeof seqFn[i]) {
 | 
			
		||||
                            seqFn[i](this.transport)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.transport.on("drain", onDrain)
 | 
			
		||||
 | 
			
		||||
        this.cleanupFn.push(() => {
 | 
			
		||||
            this.transport.removeListener("drain", onDrain)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a message packet.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} message
 | 
			
		||||
     * @param {Object} options
 | 
			
		||||
     * @param {Function} callback
 | 
			
		||||
     * @return {Socket} for chaining
 | 
			
		||||
     * @api public
 | 
			
		||||
     */
 | 
			
		||||
    send(data: any, options: any, callback: any) {
 | 
			
		||||
        this.sendPacket("message", data, options, callback)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    write(data: any, options: any, callback?: any) {
 | 
			
		||||
        this.sendPacket("message", data, options, callback)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a packet.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} packet type
 | 
			
		||||
     * @param {String} optional, data
 | 
			
		||||
     * @param {Object} options
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    sendPacket(type: string, data?: string, options?: { compress?: any }, callback?: undefined) {
 | 
			
		||||
        if ("function" === typeof options) {
 | 
			
		||||
            callback = options
 | 
			
		||||
            options = null
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        options = options || {}
 | 
			
		||||
        options.compress = false !== options.compress
 | 
			
		||||
 | 
			
		||||
        if ("closing" !== this.readyState && "closed" !== this.readyState) {
 | 
			
		||||
            // console.debug('sending packet "%s" (%s)', type, data)
 | 
			
		||||
 | 
			
		||||
            const packet: any = {
 | 
			
		||||
                type: type,
 | 
			
		||||
                options: options
 | 
			
		||||
            }
 | 
			
		||||
            if (data) packet.data = data
 | 
			
		||||
 | 
			
		||||
            // exports packetCreate event
 | 
			
		||||
            this.emit("packetCreate", packet)
 | 
			
		||||
 | 
			
		||||
            this.writeBuffer.push(packet)
 | 
			
		||||
 | 
			
		||||
            // add send callback to object, if defined
 | 
			
		||||
            if (callback) this.packetsFn.push(callback)
 | 
			
		||||
 | 
			
		||||
            this.flush()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attempts to flush the packets buffer.
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    flush() {
 | 
			
		||||
        if (
 | 
			
		||||
            "closed" !== this.readyState &&
 | 
			
		||||
            this.transport.writable &&
 | 
			
		||||
            this.writeBuffer.length
 | 
			
		||||
        ) {
 | 
			
		||||
            console.trace("flushing buffer to transport")
 | 
			
		||||
            this.emit("flush", this.writeBuffer)
 | 
			
		||||
            this.server.emit("flush", this, this.writeBuffer)
 | 
			
		||||
            const wbuf = this.writeBuffer
 | 
			
		||||
            this.writeBuffer = []
 | 
			
		||||
            if (!this.transport.supportsFraming) {
 | 
			
		||||
                this.sentCallbackFn.push(this.packetsFn)
 | 
			
		||||
            } else {
 | 
			
		||||
                this.sentCallbackFn.push.apply(this.sentCallbackFn, this.packetsFn)
 | 
			
		||||
            }
 | 
			
		||||
            this.packetsFn = []
 | 
			
		||||
            this.transport.send(wbuf)
 | 
			
		||||
            this.emit("drain")
 | 
			
		||||
            this.server.emit("drain", this)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get available upgrades for this socket.
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    getAvailableUpgrades() {
 | 
			
		||||
        const availableUpgrades = []
 | 
			
		||||
        const allUpgrades = this.server.upgrades(this.transport.name)
 | 
			
		||||
        let i = 0
 | 
			
		||||
        const l = allUpgrades.length
 | 
			
		||||
        for (; i < l; ++i) {
 | 
			
		||||
            const upg = allUpgrades[i]
 | 
			
		||||
            if (this.server.opts.transports.indexOf(upg) !== -1) {
 | 
			
		||||
                availableUpgrades.push(upg)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return availableUpgrades
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Closes the socket and underlying transport.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Boolean} optional, discard
 | 
			
		||||
     * @return {Socket} for chaining
 | 
			
		||||
     * @api public
 | 
			
		||||
     */
 | 
			
		||||
    close(discard?: any) {
 | 
			
		||||
        if ("open" !== this.readyState) return
 | 
			
		||||
 | 
			
		||||
        this.readyState = "closing"
 | 
			
		||||
 | 
			
		||||
        if (this.writeBuffer.length) {
 | 
			
		||||
            this.once("drain", this.closeTransport.bind(this, discard))
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.closeTransport(discard)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Closes the underlying transport.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Boolean} discard
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    closeTransport(discard: any) {
 | 
			
		||||
        if (discard) this.transport.discard()
 | 
			
		||||
        this.transport.close(this.onClose.bind(this, "forced close"))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										121
									
								
								packages/websocket/src/engine.io/transport.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								packages/websocket/src/engine.io/transport.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
import parser_v4 from "../engine.io-parser"
 | 
			
		||||
import type { WebSocketClient } from '../server/client'
 | 
			
		||||
/**
 | 
			
		||||
 * Noop function.
 | 
			
		||||
 *
 | 
			
		||||
 * @api private
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
function noop() { }
 | 
			
		||||
 | 
			
		||||
export abstract class Transport extends EventEmitter {
 | 
			
		||||
    public sid: string
 | 
			
		||||
    public req /**http.IncomingMessage */
 | 
			
		||||
    public socket: WebSocketClient
 | 
			
		||||
    public writable: boolean
 | 
			
		||||
    public readyState: string
 | 
			
		||||
    public discarded: boolean
 | 
			
		||||
    public protocol: Number
 | 
			
		||||
    public parser: any
 | 
			
		||||
    public perMessageDeflate: any
 | 
			
		||||
    public supportsBinary: boolean = false
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Transport constructor.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {http.IncomingMessage} request
 | 
			
		||||
     * @api public
 | 
			
		||||
     */
 | 
			
		||||
    constructor(req) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.readyState = "open"
 | 
			
		||||
        this.discarded = false
 | 
			
		||||
        this.protocol = req._query.EIO === "4" ? 4 : 3 // 3rd revision by default
 | 
			
		||||
        this.parser = parser_v4//= this.protocol === 4 ? parser_v4 : parser_v3
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Flags the transport as discarded.
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    discard() {
 | 
			
		||||
        this.discarded = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called with an incoming HTTP request.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {http.IncomingMessage} request
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    onRequest(req) {
 | 
			
		||||
        console.debug(`engine.io transport ${this.socket.id} setting request`, JSON.stringify(req))
 | 
			
		||||
        this.req = req
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Closes the transport.
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    close(fn?) {
 | 
			
		||||
        if ("closed" === this.readyState || "closing" === this.readyState) return
 | 
			
		||||
 | 
			
		||||
        this.readyState = "closing"
 | 
			
		||||
        this.doClose(fn || noop)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called with a transport error.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} message error
 | 
			
		||||
     * @param {Object} error description
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    onError(msg: string, desc?: string) {
 | 
			
		||||
        if (this.listeners("error").length) {
 | 
			
		||||
            const err: any = new Error(msg)
 | 
			
		||||
            err.type = "TransportError"
 | 
			
		||||
            err.description = desc
 | 
			
		||||
            this.emit("error", err)
 | 
			
		||||
        } else {
 | 
			
		||||
            console.debug(`ignored transport error ${msg} (${desc})`)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called with parsed out a packets from the data stream.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} packet
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    onPacket(packet) {
 | 
			
		||||
        this.emit("packet", packet)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called with the encoded packet data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} data
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    onData(data) {
 | 
			
		||||
        this.onPacket(this.parser.decodePacket(data))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon transport close.
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    onClose() {
 | 
			
		||||
        this.readyState = "closed"
 | 
			
		||||
        this.emit("close")
 | 
			
		||||
    }
 | 
			
		||||
    abstract get supportsFraming()
 | 
			
		||||
    abstract get name()
 | 
			
		||||
    abstract send(...args: any[])
 | 
			
		||||
    abstract doClose(d: Function)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								packages/websocket/src/engine.io/transports/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/websocket/src/engine.io/transports/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
export default {
 | 
			
		||||
    websocket: require("./websocket").WebSocket
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										116
									
								
								packages/websocket/src/engine.io/transports/websocket.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								packages/websocket/src/engine.io/transports/websocket.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
import { Transport } from '../transport'
 | 
			
		||||
// const debug = require("debug")("engine:ws")
 | 
			
		||||
 | 
			
		||||
export class WebSocket extends Transport {
 | 
			
		||||
    public perMessageDeflate: any
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * WebSocket transport
 | 
			
		||||
     *
 | 
			
		||||
     * @param {http.IncomingMessage}
 | 
			
		||||
     * @api public
 | 
			
		||||
     */
 | 
			
		||||
    constructor(req) {
 | 
			
		||||
        super(req)
 | 
			
		||||
        this.socket = req.websocket
 | 
			
		||||
        this.socket.on("message", this.onData.bind(this))
 | 
			
		||||
        this.socket.once("close", this.onClose.bind(this))
 | 
			
		||||
        this.socket.on("error", this.onError.bind(this))
 | 
			
		||||
        this.writable = true
 | 
			
		||||
        this.perMessageDeflate = null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Transport name
 | 
			
		||||
     *
 | 
			
		||||
     * @api public
 | 
			
		||||
     */
 | 
			
		||||
    get name() {
 | 
			
		||||
        return "websocket"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Advertise upgrade support.
 | 
			
		||||
     *
 | 
			
		||||
     * @api public
 | 
			
		||||
     */
 | 
			
		||||
    get handlesUpgrades() {
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Advertise framing support.
 | 
			
		||||
     *
 | 
			
		||||
     * @api public
 | 
			
		||||
     */
 | 
			
		||||
    get supportsFraming() {
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Processes the incoming data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} encoded packet
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    onData(data) {
 | 
			
		||||
        // debug('received "%s"', data)
 | 
			
		||||
        super.onData(data)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Writes a packet payload.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Array} packets
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    send(packets) {
 | 
			
		||||
        // console.log('WebSocket send packets', JSON.stringify(packets))
 | 
			
		||||
        const packet = packets.shift()
 | 
			
		||||
        if (typeof packet === "undefined") {
 | 
			
		||||
            this.writable = true
 | 
			
		||||
            this.emit("drain")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // always creates a new object since ws modifies it
 | 
			
		||||
        const opts: any = {}
 | 
			
		||||
        if (packet.options) {
 | 
			
		||||
            opts.compress = packet.options.compress
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const send = data => {
 | 
			
		||||
            if (this.perMessageDeflate) {
 | 
			
		||||
                const len =
 | 
			
		||||
                    "string" === typeof data ? Buffer.byteLength(data) : data.length
 | 
			
		||||
                if (len < this.perMessageDeflate.threshold) {
 | 
			
		||||
                    opts.compress = false
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            console.trace('writing', data)
 | 
			
		||||
            this.writable = false
 | 
			
		||||
 | 
			
		||||
            this.socket.send(data, opts, err => {
 | 
			
		||||
                if (err) return this.onError("write error", err.stack)
 | 
			
		||||
                this.send(packets)
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (packet.options && typeof packet.options.wsPreEncoded === "string") {
 | 
			
		||||
            send(packet.options.wsPreEncoded)
 | 
			
		||||
        } else {
 | 
			
		||||
            this.parser.encodePacket(packet, this.supportsBinary, send)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Closes the transport.
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    doClose(fn) {
 | 
			
		||||
        // debug("closing")
 | 
			
		||||
        this.socket.close()
 | 
			
		||||
        fn && fn()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
/// <reference types="@ccms/nashorn" />
 | 
			
		||||
/// <reference types="@javatypes/tomcat-websocket-api" />
 | 
			
		||||
 | 
			
		||||
import { Server, ServerOptions } from './socket-io'
 | 
			
		||||
import { Server, ServerOptions } from './socket.io'
 | 
			
		||||
 | 
			
		||||
interface SocketIOStatic {
 | 
			
		||||
    /**
 | 
			
		||||
@@ -44,7 +44,7 @@ let io: SocketStatic = function (pipeline: any, options: Partial<ServerOptions>)
 | 
			
		||||
}
 | 
			
		||||
io.Instance = Symbol("@ccms/websocket")
 | 
			
		||||
export default io
 | 
			
		||||
export * from './socket-io'
 | 
			
		||||
export * from './socket.io'
 | 
			
		||||
export * from './client'
 | 
			
		||||
export * from './server'
 | 
			
		||||
export * from './transport'
 | 
			
		||||
export * from './engine.io/transport'
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,26 @@
 | 
			
		||||
import { Transport } from '../transport'
 | 
			
		||||
import { AttributeKeys } from './constants'
 | 
			
		||||
import { WebSocketClient } from '../server/client'
 | 
			
		||||
 | 
			
		||||
const TextWebSocketFrame = Java.type('io.netty.handler.codec.http.websocketx.TextWebSocketFrame')
 | 
			
		||||
 | 
			
		||||
export class NettyClient extends Transport {
 | 
			
		||||
export class NettyClient extends WebSocketClient {
 | 
			
		||||
    private channel: any
 | 
			
		||||
 | 
			
		||||
    constructor(server: any, channel: any) {
 | 
			
		||||
        super(server)
 | 
			
		||||
        this.remoteAddress = channel.remoteAddress() + ''
 | 
			
		||||
        this.request = channel.attr(AttributeKeys.Request).get()
 | 
			
		||||
 | 
			
		||||
        this._id = channel.id() + ''
 | 
			
		||||
    constructor(channel: any) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.id = channel.id() + ''
 | 
			
		||||
        this.channel = channel
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    doSend(text: string) {
 | 
			
		||||
        this.channel.writeAndFlush(new TextWebSocketFrame(text))
 | 
			
		||||
    send(text: string, opts?: any, callback?: (err?: Error) => void) {
 | 
			
		||||
        try {
 | 
			
		||||
            this.channel.writeAndFlush(new TextWebSocketFrame(text))
 | 
			
		||||
            callback?.()
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            callback?.(error)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    doClose() {
 | 
			
		||||
 | 
			
		||||
    close() {
 | 
			
		||||
        this.channel.close()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { JavaServerOptions } from '../server'
 | 
			
		||||
 | 
			
		||||
import { HttpRequestHandlerAdapter } from './adapter'
 | 
			
		||||
import { AttributeKeys } from './constants'
 | 
			
		||||
import { ServerOptions } from 'socket-io'
 | 
			
		||||
 | 
			
		||||
const DefaultHttpResponse = Java.type('io.netty.handler.codec.http.DefaultHttpResponse')
 | 
			
		||||
const DefaultFullHttpResponse = Java.type('io.netty.handler.codec.http.DefaultFullHttpResponse')
 | 
			
		||||
@@ -18,7 +19,7 @@ const ChannelFutureListener = Java.type('io.netty.channel.ChannelFutureListener'
 | 
			
		||||
export class HttpRequestHandler extends HttpRequestHandlerAdapter {
 | 
			
		||||
    private ws: string
 | 
			
		||||
    private root: string
 | 
			
		||||
    constructor(options: ServerOptions) {
 | 
			
		||||
    constructor(options: JavaServerOptions) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.root = options.root
 | 
			
		||||
        this.ws = options.path
 | 
			
		||||
 
 | 
			
		||||
@@ -1,65 +1,70 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
 | 
			
		||||
import { ServerOptions } from '../socket-io'
 | 
			
		||||
import { ServerEvent } from '../socket-io/constants'
 | 
			
		||||
import { JavaServerOptions, ServerEvent, WebSocketServer } from '../server'
 | 
			
		||||
import { Request } from '../server/request'
 | 
			
		||||
 | 
			
		||||
import { NettyClient } from './client'
 | 
			
		||||
import { Keys } from './constants'
 | 
			
		||||
import { AttributeKeys, Keys } from './constants'
 | 
			
		||||
import { WebSocketDetect } from './websocket_detect'
 | 
			
		||||
import { WebSocketHandler } from './websocket_handler'
 | 
			
		||||
 | 
			
		||||
class NettyWebSocketServer extends EventEmitter {
 | 
			
		||||
    private pipeline: any
 | 
			
		||||
    private clients: Map<string, NettyClient>
 | 
			
		||||
class NettyWebSocketServer extends WebSocketServer {
 | 
			
		||||
    constructor(pipeline: any, options: JavaServerOptions) {
 | 
			
		||||
        super(pipeline, options)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor(pipeline: any, options: ServerOptions) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.clients = new Map()
 | 
			
		||||
        this.pipeline = pipeline
 | 
			
		||||
        let connectEvent = options.event
 | 
			
		||||
        try { this.pipeline.remove(Keys.Detect) } catch (error) { }
 | 
			
		||||
        this.pipeline.addFirst(Keys.Detect, new WebSocketDetect(connectEvent).getHandler())
 | 
			
		||||
    protected initialize() {
 | 
			
		||||
        let connectEvent = this.options.event
 | 
			
		||||
        try { this.instance.remove(Keys.Detect) } catch (error) { }
 | 
			
		||||
        this.instance.addFirst(Keys.Detect, new WebSocketDetect(connectEvent).getHandler())
 | 
			
		||||
        connectEvent.on(ServerEvent.detect, (ctx, channel) => {
 | 
			
		||||
            channel.pipeline().addFirst(Keys.Handler, new WebSocketHandler(options).getHandler())
 | 
			
		||||
            channel.pipeline().addFirst(Keys.Handler, new WebSocketHandler(this.options).getHandler())
 | 
			
		||||
            ctx.fireChannelRead(channel)
 | 
			
		||||
        })
 | 
			
		||||
        connectEvent.on(ServerEvent.connect, (ctx) => {
 | 
			
		||||
            let cid = ctx?.channel().id() + ''
 | 
			
		||||
            let nettyClient = new NettyClient(this, ctx.channel())
 | 
			
		||||
            this.clients.set(cid, nettyClient)
 | 
			
		||||
            this.emit(ServerEvent.connect, nettyClient)
 | 
			
		||||
            this.onconnect(ctx)
 | 
			
		||||
        })
 | 
			
		||||
        connectEvent.on(ServerEvent.message, (ctx, msg) => {
 | 
			
		||||
            let cid = ctx?.channel().id() + ''
 | 
			
		||||
            if (this.clients.has(cid)) {
 | 
			
		||||
                this.emit(ServerEvent.message, this.clients.get(cid), msg.text())
 | 
			
		||||
            } else if (global.debug) {
 | 
			
		||||
                console.error(`unknow client ${ctx} reciver message ${msg.text()}`)
 | 
			
		||||
            }
 | 
			
		||||
            this.onmessage(ctx, msg.text())
 | 
			
		||||
        })
 | 
			
		||||
        connectEvent.on(ServerEvent.disconnect, (ctx, cause) => {
 | 
			
		||||
            let cid = ctx?.channel().id() + ''
 | 
			
		||||
            if (this.clients.has(cid)) {
 | 
			
		||||
                this.emit(ServerEvent.disconnect, this.clients.get(cid), cause)
 | 
			
		||||
            } else if (global.debug) {
 | 
			
		||||
                console.error(`unknow client ${ctx} disconnect cause ${cause}`)
 | 
			
		||||
            }
 | 
			
		||||
            this.ondisconnect(ctx, cause)
 | 
			
		||||
        })
 | 
			
		||||
        connectEvent.on(ServerEvent.error, (ctx, cause) => {
 | 
			
		||||
            let cid = ctx?.channel().id() + ''
 | 
			
		||||
            if (this.clients.has(cid)) {
 | 
			
		||||
                this.emit(ServerEvent.error, this.clients.get(cid), cause)
 | 
			
		||||
            } else if (global.debug) {
 | 
			
		||||
                console.error(`unknow client ${ctx} cause error ${cause}`)
 | 
			
		||||
                console.ex(cause)
 | 
			
		||||
            }
 | 
			
		||||
        connectEvent.on(ServerEvent.error, (ctx, error) => {
 | 
			
		||||
            this.onerror(ctx, error)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    close() {
 | 
			
		||||
        if (this.pipeline.names().contains(Keys.Detect)) {
 | 
			
		||||
            this.pipeline.remove(Keys.Detect)
 | 
			
		||||
 | 
			
		||||
    protected getId(ctx: any) {
 | 
			
		||||
        try {
 | 
			
		||||
            return ctx.channel().id() + ''
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            console.log(Object.toString.apply(ctx))
 | 
			
		||||
            console.ex(error)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected getRequest(ctx) {
 | 
			
		||||
        let channel = ctx.channel()
 | 
			
		||||
        let req = channel.attr(AttributeKeys.Request).get()
 | 
			
		||||
        let headers = {}
 | 
			
		||||
        let nativeHeaders = req.headers()
 | 
			
		||||
        nativeHeaders.forEach(function (header) {
 | 
			
		||||
            headers[header.getKey()] = header.getValue()
 | 
			
		||||
        })
 | 
			
		||||
        let request = new Request(req.uri(), req.method().name(), headers)
 | 
			
		||||
        request.connection = {
 | 
			
		||||
            remoteAddress: channel.remoteAddress() + ''
 | 
			
		||||
        }
 | 
			
		||||
        return request
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected getSocket(ctx) {
 | 
			
		||||
        return new NettyClient(ctx.channel())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected doClose() {
 | 
			
		||||
        if (this.instance.names().contains(Keys.Detect)) {
 | 
			
		||||
            this.instance.remove(Keys.Detect)
 | 
			
		||||
        }
 | 
			
		||||
        this.clients.forEach(client => client.close())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
import { ServerOptions } from '../socket-io'
 | 
			
		||||
import { ServerEvent } from '../socket-io/constants'
 | 
			
		||||
import { JavaServerOptions, ServerEvent } from '../server'
 | 
			
		||||
 | 
			
		||||
import { TextWebSocketFrameHandlerAdapter } from './adapter'
 | 
			
		||||
 | 
			
		||||
export class TextWebSocketFrameHandler extends TextWebSocketFrameHandlerAdapter {
 | 
			
		||||
    private event: EventEmitter
 | 
			
		||||
    constructor(options: ServerOptions) {
 | 
			
		||||
    constructor(options: JavaServerOptions) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.event = options.event
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
import { WebSocketHandlerAdapter } from "./adapter"
 | 
			
		||||
import { ServerEvent } from '../socket-io/constants'
 | 
			
		||||
 | 
			
		||||
import { ServerEvent } from '../server'
 | 
			
		||||
 | 
			
		||||
export class WebSocketDetect extends WebSocketHandlerAdapter {
 | 
			
		||||
    private event: EventEmitter
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import { ServerOptions } from '../socket-io'
 | 
			
		||||
import { ServerEvent } from '../socket-io/constants'
 | 
			
		||||
import { JavaServerOptions, ServerEvent } from '../server'
 | 
			
		||||
 | 
			
		||||
import { Keys } from './constants'
 | 
			
		||||
import { HttpRequestHandler } from './httprequest'
 | 
			
		||||
@@ -13,8 +12,8 @@ const HttpObjectAggregator = Java.type('io.netty.handler.codec.http.HttpObjectAg
 | 
			
		||||
const WebSocketServerProtocolHandler = Java.type('io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler')
 | 
			
		||||
 | 
			
		||||
export class WebSocketHandler extends WebSocketHandlerAdapter {
 | 
			
		||||
    private options: ServerOptions
 | 
			
		||||
    constructor(options: ServerOptions) {
 | 
			
		||||
    private options: JavaServerOptions
 | 
			
		||||
    constructor(options: JavaServerOptions) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.options = options
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/websocket/src/server/client.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/websocket/src/server/client.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
export abstract class WebSocketClient extends EventEmitter {
 | 
			
		||||
    public id: string
 | 
			
		||||
    public _socket: any
 | 
			
		||||
    abstract send(text: string, opts?: any, callback?: (err?: Error) => void)
 | 
			
		||||
    abstract close()
 | 
			
		||||
}
 | 
			
		||||
@@ -1,52 +1,90 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
import { ServerOptions } from '../socket.io'
 | 
			
		||||
import { WebSocketClient } from './client'
 | 
			
		||||
 | 
			
		||||
import { Transport } from '../transport'
 | 
			
		||||
import type { Request } from './request'
 | 
			
		||||
 | 
			
		||||
interface ServerOptions {
 | 
			
		||||
export enum ServerEvent {
 | 
			
		||||
    detect = 'detect',
 | 
			
		||||
    request = 'request',
 | 
			
		||||
    upgrade = 'upgrade',
 | 
			
		||||
    connect = 'connect',
 | 
			
		||||
    connection = 'connection',
 | 
			
		||||
    message = 'message',
 | 
			
		||||
    error = 'error',
 | 
			
		||||
    disconnecting = 'disconnecting',
 | 
			
		||||
    disconnect = 'disconnect',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface JavaServerOptions extends ServerOptions {
 | 
			
		||||
    event?: EventEmitter
 | 
			
		||||
    root?: string
 | 
			
		||||
    /**
 | 
			
		||||
     * name of the path to capture
 | 
			
		||||
     * @default "/socket.io"
 | 
			
		||||
     */
 | 
			
		||||
    path: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface WebSocketServerImpl extends EventEmitter {
 | 
			
		||||
    close(): void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class WebSocketServer extends EventEmitter {
 | 
			
		||||
    options: Partial<ServerOptions>
 | 
			
		||||
    private websocketServer: WebSocketServerImpl
 | 
			
		||||
 | 
			
		||||
    constructor(instance: any, options: Partial<ServerOptions>) {
 | 
			
		||||
export abstract class WebSocketServer extends EventEmitter {
 | 
			
		||||
    protected instance: any
 | 
			
		||||
    protected options: JavaServerOptions
 | 
			
		||||
    private clients: Map<string, WebSocketClient>
 | 
			
		||||
    constructor(instance: any, options: JavaServerOptions) {
 | 
			
		||||
        super()
 | 
			
		||||
        if (!instance) { throw new Error('instance can\'t be undefiend!') }
 | 
			
		||||
        this.options = Object.assign({
 | 
			
		||||
            event: new EventEmitter(),
 | 
			
		||||
            path: '/ws',
 | 
			
		||||
            root: root + '/wwwroot',
 | 
			
		||||
        }, options)
 | 
			
		||||
        this.selectServerImpl(instance)
 | 
			
		||||
        this.instance = instance
 | 
			
		||||
        this.options = options
 | 
			
		||||
        this.clients = new Map()
 | 
			
		||||
        console.debug('create websocket server from ' + this.constructor.name)
 | 
			
		||||
        this.initialize()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    on(event: "connect", cb: (transport: Transport) => void): this
 | 
			
		||||
    on(event: "message", cb: (transport: Transport, text: string) => void): this
 | 
			
		||||
    on(event: "disconnect", cb: (transport: Transport, reason: string) => void): this
 | 
			
		||||
    on(event: "error", cb: (transport: Transport, cause: Error) => void): this
 | 
			
		||||
    on(event: string, cb: (transport: Transport, extra?: any) => void): this {
 | 
			
		||||
        this.websocketServer.on(event, cb)
 | 
			
		||||
        return this
 | 
			
		||||
    protected onconnect(handler: any) {
 | 
			
		||||
        let id = this.getId(handler)
 | 
			
		||||
        console.log('client', id, 'connect')
 | 
			
		||||
        let request = this.getRequest(handler)
 | 
			
		||||
        request.id = id
 | 
			
		||||
        let websocket = this.getSocket(handler)
 | 
			
		||||
        this.clients.set(this.getId(handler), websocket)
 | 
			
		||||
        this.emit(ServerEvent.connect, request, websocket)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private selectServerImpl(instance: any) {
 | 
			
		||||
        let WebSocketServerImpl = undefined
 | 
			
		||||
        if (instance.class.name.startsWith('io.netty.channel')) {
 | 
			
		||||
            WebSocketServerImpl = require("../netty").NettyWebSocketServer
 | 
			
		||||
        } else {
 | 
			
		||||
            WebSocketServerImpl = require("../tomcat").TomcatWebSocketServer
 | 
			
		||||
    protected onmessage(handler: any, message: string) {
 | 
			
		||||
        this.execute(handler, (websocket) => websocket.emit(ServerEvent.message, message))
 | 
			
		||||
    }
 | 
			
		||||
    protected ondisconnect(handler: any, cause: string) {
 | 
			
		||||
        this.execute(handler, (websocket) => websocket.emit(ServerEvent.disconnect, cause))
 | 
			
		||||
    }
 | 
			
		||||
    protected onerror(handler: any, error: Error) {
 | 
			
		||||
        if (global.debug) {
 | 
			
		||||
            console.ex(error)
 | 
			
		||||
        }
 | 
			
		||||
        this.websocketServer = new WebSocketServerImpl(instance, this.options)
 | 
			
		||||
        this.execute(handler, (websocket) => websocket.emit(ServerEvent.error, error))
 | 
			
		||||
    }
 | 
			
		||||
    protected execute(handler: any, callback: (websocket: WebSocketClient) => void) {
 | 
			
		||||
        let id = this.getId(handler)
 | 
			
		||||
        if (this.clients.has(id)) {
 | 
			
		||||
            this.clients.has(id) && callback(this.clients.get(id))
 | 
			
		||||
        } else {
 | 
			
		||||
            console.debug('ignore execute', handler, 'callback', callback)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    public close() {
 | 
			
		||||
        this.clients.forEach(websocket => websocket.close())
 | 
			
		||||
        this.doClose()
 | 
			
		||||
    }
 | 
			
		||||
    protected abstract initialize(): void
 | 
			
		||||
    protected abstract getId(handler: any): string
 | 
			
		||||
    protected abstract getRequest(handler: any): Request
 | 
			
		||||
    protected abstract getSocket(handler: any): WebSocketClient
 | 
			
		||||
    protected abstract doClose(): void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const attach = (instance, options) => {
 | 
			
		||||
    if (!instance) { throw new Error('instance can\'t be undefiend!') }
 | 
			
		||||
    options = Object.assign({
 | 
			
		||||
        event: new EventEmitter(),
 | 
			
		||||
        path: '/ws',
 | 
			
		||||
        root: root + '/wwwroot',
 | 
			
		||||
    }, options)
 | 
			
		||||
    let WebSocketServerImpl = undefined
 | 
			
		||||
    if (instance.class.name.startsWith('io.netty.channel')) {
 | 
			
		||||
        WebSocketServerImpl = require("../netty").NettyWebSocketServer
 | 
			
		||||
    } else {
 | 
			
		||||
        WebSocketServerImpl = require("../tomcat").TomcatWebSocketServer
 | 
			
		||||
    }
 | 
			
		||||
    return new WebSocketServerImpl(instance, options)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								packages/websocket/src/server/request.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								packages/websocket/src/server/request.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
import { WebSocketClient } from "./client"
 | 
			
		||||
interface HttpHeaders {
 | 
			
		||||
    [name: string]: string
 | 
			
		||||
}
 | 
			
		||||
interface Connection {
 | 
			
		||||
    remoteAddress: string
 | 
			
		||||
}
 | 
			
		||||
export class Request {
 | 
			
		||||
    public id: string
 | 
			
		||||
    public url: string
 | 
			
		||||
    public method: string
 | 
			
		||||
    public headers: HttpHeaders
 | 
			
		||||
    public connection: Connection
 | 
			
		||||
    public websocket: WebSocketClient
 | 
			
		||||
 | 
			
		||||
    public _query: any
 | 
			
		||||
 | 
			
		||||
    constructor(url: string, method = "GET", headers = {}) {
 | 
			
		||||
        this.url = url
 | 
			
		||||
        this.method = method
 | 
			
		||||
        this.headers = headers
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,164 +0,0 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
import { Namespace } from './namespace'
 | 
			
		||||
import { Parser } from './parser'
 | 
			
		||||
import { Socket } from './socket'
 | 
			
		||||
 | 
			
		||||
export type SocketId = string
 | 
			
		||||
export type Room = string
 | 
			
		||||
 | 
			
		||||
export interface BroadcastFlags {
 | 
			
		||||
    volatile?: boolean
 | 
			
		||||
    compress?: boolean
 | 
			
		||||
    local?: boolean
 | 
			
		||||
    broadcast?: boolean
 | 
			
		||||
    binary?: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BroadcastOptions {
 | 
			
		||||
    rooms: Set<Room>
 | 
			
		||||
    except?: Set<SocketId>
 | 
			
		||||
    flags?: BroadcastFlags
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Adapter extends EventEmitter implements Adapter {
 | 
			
		||||
    rooms: Map<Room, Set<SocketId>>
 | 
			
		||||
    sids: Map<SocketId, Set<Room>>
 | 
			
		||||
    private readonly encoder: Parser
 | 
			
		||||
    parser: Parser
 | 
			
		||||
 | 
			
		||||
    constructor(readonly nsp: Namespace) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.rooms = new Map()
 | 
			
		||||
        this.sids = new Map()
 | 
			
		||||
        this.parser = nsp.server._parser
 | 
			
		||||
        this.encoder = this.parser
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a socket to a list of room.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} socket id
 | 
			
		||||
     * @param {String} rooms
 | 
			
		||||
     * @param {Function} callback
 | 
			
		||||
     * @api public
 | 
			
		||||
     */
 | 
			
		||||
    addAll(id: SocketId, rooms: Set<Room>): Promise<void> | void {
 | 
			
		||||
        for (const room of rooms) {
 | 
			
		||||
            if (!this.sids.has(id)) {
 | 
			
		||||
                this.sids.set(id, new Set())
 | 
			
		||||
            }
 | 
			
		||||
            this.sids.get(id).add(room)
 | 
			
		||||
 | 
			
		||||
            if (!this.rooms.has(room)) {
 | 
			
		||||
                this.rooms.set(room, new Set())
 | 
			
		||||
            }
 | 
			
		||||
            this.rooms.get(room).add(id)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    del(id: string, room: string, callback?: (err?: any) => void): void {
 | 
			
		||||
        if (this.sids.has(id)) {
 | 
			
		||||
            this.sids.get(id).delete(room)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.rooms.has(room)) {
 | 
			
		||||
            this.rooms.get(room).delete(id)
 | 
			
		||||
            if (this.rooms.get(room).size === 0) this.rooms.delete(room)
 | 
			
		||||
        }
 | 
			
		||||
        callback && callback.bind(null, null)
 | 
			
		||||
    }
 | 
			
		||||
    delAll(id: string): void {
 | 
			
		||||
        if (!this.sids.has(id)) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const room of this.sids.get(id)) {
 | 
			
		||||
            if (this.rooms.has(room)) {
 | 
			
		||||
                this.rooms.get(room).delete(id)
 | 
			
		||||
                if (this.rooms.get(room).size === 0) this.rooms.delete(room)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.sids.delete(id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Broadcasts a packet.
 | 
			
		||||
     *
 | 
			
		||||
     * Options:
 | 
			
		||||
     *  - `flags` {Object} flags for this packet
 | 
			
		||||
     *  - `except` {Array} sids that should be excluded
 | 
			
		||||
     *  - `rooms` {Array} list of rooms to broadcast to
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} packet   the packet object
 | 
			
		||||
     * @param {Object} opts     the options
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public broadcast(packet: any, opts: BroadcastOptions): void {
 | 
			
		||||
        const rooms = opts.rooms
 | 
			
		||||
        const except = opts.except || new Set()
 | 
			
		||||
        const flags = opts.flags || {}
 | 
			
		||||
        const packetOpts = {
 | 
			
		||||
            preEncoded: true,
 | 
			
		||||
            volatile: flags.volatile,
 | 
			
		||||
            compress: flags.compress
 | 
			
		||||
        }
 | 
			
		||||
        const ids = new Set()
 | 
			
		||||
 | 
			
		||||
        packet.nsp = this.nsp.name
 | 
			
		||||
        const encodedPackets = this.encoder.encode(packet)
 | 
			
		||||
 | 
			
		||||
        if (rooms.size) {
 | 
			
		||||
            for (const room of rooms) {
 | 
			
		||||
                if (!this.rooms.has(room)) continue
 | 
			
		||||
 | 
			
		||||
                for (const id of this.rooms.get(room)) {
 | 
			
		||||
                    if (ids.has(id) || except.has(id)) continue
 | 
			
		||||
                    const socket = this.nsp.sockets.get(id)
 | 
			
		||||
                    if (socket) {
 | 
			
		||||
                        socket.packet(encodedPackets as any, packetOpts)
 | 
			
		||||
                        ids.add(id)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            for (const [id] of this.sids) {
 | 
			
		||||
                if (except.has(id)) continue
 | 
			
		||||
                const socket = this.nsp.sockets.get(id)
 | 
			
		||||
                if (socket) socket.packet(encodedPackets as any, packetOpts)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a list of sockets by sid.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Set<Room>} rooms   the explicit set of rooms to check.
 | 
			
		||||
     */
 | 
			
		||||
    public sockets(rooms: Set<Room>): Promise<Set<SocketId>> {
 | 
			
		||||
        const sids = new Set<SocketId>()
 | 
			
		||||
        if (rooms.size) {
 | 
			
		||||
            for (const room of rooms) {
 | 
			
		||||
                if (!this.rooms.has(room)) continue
 | 
			
		||||
                for (const id of this.rooms.get(room)) {
 | 
			
		||||
                    if (this.nsp.sockets.has(id)) {
 | 
			
		||||
                        sids.add(id)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            for (const [id] of this.sids) {
 | 
			
		||||
                if (this.nsp.sockets.has(id)) sids.add(id)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve(sids)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the list of rooms a given socket has joined.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {SocketId} id   the socket id
 | 
			
		||||
     */
 | 
			
		||||
    public socketRooms(id: SocketId): Set<Room> | undefined {
 | 
			
		||||
        return this.sids.get(id)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,360 +0,0 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
import { Parser } from './parser'
 | 
			
		||||
import { Packet } from './packet'
 | 
			
		||||
import { Namespace, Server, Socket } from './index'
 | 
			
		||||
import { PacketTypes, SubPacketTypes } from './types'
 | 
			
		||||
import { ServerEvent } from './constants'
 | 
			
		||||
import { SocketId } from './adapter'
 | 
			
		||||
import { Transport } from '../transport'
 | 
			
		||||
 | 
			
		||||
const parser = new Parser()
 | 
			
		||||
 | 
			
		||||
export class Client extends EventEmitter {
 | 
			
		||||
    public readonly conn: Transport
 | 
			
		||||
    /**
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    readonly id: string
 | 
			
		||||
    private readonly server: Server
 | 
			
		||||
    // private readonly encoder: Encoder
 | 
			
		||||
    private readonly decoder: any
 | 
			
		||||
    private sockets: Map<SocketId, Socket>
 | 
			
		||||
    private nsps: Map<string, Socket>
 | 
			
		||||
    private connectTimeout: NodeJS.Timeout
 | 
			
		||||
 | 
			
		||||
    private checkIntervalTimer: NodeJS.Timeout
 | 
			
		||||
    private upgradeTimeoutTimer: NodeJS.Timeout
 | 
			
		||||
    private pingTimeoutTimer: NodeJS.Timeout
 | 
			
		||||
    private pingIntervalTimer: NodeJS.Timeout
 | 
			
		||||
 | 
			
		||||
    constructor(server: Server, conn) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.server = server
 | 
			
		||||
        this.conn = conn
 | 
			
		||||
        // this.encoder = server.encoder
 | 
			
		||||
        this.decoder = server._parser
 | 
			
		||||
        this.id = this.conn.id + ''
 | 
			
		||||
        this.setup()
 | 
			
		||||
        // =============================
 | 
			
		||||
        this.sockets = new Map()
 | 
			
		||||
        this.nsps = new Map()
 | 
			
		||||
        // ================== engine.io
 | 
			
		||||
        this.onOpen()
 | 
			
		||||
        // ================== Transport
 | 
			
		||||
        this.conn.on(ServerEvent.disconnect, (reason) => {
 | 
			
		||||
            this.onclose(reason)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * @return the reference to the request that originated the Engine.IO connection
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get request(): any /**IncomingMessage */ {
 | 
			
		||||
        return this.conn.request
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets up event listeners.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private setup() {
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        // this.decoder.on("decoded", this.ondecoded)
 | 
			
		||||
        this.conn.on("data", this.ondata.bind(this))
 | 
			
		||||
        this.conn.on("error", this.onerror.bind(this))
 | 
			
		||||
        this.conn.on("close", this.onclose.bind(this))
 | 
			
		||||
        console.debug(`setup client ${this.id}`)
 | 
			
		||||
        this.connectTimeout = setTimeout(() => {
 | 
			
		||||
            if (this.nsps.size === 0) {
 | 
			
		||||
                console.debug("no namespace joined yet, close the client")
 | 
			
		||||
                this.close()
 | 
			
		||||
            } else {
 | 
			
		||||
                console.debug("the client has already joined a namespace, nothing to do")
 | 
			
		||||
            }
 | 
			
		||||
        }, this.server._connectTimeout)
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Connects a client to a namespace.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} name - the namespace
 | 
			
		||||
     * @param {Object} auth - the auth parameters
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private connect(name: string, auth: object = {}) {
 | 
			
		||||
        console.debug(`client ${this.id} connecting to namespace ${name} has: ${this.server._nsps.has(name)}`)
 | 
			
		||||
        if (this.server._nsps.has(name)) {
 | 
			
		||||
            return this.doConnect(name, auth)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.server._checkNamespace(name, auth, (dynamicNsp: Namespace) => {
 | 
			
		||||
            if (dynamicNsp) {
 | 
			
		||||
                console.debug(`dynamic namespace ${dynamicNsp.name} was created`)
 | 
			
		||||
                this.doConnect(name, auth)
 | 
			
		||||
            } else {
 | 
			
		||||
                console.debug(`creation of namespace ${name} was denied`)
 | 
			
		||||
                this._packet({
 | 
			
		||||
                    type: PacketTypes.MESSAGE,
 | 
			
		||||
                    sub_type: SubPacketTypes.ERROR,
 | 
			
		||||
                    nsp: name,
 | 
			
		||||
                    data: {
 | 
			
		||||
                        message: "Invalid namespace"
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    doConnect(name, auth: object) {
 | 
			
		||||
        if (this.connectTimeout) {
 | 
			
		||||
            clearTimeout(this.connectTimeout)
 | 
			
		||||
            this.connectTimeout = null
 | 
			
		||||
        }
 | 
			
		||||
        const nsp = this.server.of(name)
 | 
			
		||||
 | 
			
		||||
        nsp._add(this, auth, (socket: Socket) => {
 | 
			
		||||
            this.sockets.set(socket.id, socket)
 | 
			
		||||
            this.nsps.set(nsp.name, socket)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Disconnects from all namespaces and closes transport.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _disconnect() {
 | 
			
		||||
        for (const socket of this.sockets.values()) {
 | 
			
		||||
            socket.disconnect()
 | 
			
		||||
        }
 | 
			
		||||
        this.sockets.clear()
 | 
			
		||||
        this.close()
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes a socket. Called by each `Socket`.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _remove(socket: Socket) {
 | 
			
		||||
        if (this.sockets.has(socket.id)) {
 | 
			
		||||
            this.sockets.delete(socket.id)
 | 
			
		||||
            this.nsps.delete(socket.nsp.name)
 | 
			
		||||
        } else {
 | 
			
		||||
            console.debug(`ignoring remove for ${socket.id}`,)
 | 
			
		||||
        }
 | 
			
		||||
        process.nextTick(() => {
 | 
			
		||||
            if (this.sockets.size == 0) {
 | 
			
		||||
                this.onclose('no live socket')
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Closes the underlying connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private close() {
 | 
			
		||||
        console.debug(`client ${this.id} close`)
 | 
			
		||||
        if ("open" == this.conn.readyState) {
 | 
			
		||||
            console.debug("forcing transport close")
 | 
			
		||||
            this.onclose("forced server close")
 | 
			
		||||
            this.conn.close()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Writes a packet to the transport.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} packet object
 | 
			
		||||
     * @param {Object} opts
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _packet(packet: Packet, opts = { preEncoded: false }) {
 | 
			
		||||
        // opts = opts || {}
 | 
			
		||||
        // const self = this
 | 
			
		||||
 | 
			
		||||
        // // this writes to the actual connection
 | 
			
		||||
        // function writeToEngine(encodedPackets) {
 | 
			
		||||
        //     if (opts.volatile && !self.conn.transport.writable) return
 | 
			
		||||
        //     for (let i = 0; i < encodedPackets.length; i++) {
 | 
			
		||||
        //         self.conn.write(encodedPackets[i], { compress: opts.compress })
 | 
			
		||||
        //     }
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        // if ("open" == this.conn.readyState) {
 | 
			
		||||
        //     debug("writing packet %j", packet)
 | 
			
		||||
        //     if (!opts.preEncoded) {
 | 
			
		||||
        //         // not broadcasting, need to encode
 | 
			
		||||
        //         writeToEngine(this.encoder.encode(packet)) // encode, then write results to engine
 | 
			
		||||
        //     } else {
 | 
			
		||||
        //         // a broadcast pre-encodes a packet
 | 
			
		||||
        //         writeToEngine(packet)
 | 
			
		||||
        //     }
 | 
			
		||||
        // } else {
 | 
			
		||||
        //     debug("ignoring packet write %j", packet)
 | 
			
		||||
        // }
 | 
			
		||||
        if ("open" == this.conn.readyState) {
 | 
			
		||||
            this.conn.send(opts.preEncoded ? packet as unknown as string : parser.encode(packet))
 | 
			
		||||
        } else {
 | 
			
		||||
            console.debug(`ignoring write packet ${JSON.stringify(packet)} to client ${this.id} is already close!`)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Called with incoming transport data.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private ondata(data) {
 | 
			
		||||
        // try/catch is needed for protocol violations (GH-1880)
 | 
			
		||||
        try {
 | 
			
		||||
            this.decoder.add(data)
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            this.onerror(e)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when parser fully decodes a packet.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    ondecoded(packet: Packet) {
 | 
			
		||||
        if (SubPacketTypes.CONNECT == packet.sub_type) {
 | 
			
		||||
            this.connect(packet.nsp, packet.data)
 | 
			
		||||
        } else {
 | 
			
		||||
            process.nextTick(() => {
 | 
			
		||||
                const socket = this.nsps.get(packet.nsp)
 | 
			
		||||
                if (socket) {
 | 
			
		||||
                    socket._onpacket(packet)
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.debug(`client ${this.id} no socket for namespace ${packet.nsp}.`)
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles an error.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} err object
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private onerror(err) {
 | 
			
		||||
        for (const socket of this.sockets.values()) {
 | 
			
		||||
            socket._onerror(err)
 | 
			
		||||
        }
 | 
			
		||||
        this.conn.close()
 | 
			
		||||
    }
 | 
			
		||||
    onclose(reason?: string) {
 | 
			
		||||
        this.conn.readyState = "closing"
 | 
			
		||||
        // ======= engine.io
 | 
			
		||||
        this.onClose(reason)
 | 
			
		||||
        // cleanup connectTimeout
 | 
			
		||||
        if (this.connectTimeout) {
 | 
			
		||||
            clearTimeout(this.connectTimeout)
 | 
			
		||||
            this.connectTimeout = null
 | 
			
		||||
        }
 | 
			
		||||
        console.debug(`client ${this.id} close with reason ${reason}`)
 | 
			
		||||
        // ignore a potential subsequent `close` event
 | 
			
		||||
        // `nsps` and `sockets` are cleaned up seamlessly
 | 
			
		||||
        for (const socket of this.sockets.values()) {
 | 
			
		||||
            socket._onclose(reason)
 | 
			
		||||
        }
 | 
			
		||||
        this.sockets.clear()
 | 
			
		||||
        // this.decoder.destroy(); // clean up decoder
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destroy() {
 | 
			
		||||
        // this.conn.removeListener('data', this.ondata);
 | 
			
		||||
        // this.conn.removeListener('error', this.onerror);
 | 
			
		||||
        // this.conn.removeListener('close', this.onclose);
 | 
			
		||||
        // this.decoder.removeListener('decoded', this.ondecoded);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //================== engine.io
 | 
			
		||||
    onOpen() {
 | 
			
		||||
        this.conn.readyState = "open"
 | 
			
		||||
        this._packet({
 | 
			
		||||
            type: PacketTypes.OPEN,
 | 
			
		||||
            data: {
 | 
			
		||||
                sid: this.id,
 | 
			
		||||
                upgrades: [],
 | 
			
		||||
                pingInterval: this.server.options.pingInterval,
 | 
			
		||||
                pingTimeout: this.server.options.pingTimeout
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        this.schedulePing()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onPacket(packet: Packet) {
 | 
			
		||||
        if ("open" === this.conn.readyState) {
 | 
			
		||||
            // export packet event
 | 
			
		||||
            // debug("packet")
 | 
			
		||||
            // this.emit("packet", packet)
 | 
			
		||||
 | 
			
		||||
            // Reset ping timeout on any packet, incoming data is a good sign of
 | 
			
		||||
            // other side's liveness
 | 
			
		||||
            this.resetPingTimeout(this.server.options.pingInterval + this.server.options.pingTimeout * 2)
 | 
			
		||||
            switch (packet.type) {
 | 
			
		||||
                case PacketTypes.PING:
 | 
			
		||||
                    this._packet({
 | 
			
		||||
                        type: PacketTypes.PONG,
 | 
			
		||||
                        data: packet.data
 | 
			
		||||
                    })
 | 
			
		||||
                    break
 | 
			
		||||
                case PacketTypes.PONG:
 | 
			
		||||
                    this.schedulePing()
 | 
			
		||||
                    break
 | 
			
		||||
                case PacketTypes.UPGRADE:
 | 
			
		||||
                    break
 | 
			
		||||
                case PacketTypes.MESSAGE:
 | 
			
		||||
                    this.ondecoded(packet)
 | 
			
		||||
                    break
 | 
			
		||||
                case PacketTypes.CLOSE:
 | 
			
		||||
                    this.onclose()
 | 
			
		||||
                    break
 | 
			
		||||
                default:
 | 
			
		||||
                    console.log(`client ${this.id} reciver unknow packet type: ${packet.type}`)
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            console.debug(`packet received with closed client ${this.id}`)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon transport considered closed.
 | 
			
		||||
     * Possible reasons: `ping timeout`, `client error`, `parse error`,
 | 
			
		||||
     * `transport error`, `server close`, `transport close`
 | 
			
		||||
     */
 | 
			
		||||
    onClose(reason, description?: string) {
 | 
			
		||||
        // if ("closed" !== this.conn.readyState) {
 | 
			
		||||
        clearTimeout(this.pingIntervalTimer)
 | 
			
		||||
        clearTimeout(this.pingTimeoutTimer)
 | 
			
		||||
 | 
			
		||||
        clearInterval(this.checkIntervalTimer)
 | 
			
		||||
        this.checkIntervalTimer = null
 | 
			
		||||
        clearTimeout(this.upgradeTimeoutTimer)
 | 
			
		||||
        //     this.emit("close", reason, description)
 | 
			
		||||
        // }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Pings client every `this.pingInterval` and expects response
 | 
			
		||||
     * within `this.pingTimeout` or closes connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    schedulePing() {
 | 
			
		||||
        clearTimeout(this.pingIntervalTimer)
 | 
			
		||||
        this.pingIntervalTimer = setTimeout(() => {
 | 
			
		||||
            this.resetPingTimeout(this.server.options.pingTimeout)
 | 
			
		||||
            process.nextTick(() => this._packet({ type: PacketTypes.PING }))
 | 
			
		||||
        }, this.server.options.pingInterval)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Resets ping timeout.
 | 
			
		||||
     *
 | 
			
		||||
     * @api private
 | 
			
		||||
     */
 | 
			
		||||
    resetPingTimeout(timeout: number) {
 | 
			
		||||
        clearTimeout(this.pingTimeoutTimer)
 | 
			
		||||
        this.pingTimeoutTimer = setTimeout(() => {
 | 
			
		||||
            if (this.conn.readyState === "closed") return
 | 
			
		||||
            this.onclose("ping timeout")
 | 
			
		||||
        }, timeout)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
export enum ServerEvent {
 | 
			
		||||
    detect = 'detect',
 | 
			
		||||
    connect = 'connect',
 | 
			
		||||
    connection = 'connection',
 | 
			
		||||
    message = 'message',
 | 
			
		||||
    error = 'error',
 | 
			
		||||
    disconnecting = 'disconnecting',
 | 
			
		||||
    disconnect = 'disconnect',
 | 
			
		||||
}
 | 
			
		||||
@@ -1,677 +0,0 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
 | 
			
		||||
import { ServerEvent } from './constants'
 | 
			
		||||
import { Namespace } from './namespace'
 | 
			
		||||
import { Client } from './client'
 | 
			
		||||
import { Parser } from './parser'
 | 
			
		||||
import { Socket } from './socket'
 | 
			
		||||
import { Adapter } from './adapter'
 | 
			
		||||
import { Transport } from '../transport'
 | 
			
		||||
import { ParentNamespace } from './parent-namespace'
 | 
			
		||||
 | 
			
		||||
interface EngineOptions {
 | 
			
		||||
    /**
 | 
			
		||||
     * how many ms without a pong packet to consider the connection closed
 | 
			
		||||
     * @default 5000
 | 
			
		||||
     */
 | 
			
		||||
    pingTimeout: number
 | 
			
		||||
    /**
 | 
			
		||||
     * how many ms before sending a new ping packet
 | 
			
		||||
     * @default 25000
 | 
			
		||||
     */
 | 
			
		||||
    pingInterval: number
 | 
			
		||||
    /**
 | 
			
		||||
     * how many ms before an uncompleted transport upgrade is cancelled
 | 
			
		||||
     * @default 10000
 | 
			
		||||
     */
 | 
			
		||||
    upgradeTimeout: number
 | 
			
		||||
    /**
 | 
			
		||||
     * how many bytes or characters a message can be, before closing the session (to avoid DoS).
 | 
			
		||||
     * @default 1e5 (100 KB)
 | 
			
		||||
     */
 | 
			
		||||
    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.
 | 
			
		||||
     */
 | 
			
		||||
    // allowRequest: (
 | 
			
		||||
    //     req: http.IncomingMessage,
 | 
			
		||||
    //     fn: (err: string | null | undefined, success: boolean) => void
 | 
			
		||||
    // ) => void
 | 
			
		||||
    /**
 | 
			
		||||
     * the low-level transports that are enabled
 | 
			
		||||
     * @default ["polling", "websocket"]
 | 
			
		||||
     */
 | 
			
		||||
    // transports: Transport[]
 | 
			
		||||
    /**
 | 
			
		||||
     * whether to allow transport upgrades
 | 
			
		||||
     * @default true
 | 
			
		||||
     */
 | 
			
		||||
    allowUpgrades: boolean
 | 
			
		||||
    /**
 | 
			
		||||
     * parameters of the WebSocket permessage-deflate extension (see ws module api docs). Set to false to disable.
 | 
			
		||||
     * @default false
 | 
			
		||||
     */
 | 
			
		||||
    perMessageDeflate: boolean | object
 | 
			
		||||
    /**
 | 
			
		||||
     * parameters of the http compression for the polling transports (see zlib api docs). Set to false to disable.
 | 
			
		||||
     * @default true
 | 
			
		||||
     */
 | 
			
		||||
    httpCompression: boolean | object
 | 
			
		||||
    /**
 | 
			
		||||
     * what WebSocket server implementation to use. Specified module must
 | 
			
		||||
     * conform to the ws interface (see ws module api docs). Default value is ws.
 | 
			
		||||
     * An alternative c++ addon is also available by installing uws module.
 | 
			
		||||
     */
 | 
			
		||||
    wsEngine: string
 | 
			
		||||
    /**
 | 
			
		||||
     * an optional packet which will be concatenated to the handshake packet emitted by Engine.IO.
 | 
			
		||||
     */
 | 
			
		||||
    initialPacket: any
 | 
			
		||||
    /**
 | 
			
		||||
     * configuration of the cookie that contains the client sid to send as part of handshake response headers. This cookie
 | 
			
		||||
     * might be used for sticky-session. Defaults to not sending any cookie.
 | 
			
		||||
     * @default false
 | 
			
		||||
     */
 | 
			
		||||
    // cookie: CookieSerializeOptions | boolean
 | 
			
		||||
    /**
 | 
			
		||||
     * the options that will be forwarded to the cors module
 | 
			
		||||
     */
 | 
			
		||||
    // cors: CorsOptions
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface AttachOptions {
 | 
			
		||||
    /**
 | 
			
		||||
     * name of the path to capture
 | 
			
		||||
     * @default "/engine.io"
 | 
			
		||||
     */
 | 
			
		||||
    path: string
 | 
			
		||||
    /**
 | 
			
		||||
     * destroy unhandled upgrade requests
 | 
			
		||||
     * @default true
 | 
			
		||||
     */
 | 
			
		||||
    destroyUpgrade: boolean
 | 
			
		||||
    /**
 | 
			
		||||
     * milliseconds after which unhandled requests are ended
 | 
			
		||||
     * @default 1000
 | 
			
		||||
     */
 | 
			
		||||
    destroyUpgradeTimeout: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface EngineAttachOptions extends EngineOptions, AttachOptions { }
 | 
			
		||||
 | 
			
		||||
interface ServerOptions extends EngineAttachOptions {
 | 
			
		||||
    event?: EventEmitter
 | 
			
		||||
    root?: string
 | 
			
		||||
    /**
 | 
			
		||||
     * name of the path to capture
 | 
			
		||||
     * @default "/socket.io"
 | 
			
		||||
     */
 | 
			
		||||
    path: string
 | 
			
		||||
    /**
 | 
			
		||||
     * whether to serve the client files
 | 
			
		||||
     * @default true
 | 
			
		||||
     */
 | 
			
		||||
    serveClient: boolean
 | 
			
		||||
    /**
 | 
			
		||||
     * the adapter to use
 | 
			
		||||
     * @default the in-memory adapter (https://github.com/socketio/socket.io-adapter)
 | 
			
		||||
     */
 | 
			
		||||
    adapter: any
 | 
			
		||||
    /**
 | 
			
		||||
     * the parser to use
 | 
			
		||||
     * @default the default parser (https://github.com/socketio/socket.io-parser)
 | 
			
		||||
     */
 | 
			
		||||
    parser: any
 | 
			
		||||
    /**
 | 
			
		||||
     * how many ms before a client without namespace is closed
 | 
			
		||||
     * @default 45000
 | 
			
		||||
     */
 | 
			
		||||
    connectTimeout: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface WebSocketServer extends EventEmitter {
 | 
			
		||||
    close(): void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Server {
 | 
			
		||||
    public readonly sockets: Namespace
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _parser: Parser
 | 
			
		||||
    private readonly encoder
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _nsps: Map<string, Namespace>
 | 
			
		||||
    private parentNsps: Map<
 | 
			
		||||
        | string
 | 
			
		||||
        | RegExp
 | 
			
		||||
        | ((
 | 
			
		||||
            name: string,
 | 
			
		||||
            query: object,
 | 
			
		||||
            fn: (err: Error, success: boolean) => void
 | 
			
		||||
        ) => void),
 | 
			
		||||
        ParentNamespace
 | 
			
		||||
    > = new Map();
 | 
			
		||||
    private _adapter: Adapter
 | 
			
		||||
    // private _serveClient: boolean;
 | 
			
		||||
    private eio
 | 
			
		||||
    private engine: { ws: any }
 | 
			
		||||
    private _path: string
 | 
			
		||||
    private clientPathRegex: RegExp
 | 
			
		||||
    /**
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _connectTimeout: number
 | 
			
		||||
 | 
			
		||||
    options: Partial<ServerOptions>
 | 
			
		||||
    private websocketServer: WebSocketServer
 | 
			
		||||
    private allClients: Map<string, Client>
 | 
			
		||||
 | 
			
		||||
    constructor(instance: any, options: Partial<ServerOptions>) {
 | 
			
		||||
        if (!instance) { throw new Error('instance can\'t be undefiend!') }
 | 
			
		||||
        this.options = Object.assign({
 | 
			
		||||
            event: new EventEmitter(),
 | 
			
		||||
            path: '/socket.io',
 | 
			
		||||
            root: root + '/wwwroot',
 | 
			
		||||
            serveClient: false,
 | 
			
		||||
            connectTimeout: 45000,
 | 
			
		||||
            wsEngine: process.env.EIO_WS_ENGINE || "ws",
 | 
			
		||||
            pingTimeout: 5000,
 | 
			
		||||
            pingInterval: 25000,
 | 
			
		||||
            upgradeTimeout: 10000,
 | 
			
		||||
            maxHttpBufferSize: 1e6,
 | 
			
		||||
            transports: 'websocket',
 | 
			
		||||
            allowUpgrades: true,
 | 
			
		||||
            httpCompression: {
 | 
			
		||||
                threshold: 1024
 | 
			
		||||
            },
 | 
			
		||||
            cors: false
 | 
			
		||||
        }, options)
 | 
			
		||||
        this.initServerConfig()
 | 
			
		||||
        this.sockets = this.of('/')
 | 
			
		||||
        this.selectServerImpl(instance)
 | 
			
		||||
        this.initServer()
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets/gets whether client code is being served.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Boolean} v - whether to serve client code
 | 
			
		||||
     * @return {Server|Boolean} self when setting or value when getting
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public serveClient(v: boolean): Server
 | 
			
		||||
    public serveClient(): boolean
 | 
			
		||||
    public serveClient(v?: boolean): Server | boolean {
 | 
			
		||||
        throw new Error("Method not implemented.")
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Executes the middleware for an incoming namespace not already created on the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} name - name of incoming namespace
 | 
			
		||||
     * @param {Object} auth - the auth parameters
 | 
			
		||||
     * @param {Function} fn - callback
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _checkNamespace(
 | 
			
		||||
        name: string,
 | 
			
		||||
        auth: object,
 | 
			
		||||
        fn: (nsp: Namespace) => void
 | 
			
		||||
    ) {
 | 
			
		||||
        // if (this.parentNsps.size === 0) return fn(false)
 | 
			
		||||
 | 
			
		||||
        // const keysIterator = this.parentNsps.keys()
 | 
			
		||||
 | 
			
		||||
        // const run = () => {
 | 
			
		||||
        //     let nextFn = keysIterator.next()
 | 
			
		||||
        //     if (nextFn.done) {
 | 
			
		||||
        //         return fn(false)
 | 
			
		||||
        //     }
 | 
			
		||||
        //     nextFn.value(name, auth, (err, allow) => {
 | 
			
		||||
        //         if (err || !allow) {
 | 
			
		||||
        //             run()
 | 
			
		||||
        //         } else {
 | 
			
		||||
        //             fn(this.parentNsps.get(nextFn.value).createChild(name))
 | 
			
		||||
        //         }
 | 
			
		||||
        //     })
 | 
			
		||||
        // }
 | 
			
		||||
        fn(undefined)
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the client serving path.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} v pathname
 | 
			
		||||
     * @return {Server|String} self when setting or value when getting
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    path(): string
 | 
			
		||||
    path(v: string): Server
 | 
			
		||||
    path(v?: any): string | Server {
 | 
			
		||||
        if (!arguments.length) return this._path
 | 
			
		||||
 | 
			
		||||
        this._path = v.replace(/\/$/, "")
 | 
			
		||||
 | 
			
		||||
        const escapedPath = this._path.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")
 | 
			
		||||
        this.clientPathRegex = new RegExp(
 | 
			
		||||
            "^" +
 | 
			
		||||
            escapedPath +
 | 
			
		||||
            "/socket\\.io(\\.min|\\.msgpack\\.min)?\\.js(\\.map)?$"
 | 
			
		||||
        )
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the delay after which a client without namespace is closed
 | 
			
		||||
     * @param v
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public connectTimeout(v: number): Server
 | 
			
		||||
    public connectTimeout(): number
 | 
			
		||||
    public connectTimeout(v?: number): Server | number {
 | 
			
		||||
        if (v === undefined) return this._connectTimeout
 | 
			
		||||
        this._connectTimeout = v
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the adapter for rooms.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Adapter} v pathname
 | 
			
		||||
     * @return {Server|Adapter} self when setting or value when getting
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public adapter(): any
 | 
			
		||||
    public adapter(v: any)
 | 
			
		||||
    public adapter(v?): Server | any {
 | 
			
		||||
        if (!arguments.length) return this._adapter
 | 
			
		||||
        this._adapter = v
 | 
			
		||||
        for (const nsp of this._nsps.values()) {
 | 
			
		||||
            nsp._initAdapter()
 | 
			
		||||
        }
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * Attaches socket.io to a server or port.
 | 
			
		||||
    //  *
 | 
			
		||||
    //  * @param {http.Server|Number} srv - server or port
 | 
			
		||||
    //  * @param {Object} opts - options passed to engine.io
 | 
			
		||||
    //  * @return {Server} self
 | 
			
		||||
    //  * @public
 | 
			
		||||
    //  */
 | 
			
		||||
    // public listen(srv: http.Server, opts?: Partial<ServerOptions>): Server
 | 
			
		||||
    // public listen(srv: number, opts?: Partial<ServerOptions>): Server
 | 
			
		||||
    // public listen(srv: any, opts: Partial<ServerOptions> = {}): Server {
 | 
			
		||||
    //     return this.attach(srv, opts)
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * Attaches socket.io to a server or port.
 | 
			
		||||
    //  *
 | 
			
		||||
    //  * @param {http.Server|Number} srv - server or port
 | 
			
		||||
    //  * @param {Object} opts - options passed to engine.io
 | 
			
		||||
    //  * @return {Server} self
 | 
			
		||||
    //  * @public
 | 
			
		||||
    //  */
 | 
			
		||||
    // public attach(srv: http.Server, opts?: Partial<ServerOptions>): Server
 | 
			
		||||
    // public attach(port: number, opts?: Partial<ServerOptions>): Server
 | 
			
		||||
    // public attach(srv: any, opts: Partial<ServerOptions> = {}): Server {
 | 
			
		||||
    //     if ("function" == typeof srv) {
 | 
			
		||||
    //         const msg =
 | 
			
		||||
    //             "You are trying to attach socket.io to an express " +
 | 
			
		||||
    //             "request handler function. Please pass a http.Server instance."
 | 
			
		||||
    //         throw new Error(msg)
 | 
			
		||||
    //     }
 | 
			
		||||
 | 
			
		||||
    //     // handle a port as a string
 | 
			
		||||
    //     if (Number(srv) == srv) {
 | 
			
		||||
    //         srv = Number(srv)
 | 
			
		||||
    //     }
 | 
			
		||||
 | 
			
		||||
    //     if ("number" == typeof srv) {
 | 
			
		||||
    //         debug("creating http server and binding to %d", srv)
 | 
			
		||||
    //         const port = srv
 | 
			
		||||
    //         srv = http.createServer((req, res) => {
 | 
			
		||||
    //             res.writeHead(404)
 | 
			
		||||
    //             res.end()
 | 
			
		||||
    //         })
 | 
			
		||||
    //         srv.listen(port)
 | 
			
		||||
    //     }
 | 
			
		||||
 | 
			
		||||
    //     // set engine.io path to `/socket.io`
 | 
			
		||||
    //     opts.path = opts.path || this._path
 | 
			
		||||
 | 
			
		||||
    //     this.initEngine(srv, opts)
 | 
			
		||||
 | 
			
		||||
    //     return this
 | 
			
		||||
    // }
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * Initialize engine
 | 
			
		||||
    //  *
 | 
			
		||||
    //  * @param srv - the server to attach to
 | 
			
		||||
    //  * @param opts - options passed to engine.io
 | 
			
		||||
    //  * @private
 | 
			
		||||
    //  */
 | 
			
		||||
    // private initEngine(srv: http.Server, opts: Partial<EngineAttachOptions>) {
 | 
			
		||||
    //     // initialize engine
 | 
			
		||||
    //     debug("creating engine.io instance with opts %j", opts)
 | 
			
		||||
    //     this.eio = engine.attach(srv, opts)
 | 
			
		||||
 | 
			
		||||
    //     // attach static file serving
 | 
			
		||||
    //     if (this._serveClient) this.attachServe(srv)
 | 
			
		||||
 | 
			
		||||
    //     // Export http server
 | 
			
		||||
    //     this.httpServer = srv
 | 
			
		||||
 | 
			
		||||
    //     // bind to engine events
 | 
			
		||||
    //     this.bind(this.eio)
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * Attaches the static file serving.
 | 
			
		||||
    //  *
 | 
			
		||||
    //  * @param {Function|http.Server} srv http server
 | 
			
		||||
    //  * @private
 | 
			
		||||
    //  */
 | 
			
		||||
    // private attachServe(srv) {
 | 
			
		||||
    //     debug("attaching client serving req handler")
 | 
			
		||||
 | 
			
		||||
    //     const evs = srv.listeners("request").slice(0)
 | 
			
		||||
    //     srv.removeAllListeners("request")
 | 
			
		||||
    //     srv.on("request", (req, res) => {
 | 
			
		||||
    //         if (this.clientPathRegex.test(req.url)) {
 | 
			
		||||
    //             this.serve(req, res)
 | 
			
		||||
    //         } else {
 | 
			
		||||
    //             for (let i = 0; i < evs.length; i++) {
 | 
			
		||||
    //                 evs[i].call(srv, req, res)
 | 
			
		||||
    //             }
 | 
			
		||||
    //         }
 | 
			
		||||
    //     })
 | 
			
		||||
    // }
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * Handles a request serving of client source and map
 | 
			
		||||
    //  *
 | 
			
		||||
    //  * @param {http.IncomingMessage} req
 | 
			
		||||
    //  * @param {http.ServerResponse} res
 | 
			
		||||
    //  * @private
 | 
			
		||||
    //  */
 | 
			
		||||
    // private serve(req: http.IncomingMessage, res: http.ServerResponse) {
 | 
			
		||||
    //     const filename = req.url.replace(this._path, "")
 | 
			
		||||
    //     const isMap = dotMapRegex.test(filename)
 | 
			
		||||
    //     const type = isMap ? "map" : "source"
 | 
			
		||||
 | 
			
		||||
    //     // Per the standard, ETags must be quoted:
 | 
			
		||||
    //     // https://tools.ietf.org/html/rfc7232#section-2.3
 | 
			
		||||
    //     const expectedEtag = '"' + clientVersion + '"'
 | 
			
		||||
 | 
			
		||||
    //     const etag = req.headers["if-none-match"]
 | 
			
		||||
    //     if (etag) {
 | 
			
		||||
    //         if (expectedEtag == etag) {
 | 
			
		||||
    //             debug("serve client %s 304", type)
 | 
			
		||||
    //             res.writeHead(304)
 | 
			
		||||
    //             res.end()
 | 
			
		||||
    //             return
 | 
			
		||||
    //         }
 | 
			
		||||
    //     }
 | 
			
		||||
 | 
			
		||||
    //     debug("serve client %s", type)
 | 
			
		||||
 | 
			
		||||
    //     res.setHeader("Cache-Control", "public, max-age=0")
 | 
			
		||||
    //     res.setHeader(
 | 
			
		||||
    //         "Content-Type",
 | 
			
		||||
    //         "application/" + (isMap ? "json" : "javascript")
 | 
			
		||||
    //     )
 | 
			
		||||
    //     res.setHeader("ETag", expectedEtag)
 | 
			
		||||
 | 
			
		||||
    //     if (!isMap) {
 | 
			
		||||
    //         res.setHeader("X-SourceMap", filename.substring(1) + ".map")
 | 
			
		||||
    //     }
 | 
			
		||||
    //     Server.sendFile(filename, req, res)
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * @param filename
 | 
			
		||||
    //  * @param req
 | 
			
		||||
    //  * @param res
 | 
			
		||||
    //  * @private
 | 
			
		||||
    //  */
 | 
			
		||||
    // private static sendFile(
 | 
			
		||||
    //     filename: string,
 | 
			
		||||
    //     req: http.IncomingMessage,
 | 
			
		||||
    //     res: http.ServerResponse
 | 
			
		||||
    // ) {
 | 
			
		||||
    //     const readStream = createReadStream(
 | 
			
		||||
    //         path.join(__dirname, "../client-dist/", filename)
 | 
			
		||||
    //     )
 | 
			
		||||
    //     const encoding = accepts(req).encodings(["br", "gzip", "deflate"])
 | 
			
		||||
 | 
			
		||||
    //     const onError = err => {
 | 
			
		||||
    //         if (err) {
 | 
			
		||||
    //             res.end()
 | 
			
		||||
    //         }
 | 
			
		||||
    //     }
 | 
			
		||||
 | 
			
		||||
    //     switch (encoding) {
 | 
			
		||||
    //         case "br":
 | 
			
		||||
    //             res.writeHead(200, { "content-encoding": "br" })
 | 
			
		||||
    //             readStream.pipe(createBrotliCompress()).pipe(res)
 | 
			
		||||
    //             pipeline(readStream, createBrotliCompress(), res, onError)
 | 
			
		||||
    //             break
 | 
			
		||||
    //         case "gzip":
 | 
			
		||||
    //             res.writeHead(200, { "content-encoding": "gzip" })
 | 
			
		||||
    //             pipeline(readStream, createGzip(), res, onError)
 | 
			
		||||
    //             break
 | 
			
		||||
    //         case "deflate":
 | 
			
		||||
    //             res.writeHead(200, { "content-encoding": "deflate" })
 | 
			
		||||
    //             pipeline(readStream, createDeflate(), res, onError)
 | 
			
		||||
    //             break
 | 
			
		||||
    //         default:
 | 
			
		||||
    //             res.writeHead(200)
 | 
			
		||||
    //             pipeline(readStream, res, onError)
 | 
			
		||||
    //     }
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * Binds socket.io to an engine.io instance.
 | 
			
		||||
    //  *
 | 
			
		||||
    //  * @param {engine.Server} engine engine.io (or compatible) server
 | 
			
		||||
    //  * @return {Server} self
 | 
			
		||||
    //  * @public
 | 
			
		||||
    //  */
 | 
			
		||||
    // public bind(engine): Server {
 | 
			
		||||
    //     this.engine = engine
 | 
			
		||||
    //     this.engine.on("connection", this.onconnection.bind(this))
 | 
			
		||||
    //     return this
 | 
			
		||||
    // }
 | 
			
		||||
    /**
 | 
			
		||||
     * Called with each incoming transport connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {engine.Socket} conn
 | 
			
		||||
     * @return {Server} self
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private onconnection(conn): Server {
 | 
			
		||||
        console.debug(`incoming connection with id ${conn.id}`)
 | 
			
		||||
        let client = new Client(this, conn)
 | 
			
		||||
        this.allClients.set(conn.id, client)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
    // of(nsp: string): Namespace {
 | 
			
		||||
    //     if (!this._nsps.has(nsp)) {
 | 
			
		||||
    //         console.debug(`create Namespace ${nsp}`)
 | 
			
		||||
    //         this._nsps.set(nsp, new Namespace(this, nsp))
 | 
			
		||||
    //     }
 | 
			
		||||
    //     return this._nsps.get(nsp)
 | 
			
		||||
    // }
 | 
			
		||||
    /**
 | 
			
		||||
     * Looks up a namespace.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String|RegExp|Function} name nsp name
 | 
			
		||||
     * @param {Function} [fn] optional, nsp `connection` ev handler
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public of(
 | 
			
		||||
        name:
 | 
			
		||||
            | string
 | 
			
		||||
            | RegExp
 | 
			
		||||
            | ((
 | 
			
		||||
                name: string,
 | 
			
		||||
                query: object,
 | 
			
		||||
                fn: (err: Error, success: boolean) => void
 | 
			
		||||
            ) => void),
 | 
			
		||||
        fn?: (socket: Socket) => void
 | 
			
		||||
    ) {
 | 
			
		||||
        if (typeof name === "function" || name instanceof RegExp) {
 | 
			
		||||
            const parentNsp = new ParentNamespace(this)
 | 
			
		||||
            console.debug(`initializing parent namespace ${parentNsp.name}`)
 | 
			
		||||
            if (typeof name === "function") {
 | 
			
		||||
                this.parentNsps.set(name, parentNsp)
 | 
			
		||||
            } else {
 | 
			
		||||
                this.parentNsps.set(
 | 
			
		||||
                    (nsp, conn, next) => next(null, (name as RegExp).test(nsp)),
 | 
			
		||||
                    parentNsp
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            if (fn) {
 | 
			
		||||
                // @ts-ignore
 | 
			
		||||
                parentNsp.on("connect", fn)
 | 
			
		||||
            }
 | 
			
		||||
            return parentNsp
 | 
			
		||||
        }
 | 
			
		||||
        if (String(name)[0] !== "/") name = "/" + name
 | 
			
		||||
        let nsp = this._nsps.get(name)
 | 
			
		||||
        if (!nsp) {
 | 
			
		||||
            console.debug(`initializing namespace ${name}`)
 | 
			
		||||
            nsp = new Namespace(this, name)
 | 
			
		||||
            this._nsps.set(name, nsp)
 | 
			
		||||
        }
 | 
			
		||||
        if (fn) nsp.on("connect", fn)
 | 
			
		||||
        return nsp
 | 
			
		||||
    }
 | 
			
		||||
    close(fn?: () => void): void {
 | 
			
		||||
        this.clients.length
 | 
			
		||||
        for (const client of this.allClients.values()) {
 | 
			
		||||
            client._disconnect()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // this.engine.close()
 | 
			
		||||
        this.websocketServer.close()
 | 
			
		||||
 | 
			
		||||
        // if (this.httpServer) {
 | 
			
		||||
        //     this.httpServer.close(fn)
 | 
			
		||||
        // } else {
 | 
			
		||||
        fn && fn()
 | 
			
		||||
        // }
 | 
			
		||||
    }
 | 
			
		||||
    on(event: "connection", listener: (socket: Socket) => void): Namespace
 | 
			
		||||
    on(event: "connect", listener: (socket: Socket) => void): Namespace
 | 
			
		||||
    on(event: string, listener: Function): Namespace
 | 
			
		||||
    on(event: any, listener: any): Namespace {
 | 
			
		||||
        return this.sockets.on(event, listener)
 | 
			
		||||
    }
 | 
			
		||||
    to(room: string): Namespace {
 | 
			
		||||
        return this.sockets.to(room)
 | 
			
		||||
    }
 | 
			
		||||
    in(room: string): Namespace {
 | 
			
		||||
        return this.sockets.in(room)
 | 
			
		||||
    }
 | 
			
		||||
    use(fn: (socket: Socket, fn: (err?: any) => void) => void): Namespace {
 | 
			
		||||
        return this.sockets.use(fn)
 | 
			
		||||
    }
 | 
			
		||||
    emit(event: string, ...args: any[]): Namespace {
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        return this.sockets.emit(event, ...args)
 | 
			
		||||
    }
 | 
			
		||||
    send(...args: any[]): Namespace {
 | 
			
		||||
        return this.sockets.send(...args)
 | 
			
		||||
    }
 | 
			
		||||
    write(...args: any[]): Namespace {
 | 
			
		||||
        return this.sockets.write(...args)
 | 
			
		||||
    }
 | 
			
		||||
    clients(...args: any[]): Namespace {
 | 
			
		||||
        return this.sockets.clients(args[0])
 | 
			
		||||
    }
 | 
			
		||||
    compress(...args: any[]): Namespace {
 | 
			
		||||
        return this.sockets.compress(args[0])
 | 
			
		||||
    }
 | 
			
		||||
    // ===============================
 | 
			
		||||
    private initServerConfig() {
 | 
			
		||||
        this.allClients = new Map()
 | 
			
		||||
        this._nsps = new Map()
 | 
			
		||||
        this.connectTimeout(this.options.connectTimeout || 45000)
 | 
			
		||||
        this._parser = this.options.parser || new Parser()
 | 
			
		||||
        this.adapter(this.options.adapter || Adapter)
 | 
			
		||||
    }
 | 
			
		||||
    private selectServerImpl(instance: any) {
 | 
			
		||||
        let WebSocketServerImpl = undefined
 | 
			
		||||
        if (instance.class.name.startsWith('io.netty.channel')) {
 | 
			
		||||
            WebSocketServerImpl = require("../netty").NettyWebSocketServer
 | 
			
		||||
        } else {
 | 
			
		||||
            WebSocketServerImpl = require("../tomcat").TomcatWebSocketServer
 | 
			
		||||
        }
 | 
			
		||||
        this.websocketServer = new WebSocketServerImpl(instance, this.options)
 | 
			
		||||
    }
 | 
			
		||||
    private initServer() {
 | 
			
		||||
        this.websocketServer.on(ServerEvent.connect, (transport: Transport) => {
 | 
			
		||||
            this.onconnection(transport)
 | 
			
		||||
        })
 | 
			
		||||
        this.websocketServer.on(ServerEvent.message, (transport: Transport, text) => {
 | 
			
		||||
            if (this.allClients.has(transport.id)) {
 | 
			
		||||
                let client = this.allClients.get(transport.id)
 | 
			
		||||
                client.onPacket(this._parser.decode(text))
 | 
			
		||||
            } else {
 | 
			
		||||
                console.error(`unknow transport ${transport.id} reciver message ${text}`)
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        this.websocketServer.on(ServerEvent.disconnect, (transport: Transport, reason) => {
 | 
			
		||||
            if (this.allClients.has(transport.id)) {
 | 
			
		||||
                this.allClients.get(transport.id).onclose(reason)
 | 
			
		||||
                this.allClients.delete(transport.id)
 | 
			
		||||
            } else {
 | 
			
		||||
                console.error(`unknow transport ${transport?.id} disconnect cause ${reason}`)
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        this.websocketServer.on(ServerEvent.error, (transport: Transport, cause) => {
 | 
			
		||||
            if (this.allClients.has(transport?.id)) {
 | 
			
		||||
                let client = this.allClients.get(transport?.id)
 | 
			
		||||
                if (client.listeners(ServerEvent.error).length) {
 | 
			
		||||
                    client.emit(ServerEvent.error, cause)
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.error(`client ${client.id} cause error: ${cause}`)
 | 
			
		||||
                    console.ex(cause)
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                console.error(`unknow transport ${transport?.id} cause error: ${cause}`)
 | 
			
		||||
                console.ex(cause)
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Expose main namespace (/).
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const emitterMethods = Object.keys(EventEmitter.prototype).filter(function (
 | 
			
		||||
    key
 | 
			
		||||
) {
 | 
			
		||||
    return typeof EventEmitter.prototype[key] === "function"
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
emitterMethods.forEach(function (fn) {
 | 
			
		||||
    Server.prototype[fn] = function () {
 | 
			
		||||
        return this.sockets[fn].apply(this.sockets, arguments)
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
    Server,
 | 
			
		||||
    Socket,
 | 
			
		||||
    Client,
 | 
			
		||||
    Namespace,
 | 
			
		||||
    ServerOptions
 | 
			
		||||
}
 | 
			
		||||
@@ -1,242 +0,0 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
 | 
			
		||||
import { Client } from './client'
 | 
			
		||||
import { ServerEvent } from './constants'
 | 
			
		||||
import { RESERVED_EVENTS, Socket } from './socket'
 | 
			
		||||
import { Adapter, Room, SocketId } from './adapter'
 | 
			
		||||
import { Server } from './index'
 | 
			
		||||
import { Packet } from './packet'
 | 
			
		||||
import { PacketTypes, SubPacketTypes } from './types'
 | 
			
		||||
 | 
			
		||||
export interface ExtendedError extends Error {
 | 
			
		||||
    data?: any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Namespace extends EventEmitter {
 | 
			
		||||
    public readonly name: string
 | 
			
		||||
    public readonly sockets: Map<SocketId, Socket>
 | 
			
		||||
 | 
			
		||||
    public adapter: Adapter
 | 
			
		||||
 | 
			
		||||
    /** @private */
 | 
			
		||||
    readonly server: Server
 | 
			
		||||
    json: Namespace
 | 
			
		||||
 | 
			
		||||
    /** @private */
 | 
			
		||||
    _fns: Array<
 | 
			
		||||
        (socket: Socket, next: (err: ExtendedError) => void) => void
 | 
			
		||||
    > = [];
 | 
			
		||||
 | 
			
		||||
    /** @private */
 | 
			
		||||
    _rooms: Set<Room>
 | 
			
		||||
 | 
			
		||||
    /** @private */
 | 
			
		||||
    _flags: any = {}
 | 
			
		||||
 | 
			
		||||
    /** @private */
 | 
			
		||||
    _ids: number = 0
 | 
			
		||||
 | 
			
		||||
    constructor(server: Server, name: string) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.server = server
 | 
			
		||||
        this.name = name + ''
 | 
			
		||||
        this._initAdapter()
 | 
			
		||||
        // =======================
 | 
			
		||||
        this.sockets = new Map()
 | 
			
		||||
        this._rooms = new Set()
 | 
			
		||||
    }
 | 
			
		||||
    _initAdapter() {
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        this.adapter = new (this.server.adapter())(this)
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets up namespace middleware.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Namespace} self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public use(
 | 
			
		||||
        fn: (socket: Socket, next: (err?: ExtendedError) => void) => void
 | 
			
		||||
    ): Namespace {
 | 
			
		||||
        this._fns.push(fn)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Executes the middleware for an incoming client.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Socket} socket - the socket that will get added
 | 
			
		||||
     * @param {Function} fn - last fn call in the middleware
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private run(socket: Socket, fn: (err: ExtendedError) => void) {
 | 
			
		||||
        const fns = this._fns.slice(0)
 | 
			
		||||
        if (!fns.length) return fn(null)
 | 
			
		||||
 | 
			
		||||
        function run(i) {
 | 
			
		||||
            fns[i](socket, function (err) {
 | 
			
		||||
                // upon error, short-circuit
 | 
			
		||||
                if (err) return fn(err)
 | 
			
		||||
 | 
			
		||||
                // if no middleware left, summon callback
 | 
			
		||||
                if (!fns[i + 1]) return fn(null)
 | 
			
		||||
 | 
			
		||||
                // go on to next
 | 
			
		||||
                run(i + 1)
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        run(0)
 | 
			
		||||
    }
 | 
			
		||||
    to(name: string): Namespace {
 | 
			
		||||
        this._rooms.add(name)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
    in(name: string): Namespace {
 | 
			
		||||
        return this.to(name)
 | 
			
		||||
    }
 | 
			
		||||
    _add(client: Client, query?: any, fn?: (socket: Socket) => void) {
 | 
			
		||||
        const socket = new Socket(this, client, query || {})
 | 
			
		||||
        console.debug(`client ${client.id} adding socket ${socket.id} to nsp ${this.name}`)
 | 
			
		||||
        this.run(socket, err => {
 | 
			
		||||
            process.nextTick(() => {
 | 
			
		||||
                if ("open" == client.conn.readyState) {
 | 
			
		||||
                    if (err)
 | 
			
		||||
                        return socket._error({
 | 
			
		||||
                            message: err.message,
 | 
			
		||||
                            data: err.data
 | 
			
		||||
                        })
 | 
			
		||||
 | 
			
		||||
                    // track socket
 | 
			
		||||
                    this.sockets.set(socket.id, socket)
 | 
			
		||||
 | 
			
		||||
                    // it's paramount that the internal `onconnect` logic
 | 
			
		||||
                    // fires before user-set events to prevent state order
 | 
			
		||||
                    // violations (such as a disconnection before the connection
 | 
			
		||||
                    // logic is complete)
 | 
			
		||||
                    socket._onconnect()
 | 
			
		||||
                    // !!! at java multi thread need direct callback socket
 | 
			
		||||
                    if (fn) fn(socket)
 | 
			
		||||
 | 
			
		||||
                    // fire user-set events
 | 
			
		||||
                    super.emit(ServerEvent.connect, socket)
 | 
			
		||||
                    super.emit(ServerEvent.connection, socket)
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.debug(`next called after client ${client.id} was closed - ignoring socket`)
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        })
 | 
			
		||||
        return socket
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes a client. Called by each `Socket`.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _remove(socket: Socket): void {
 | 
			
		||||
        if (this.sockets.has(socket.id)) {
 | 
			
		||||
            console.debug(`namespace ${this.name} remove socket ${socket.id}`)
 | 
			
		||||
            this.sockets.delete(socket.id)
 | 
			
		||||
        } else {
 | 
			
		||||
            console.debug(`namespace ${this.name} ignoring remove for ${socket.id}`)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    emit(event: string, ...args: any[]): boolean {
 | 
			
		||||
        if (RESERVED_EVENTS.has(event)) {
 | 
			
		||||
            throw new Error(`"${event}" is a reserved event name`)
 | 
			
		||||
        }
 | 
			
		||||
        // set up packet object
 | 
			
		||||
        var packet = {
 | 
			
		||||
            type: PacketTypes.MESSAGE,
 | 
			
		||||
            sub_type: (this._flags.binary !== undefined ? this._flags.binary : this.hasBin(args)) ? SubPacketTypes.BINARY_EVENT : SubPacketTypes.EVENT,
 | 
			
		||||
            name: event,
 | 
			
		||||
            data: args
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ('function' == typeof args[args.length - 1]) {
 | 
			
		||||
            throw new Error('Callbacks are not supported when broadcasting')
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var rooms = new Set(this._rooms)
 | 
			
		||||
        var flags = Object.assign({}, this._flags)
 | 
			
		||||
 | 
			
		||||
        // reset flags
 | 
			
		||||
        this._rooms.clear()
 | 
			
		||||
        this._flags = {}
 | 
			
		||||
 | 
			
		||||
        this.adapter.broadcast(packet, {
 | 
			
		||||
            rooms: new Set(rooms),
 | 
			
		||||
            flags: flags
 | 
			
		||||
        })
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
    send(...args: any[]): Namespace {
 | 
			
		||||
        this.emit('message', ...args)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
    write(...args: any[]): Namespace {
 | 
			
		||||
        return this.send(...args)
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a list of clients.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Namespace} self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public allSockets(): Promise<Set<SocketId>> {
 | 
			
		||||
        if (!this.adapter) {
 | 
			
		||||
            throw new Error("No adapter for this namespace, are you trying to get the list of clients of a dynamic namespace?")
 | 
			
		||||
        }
 | 
			
		||||
        const rooms = new Set(this._rooms)
 | 
			
		||||
        this._rooms.clear()
 | 
			
		||||
        return this.adapter.sockets(rooms)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the compress flag.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Boolean} compress - if `true`, compresses the sending data
 | 
			
		||||
     * @return {Namespace} self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public compress(compress: boolean): Namespace {
 | 
			
		||||
        this._flags.compress = compress
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
 | 
			
		||||
     * receive messages (because of network slowness or other issues, or because they’re connected through long polling
 | 
			
		||||
     * and is in the middle of a request-response cycle).
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Namespace} self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get volatile(): Namespace {
 | 
			
		||||
        this._flags.volatile = true
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Namespace} self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get local(): Namespace {
 | 
			
		||||
        this._flags.local = true
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hasBin(args: any[]) {
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
    clients(fn: (sockets: Socket[]) => Namespace): Namespace {
 | 
			
		||||
        return fn(Object.values(this.sockets))
 | 
			
		||||
    }
 | 
			
		||||
    close() {
 | 
			
		||||
        this.removeAllListeners(ServerEvent.connect)
 | 
			
		||||
        this.removeAllListeners(ServerEvent.connection)
 | 
			
		||||
        Object.values(this.sockets).forEach(socket => socket.disconnect(false))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
import { PacketTypes, SubPacketTypes } from './types'
 | 
			
		||||
 | 
			
		||||
export interface Packet {
 | 
			
		||||
    type: PacketTypes;
 | 
			
		||||
    sub_type?: SubPacketTypes;
 | 
			
		||||
    nsp?: string;
 | 
			
		||||
    id?: number;
 | 
			
		||||
    name?: string;
 | 
			
		||||
    data?: any;
 | 
			
		||||
    attachments?: any;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,40 +0,0 @@
 | 
			
		||||
import { Namespace } from "./namespace"
 | 
			
		||||
 | 
			
		||||
export class ParentNamespace extends Namespace {
 | 
			
		||||
    private static count: number = 0;
 | 
			
		||||
    private children: Set<Namespace> = new Set();
 | 
			
		||||
 | 
			
		||||
    constructor(server) {
 | 
			
		||||
        super(server, "/_" + ParentNamespace.count++)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _initAdapter() { }
 | 
			
		||||
 | 
			
		||||
    public emit(...args: any[]): boolean {
 | 
			
		||||
        this.children.forEach(nsp => {
 | 
			
		||||
            nsp._rooms = this._rooms
 | 
			
		||||
            nsp._flags = this._flags
 | 
			
		||||
            nsp.emit.apply(nsp, args as any)
 | 
			
		||||
        })
 | 
			
		||||
        this._rooms.clear()
 | 
			
		||||
        this._flags = {}
 | 
			
		||||
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createChild(name) {
 | 
			
		||||
        const namespace = new Namespace(this.server, name)
 | 
			
		||||
        namespace._fns = this._fns.slice(0)
 | 
			
		||||
        this.listeners("connect").forEach(listener =>
 | 
			
		||||
            // @ts-ignore
 | 
			
		||||
            namespace.on("connect", listener)
 | 
			
		||||
        )
 | 
			
		||||
        this.listeners("connection").forEach(listener =>
 | 
			
		||||
            // @ts-ignore
 | 
			
		||||
            namespace.on("connection", listener)
 | 
			
		||||
        )
 | 
			
		||||
        this.children.add(namespace)
 | 
			
		||||
        this.server._nsps.set(name, namespace)
 | 
			
		||||
        return namespace
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,164 +0,0 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
import { Packet } from "./packet"
 | 
			
		||||
import { PacketTypes, SubPacketTypes } from "./types"
 | 
			
		||||
 | 
			
		||||
export class Parser extends EventEmitter {
 | 
			
		||||
    encode(packet: Packet): string {
 | 
			
		||||
        let origin = JSON.stringify(packet)
 | 
			
		||||
        // first is type
 | 
			
		||||
        let 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) {
 | 
			
		||||
            if (packet.name == undefined) { throw new Error(`SubPacketTypes.EVENT name can't be empty!`) }
 | 
			
		||||
            packet.data = [packet.name, ...packet.data]
 | 
			
		||||
        }
 | 
			
		||||
        // json data
 | 
			
		||||
        if (null != packet.data) {
 | 
			
		||||
            let payload = this.tryStringify(packet.data)
 | 
			
		||||
            if (payload !== false) {
 | 
			
		||||
                str += payload
 | 
			
		||||
            } else {
 | 
			
		||||
                return '4"encode error"'
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        console.trace(`encoded ${origin} as ${str}`)
 | 
			
		||||
        return str
 | 
			
		||||
    }
 | 
			
		||||
    tryStringify(str: any) {
 | 
			
		||||
        try {
 | 
			
		||||
            return JSON.stringify(str)
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            return false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    decode(str: string): Packet {
 | 
			
		||||
        let 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
 | 
			
		||||
        let 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)) {
 | 
			
		||||
            let buf = ''
 | 
			
		||||
            while (str.charAt(++i) !== '-') {
 | 
			
		||||
                buf += str.charAt(i)
 | 
			
		||||
                if (i == str.length) break
 | 
			
		||||
            }
 | 
			
		||||
            if (buf != `${Number(buf)}` || str.charAt(i) !== '-') {
 | 
			
		||||
                return this.error('Illegal attachments')
 | 
			
		||||
            }
 | 
			
		||||
            p.attachments = Number(buf)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // look up namespace (if any)
 | 
			
		||||
        if ('/' === str.charAt(i + 1)) {
 | 
			
		||||
            p.nsp = ''
 | 
			
		||||
            while (++i) {
 | 
			
		||||
                let c = str.charAt(i)
 | 
			
		||||
                if (',' === c) break
 | 
			
		||||
                p.nsp += c
 | 
			
		||||
                if (i === str.length) break
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            p.nsp = '/'
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // handle namespace query
 | 
			
		||||
        if (p.nsp.indexOf('?') !== -1) {
 | 
			
		||||
            p.nsp = p.nsp.split('?')[0]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // look up id
 | 
			
		||||
        let next = str.charAt(i + 1)
 | 
			
		||||
        if ('' !== next && !isNaN(Number(next))) {
 | 
			
		||||
            let id = ''
 | 
			
		||||
            while (++i) {
 | 
			
		||||
                let c = str.charAt(i)
 | 
			
		||||
                if (null == c || isNaN(Number(c))) {
 | 
			
		||||
                    --i
 | 
			
		||||
                    break
 | 
			
		||||
                }
 | 
			
		||||
                id += str.charAt(i)
 | 
			
		||||
                if (i === str.length) break
 | 
			
		||||
            }
 | 
			
		||||
            p.id = Number(id)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 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)) {
 | 
			
		||||
            let payload = this.tryParse(str.substr(i))
 | 
			
		||||
            let isPayloadValid = payload !== false && (p.sub_type == SubPacketTypes.ERROR || Array.isArray(payload))
 | 
			
		||||
            if (isPayloadValid) {
 | 
			
		||||
                p.name = payload[0]
 | 
			
		||||
                p.data = payload.slice(1)
 | 
			
		||||
            } else {
 | 
			
		||||
                return this.error('invalid payload ' + str.substr(i))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        console.trace(`decoded ${str} as ${JSON.stringify(p)}`)
 | 
			
		||||
        return p
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tryParse(str: string) {
 | 
			
		||||
        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
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,491 +0,0 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
 | 
			
		||||
import { Packet } from './packet'
 | 
			
		||||
import { PacketTypes, SubPacketTypes } from './types'
 | 
			
		||||
import { Client } from './client'
 | 
			
		||||
import { Namespace } from './namespace'
 | 
			
		||||
import * as querystring from 'querystring'
 | 
			
		||||
import { ServerEvent } from './constants'
 | 
			
		||||
import { Adapter, BroadcastFlags, Room, SocketId } from './adapter'
 | 
			
		||||
import { Server } from 'index'
 | 
			
		||||
 | 
			
		||||
export const RESERVED_EVENTS = new Set([
 | 
			
		||||
    "connect",
 | 
			
		||||
    "connect_error",
 | 
			
		||||
    "disconnect",
 | 
			
		||||
    "disconnecting",
 | 
			
		||||
    // EventEmitter reserved events: https://nodejs.org/api/events.html#events_event_newlistener
 | 
			
		||||
    "newListener",
 | 
			
		||||
    "removeListener"
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The handshake details
 | 
			
		||||
 */
 | 
			
		||||
export interface Handshake {
 | 
			
		||||
    /**
 | 
			
		||||
     * The headers sent as part of the handshake
 | 
			
		||||
     */
 | 
			
		||||
    headers: object
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The date of creation (as string)
 | 
			
		||||
     */
 | 
			
		||||
    time: string
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The ip of the client
 | 
			
		||||
     */
 | 
			
		||||
    address: string
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the connection is cross-domain
 | 
			
		||||
     */
 | 
			
		||||
    xdomain: boolean
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the connection is secure
 | 
			
		||||
     */
 | 
			
		||||
    secure: boolean
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The date of creation (as unix timestamp)
 | 
			
		||||
     */
 | 
			
		||||
    issued: number
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The request URL string
 | 
			
		||||
     */
 | 
			
		||||
    url: string
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The query object
 | 
			
		||||
     */
 | 
			
		||||
    query: any
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The auth object
 | 
			
		||||
     */
 | 
			
		||||
    auth: any
 | 
			
		||||
}
 | 
			
		||||
export class Socket extends EventEmitter {
 | 
			
		||||
    nsp: Namespace
 | 
			
		||||
 | 
			
		||||
    public readonly id: SocketId
 | 
			
		||||
    public readonly handshake: Handshake
 | 
			
		||||
 | 
			
		||||
    public connected: boolean
 | 
			
		||||
    public disconnected: boolean
 | 
			
		||||
 | 
			
		||||
    private readonly server: Server
 | 
			
		||||
    private readonly adapter: Adapter
 | 
			
		||||
 | 
			
		||||
    client: Client
 | 
			
		||||
    private acks: Map<number, () => void>
 | 
			
		||||
 | 
			
		||||
    fns: any[]
 | 
			
		||||
    private flags: BroadcastFlags = {};
 | 
			
		||||
    private _rooms: Set<Room> = new Set();
 | 
			
		||||
    private _anyListeners: Array<(...args: any[]) => void>
 | 
			
		||||
 | 
			
		||||
    constructor(nsp: Namespace, client: Client, auth = {}) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.nsp = nsp
 | 
			
		||||
        this.server = nsp.server
 | 
			
		||||
        this.adapter = this.nsp.adapter
 | 
			
		||||
        this.id = nsp.name !== '/' ? nsp.name + '#' + client.id : client.id
 | 
			
		||||
        this.client = client
 | 
			
		||||
        this.acks = new Map()
 | 
			
		||||
        this.connected = true
 | 
			
		||||
        this.disconnected = false
 | 
			
		||||
        this.handshake = this.buildHandshake(auth)
 | 
			
		||||
 | 
			
		||||
        this.fns = []
 | 
			
		||||
        this.flags = {}
 | 
			
		||||
        this._rooms = new Set()
 | 
			
		||||
    }
 | 
			
		||||
    emit(event: string, ...args: any[]): boolean {
 | 
			
		||||
        let packet: Packet = {
 | 
			
		||||
            type: PacketTypes.MESSAGE,
 | 
			
		||||
            sub_type: (this.flags.binary !== undefined ? this.flags.binary : this.hasBin(args)) ? SubPacketTypes.BINARY_EVENT : SubPacketTypes.EVENT,
 | 
			
		||||
            name: event,
 | 
			
		||||
            data: args
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // access last argument to see if it's an ACK callback
 | 
			
		||||
        if (typeof args[args.length - 1] === "function") {
 | 
			
		||||
            if (this._rooms.size || this.flags.broadcast) {
 | 
			
		||||
                throw new Error("Callbacks are not supported when broadcasting")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // console.debug("emitting packet with ack id %d", this.nsp._ids)
 | 
			
		||||
            this.acks.set(this.nsp._ids, args.pop())
 | 
			
		||||
            packet.id = this.nsp._ids++
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const rooms = new Set(this._rooms)
 | 
			
		||||
        const flags = Object.assign({}, this.flags)
 | 
			
		||||
 | 
			
		||||
        // reset flags
 | 
			
		||||
        this._rooms.clear()
 | 
			
		||||
        this.flags = {}
 | 
			
		||||
 | 
			
		||||
        if (rooms.size || flags.broadcast) {
 | 
			
		||||
            this.adapter.broadcast(packet, {
 | 
			
		||||
                except: new Set([this.id]),
 | 
			
		||||
                rooms: rooms,
 | 
			
		||||
                flags: flags
 | 
			
		||||
            })
 | 
			
		||||
        } else {
 | 
			
		||||
            // dispatch packet
 | 
			
		||||
            this.packet(packet, flags)
 | 
			
		||||
        }
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
    to(name: Room): Socket {
 | 
			
		||||
        this._rooms.add(name)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
    in(room: string): Socket {
 | 
			
		||||
        return this.to(room)
 | 
			
		||||
    }
 | 
			
		||||
    use(fn: (packet: Packet, next: (err?: any) => void) => void): Socket {
 | 
			
		||||
        throw new Error("Method not implemented.")
 | 
			
		||||
    }
 | 
			
		||||
    send(...args: any[]): Socket {
 | 
			
		||||
        this.emit("message", ...args)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
    write(...args: any[]): Socket {
 | 
			
		||||
        return this.send(...args)
 | 
			
		||||
    }
 | 
			
		||||
    public join(rooms: Room | Array<Room>): Promise<void> | void {
 | 
			
		||||
        console.debug(`join room ${rooms}`)
 | 
			
		||||
 | 
			
		||||
        return this.adapter.addAll(
 | 
			
		||||
            this.id,
 | 
			
		||||
            new Set(Array.isArray(rooms) ? rooms : [rooms])
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Leaves a room.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} room
 | 
			
		||||
     * @return a Promise or nothing, depending on the adapter
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public leave(room: string): Promise<void> | void {
 | 
			
		||||
        console.debug(`leave room ${room}`)
 | 
			
		||||
 | 
			
		||||
        return this.adapter.del(this.id, room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Leave all rooms.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private leaveAll(): void {
 | 
			
		||||
        this.adapter.delAll(this.id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
      * Called by `Namespace` upon successful
 | 
			
		||||
      * middleware execution (ie: authorization).
 | 
			
		||||
      * Socket is added to namespace array before
 | 
			
		||||
      * call to join, so adapters can access it.
 | 
			
		||||
      *
 | 
			
		||||
      * @private
 | 
			
		||||
      */
 | 
			
		||||
    _onconnect(): void {
 | 
			
		||||
        console.debug(`socket ${this.id} connected - writing packet`)
 | 
			
		||||
        this.join(this.id)
 | 
			
		||||
        this.packet({ type: PacketTypes.MESSAGE, sub_type: SubPacketTypes.CONNECT, data: { sid: this.id } })
 | 
			
		||||
    }
 | 
			
		||||
    _onpacket(packet: Packet) {
 | 
			
		||||
        switch (packet.sub_type) {
 | 
			
		||||
            // 2
 | 
			
		||||
            case SubPacketTypes.EVENT:
 | 
			
		||||
                this.onevent(packet)
 | 
			
		||||
                break
 | 
			
		||||
            // 5
 | 
			
		||||
            case SubPacketTypes.BINARY_EVENT:
 | 
			
		||||
                this.onevent(packet)
 | 
			
		||||
                break
 | 
			
		||||
            // 3
 | 
			
		||||
            case SubPacketTypes.ACK:
 | 
			
		||||
                this.onack(packet)
 | 
			
		||||
                break
 | 
			
		||||
            // 6
 | 
			
		||||
            case SubPacketTypes.BINARY_ACK:
 | 
			
		||||
                this.onack(packet)
 | 
			
		||||
                break
 | 
			
		||||
            // 1
 | 
			
		||||
            case SubPacketTypes.DISCONNECT:
 | 
			
		||||
                this.ondisconnect()
 | 
			
		||||
                break
 | 
			
		||||
            // 4
 | 
			
		||||
            case SubPacketTypes.ERROR:
 | 
			
		||||
                this._onerror(new Error(packet.data))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    onevent(packet: Packet) {
 | 
			
		||||
        if (null != packet.id) {
 | 
			
		||||
            console.trace(`attaching ack ${packet.id} callback to client ${this.id} event`)
 | 
			
		||||
            this.dispatch(packet, this.ack(packet.id))
 | 
			
		||||
        } else {
 | 
			
		||||
            this.dispatch(packet)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    ack(id: number) {
 | 
			
		||||
        let sent = false
 | 
			
		||||
        return (...args: any[]) => {
 | 
			
		||||
            if (sent) return
 | 
			
		||||
            this.packet({
 | 
			
		||||
                id: id,
 | 
			
		||||
                type: PacketTypes.MESSAGE,
 | 
			
		||||
                sub_type: this.hasBin(args) ? SubPacketTypes.BINARY_ACK : SubPacketTypes.ACK,
 | 
			
		||||
                data: args
 | 
			
		||||
            })
 | 
			
		||||
            sent = true
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    onack(packet: Packet) {
 | 
			
		||||
        let ack = this.acks.get(packet.id)
 | 
			
		||||
        if ('function' == typeof ack) {
 | 
			
		||||
            console.trace(`calling ack ${packet.id} on socket ${this.id} with ${packet.data}`)
 | 
			
		||||
            ack.apply(this, packet.data)
 | 
			
		||||
            this.acks.delete(packet.id)
 | 
			
		||||
        } else {
 | 
			
		||||
            console.trace(`bad ack ${packet.id} on socket ${this.id}`)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon client disconnect packet.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private ondisconnect(): void {
 | 
			
		||||
        console.debug(`socket ${this.id} got disconnect packet`)
 | 
			
		||||
        this._onclose("client namespace disconnect")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a client error.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _onerror(err): void {
 | 
			
		||||
        if (this.listeners("error").length) {
 | 
			
		||||
            super.emit("error", err)
 | 
			
		||||
        } else {
 | 
			
		||||
            console.error(`Missing error handler on 'socket(${this.id})'.`)
 | 
			
		||||
            console.error(err.stack)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon closing. Called by `Client`.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} reason
 | 
			
		||||
     * @throw {Error} optional error object
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _onclose(reason: string) {
 | 
			
		||||
        if (!this.connected) return this
 | 
			
		||||
        console.debug(`closing socket ${this.id} - reason: ${reason} connected: ${this.connected}`)
 | 
			
		||||
        super.emit(ServerEvent.disconnecting, reason)
 | 
			
		||||
        this.leaveAll()
 | 
			
		||||
        this.nsp._remove(this)
 | 
			
		||||
        this.client._remove(this)
 | 
			
		||||
        this.connected = false
 | 
			
		||||
        this.disconnected = true
 | 
			
		||||
        super.emit(ServerEvent.disconnect, reason)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Produces an `error` packet.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} err - error object
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _error(err) {
 | 
			
		||||
        this.packet({ type: PacketTypes.MESSAGE, sub_type: SubPacketTypes.ERROR, data: err })
 | 
			
		||||
    }
 | 
			
		||||
    disconnect(close?: boolean): Socket {
 | 
			
		||||
        if (!this.connected) return this
 | 
			
		||||
        if (close) {
 | 
			
		||||
            this.client._disconnect()
 | 
			
		||||
        } else {
 | 
			
		||||
            this.packet({ type: PacketTypes.MESSAGE, sub_type: SubPacketTypes.DISCONNECT })
 | 
			
		||||
            this._onclose('server namespace disconnect')
 | 
			
		||||
        }
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    compress(compress: boolean): Socket {
 | 
			
		||||
        throw new Error("Method not implemented.")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
 | 
			
		||||
    * receive messages (because of network slowness or other issues, or because they’re connected through long polling
 | 
			
		||||
    * and is in the middle of a request-response cycle).
 | 
			
		||||
    *
 | 
			
		||||
    * @return {Socket} self
 | 
			
		||||
    * @public
 | 
			
		||||
    */
 | 
			
		||||
    public get volatile(): Socket {
 | 
			
		||||
        this.flags.volatile = true
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a modifier for a subsequent event emission that the event data will only be broadcast to every sockets but the
 | 
			
		||||
     * sender.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Socket} self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get broadcast(): Socket {
 | 
			
		||||
        this.flags.broadcast = true
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Socket} self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get local(): Socket {
 | 
			
		||||
        this.flags.local = true
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A reference to the request that originated the underlying Engine.IO Socket.
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get request(): any {
 | 
			
		||||
        return this.client.request
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A reference to the underlying Client transport connection (Engine.IO Socket object).
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get conn() {
 | 
			
		||||
        return this.client.conn
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get rooms(): Set<Room> {
 | 
			
		||||
        return this.adapter.socketRooms(this.id) || new Set()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
 | 
			
		||||
     * callback.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public onAny(listener: (...args: any[]) => void): Socket {
 | 
			
		||||
        this._anyListeners = this._anyListeners || []
 | 
			
		||||
        this._anyListeners.push(listener)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
 | 
			
		||||
     * callback. The listener is added to the beginning of the listeners array.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public prependAny(listener: (...args: any[]) => void): Socket {
 | 
			
		||||
        this._anyListeners = this._anyListeners || []
 | 
			
		||||
        this._anyListeners.unshift(listener)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes the listener that will be fired when any event is emitted.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public offAny(listener?: (...args: any[]) => void): Socket {
 | 
			
		||||
        if (!this._anyListeners) {
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
        if (listener) {
 | 
			
		||||
            const listeners = this._anyListeners
 | 
			
		||||
            for (let i = 0; i < listeners.length; i++) {
 | 
			
		||||
                if (listener === listeners[i]) {
 | 
			
		||||
                    listeners.splice(i, 1)
 | 
			
		||||
                    return this
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            this._anyListeners = []
 | 
			
		||||
        }
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,
 | 
			
		||||
     * e.g. to remove listeners.
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public listenersAny() {
 | 
			
		||||
        return this._anyListeners || []
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ==========================================
 | 
			
		||||
    buildHandshake(auth): Handshake {
 | 
			
		||||
        let requestUri = this.request.uri()
 | 
			
		||||
        let headers = {}
 | 
			
		||||
        let nativeHeaders = this.request.headers()
 | 
			
		||||
        nativeHeaders.forEach(function (header) {
 | 
			
		||||
            headers[header.getKey()] = header.getValue()
 | 
			
		||||
        })
 | 
			
		||||
        return {
 | 
			
		||||
            headers: headers,
 | 
			
		||||
            time: new Date() + '',
 | 
			
		||||
            address: this.conn.remoteAddress + '',
 | 
			
		||||
            xdomain: !!headers['origin'],
 | 
			
		||||
            secure: false,
 | 
			
		||||
            issued: +new Date(),
 | 
			
		||||
            url: requestUri,
 | 
			
		||||
            query: querystring.parse(requestUri.indexOf('?') != -1 ? requestUri.split('?')[1] : ''),
 | 
			
		||||
            auth
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    packet(packet: Packet, opts: any = { preEncoded: false }) {
 | 
			
		||||
        if (!opts.preEncoded) {
 | 
			
		||||
            packet.nsp = this.nsp.name
 | 
			
		||||
            opts.compress = false !== opts.compress
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            this.client._packet(packet, opts)
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            this._onerror(error)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    dispatch(packet: Packet, ack?: () => void) {
 | 
			
		||||
        if (ack) { this.acks.set(packet.id, ack) }
 | 
			
		||||
        super.emit(packet.name, ...packet.data, ack)
 | 
			
		||||
    }
 | 
			
		||||
    private hasBin(obj: any) {
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
export enum PacketTypes {
 | 
			
		||||
    OPEN,
 | 
			
		||||
    CLOSE,
 | 
			
		||||
    PING,
 | 
			
		||||
    PONG,
 | 
			
		||||
    MESSAGE,
 | 
			
		||||
    UPGRADE,
 | 
			
		||||
    NOOP,
 | 
			
		||||
}
 | 
			
		||||
export enum SubPacketTypes {
 | 
			
		||||
    CONNECT,
 | 
			
		||||
    DISCONNECT,
 | 
			
		||||
    EVENT,
 | 
			
		||||
    ACK,
 | 
			
		||||
    ERROR,
 | 
			
		||||
    BINARY_EVENT,
 | 
			
		||||
    BINARY_ACK
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										279
									
								
								packages/websocket/src/socket.io-adapter/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								packages/websocket/src/socket.io-adapter/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,279 @@
 | 
			
		||||
import { EventEmitter } from "events"
 | 
			
		||||
 | 
			
		||||
export type SocketId = string
 | 
			
		||||
export type Room = string
 | 
			
		||||
 | 
			
		||||
export interface BroadcastFlags {
 | 
			
		||||
    volatile?: boolean
 | 
			
		||||
    compress?: boolean
 | 
			
		||||
    local?: boolean
 | 
			
		||||
    broadcast?: boolean
 | 
			
		||||
    binary?: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BroadcastOptions {
 | 
			
		||||
    rooms: Set<Room>
 | 
			
		||||
    except?: Set<SocketId>
 | 
			
		||||
    flags?: BroadcastFlags
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Adapter extends EventEmitter {
 | 
			
		||||
    public rooms: Map<Room, Set<SocketId>> = new Map();
 | 
			
		||||
    public sids: Map<SocketId, Set<Room>> = new Map();
 | 
			
		||||
    private readonly encoder
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * In-memory adapter constructor.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Namespace} nsp
 | 
			
		||||
     */
 | 
			
		||||
    constructor(readonly nsp: any) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.encoder = nsp.server.encoder
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * To be overridden
 | 
			
		||||
     */
 | 
			
		||||
    public init(): Promise<void> | void { }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * To be overridden
 | 
			
		||||
     */
 | 
			
		||||
    public close(): Promise<void> | void { }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a socket to a list of room.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {SocketId}  id      the socket id
 | 
			
		||||
     * @param {Set<Room>} rooms   a set of rooms
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public addAll(id: SocketId, rooms: Set<Room>): Promise<void> | void {
 | 
			
		||||
        if (!this.sids.has(id)) {
 | 
			
		||||
            this.sids.set(id, new Set())
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const room of rooms) {
 | 
			
		||||
            this.sids.get(id).add(room)
 | 
			
		||||
 | 
			
		||||
            if (!this.rooms.has(room)) {
 | 
			
		||||
                this.rooms.set(room, new Set())
 | 
			
		||||
                this.emit("create-room", room)
 | 
			
		||||
            }
 | 
			
		||||
            if (!this.rooms.get(room).has(id)) {
 | 
			
		||||
                this.rooms.get(room).add(id)
 | 
			
		||||
                this.emit("join-room", room, id)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes a socket from a room.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {SocketId} id     the socket id
 | 
			
		||||
     * @param {Room}     room   the room name
 | 
			
		||||
     */
 | 
			
		||||
    public del(id: SocketId, room: Room): Promise<void> | void {
 | 
			
		||||
        if (this.sids.has(id)) {
 | 
			
		||||
            this.sids.get(id).delete(room)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._del(room, id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private _del(room, id) {
 | 
			
		||||
        if (this.rooms.has(room)) {
 | 
			
		||||
            const deleted = this.rooms.get(room).delete(id)
 | 
			
		||||
            if (deleted) {
 | 
			
		||||
                this.emit("leave-room", room, id)
 | 
			
		||||
            }
 | 
			
		||||
            if (this.rooms.get(room).size === 0) {
 | 
			
		||||
                this.rooms.delete(room)
 | 
			
		||||
                this.emit("delete-room", room)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes a socket from all rooms it's joined.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {SocketId} id   the socket id
 | 
			
		||||
     */
 | 
			
		||||
    public delAll(id: SocketId): void {
 | 
			
		||||
        if (!this.sids.has(id)) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const room of this.sids.get(id)) {
 | 
			
		||||
            this._del(room, id)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.sids.delete(id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Broadcasts a packet.
 | 
			
		||||
     *
 | 
			
		||||
     * Options:
 | 
			
		||||
     *  - `flags` {Object} flags for this packet
 | 
			
		||||
     *  - `except` {Array} sids that should be excluded
 | 
			
		||||
     *  - `rooms` {Array} list of rooms to broadcast to
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} packet   the packet object
 | 
			
		||||
     * @param {Object} opts     the options
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public broadcast(packet: any, opts: BroadcastOptions): void {
 | 
			
		||||
        const flags = opts.flags || {}
 | 
			
		||||
        const basePacketOpts = {
 | 
			
		||||
            preEncoded: true,
 | 
			
		||||
            volatile: flags.volatile,
 | 
			
		||||
            compress: flags.compress
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        packet.nsp = this.nsp.name
 | 
			
		||||
        const encodedPackets = this.encoder.encode(packet)
 | 
			
		||||
 | 
			
		||||
        const packetOpts = encodedPackets.map(encodedPacket => {
 | 
			
		||||
            if (typeof encodedPacket === "string") {
 | 
			
		||||
                return {
 | 
			
		||||
                    ...basePacketOpts,
 | 
			
		||||
                    wsPreEncoded: "4" + encodedPacket // "4" being the "message" packet type in Engine.IO
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                return basePacketOpts
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        this.apply(opts, socket => {
 | 
			
		||||
            for (let i = 0; i < encodedPackets.length; i++) {
 | 
			
		||||
                socket.client.writeToEngine(encodedPackets[i], packetOpts[i])
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a list of sockets by sid.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Set<Room>} rooms   the explicit set of rooms to check.
 | 
			
		||||
     */
 | 
			
		||||
    public sockets(rooms: Set<Room>): Promise<Set<SocketId>> {
 | 
			
		||||
        const sids = new Set<SocketId>()
 | 
			
		||||
 | 
			
		||||
        this.apply({ rooms }, socket => {
 | 
			
		||||
            sids.add(socket.id)
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve(sids)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the list of rooms a given socket has joined.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {SocketId} id   the socket id
 | 
			
		||||
     */
 | 
			
		||||
    public socketRooms(id: SocketId): Set<Room> | undefined {
 | 
			
		||||
        return this.sids.get(id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the matching socket instances
 | 
			
		||||
     *
 | 
			
		||||
     * @param opts - the filters to apply
 | 
			
		||||
     */
 | 
			
		||||
    public fetchSockets(opts: BroadcastOptions): Promise<any[]> {
 | 
			
		||||
        const sockets = []
 | 
			
		||||
 | 
			
		||||
        this.apply(opts, socket => {
 | 
			
		||||
            sockets.push(socket)
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve(sockets)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes the matching socket instances join the specified rooms
 | 
			
		||||
     *
 | 
			
		||||
     * @param opts - the filters to apply
 | 
			
		||||
     * @param rooms - the rooms to join
 | 
			
		||||
     */
 | 
			
		||||
    public addSockets(opts: BroadcastOptions, rooms: Room[]): void {
 | 
			
		||||
        this.apply(opts, socket => {
 | 
			
		||||
            socket.join(rooms)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes the matching socket instances leave the specified rooms
 | 
			
		||||
     *
 | 
			
		||||
     * @param opts - the filters to apply
 | 
			
		||||
     * @param rooms - the rooms to leave
 | 
			
		||||
     */
 | 
			
		||||
    public delSockets(opts: BroadcastOptions, rooms: Room[]): void {
 | 
			
		||||
        this.apply(opts, socket => {
 | 
			
		||||
            rooms.forEach(room => socket.leave(room))
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes the matching socket instances disconnect
 | 
			
		||||
     *
 | 
			
		||||
     * @param opts - the filters to apply
 | 
			
		||||
     * @param close - whether to close the underlying connection
 | 
			
		||||
     */
 | 
			
		||||
    public disconnectSockets(opts: BroadcastOptions, close: boolean): void {
 | 
			
		||||
        this.apply(opts, socket => {
 | 
			
		||||
            socket.disconnect(close)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private apply(opts: BroadcastOptions, callback: (socket) => void): void {
 | 
			
		||||
        const rooms = opts.rooms
 | 
			
		||||
        const except = this.computeExceptSids(opts.except)
 | 
			
		||||
 | 
			
		||||
        if (rooms.size) {
 | 
			
		||||
            const ids = new Set()
 | 
			
		||||
            for (const room of rooms) {
 | 
			
		||||
                if (!this.rooms.has(room)) continue
 | 
			
		||||
 | 
			
		||||
                for (const id of this.rooms.get(room)) {
 | 
			
		||||
                    if (ids.has(id) || except.has(id)) continue
 | 
			
		||||
                    const socket = this.nsp.sockets.get(id)
 | 
			
		||||
                    if (socket) {
 | 
			
		||||
                        callback(socket)
 | 
			
		||||
                        ids.add(id)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            for (const [id] of this.sids) {
 | 
			
		||||
                if (except.has(id)) continue
 | 
			
		||||
                const socket = this.nsp.sockets.get(id)
 | 
			
		||||
                if (socket) callback(socket)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private computeExceptSids(exceptRooms?: Set<Room>) {
 | 
			
		||||
        const exceptSids = new Set()
 | 
			
		||||
        if (exceptRooms && exceptRooms.size > 0) {
 | 
			
		||||
            for (const room of exceptRooms) {
 | 
			
		||||
                if (this.rooms.has(room)) {
 | 
			
		||||
                    this.rooms.get(room).forEach(sid => exceptSids.add(sid))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return exceptSids
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send a packet to the other Socket.IO servers in the cluster
 | 
			
		||||
     * @param packet - an array of arguments, which may include an acknowledgement callback at the end
 | 
			
		||||
     */
 | 
			
		||||
    public serverSideEmit(packet: any[]): void {
 | 
			
		||||
        throw new Error(
 | 
			
		||||
            "this adapter does not support the serverSideEmit() functionality"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										78
									
								
								packages/websocket/src/socket.io-parser/binary.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								packages/websocket/src/socket.io-parser/binary.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
import { isBinary } from "./is-binary"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Replaces every Buffer | ArrayBuffer | Blob | File in packet with a numbered placeholder.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} packet - socket.io event packet
 | 
			
		||||
 * @return {Object} with deconstructed packet and list of buffers
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export function deconstructPacket(packet) {
 | 
			
		||||
    const buffers = []
 | 
			
		||||
    const packetData = packet.data
 | 
			
		||||
    const pack = packet
 | 
			
		||||
    pack.data = _deconstructPacket(packetData, buffers)
 | 
			
		||||
    pack.attachments = buffers.length // number of binary 'attachments'
 | 
			
		||||
    return { packet: pack, buffers: buffers }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _deconstructPacket(data, buffers) {
 | 
			
		||||
    if (!data) return data
 | 
			
		||||
 | 
			
		||||
    if (isBinary(data)) {
 | 
			
		||||
        const placeholder = { _placeholder: true, num: buffers.length }
 | 
			
		||||
        buffers.push(data)
 | 
			
		||||
        return placeholder
 | 
			
		||||
    } else if (Array.isArray(data)) {
 | 
			
		||||
        const newData = new Array(data.length)
 | 
			
		||||
        for (let i = 0; i < data.length; i++) {
 | 
			
		||||
            newData[i] = _deconstructPacket(data[i], buffers)
 | 
			
		||||
        }
 | 
			
		||||
        return newData
 | 
			
		||||
    } else if (typeof data === "object" && !(data instanceof Date)) {
 | 
			
		||||
        const newData = {}
 | 
			
		||||
        for (const key in data) {
 | 
			
		||||
            if (data.hasOwnProperty(key)) {
 | 
			
		||||
                newData[key] = _deconstructPacket(data[key], buffers)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return newData
 | 
			
		||||
    }
 | 
			
		||||
    return data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Reconstructs a binary packet from its placeholder packet and buffers
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} packet - event packet with placeholders
 | 
			
		||||
 * @param {Array} buffers - binary buffers to put in placeholder positions
 | 
			
		||||
 * @return {Object} reconstructed packet
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export function reconstructPacket(packet, buffers) {
 | 
			
		||||
    packet.data = _reconstructPacket(packet.data, buffers)
 | 
			
		||||
    packet.attachments = undefined // no longer useful
 | 
			
		||||
    return packet
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function _reconstructPacket(data, buffers) {
 | 
			
		||||
    if (!data) return data
 | 
			
		||||
 | 
			
		||||
    if (data && data._placeholder) {
 | 
			
		||||
        return buffers[data.num] // appropriate buffer (should be natural order anyway)
 | 
			
		||||
    } else if (Array.isArray(data)) {
 | 
			
		||||
        for (let i = 0; i < data.length; i++) {
 | 
			
		||||
            data[i] = _reconstructPacket(data[i], buffers)
 | 
			
		||||
        }
 | 
			
		||||
    } else if (typeof data === "object") {
 | 
			
		||||
        for (const key in data) {
 | 
			
		||||
            if (data.hasOwnProperty(key)) {
 | 
			
		||||
                data[key] = _reconstructPacket(data[key], buffers)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return data
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										316
									
								
								packages/websocket/src/socket.io-parser/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										316
									
								
								packages/websocket/src/socket.io-parser/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,316 @@
 | 
			
		||||
import EventEmitter = require("events")
 | 
			
		||||
import { deconstructPacket, reconstructPacket } from "./binary"
 | 
			
		||||
import { isBinary, hasBinary } from "./is-binary"
 | 
			
		||||
 | 
			
		||||
// const debug = require("debug")("socket.io-parser")
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Protocol version.
 | 
			
		||||
 *
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export const protocol: number = 5
 | 
			
		||||
 | 
			
		||||
export enum PacketType {
 | 
			
		||||
    CONNECT,
 | 
			
		||||
    DISCONNECT,
 | 
			
		||||
    EVENT,
 | 
			
		||||
    ACK,
 | 
			
		||||
    CONNECT_ERROR,
 | 
			
		||||
    BINARY_EVENT,
 | 
			
		||||
    BINARY_ACK,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Packet {
 | 
			
		||||
    type: PacketType
 | 
			
		||||
    nsp: string
 | 
			
		||||
    data?: any
 | 
			
		||||
    id?: number
 | 
			
		||||
    attachments?: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A socket.io Encoder instance
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export class Encoder {
 | 
			
		||||
    /**
 | 
			
		||||
     * Encode a packet as a single string if non-binary, or as a
 | 
			
		||||
     * buffer sequence, depending on packet type.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} obj - packet object
 | 
			
		||||
     */
 | 
			
		||||
    public encode(obj: Packet) {
 | 
			
		||||
        console.trace("encoding packet", JSON.stringify(obj))
 | 
			
		||||
 | 
			
		||||
        if (obj.type === PacketType.EVENT || obj.type === PacketType.ACK) {
 | 
			
		||||
            if (hasBinary(obj)) {
 | 
			
		||||
                obj.type =
 | 
			
		||||
                    obj.type === PacketType.EVENT
 | 
			
		||||
                        ? PacketType.BINARY_EVENT
 | 
			
		||||
                        : PacketType.BINARY_ACK
 | 
			
		||||
                return this.encodeAsBinary(obj)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return [this.encodeAsString(obj)]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Encode packet as string.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    private encodeAsString(obj: Packet) {
 | 
			
		||||
        // first is type
 | 
			
		||||
        let str = "" + obj.type
 | 
			
		||||
 | 
			
		||||
        // attachments if we have them
 | 
			
		||||
        if (
 | 
			
		||||
            obj.type === PacketType.BINARY_EVENT ||
 | 
			
		||||
            obj.type === PacketType.BINARY_ACK
 | 
			
		||||
        ) {
 | 
			
		||||
            str += obj.attachments + "-"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // if we have a namespace other than `/`
 | 
			
		||||
        // we append it followed by a comma `,`
 | 
			
		||||
        if (obj.nsp && "/" !== obj.nsp) {
 | 
			
		||||
            str += obj.nsp + ","
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // immediately followed by the id
 | 
			
		||||
        if (null != obj.id) {
 | 
			
		||||
            str += obj.id
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // json data
 | 
			
		||||
        if (null != obj.data) {
 | 
			
		||||
            str += JSON.stringify(obj.data)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        console.trace("encoded", JSON.stringify(obj), "as", str)
 | 
			
		||||
        return str
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Encode packet as 'buffer sequence' by removing blobs, and
 | 
			
		||||
     * deconstructing packet into object with placeholders and
 | 
			
		||||
     * a list of buffers.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    private encodeAsBinary(obj: Packet) {
 | 
			
		||||
        const deconstruction = deconstructPacket(obj)
 | 
			
		||||
        const pack = this.encodeAsString(deconstruction.packet)
 | 
			
		||||
        const buffers = deconstruction.buffers
 | 
			
		||||
 | 
			
		||||
        buffers.unshift(pack) // add packet info to beginning of data list
 | 
			
		||||
        return buffers // write all the buffers
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A socket.io Decoder instance
 | 
			
		||||
 *
 | 
			
		||||
 * @return {Object} decoder
 | 
			
		||||
 */
 | 
			
		||||
export class Decoder extends EventEmitter {
 | 
			
		||||
    private reconstructor: BinaryReconstructor
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Decodes an encoded packet string into packet JSON.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} obj - encoded packet
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    public add(obj: any) {
 | 
			
		||||
        let packet
 | 
			
		||||
        if (typeof obj === "string") {
 | 
			
		||||
            packet = this.decodeString(obj)
 | 
			
		||||
            if (
 | 
			
		||||
                packet.type === PacketType.BINARY_EVENT ||
 | 
			
		||||
                packet.type === PacketType.BINARY_ACK
 | 
			
		||||
            ) {
 | 
			
		||||
                // binary packet's json
 | 
			
		||||
                this.reconstructor = new BinaryReconstructor(packet)
 | 
			
		||||
 | 
			
		||||
                // no attachments, labeled binary but no binary data to follow
 | 
			
		||||
                if (packet.attachments === 0) {
 | 
			
		||||
                    super.emit("decoded", packet)
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // non-binary full packet
 | 
			
		||||
                super.emit("decoded", packet)
 | 
			
		||||
            }
 | 
			
		||||
        } else if (isBinary(obj) || obj.base64) {
 | 
			
		||||
            // raw binary data
 | 
			
		||||
            if (!this.reconstructor) {
 | 
			
		||||
                throw new Error("got binary data when not reconstructing a packet")
 | 
			
		||||
            } else {
 | 
			
		||||
                packet = this.reconstructor.takeBinaryData(obj)
 | 
			
		||||
                if (packet) {
 | 
			
		||||
                    // received final buffer
 | 
			
		||||
                    this.reconstructor = null
 | 
			
		||||
                    super.emit("decoded", packet)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new Error("Unknown type: " + obj)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Decode a packet String (JSON data)
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} str
 | 
			
		||||
     * @return {Object} packet
 | 
			
		||||
     */
 | 
			
		||||
    private decodeString(str): Packet {
 | 
			
		||||
        let i = 0
 | 
			
		||||
        // look up type
 | 
			
		||||
        const p: any = {
 | 
			
		||||
            type: Number(str.charAt(0)),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (PacketType[p.type] === undefined) {
 | 
			
		||||
            throw new Error("unknown packet type " + p.type)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // look up attachments if type binary
 | 
			
		||||
        if (
 | 
			
		||||
            p.type === PacketType.BINARY_EVENT ||
 | 
			
		||||
            p.type === PacketType.BINARY_ACK
 | 
			
		||||
        ) {
 | 
			
		||||
            const start = i + 1
 | 
			
		||||
            while (str.charAt(++i) !== "-" && i != str.length) { }
 | 
			
		||||
            const buf = str.substring(start, i)
 | 
			
		||||
            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)) {
 | 
			
		||||
            const start = i + 1
 | 
			
		||||
            while (++i) {
 | 
			
		||||
                const c = str.charAt(i)
 | 
			
		||||
                if ("," === c) break
 | 
			
		||||
                if (i === str.length) break
 | 
			
		||||
            }
 | 
			
		||||
            p.nsp = str.substring(start, i)
 | 
			
		||||
        } else {
 | 
			
		||||
            p.nsp = "/"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // look up id
 | 
			
		||||
        const next = str.charAt(i + 1)
 | 
			
		||||
        if ("" !== next && Number(next) == next) {
 | 
			
		||||
            const start = i + 1
 | 
			
		||||
            while (++i) {
 | 
			
		||||
                const c = str.charAt(i)
 | 
			
		||||
                if (null == c || Number(c) != c) {
 | 
			
		||||
                    --i
 | 
			
		||||
                    break
 | 
			
		||||
                }
 | 
			
		||||
                if (i === str.length) break
 | 
			
		||||
            }
 | 
			
		||||
            p.id = Number(str.substring(start, i + 1))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // look up json data
 | 
			
		||||
        if (str.charAt(++i)) {
 | 
			
		||||
            const payload = tryParse(str.substr(i))
 | 
			
		||||
            if (Decoder.isPayloadValid(p.type, payload)) {
 | 
			
		||||
                p.data = payload
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new Error("invalid payload")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        console.trace("decoded", str, "as", p)
 | 
			
		||||
        return p
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static isPayloadValid(type: PacketType, payload: any): boolean {
 | 
			
		||||
        switch (type) {
 | 
			
		||||
            case PacketType.CONNECT:
 | 
			
		||||
                return typeof payload === "object"
 | 
			
		||||
            case PacketType.DISCONNECT:
 | 
			
		||||
                return payload === undefined
 | 
			
		||||
            case PacketType.CONNECT_ERROR:
 | 
			
		||||
                return typeof payload === "string" || typeof payload === "object"
 | 
			
		||||
            case PacketType.EVENT:
 | 
			
		||||
            case PacketType.BINARY_EVENT:
 | 
			
		||||
                return Array.isArray(payload) && payload.length > 0
 | 
			
		||||
            case PacketType.ACK:
 | 
			
		||||
            case PacketType.BINARY_ACK:
 | 
			
		||||
                return Array.isArray(payload)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deallocates a parser's resources
 | 
			
		||||
     */
 | 
			
		||||
    public destroy() {
 | 
			
		||||
        if (this.reconstructor) {
 | 
			
		||||
            this.reconstructor.finishedReconstruction()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function tryParse(str) {
 | 
			
		||||
    try {
 | 
			
		||||
        return JSON.parse(str)
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A manager of a binary event's 'buffer sequence'. Should
 | 
			
		||||
 * be constructed whenever a packet of type BINARY_EVENT is
 | 
			
		||||
 * decoded.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} packet
 | 
			
		||||
 * @return {BinaryReconstructor} initialized reconstructor
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class BinaryReconstructor {
 | 
			
		||||
    private reconPack
 | 
			
		||||
    private buffers: Array<Buffer | ArrayBuffer> = [];
 | 
			
		||||
 | 
			
		||||
    constructor(readonly packet: Packet) {
 | 
			
		||||
        this.reconPack = packet
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to be called when binary data received from connection
 | 
			
		||||
     * after a BINARY_EVENT packet.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Buffer | ArrayBuffer} binData - the raw binary data received
 | 
			
		||||
     * @return {null | Object} returns null if more binary data is expected or
 | 
			
		||||
     *   a reconstructed packet object if all buffers have been received.
 | 
			
		||||
     */
 | 
			
		||||
    public takeBinaryData(binData) {
 | 
			
		||||
        this.buffers.push(binData)
 | 
			
		||||
        if (this.buffers.length === this.reconPack.attachments) {
 | 
			
		||||
            // done with buffer list
 | 
			
		||||
            const packet = reconstructPacket(this.reconPack, this.buffers)
 | 
			
		||||
            this.finishedReconstruction()
 | 
			
		||||
            return packet
 | 
			
		||||
        }
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Cleans up binary packet reconstruction variables.
 | 
			
		||||
     */
 | 
			
		||||
    public finishedReconstruction() {
 | 
			
		||||
        this.reconPack = null
 | 
			
		||||
        this.buffers = []
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								packages/websocket/src/socket.io-parser/is-binary.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								packages/websocket/src/socket.io-parser/is-binary.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
const withNativeArrayBuffer: boolean = typeof ArrayBuffer === "function"
 | 
			
		||||
 | 
			
		||||
const isView = (obj: any) => {
 | 
			
		||||
    return typeof ArrayBuffer.isView === "function"
 | 
			
		||||
        ? ArrayBuffer.isView(obj)
 | 
			
		||||
        : obj.buffer instanceof ArrayBuffer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const toString = Object.prototype.toString
 | 
			
		||||
const withNativeBlob = false
 | 
			
		||||
// typeof Blob === "function" ||
 | 
			
		||||
// (typeof Blob !== "undefined" &&
 | 
			
		||||
//     toString.call(Blob) === "[object BlobConstructor]")
 | 
			
		||||
const withNativeFile = false
 | 
			
		||||
// typeof File === "function" ||
 | 
			
		||||
// (typeof File !== "undefined" &&
 | 
			
		||||
//     toString.call(File) === "[object FileConstructor]")
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns true if obj is a Buffer, an ArrayBuffer, a Blob or a File.
 | 
			
		||||
 *
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export function isBinary(obj: any) {
 | 
			
		||||
    return (
 | 
			
		||||
        (withNativeArrayBuffer && (obj instanceof ArrayBuffer || isView(obj)))
 | 
			
		||||
        // || (withNativeBlob && obj instanceof Blob) || (withNativeFile && obj instanceof File)
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function hasBinary(obj: any, toJSON?: boolean) {
 | 
			
		||||
    if (!obj || typeof obj !== "object") {
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (Array.isArray(obj)) {
 | 
			
		||||
        for (let i = 0, l = obj.length; i < l; i++) {
 | 
			
		||||
            if (hasBinary(obj[i])) {
 | 
			
		||||
                return true
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (isBinary(obj)) {
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        obj.toJSON &&
 | 
			
		||||
        typeof obj.toJSON === "function" &&
 | 
			
		||||
        arguments.length === 1
 | 
			
		||||
    ) {
 | 
			
		||||
        return hasBinary(obj.toJSON(), true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const key in obj) {
 | 
			
		||||
        if (Object.prototype.hasOwnProperty.call(obj, key) && hasBinary(obj[key])) {
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										320
									
								
								packages/websocket/src/socket.io/broadcast-operator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								packages/websocket/src/socket.io/broadcast-operator.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,320 @@
 | 
			
		||||
// import type { BroadcastFlags, Room, SocketId } from "socket.io-adapter"
 | 
			
		||||
import type { BroadcastFlags, Room, SocketId } from "../socket.io-adapter"
 | 
			
		||||
import { Handshake, RESERVED_EVENTS, Socket } from "./socket"
 | 
			
		||||
// import { PacketType } from "socket.io-parser"
 | 
			
		||||
import { PacketType } from "../socket.io-parser"
 | 
			
		||||
// import type { Adapter } from "socket.io-adapter"
 | 
			
		||||
import type { Adapter } from "../socket.io-adapter"
 | 
			
		||||
import type {
 | 
			
		||||
    EventParams,
 | 
			
		||||
    EventNames,
 | 
			
		||||
    EventsMap,
 | 
			
		||||
    TypedEventBroadcaster,
 | 
			
		||||
} from "./typed-events"
 | 
			
		||||
 | 
			
		||||
export class BroadcastOperator<EmitEvents extends EventsMap>
 | 
			
		||||
    implements TypedEventBroadcaster<EmitEvents>
 | 
			
		||||
{
 | 
			
		||||
    constructor(
 | 
			
		||||
        private readonly adapter: Adapter,
 | 
			
		||||
        private readonly rooms: Set<Room> = new Set<Room>(),
 | 
			
		||||
        private readonly exceptRooms: Set<Room> = new Set<Room>(),
 | 
			
		||||
        private readonly flags: BroadcastFlags = {}
 | 
			
		||||
    ) { }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Targets a room when emitting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @return a new BroadcastOperator instance
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public to(room: Room | Room[]): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        const rooms = new Set(this.rooms)
 | 
			
		||||
        if (Array.isArray(room)) {
 | 
			
		||||
            room.forEach((r) => rooms.add(r))
 | 
			
		||||
        } else {
 | 
			
		||||
            rooms.add(room)
 | 
			
		||||
        }
 | 
			
		||||
        return new BroadcastOperator(
 | 
			
		||||
            this.adapter,
 | 
			
		||||
            rooms,
 | 
			
		||||
            this.exceptRooms,
 | 
			
		||||
            this.flags
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Targets a room when emitting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @return a new BroadcastOperator instance
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public in(room: Room | Room[]): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return this.to(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Excludes a room when emitting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @return a new BroadcastOperator instance
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public except(room: Room | Room[]): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        const exceptRooms = new Set(this.exceptRooms)
 | 
			
		||||
        if (Array.isArray(room)) {
 | 
			
		||||
            room.forEach((r) => exceptRooms.add(r))
 | 
			
		||||
        } else {
 | 
			
		||||
            exceptRooms.add(room)
 | 
			
		||||
        }
 | 
			
		||||
        return new BroadcastOperator(
 | 
			
		||||
            this.adapter,
 | 
			
		||||
            this.rooms,
 | 
			
		||||
            exceptRooms,
 | 
			
		||||
            this.flags
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the compress flag.
 | 
			
		||||
     *
 | 
			
		||||
     * @param compress - if `true`, compresses the sending data
 | 
			
		||||
     * @return a new BroadcastOperator instance
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public compress(compress: boolean): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        const flags = Object.assign({}, this.flags, { compress })
 | 
			
		||||
        return new BroadcastOperator(
 | 
			
		||||
            this.adapter,
 | 
			
		||||
            this.rooms,
 | 
			
		||||
            this.exceptRooms,
 | 
			
		||||
            flags
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
 | 
			
		||||
     * receive messages (because of network slowness or other issues, or because they’re connected through long polling
 | 
			
		||||
     * and is in the middle of a request-response cycle).
 | 
			
		||||
     *
 | 
			
		||||
     * @return a new BroadcastOperator instance
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get volatile(): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        const flags = Object.assign({}, this.flags, { volatile: true })
 | 
			
		||||
        return new BroadcastOperator(
 | 
			
		||||
            this.adapter,
 | 
			
		||||
            this.rooms,
 | 
			
		||||
            this.exceptRooms,
 | 
			
		||||
            flags
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a new BroadcastOperator instance
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get local(): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        const flags = Object.assign({}, this.flags, { local: true })
 | 
			
		||||
        return new BroadcastOperator(
 | 
			
		||||
            this.adapter,
 | 
			
		||||
            this.rooms,
 | 
			
		||||
            this.exceptRooms,
 | 
			
		||||
            flags
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Emits to all clients.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Always true
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public emit<Ev extends EventNames<EmitEvents>>(
 | 
			
		||||
        ev: Ev,
 | 
			
		||||
        ...args: EventParams<EmitEvents, Ev>
 | 
			
		||||
    ): boolean {
 | 
			
		||||
        if (RESERVED_EVENTS.has(ev)) {
 | 
			
		||||
            throw new Error(`"${ev}" is a reserved event name`)
 | 
			
		||||
        }
 | 
			
		||||
        // set up packet object
 | 
			
		||||
        const data = [ev, ...args]
 | 
			
		||||
        const packet = {
 | 
			
		||||
            type: PacketType.EVENT,
 | 
			
		||||
            data: data,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ("function" == typeof data[data.length - 1]) {
 | 
			
		||||
            throw new Error("Callbacks are not supported when broadcasting")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.adapter.broadcast(packet, {
 | 
			
		||||
            rooms: this.rooms,
 | 
			
		||||
            except: this.exceptRooms,
 | 
			
		||||
            flags: this.flags,
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a list of clients.
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public allSockets(): Promise<Set<SocketId>> {
 | 
			
		||||
        if (!this.adapter) {
 | 
			
		||||
            throw new Error(
 | 
			
		||||
                "No adapter for this namespace, are you trying to get the list of clients of a dynamic namespace?"
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        return this.adapter.sockets(this.rooms)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the matching socket instances
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public fetchSockets(): Promise<RemoteSocket<EmitEvents>[]> {
 | 
			
		||||
        return this.adapter
 | 
			
		||||
            .fetchSockets({
 | 
			
		||||
                rooms: this.rooms,
 | 
			
		||||
                except: this.exceptRooms,
 | 
			
		||||
            })
 | 
			
		||||
            .then((sockets) => {
 | 
			
		||||
                return sockets.map((socket) => {
 | 
			
		||||
                    if (socket instanceof Socket) {
 | 
			
		||||
                        // FIXME the TypeScript compiler complains about missing private properties
 | 
			
		||||
                        return socket as unknown as RemoteSocket<EmitEvents>
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return new RemoteSocket(this.adapter, socket as SocketDetails)
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes the matching socket instances join the specified rooms
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public socketsJoin(room: Room | Room[]): void {
 | 
			
		||||
        this.adapter.addSockets(
 | 
			
		||||
            {
 | 
			
		||||
                rooms: this.rooms,
 | 
			
		||||
                except: this.exceptRooms,
 | 
			
		||||
            },
 | 
			
		||||
            Array.isArray(room) ? room : [room]
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes the matching socket instances leave the specified rooms
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public socketsLeave(room: Room | Room[]): void {
 | 
			
		||||
        this.adapter.delSockets(
 | 
			
		||||
            {
 | 
			
		||||
                rooms: this.rooms,
 | 
			
		||||
                except: this.exceptRooms,
 | 
			
		||||
            },
 | 
			
		||||
            Array.isArray(room) ? room : [room]
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes the matching socket instances disconnect
 | 
			
		||||
     *
 | 
			
		||||
     * @param close - whether to close the underlying connection
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public disconnectSockets(close: boolean = false): void {
 | 
			
		||||
        this.adapter.disconnectSockets(
 | 
			
		||||
            {
 | 
			
		||||
                rooms: this.rooms,
 | 
			
		||||
                except: this.exceptRooms,
 | 
			
		||||
            },
 | 
			
		||||
            close
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Format of the data when the Socket instance exists on another Socket.IO server
 | 
			
		||||
 */
 | 
			
		||||
interface SocketDetails {
 | 
			
		||||
    id: SocketId
 | 
			
		||||
    handshake: Handshake
 | 
			
		||||
    rooms: Room[]
 | 
			
		||||
    data: any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Expose of subset of the attributes and methods of the Socket class
 | 
			
		||||
 */
 | 
			
		||||
export class RemoteSocket<EmitEvents extends EventsMap>
 | 
			
		||||
    implements TypedEventBroadcaster<EmitEvents>
 | 
			
		||||
{
 | 
			
		||||
    public readonly id: SocketId
 | 
			
		||||
    public readonly handshake: Handshake
 | 
			
		||||
    public readonly rooms: Set<Room>
 | 
			
		||||
    public readonly data: any
 | 
			
		||||
 | 
			
		||||
    private readonly operator: BroadcastOperator<EmitEvents>
 | 
			
		||||
 | 
			
		||||
    constructor(adapter: Adapter, details: SocketDetails) {
 | 
			
		||||
        this.id = details.id
 | 
			
		||||
        this.handshake = details.handshake
 | 
			
		||||
        this.rooms = new Set(details.rooms)
 | 
			
		||||
        this.data = details.data
 | 
			
		||||
        this.operator = new BroadcastOperator(adapter, new Set([this.id]))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public emit<Ev extends EventNames<EmitEvents>>(
 | 
			
		||||
        ev: Ev,
 | 
			
		||||
        ...args: EventParams<EmitEvents, Ev>
 | 
			
		||||
    ): boolean {
 | 
			
		||||
        return this.operator.emit(ev, ...args)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Joins a room.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String|Array} room - room or array of rooms
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public join(room: Room | Room[]): void {
 | 
			
		||||
        return this.operator.socketsJoin(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Leaves a room.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} room
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public leave(room: Room): void {
 | 
			
		||||
        return this.operator.socketsLeave(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Disconnects this client.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Boolean} close - if `true`, closes the underlying connection
 | 
			
		||||
     * @return {Socket} self
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public disconnect(close = false): this {
 | 
			
		||||
        this.operator.disconnectSockets(close)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										331
									
								
								packages/websocket/src/socket.io/client.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										331
									
								
								packages/websocket/src/socket.io/client.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,331 @@
 | 
			
		||||
// import { Decoder, Encoder, Packet, PacketType } from "socket.io-parser"
 | 
			
		||||
import { Decoder, Encoder, Packet, PacketType } from "../socket.io-parser"
 | 
			
		||||
// import debugModule = require("debug")
 | 
			
		||||
import url = require("url")
 | 
			
		||||
// import type { IncomingMessage } from "http"
 | 
			
		||||
import type { Server } from "./index"
 | 
			
		||||
import type { Namespace } from "./namespace"
 | 
			
		||||
import type { EventsMap } from "./typed-events"
 | 
			
		||||
import type { Socket } from "./socket"
 | 
			
		||||
// import type { SocketId } from "socket.io-adapter"
 | 
			
		||||
import type { SocketId } from "../socket.io-adapter"
 | 
			
		||||
import type { Socket as EngineIOSocket } from '../engine.io/socket'
 | 
			
		||||
 | 
			
		||||
// const debug = debugModule("socket.io:client");
 | 
			
		||||
 | 
			
		||||
interface WriteOptions {
 | 
			
		||||
    compress?: boolean
 | 
			
		||||
    volatile?: boolean
 | 
			
		||||
    preEncoded?: boolean
 | 
			
		||||
    wsPreEncoded?: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Client<
 | 
			
		||||
    ListenEvents extends EventsMap,
 | 
			
		||||
    EmitEvents extends EventsMap,
 | 
			
		||||
    ServerSideEvents extends EventsMap
 | 
			
		||||
    > {
 | 
			
		||||
    public readonly conn: EngineIOSocket
 | 
			
		||||
    /**
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    readonly id: string
 | 
			
		||||
    private readonly server: Server<ListenEvents, EmitEvents, ServerSideEvents>
 | 
			
		||||
    private readonly encoder: Encoder
 | 
			
		||||
    private readonly decoder: any
 | 
			
		||||
    private sockets: Map<
 | 
			
		||||
        SocketId,
 | 
			
		||||
        Socket<ListenEvents, EmitEvents, ServerSideEvents>
 | 
			
		||||
    > = new Map()
 | 
			
		||||
    private nsps: Map<
 | 
			
		||||
        string,
 | 
			
		||||
        Socket<ListenEvents, EmitEvents, ServerSideEvents>
 | 
			
		||||
    > = new Map()
 | 
			
		||||
    private connectTimeout: NodeJS.Timeout
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Client constructor.
 | 
			
		||||
     *
 | 
			
		||||
     * @param server instance
 | 
			
		||||
     * @param conn
 | 
			
		||||
     * @package
 | 
			
		||||
     */
 | 
			
		||||
    constructor(
 | 
			
		||||
        server: Server<ListenEvents, EmitEvents, ServerSideEvents>,
 | 
			
		||||
        conn: EngineIOSocket
 | 
			
		||||
    ) {
 | 
			
		||||
        this.server = server
 | 
			
		||||
        this.conn = conn
 | 
			
		||||
        this.encoder = server.encoder
 | 
			
		||||
        this.decoder = new server._parser.Decoder()
 | 
			
		||||
        this.id = conn.id
 | 
			
		||||
        this.setup()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return the reference to the request that originated the Engine.IO connection
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get request(): any/** IncomingMessage */ {
 | 
			
		||||
        return this.conn.request
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets up event listeners.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private setup() {
 | 
			
		||||
        console.debug(`socket.io client setup conn ${this.conn.id}`)
 | 
			
		||||
        this.onclose = this.onclose.bind(this)
 | 
			
		||||
        this.ondata = this.ondata.bind(this)
 | 
			
		||||
        this.onerror = this.onerror.bind(this)
 | 
			
		||||
        this.ondecoded = this.ondecoded.bind(this)
 | 
			
		||||
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        this.decoder.on("decoded", this.ondecoded)
 | 
			
		||||
        this.conn.on("data", this.ondata)
 | 
			
		||||
        this.conn.on("error", this.onerror)
 | 
			
		||||
        this.conn.on("close", this.onclose)
 | 
			
		||||
 | 
			
		||||
        this.connectTimeout = setTimeout(() => {
 | 
			
		||||
            if (this.nsps.size === 0) {
 | 
			
		||||
                console.debug(`no namespace joined yet, close the client ${this.id}`)
 | 
			
		||||
                this.close()
 | 
			
		||||
            } else {
 | 
			
		||||
                console.debug(`the client ${this.id} has already joined a namespace, nothing to do`)
 | 
			
		||||
            }
 | 
			
		||||
        }, this.server._connectTimeout)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Connects a client to a namespace.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} name - the namespace
 | 
			
		||||
     * @param {Object} auth - the auth parameters
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private connect(name: string, auth: object = {}): void {
 | 
			
		||||
        if (this.server._nsps.has(name)) {
 | 
			
		||||
            console.debug(`socket.io client ${this.id} connecting to namespace ${name}`)
 | 
			
		||||
            return this.doConnect(name, auth)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.server._checkNamespace(
 | 
			
		||||
            name,
 | 
			
		||||
            auth,
 | 
			
		||||
            (
 | 
			
		||||
                dynamicNspName:
 | 
			
		||||
                    | Namespace<ListenEvents, EmitEvents, ServerSideEvents>
 | 
			
		||||
                    | false
 | 
			
		||||
            ) => {
 | 
			
		||||
                if (dynamicNspName) {
 | 
			
		||||
                    console.debug(`dynamic namespace ${dynamicNspName} was created`)
 | 
			
		||||
                    this.doConnect(name, auth)
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.debug(`creation of namespace ${name} was denied`)
 | 
			
		||||
                    this._packet({
 | 
			
		||||
                        type: PacketType.CONNECT_ERROR,
 | 
			
		||||
                        nsp: name,
 | 
			
		||||
                        data: {
 | 
			
		||||
                            message: "Invalid namespace",
 | 
			
		||||
                        },
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Connects a client to a namespace.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name - the namespace
 | 
			
		||||
     * @param {Object} auth - the auth parameters
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private doConnect(name: string, auth: object): void {
 | 
			
		||||
        const nsp = this.server.of(name)
 | 
			
		||||
 | 
			
		||||
        // @java-patch multi thread need direct callback socket
 | 
			
		||||
        const socket = nsp._add(this, auth, (socket) => {
 | 
			
		||||
            this.sockets.set(socket.id, socket)
 | 
			
		||||
            this.nsps.set(nsp.name, socket)
 | 
			
		||||
 | 
			
		||||
            if (this.connectTimeout) {
 | 
			
		||||
                clearTimeout(this.connectTimeout)
 | 
			
		||||
                this.connectTimeout = undefined
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Disconnects from all namespaces and closes transport.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _disconnect(): void {
 | 
			
		||||
        for (const socket of this.sockets.values()) {
 | 
			
		||||
            socket.disconnect()
 | 
			
		||||
        }
 | 
			
		||||
        this.sockets.clear()
 | 
			
		||||
        this.close()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes a socket. Called by each `Socket`.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _remove(socket: Socket<ListenEvents, EmitEvents, ServerSideEvents>): void {
 | 
			
		||||
        if (this.sockets.has(socket.id)) {
 | 
			
		||||
            const nsp = this.sockets.get(socket.id)!.nsp.name
 | 
			
		||||
            this.sockets.delete(socket.id)
 | 
			
		||||
            this.nsps.delete(nsp)
 | 
			
		||||
        } else {
 | 
			
		||||
            console.debug("ignoring remove for", socket.id)
 | 
			
		||||
        }
 | 
			
		||||
        // @java-patch  disconnect client when no live socket
 | 
			
		||||
        process.nextTick(() => {
 | 
			
		||||
            if (this.sockets.size == 0) {
 | 
			
		||||
                this.onclose('no live socket')
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Closes the underlying connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private close(): void {
 | 
			
		||||
        console.debug(`client ${this.id} clise - reason: forcing transport close`)
 | 
			
		||||
        if ("open" === this.conn.readyState) {
 | 
			
		||||
            console.debug("forcing transport close")
 | 
			
		||||
            this.conn.close()
 | 
			
		||||
            this.onclose("forced server close")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
   * Writes a packet to the transport.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {Object} packet object
 | 
			
		||||
   * @param {Object} opts
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
    _packet(packet: Packet | any[], opts: WriteOptions = {}): void {
 | 
			
		||||
        if (this.conn.readyState !== "open") {
 | 
			
		||||
            console.debug(`client ${this.id} ignoring packet write ${JSON.stringify(packet)}`)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        const encodedPackets = opts.preEncoded
 | 
			
		||||
            ? (packet as any[]) // previous versions of the adapter incorrectly used socket.packet() instead of writeToEngine()
 | 
			
		||||
            : this.encoder.encode(packet as Packet)
 | 
			
		||||
        for (const encodedPacket of encodedPackets) {
 | 
			
		||||
            this.writeToEngine(encodedPacket, opts)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private writeToEngine(
 | 
			
		||||
        encodedPacket: String | Buffer,
 | 
			
		||||
        opts: WriteOptions
 | 
			
		||||
    ): void {
 | 
			
		||||
        if (opts.volatile && !this.conn.transport.writable) {
 | 
			
		||||
            console.debug(`client ${this.id} volatile packet is discarded since the transport is not currently writable`)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        this.conn.write(encodedPacket, opts)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called with incoming transport data.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private ondata(data): void {
 | 
			
		||||
        // try/catch is needed for protocol violations (GH-1880)
 | 
			
		||||
        try {
 | 
			
		||||
            this.decoder.add(data)
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            this.onerror(e)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when parser fully decodes a packet.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private ondecoded(packet: Packet): void {
 | 
			
		||||
        if (PacketType.CONNECT === packet.type) {
 | 
			
		||||
            if (this.conn.protocol === 3) {
 | 
			
		||||
                const parsed = url.parse(packet.nsp, true)
 | 
			
		||||
                this.connect(parsed.pathname!, parsed.query)
 | 
			
		||||
            } else {
 | 
			
		||||
                this.connect(packet.nsp, packet.data)
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            const socket = this.nsps.get(packet.nsp)
 | 
			
		||||
            if (socket) {
 | 
			
		||||
                process.nextTick(function () {
 | 
			
		||||
                    socket._onpacket(packet)
 | 
			
		||||
                })
 | 
			
		||||
            } else {
 | 
			
		||||
                console.debug(`client ${this.id} no socket for namespace ${packet.nsp}.`)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles an error.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} err object
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private onerror(err): void {
 | 
			
		||||
        for (const socket of this.sockets.values()) {
 | 
			
		||||
            socket._onerror(err)
 | 
			
		||||
        }
 | 
			
		||||
        this.conn.close()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon transport close.
 | 
			
		||||
     *
 | 
			
		||||
     * @param reason
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private onclose(reason: string): void {
 | 
			
		||||
        console.debug(`client ${this.id} close with reason ${reason}`)
 | 
			
		||||
 | 
			
		||||
        // ignore a potential subsequent `close` event
 | 
			
		||||
        this.destroy()
 | 
			
		||||
 | 
			
		||||
        // `nsps` and `sockets` are cleaned up seamlessly
 | 
			
		||||
        for (const socket of this.sockets.values()) {
 | 
			
		||||
            socket._onclose(reason)
 | 
			
		||||
        }
 | 
			
		||||
        this.sockets.clear()
 | 
			
		||||
 | 
			
		||||
        this.decoder.destroy() // clean up decoder
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Cleans up event listeners.
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private destroy(): void {
 | 
			
		||||
        this.conn.removeListener("data", this.ondata)
 | 
			
		||||
        this.conn.removeListener("error", this.onerror)
 | 
			
		||||
        this.conn.removeListener("close", this.onclose)
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        this.decoder.removeListener("decoded", this.ondecoded)
 | 
			
		||||
 | 
			
		||||
        if (this.connectTimeout) {
 | 
			
		||||
            clearTimeout(this.connectTimeout)
 | 
			
		||||
            this.connectTimeout = undefined
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										825
									
								
								packages/websocket/src/socket.io/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										825
									
								
								packages/websocket/src/socket.io/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,825 @@
 | 
			
		||||
// import http = require("http");
 | 
			
		||||
// import { createReadStream } from "fs";
 | 
			
		||||
// import { createDeflate, createGzip, createBrotliCompress } from "zlib";
 | 
			
		||||
// import accepts = require("accepts");
 | 
			
		||||
// import { pipeline } from "stream";
 | 
			
		||||
// import path = require("path");
 | 
			
		||||
import engine = require("../engine.io")
 | 
			
		||||
import { Client } from './client'
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
import { ExtendedError, Namespace, ServerReservedEventsMap } from "./namespace"
 | 
			
		||||
import { ParentNamespace } from './parent-namespace'
 | 
			
		||||
// import { Adapter, Room, SocketId } from "socket.io-adapter"
 | 
			
		||||
import { Adapter, Room, SocketId } from "../socket.io-adapter"
 | 
			
		||||
// import * as parser from "socket.io-parser";
 | 
			
		||||
import * as parser from "../socket.io-parser"
 | 
			
		||||
// import type { Encoder } from "socket.io-parser";
 | 
			
		||||
import type { Encoder } from "../socket.io-parser"
 | 
			
		||||
// import debugModule from "debug";
 | 
			
		||||
import { Socket } from './socket'
 | 
			
		||||
// import type { CookieSerializeOptions } from "cookie";
 | 
			
		||||
// import type { CorsOptions } from "cors";
 | 
			
		||||
import type { BroadcastOperator, RemoteSocket } from "./broadcast-operator"
 | 
			
		||||
import {
 | 
			
		||||
    EventsMap,
 | 
			
		||||
    DefaultEventsMap,
 | 
			
		||||
    EventParams,
 | 
			
		||||
    StrictEventEmitter,
 | 
			
		||||
    EventNames,
 | 
			
		||||
} from "./typed-events"
 | 
			
		||||
 | 
			
		||||
import type { Socket as EngineIOSocket } from '../engine.io/socket'
 | 
			
		||||
 | 
			
		||||
// const clientVersion = require("../package.json").version
 | 
			
		||||
// const dotMapRegex = /\.map/
 | 
			
		||||
 | 
			
		||||
// type Transport = "polling" | "websocket";
 | 
			
		||||
type ParentNspNameMatchFn = (
 | 
			
		||||
    name: string,
 | 
			
		||||
    auth: { [key: string]: any },
 | 
			
		||||
    fn: (err: Error | null, success: boolean) => void
 | 
			
		||||
) => void
 | 
			
		||||
 | 
			
		||||
type AdapterConstructor = typeof Adapter | ((nsp: Namespace) => Adapter)
 | 
			
		||||
 | 
			
		||||
interface EngineOptions {
 | 
			
		||||
    /**
 | 
			
		||||
     * how many ms without a pong packet to consider the connection closed
 | 
			
		||||
     * @default 5000
 | 
			
		||||
     */
 | 
			
		||||
    pingTimeout: number
 | 
			
		||||
    /**
 | 
			
		||||
     * how many ms before sending a new ping packet
 | 
			
		||||
     * @default 25000
 | 
			
		||||
     */
 | 
			
		||||
    pingInterval: number
 | 
			
		||||
    /**
 | 
			
		||||
     * how many ms before an uncompleted transport upgrade is cancelled
 | 
			
		||||
     * @default 10000
 | 
			
		||||
     */
 | 
			
		||||
    upgradeTimeout: number
 | 
			
		||||
    /**
 | 
			
		||||
     * how many bytes or characters a message can be, before closing the session (to avoid DoS).
 | 
			
		||||
     * @default 1e5 (100 KB)
 | 
			
		||||
     */
 | 
			
		||||
    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.
 | 
			
		||||
     */
 | 
			
		||||
    // allowRequest: (
 | 
			
		||||
    //     req: http.IncomingMessage,
 | 
			
		||||
    //     fn: (err: string | null | undefined, success: boolean) => void
 | 
			
		||||
    // ) => void
 | 
			
		||||
    /**
 | 
			
		||||
     * the low-level transports that are enabled
 | 
			
		||||
     * @default ["polling", "websocket"]
 | 
			
		||||
     */
 | 
			
		||||
    // transports: Transport[]
 | 
			
		||||
    /**
 | 
			
		||||
     * whether to allow transport upgrades
 | 
			
		||||
     * @default true
 | 
			
		||||
     */
 | 
			
		||||
    allowUpgrades: boolean
 | 
			
		||||
    /**
 | 
			
		||||
     * parameters of the WebSocket permessage-deflate extension (see ws module api docs). Set to false to disable.
 | 
			
		||||
     * @default false
 | 
			
		||||
     */
 | 
			
		||||
    perMessageDeflate: boolean | object
 | 
			
		||||
    /**
 | 
			
		||||
     * parameters of the http compression for the polling transports (see zlib api docs). Set to false to disable.
 | 
			
		||||
     * @default true
 | 
			
		||||
     */
 | 
			
		||||
    httpCompression: boolean | object
 | 
			
		||||
    /**
 | 
			
		||||
     * what WebSocket server implementation to use. Specified module must
 | 
			
		||||
     * conform to the ws interface (see ws module api docs). Default value is ws.
 | 
			
		||||
     * An alternative c++ addon is also available by installing uws module.
 | 
			
		||||
     */
 | 
			
		||||
    wsEngine: string
 | 
			
		||||
    /**
 | 
			
		||||
     * an optional packet which will be concatenated to the handshake packet emitted by Engine.IO.
 | 
			
		||||
     */
 | 
			
		||||
    initialPacket: any
 | 
			
		||||
    /**
 | 
			
		||||
     * configuration of the cookie that contains the client sid to send as part of handshake response headers. This cookie
 | 
			
		||||
     * might be used for sticky-session. Defaults to not sending any cookie.
 | 
			
		||||
     * @default false
 | 
			
		||||
     */
 | 
			
		||||
    // cookie: CookieSerializeOptions | boolean
 | 
			
		||||
    /**
 | 
			
		||||
     * the options that will be forwarded to the cors module
 | 
			
		||||
     */
 | 
			
		||||
    // cors: CorsOptions
 | 
			
		||||
    /**
 | 
			
		||||
     * whether to enable compatibility with Socket.IO v2 clients
 | 
			
		||||
     * @default false
 | 
			
		||||
     */
 | 
			
		||||
    allowEIO3: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface AttachOptions {
 | 
			
		||||
    /**
 | 
			
		||||
     * name of the path to capture
 | 
			
		||||
     * @default "/engine.io"
 | 
			
		||||
     */
 | 
			
		||||
    path: string
 | 
			
		||||
    /**
 | 
			
		||||
     * destroy unhandled upgrade requests
 | 
			
		||||
     * @default true
 | 
			
		||||
     */
 | 
			
		||||
    destroyUpgrade: boolean
 | 
			
		||||
    /**
 | 
			
		||||
     * milliseconds after which unhandled requests are ended
 | 
			
		||||
     * @default 1000
 | 
			
		||||
     */
 | 
			
		||||
    destroyUpgradeTimeout: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface EngineAttachOptions extends EngineOptions, AttachOptions { }
 | 
			
		||||
 | 
			
		||||
interface ServerOptions extends EngineAttachOptions {
 | 
			
		||||
    /**
 | 
			
		||||
     * name of the path to capture
 | 
			
		||||
     * @default "/socket.io"
 | 
			
		||||
     */
 | 
			
		||||
    path: string
 | 
			
		||||
    /**
 | 
			
		||||
     * whether to serve the client files
 | 
			
		||||
     * @default true
 | 
			
		||||
     */
 | 
			
		||||
    serveClient: boolean
 | 
			
		||||
    /**
 | 
			
		||||
     * the adapter to use
 | 
			
		||||
     * @default the in-memory adapter (https://github.com/socketio/socket.io-adapter)
 | 
			
		||||
     */
 | 
			
		||||
    adapter: any
 | 
			
		||||
    /**
 | 
			
		||||
     * the parser to use
 | 
			
		||||
     * @default the default parser (https://github.com/socketio/socket.io-parser)
 | 
			
		||||
     */
 | 
			
		||||
    parser: any
 | 
			
		||||
    /**
 | 
			
		||||
     * how many ms before a client without namespace is closed
 | 
			
		||||
     * @default 45000
 | 
			
		||||
     */
 | 
			
		||||
    connectTimeout: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Server<
 | 
			
		||||
    ListenEvents extends EventsMap = DefaultEventsMap,
 | 
			
		||||
    EmitEvents extends EventsMap = ListenEvents,
 | 
			
		||||
    ServerSideEvents extends EventsMap = DefaultEventsMap
 | 
			
		||||
    > extends StrictEventEmitter<
 | 
			
		||||
    ServerSideEvents,
 | 
			
		||||
    EmitEvents,
 | 
			
		||||
    ServerReservedEventsMap<ListenEvents, EmitEvents, ServerSideEvents>
 | 
			
		||||
    > {
 | 
			
		||||
    public readonly sockets: Namespace<
 | 
			
		||||
        ListenEvents,
 | 
			
		||||
        EmitEvents,
 | 
			
		||||
        ServerSideEvents
 | 
			
		||||
    >
 | 
			
		||||
    /**
 | 
			
		||||
     * A reference to the underlying Engine.IO server.
 | 
			
		||||
     *
 | 
			
		||||
     * Example:
 | 
			
		||||
     *
 | 
			
		||||
     * <code>
 | 
			
		||||
     *   const clientsCount = io.engine.clientsCount;
 | 
			
		||||
     * </code>
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public engine: any
 | 
			
		||||
    /** @private */
 | 
			
		||||
    readonly _parser: typeof parser
 | 
			
		||||
    /** @private */
 | 
			
		||||
    readonly encoder: Encoder
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _nsps: Map<string, Namespace<ListenEvents, EmitEvents, ServerSideEvents>> =
 | 
			
		||||
        new Map();
 | 
			
		||||
    private parentNsps: Map<
 | 
			
		||||
        ParentNspNameMatchFn,
 | 
			
		||||
        ParentNamespace<ListenEvents, EmitEvents, ServerSideEvents>
 | 
			
		||||
    > = new Map();
 | 
			
		||||
    private _adapter?: AdapterConstructor
 | 
			
		||||
    private _serveClient: boolean
 | 
			
		||||
    private opts: Partial<EngineOptions>
 | 
			
		||||
    private eio
 | 
			
		||||
    private _path: string
 | 
			
		||||
    private clientPathRegex: RegExp
 | 
			
		||||
    /**
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _connectTimeout: number
 | 
			
		||||
 | 
			
		||||
    // private httpServer: http.Server
 | 
			
		||||
 | 
			
		||||
    constructor(srv: any, opts: Partial<ServerOptions> = {}) {
 | 
			
		||||
        super()
 | 
			
		||||
        if (!srv) { throw new Error('srv can\'t be undefiend!') }
 | 
			
		||||
        // if (
 | 
			
		||||
        //     "object" === typeof srv &&
 | 
			
		||||
        //     srv instanceof Object &&
 | 
			
		||||
        //     !(srv as Partial<http.Server>).listen
 | 
			
		||||
        // ) {
 | 
			
		||||
        //     opts = srv as Partial<ServerOptions>
 | 
			
		||||
        //     srv = undefined
 | 
			
		||||
        // }
 | 
			
		||||
        this.path(opts.path || "/socket.io")
 | 
			
		||||
        this.connectTimeout(opts.connectTimeout || 45000)
 | 
			
		||||
        this.serveClient(false !== opts.serveClient)
 | 
			
		||||
        this._parser = opts.parser || parser
 | 
			
		||||
        this.encoder = new this._parser.Encoder()
 | 
			
		||||
        this.adapter(opts.adapter || Adapter)
 | 
			
		||||
        this.sockets = this.of('/')
 | 
			
		||||
        // if (srv) this.attach(srv as http.Server);
 | 
			
		||||
        this.attach(srv, this.opts)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets/gets whether client code is being served.
 | 
			
		||||
     *
 | 
			
		||||
     * @param v - whether to serve client code
 | 
			
		||||
     * @return self when setting or value when getting
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public serveClient(v: boolean): this
 | 
			
		||||
    public serveClient(): boolean
 | 
			
		||||
    public serveClient(v?: boolean): this | boolean
 | 
			
		||||
    public serveClient(v?: boolean): this | boolean {
 | 
			
		||||
        if (!arguments.length) return this._serveClient
 | 
			
		||||
        this._serveClient = v!
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Executes the middleware for an incoming namespace not already created on the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name - name of incoming namespace
 | 
			
		||||
     * @param auth - the auth parameters
 | 
			
		||||
     * @param fn - callback
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _checkNamespace(
 | 
			
		||||
        name: string,
 | 
			
		||||
        auth: { [key: string]: any },
 | 
			
		||||
        fn: (
 | 
			
		||||
            nsp: Namespace<ListenEvents, EmitEvents, ServerSideEvents> | false
 | 
			
		||||
        ) => void
 | 
			
		||||
    ): void {
 | 
			
		||||
        if (this.parentNsps.size === 0) return fn(false)
 | 
			
		||||
 | 
			
		||||
        const keysIterator = this.parentNsps.keys()
 | 
			
		||||
 | 
			
		||||
        const run = () => {
 | 
			
		||||
            const nextFn = keysIterator.next()
 | 
			
		||||
            if (nextFn.done) {
 | 
			
		||||
                return fn(false)
 | 
			
		||||
            }
 | 
			
		||||
            nextFn.value(name, auth, (err, allow) => {
 | 
			
		||||
                if (err || !allow) {
 | 
			
		||||
                    run()
 | 
			
		||||
                } else {
 | 
			
		||||
                    const namespace = this.parentNsps
 | 
			
		||||
                        .get(nextFn.value)!
 | 
			
		||||
                        .createChild(name)
 | 
			
		||||
                    // @ts-ignore
 | 
			
		||||
                    this.sockets.emitReserved("new_namespace", namespace)
 | 
			
		||||
                    fn(namespace)
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        run()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the client serving path.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} v pathname
 | 
			
		||||
     * @return {Server|String} self when setting or value when getting
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public path(v: string): this
 | 
			
		||||
    public path(): string
 | 
			
		||||
    public path(v?: string): this | string
 | 
			
		||||
    public path(v?: string): this | string {
 | 
			
		||||
        if (!arguments.length) return this._path
 | 
			
		||||
 | 
			
		||||
        this._path = v!.replace(/\/$/, "")
 | 
			
		||||
 | 
			
		||||
        const escapedPath = this._path.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")
 | 
			
		||||
        this.clientPathRegex = new RegExp(
 | 
			
		||||
            "^" +
 | 
			
		||||
            escapedPath +
 | 
			
		||||
            "/socket\\.io(\\.min|\\.msgpack\\.min)?\\.js(\\.map)?$"
 | 
			
		||||
        )
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the delay after which a client without namespace is closed
 | 
			
		||||
     * @param v
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public connectTimeout(v: number): this
 | 
			
		||||
    public connectTimeout(): number
 | 
			
		||||
    public connectTimeout(v?: number): this | number
 | 
			
		||||
    public connectTimeout(v?: number): this | number {
 | 
			
		||||
        if (v === undefined) return this._connectTimeout
 | 
			
		||||
        this._connectTimeout = v
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the adapter for rooms.
 | 
			
		||||
     *
 | 
			
		||||
     * @param v pathname
 | 
			
		||||
     * @return self when setting or value when getting
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public adapter(): AdapterConstructor | undefined
 | 
			
		||||
    public adapter(v: AdapterConstructor): this
 | 
			
		||||
    public adapter(
 | 
			
		||||
        v?: AdapterConstructor
 | 
			
		||||
    ): AdapterConstructor | undefined | this {
 | 
			
		||||
        if (!arguments.length) return this._adapter
 | 
			
		||||
        this._adapter = v
 | 
			
		||||
        for (const nsp of this._nsps.values()) {
 | 
			
		||||
            nsp._initAdapter()
 | 
			
		||||
        }
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attaches socket.io to a server or port.
 | 
			
		||||
     *
 | 
			
		||||
     * @param srv - server or port
 | 
			
		||||
     * @param opts - options passed to engine.io
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public listen(
 | 
			
		||||
        srv: any,//http.Server | number,
 | 
			
		||||
        opts: Partial<ServerOptions> = {}
 | 
			
		||||
    ): this {
 | 
			
		||||
        throw Error('Unsupport listen at MiaoScript Engine!')
 | 
			
		||||
        //return this.attach(srv, opts)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attaches socket.io to a server or port.
 | 
			
		||||
     *
 | 
			
		||||
     * @param srv - server or port
 | 
			
		||||
     * @param opts - options passed to engine.io
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public attach(
 | 
			
		||||
        srv: any,//http.Server | number,
 | 
			
		||||
        opts: Partial<ServerOptions> = {}
 | 
			
		||||
    ): this {
 | 
			
		||||
        // if ("function" == typeof srv) {
 | 
			
		||||
        //     const msg =
 | 
			
		||||
        //         "You are trying to attach socket.io to an express " +
 | 
			
		||||
        //         "request handler function. Please pass a http.Server instance."
 | 
			
		||||
        //     throw new Error(msg)
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        // // handle a port as a string
 | 
			
		||||
        // if (Number(srv) == srv) {
 | 
			
		||||
        //     srv = Number(srv)
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        // if ("number" == typeof srv) {
 | 
			
		||||
        //     debug("creating http server and binding to %d", srv)
 | 
			
		||||
        //     const port = srv
 | 
			
		||||
        //     srv = http.createServer((req, res) => {
 | 
			
		||||
        //         res.writeHead(404)
 | 
			
		||||
        //         res.end()
 | 
			
		||||
        //     })
 | 
			
		||||
        //     srv.listen(port)
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        // merge the options passed to the Socket.IO server
 | 
			
		||||
        Object.assign(opts, this.opts)
 | 
			
		||||
        // set engine.io path to `/socket.io`
 | 
			
		||||
        opts.path = opts.path || this._path
 | 
			
		||||
 | 
			
		||||
        this.initEngine(srv, opts)
 | 
			
		||||
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize engine
 | 
			
		||||
     *
 | 
			
		||||
     * @param srv - the server to attach to
 | 
			
		||||
     * @param opts - options passed to engine.io
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private initEngine(srv: any, opts: Partial<EngineAttachOptions>) {
 | 
			
		||||
        // // initialize engine
 | 
			
		||||
        console.debug("creating engine.io instance with opts", JSON.stringify(opts))
 | 
			
		||||
        this.eio = engine.attach(srv, opts)
 | 
			
		||||
 | 
			
		||||
        // // attach static file serving
 | 
			
		||||
        // if (this._serveClient) this.attachServe(srv)
 | 
			
		||||
 | 
			
		||||
        // // Export http server
 | 
			
		||||
        // this.httpServer = srv
 | 
			
		||||
 | 
			
		||||
        // bind to engine events
 | 
			
		||||
        this.bind(this.eio)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * Attaches the static file serving.
 | 
			
		||||
    //  *
 | 
			
		||||
    //  * @param srv http server
 | 
			
		||||
    //  * @private
 | 
			
		||||
    //  */
 | 
			
		||||
    // private attachServe(srv: http.Server): void {
 | 
			
		||||
    //     debug("attaching client serving req handler")
 | 
			
		||||
 | 
			
		||||
    //     const evs = srv.listeners("request").slice(0)
 | 
			
		||||
    //     srv.removeAllListeners("request")
 | 
			
		||||
    //     srv.on("request", (req, res) => {
 | 
			
		||||
    //         if (this.clientPathRegex.test(req.url)) {
 | 
			
		||||
    //             this.serve(req, res)
 | 
			
		||||
    //         } else {
 | 
			
		||||
    //             for (let i = 0; i < evs.length; i++) {
 | 
			
		||||
    //                 evs[i].call(srv, req, res)
 | 
			
		||||
    //             }
 | 
			
		||||
    //         }
 | 
			
		||||
    //     })
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * Handles a request serving of client source and map
 | 
			
		||||
    //  *
 | 
			
		||||
    //  * @param req
 | 
			
		||||
    //  * @param res
 | 
			
		||||
    //  * @private
 | 
			
		||||
    //  */
 | 
			
		||||
    // private serve(req: http.IncomingMessage, res: http.ServerResponse): void {
 | 
			
		||||
    //     const filename = req.url!.replace(this._path, "")
 | 
			
		||||
    //     const isMap = dotMapRegex.test(filename)
 | 
			
		||||
    //     const type = isMap ? "map" : "source"
 | 
			
		||||
 | 
			
		||||
    //     // Per the standard, ETags must be quoted:
 | 
			
		||||
    //     // https://tools.ietf.org/html/rfc7232#section-2.3
 | 
			
		||||
    //     const expectedEtag = '"' + clientVersion + '"'
 | 
			
		||||
    //     const weakEtag = "W/" + expectedEtag
 | 
			
		||||
 | 
			
		||||
    //     const etag = req.headers["if-none-match"]
 | 
			
		||||
    //     if (etag) {
 | 
			
		||||
    //         if (expectedEtag === etag || weakEtag === etag) {
 | 
			
		||||
    //             debug("serve client %s 304", type)
 | 
			
		||||
    //             res.writeHead(304)
 | 
			
		||||
    //             res.end()
 | 
			
		||||
    //             return
 | 
			
		||||
    //         }
 | 
			
		||||
    //     }
 | 
			
		||||
 | 
			
		||||
    //     debug("serve client %s", type)
 | 
			
		||||
 | 
			
		||||
    //     res.setHeader("Cache-Control", "public, max-age=0")
 | 
			
		||||
    //     res.setHeader(
 | 
			
		||||
    //         "Content-Type",
 | 
			
		||||
    //         "application/" + (isMap ? "json" : "javascript")
 | 
			
		||||
    //     )
 | 
			
		||||
    //     res.setHeader("ETag", expectedEtag)
 | 
			
		||||
 | 
			
		||||
    //     Server.sendFile(filename, req, res)
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * @param filename
 | 
			
		||||
    //  * @param req
 | 
			
		||||
    //  * @param res
 | 
			
		||||
    //  * @private
 | 
			
		||||
    //  */
 | 
			
		||||
    // private static sendFile(
 | 
			
		||||
    //     filename: string,
 | 
			
		||||
    //     req: http.IncomingMessage,
 | 
			
		||||
    //     res: http.ServerResponse
 | 
			
		||||
    // ): void {
 | 
			
		||||
    //     const readStream = createReadStream(
 | 
			
		||||
    //         path.join(__dirname, "../client-dist/", filename)
 | 
			
		||||
    //     )
 | 
			
		||||
    //     const encoding = accepts(req).encodings(["br", "gzip", "deflate"])
 | 
			
		||||
 | 
			
		||||
    //     const onError = (err: NodeJS.ErrnoException | null) => {
 | 
			
		||||
    //         if (err) {
 | 
			
		||||
    //             res.end()
 | 
			
		||||
    //         }
 | 
			
		||||
    //     }
 | 
			
		||||
 | 
			
		||||
    //     switch (encoding) {
 | 
			
		||||
    //         case "br":
 | 
			
		||||
    //             res.writeHead(200, { "content-encoding": "br" })
 | 
			
		||||
    //             readStream.pipe(createBrotliCompress()).pipe(res)
 | 
			
		||||
    //             pipeline(readStream, createBrotliCompress(), res, onError)
 | 
			
		||||
    //             break
 | 
			
		||||
    //         case "gzip":
 | 
			
		||||
    //             res.writeHead(200, { "content-encoding": "gzip" })
 | 
			
		||||
    //             pipeline(readStream, createGzip(), res, onError)
 | 
			
		||||
    //             break
 | 
			
		||||
    //         case "deflate":
 | 
			
		||||
    //             res.writeHead(200, { "content-encoding": "deflate" })
 | 
			
		||||
    //             pipeline(readStream, createDeflate(), res, onError)
 | 
			
		||||
    //             break
 | 
			
		||||
    //         default:
 | 
			
		||||
    //             res.writeHead(200)
 | 
			
		||||
    //             pipeline(readStream, res, onError)
 | 
			
		||||
    //     }
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Binds socket.io to an engine.io instance.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {engine.Server} engine engine.io (or compatible) server
 | 
			
		||||
     * @return {Server} self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public bind(engine): Server {
 | 
			
		||||
        console.debug('engine.io', engine.constructor.name, 'bind to socket.io')
 | 
			
		||||
        this.engine = engine
 | 
			
		||||
        this.engine.on("connection", this.onconnection.bind(this))
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called with each incoming transport connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {engine.Socket} conn
 | 
			
		||||
     * @return {Server} self
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private onconnection(conn: EngineIOSocket): Server {
 | 
			
		||||
        console.debug(`socket.io index incoming connection with id ${conn.id}`)
 | 
			
		||||
        let client = new Client(this, conn)
 | 
			
		||||
        if (conn.protocol === 3) {
 | 
			
		||||
            // @ts-ignore
 | 
			
		||||
            client.connect("/")
 | 
			
		||||
        }
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Looks up a namespace.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String|RegExp|Function} name nsp name
 | 
			
		||||
     * @param fn optional, nsp `connection` ev handler
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public of(
 | 
			
		||||
        name: string | RegExp | ParentNspNameMatchFn,
 | 
			
		||||
        fn?: (socket: Socket<ListenEvents, EmitEvents, ServerSideEvents>) => void
 | 
			
		||||
    ): Namespace<ListenEvents, EmitEvents, ServerSideEvents> {
 | 
			
		||||
        if (typeof name === "function" || name instanceof RegExp) {
 | 
			
		||||
            const parentNsp = new ParentNamespace(this)
 | 
			
		||||
            console.debug(`initializing parent namespace ${parentNsp.name}`)
 | 
			
		||||
            if (typeof name === "function") {
 | 
			
		||||
                this.parentNsps.set(name, parentNsp)
 | 
			
		||||
            } else {
 | 
			
		||||
                this.parentNsps.set(
 | 
			
		||||
                    (nsp, conn, next) => next(null, (name as RegExp).test(nsp)),
 | 
			
		||||
                    parentNsp
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            if (fn) {
 | 
			
		||||
                // @ts-ignore
 | 
			
		||||
                parentNsp.on("connect", fn)
 | 
			
		||||
            }
 | 
			
		||||
            return parentNsp
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (String(name)[0] !== "/") name = "/" + name
 | 
			
		||||
 | 
			
		||||
        let nsp = this._nsps.get(name)
 | 
			
		||||
        if (!nsp) {
 | 
			
		||||
            console.debug("initializing namespace", name)
 | 
			
		||||
            nsp = new Namespace(this, name)
 | 
			
		||||
            this._nsps.set(name, nsp)
 | 
			
		||||
            if (name !== "/") {
 | 
			
		||||
                // @ts-ignore
 | 
			
		||||
                this.sockets.emitReserved("new_namespace", nsp)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (fn) nsp.on("connect", fn)
 | 
			
		||||
        return nsp
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Closes server connection
 | 
			
		||||
     *
 | 
			
		||||
     * @param [fn] optional, called as `fn([err])` on error OR all conns closed
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public close(fn?: (err?: Error) => void): void {
 | 
			
		||||
        for (const socket of this.sockets.sockets.values()) {
 | 
			
		||||
            socket._onclose("server shutting down")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.engine.close()
 | 
			
		||||
 | 
			
		||||
        // if (this.httpServer) {
 | 
			
		||||
        //     this.httpServer.close(fn)
 | 
			
		||||
        // } else {
 | 
			
		||||
        fn && fn()
 | 
			
		||||
        // }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets up namespace middleware.
 | 
			
		||||
     *
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public use(
 | 
			
		||||
        fn: (
 | 
			
		||||
            socket: Socket<ListenEvents, EmitEvents, ServerSideEvents>,
 | 
			
		||||
            next: (err?: ExtendedError) => void
 | 
			
		||||
        ) => void
 | 
			
		||||
    ): this {
 | 
			
		||||
        this.sockets.use(fn)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Targets a room when emitting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public to(room: Room | Room[]): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return this.sockets.to(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Targets a room when emitting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public in(room: Room | Room[]): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return this.sockets.in(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Excludes a room when emitting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public except(name: Room | Room[]): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return this.sockets.except(name)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a `message` event to all clients.
 | 
			
		||||
     *
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public send(...args: EventParams<EmitEvents, "message">): this {
 | 
			
		||||
        this.sockets.emit("message", ...args)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a `message` event to all clients.
 | 
			
		||||
     *
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public write(...args: EventParams<EmitEvents, "message">): this {
 | 
			
		||||
        this.sockets.emit("message", ...args)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Emit a packet to other Socket.IO servers
 | 
			
		||||
     *
 | 
			
		||||
     * @param ev - the event name
 | 
			
		||||
     * @param args - an array of arguments, which may include an acknowledgement callback at the end
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public serverSideEmit<Ev extends EventNames<ServerSideEvents>>(
 | 
			
		||||
        ev: Ev,
 | 
			
		||||
        ...args: EventParams<ServerSideEvents, Ev>
 | 
			
		||||
    ): boolean {
 | 
			
		||||
        return this.sockets.serverSideEmit(ev, ...args)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a list of socket ids.
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public allSockets(): Promise<Set<SocketId>> {
 | 
			
		||||
        return this.sockets.allSockets()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the compress flag.
 | 
			
		||||
     *
 | 
			
		||||
     * @param compress - if `true`, compresses the sending data
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public compress(compress: boolean): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return this.sockets.compress(compress)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
 | 
			
		||||
     * receive messages (because of network slowness or other issues, or because they’re connected through long polling
 | 
			
		||||
     * and is in the middle of a request-response cycle).
 | 
			
		||||
     *
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get volatile(): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return this.sockets.volatile
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
 | 
			
		||||
     *
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get local(): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return this.sockets.local
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the matching socket instances
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public fetchSockets(): Promise<RemoteSocket<EmitEvents>[]> {
 | 
			
		||||
        return this.sockets.fetchSockets()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes the matching socket instances join the specified rooms
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public socketsJoin(room: Room | Room[]): void {
 | 
			
		||||
        return this.sockets.socketsJoin(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes the matching socket instances leave the specified rooms
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public socketsLeave(room: Room | Room[]): void {
 | 
			
		||||
        return this.sockets.socketsLeave(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes the matching socket instances disconnect
 | 
			
		||||
     *
 | 
			
		||||
     * @param close - whether to close the underlying connection
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public disconnectSockets(close: boolean = false): void {
 | 
			
		||||
        return this.sockets.disconnectSockets(close)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Expose main namespace (/).
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const emitterMethods = Object.keys(EventEmitter.prototype).filter(function (
 | 
			
		||||
    key
 | 
			
		||||
) {
 | 
			
		||||
    return typeof EventEmitter.prototype[key] === "function"
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
emitterMethods.forEach(function (fn) {
 | 
			
		||||
    Server.prototype[fn] = function () {
 | 
			
		||||
        return this.sockets[fn].apply(this.sockets, arguments)
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export { Socket, ServerOptions, Namespace, BroadcastOperator, RemoteSocket }
 | 
			
		||||
							
								
								
									
										407
									
								
								packages/websocket/src/socket.io/namespace.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										407
									
								
								packages/websocket/src/socket.io/namespace.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,407 @@
 | 
			
		||||
 | 
			
		||||
import { Socket } from "./socket"
 | 
			
		||||
import type { Server } from "./index"
 | 
			
		||||
import {
 | 
			
		||||
    EventParams,
 | 
			
		||||
    EventNames,
 | 
			
		||||
    EventsMap,
 | 
			
		||||
    StrictEventEmitter,
 | 
			
		||||
    DefaultEventsMap,
 | 
			
		||||
} from "./typed-events"
 | 
			
		||||
import type { Client } from "./client"
 | 
			
		||||
// import debugModule from "debug"
 | 
			
		||||
// import type { Adapter, Room, SocketId } from "socket.io-adapter"
 | 
			
		||||
import type { Adapter, Room, SocketId } from "../socket.io-adapter"
 | 
			
		||||
import { BroadcastOperator, RemoteSocket } from "./broadcast-operator"
 | 
			
		||||
 | 
			
		||||
// const debug = debugModule("socket.io:namespace");
 | 
			
		||||
 | 
			
		||||
export interface ExtendedError extends Error {
 | 
			
		||||
    data?: any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface NamespaceReservedEventsMap<
 | 
			
		||||
    ListenEvents extends EventsMap,
 | 
			
		||||
    EmitEvents extends EventsMap,
 | 
			
		||||
    ServerSideEvents extends EventsMap
 | 
			
		||||
    > {
 | 
			
		||||
    connect: (socket: Socket<ListenEvents, EmitEvents, ServerSideEvents>) => void
 | 
			
		||||
    connection: (
 | 
			
		||||
        socket: Socket<ListenEvents, EmitEvents, ServerSideEvents>
 | 
			
		||||
    ) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ServerReservedEventsMap<
 | 
			
		||||
    ListenEvents,
 | 
			
		||||
    EmitEvents,
 | 
			
		||||
    ServerSideEvents
 | 
			
		||||
    > extends NamespaceReservedEventsMap<
 | 
			
		||||
    ListenEvents,
 | 
			
		||||
    EmitEvents,
 | 
			
		||||
    ServerSideEvents
 | 
			
		||||
    > {
 | 
			
		||||
    new_namespace: (
 | 
			
		||||
        namespace: Namespace<ListenEvents, EmitEvents, ServerSideEvents>
 | 
			
		||||
    ) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const RESERVED_EVENTS: ReadonlySet<string | Symbol> = new Set<
 | 
			
		||||
    keyof ServerReservedEventsMap<never, never, never>
 | 
			
		||||
>(<const>["connect", "connection", "new_namespace"])
 | 
			
		||||
 | 
			
		||||
export class Namespace<
 | 
			
		||||
    ListenEvents extends EventsMap = DefaultEventsMap,
 | 
			
		||||
    EmitEvents extends EventsMap = ListenEvents,
 | 
			
		||||
    ServerSideEvents extends EventsMap = DefaultEventsMap
 | 
			
		||||
    > extends StrictEventEmitter<
 | 
			
		||||
    ServerSideEvents,
 | 
			
		||||
    EmitEvents,
 | 
			
		||||
    NamespaceReservedEventsMap<ListenEvents, EmitEvents, ServerSideEvents>
 | 
			
		||||
    > {
 | 
			
		||||
    public readonly name: string
 | 
			
		||||
    public readonly sockets: Map<
 | 
			
		||||
        SocketId,
 | 
			
		||||
        Socket<ListenEvents, EmitEvents, ServerSideEvents>
 | 
			
		||||
    > = new Map();
 | 
			
		||||
 | 
			
		||||
    public adapter: Adapter
 | 
			
		||||
 | 
			
		||||
    /** @private */
 | 
			
		||||
    readonly server: Server<ListenEvents, EmitEvents, ServerSideEvents>
 | 
			
		||||
 | 
			
		||||
    /** @private */
 | 
			
		||||
    _fns: Array<
 | 
			
		||||
        (
 | 
			
		||||
            socket: Socket<ListenEvents, EmitEvents, ServerSideEvents>,
 | 
			
		||||
            next: (err?: ExtendedError) => void
 | 
			
		||||
        ) => void
 | 
			
		||||
    > = [];
 | 
			
		||||
 | 
			
		||||
    /** @private */
 | 
			
		||||
    _ids: number = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Namespace constructor.
 | 
			
		||||
     *
 | 
			
		||||
     * @param server instance
 | 
			
		||||
     * @param name
 | 
			
		||||
     */
 | 
			
		||||
    constructor(
 | 
			
		||||
        server: Server<ListenEvents, EmitEvents, ServerSideEvents>,
 | 
			
		||||
        name: string
 | 
			
		||||
    ) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.server = server
 | 
			
		||||
        this.name = name
 | 
			
		||||
        this._initAdapter()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the `Adapter` for this nsp.
 | 
			
		||||
     * Run upon changing adapter by `Server#adapter`
 | 
			
		||||
     * in addition to the constructor.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _initAdapter() {
 | 
			
		||||
        // @ts-ignore
 | 
			
		||||
        this.adapter = new (this.server.adapter()!)(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets up namespace middleware.
 | 
			
		||||
     *
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public use(
 | 
			
		||||
        fn: (
 | 
			
		||||
            socket: Socket<ListenEvents, EmitEvents, ServerSideEvents>,
 | 
			
		||||
            next: (err?: ExtendedError) => void
 | 
			
		||||
        ) => void
 | 
			
		||||
    ): this {
 | 
			
		||||
        this._fns.push(fn)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Executes the middleware for an incoming client.
 | 
			
		||||
     *
 | 
			
		||||
     * @param socket - the socket that will get added
 | 
			
		||||
     * @param fn - last fn call in the middleware
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private run(
 | 
			
		||||
        socket: Socket<ListenEvents, EmitEvents, ServerSideEvents>,
 | 
			
		||||
        fn: (err: ExtendedError | null) => void
 | 
			
		||||
    ) {
 | 
			
		||||
        const fns = this._fns.slice(0)
 | 
			
		||||
        if (!fns.length) return fn(null)
 | 
			
		||||
 | 
			
		||||
        function run(i: number) {
 | 
			
		||||
            fns[i](socket, function (err) {
 | 
			
		||||
                // upon error, short-circuit
 | 
			
		||||
                if (err) return fn(err)
 | 
			
		||||
 | 
			
		||||
                // if no middleware left, summon callback
 | 
			
		||||
                if (!fns[i + 1]) return fn(null)
 | 
			
		||||
 | 
			
		||||
                // go on to next
 | 
			
		||||
                run(i + 1)
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        run(0)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Targets a room when emitting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public to(room: Room | Room[]): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return new BroadcastOperator(this.adapter).to(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Targets a room when emitting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public in(room: Room | Room[]): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return new BroadcastOperator(this.adapter).in(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Excludes a room when emitting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public except(room: Room | Room[]): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return new BroadcastOperator(this.adapter).except(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a new client.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Socket}
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _add(
 | 
			
		||||
        client: Client<ListenEvents, EmitEvents, ServerSideEvents>,
 | 
			
		||||
        query,
 | 
			
		||||
        fn?: (socket: Socket) => void
 | 
			
		||||
    ): Socket<ListenEvents, EmitEvents, ServerSideEvents> {
 | 
			
		||||
        const socket = new Socket(this, client, query || {})
 | 
			
		||||
        console.debug(`socket.io namespace client ${client.id} adding socket ${socket.id} to nsp ${this.name}`)
 | 
			
		||||
        this.run(socket, err => {
 | 
			
		||||
            process.nextTick(() => {
 | 
			
		||||
                if ("open" == client.conn.readyState) {
 | 
			
		||||
                    if (err) {
 | 
			
		||||
                        if (client.conn.protocol === 3) {
 | 
			
		||||
                            return socket._error(err.data || err.message)
 | 
			
		||||
                        } else {
 | 
			
		||||
                            return socket._error({
 | 
			
		||||
                                message: err.message,
 | 
			
		||||
                                data: err.data,
 | 
			
		||||
                            })
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // track socket
 | 
			
		||||
                    this.sockets.set(socket.id, socket)
 | 
			
		||||
                    console.debug(`socket.io namespace ${this.name} track client ${client.id} socket ${socket.id}`)
 | 
			
		||||
 | 
			
		||||
                    // it's paramount that the internal `onconnect` logic
 | 
			
		||||
                    // fires before user-set events to prevent state order
 | 
			
		||||
                    // violations (such as a disconnection before the connection
 | 
			
		||||
                    // logic is complete)
 | 
			
		||||
                    socket._onconnect()
 | 
			
		||||
                    // @java-patch multi thread need direct callback socket
 | 
			
		||||
                    if (fn) fn(socket)
 | 
			
		||||
 | 
			
		||||
                    // fire user-set events
 | 
			
		||||
                    this.emitReserved("connect", socket)
 | 
			
		||||
                    this.emitReserved("connection", socket)
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.debug(`next called after client ${client.id} was closed - ignoring socket`)
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        })
 | 
			
		||||
        return socket
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes a client. Called by each `Socket`.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _remove(socket: Socket<ListenEvents, EmitEvents, ServerSideEvents>): void {
 | 
			
		||||
        if (this.sockets.has(socket.id)) {
 | 
			
		||||
            console.debug(`namespace ${this.name} remove socket ${socket.id}`)
 | 
			
		||||
            this.sockets.delete(socket.id)
 | 
			
		||||
        } else {
 | 
			
		||||
            console.debug(`namespace ${this.name} ignoring remove for ${socket.id}`)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Emits to all clients.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Always true
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public emit<Ev extends EventNames<EmitEvents>>(
 | 
			
		||||
        ev: Ev,
 | 
			
		||||
        ...args: EventParams<EmitEvents, Ev>
 | 
			
		||||
    ): boolean {
 | 
			
		||||
        return new BroadcastOperator<EmitEvents>(this.adapter).emit(ev, ...args)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a `message` event to all clients.
 | 
			
		||||
     *
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public send(...args: EventParams<EmitEvents, "message">): this {
 | 
			
		||||
        this.emit("message", ...args)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a `message` event to all clients.
 | 
			
		||||
     *
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public write(...args: EventParams<EmitEvents, "message">): this {
 | 
			
		||||
        this.emit("message", ...args)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Emit a packet to other Socket.IO servers
 | 
			
		||||
     *
 | 
			
		||||
     * @param ev - the event name
 | 
			
		||||
     * @param args - an array of arguments, which may include an acknowledgement callback at the end
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public serverSideEmit<Ev extends EventNames<ServerSideEvents>>(
 | 
			
		||||
        ev: Ev,
 | 
			
		||||
        ...args: EventParams<ServerSideEvents, Ev>
 | 
			
		||||
    ): boolean {
 | 
			
		||||
        if (RESERVED_EVENTS.has(ev)) {
 | 
			
		||||
            throw new Error(`"${ev}" is a reserved event name`)
 | 
			
		||||
        }
 | 
			
		||||
        args.unshift(ev)
 | 
			
		||||
        this.adapter.serverSideEmit(args)
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when a packet is received from another Socket.IO server
 | 
			
		||||
     *
 | 
			
		||||
     * @param args - an array of arguments, which may include an acknowledgement callback at the end
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _onServerSideEmit(args: [string, ...any[]]) {
 | 
			
		||||
        super.emitUntyped.apply(this, args)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a list of clients.
 | 
			
		||||
     *
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public allSockets(): Promise<Set<SocketId>> {
 | 
			
		||||
        return new BroadcastOperator(this.adapter).allSockets()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * Sets the compress flag.
 | 
			
		||||
    *
 | 
			
		||||
    * @param compress - if `true`, compresses the sending data
 | 
			
		||||
    * @return self
 | 
			
		||||
    * @public
 | 
			
		||||
    */
 | 
			
		||||
    public compress(compress: boolean): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return new BroadcastOperator(this.adapter).compress(compress)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
 | 
			
		||||
     * receive messages (because of network slowness or other issues, or because they’re connected through long polling
 | 
			
		||||
     * and is in the middle of a request-response cycle).
 | 
			
		||||
     *
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get volatile(): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return new BroadcastOperator(this.adapter).volatile
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
 | 
			
		||||
     *
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get local(): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return new BroadcastOperator(this.adapter).local
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the matching socket instances
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public fetchSockets(): Promise<RemoteSocket<EmitEvents>[]> {
 | 
			
		||||
        return new BroadcastOperator(this.adapter).fetchSockets()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes the matching socket instances join the specified rooms
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public socketsJoin(room: Room | Room[]): void {
 | 
			
		||||
        return new BroadcastOperator(this.adapter).socketsJoin(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes the matching socket instances leave the specified rooms
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public socketsLeave(room: Room | Room[]): void {
 | 
			
		||||
        return new BroadcastOperator(this.adapter).socketsLeave(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes the matching socket instances disconnect
 | 
			
		||||
     *
 | 
			
		||||
     * @param close - whether to close the underlying connection
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public disconnectSockets(close: boolean = false): void {
 | 
			
		||||
        return new BroadcastOperator(this.adapter).disconnectSockets(close)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public close() {
 | 
			
		||||
        RESERVED_EVENTS.forEach(event => this.removeAllListeners(event as any))
 | 
			
		||||
        this.server._nsps.delete(this.name)
 | 
			
		||||
        // @java-patch close all socket when namespace close
 | 
			
		||||
        this.sockets.forEach(socket => socket._onclose(`namepsace ${this.name} close`))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										72
									
								
								packages/websocket/src/socket.io/parent-namespace.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								packages/websocket/src/socket.io/parent-namespace.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
import { Namespace } from "./namespace"
 | 
			
		||||
import type { Server } from "./index"
 | 
			
		||||
import type {
 | 
			
		||||
    EventParams,
 | 
			
		||||
    EventNames,
 | 
			
		||||
    EventsMap,
 | 
			
		||||
    DefaultEventsMap,
 | 
			
		||||
} from "./typed-events"
 | 
			
		||||
// import type { BroadcastOptions } from "socket.io-adapter"
 | 
			
		||||
import type { BroadcastOptions } from "../socket.io-adapter"
 | 
			
		||||
 | 
			
		||||
export class ParentNamespace<
 | 
			
		||||
    ListenEvents extends EventsMap = DefaultEventsMap,
 | 
			
		||||
    EmitEvents extends EventsMap = ListenEvents,
 | 
			
		||||
    ServerSideEvents extends EventsMap = DefaultEventsMap
 | 
			
		||||
    > extends Namespace<ListenEvents, EmitEvents, ServerSideEvents> {
 | 
			
		||||
    private static count: number = 0;
 | 
			
		||||
    private children: Set<Namespace<ListenEvents, EmitEvents, ServerSideEvents>> = new Set();
 | 
			
		||||
 | 
			
		||||
    constructor(server: Server<ListenEvents, EmitEvents, ServerSideEvents>) {
 | 
			
		||||
        super(server, "/_" + ParentNamespace.count++)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _initAdapter() {
 | 
			
		||||
        const broadcast = (packet: any, opts: BroadcastOptions) => {
 | 
			
		||||
            this.children.forEach((nsp) => {
 | 
			
		||||
                nsp.adapter.broadcast(packet, opts)
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        // @ts-ignore FIXME is there a way to declare an inner class in TypeScript?
 | 
			
		||||
        this.adapter = { broadcast }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public emit<Ev extends EventNames<EmitEvents>>(
 | 
			
		||||
        ev: Ev,
 | 
			
		||||
        ...args: EventParams<EmitEvents, Ev>
 | 
			
		||||
    ): boolean {
 | 
			
		||||
        this.children.forEach((nsp) => {
 | 
			
		||||
            nsp.emit(ev, ...args)
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // public emit(...args: any[]): boolean {
 | 
			
		||||
    //     this.children.forEach(nsp => {
 | 
			
		||||
    //         nsp._rooms = this._rooms
 | 
			
		||||
    //         nsp._flags = this._flags
 | 
			
		||||
    //         nsp.emit.apply(nsp, args as any)
 | 
			
		||||
    //     })
 | 
			
		||||
    //     this._rooms.clear()
 | 
			
		||||
    //     this._flags = {}
 | 
			
		||||
 | 
			
		||||
    //     return true
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    createChild(
 | 
			
		||||
        name: string
 | 
			
		||||
    ): Namespace<ListenEvents, EmitEvents, ServerSideEvents> {
 | 
			
		||||
        const namespace = new Namespace(this.server, name)
 | 
			
		||||
        namespace._fns = this._fns.slice(0)
 | 
			
		||||
        this.listeners("connect").forEach((listener) =>
 | 
			
		||||
            namespace.on("connect", listener)
 | 
			
		||||
        )
 | 
			
		||||
        this.listeners("connection").forEach((listener) =>
 | 
			
		||||
            namespace.on("connection", listener)
 | 
			
		||||
        )
 | 
			
		||||
        this.children.add(namespace)
 | 
			
		||||
        this.server._nsps.set(name, namespace)
 | 
			
		||||
        return namespace
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										727
									
								
								packages/websocket/src/socket.io/socket.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										727
									
								
								packages/websocket/src/socket.io/socket.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,727 @@
 | 
			
		||||
 | 
			
		||||
import { Packet, PacketType } from "../socket.io-parser"
 | 
			
		||||
import url = require("url")
 | 
			
		||||
// import debugModule from "debug"
 | 
			
		||||
import type { Server } from "./index"
 | 
			
		||||
import {
 | 
			
		||||
    EventParams,
 | 
			
		||||
    EventNames,
 | 
			
		||||
    EventsMap,
 | 
			
		||||
    StrictEventEmitter,
 | 
			
		||||
    DefaultEventsMap,
 | 
			
		||||
} from "./typed-events"
 | 
			
		||||
import type { Client } from "./client"
 | 
			
		||||
import type { Namespace, NamespaceReservedEventsMap } from "./namespace"
 | 
			
		||||
// import type { IncomingMessage, IncomingHttpHeaders } from "http"
 | 
			
		||||
import type {
 | 
			
		||||
    Adapter,
 | 
			
		||||
    BroadcastFlags,
 | 
			
		||||
    Room,
 | 
			
		||||
    SocketId,
 | 
			
		||||
} from "socket.io-adapter"
 | 
			
		||||
// import base64id from "base64id"
 | 
			
		||||
import type { ParsedUrlQuery } from "querystring"
 | 
			
		||||
import { BroadcastOperator } from "./broadcast-operator"
 | 
			
		||||
 | 
			
		||||
// const debug = debugModule("socket.io:socket");
 | 
			
		||||
 | 
			
		||||
type ClientReservedEvents = "connect_error"
 | 
			
		||||
 | 
			
		||||
export interface SocketReservedEventsMap {
 | 
			
		||||
    disconnect: (reason: string) => void
 | 
			
		||||
    disconnecting: (reason: string) => void
 | 
			
		||||
    error: (err: Error) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EventEmitter reserved events: https://nodejs.org/api/events.html#events_event_newlistener
 | 
			
		||||
export interface EventEmitterReservedEventsMap {
 | 
			
		||||
    newListener: (
 | 
			
		||||
        eventName: string | Symbol,
 | 
			
		||||
        listener: (...args: any[]) => void
 | 
			
		||||
    ) => void
 | 
			
		||||
    removeListener: (
 | 
			
		||||
        eventName: string | Symbol,
 | 
			
		||||
        listener: (...args: any[]) => void
 | 
			
		||||
    ) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const RESERVED_EVENTS: ReadonlySet<string | Symbol> = new Set<
 | 
			
		||||
    | ClientReservedEvents
 | 
			
		||||
    | keyof NamespaceReservedEventsMap<never, never, never>
 | 
			
		||||
    | keyof SocketReservedEventsMap
 | 
			
		||||
    | keyof EventEmitterReservedEventsMap
 | 
			
		||||
>(<const>[
 | 
			
		||||
    "connect",
 | 
			
		||||
    "connect_error",
 | 
			
		||||
    "disconnect",
 | 
			
		||||
    "disconnecting",
 | 
			
		||||
    "newListener",
 | 
			
		||||
    "removeListener",
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The handshake details
 | 
			
		||||
 */
 | 
			
		||||
export interface Handshake {
 | 
			
		||||
    /**
 | 
			
		||||
     * The headers sent as part of the handshake
 | 
			
		||||
     */
 | 
			
		||||
    headers: any//IncomingHttpHeaders
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The date of creation (as string)
 | 
			
		||||
     */
 | 
			
		||||
    time: string
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The ip of the client
 | 
			
		||||
     */
 | 
			
		||||
    address: string
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the connection is cross-domain
 | 
			
		||||
     */
 | 
			
		||||
    xdomain: boolean
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the connection is secure
 | 
			
		||||
     */
 | 
			
		||||
    secure: boolean
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The date of creation (as unix timestamp)
 | 
			
		||||
     */
 | 
			
		||||
    issued: number
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The request URL string
 | 
			
		||||
     */
 | 
			
		||||
    url: string
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The query object
 | 
			
		||||
     */
 | 
			
		||||
    query: ParsedUrlQuery
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The auth object
 | 
			
		||||
     */
 | 
			
		||||
    auth: { [key: string]: any }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Socket<
 | 
			
		||||
    ListenEvents extends EventsMap = DefaultEventsMap,
 | 
			
		||||
    EmitEvents extends EventsMap = ListenEvents,
 | 
			
		||||
    ServerSideEvents extends EventsMap = DefaultEventsMap
 | 
			
		||||
    > extends StrictEventEmitter<
 | 
			
		||||
    ListenEvents,
 | 
			
		||||
    EmitEvents,
 | 
			
		||||
    SocketReservedEventsMap
 | 
			
		||||
    > {
 | 
			
		||||
    public readonly id: SocketId
 | 
			
		||||
    public readonly handshake: Handshake
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Additional information that can be attached to the Socket instance and which will be used in the fetchSockets method
 | 
			
		||||
     */
 | 
			
		||||
    public data: any = {};
 | 
			
		||||
 | 
			
		||||
    public connected: boolean
 | 
			
		||||
    public disconnected: boolean
 | 
			
		||||
 | 
			
		||||
    private readonly server: Server<ListenEvents, EmitEvents, ServerSideEvents>
 | 
			
		||||
    private readonly adapter: Adapter
 | 
			
		||||
    private acks: Map<number, () => void> = new Map();
 | 
			
		||||
    private fns: Array<(event: Array<any>, next: (err?: Error) => void) => void> =
 | 
			
		||||
        [];
 | 
			
		||||
    private flags: BroadcastFlags = {};
 | 
			
		||||
    private _anyListeners?: Array<(...args: any[]) => void>
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Interface to a `Client` for a given `Namespace`.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Namespace} nsp
 | 
			
		||||
     * @param {Client} client
 | 
			
		||||
     * @param {Object} auth
 | 
			
		||||
     * @package
 | 
			
		||||
     */
 | 
			
		||||
    constructor(
 | 
			
		||||
        readonly nsp: Namespace<ListenEvents, EmitEvents, ServerSideEvents>,
 | 
			
		||||
        readonly client: Client<ListenEvents, EmitEvents, ServerSideEvents>,
 | 
			
		||||
        auth: object
 | 
			
		||||
    ) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.nsp = nsp
 | 
			
		||||
        this.server = nsp.server
 | 
			
		||||
        this.adapter = this.nsp.adapter
 | 
			
		||||
        // if (client.conn.protocol === 3) {
 | 
			
		||||
        //     // @ts-ignore
 | 
			
		||||
        this.id = nsp.name !== "/" ? nsp.name + "#" + client.id : client.id
 | 
			
		||||
        // } else {
 | 
			
		||||
        //     this.id = base64id.generateId() // don't reuse the Engine.IO id because it's sensitive information
 | 
			
		||||
        // }
 | 
			
		||||
        this.client = client
 | 
			
		||||
        this.acks = new Map()
 | 
			
		||||
        this.connected = true
 | 
			
		||||
        this.disconnected = false
 | 
			
		||||
        this.handshake = this.buildHandshake(auth)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildHandshake(auth): Handshake {
 | 
			
		||||
        return {
 | 
			
		||||
            headers: this.request.headers,
 | 
			
		||||
            time: new Date() + "",
 | 
			
		||||
            address: this.conn.remoteAddress,
 | 
			
		||||
            xdomain: !!this.request.headers.origin,
 | 
			
		||||
            // @ts-ignore
 | 
			
		||||
            secure: !!this.request.connection.encrypted,
 | 
			
		||||
            issued: +new Date(),
 | 
			
		||||
            url: this.request.url!,
 | 
			
		||||
            query: url.parse(this.request.url!, true).query,
 | 
			
		||||
            auth,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Emits to this client.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Always returns `true`.
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public emit<Ev extends EventNames<EmitEvents>>(
 | 
			
		||||
        ev: Ev,
 | 
			
		||||
        ...args: EventParams<EmitEvents, Ev>
 | 
			
		||||
    ): boolean {
 | 
			
		||||
        if (RESERVED_EVENTS.has(ev)) {
 | 
			
		||||
            throw new Error(`"${ev}" is a reserved event name`)
 | 
			
		||||
        }
 | 
			
		||||
        const data: any[] = [ev, ...args]
 | 
			
		||||
        const packet: any = {
 | 
			
		||||
            type: PacketType.EVENT,
 | 
			
		||||
            data: data,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // access last argument to see if it's an ACK callback
 | 
			
		||||
        if (typeof data[data.length - 1] === "function") {
 | 
			
		||||
            console.trace("emitting packet with ack id %d", this.nsp._ids)
 | 
			
		||||
            this.acks.set(this.nsp._ids, data.pop())
 | 
			
		||||
            packet.id = this.nsp._ids++
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const flags = Object.assign({}, this.flags)
 | 
			
		||||
        this.flags = {}
 | 
			
		||||
 | 
			
		||||
        this.packet(packet, flags)
 | 
			
		||||
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Targets a room when broadcasting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public to(room: Room | Room[]): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return this.newBroadcastOperator().to(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Targets a room when broadcasting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public in(room: Room | Room[]): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return this.newBroadcastOperator().in(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Excludes a room when broadcasting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param room
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public except(room: Room | Room[]): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return this.newBroadcastOperator().except(room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a `message` event.
 | 
			
		||||
     *
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public send(...args: EventParams<EmitEvents, "message">): this {
 | 
			
		||||
        this.emit("message", ...args)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a `message` event.
 | 
			
		||||
     *
 | 
			
		||||
     * @return self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public write(...args: EventParams<EmitEvents, "message">): this {
 | 
			
		||||
        this.emit("message", ...args)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Writes a packet.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} packet - packet object
 | 
			
		||||
     * @param {Object} opts - options
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private packet(
 | 
			
		||||
        packet: Omit<Packet, "nsp"> & Partial<Pick<Packet, "nsp">>,
 | 
			
		||||
        opts: any = {}
 | 
			
		||||
    ): void {
 | 
			
		||||
        packet.nsp = this.nsp.name
 | 
			
		||||
        opts.compress = false !== opts.compress
 | 
			
		||||
        this.client._packet(packet as Packet, opts)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Joins a room.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String|Array} rooms - room or array of rooms
 | 
			
		||||
     * @return a Promise or nothing, depending on the adapter
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public join(rooms: Room | Array<Room>): Promise<void> | void {
 | 
			
		||||
        console.debug(`join room ${rooms}`)
 | 
			
		||||
 | 
			
		||||
        return this.adapter.addAll(
 | 
			
		||||
            this.id,
 | 
			
		||||
            new Set(Array.isArray(rooms) ? rooms : [rooms])
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Leaves a room.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} room
 | 
			
		||||
     * @return a Promise or nothing, depending on the adapter
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public leave(room: string): Promise<void> | void {
 | 
			
		||||
        console.debug(`leave room ${room}`)
 | 
			
		||||
 | 
			
		||||
        return this.adapter.del(this.id, room)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Leave all rooms.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private leaveAll(): void {
 | 
			
		||||
        this.adapter.delAll(this.id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called by `Namespace` upon successful
 | 
			
		||||
     * middleware execution (ie: authorization).
 | 
			
		||||
     * Socket is added to namespace array before
 | 
			
		||||
     * call to join, so adapters can access it.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _onconnect(): void {
 | 
			
		||||
        console.debug(`socket ${this.id} connected - writing packet`)
 | 
			
		||||
        this.join(this.id)
 | 
			
		||||
        if (this.conn.protocol === 3) {
 | 
			
		||||
            this.packet({ type: PacketType.CONNECT })
 | 
			
		||||
        } else {
 | 
			
		||||
            this.packet({ type: PacketType.CONNECT, data: { sid: this.id } })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called with each packet. Called by `Client`.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} packet
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _onpacket(packet: Packet): void {
 | 
			
		||||
        console.trace("got packet", JSON.stringify(packet))
 | 
			
		||||
        switch (packet.type) {
 | 
			
		||||
            case PacketType.EVENT:
 | 
			
		||||
                this.onevent(packet)
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            case PacketType.BINARY_EVENT:
 | 
			
		||||
                this.onevent(packet)
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            case PacketType.ACK:
 | 
			
		||||
                this.onack(packet)
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            case PacketType.BINARY_ACK:
 | 
			
		||||
                this.onack(packet)
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            case PacketType.DISCONNECT:
 | 
			
		||||
                this.ondisconnect()
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            case PacketType.CONNECT_ERROR:
 | 
			
		||||
                this._onerror(new Error(packet.data))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon event packet.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Packet} packet - packet object
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private onevent(packet: Packet): void {
 | 
			
		||||
        const args = packet.data || []
 | 
			
		||||
        console.trace("emitting event", JSON.stringify(args))
 | 
			
		||||
 | 
			
		||||
        if (null != packet.id) {
 | 
			
		||||
            console.trace("attaching ack callback to event")
 | 
			
		||||
            args.push(this.ack(packet.id))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this._anyListeners && this._anyListeners.length) {
 | 
			
		||||
            const listeners = this._anyListeners.slice()
 | 
			
		||||
            for (const listener of listeners) {
 | 
			
		||||
                listener.apply(this, args)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        this.dispatch(args)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Produces an ack callback to emit with an event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Number} id - packet id
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private ack(id: number): () => void {
 | 
			
		||||
        const self = this
 | 
			
		||||
        let sent = false
 | 
			
		||||
        return function () {
 | 
			
		||||
            // prevent double callbacks
 | 
			
		||||
            if (sent) return
 | 
			
		||||
            const args = Array.prototype.slice.call(arguments)
 | 
			
		||||
            console.trace("sending ack", JSON.stringify(args))
 | 
			
		||||
 | 
			
		||||
            self.packet({
 | 
			
		||||
                id: id,
 | 
			
		||||
                type: PacketType.ACK,
 | 
			
		||||
                data: args,
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            sent = true
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon ack packet.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private onack(packet: Packet): void {
 | 
			
		||||
        const ack = this.acks.get(packet.id!)
 | 
			
		||||
        if ("function" == typeof ack) {
 | 
			
		||||
            console.trace(`socket ${this.id} calling ack ${packet.id} with ${packet.data}`)
 | 
			
		||||
            ack.apply(this, packet.data)
 | 
			
		||||
            this.acks.delete(packet.id!)
 | 
			
		||||
        } else {
 | 
			
		||||
            console.debug(`socket ${this.id} bad ack`, packet.id)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon client disconnect packet.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private ondisconnect(): void {
 | 
			
		||||
        console.debug(`socket ${this.id} got disconnect packet`)
 | 
			
		||||
        this._onclose("client namespace disconnect")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a client error.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _onerror(err: Error): void {
 | 
			
		||||
        if (this.listeners("error").length) {
 | 
			
		||||
            this.emitReserved("error", err)
 | 
			
		||||
        } else {
 | 
			
		||||
            console.error("Missing error handler on `socket`.")
 | 
			
		||||
            console.error(err.stack)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called upon closing. Called by `Client`.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} reason
 | 
			
		||||
     * @throw {Error} optional error object
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _onclose(reason: string): this | undefined {
 | 
			
		||||
        if (!this.connected) return this
 | 
			
		||||
        console.debug(`closing socket ${this.id} - reason: ${reason}`)
 | 
			
		||||
        this.emitReserved("disconnecting", reason)
 | 
			
		||||
        this.leaveAll()
 | 
			
		||||
        this.nsp._remove(this)
 | 
			
		||||
        this.client._remove(this)
 | 
			
		||||
        this.connected = false
 | 
			
		||||
        this.disconnected = true
 | 
			
		||||
        this.emitReserved("disconnect", reason)
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Produces an `error` packet.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} err - error object
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _error(err): void {
 | 
			
		||||
        this.packet({ type: PacketType.CONNECT_ERROR, data: err })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Disconnects this client.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Boolean} close - if `true`, closes the underlying connection
 | 
			
		||||
     * @return {Socket} self
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public disconnect(close = false): this {
 | 
			
		||||
        if (!this.connected) return this
 | 
			
		||||
        if (close) {
 | 
			
		||||
            this.client._disconnect()
 | 
			
		||||
        } else {
 | 
			
		||||
            this.packet({ type: PacketType.DISCONNECT })
 | 
			
		||||
            this._onclose("server namespace disconnect")
 | 
			
		||||
        }
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the compress flag.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Boolean} compress - if `true`, compresses the sending data
 | 
			
		||||
     * @return {Socket} self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public compress(compress: boolean): this {
 | 
			
		||||
        this.flags.compress = compress
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
   * Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
 | 
			
		||||
   * receive messages (because of network slowness or other issues, or because they’re connected through long polling
 | 
			
		||||
   * and is in the middle of a request-response cycle).
 | 
			
		||||
   *
 | 
			
		||||
   * @return {Socket} self
 | 
			
		||||
   * @public
 | 
			
		||||
   */
 | 
			
		||||
    public get volatile(): this {
 | 
			
		||||
        this.flags.volatile = true
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a modifier for a subsequent event emission that the event data will only be broadcast to every sockets but the
 | 
			
		||||
     * sender.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Socket} self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get broadcast(): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return this.newBroadcastOperator()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Socket} self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get local(): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        return this.newBroadcastOperator().local
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Dispatch incoming event to socket listeners.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Array} event - event that will get emitted
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private dispatch(event: [eventName: string, ...args: any[]]): void {
 | 
			
		||||
        console.trace("dispatching an event", JSON.stringify(event))
 | 
			
		||||
        this.run(event, (err) => {
 | 
			
		||||
            process.nextTick(() => {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    return this._onerror(err)
 | 
			
		||||
                }
 | 
			
		||||
                if (this.connected) {
 | 
			
		||||
                    super.emitUntyped.apply(this, event)
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.debug("ignore packet received after disconnection")
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets up socket middleware.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Function} fn - middleware function (event, next)
 | 
			
		||||
     * @return {Socket} self
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public use(
 | 
			
		||||
        fn: (event: Array<any>, next: (err?: Error) => void) => void
 | 
			
		||||
    ): this {
 | 
			
		||||
        this.fns.push(fn)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Executes the middleware for an incoming event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Array} event - event that will get emitted
 | 
			
		||||
     * @param {Function} fn - last fn call in the middleware
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    private run(
 | 
			
		||||
        event: [eventName: string, ...args: any[]],
 | 
			
		||||
        fn: (err: Error | null) => void
 | 
			
		||||
    ): void {
 | 
			
		||||
        const fns = this.fns.slice(0)
 | 
			
		||||
        if (!fns.length) return fn(null)
 | 
			
		||||
 | 
			
		||||
        function run(i: number) {
 | 
			
		||||
            fns[i](event, function (err) {
 | 
			
		||||
                // upon error, short-circuit
 | 
			
		||||
                if (err) return fn(err)
 | 
			
		||||
 | 
			
		||||
                // if no middleware left, summon callback
 | 
			
		||||
                if (!fns[i + 1]) return fn(null)
 | 
			
		||||
 | 
			
		||||
                // go on to next
 | 
			
		||||
                run(i + 1)
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        run(0)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A reference to the request that originated the underlying Engine.IO Socket.
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get request(): any /** IncomingMessage */ {
 | 
			
		||||
        return this.client.request
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A reference to the underlying Client transport connection (Engine.IO Socket object).
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get conn() {
 | 
			
		||||
        return this.client.conn
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public get rooms(): Set<Room> {
 | 
			
		||||
        return this.adapter.socketRooms(this.id) || new Set()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
 | 
			
		||||
     * callback.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public onAny(listener: (...args: any[]) => void): this {
 | 
			
		||||
        this._anyListeners = this._anyListeners || []
 | 
			
		||||
        this._anyListeners.push(listener)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
 | 
			
		||||
     * callback. The listener is added to the beginning of the listeners array.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public prependAny(listener: (...args: any[]) => void): this {
 | 
			
		||||
        this._anyListeners = this._anyListeners || []
 | 
			
		||||
        this._anyListeners.unshift(listener)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes the listener that will be fired when any event is emitted.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public offAny(listener?: (...args: any[]) => void): this {
 | 
			
		||||
        if (!this._anyListeners) {
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
        if (listener) {
 | 
			
		||||
            const listeners = this._anyListeners
 | 
			
		||||
            for (let i = 0; i < listeners.length; i++) {
 | 
			
		||||
                if (listener === listeners[i]) {
 | 
			
		||||
                    listeners.splice(i, 1)
 | 
			
		||||
                    return this
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            this._anyListeners = []
 | 
			
		||||
        }
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,
 | 
			
		||||
     * e.g. to remove listeners.
 | 
			
		||||
     *
 | 
			
		||||
     * @public
 | 
			
		||||
     */
 | 
			
		||||
    public listenersAny() {
 | 
			
		||||
        return this._anyListeners || []
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private newBroadcastOperator(): BroadcastOperator<EmitEvents> {
 | 
			
		||||
        const flags = Object.assign({}, this.flags)
 | 
			
		||||
        this.flags = {}
 | 
			
		||||
        return new BroadcastOperator(
 | 
			
		||||
            this.adapter,
 | 
			
		||||
            new Set<Room>(),
 | 
			
		||||
            new Set<Room>([this.id]),
 | 
			
		||||
            flags
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										180
									
								
								packages/websocket/src/socket.io/typed-events.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								packages/websocket/src/socket.io/typed-events.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,180 @@
 | 
			
		||||
import { EventEmitter } from "events"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An events map is an interface that maps event names to their value, which
 | 
			
		||||
 * represents the type of the `on` listener.
 | 
			
		||||
 */
 | 
			
		||||
export interface EventsMap {
 | 
			
		||||
    [event: string]: any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The default events map, used if no EventsMap is given. Using this EventsMap
 | 
			
		||||
 * is equivalent to accepting all event names, and any data.
 | 
			
		||||
 */
 | 
			
		||||
export interface DefaultEventsMap {
 | 
			
		||||
    [event: string]: (...args: any[]) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a union type containing all the keys of an event map.
 | 
			
		||||
 */
 | 
			
		||||
export type EventNames<Map extends EventsMap> = keyof Map & (string | symbol)
 | 
			
		||||
 | 
			
		||||
/** The tuple type representing the parameters of an event listener */
 | 
			
		||||
export type EventParams<
 | 
			
		||||
    Map extends EventsMap,
 | 
			
		||||
    Ev extends EventNames<Map>
 | 
			
		||||
    > = Parameters<Map[Ev]>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The event names that are either in ReservedEvents or in UserEvents
 | 
			
		||||
 */
 | 
			
		||||
export type ReservedOrUserEventNames<
 | 
			
		||||
    ReservedEventsMap extends EventsMap,
 | 
			
		||||
    UserEvents extends EventsMap
 | 
			
		||||
    > = EventNames<ReservedEventsMap> | EventNames<UserEvents>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of a listener of a user event or a reserved event. If `Ev` is in
 | 
			
		||||
 * `ReservedEvents`, the reserved event listener is returned.
 | 
			
		||||
 */
 | 
			
		||||
export type ReservedOrUserListener<
 | 
			
		||||
    ReservedEvents extends EventsMap,
 | 
			
		||||
    UserEvents extends EventsMap,
 | 
			
		||||
    Ev extends ReservedOrUserEventNames<ReservedEvents, UserEvents>
 | 
			
		||||
    > = FallbackToUntypedListener<
 | 
			
		||||
        Ev extends EventNames<ReservedEvents>
 | 
			
		||||
        ? ReservedEvents[Ev]
 | 
			
		||||
        : Ev extends EventNames<UserEvents>
 | 
			
		||||
        ? UserEvents[Ev]
 | 
			
		||||
        : never
 | 
			
		||||
    >
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns an untyped listener type if `T` is `never`; otherwise, returns `T`.
 | 
			
		||||
 *
 | 
			
		||||
 * This is a hack to mitigate https://github.com/socketio/socket.io/issues/3833.
 | 
			
		||||
 * Needed because of https://github.com/microsoft/TypeScript/issues/41778
 | 
			
		||||
 */
 | 
			
		||||
type FallbackToUntypedListener<T> = [T] extends [never]
 | 
			
		||||
    ? (...args: any[]) => void
 | 
			
		||||
    : T
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface for classes that aren't `EventEmitter`s, but still expose a
 | 
			
		||||
 * strictly typed `emit` method.
 | 
			
		||||
 */
 | 
			
		||||
export interface TypedEventBroadcaster<EmitEvents extends EventsMap> {
 | 
			
		||||
    emit<Ev extends EventNames<EmitEvents>>(
 | 
			
		||||
        ev: Ev,
 | 
			
		||||
        ...args: EventParams<EmitEvents, Ev>
 | 
			
		||||
    ): boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Strictly typed version of an `EventEmitter`. A `TypedEventEmitter` takes type
 | 
			
		||||
 * parameters for mappings of event names to event data types, and strictly
 | 
			
		||||
 * types method calls to the `EventEmitter` according to these event maps.
 | 
			
		||||
 *
 | 
			
		||||
 * @typeParam ListenEvents - `EventsMap` of user-defined events that can be
 | 
			
		||||
 * listened to with `on` or `once`
 | 
			
		||||
 * @typeParam EmitEvents - `EventsMap` of user-defined events that can be
 | 
			
		||||
 * emitted with `emit`
 | 
			
		||||
 * @typeParam ReservedEvents - `EventsMap` of reserved events, that can be
 | 
			
		||||
 * emitted by socket.io with `emitReserved`, and can be listened to with
 | 
			
		||||
 * `listen`.
 | 
			
		||||
 */
 | 
			
		||||
export abstract class StrictEventEmitter<
 | 
			
		||||
    ListenEvents extends EventsMap,
 | 
			
		||||
    EmitEvents extends EventsMap,
 | 
			
		||||
    ReservedEvents extends EventsMap = {}
 | 
			
		||||
    >
 | 
			
		||||
    extends EventEmitter
 | 
			
		||||
    implements TypedEventBroadcaster<EmitEvents>
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds the `listener` function as an event listener for `ev`.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ev Name of the event
 | 
			
		||||
     * @param listener Callback function
 | 
			
		||||
     */
 | 
			
		||||
    on<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(
 | 
			
		||||
        ev: Ev,
 | 
			
		||||
        listener: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>
 | 
			
		||||
    ): this {
 | 
			
		||||
        return super.on(ev, listener)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a one-time `listener` function as an event listener for `ev`.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ev Name of the event
 | 
			
		||||
     * @param listener Callback function
 | 
			
		||||
     */
 | 
			
		||||
    once<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(
 | 
			
		||||
        ev: Ev,
 | 
			
		||||
        listener: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>
 | 
			
		||||
    ): this {
 | 
			
		||||
        return super.once(ev, listener)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Emits an event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ev Name of the event
 | 
			
		||||
     * @param args Values to send to listeners of this event
 | 
			
		||||
     */
 | 
			
		||||
    emit<Ev extends EventNames<EmitEvents>>(
 | 
			
		||||
        ev: Ev,
 | 
			
		||||
        ...args: EventParams<EmitEvents, Ev>
 | 
			
		||||
    ): boolean {
 | 
			
		||||
        return super.emit(ev, ...args)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Emits a reserved event.
 | 
			
		||||
     *
 | 
			
		||||
     * This method is `protected`, so that only a class extending
 | 
			
		||||
     * `StrictEventEmitter` can emit its own reserved events.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ev Reserved event name
 | 
			
		||||
     * @param args Arguments to emit along with the event
 | 
			
		||||
     */
 | 
			
		||||
    protected emitReserved<Ev extends EventNames<ReservedEvents>>(
 | 
			
		||||
        ev: Ev,
 | 
			
		||||
        ...args: EventParams<ReservedEvents, Ev>
 | 
			
		||||
    ): boolean {
 | 
			
		||||
        return super.emit(ev, ...args)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Emits an event.
 | 
			
		||||
     *
 | 
			
		||||
     * This method is `protected`, so that only a class extending
 | 
			
		||||
     * `StrictEventEmitter` can get around the strict typing. This is useful for
 | 
			
		||||
     * calling `emit.apply`, which can be called as `emitUntyped.apply`.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ev Event name
 | 
			
		||||
     * @param args Arguments to emit along with the event
 | 
			
		||||
     */
 | 
			
		||||
    protected emitUntyped(ev: string, ...args: any[]): boolean {
 | 
			
		||||
        return super.emit(ev, ...args)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the listeners listening to an event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event Event name
 | 
			
		||||
     * @returns Array of listeners subscribed to `event`
 | 
			
		||||
     */
 | 
			
		||||
    listeners<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(
 | 
			
		||||
        event: Ev
 | 
			
		||||
    ): ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>[] {
 | 
			
		||||
        return super.listeners(event) as ReservedOrUserListener<
 | 
			
		||||
            ReservedEvents,
 | 
			
		||||
            ListenEvents,
 | 
			
		||||
            Ev
 | 
			
		||||
        >[]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,22 +1,24 @@
 | 
			
		||||
import { Transport } from '../transport'
 | 
			
		||||
import { WebSocketClient } from '../server/client'
 | 
			
		||||
 | 
			
		||||
export class TomcatClient extends Transport {
 | 
			
		||||
export class TomcatClient extends WebSocketClient {
 | 
			
		||||
    private session: javax.websocket.Session
 | 
			
		||||
 | 
			
		||||
    constructor(server: any, session: javax.websocket.Session) {
 | 
			
		||||
        super(server)
 | 
			
		||||
        this.remoteAddress = session + ''
 | 
			
		||||
        this.request = {
 | 
			
		||||
            uri: () => `${session.getRequestURI()}`,
 | 
			
		||||
            headers: () => []
 | 
			
		||||
        }
 | 
			
		||||
        this._id = session.getId() + ''
 | 
			
		||||
    constructor(session: javax.websocket.Session) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.id = session.getId() + ''
 | 
			
		||||
        this.session = session
 | 
			
		||||
    }
 | 
			
		||||
    doSend(text: string) {
 | 
			
		||||
        Java.synchronized(() => this.session.getBasicRemote().sendText(text), this.session)()
 | 
			
		||||
    send(text: string, opts?: any, callback?: (err?: Error) => void) {
 | 
			
		||||
        Java.synchronized(() => {
 | 
			
		||||
            try {
 | 
			
		||||
                this.session.getBasicRemote().sendText(text)
 | 
			
		||||
                callback?.()
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                callback?.(error)
 | 
			
		||||
            }
 | 
			
		||||
        }, this.session)()
 | 
			
		||||
    }
 | 
			
		||||
    doClose() {
 | 
			
		||||
    close() {
 | 
			
		||||
        this.session.close()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,60 +1,61 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
import { JavaServerOptions, WebSocketServer } from '../server'
 | 
			
		||||
import { Request } from '../server/request'
 | 
			
		||||
 | 
			
		||||
import { ServerOptions } from '../socket-io'
 | 
			
		||||
import { ServerEvent } from '../socket-io/constants'
 | 
			
		||||
import { ProxyBeanName } from './constants'
 | 
			
		||||
import { TomcatClient } from './client'
 | 
			
		||||
import { ProxyBeanName } from './constants'
 | 
			
		||||
 | 
			
		||||
const ThreadPoolExecutor = Java.type('java.util.concurrent.ThreadPoolExecutor')
 | 
			
		||||
 | 
			
		||||
type TomcatWebSocketSession = javax.websocket.Session
 | 
			
		||||
 | 
			
		||||
class TomcatWebSocketServer extends EventEmitter {
 | 
			
		||||
    private beanFactory: any
 | 
			
		||||
class TomcatWebSocketServer extends WebSocketServer {
 | 
			
		||||
    private executor: any
 | 
			
		||||
    private clients: Map<string, any>
 | 
			
		||||
 | 
			
		||||
    constructor(beanFactory: any, options: ServerOptions) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.clients = new Map()
 | 
			
		||||
        this.beanFactory = beanFactory
 | 
			
		||||
    constructor(beanFactory: any, options: JavaServerOptions) {
 | 
			
		||||
        super(beanFactory, options)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected initialize(): void {
 | 
			
		||||
        this.initThreadPool()
 | 
			
		||||
        try { this.beanFactory.destroySingleton(ProxyBeanName) } catch (error) { }
 | 
			
		||||
        try { this.instance.destroySingleton(ProxyBeanName) } catch (error) { }
 | 
			
		||||
        let NashornWebSocketServerProxy = Java.extend(Java.type("pw.yumc.MiaoScript.websocket.WebSocketProxy"), {
 | 
			
		||||
            onOpen: (session: TomcatWebSocketSession) => {
 | 
			
		||||
                let cid = `${session?.getId()}`
 | 
			
		||||
                let tomcatClient = new TomcatClient(this, session)
 | 
			
		||||
                this.clients.set(cid, tomcatClient)
 | 
			
		||||
                this.emit(ServerEvent.connect, tomcatClient)
 | 
			
		||||
                this.onconnect(session)
 | 
			
		||||
            },
 | 
			
		||||
            onMessage: (session: TomcatWebSocketSession, message: string) => {
 | 
			
		||||
                let cid = `${session?.getId()}`
 | 
			
		||||
                if (this.clients.has(cid)) {
 | 
			
		||||
                    this.executor.execute(() => this.emit(ServerEvent.message, this.clients.get(cid), message))
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.error(`unknow client ${session} reciver message ${message}`)
 | 
			
		||||
                }
 | 
			
		||||
                this.onmessage(session, message)
 | 
			
		||||
            },
 | 
			
		||||
            onClose: (session: TomcatWebSocketSession, reason: any) => {
 | 
			
		||||
                let cid = `${session?.getId()}`
 | 
			
		||||
                if (this.clients.has(cid)) {
 | 
			
		||||
                    this.emit(ServerEvent.disconnect, this.clients.get(cid), reason)
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.error(`unknow client ${session} disconnect cause ${reason}`)
 | 
			
		||||
                }
 | 
			
		||||
                this.ondisconnect(session, reason)
 | 
			
		||||
            },
 | 
			
		||||
            onError: (session: TomcatWebSocketSession, error: Error) => {
 | 
			
		||||
                let cid = `${session?.getId()}`
 | 
			
		||||
                if (this.clients.has(cid)) {
 | 
			
		||||
                    this.emit(ServerEvent.error, this.clients.get(cid), error)
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.error(`unknow client ${session} cause error ${error}`)
 | 
			
		||||
                    console.ex(error)
 | 
			
		||||
                }
 | 
			
		||||
                this.onerror(session, error)
 | 
			
		||||
            },
 | 
			
		||||
        })
 | 
			
		||||
        this.beanFactory.registerSingleton(ProxyBeanName, new NashornWebSocketServerProxy())
 | 
			
		||||
        this.instance.registerSingleton(ProxyBeanName, new NashornWebSocketServerProxy())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected getId(session) {
 | 
			
		||||
        return session?.getId() + ''
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected getRequest(session) {
 | 
			
		||||
        let request = new Request(session.getRequestURI(), "GET")
 | 
			
		||||
        request.connection = {
 | 
			
		||||
            remoteAddress: ''
 | 
			
		||||
        }
 | 
			
		||||
        return request
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected getSocket(session) {
 | 
			
		||||
        return new TomcatClient(session)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected doClose() {
 | 
			
		||||
        this.instance.destroySingleton(ProxyBeanName)
 | 
			
		||||
        this.executor.shutdown()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private initThreadPool() {
 | 
			
		||||
        const ThreadPoolTaskExecutor = Java.type('org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor')
 | 
			
		||||
        this.executor = new ThreadPoolTaskExecutor()
 | 
			
		||||
@@ -66,11 +67,6 @@ class TomcatWebSocketServer extends EventEmitter {
 | 
			
		||||
        this.executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy())
 | 
			
		||||
        this.executor.initialize()
 | 
			
		||||
    }
 | 
			
		||||
    close() {
 | 
			
		||||
        this.clients.forEach(client => client.close())
 | 
			
		||||
        this.beanFactory.destroySingleton(ProxyBeanName)
 | 
			
		||||
        this.executor.shutdown()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,35 +0,0 @@
 | 
			
		||||
import { EventEmitter } from 'events'
 | 
			
		||||
 | 
			
		||||
export abstract class Transport extends EventEmitter {
 | 
			
		||||
    protected _id: string
 | 
			
		||||
 | 
			
		||||
    server: any
 | 
			
		||||
    readyState: 'opening' | 'open' | 'closing' | 'closed'
 | 
			
		||||
    remoteAddress: string
 | 
			
		||||
    upgraded: boolean
 | 
			
		||||
    request: any
 | 
			
		||||
 | 
			
		||||
    constructor(server: any) {
 | 
			
		||||
        super()
 | 
			
		||||
        this.server = server
 | 
			
		||||
        this.readyState = 'open'
 | 
			
		||||
        this.upgraded = true
 | 
			
		||||
    }
 | 
			
		||||
    get id() {
 | 
			
		||||
        return this._id
 | 
			
		||||
    }
 | 
			
		||||
    send(text: string) {
 | 
			
		||||
        if (this.readyState == 'open') {
 | 
			
		||||
            this.doSend(text)
 | 
			
		||||
        } else {
 | 
			
		||||
            console.debug(`send message ${text} to close client ${this._id}`)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    close() {
 | 
			
		||||
        if ("closed" === this.readyState || "closing" === this.readyState) { return }
 | 
			
		||||
        this.doClose()
 | 
			
		||||
        this.readyState = 'closed'
 | 
			
		||||
    }
 | 
			
		||||
    abstract doSend(text: string)
 | 
			
		||||
    abstract doClose()
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user