feat: 完善client相关功能 重构server部分
Signed-off-by: MiaoWoo <admin@yumc.pw>
This commit is contained in:
parent
774763be13
commit
53502e12cf
@ -18,6 +18,10 @@
|
|||||||
"build": "yarn clean && tsc",
|
"build": "yarn clean && tsc",
|
||||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||||
},
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"backo2": "^1.0.2",
|
||||||
|
"parseuri": "^0.0.6"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ccms/nashorn": "^0.16.0",
|
"@ccms/nashorn": "^0.16.0",
|
||||||
"@javatypes/tomcat-websocket-api": "^0.0.3",
|
"@javatypes/tomcat-websocket-api": "^0.0.3",
|
||||||
|
@ -46,22 +46,25 @@ export class WebSocket extends EventEmitter {
|
|||||||
try {
|
try {
|
||||||
let TransportImpl = require('./netty').NettyWebSocket
|
let TransportImpl = require('./netty').NettyWebSocket
|
||||||
this.client = new TransportImpl(url, subProtocol, headers)
|
this.client = new TransportImpl(url, subProtocol, headers)
|
||||||
|
console.debug('create websocket from ' + this.client.constructor.name)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('create websocket impl error: ' + error)
|
console.error('create websocket impl error: ' + error)
|
||||||
console.ex(error)
|
console.ex(error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.client.on('open', (event) => {
|
this.client.on('open', (event) => {
|
||||||
|
console.debug('client WebSocket call open', this.onopen)
|
||||||
this.onopen?.(event)
|
this.onopen?.(event)
|
||||||
manager.add(this)
|
manager.add(this)
|
||||||
})
|
})
|
||||||
this.client.on('message', (event) => this.onmessage?.(event))
|
this.client.on('message', (event) => this.onmessage?.(event))
|
||||||
this.client.on('close', (event) => {
|
this.client.on('close', (event) => {
|
||||||
|
console.log('client WebSocket call close', this.onclose)
|
||||||
this.onclose?.(event)
|
this.onclose?.(event)
|
||||||
manager.del(this)
|
manager.del(this)
|
||||||
})
|
})
|
||||||
this.client.on('error', (event) => this.onerror?.(event))
|
this.client.on('error', (event) => this.onerror?.(event))
|
||||||
this.client.connect()
|
setTimeout(() => this.client.connect(), 20)
|
||||||
}
|
}
|
||||||
get id() {
|
get id() {
|
||||||
return this.client.id
|
return this.client.id
|
||||||
|
@ -63,7 +63,7 @@ export class NettyWebSocket extends Transport {
|
|||||||
this._port = 80
|
this._port = 80
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.debug(`constructor NettyWebSocket url: ${url} scheme: ${this._schema} host: ${this._host} port: ${this._port}`)
|
console.debug(`constructor NettyWebSocket url: ${url} scheme: ${this._schema} host: ${this._host} port: ${this._port} header: ${JSON.stringify(headers)}`)
|
||||||
}
|
}
|
||||||
getId() {
|
getId() {
|
||||||
return this.channel?.id() + ''
|
return this.channel?.id() + ''
|
||||||
|
@ -32,9 +32,11 @@ export abstract class Transport extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
|
console.debug(`client Transport connect`)
|
||||||
try {
|
try {
|
||||||
this.doConnect()
|
this.doConnect()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.ex(error)
|
||||||
this.onerror({ error })
|
this.onerror({ error })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
packages/websocket/src/debug.ts
Normal file
1
packages/websocket/src/debug.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export = (namepsace) => (...args) => { }//console.debug(namepsace, ...args)
|
16
packages/websocket/src/engine.io-client/index.ts
Normal file
16
packages/websocket/src/engine.io-client/index.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Socket } from './socket'
|
||||||
|
|
||||||
|
export default (uri, opts) => new Socket(uri, opts)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose deps for legacy compatibility
|
||||||
|
* and standalone browser access.
|
||||||
|
*/
|
||||||
|
const protocol = Socket.protocol // this is an int
|
||||||
|
export { Socket, protocol }
|
||||||
|
// module.exports.Transport = require("./transport")
|
||||||
|
// module.exports.transports = require("./transports/index")
|
||||||
|
// module.exports.parser = require("../engine.io-parser")
|
||||||
|
export * from './transport'
|
||||||
|
export * from './transports/index'
|
||||||
|
export * from '../engine.io-parser'
|
688
packages/websocket/src/engine.io-client/socket.ts
Normal file
688
packages/websocket/src/engine.io-client/socket.ts
Normal file
@ -0,0 +1,688 @@
|
|||||||
|
import transports from "./transports"
|
||||||
|
// const transports = require("./transports/index")
|
||||||
|
const Emitter = require("component-emitter")
|
||||||
|
const debug = (...args: any) => console.debug('engine.io-client:socket', ...args)//require("debug")("engine.io-client:socket")
|
||||||
|
import parser from "../engine.io-parser"
|
||||||
|
const parseuri = require("parseuri")
|
||||||
|
const parseqs = require("parseqs")
|
||||||
|
import { installTimerFunctions } from "./util"
|
||||||
|
|
||||||
|
export class Socket extends Emitter {
|
||||||
|
/**
|
||||||
|
* Socket constructor.
|
||||||
|
*
|
||||||
|
* @param {String|Object} uri or options
|
||||||
|
* @param {Object} options
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
constructor(uri, opts: any = {}) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
if (uri && "object" === typeof uri) {
|
||||||
|
opts = uri
|
||||||
|
uri = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri) {
|
||||||
|
uri = parseuri(uri)
|
||||||
|
opts.hostname = uri.host
|
||||||
|
opts.secure = uri.protocol === "https" || uri.protocol === "wss"
|
||||||
|
opts.port = uri.port
|
||||||
|
if (uri.query) opts.query = uri.query
|
||||||
|
} else if (opts.host) {
|
||||||
|
opts.hostname = parseuri(opts.host).host
|
||||||
|
}
|
||||||
|
|
||||||
|
installTimerFunctions(this, opts)
|
||||||
|
|
||||||
|
this.secure =
|
||||||
|
null != opts.secure
|
||||||
|
? opts.secure
|
||||||
|
: typeof location !== "undefined" && "https:" === location.protocol
|
||||||
|
|
||||||
|
if (opts.hostname && !opts.port) {
|
||||||
|
// if no port is specified manually, use the protocol default
|
||||||
|
opts.port = this.secure ? "443" : "80"
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hostname =
|
||||||
|
opts.hostname ||
|
||||||
|
(typeof location !== "undefined" ? location.hostname : "localhost")
|
||||||
|
this.port =
|
||||||
|
opts.port ||
|
||||||
|
(typeof location !== "undefined" && location.port
|
||||||
|
? location.port
|
||||||
|
: this.secure
|
||||||
|
? 443
|
||||||
|
: 80)
|
||||||
|
|
||||||
|
this.transports = ["websocket"]
|
||||||
|
this.readyState = ""
|
||||||
|
this.writeBuffer = []
|
||||||
|
this.prevBufferLen = 0
|
||||||
|
|
||||||
|
this.opts = Object.assign(
|
||||||
|
{
|
||||||
|
path: "/engine.io",
|
||||||
|
agent: false,
|
||||||
|
withCredentials: false,
|
||||||
|
upgrade: true,
|
||||||
|
jsonp: true,
|
||||||
|
timestampParam: "t",
|
||||||
|
rememberUpgrade: false,
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
perMessageDeflate: {
|
||||||
|
threshold: 1024
|
||||||
|
},
|
||||||
|
transportOptions: {},
|
||||||
|
closeOnBeforeunload: true
|
||||||
|
},
|
||||||
|
opts
|
||||||
|
)
|
||||||
|
|
||||||
|
this.opts.path = this.opts.path.replace(/\/$/, "") + "/"
|
||||||
|
|
||||||
|
if (typeof this.opts.query === "string") {
|
||||||
|
this.opts.query = parseqs.decode(this.opts.query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set on handshake
|
||||||
|
this.id = null
|
||||||
|
this.upgrades = null
|
||||||
|
this.pingInterval = null
|
||||||
|
this.pingTimeout = null
|
||||||
|
|
||||||
|
// set on heartbeat
|
||||||
|
this.pingTimeoutTimer = null
|
||||||
|
|
||||||
|
if (typeof addEventListener === "function") {
|
||||||
|
if (this.opts.closeOnBeforeunload) {
|
||||||
|
// Firefox closes the connection when the "beforeunload" event is emitted but not Chrome. This event listener
|
||||||
|
// ensures every browser behaves the same (no "disconnect" event at the Socket.IO level when the page is
|
||||||
|
// closed/reloaded)
|
||||||
|
addEventListener(
|
||||||
|
"beforeunload",
|
||||||
|
() => {
|
||||||
|
if (this.transport) {
|
||||||
|
// silently close the transport
|
||||||
|
this.transport.removeAllListeners()
|
||||||
|
this.transport.close()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (this.hostname !== "localhost") {
|
||||||
|
this.offlineEventListener = () => {
|
||||||
|
this.onClose("transport close")
|
||||||
|
}
|
||||||
|
addEventListener("offline", this.offlineEventListener, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates transport of the given type.
|
||||||
|
*
|
||||||
|
* @param {String} transport name
|
||||||
|
* @return {Transport}
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
createTransport(name, opt?) {
|
||||||
|
if (name != 'websocket') {
|
||||||
|
throw new Error('Only Support WebSocket in MiaoScript!')
|
||||||
|
}
|
||||||
|
debug('creating transport "%s"', name)
|
||||||
|
const query: any = clone(this.opts.query)
|
||||||
|
|
||||||
|
// append engine.io protocol identifier
|
||||||
|
query.EIO = parser.protocol
|
||||||
|
|
||||||
|
// transport name
|
||||||
|
query.transport = name
|
||||||
|
|
||||||
|
// session id if we already have one
|
||||||
|
if (this.id) query.sid = this.id
|
||||||
|
|
||||||
|
const opts = Object.assign(
|
||||||
|
{},
|
||||||
|
this.opts.transportOptions[name],
|
||||||
|
this.opts,
|
||||||
|
{
|
||||||
|
query,
|
||||||
|
socket: this,
|
||||||
|
hostname: this.hostname,
|
||||||
|
secure: this.secure,
|
||||||
|
port: this.port
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
debug("options: %j", JSON.stringify(opts))
|
||||||
|
debug("new func", transports[name])
|
||||||
|
return new transports[name](opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes transport to use and starts probe.
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
open() {
|
||||||
|
let transport
|
||||||
|
if (
|
||||||
|
this.opts.rememberUpgrade &&
|
||||||
|
Socket.priorWebsocketSuccess &&
|
||||||
|
this.transports.indexOf("websocket") !== -1
|
||||||
|
) {
|
||||||
|
transport = "websocket"
|
||||||
|
} else if (0 === this.transports.length) {
|
||||||
|
// Emit error on next tick so it can be listened to
|
||||||
|
this.setTimeoutFn(() => {
|
||||||
|
this.emit("error", "No transports available")
|
||||||
|
}, 0)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
transport = this.transports[0]
|
||||||
|
}
|
||||||
|
this.readyState = "opening"
|
||||||
|
|
||||||
|
// Retry with the next transport if the transport is disabled (jsonp: false)
|
||||||
|
try {
|
||||||
|
transport = this.createTransport(transport)
|
||||||
|
} catch (e) {
|
||||||
|
debug("error while creating transport: %s", e)
|
||||||
|
this.transports.shift()
|
||||||
|
this.open()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
transport.open()
|
||||||
|
this.setTransport(transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current transport. Disables the existing one (if any).
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
setTransport(transport) {
|
||||||
|
debug("setting transport %s", transport.name)
|
||||||
|
|
||||||
|
if (this.transport) {
|
||||||
|
debug("clearing existing transport %s", this.transport.name)
|
||||||
|
this.transport.removeAllListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up transport
|
||||||
|
this.transport = transport
|
||||||
|
|
||||||
|
// set up transport listeners
|
||||||
|
transport
|
||||||
|
.on("drain", this.onDrain.bind(this))
|
||||||
|
.on("packet", this.onPacket.bind(this))
|
||||||
|
.on("error", this.onError.bind(this))
|
||||||
|
.on("close", () => {
|
||||||
|
this.onClose("transport close")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Probes a transport.
|
||||||
|
*
|
||||||
|
* @param {String} transport name
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
probe(name) {
|
||||||
|
debug('probing transport "%s"', name)
|
||||||
|
let transport = this.createTransport(name, { probe: 1 })
|
||||||
|
let failed = false
|
||||||
|
|
||||||
|
Socket.priorWebsocketSuccess = false
|
||||||
|
|
||||||
|
const onTransportOpen = () => {
|
||||||
|
if (failed) return
|
||||||
|
|
||||||
|
debug('probe transport "%s" opened', name)
|
||||||
|
transport.send([{ type: "ping", data: "probe" }])
|
||||||
|
transport.once("packet", msg => {
|
||||||
|
if (failed) return
|
||||||
|
if ("pong" === msg.type && "probe" === msg.data) {
|
||||||
|
debug('probe transport "%s" pong', name)
|
||||||
|
this.upgrading = true
|
||||||
|
this.emit("upgrading", transport)
|
||||||
|
if (!transport) return
|
||||||
|
Socket.priorWebsocketSuccess = "websocket" === transport.name
|
||||||
|
|
||||||
|
debug('pausing current transport "%s"', this.transport.name)
|
||||||
|
this.transport.pause(() => {
|
||||||
|
if (failed) return
|
||||||
|
if ("closed" === this.readyState) return
|
||||||
|
debug("changing transport and sending upgrade packet")
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
this.setTransport(transport)
|
||||||
|
transport.send([{ type: "upgrade" }])
|
||||||
|
this.emit("upgrade", transport)
|
||||||
|
transport = null
|
||||||
|
this.upgrading = false
|
||||||
|
this.flush()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
debug('probe transport "%s" failed', name)
|
||||||
|
const err: any = new Error("probe error")
|
||||||
|
err.transport = transport.name
|
||||||
|
this.emit("upgradeError", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function freezeTransport() {
|
||||||
|
if (failed) return
|
||||||
|
|
||||||
|
// Any callback called by transport should be ignored since now
|
||||||
|
failed = true
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
transport.close()
|
||||||
|
transport = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle any error that happens while probing
|
||||||
|
const onerror = err => {
|
||||||
|
const error: any = new Error("probe error: " + err)
|
||||||
|
error.transport = transport.name
|
||||||
|
|
||||||
|
freezeTransport()
|
||||||
|
|
||||||
|
debug('probe transport "%s" failed because of error: %s', name, err)
|
||||||
|
|
||||||
|
this.emit("upgradeError", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTransportClose() {
|
||||||
|
onerror("transport closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the socket is closed while we're probing
|
||||||
|
function onclose() {
|
||||||
|
onerror("socket closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the socket is upgraded while we're probing
|
||||||
|
function onupgrade(to) {
|
||||||
|
if (transport && to.name !== transport.name) {
|
||||||
|
debug('"%s" works - aborting "%s"', to.name, transport.name)
|
||||||
|
freezeTransport()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all listeners on the transport and on self
|
||||||
|
const cleanup = () => {
|
||||||
|
transport.removeListener("open", onTransportOpen)
|
||||||
|
transport.removeListener("error", onerror)
|
||||||
|
transport.removeListener("close", onTransportClose)
|
||||||
|
this.removeListener("close", onclose)
|
||||||
|
this.removeListener("upgrading", onupgrade)
|
||||||
|
}
|
||||||
|
|
||||||
|
transport.once("open", onTransportOpen)
|
||||||
|
transport.once("error", onerror)
|
||||||
|
transport.once("close", onTransportClose)
|
||||||
|
|
||||||
|
this.once("close", onclose)
|
||||||
|
this.once("upgrading", onupgrade)
|
||||||
|
|
||||||
|
transport.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when connection is deemed open.
|
||||||
|
*
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
onOpen() {
|
||||||
|
debug("socket open")
|
||||||
|
this.readyState = "open"
|
||||||
|
Socket.priorWebsocketSuccess = "websocket" === this.transport.name
|
||||||
|
this.emit("open")
|
||||||
|
this.flush()
|
||||||
|
|
||||||
|
// we check for `readyState` in case an `open`
|
||||||
|
// listener already closed the socket
|
||||||
|
if (
|
||||||
|
"open" === this.readyState &&
|
||||||
|
this.opts.upgrade &&
|
||||||
|
this.transport.pause
|
||||||
|
) {
|
||||||
|
debug("starting upgrade probes")
|
||||||
|
let i = 0
|
||||||
|
const l = this.upgrades.length
|
||||||
|
for (; i < l; i++) {
|
||||||
|
this.probe(this.upgrades[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a packet.
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
onPacket(packet) {
|
||||||
|
if (
|
||||||
|
"opening" === this.readyState ||
|
||||||
|
"open" === this.readyState ||
|
||||||
|
"closing" === this.readyState
|
||||||
|
) {
|
||||||
|
debug('socket receive: type "%s", data "%s"', packet.type, packet.data)
|
||||||
|
|
||||||
|
this.emit("packet", packet)
|
||||||
|
|
||||||
|
// Socket is live - any packet counts
|
||||||
|
this.emit("heartbeat")
|
||||||
|
|
||||||
|
switch (packet.type) {
|
||||||
|
case "open":
|
||||||
|
this.onHandshake(JSON.parse(packet.data))
|
||||||
|
break
|
||||||
|
|
||||||
|
case "ping":
|
||||||
|
this.resetPingTimeout()
|
||||||
|
this.sendPacket("pong")
|
||||||
|
this.emit("ping")
|
||||||
|
this.emit("pong")
|
||||||
|
break
|
||||||
|
|
||||||
|
case "error":
|
||||||
|
const err: any = new Error("server error")
|
||||||
|
err.code = packet.data
|
||||||
|
this.onError(err)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "message":
|
||||||
|
this.emit("data", packet.data)
|
||||||
|
this.emit("message", packet.data)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug('packet received with socket readyState "%s"', this.readyState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon handshake completion.
|
||||||
|
*
|
||||||
|
* @param {Object} handshake obj
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
onHandshake(data) {
|
||||||
|
this.emit("handshake", data)
|
||||||
|
this.id = data.sid
|
||||||
|
this.transport.query.sid = data.sid
|
||||||
|
this.upgrades = this.filterUpgrades(data.upgrades)
|
||||||
|
this.pingInterval = data.pingInterval
|
||||||
|
this.pingTimeout = data.pingTimeout
|
||||||
|
this.onOpen()
|
||||||
|
// In case open handler closes socket
|
||||||
|
if ("closed" === this.readyState) return
|
||||||
|
this.resetPingTimeout()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets and resets ping timeout timer based on server pings.
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
resetPingTimeout() {
|
||||||
|
this.clearTimeoutFn(this.pingTimeoutTimer)
|
||||||
|
this.pingTimeoutTimer = this.setTimeoutFn(() => {
|
||||||
|
this.onClose("ping timeout")
|
||||||
|
}, this.pingInterval + this.pingTimeout)
|
||||||
|
if (this.opts.autoUnref) {
|
||||||
|
this.pingTimeoutTimer.unref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on `drain` event
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
onDrain() {
|
||||||
|
this.writeBuffer.splice(0, this.prevBufferLen)
|
||||||
|
|
||||||
|
// setting prevBufferLen = 0 is very important
|
||||||
|
// for example, when upgrading, upgrade packet is sent over,
|
||||||
|
// and a nonzero prevBufferLen could cause problems on `drain`
|
||||||
|
this.prevBufferLen = 0
|
||||||
|
|
||||||
|
if (0 === this.writeBuffer.length) {
|
||||||
|
this.emit("drain")
|
||||||
|
} else {
|
||||||
|
this.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush write buffers.
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
flush() {
|
||||||
|
if (
|
||||||
|
"closed" !== this.readyState &&
|
||||||
|
this.transport.writable &&
|
||||||
|
!this.upgrading &&
|
||||||
|
this.writeBuffer.length
|
||||||
|
) {
|
||||||
|
debug("flushing %d packets in socket", this.writeBuffer.length)
|
||||||
|
this.transport.send(this.writeBuffer)
|
||||||
|
// keep track of current length of writeBuffer
|
||||||
|
// splice writeBuffer and callbackBuffer on `drain`
|
||||||
|
this.prevBufferLen = this.writeBuffer.length
|
||||||
|
this.emit("flush")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message.
|
||||||
|
*
|
||||||
|
* @param {String} message.
|
||||||
|
* @param {Function} callback function.
|
||||||
|
* @param {Object} options.
|
||||||
|
* @return {Socket} for chaining.
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
write(msg, options, fn) {
|
||||||
|
this.sendPacket("message", msg, options, fn)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
send(msg, options, fn) {
|
||||||
|
this.sendPacket("message", msg, options, fn)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a packet.
|
||||||
|
*
|
||||||
|
* @param {String} packet type.
|
||||||
|
* @param {String} data.
|
||||||
|
* @param {Object} options.
|
||||||
|
* @param {Function} callback function.
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
sendPacket(type, data?, options?, fn?) {
|
||||||
|
if ("function" === typeof data) {
|
||||||
|
fn = data
|
||||||
|
data = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("function" === typeof options) {
|
||||||
|
fn = options
|
||||||
|
options = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("closing" === this.readyState || "closed" === this.readyState) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {}
|
||||||
|
options.compress = false !== options.compress
|
||||||
|
|
||||||
|
const packet = {
|
||||||
|
type: type,
|
||||||
|
data: data,
|
||||||
|
options: options
|
||||||
|
}
|
||||||
|
this.emit("packetCreate", packet)
|
||||||
|
this.writeBuffer.push(packet)
|
||||||
|
if (fn) this.once("flush", fn)
|
||||||
|
this.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the connection.
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
const close = () => {
|
||||||
|
this.onClose("forced close")
|
||||||
|
debug("socket closing - telling transport to close")
|
||||||
|
this.transport.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanupAndClose = () => {
|
||||||
|
this.removeListener("upgrade", cleanupAndClose)
|
||||||
|
this.removeListener("upgradeError", cleanupAndClose)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const waitForUpgrade = () => {
|
||||||
|
// wait for upgrade to finish since we can't send packets while pausing a transport
|
||||||
|
this.once("upgrade", cleanupAndClose)
|
||||||
|
this.once("upgradeError", cleanupAndClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("opening" === this.readyState || "open" === this.readyState) {
|
||||||
|
this.readyState = "closing"
|
||||||
|
|
||||||
|
if (this.writeBuffer.length) {
|
||||||
|
this.once("drain", () => {
|
||||||
|
if (this.upgrading) {
|
||||||
|
waitForUpgrade()
|
||||||
|
} else {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (this.upgrading) {
|
||||||
|
waitForUpgrade()
|
||||||
|
} else {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon transport error
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
onError(err) {
|
||||||
|
debug("socket error %j", err)
|
||||||
|
Socket.priorWebsocketSuccess = false
|
||||||
|
this.emit("error", err)
|
||||||
|
this.onClose("transport error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon transport close.
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
onClose(reason, desc?) {
|
||||||
|
if (
|
||||||
|
"opening" === this.readyState ||
|
||||||
|
"open" === this.readyState ||
|
||||||
|
"closing" === this.readyState
|
||||||
|
) {
|
||||||
|
debug('socket close with reason: "%s"', reason)
|
||||||
|
|
||||||
|
// clear timers
|
||||||
|
this.clearTimeoutFn(this.pingIntervalTimer)
|
||||||
|
this.clearTimeoutFn(this.pingTimeoutTimer)
|
||||||
|
|
||||||
|
// stop event from firing again for transport
|
||||||
|
this.transport.removeAllListeners("close")
|
||||||
|
|
||||||
|
// ensure transport won't stay open
|
||||||
|
this.transport.close()
|
||||||
|
|
||||||
|
// ignore further transport communication
|
||||||
|
this.transport.removeAllListeners()
|
||||||
|
|
||||||
|
if (typeof removeEventListener === "function") {
|
||||||
|
removeEventListener("offline", this.offlineEventListener, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set ready state
|
||||||
|
this.readyState = "closed"
|
||||||
|
|
||||||
|
// clear session id
|
||||||
|
this.id = null
|
||||||
|
|
||||||
|
// emit close event
|
||||||
|
this.emit("close", reason, desc)
|
||||||
|
|
||||||
|
// clean buffers after, so users can still
|
||||||
|
// grab the buffers on `close` event
|
||||||
|
this.writeBuffer = []
|
||||||
|
this.prevBufferLen = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters upgrades, returning only those matching client transports.
|
||||||
|
*
|
||||||
|
* @param {Array} server upgrades
|
||||||
|
* @api private
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
filterUpgrades(upgrades) {
|
||||||
|
const filteredUpgrades = []
|
||||||
|
let i = 0
|
||||||
|
const j = upgrades.length
|
||||||
|
for (; i < j; i++) {
|
||||||
|
if (~this.transports.indexOf(upgrades[i]))
|
||||||
|
filteredUpgrades.push(upgrades[i])
|
||||||
|
}
|
||||||
|
return filteredUpgrades
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket.priorWebsocketSuccess = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protocol version.
|
||||||
|
*
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
|
||||||
|
Socket.protocol = parser.protocol // this is an int
|
||||||
|
|
||||||
|
function clone(obj) {
|
||||||
|
const o = {}
|
||||||
|
for (let i in obj) {
|
||||||
|
if (obj.hasOwnProperty(i)) {
|
||||||
|
o[i] = obj[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
119
packages/websocket/src/engine.io-client/transport.ts
Normal file
119
packages/websocket/src/engine.io-client/transport.ts
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import parser from "../engine.io-parser"
|
||||||
|
const Emitter = require("component-emitter")
|
||||||
|
import { installTimerFunctions } from "./util"
|
||||||
|
const debug = (...args: any) => console.debug('engine.io-client:transport', ...args)//require("debug")("engine.io-client:transport")
|
||||||
|
|
||||||
|
export class Transport extends Emitter {
|
||||||
|
/**
|
||||||
|
* Transport abstract constructor.
|
||||||
|
*
|
||||||
|
* @param {Object} options.
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
constructor(opts) {
|
||||||
|
super()
|
||||||
|
installTimerFunctions(this, opts)
|
||||||
|
|
||||||
|
this.opts = opts
|
||||||
|
this.query = opts.query
|
||||||
|
this.readyState = ""
|
||||||
|
this.socket = opts.socket
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits an error.
|
||||||
|
*
|
||||||
|
* @param {String} str
|
||||||
|
* @return {Transport} for chaining
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
onError(msg, desc) {
|
||||||
|
const err: any = new Error(msg)
|
||||||
|
err.type = "TransportError"
|
||||||
|
err.description = desc
|
||||||
|
this.emit("error", err)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the transport.
|
||||||
|
*
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
open() {
|
||||||
|
if ("closed" === this.readyState || "" === this.readyState) {
|
||||||
|
this.readyState = "opening"
|
||||||
|
this.doOpen()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the transport.
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
if ("opening" === this.readyState || "open" === this.readyState) {
|
||||||
|
this.doClose()
|
||||||
|
this.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends multiple packets.
|
||||||
|
*
|
||||||
|
* @param {Array} packets
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
send(packets) {
|
||||||
|
if ("open" === this.readyState) {
|
||||||
|
this.write(packets)
|
||||||
|
} else {
|
||||||
|
// this might happen if the transport was silently closed in the beforeunload event handler
|
||||||
|
debug("transport is not open, discarding packets")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon open
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
onOpen() {
|
||||||
|
this.readyState = "open"
|
||||||
|
this.writable = true
|
||||||
|
this.emit("open")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called with data.
|
||||||
|
*
|
||||||
|
* @param {String} data
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
onData(data) {
|
||||||
|
const packet = parser.decodePacket(data, this.socket.binaryType)
|
||||||
|
this.onPacket(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called with a decoded packet.
|
||||||
|
*/
|
||||||
|
onPacket(packet) {
|
||||||
|
this.emit("packet", packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon close.
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
onClose() {
|
||||||
|
this.readyState = "closed"
|
||||||
|
this.emit("close")
|
||||||
|
}
|
||||||
|
}
|
4
packages/websocket/src/engine.io-client/transports/index.ts
Executable file
4
packages/websocket/src/engine.io-client/transports/index.ts
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
import { WS } from "./websocket"
|
||||||
|
export default {
|
||||||
|
'websocket': WS
|
||||||
|
}
|
259
packages/websocket/src/engine.io-client/transports/websocket.ts
Normal file
259
packages/websocket/src/engine.io-client/transports/websocket.ts
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
import { Transport } from '../transport'
|
||||||
|
// const Transport = require("../transport")
|
||||||
|
import parser from '../../engine.io-parser'
|
||||||
|
// const parser = require("../engine.io-parser")
|
||||||
|
const parseqs = require("parseqs")
|
||||||
|
const yeast = require("yeast")
|
||||||
|
import { pick } from '../util'
|
||||||
|
// const { pick } = require("../util")
|
||||||
|
import { WebSocket } from '../../client'
|
||||||
|
const usingBrowserWebSocket = true
|
||||||
|
// const {
|
||||||
|
// WebSocket,
|
||||||
|
// usingBrowserWebSocket,
|
||||||
|
// defaultBinaryType,
|
||||||
|
// nextTick
|
||||||
|
// } = require("./websocket-constructor")
|
||||||
|
|
||||||
|
const debug = (...args: any) => console.debug('engine.io-client:websocket', ...args)//require("debug")("engine.io-client:websocket")
|
||||||
|
|
||||||
|
// detect ReactNative environment
|
||||||
|
const isReactNative =
|
||||||
|
typeof navigator !== "undefined" &&
|
||||||
|
typeof navigator.product === "string" &&
|
||||||
|
navigator.product.toLowerCase() === "reactnative"
|
||||||
|
|
||||||
|
export class WS extends Transport {
|
||||||
|
/**
|
||||||
|
* WebSocket transport constructor.
|
||||||
|
*
|
||||||
|
* @api {Object} connection options
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
constructor(opts) {
|
||||||
|
super(opts)
|
||||||
|
|
||||||
|
this.supportsBinary = !opts.forceBase64
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transport name.
|
||||||
|
*
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
get name() {
|
||||||
|
return "websocket"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens socket.
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
doOpen() {
|
||||||
|
if (!this.check()) {
|
||||||
|
// let probe timeout
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const uri = this.uri()
|
||||||
|
const protocols = this.opts.protocols
|
||||||
|
|
||||||
|
// React Native only supports the 'headers' option, and will print a warning if anything else is passed
|
||||||
|
const opts = isReactNative
|
||||||
|
? {}
|
||||||
|
: pick(
|
||||||
|
this.opts,
|
||||||
|
"agent",
|
||||||
|
"perMessageDeflate",
|
||||||
|
"pfx",
|
||||||
|
"key",
|
||||||
|
"passphrase",
|
||||||
|
"cert",
|
||||||
|
"ca",
|
||||||
|
"ciphers",
|
||||||
|
"rejectUnauthorized",
|
||||||
|
"localAddress",
|
||||||
|
"protocolVersion",
|
||||||
|
"origin",
|
||||||
|
"maxPayload",
|
||||||
|
"family",
|
||||||
|
"checkServerIdentity"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (this.opts.extraHeaders) {
|
||||||
|
opts.headers = this.opts.extraHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.ws = new WebSocket(uri, protocols)
|
||||||
|
// usingBrowserWebSocket && !isReactNative
|
||||||
|
// ? protocols
|
||||||
|
// ? new WebSocket(uri, protocols)
|
||||||
|
// : new WebSocket(uri)
|
||||||
|
// : new WebSocket(uri, protocols, opts)
|
||||||
|
} catch (err) {
|
||||||
|
return this.emit("error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ws.binaryType = this.socket.binaryType || 'arraybuffer'
|
||||||
|
|
||||||
|
this.addEventListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds event listeners to the socket
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
addEventListeners() {
|
||||||
|
this.ws.onopen = () => {
|
||||||
|
if (this.opts.autoUnref) {
|
||||||
|
this.ws._socket.unref()
|
||||||
|
}
|
||||||
|
this.onOpen()
|
||||||
|
}
|
||||||
|
this.ws.onclose = this.onClose.bind(this)
|
||||||
|
this.ws.onmessage = ev => this.onData(ev.data)
|
||||||
|
this.ws.onerror = e => this.onError("websocket error", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes data to socket.
|
||||||
|
*
|
||||||
|
* @param {Array} array of packets.
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
write(packets) {
|
||||||
|
this.writable = false
|
||||||
|
|
||||||
|
// encodePacket efficient as it uses WS framing
|
||||||
|
// no need for encodePayload
|
||||||
|
for (let i = 0; i < packets.length; i++) {
|
||||||
|
const packet = packets[i]
|
||||||
|
const lastPacket = i === packets.length - 1
|
||||||
|
|
||||||
|
parser.encodePacket(packet, this.supportsBinary, data => {
|
||||||
|
// always create a new object (GH-437)
|
||||||
|
const opts: any = {}
|
||||||
|
if (!usingBrowserWebSocket) {
|
||||||
|
if (packet.options) {
|
||||||
|
opts.compress = packet.options.compress
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.opts.perMessageDeflate) {
|
||||||
|
const len =
|
||||||
|
"string" === typeof data ? Buffer.byteLength(data) : data.length
|
||||||
|
if (len < this.opts.perMessageDeflate.threshold) {
|
||||||
|
opts.compress = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sometimes the websocket has already been closed but the browser didn't
|
||||||
|
// have a chance of informing us about it yet, in that case send will
|
||||||
|
// throw an error
|
||||||
|
try {
|
||||||
|
if (usingBrowserWebSocket) {
|
||||||
|
// TypeError is thrown when passing the second argument on Safari
|
||||||
|
this.ws.send(data)
|
||||||
|
} else {
|
||||||
|
this.ws.send(data, opts)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debug("websocket closed before onclose event")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPacket) {
|
||||||
|
// fake drain
|
||||||
|
// defer to next tick to allow Socket to clear writeBuffer
|
||||||
|
process.nextTick(() => {
|
||||||
|
this.writable = true
|
||||||
|
this.emit("drain")
|
||||||
|
}, this.setTimeoutFn)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon close
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
onClose() {
|
||||||
|
Transport.prototype.onClose.call(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes socket.
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
doClose() {
|
||||||
|
if (typeof this.ws !== "undefined") {
|
||||||
|
this.ws.close()
|
||||||
|
this.ws = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates uri for connection.
|
||||||
|
*
|
||||||
|
* @api private
|
||||||
|
*/
|
||||||
|
uri() {
|
||||||
|
let query = this.query || {}
|
||||||
|
const schema = this.opts.secure ? "wss" : "ws"
|
||||||
|
let port = ""
|
||||||
|
|
||||||
|
// avoid port if default for schema
|
||||||
|
if (
|
||||||
|
this.opts.port &&
|
||||||
|
(("wss" === schema && Number(this.opts.port) !== 443) ||
|
||||||
|
("ws" === schema && Number(this.opts.port) !== 80))
|
||||||
|
) {
|
||||||
|
port = ":" + this.opts.port
|
||||||
|
}
|
||||||
|
|
||||||
|
// append timestamp to URI
|
||||||
|
if (this.opts.timestampRequests) {
|
||||||
|
query[this.opts.timestampParam] = yeast()
|
||||||
|
}
|
||||||
|
|
||||||
|
// communicate binary support capabilities
|
||||||
|
if (!this.supportsBinary) {
|
||||||
|
query.b64 = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
query = parseqs.encode(query)
|
||||||
|
|
||||||
|
// prepend ? to query
|
||||||
|
if (query.length) {
|
||||||
|
query = "?" + query
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipv6 = this.opts.hostname.indexOf(":") !== -1
|
||||||
|
return (
|
||||||
|
schema +
|
||||||
|
"://" +
|
||||||
|
(ipv6 ? "[" + this.opts.hostname + "]" : this.opts.hostname) +
|
||||||
|
port +
|
||||||
|
this.opts.path +
|
||||||
|
query
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature detection for WebSocket.
|
||||||
|
*
|
||||||
|
* @return {Boolean} whether this transport is available.
|
||||||
|
* @api public
|
||||||
|
*/
|
||||||
|
check() {
|
||||||
|
return (
|
||||||
|
!!WebSocket &&
|
||||||
|
!("__initialize" in WebSocket && this.name === WS.prototype.name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
23
packages/websocket/src/engine.io-client/util.ts
Normal file
23
packages/websocket/src/engine.io-client/util.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const pick = (obj, ...attr) => {
|
||||||
|
return attr.reduce((acc, k) => {
|
||||||
|
if (obj.hasOwnProperty(k)) {
|
||||||
|
acc[k] = obj[k]
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep a reference to the real timeout functions so they can be used when overridden
|
||||||
|
const NATIVE_SET_TIMEOUT = setTimeout
|
||||||
|
const NATIVE_CLEAR_TIMEOUT = clearTimeout
|
||||||
|
|
||||||
|
const installTimerFunctions = (obj, opts) => {
|
||||||
|
if (opts.useNativeTimers) {
|
||||||
|
obj.setTimeoutFn = NATIVE_SET_TIMEOUT.bind(globalThis)
|
||||||
|
obj.clearTimeoutFn = NATIVE_CLEAR_TIMEOUT.bind(globalThis)
|
||||||
|
} else {
|
||||||
|
obj.setTimeoutFn = setTimeout.bind(globalThis)
|
||||||
|
obj.clearTimeoutFn = clearTimeout.bind(globalThis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export { pick, installTimerFunctions }
|
@ -48,3 +48,4 @@ export * from './socket.io'
|
|||||||
export * from './client'
|
export * from './client'
|
||||||
export * from './server'
|
export * from './server'
|
||||||
export * from './engine.io/transport'
|
export * from './engine.io/transport'
|
||||||
|
export * as client from './socket.io-client'
|
||||||
|
@ -49,9 +49,6 @@ export abstract class WebSocketServer extends EventEmitter {
|
|||||||
this.execute(handler, (websocket) => websocket.emit(ServerEvent.disconnect, cause))
|
this.execute(handler, (websocket) => websocket.emit(ServerEvent.disconnect, cause))
|
||||||
}
|
}
|
||||||
protected onerror(handler: any, error: Error) {
|
protected onerror(handler: any, error: Error) {
|
||||||
if (global.debug) {
|
|
||||||
console.ex(error)
|
|
||||||
}
|
|
||||||
this.execute(handler, (websocket) => websocket.emit(ServerEvent.error, error))
|
this.execute(handler, (websocket) => websocket.emit(ServerEvent.error, error))
|
||||||
}
|
}
|
||||||
protected execute(handler: any, callback: (websocket: WebSocketClient) => void) {
|
protected execute(handler: any, callback: (websocket: WebSocketClient) => void) {
|
||||||
@ -82,9 +79,9 @@ export const attach = (instance, options) => {
|
|||||||
}, options)
|
}, options)
|
||||||
let WebSocketServerImpl = undefined
|
let WebSocketServerImpl = undefined
|
||||||
if (instance.class.name.startsWith('io.netty.channel')) {
|
if (instance.class.name.startsWith('io.netty.channel')) {
|
||||||
WebSocketServerImpl = require("../netty").NettyWebSocketServer
|
WebSocketServerImpl = require("./netty").NettyWebSocketServer
|
||||||
} else {
|
} else {
|
||||||
WebSocketServerImpl = require("../tomcat").TomcatWebSocketServer
|
WebSocketServerImpl = require("./tomcat").TomcatWebSocketServer
|
||||||
}
|
}
|
||||||
return new WebSocketServerImpl(instance, options)
|
return new WebSocketServerImpl(instance, options)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { WebSocketClient } from '../server/client'
|
import { WebSocketClient } from '../client'
|
||||||
|
|
||||||
const TextWebSocketFrame = Java.type('io.netty.handler.codec.http.websocketx.TextWebSocketFrame')
|
const TextWebSocketFrame = Java.type('io.netty.handler.codec.http.websocketx.TextWebSocketFrame')
|
||||||
|
|
@ -1,8 +1,8 @@
|
|||||||
import { JavaServerOptions } from '../server'
|
|
||||||
|
|
||||||
import { HttpRequestHandlerAdapter } from './adapter'
|
import { HttpRequestHandlerAdapter } from './adapter'
|
||||||
import { AttributeKeys } from './constants'
|
import { AttributeKeys } from './constants'
|
||||||
|
|
||||||
|
import type { JavaServerOptions } from '../'
|
||||||
|
|
||||||
const DefaultHttpResponse = Java.type('io.netty.handler.codec.http.DefaultHttpResponse')
|
const DefaultHttpResponse = Java.type('io.netty.handler.codec.http.DefaultHttpResponse')
|
||||||
const DefaultFullHttpResponse = Java.type('io.netty.handler.codec.http.DefaultFullHttpResponse')
|
const DefaultFullHttpResponse = Java.type('io.netty.handler.codec.http.DefaultFullHttpResponse')
|
||||||
const HttpHeaders = Java.type('io.netty.handler.codec.http.HttpHeaders')
|
const HttpHeaders = Java.type('io.netty.handler.codec.http.HttpHeaders')
|
@ -1,11 +1,13 @@
|
|||||||
import { JavaServerOptions, ServerEvent, WebSocketServer } from '../server'
|
import { ServerEvent, WebSocketServer } from '../'
|
||||||
import { Request } from '../server/request'
|
import { Request } from '../request'
|
||||||
|
|
||||||
import { NettyClient } from './client'
|
import { NettyClient } from './client'
|
||||||
import { AttributeKeys, Keys } from './constants'
|
import { AttributeKeys, Keys } from './constants'
|
||||||
import { WebSocketDetect } from './websocket_detect'
|
import { WebSocketDetect } from './websocket_detect'
|
||||||
import { WebSocketHandler } from './websocket_handler'
|
import { WebSocketHandler } from './websocket_handler'
|
||||||
|
|
||||||
|
import type { JavaServerOptions } from '../'
|
||||||
|
|
||||||
class NettyWebSocketServer extends WebSocketServer {
|
class NettyWebSocketServer extends WebSocketServer {
|
||||||
constructor(pipeline: any, options: JavaServerOptions) {
|
constructor(pipeline: any, options: JavaServerOptions) {
|
||||||
super(pipeline, options)
|
super(pipeline, options)
|
@ -1,8 +1,10 @@
|
|||||||
import { EventEmitter } from 'events'
|
import { EventEmitter } from 'events'
|
||||||
import { JavaServerOptions, ServerEvent } from '../server'
|
import { ServerEvent } from '../'
|
||||||
|
|
||||||
import { TextWebSocketFrameHandlerAdapter } from './adapter'
|
import { TextWebSocketFrameHandlerAdapter } from './adapter'
|
||||||
|
|
||||||
|
import type { JavaServerOptions } from '../'
|
||||||
|
|
||||||
export class TextWebSocketFrameHandler extends TextWebSocketFrameHandlerAdapter {
|
export class TextWebSocketFrameHandler extends TextWebSocketFrameHandlerAdapter {
|
||||||
private event: EventEmitter
|
private event: EventEmitter
|
||||||
constructor(options: JavaServerOptions) {
|
constructor(options: JavaServerOptions) {
|
@ -1,7 +1,7 @@
|
|||||||
import { EventEmitter } from 'events'
|
import { EventEmitter } from 'events'
|
||||||
import { WebSocketHandlerAdapter } from "./adapter"
|
import { WebSocketHandlerAdapter } from "./adapter"
|
||||||
|
|
||||||
import { ServerEvent } from '../server'
|
import { ServerEvent } from '../'
|
||||||
|
|
||||||
export class WebSocketDetect extends WebSocketHandlerAdapter {
|
export class WebSocketDetect extends WebSocketHandlerAdapter {
|
||||||
private event: EventEmitter
|
private event: EventEmitter
|
@ -1,10 +1,12 @@
|
|||||||
import { JavaServerOptions, ServerEvent } from '../server'
|
import { ServerEvent } from '../'
|
||||||
|
|
||||||
import { Keys } from './constants'
|
import { Keys } from './constants'
|
||||||
import { HttpRequestHandler } from './httprequest'
|
import { HttpRequestHandler } from './httprequest'
|
||||||
import { WebSocketHandlerAdapter } from "./adapter"
|
import { WebSocketHandlerAdapter } from "./adapter"
|
||||||
import { TextWebSocketFrameHandler } from './text_websocket_frame'
|
import { TextWebSocketFrameHandler } from './text_websocket_frame'
|
||||||
|
|
||||||
|
import type { JavaServerOptions } from '../'
|
||||||
|
|
||||||
const CharsetUtil = Java.type('io.netty.util.CharsetUtil')
|
const CharsetUtil = Java.type('io.netty.util.CharsetUtil')
|
||||||
const HttpServerCodec = Java.type('io.netty.handler.codec.http.HttpServerCodec')
|
const HttpServerCodec = Java.type('io.netty.handler.codec.http.HttpServerCodec')
|
||||||
const ChunkedWriteHandler = Java.type('io.netty.handler.stream.ChunkedWriteHandler')
|
const ChunkedWriteHandler = Java.type('io.netty.handler.stream.ChunkedWriteHandler')
|
@ -1,4 +1,4 @@
|
|||||||
import { WebSocketClient } from '../server/client'
|
import { WebSocketClient } from '../client'
|
||||||
|
|
||||||
export class TomcatClient extends WebSocketClient {
|
export class TomcatClient extends WebSocketClient {
|
||||||
private session: javax.websocket.Session
|
private session: javax.websocket.Session
|
@ -1,5 +1,5 @@
|
|||||||
import { JavaServerOptions, WebSocketServer } from '../server'
|
import { JavaServerOptions, WebSocketServer } from '../'
|
||||||
import { Request } from '../server/request'
|
import { Request } from '../request'
|
||||||
|
|
||||||
import { TomcatClient } from './client'
|
import { TomcatClient } from './client'
|
||||||
import { ProxyBeanName } from './constants'
|
import { ProxyBeanName } from './constants'
|
105
packages/websocket/src/socket.io-client/index.ts
Normal file
105
packages/websocket/src/socket.io-client/index.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { url } from "./url"
|
||||||
|
import { Manager, ManagerOptions } from "./manager"
|
||||||
|
import { Socket, SocketOptions } from "./socket"
|
||||||
|
|
||||||
|
const debug = require("../debug")("socket.io-client")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module exports.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = exports = lookup
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Managers cache.
|
||||||
|
*/
|
||||||
|
const cache: Record<string, Manager> = (exports.managers = {})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up an existing `Manager` for multiplexing.
|
||||||
|
* If the user summons:
|
||||||
|
*
|
||||||
|
* `io('http://localhost/a');`
|
||||||
|
* `io('http://localhost/b');`
|
||||||
|
*
|
||||||
|
* We reuse the existing instance based on same scheme/port/host,
|
||||||
|
* and we initialize sockets for each namespace.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
function lookup(opts?: Partial<ManagerOptions & SocketOptions>): Socket
|
||||||
|
function lookup(
|
||||||
|
uri: string,
|
||||||
|
opts?: Partial<ManagerOptions & SocketOptions>
|
||||||
|
): Socket
|
||||||
|
function lookup(
|
||||||
|
uri: string | Partial<ManagerOptions & SocketOptions>,
|
||||||
|
opts?: Partial<ManagerOptions & SocketOptions>
|
||||||
|
): Socket
|
||||||
|
function lookup(
|
||||||
|
uri: string | Partial<ManagerOptions & SocketOptions>,
|
||||||
|
opts?: Partial<ManagerOptions & SocketOptions>
|
||||||
|
): Socket {
|
||||||
|
if (typeof uri === "object") {
|
||||||
|
opts = uri
|
||||||
|
uri = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = opts || {}
|
||||||
|
|
||||||
|
const parsed = url(uri as string, opts.path || "/socket.io")
|
||||||
|
const source = parsed.source
|
||||||
|
const id = parsed.id
|
||||||
|
const path = parsed.path
|
||||||
|
const sameNamespace = cache[id] && path in cache[id]["nsps"]
|
||||||
|
const newConnection =
|
||||||
|
opts.forceNew ||
|
||||||
|
opts["force new connection"] ||
|
||||||
|
false === opts.multiplex ||
|
||||||
|
sameNamespace
|
||||||
|
|
||||||
|
let io: Manager
|
||||||
|
|
||||||
|
if (newConnection) {
|
||||||
|
debug("ignoring socket cache for %s", source)
|
||||||
|
io = new Manager(source, opts)
|
||||||
|
} else {
|
||||||
|
if (!cache[id]) {
|
||||||
|
debug("new io instance for %s", source)
|
||||||
|
cache[id] = new Manager(source, opts)
|
||||||
|
}
|
||||||
|
io = cache[id]
|
||||||
|
}
|
||||||
|
if (parsed.query && !opts.query) {
|
||||||
|
opts.query = parsed.queryKey
|
||||||
|
}
|
||||||
|
return io.socket(parsed.path, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protocol version.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { protocol } from "../socket.io-parser"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `connect`.
|
||||||
|
*
|
||||||
|
* @param {String} uri
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.connect = lookup
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose constructors for standalone build.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { Manager, ManagerOptions } from "./manager"
|
||||||
|
export { Socket } from "./socket"
|
||||||
|
export { lookup as io, SocketOptions }
|
||||||
|
export default lookup
|
816
packages/websocket/src/socket.io-client/manager.ts
Normal file
816
packages/websocket/src/socket.io-client/manager.ts
Normal file
@ -0,0 +1,816 @@
|
|||||||
|
import eio from "../engine.io-client"
|
||||||
|
import { Socket, SocketOptions } from "./socket"
|
||||||
|
import * as parser from "../socket.io-parser"
|
||||||
|
import { Decoder, Encoder, Packet } from "../socket.io-parser"
|
||||||
|
import { on } from "./on"
|
||||||
|
import * as Backoff from "backo2"
|
||||||
|
import {
|
||||||
|
DefaultEventsMap,
|
||||||
|
EventsMap,
|
||||||
|
StrictEventEmitter,
|
||||||
|
} from "./typed-events"
|
||||||
|
|
||||||
|
const debug = require("../debug")("socket.io-client")
|
||||||
|
|
||||||
|
interface EngineOptions {
|
||||||
|
/**
|
||||||
|
* The host that we're connecting to. Set from the URI passed when connecting
|
||||||
|
*/
|
||||||
|
host: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hostname for our connection. Set from the URI passed when connecting
|
||||||
|
*/
|
||||||
|
hostname: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is a secure connection. Set from the URI passed when connecting
|
||||||
|
*/
|
||||||
|
secure: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The port for our connection. Set from the URI passed when connecting
|
||||||
|
*/
|
||||||
|
port: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any query parameters in our uri. Set from the URI passed when connecting
|
||||||
|
*/
|
||||||
|
query: { [key: string]: string }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `http.Agent` to use, defaults to `false` (NodeJS only)
|
||||||
|
*/
|
||||||
|
agent: string | boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the client should try to upgrade the transport from
|
||||||
|
* long-polling to something better.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
upgrade: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces JSONP for polling transport.
|
||||||
|
*/
|
||||||
|
forceJSONP: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether to use JSONP when necessary for polling. If
|
||||||
|
* disabled (by settings to false) an error will be emitted (saying
|
||||||
|
* "No transports available") if no other transports are available.
|
||||||
|
* If another transport is available for opening a connection (e.g.
|
||||||
|
* WebSocket) that transport will be used instead.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
jsonp: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces base 64 encoding for polling transport even when XHR2
|
||||||
|
* responseType is available and WebSocket even if the used standard
|
||||||
|
* supports binary.
|
||||||
|
*/
|
||||||
|
forceBase64: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables XDomainRequest for IE8 to avoid loading bar flashing with
|
||||||
|
* click sound. default to `false` because XDomainRequest has a flaw
|
||||||
|
* of not sending cookie.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
enablesXDR: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The param name to use as our timestamp key
|
||||||
|
* @default 't'
|
||||||
|
*/
|
||||||
|
timestampParam: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to add the timestamp with each transport request. Note: this
|
||||||
|
* is ignored if the browser is IE or Android, in which case requests
|
||||||
|
* are always stamped
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
timestampRequests: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of transports to try (in order). Engine.io always attempts to
|
||||||
|
* connect directly with the first one, provided the feature detection test
|
||||||
|
* for it passes.
|
||||||
|
* @default ['polling','websocket']
|
||||||
|
*/
|
||||||
|
transports: string[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The port the policy server listens on
|
||||||
|
* @default 843
|
||||||
|
*/
|
||||||
|
policyPost: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true and if the previous websocket connection to the server succeeded,
|
||||||
|
* the connection attempt will bypass the normal upgrade process and will
|
||||||
|
* initially try websocket. A connection attempt following a transport error
|
||||||
|
* will use the normal upgrade process. It is recommended you turn this on
|
||||||
|
* only when using SSL/TLS connections, or if you know that your network does
|
||||||
|
* not block websockets.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
rememberUpgrade: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are we only interested in transports that support binary?
|
||||||
|
*/
|
||||||
|
onlyBinaryUpgrades: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeout for xhr-polling requests in milliseconds (0) (only for polling transport)
|
||||||
|
*/
|
||||||
|
requestTimeout: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transport options for Node.js client (headers etc)
|
||||||
|
*/
|
||||||
|
transportOptions: Object
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (SSL) Certificate, Private key and CA certificates to use for SSL.
|
||||||
|
* Can be used in Node.js client environment to manually specify
|
||||||
|
* certificate information.
|
||||||
|
*/
|
||||||
|
pfx: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (SSL) Private key to use for SSL. Can be used in Node.js client
|
||||||
|
* environment to manually specify certificate information.
|
||||||
|
*/
|
||||||
|
key: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (SSL) A string or passphrase for the private key or pfx. Can be
|
||||||
|
* used in Node.js client environment to manually specify certificate
|
||||||
|
* information.
|
||||||
|
*/
|
||||||
|
passphrase: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (SSL) Public x509 certificate to use. Can be used in Node.js client
|
||||||
|
* environment to manually specify certificate information.
|
||||||
|
*/
|
||||||
|
cert: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (SSL) An authority certificate or array of authority certificates to
|
||||||
|
* check the remote host against.. Can be used in Node.js client
|
||||||
|
* environment to manually specify certificate information.
|
||||||
|
*/
|
||||||
|
ca: string | string[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (SSL) A string describing the ciphers to use or exclude. Consult the
|
||||||
|
* [cipher format list]
|
||||||
|
* (http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT) for
|
||||||
|
* details on the format.. Can be used in Node.js client environment to
|
||||||
|
* manually specify certificate information.
|
||||||
|
*/
|
||||||
|
ciphers: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (SSL) If true, the server certificate is verified against the list of
|
||||||
|
* supplied CAs. An 'error' event is emitted if verification fails.
|
||||||
|
* Verification happens at the connection level, before the HTTP request
|
||||||
|
* is sent. Can be used in Node.js client environment to manually specify
|
||||||
|
* certificate information.
|
||||||
|
*/
|
||||||
|
rejectUnauthorized: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Headers that will be passed for each request to the server (via xhr-polling and via websockets).
|
||||||
|
* These values then can be used during handshake or for special proxies.
|
||||||
|
*/
|
||||||
|
extraHeaders?: { [header: string]: string }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to include credentials (cookies, authorization headers, TLS
|
||||||
|
* client certificates, etc.) with cross-origin XHR polling requests
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
withCredentials: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to automatically close the connection whenever the beforeunload event is received.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
closeOnBeforeunload: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ManagerOptions extends EngineOptions {
|
||||||
|
/**
|
||||||
|
* Should we force a new Manager for this connection?
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
forceNew: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should we multiplex our connection (reuse existing Manager) ?
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
multiplex: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to get our client file from, in the case of the server
|
||||||
|
* serving it
|
||||||
|
* @default '/socket.io'
|
||||||
|
*/
|
||||||
|
path: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should we allow reconnections?
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
reconnection: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many reconnection attempts should we try?
|
||||||
|
* @default Infinity
|
||||||
|
*/
|
||||||
|
reconnectionAttempts: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time delay in milliseconds between reconnection attempts
|
||||||
|
* @default 1000
|
||||||
|
*/
|
||||||
|
reconnectionDelay: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The max time delay in milliseconds between reconnection attempts
|
||||||
|
* @default 5000
|
||||||
|
*/
|
||||||
|
reconnectionDelayMax: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in the exponential backoff jitter when reconnecting
|
||||||
|
* @default 0.5
|
||||||
|
*/
|
||||||
|
randomizationFactor: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timeout in milliseconds for our connection attempt
|
||||||
|
* @default 20000
|
||||||
|
*/
|
||||||
|
timeout: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should we automatically connect?
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
autoConnect: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* weather we should unref the reconnect timer when it is
|
||||||
|
* create automatically
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
autoUnref: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the parser to use. Defaults to an instance of the Parser that ships with socket.io.
|
||||||
|
*/
|
||||||
|
parser: any
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ManagerReservedEvents {
|
||||||
|
open: () => void
|
||||||
|
error: (err: Error) => void
|
||||||
|
ping: () => void
|
||||||
|
packet: (packet: Packet) => void
|
||||||
|
close: (reason: string) => void
|
||||||
|
reconnect_failed: () => void
|
||||||
|
reconnect_attempt: (attempt: number) => void
|
||||||
|
reconnect_error: (err: Error) => void
|
||||||
|
reconnect: (attempt: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Manager<
|
||||||
|
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||||
|
EmitEvents extends EventsMap = ListenEvents
|
||||||
|
> extends StrictEventEmitter<{}, {}, ManagerReservedEvents> {
|
||||||
|
/**
|
||||||
|
* The Engine.IO client instance
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public engine: any
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_autoConnect: boolean
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_readyState: "opening" | "open" | "closed"
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_reconnecting: boolean
|
||||||
|
|
||||||
|
private readonly uri: string
|
||||||
|
public opts: Partial<ManagerOptions>
|
||||||
|
|
||||||
|
private nsps: Record<string, Socket> = {};
|
||||||
|
private subs: Array<ReturnType<typeof on>> = [];
|
||||||
|
private backoff: Backoff
|
||||||
|
private _reconnection: boolean
|
||||||
|
private _reconnectionAttempts: number
|
||||||
|
private _reconnectionDelay: number
|
||||||
|
private _randomizationFactor: number
|
||||||
|
private _reconnectionDelayMax: number
|
||||||
|
private _timeout: any
|
||||||
|
|
||||||
|
private encoder: Encoder
|
||||||
|
private decoder: Decoder
|
||||||
|
private skipReconnect: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `Manager` constructor.
|
||||||
|
*
|
||||||
|
* @param uri - engine instance or engine uri/opts
|
||||||
|
* @param opts - options
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
constructor(opts: Partial<ManagerOptions>)
|
||||||
|
constructor(uri?: string, opts?: Partial<ManagerOptions>)
|
||||||
|
constructor(
|
||||||
|
uri?: string | Partial<ManagerOptions>,
|
||||||
|
opts?: Partial<ManagerOptions>
|
||||||
|
)
|
||||||
|
constructor(
|
||||||
|
uri?: string | Partial<ManagerOptions>,
|
||||||
|
opts?: Partial<ManagerOptions>
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
if (uri && "object" === typeof uri) {
|
||||||
|
opts = uri
|
||||||
|
uri = undefined
|
||||||
|
}
|
||||||
|
opts = opts || {}
|
||||||
|
|
||||||
|
opts.path = opts.path || "/socket.io"
|
||||||
|
this.opts = opts
|
||||||
|
this.reconnection(opts.reconnection !== false)
|
||||||
|
this.reconnectionAttempts(opts.reconnectionAttempts || Infinity)
|
||||||
|
this.reconnectionDelay(opts.reconnectionDelay || 1000)
|
||||||
|
this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000)
|
||||||
|
this.randomizationFactor(opts.randomizationFactor ?? 0.5)
|
||||||
|
this.backoff = new Backoff({
|
||||||
|
min: this.reconnectionDelay(),
|
||||||
|
max: this.reconnectionDelayMax(),
|
||||||
|
jitter: this.randomizationFactor(),
|
||||||
|
})
|
||||||
|
this.timeout(null == opts.timeout ? 20000 : opts.timeout)
|
||||||
|
this._readyState = "closed"
|
||||||
|
this.uri = uri as string
|
||||||
|
const _parser = opts.parser || parser
|
||||||
|
this.encoder = new _parser.Encoder()
|
||||||
|
this.decoder = new _parser.Decoder()
|
||||||
|
this._autoConnect = opts.autoConnect !== false
|
||||||
|
if (this._autoConnect) this.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the `reconnection` config.
|
||||||
|
*
|
||||||
|
* @param {Boolean} v - true/false if it should automatically reconnect
|
||||||
|
* @return {Manager} self or value
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public reconnection(v: boolean): this
|
||||||
|
public reconnection(): boolean
|
||||||
|
public reconnection(v?: boolean): this | boolean
|
||||||
|
public reconnection(v?: boolean): this | boolean {
|
||||||
|
if (!arguments.length) return this._reconnection
|
||||||
|
this._reconnection = !!v
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the reconnection attempts config.
|
||||||
|
*
|
||||||
|
* @param {Number} v - max reconnection attempts before giving up
|
||||||
|
* @return {Manager} self or value
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public reconnectionAttempts(v: number): this
|
||||||
|
public reconnectionAttempts(): number
|
||||||
|
public reconnectionAttempts(v?: number): this | number
|
||||||
|
public reconnectionAttempts(v?: number): this | number {
|
||||||
|
if (v === undefined) return this._reconnectionAttempts
|
||||||
|
this._reconnectionAttempts = v
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the delay between reconnections.
|
||||||
|
*
|
||||||
|
* @param {Number} v - delay
|
||||||
|
* @return {Manager} self or value
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public reconnectionDelay(v: number): this
|
||||||
|
public reconnectionDelay(): number
|
||||||
|
public reconnectionDelay(v?: number): this | number
|
||||||
|
public reconnectionDelay(v?: number): this | number {
|
||||||
|
if (v === undefined) return this._reconnectionDelay
|
||||||
|
this._reconnectionDelay = v
|
||||||
|
this.backoff?.setMin(v)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the randomization factor
|
||||||
|
*
|
||||||
|
* @param v - the randomization factor
|
||||||
|
* @return self or value
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public randomizationFactor(v: number): this
|
||||||
|
public randomizationFactor(): number
|
||||||
|
public randomizationFactor(v?: number): this | number
|
||||||
|
public randomizationFactor(v?: number): this | number {
|
||||||
|
if (v === undefined) return this._randomizationFactor
|
||||||
|
this._randomizationFactor = v
|
||||||
|
this.backoff?.setJitter(v)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum delay between reconnections.
|
||||||
|
*
|
||||||
|
* @param v - delay
|
||||||
|
* @return self or value
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public reconnectionDelayMax(v: number): this
|
||||||
|
public reconnectionDelayMax(): number
|
||||||
|
public reconnectionDelayMax(v?: number): this | number
|
||||||
|
public reconnectionDelayMax(v?: number): this | number {
|
||||||
|
if (v === undefined) return this._reconnectionDelayMax
|
||||||
|
this._reconnectionDelayMax = v
|
||||||
|
this.backoff?.setMax(v)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the connection timeout. `false` to disable
|
||||||
|
*
|
||||||
|
* @param v - connection timeout
|
||||||
|
* @return self or value
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public timeout(v: number | boolean): this
|
||||||
|
public timeout(): number | boolean
|
||||||
|
public timeout(v?: number | boolean): this | number | boolean
|
||||||
|
public timeout(v?: number | boolean): this | number | boolean {
|
||||||
|
if (!arguments.length) return this._timeout
|
||||||
|
this._timeout = v
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts trying to reconnect if reconnection is enabled and we have not
|
||||||
|
* started reconnecting yet
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private maybeReconnectOnOpen() {
|
||||||
|
// Only try to reconnect if it's the first time we're connecting
|
||||||
|
if (
|
||||||
|
!this._reconnecting &&
|
||||||
|
this._reconnection &&
|
||||||
|
this.backoff.attempts === 0
|
||||||
|
) {
|
||||||
|
// keeps reconnection from firing twice for the same reconnection loop
|
||||||
|
this.reconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current transport `socket`.
|
||||||
|
*
|
||||||
|
* @param {Function} fn - optional, callback
|
||||||
|
* @return self
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public open(fn?: (err?: Error) => void): this {
|
||||||
|
debug("readyState %s", this._readyState)
|
||||||
|
if (~this._readyState.indexOf("open")) return this
|
||||||
|
|
||||||
|
debug("opening %s", this.uri)
|
||||||
|
// @ts-ignore
|
||||||
|
this.engine = eio(this.uri, this.opts)
|
||||||
|
const socket = this.engine
|
||||||
|
const self = this
|
||||||
|
this._readyState = "opening"
|
||||||
|
this.skipReconnect = false
|
||||||
|
|
||||||
|
// emit `open`
|
||||||
|
const openSubDestroy = on(socket, "open", function () {
|
||||||
|
self.onopen()
|
||||||
|
fn && fn()
|
||||||
|
})
|
||||||
|
|
||||||
|
// emit `error`
|
||||||
|
const errorSub = on(socket, "error", (err) => {
|
||||||
|
debug("error")
|
||||||
|
self.cleanup()
|
||||||
|
self._readyState = "closed"
|
||||||
|
this.emitReserved("error", err)
|
||||||
|
if (fn) {
|
||||||
|
fn(err)
|
||||||
|
} else {
|
||||||
|
// Only do this if there is no fn to handle the error
|
||||||
|
self.maybeReconnectOnOpen()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (false !== this._timeout) {
|
||||||
|
const timeout = this._timeout
|
||||||
|
debug("connect attempt will timeout after %d", timeout)
|
||||||
|
|
||||||
|
if (timeout === 0) {
|
||||||
|
openSubDestroy() // prevents a race condition with the 'open' event
|
||||||
|
}
|
||||||
|
|
||||||
|
// set timer
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
debug("connect attempt timed out after %d", timeout)
|
||||||
|
openSubDestroy()
|
||||||
|
socket.close()
|
||||||
|
socket.emit("error", new Error("timeout"))
|
||||||
|
}, timeout)
|
||||||
|
|
||||||
|
if (this.opts.autoUnref) {
|
||||||
|
timer.unref()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subs.push(function subDestroy(): void {
|
||||||
|
clearTimeout(timer)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subs.push(openSubDestroy)
|
||||||
|
this.subs.push(errorSub)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for open()
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public connect(fn?: (err?: Error) => void): this {
|
||||||
|
return this.open(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon transport open.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private onopen(): void {
|
||||||
|
debug("open")
|
||||||
|
|
||||||
|
// clear old subs
|
||||||
|
this.cleanup()
|
||||||
|
|
||||||
|
// mark as open
|
||||||
|
this._readyState = "open"
|
||||||
|
this.emitReserved("open")
|
||||||
|
|
||||||
|
// add new subs
|
||||||
|
const socket = this.engine
|
||||||
|
this.subs.push(
|
||||||
|
on(socket, "ping", this.onping.bind(this)),
|
||||||
|
on(socket, "data", this.ondata.bind(this)),
|
||||||
|
on(socket, "error", this.onerror.bind(this)),
|
||||||
|
on(socket, "close", this.onclose.bind(this)),
|
||||||
|
on(this.decoder, "decoded", this.ondecoded.bind(this))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon a ping.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private onping(): void {
|
||||||
|
this.emitReserved("ping")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called with data.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private ondata(data): void {
|
||||||
|
this.decoder.add(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when parser fully decodes a packet.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private ondecoded(packet): void {
|
||||||
|
this.emitReserved("packet", packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon socket error.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private onerror(err): void {
|
||||||
|
debug("error", err)
|
||||||
|
this.emitReserved("error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new socket for the given `nsp`.
|
||||||
|
*
|
||||||
|
* @return {Socket}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public socket(nsp: string, opts?: Partial<SocketOptions>): Socket {
|
||||||
|
let socket = this.nsps[nsp]
|
||||||
|
if (!socket) {
|
||||||
|
socket = new Socket(this, nsp, opts)
|
||||||
|
this.nsps[nsp] = socket
|
||||||
|
}
|
||||||
|
|
||||||
|
return socket
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon a socket close.
|
||||||
|
*
|
||||||
|
* @param socket
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_destroy(socket: Socket): void {
|
||||||
|
const nsps = Object.keys(this.nsps)
|
||||||
|
|
||||||
|
for (const nsp of nsps) {
|
||||||
|
const socket = this.nsps[nsp]
|
||||||
|
|
||||||
|
if (socket.active) {
|
||||||
|
debug("socket %s is still active, skipping close", nsp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a packet.
|
||||||
|
*
|
||||||
|
* @param packet
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_packet(packet: Partial<Packet & { query: string; options: any }>): void {
|
||||||
|
debug("writing packet %j", packet)
|
||||||
|
|
||||||
|
const encodedPackets = this.encoder.encode(packet as Packet)
|
||||||
|
for (let i = 0; i < encodedPackets.length; i++) {
|
||||||
|
this.engine.write(encodedPackets[i], packet.options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up transport subscriptions and packet buffer.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private cleanup(): void {
|
||||||
|
debug("cleanup")
|
||||||
|
|
||||||
|
this.subs.forEach((subDestroy) => subDestroy())
|
||||||
|
this.subs.length = 0
|
||||||
|
|
||||||
|
this.decoder.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the current socket.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_close(): void {
|
||||||
|
debug("disconnect")
|
||||||
|
this.skipReconnect = true
|
||||||
|
this._reconnecting = false
|
||||||
|
if ("opening" === this._readyState) {
|
||||||
|
// `onclose` will not fire because
|
||||||
|
// an open event never happened
|
||||||
|
this.cleanup()
|
||||||
|
}
|
||||||
|
this.backoff.reset()
|
||||||
|
this._readyState = "closed"
|
||||||
|
if (this.engine) this.engine.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for close()
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private disconnect(): void {
|
||||||
|
return this._close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon engine close.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private onclose(reason: string): void {
|
||||||
|
debug("onclose")
|
||||||
|
|
||||||
|
this.cleanup()
|
||||||
|
this.backoff.reset()
|
||||||
|
this._readyState = "closed"
|
||||||
|
this.emitReserved("close", reason)
|
||||||
|
|
||||||
|
if (this._reconnection && !this.skipReconnect) {
|
||||||
|
this.reconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt a reconnection.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private reconnect(): this | void {
|
||||||
|
if (this._reconnecting || this.skipReconnect) return this
|
||||||
|
|
||||||
|
const self = this
|
||||||
|
|
||||||
|
if (this.backoff.attempts >= this._reconnectionAttempts) {
|
||||||
|
debug("reconnect failed")
|
||||||
|
this.backoff.reset()
|
||||||
|
this.emitReserved("reconnect_failed")
|
||||||
|
this._reconnecting = false
|
||||||
|
} else {
|
||||||
|
const delay = this.backoff.duration()
|
||||||
|
debug("will wait %dms before reconnect attempt", delay)
|
||||||
|
|
||||||
|
this._reconnecting = true
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
if (self.skipReconnect) return
|
||||||
|
|
||||||
|
debug("attempting reconnect")
|
||||||
|
this.emitReserved("reconnect_attempt", self.backoff.attempts)
|
||||||
|
|
||||||
|
// check again for the case socket closed in above events
|
||||||
|
if (self.skipReconnect) return
|
||||||
|
|
||||||
|
self.open((err) => {
|
||||||
|
if (err) {
|
||||||
|
debug("reconnect attempt error")
|
||||||
|
self._reconnecting = false
|
||||||
|
self.reconnect()
|
||||||
|
this.emitReserved("reconnect_error", err)
|
||||||
|
} else {
|
||||||
|
debug("reconnect success")
|
||||||
|
self.onreconnect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, delay)
|
||||||
|
|
||||||
|
if (this.opts.autoUnref) {
|
||||||
|
timer.unref()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subs.push(function subDestroy() {
|
||||||
|
clearTimeout(timer)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon successful reconnect.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private onreconnect(): void {
|
||||||
|
const attempt = this.backoff.attempts
|
||||||
|
this._reconnecting = false
|
||||||
|
this.backoff.reset()
|
||||||
|
this.emitReserved("reconnect", attempt)
|
||||||
|
}
|
||||||
|
}
|
14
packages/websocket/src/socket.io-client/on.ts
Normal file
14
packages/websocket/src/socket.io-client/on.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// import type * as Emitter from "component-emitter";
|
||||||
|
import { EventEmitter } from "events"
|
||||||
|
import { StrictEventEmitter } from "./typed-events"
|
||||||
|
|
||||||
|
export function on(
|
||||||
|
obj: EventEmitter | StrictEventEmitter<any, any>,
|
||||||
|
ev: string,
|
||||||
|
fn: (err?: any) => any
|
||||||
|
): VoidFunction {
|
||||||
|
obj.on(ev, fn)
|
||||||
|
return function subDestroy(): void {
|
||||||
|
obj.off(ev, fn)
|
||||||
|
}
|
||||||
|
}
|
558
packages/websocket/src/socket.io-client/socket.ts
Normal file
558
packages/websocket/src/socket.io-client/socket.ts
Normal file
@ -0,0 +1,558 @@
|
|||||||
|
import { Packet, PacketType } from "../socket.io-parser"
|
||||||
|
import { on } from "./on"
|
||||||
|
import { Manager } from "./manager"
|
||||||
|
import {
|
||||||
|
DefaultEventsMap,
|
||||||
|
EventNames,
|
||||||
|
EventParams,
|
||||||
|
EventsMap,
|
||||||
|
StrictEventEmitter,
|
||||||
|
} from "./typed-events"
|
||||||
|
|
||||||
|
const debug = require("../debug")("socket.io-client")
|
||||||
|
|
||||||
|
export interface SocketOptions {
|
||||||
|
/**
|
||||||
|
* the authentication payload sent when connecting to the Namespace
|
||||||
|
*/
|
||||||
|
auth: { [key: string]: any } | ((cb: (data: object) => void) => void)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal events.
|
||||||
|
* These events can't be emitted by the user.
|
||||||
|
*/
|
||||||
|
const RESERVED_EVENTS = Object.freeze({
|
||||||
|
connect: 1,
|
||||||
|
connect_error: 1,
|
||||||
|
disconnect: 1,
|
||||||
|
disconnecting: 1,
|
||||||
|
// EventEmitter reserved events: https://nodejs.org/api/events.html#events_event_newlistener
|
||||||
|
newListener: 1,
|
||||||
|
removeListener: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
interface Flags {
|
||||||
|
compress?: boolean
|
||||||
|
volatile?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SocketReservedEvents {
|
||||||
|
connect: () => void
|
||||||
|
connect_error: (err: Error) => void
|
||||||
|
disconnect: (reason: Socket.DisconnectReason) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Socket<
|
||||||
|
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||||
|
EmitEvents extends EventsMap = ListenEvents
|
||||||
|
> extends StrictEventEmitter<ListenEvents, EmitEvents, SocketReservedEvents> {
|
||||||
|
public readonly io: Manager<ListenEvents, EmitEvents>
|
||||||
|
|
||||||
|
public id: string
|
||||||
|
public connected: boolean
|
||||||
|
public disconnected: boolean
|
||||||
|
|
||||||
|
public auth: { [key: string]: any } | ((cb: (data: object) => void) => void)
|
||||||
|
public receiveBuffer: Array<ReadonlyArray<any>> = [];
|
||||||
|
public sendBuffer: Array<Packet> = [];
|
||||||
|
|
||||||
|
private readonly nsp: string
|
||||||
|
|
||||||
|
private ids: number = 0;
|
||||||
|
private acks: object = {};
|
||||||
|
private flags: Flags = {};
|
||||||
|
private subs?: Array<VoidFunction>
|
||||||
|
private _anyListeners: Array<(...args: any[]) => void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `Socket` constructor.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
constructor(io: Manager, nsp: string, opts?: Partial<SocketOptions>) {
|
||||||
|
super()
|
||||||
|
this.io = io
|
||||||
|
this.nsp = nsp
|
||||||
|
this.ids = 0
|
||||||
|
this.acks = {}
|
||||||
|
this.receiveBuffer = []
|
||||||
|
this.sendBuffer = []
|
||||||
|
this.connected = false
|
||||||
|
this.disconnected = true
|
||||||
|
this.flags = {}
|
||||||
|
if (opts && opts.auth) {
|
||||||
|
this.auth = opts.auth
|
||||||
|
}
|
||||||
|
if (this.io._autoConnect) this.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to open, close and packet events
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private subEvents(): void {
|
||||||
|
if (this.subs) return
|
||||||
|
|
||||||
|
const io = this.io
|
||||||
|
this.subs = [
|
||||||
|
on(io, "open", this.onopen.bind(this)),
|
||||||
|
on(io, "packet", this.onpacket.bind(this)),
|
||||||
|
on(io, "error", this.onerror.bind(this)),
|
||||||
|
on(io, "close", this.onclose.bind(this)),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the Socket will try to reconnect when its Manager connects or reconnects
|
||||||
|
*/
|
||||||
|
public get active(): boolean {
|
||||||
|
return !!this.subs
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Opens" the socket.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public connect(): this {
|
||||||
|
if (this.connected) return this
|
||||||
|
|
||||||
|
this.subEvents()
|
||||||
|
if (!this.io["_reconnecting"]) this.io.open() // ensure open
|
||||||
|
if ("open" === this.io._readyState) this.onopen()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for connect()
|
||||||
|
*/
|
||||||
|
public open(): this {
|
||||||
|
return this.connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a `message` event.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public send(...args: any[]): this {
|
||||||
|
args.unshift("message")
|
||||||
|
// @ts-ignore
|
||||||
|
this.emit.apply(this, args)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override `emit`.
|
||||||
|
* If the event is in `events`, it's emitted normally.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public emit<Ev extends EventNames<EmitEvents>>(
|
||||||
|
ev: Ev,
|
||||||
|
...args: EventParams<EmitEvents, Ev>
|
||||||
|
): this {
|
||||||
|
if (RESERVED_EVENTS.hasOwnProperty(ev)) {
|
||||||
|
throw new Error('"' + ev + '" is a reserved event name')
|
||||||
|
}
|
||||||
|
|
||||||
|
args.unshift(ev)
|
||||||
|
const packet: any = {
|
||||||
|
type: PacketType.EVENT,
|
||||||
|
data: args,
|
||||||
|
}
|
||||||
|
|
||||||
|
packet.options = {}
|
||||||
|
packet.options.compress = this.flags.compress !== false
|
||||||
|
|
||||||
|
// event ack callback
|
||||||
|
if ("function" === typeof args[args.length - 1]) {
|
||||||
|
debug("emitting packet with ack id %d", this.ids)
|
||||||
|
this.acks[this.ids] = args.pop()
|
||||||
|
packet.id = this.ids++
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTransportWritable =
|
||||||
|
this.io.engine &&
|
||||||
|
this.io.engine.transport &&
|
||||||
|
this.io.engine.transport.writable
|
||||||
|
|
||||||
|
const discardPacket =
|
||||||
|
this.flags.volatile && (!isTransportWritable || !this.connected)
|
||||||
|
if (discardPacket) {
|
||||||
|
debug("discard packet as the transport is not currently writable")
|
||||||
|
} else if (this.connected) {
|
||||||
|
this.packet(packet)
|
||||||
|
} else {
|
||||||
|
this.sendBuffer.push(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.flags = {}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a packet.
|
||||||
|
*
|
||||||
|
* @param packet
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private packet(packet: Partial<Packet>): void {
|
||||||
|
packet.nsp = this.nsp
|
||||||
|
this.io._packet(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon engine `open`.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private onopen(): void {
|
||||||
|
debug("transport is open - connecting")
|
||||||
|
if (typeof this.auth == "function") {
|
||||||
|
this.auth((data) => {
|
||||||
|
this.packet({ type: PacketType.CONNECT, data })
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.packet({ type: PacketType.CONNECT, data: this.auth })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon engine or manager `error`.
|
||||||
|
*
|
||||||
|
* @param err
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private onerror(err: Error): void {
|
||||||
|
if (!this.connected) {
|
||||||
|
this.emitReserved("connect_error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon engine `close`.
|
||||||
|
*
|
||||||
|
* @param reason
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private onclose(reason: Socket.DisconnectReason): void {
|
||||||
|
debug("close (%s)", reason)
|
||||||
|
this.connected = false
|
||||||
|
this.disconnected = true
|
||||||
|
delete this.id
|
||||||
|
this.emitReserved("disconnect", reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called with socket packet.
|
||||||
|
*
|
||||||
|
* @param packet
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private onpacket(packet: Packet): void {
|
||||||
|
const sameNamespace = packet.nsp === this.nsp
|
||||||
|
|
||||||
|
if (!sameNamespace) return
|
||||||
|
|
||||||
|
switch (packet.type) {
|
||||||
|
case PacketType.CONNECT:
|
||||||
|
if (packet.data && packet.data.sid) {
|
||||||
|
const id = packet.data.sid
|
||||||
|
this.onconnect(id)
|
||||||
|
} else {
|
||||||
|
this.emitReserved(
|
||||||
|
"connect_error",
|
||||||
|
new Error(
|
||||||
|
"It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
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:
|
||||||
|
const err = new Error(packet.data.message)
|
||||||
|
// @ts-ignore
|
||||||
|
err.data = packet.data.data
|
||||||
|
this.emitReserved("connect_error", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon a server event.
|
||||||
|
*
|
||||||
|
* @param packet
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private onevent(packet: Packet): void {
|
||||||
|
const args: Array<any> = packet.data || []
|
||||||
|
debug("emitting event %j", args)
|
||||||
|
|
||||||
|
if (null != packet.id) {
|
||||||
|
debug("attaching ack callback to event")
|
||||||
|
args.push(this.ack(packet.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.connected) {
|
||||||
|
this.emitEvent(args)
|
||||||
|
} else {
|
||||||
|
this.receiveBuffer.push(Object.freeze(args))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitEvent(args: ReadonlyArray<any>): void {
|
||||||
|
if (this._anyListeners && this._anyListeners.length) {
|
||||||
|
const listeners = this._anyListeners.slice()
|
||||||
|
for (const listener of listeners) {
|
||||||
|
// @ts-ignore
|
||||||
|
listener.apply(this, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
super.emit.apply(this, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces an ack callback to emit with an event.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private ack(id: number): (...args: any[]) => void {
|
||||||
|
const self = this
|
||||||
|
let sent = false
|
||||||
|
return function (...args: any[]) {
|
||||||
|
// prevent double callbacks
|
||||||
|
if (sent) return
|
||||||
|
sent = true
|
||||||
|
debug("sending ack %j", args)
|
||||||
|
|
||||||
|
self.packet({
|
||||||
|
type: PacketType.ACK,
|
||||||
|
id: id,
|
||||||
|
data: args,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon a server acknowlegement.
|
||||||
|
*
|
||||||
|
* @param packet
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private onack(packet: Packet): void {
|
||||||
|
const ack = this.acks[packet.id]
|
||||||
|
if ("function" === typeof ack) {
|
||||||
|
debug("calling ack %s with %j", packet.id, packet.data)
|
||||||
|
ack.apply(this, packet.data)
|
||||||
|
delete this.acks[packet.id]
|
||||||
|
} else {
|
||||||
|
debug("bad ack %s", packet.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon server connect.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private onconnect(id: string): void {
|
||||||
|
debug("socket connected with id %s", id)
|
||||||
|
this.id = id
|
||||||
|
this.connected = true
|
||||||
|
this.disconnected = false
|
||||||
|
this.emitBuffered()
|
||||||
|
this.emitReserved("connect")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit buffered events (received and emitted).
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private emitBuffered(): void {
|
||||||
|
this.receiveBuffer.forEach((args) => this.emitEvent(args))
|
||||||
|
this.receiveBuffer = []
|
||||||
|
|
||||||
|
this.sendBuffer.forEach((packet) => this.packet(packet))
|
||||||
|
this.sendBuffer = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon server disconnect.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private ondisconnect(): void {
|
||||||
|
debug("server disconnect (%s)", this.nsp)
|
||||||
|
this.destroy()
|
||||||
|
this.onclose("io server disconnect")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called upon forced client/server side disconnections,
|
||||||
|
* this method ensures the manager stops tracking us and
|
||||||
|
* that reconnections don't get triggered for this.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private destroy(): void {
|
||||||
|
if (this.subs) {
|
||||||
|
// clean subscriptions to avoid reconnections
|
||||||
|
this.subs.forEach((subDestroy) => subDestroy())
|
||||||
|
this.subs = undefined
|
||||||
|
}
|
||||||
|
this.io["_destroy"](this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects the socket manually.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public disconnect(): this {
|
||||||
|
if (this.connected) {
|
||||||
|
debug("performing disconnect (%s)", this.nsp)
|
||||||
|
this.packet({ type: PacketType.DISCONNECT })
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove socket from pool
|
||||||
|
this.destroy()
|
||||||
|
|
||||||
|
if (this.connected) {
|
||||||
|
// fire events
|
||||||
|
this.onclose("io client disconnect")
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for disconnect()
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public close(): this {
|
||||||
|
return this.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the compress flag.
|
||||||
|
*
|
||||||
|
* @param compress - if `true`, compresses the sending data
|
||||||
|
* @return self
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public compress(compress: boolean): this {
|
||||||
|
this.flags.compress = compress
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not
|
||||||
|
* ready to send messages.
|
||||||
|
*
|
||||||
|
* @returns self
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
public get volatile(): this {
|
||||||
|
this.flags.volatile = true
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @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 || []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Socket {
|
||||||
|
export type DisconnectReason =
|
||||||
|
| "io server disconnect"
|
||||||
|
| "io client disconnect"
|
||||||
|
| "ping timeout"
|
||||||
|
| "transport close"
|
||||||
|
| "transport error"
|
||||||
|
}
|
157
packages/websocket/src/socket.io-client/typed-events.ts
Normal file
157
packages/websocket/src/socket.io-client/typed-events.ts
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
super.on(ev as string, listener)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
super.once(ev as string, listener)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits an event.
|
||||||
|
*
|
||||||
|
* @param ev Name of the event
|
||||||
|
* @param args Values to send to listeners of this event
|
||||||
|
*/
|
||||||
|
// @ts-ignore
|
||||||
|
emit<Ev extends EventNames<EmitEvents>>(
|
||||||
|
ev: Ev,
|
||||||
|
...args: EventParams<EmitEvents, Ev>
|
||||||
|
): this {
|
||||||
|
super.emit(ev as string, ...args)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
): this {
|
||||||
|
super.emit(ev as string, ...args)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 string) as ReservedOrUserListener<
|
||||||
|
ReservedEvents,
|
||||||
|
ListenEvents,
|
||||||
|
Ev
|
||||||
|
>[]
|
||||||
|
}
|
||||||
|
}
|
97
packages/websocket/src/socket.io-client/url.ts
Normal file
97
packages/websocket/src/socket.io-client/url.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import * as parseuri from "parseuri"
|
||||||
|
|
||||||
|
const debug = require("../debug")("socket.io-client")
|
||||||
|
|
||||||
|
type ParsedUrl = {
|
||||||
|
source: string
|
||||||
|
protocol: string
|
||||||
|
authority: string
|
||||||
|
userInfo: string
|
||||||
|
user: string
|
||||||
|
password: string
|
||||||
|
host: string
|
||||||
|
port: string
|
||||||
|
relative: string
|
||||||
|
path: string
|
||||||
|
directory: string
|
||||||
|
file: string
|
||||||
|
query: string
|
||||||
|
anchor: string
|
||||||
|
pathNames: Array<string>
|
||||||
|
queryKey: { [key: string]: string }
|
||||||
|
|
||||||
|
// Custom properties (not native to parseuri):
|
||||||
|
id: string
|
||||||
|
href: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL parser.
|
||||||
|
*
|
||||||
|
* @param uri - url
|
||||||
|
* @param path - the request path of the connection
|
||||||
|
* @param loc - An object meant to mimic window.location.
|
||||||
|
* Defaults to window.location.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function url(
|
||||||
|
uri: string | ParsedUrl,
|
||||||
|
path: string = "",
|
||||||
|
loc?: Location
|
||||||
|
): ParsedUrl {
|
||||||
|
let obj = uri as ParsedUrl
|
||||||
|
|
||||||
|
// default to window.location
|
||||||
|
loc = loc || (typeof location !== "undefined" && location)
|
||||||
|
if (null == uri) uri = loc.protocol + "//" + loc.host
|
||||||
|
|
||||||
|
// relative path support
|
||||||
|
if (typeof uri === "string") {
|
||||||
|
if ("/" === uri.charAt(0)) {
|
||||||
|
if ("/" === uri.charAt(1)) {
|
||||||
|
uri = loc.protocol + uri
|
||||||
|
} else {
|
||||||
|
uri = loc.host + uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^(https?|wss?):\/\//.test(uri)) {
|
||||||
|
debug("protocol-less url %s", uri)
|
||||||
|
if ("undefined" !== typeof loc) {
|
||||||
|
uri = loc.protocol + "//" + uri
|
||||||
|
} else {
|
||||||
|
uri = "https://" + uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse
|
||||||
|
debug("parse %s", uri)
|
||||||
|
obj = parseuri(uri) as ParsedUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we treat `localhost:80` and `localhost` equally
|
||||||
|
if (!obj.port) {
|
||||||
|
if (/^(http|ws)$/.test(obj.protocol)) {
|
||||||
|
obj.port = "80"
|
||||||
|
} else if (/^(http|ws)s$/.test(obj.protocol)) {
|
||||||
|
obj.port = "443"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.path = obj.path || "/"
|
||||||
|
|
||||||
|
const ipv6 = obj.host.indexOf(":") !== -1
|
||||||
|
const host = ipv6 ? "[" + obj.host + "]" : obj.host
|
||||||
|
|
||||||
|
// define unique id
|
||||||
|
obj.id = obj.protocol + "://" + host + ":" + obj.port + path
|
||||||
|
// define href
|
||||||
|
obj.href =
|
||||||
|
obj.protocol +
|
||||||
|
"://" +
|
||||||
|
host +
|
||||||
|
(loc && loc.port === obj.port ? "" : ":" + obj.port)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}
|
@ -2,6 +2,9 @@
|
|||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
"outDir": "dist"
|
"outDir": "dist",
|
||||||
|
"lib": [
|
||||||
|
"dom"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user