Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 002f2c47c6 | |||
| 8633d9ea95 | |||
| 07a5d0c8de | |||
| dd76e563c8 | |||
| 7b85ff5b7c | |||
| 359aeb9d63 | |||
| 3901d9fb5f | |||
| c2867da047 | |||
| 65832c9fae | |||
| d9dffa704d | |||
| b39f29de6a | |||
| 56334c6f6e | |||
| 72673b2a67 | |||
| 597bdb721a | |||
| 6e0456d777 | |||
| fb67b06230 | |||
| 203560dcf2 | |||
| 3f58f5992c | |||
| ac16754c9c | |||
| 42d637dd63 | |||
| df0d246136 | |||
| e563e1b507 | |||
| 3b822c613a | |||
| 2967c2a1fe | |||
| 1c579c9789 | |||
| 2fe9bce2ea | |||
| 496d278a93 | |||
| bdf674b678 | |||
| 67fe13deac |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
.yarn*
|
||||
.vscode
|
||||
.theia
|
||||
node_modules
|
||||
|
||||
26
lerna.json
26
lerna.json
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"version": "0.24.0",
|
||||
"useWorkspaces": true,
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"command": {
|
||||
"run": {
|
||||
"stream": true
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"version": "0.27.6",
|
||||
"useWorkspaces": true,
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"command": {
|
||||
"run": {
|
||||
"stream": true
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
|
||||
10
package.json
10
package.json
@@ -12,14 +12,16 @@
|
||||
"build": "lerna run build",
|
||||
"ug": "yarn upgrade-interactive --latest",
|
||||
"np": "./script/push.sh",
|
||||
"lsp": "npm login --registry=https://registry.npmjs.org --scope=@ccms",
|
||||
"lp": "lerna publish --registry https://registry.npmjs.org",
|
||||
"lpb": "lerna publish --registry https://registry.npmjs.org --canary --preid beta --pre-dist-tag beta"
|
||||
"lsp": "npm login -scope=@ccms",
|
||||
"lp": "lerna publish --verify-access --force-publish",
|
||||
"lpb": "lerna publish --preid beta --dist-tag beta --verify-access --force-publish",
|
||||
"lpc": "lerna publish --canary --preid beta --pre-dist-tag beta --verify-access --force-publish",
|
||||
"lpf": "lerna publish from-package --yes"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"lerna": "^4.0.0"
|
||||
"lerna": "^6.4.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/amqp",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript amqp package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -19,17 +19,17 @@
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/api": "^0.24.0",
|
||||
"@ccms/common": "^0.24.0",
|
||||
"@ccms/container": "^0.24.0"
|
||||
"@ccms/api": "^0.27.6",
|
||||
"@ccms/common": "^0.27.6",
|
||||
"@ccms/container": "^0.27.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ccms/nashorn": "^0.24.0",
|
||||
"@ccms/nashorn": "^0.27.6",
|
||||
"@javatypes/amqp-client": "^0.0.3",
|
||||
"@javatypes/spring-amqp": "^0.0.3",
|
||||
"@javatypes/spring-rabbit": "^0.0.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/api",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript api package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -19,16 +19,16 @@
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/common": "^0.24.0",
|
||||
"@ccms/container": "^0.24.0",
|
||||
"@ccms/polyfill": "^0.24.0",
|
||||
"@ccms/common": "^0.27.6",
|
||||
"@ccms/container": "^0.27.6",
|
||||
"@ccms/polyfill": "^0.27.6",
|
||||
"base64-js": "^1.5.1",
|
||||
"source-map-builder": "^0.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/base64-js": "^1.3.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,13 +44,13 @@ export namespace jsconsole {
|
||||
let file = Paths.get(Paths.get(fileName, '..', sourceMappingURL).toFile().getCanonicalPath()).toFile()
|
||||
if (file.exists()) {
|
||||
sourceContent = base.read(file)
|
||||
sourceFileMaps[fileName] = file.getCanonicalPath()
|
||||
} else if (global.debug) {
|
||||
console.debug('readSourceMap can\'t found', fileName, 'source map file', sourceMappingURL)
|
||||
}
|
||||
}
|
||||
if (sourceContent) {
|
||||
sourceMaps[fileName] = new SourceMapBuilder(JSON.parse(sourceContent))
|
||||
sourceFileMaps[fileName] = Paths.get(fileName, '..', sourceMaps[fileName].sources[0]).toFile().getCanonicalPath()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,9 +88,9 @@ export namespace jsconsole {
|
||||
let { fileName, lineNumber } = readSourceMap(trace.fileName, trace.lineNumber)
|
||||
if (fileName.startsWith(root)) { fileName = fileName.split(root)[1] }
|
||||
if (color) {
|
||||
cache.push(` §e->§c ${fileName}:${lineNumber} => §4${trace.methodName}`)
|
||||
cache.push(` §e->§c ${fileName}:${lineNumber}(${trace.lineNumber}) => §4${trace.methodName}`)
|
||||
} else {
|
||||
cache.push(` -> ${fileName}:${lineNumber} => ${trace.methodName}`)
|
||||
cache.push(` -> ${fileName}:${lineNumber}(${trace.lineNumber}) => ${trace.methodName}`)
|
||||
}
|
||||
} else {
|
||||
let className = trace.className
|
||||
|
||||
@@ -30,6 +30,10 @@ export namespace database {
|
||||
* 链接属性
|
||||
*/
|
||||
properties?: { [key: string]: any }
|
||||
/**
|
||||
* 调试模式
|
||||
*/
|
||||
debug?: boolean
|
||||
}
|
||||
|
||||
@injectable()
|
||||
|
||||
@@ -26,6 +26,7 @@ export namespace event {
|
||||
|
||||
private mapEvent = [];
|
||||
private listenerMap = [];
|
||||
private cacheSlowEventKey = {};
|
||||
|
||||
protected baseEventDir = '';
|
||||
|
||||
@@ -92,17 +93,23 @@ export namespace event {
|
||||
return eventCls
|
||||
}
|
||||
|
||||
execute(name, exec, eventCls) {
|
||||
/**
|
||||
* 创建命令执行器
|
||||
* @param name 插件名称
|
||||
* @param exec 执行方法
|
||||
* @param eventCls 事件类
|
||||
* @returns
|
||||
*/
|
||||
createExecute(name, exec, eventCls) {
|
||||
return (...args: any[]) => {
|
||||
let event = args[args.length - 1]
|
||||
try {
|
||||
let event = args[args.length - 1]
|
||||
if (eventCls.isAssignableFrom(event.getClass())) {
|
||||
let time = Date.now()
|
||||
exec(event)
|
||||
let cost = Date.now() - time
|
||||
if (cost > global.ScriptSlowExecuteTime) {
|
||||
console.i18n("ms.api.event.execute.slow", { name, event: this.class2Name(eventCls), cost })
|
||||
}
|
||||
if (!eventCls.isAssignableFrom(event.getClass())) { return }
|
||||
let time = Date.now(); exec(event); let cost = Date.now() - time
|
||||
if (cost > global.ScriptSlowExecuteTime && !event.async) {
|
||||
let eventKey = `${name}-${this.class2Name(eventCls)}`
|
||||
if (!this.cacheSlowEventKey[eventKey]) { return this.cacheSlowEventKey[eventKey] = cost }
|
||||
console.i18n("ms.api.event.execute.slow", { name, event: this.class2Name(eventCls), cost })
|
||||
}
|
||||
} catch (ex: any) {
|
||||
console.i18n("ms.api.event.execute.error", { name, event: this.class2Name(eventCls), ex })
|
||||
@@ -113,11 +120,11 @@ export namespace event {
|
||||
|
||||
/**
|
||||
* 添加事件监听
|
||||
* @param plugin {any}
|
||||
* @param event {string}
|
||||
* @param exec {function}
|
||||
* @param priority {string} [LOWEST,LOW,NORMAL,HIGH,HIGHEST,MONITOR]
|
||||
* @param ignoreCancel
|
||||
* @param plugin {any} 插件
|
||||
* @param event {string} 事件名称
|
||||
* @param exec {function} 事件执行器
|
||||
* @param priority {string} [LOWEST,LOW,NORMAL,HIGH,HIGHEST,MONITOR] 优先级
|
||||
* @param ignoreCancel 是否忽略已取消事件
|
||||
*/
|
||||
listen(plugin: any, event: string, exec: (event: any) => void, priority: EventPriority = EventPriority.NORMAL, ignoreCancel = false) {
|
||||
if (!plugin || !plugin.description || !plugin.description.name) throw new TypeError(i18n.translate("ms.api.event.listen.plugin.name.empty"))
|
||||
@@ -135,7 +142,7 @@ export namespace event {
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
var listener = this.register(
|
||||
eventCls,
|
||||
this.execute(name, exec, eventCls),
|
||||
this.createExecute(name, exec, eventCls),
|
||||
priority,
|
||||
ignoreCancel
|
||||
)
|
||||
@@ -171,7 +178,7 @@ export namespace event {
|
||||
disable(plugin: any) {
|
||||
var eventCache = this.listenerMap[plugin.description.name]
|
||||
if (eventCache) {
|
||||
eventCache.forEach(off => off())
|
||||
eventCache.forEach((off: () => any) => off())
|
||||
delete this.listenerMap[plugin.description.name]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export namespace item {
|
||||
export abstract class Item {
|
||||
abstract builder(): ItemBuilder
|
||||
abstract toJson(item: any): string
|
||||
abstract fromJSON(json: string): any
|
||||
abstract fromJson(json: string): any
|
||||
}
|
||||
export interface ItemBuilder {
|
||||
from(item: any): ItemBuilder
|
||||
|
||||
@@ -6,6 +6,11 @@ const UUID = Java.type('java.util.UUID')
|
||||
const Math = Java.type('java.lang.Math')
|
||||
|
||||
export namespace particle {
|
||||
@injectable()
|
||||
export abstract class ParticleSpawner {
|
||||
abstract spawn(location: any, particle: Particle)
|
||||
abstract spawnToPlayer(player: any, location: any, particle: Particle)
|
||||
}
|
||||
/**
|
||||
* 表示一个特效对象
|
||||
*
|
||||
@@ -23,6 +28,11 @@ export namespace particle {
|
||||
private extra: number = 0;
|
||||
private data: Object = null;
|
||||
|
||||
/**
|
||||
* Only Show To Player
|
||||
*/
|
||||
private player: any
|
||||
|
||||
constructor() {
|
||||
this.uuid = UUID.randomUUID().toString()
|
||||
}
|
||||
@@ -105,6 +115,15 @@ export namespace particle {
|
||||
return this
|
||||
}
|
||||
|
||||
getPlayer() {
|
||||
return this.player
|
||||
}
|
||||
|
||||
setPlayer(player) {
|
||||
this.player = player
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过给定一个坐标就可以使用已经指定的参数来播放粒子
|
||||
*
|
||||
@@ -112,9 +131,14 @@ export namespace particle {
|
||||
*/
|
||||
spawn(location: any) {
|
||||
if (!this.spawner) throw new Error(`particle ${this.uuid} not set spawner can't spawn!`)
|
||||
this.spawner.spawn(location, this)
|
||||
if (this.player) {
|
||||
this.spawner.spawnToPlayer(this.player, location, this)
|
||||
} else {
|
||||
this.spawner.spawn(location, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示一条线
|
||||
*
|
||||
@@ -158,8 +182,7 @@ export namespace particle {
|
||||
|
||||
show() {
|
||||
for (let i = 0; i < this.length; i += this.step) {
|
||||
let vectorTemp = this.vector.clone().multiply(i)
|
||||
this.spawn(this.start.clone().add(vectorTemp))
|
||||
this.spawn(this.start.clone().add(this.vector.clone().multiply(i)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,15 +257,6 @@ export namespace particle {
|
||||
this.length = this.vector.length()
|
||||
this.vector.normalize()
|
||||
}
|
||||
|
||||
public static buildLine(locA: any, locB: any, step: number, particle: any) {
|
||||
let vectorAB = locB.clone().subtract(locA).toVector()
|
||||
let vectorLength = vectorAB.length()
|
||||
vectorAB.normalize()
|
||||
for (let i = 0; i < vectorLength; i += step) {
|
||||
ParticleManager.globalSpawner.spawn(locA.clone().add(vectorAB.clone().multiply(i)), particle)
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 表示一个弧
|
||||
@@ -331,9 +345,10 @@ export namespace particle {
|
||||
|
||||
@injectable()
|
||||
export abstract class ParticleManager {
|
||||
public static globalSpawner: ParticleSpawner = undefined
|
||||
@Autowired()
|
||||
private taskManager: task.TaskManager
|
||||
@Autowired()
|
||||
private particleSpawner: particle.ParticleSpawner
|
||||
|
||||
protected taskId: java.util.concurrent.atomic.AtomicInteger
|
||||
protected cacheTasks = new Map<string, ParticleTask>()
|
||||
@@ -354,6 +369,10 @@ export namespace particle {
|
||||
return this.taskManager
|
||||
}
|
||||
|
||||
public getParticleSpawner() {
|
||||
return this.particleSpawner
|
||||
}
|
||||
|
||||
public create(particle: Particle, plugin?: plugin.Plugin) {
|
||||
let uuid = particle.getUUID()
|
||||
if (this.cacheTasks.has(uuid)) {
|
||||
@@ -389,14 +408,12 @@ export namespace particle {
|
||||
}
|
||||
}
|
||||
protected create0(owner: plugin.Plugin, particle: Particle): ParticleTask {
|
||||
particle.setSpawner(this.getGlobalSpawner())
|
||||
particle.setSpawner(this.getParticleSpawner())
|
||||
return new ParticleTask(owner, particle, this)
|
||||
}
|
||||
protected abstract getGlobalSpawner(): ParticleSpawner
|
||||
}
|
||||
|
||||
export class ParticleTask {
|
||||
|
||||
private particle: Particle
|
||||
private isAsync: boolean = false
|
||||
private interval: number = 0
|
||||
@@ -486,9 +503,4 @@ export namespace particle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ParticleSpawner {
|
||||
abstract spawnParticle(location: any, particle: any, count: number)
|
||||
abstract spawn(location: any, particle: Particle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ export namespace server {
|
||||
origin: any
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export abstract class NativePluginManager {
|
||||
list(): NativePlugin[] {
|
||||
@@ -53,6 +54,7 @@ export namespace server {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MiaoScript Server
|
||||
*/
|
||||
@@ -98,6 +100,7 @@ export namespace server {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ServerChecker {
|
||||
@Autowired(ServerType)
|
||||
@@ -116,6 +119,22 @@ export namespace server {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class NativePluginChecker {
|
||||
@Autowired(NativePluginManager)
|
||||
private nativePluginManager: NativePluginManager
|
||||
|
||||
check(plugins: string[]) {
|
||||
// Not set plugins -> allow
|
||||
if (!plugins || !plugins.length) return true
|
||||
for (const plugin of plugins) {
|
||||
if (!this.nativePluginManager.has(plugin)) { return false }
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export abstract class ReflectServer extends server.Server {
|
||||
@Autowired(ContainerInstance)
|
||||
@@ -185,15 +204,25 @@ export namespace server {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.rootLogger.class.name.indexOf('slf4j') !== -1) {
|
||||
try {
|
||||
let LogManager = Java.type('org.apache.logging.log4j.LogManager')
|
||||
this.rootLogger = LogManager.getLogger('ROOT')
|
||||
} catch (error: any) {
|
||||
if (global.debug) {
|
||||
console.ex(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.rootLogger && this.rootLogger.class.name.indexOf('Logger') === -1) {
|
||||
console.error('Error Logger Class: ' + this.rootLogger.class.name)
|
||||
this.rootLogger = undefined
|
||||
}
|
||||
if (!this.rootLogger) { console.error("Can't found rootLogger!") }
|
||||
// get root logger
|
||||
for (let index = 0; index < 5 && this.rootLogger.parent; index++) {
|
||||
this.rootLogger = this.rootLogger.parent
|
||||
}
|
||||
if (!this.rootLogger) { console.error("Can't found rootLogger!") }
|
||||
this.container.bind(constants.ServiceIdentifier.RootLogger).toConstantValue(this.rootLogger)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/bukkit",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript bukkit package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -21,12 +21,12 @@
|
||||
"devDependencies": {
|
||||
"@javatypes/spigot-api": "^0.0.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/api": "^0.24.0",
|
||||
"@ccms/common": "^0.24.0",
|
||||
"@ccms/container": "^0.24.0"
|
||||
"@ccms/api": "^0.27.6",
|
||||
"@ccms/common": "^0.27.6",
|
||||
"@ccms/container": "^0.27.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,16 +161,17 @@ class BukkitChatInvoke_1_17_1 extends BukkitChatInvoke_1_16_5 {
|
||||
return base.getClass('net.minecraft.network.protocol.Packet')
|
||||
}
|
||||
}
|
||||
|
||||
class BukkitChatInvoke_1_19 extends BukkitChatInvoke_1_17_1 {
|
||||
class BukkitChatInvoke_1_18_2 extends BukkitChatInvoke_1_17_1 {
|
||||
getSendPacketMethodName(playerConnectionClass: any) {
|
||||
return playerConnectionClass.getMethod('a', this.getPacketClass()).getName()
|
||||
}
|
||||
}
|
||||
class BukkitChatInvoke_1_19 extends BukkitChatInvoke_1_18_2 {
|
||||
getPacketPlayOutChatClass() {
|
||||
return base.getClass('net.minecraft.network.protocol.game.ClientboundSystemChatPacket')
|
||||
}
|
||||
getPacketPlayOutChat(sender: any, json: any, type: number) {
|
||||
return new this.PacketPlayOutChat(this.ChatSerializer[this.nmsChatSerializerMethodName](json), type == 0 ? 1 : type)
|
||||
return new this.PacketPlayOutChat(this.ChatSerializer[this.nmsChatSerializerMethodName](json), type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +182,8 @@ try {
|
||||
let nmsSubVersion = nmsVersion.split("_")[1]
|
||||
if (nmsSubVersion >= 19) {
|
||||
bukkitChatInvoke = new BukkitChatInvoke_1_19(nmsVersion)
|
||||
} else if (nmsSubVersion >= 18) {
|
||||
bukkitChatInvoke = new BukkitChatInvoke_1_18_2(nmsVersion)
|
||||
} else if (nmsSubVersion >= 17) {
|
||||
bukkitChatInvoke = new BukkitChatInvoke_1_17_1(nmsVersion)
|
||||
} else if (nmsSubVersion >= 16) {
|
||||
|
||||
@@ -12,6 +12,7 @@ export class BukkitItem extends item.Item {
|
||||
private NBTTagCompound: any
|
||||
private nmsSaveNBTMethodName: any
|
||||
private MojangsonParser: any
|
||||
private nmsItemStack: any
|
||||
private mpParseMethodName: any
|
||||
private nmsVersion: any
|
||||
constructor() {
|
||||
@@ -25,8 +26,8 @@ export class BukkitItem extends item.Item {
|
||||
let nbt = new this.NBTTagCompound()
|
||||
return this.CraftItemStack.asNMSCopy(item)[this.nmsSaveNBTMethodName](nbt).toString()
|
||||
}
|
||||
fromJSON(json: string) {
|
||||
return this.CraftItemStack.asBukkitCopy(this.MojangsonParser[this.mpParseMethodName](json))
|
||||
fromJson(json: string) {
|
||||
return this.CraftItemStack.asBukkitCopy(new this.nmsItemStack(this.MojangsonParser[this.mpParseMethodName](json)))
|
||||
}
|
||||
private obcCls(name: string) {
|
||||
return base.getClass(['org.bukkit.craftbukkit', this.nmsVersion, name].join('.'))
|
||||
@@ -44,6 +45,7 @@ export class BukkitItem extends item.Item {
|
||||
// @ts-ignore
|
||||
let asNMSCopyMethod = CraftItemStackClass.getMethod('asNMSCopy', ItemStack.class)
|
||||
let nmsItemStackClass = asNMSCopyMethod.getReturnType()
|
||||
this.nmsItemStack = Java.type(nmsItemStackClass.getName())
|
||||
let nmsNBTTagCompoundClass = undefined
|
||||
for (let method of Java.from(nmsItemStackClass.getMethods())) {
|
||||
let rt = method.getReturnType()
|
||||
@@ -61,7 +63,7 @@ export class BukkitItem extends item.Item {
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.MojangsonParser = this.nmsCls('MojangsonParser')
|
||||
this.MojangsonParser = this.nmsCls('MojangsonParser').static
|
||||
} catch (error) {
|
||||
this.MojangsonParser = Java.type('net.minecraft.nbt.MojangsonParser')
|
||||
}
|
||||
|
||||
@@ -3,20 +3,11 @@ import { particle } from '@ccms/api'
|
||||
|
||||
@provideSingleton(particle.ParticleManager)
|
||||
export class BukkitParticleManager extends particle.ParticleManager {
|
||||
private globalSpawner = new BukkitParticleSpawner()
|
||||
constructor() {
|
||||
super()
|
||||
particle.ParticleManager.globalSpawner = this.globalSpawner
|
||||
}
|
||||
protected getGlobalSpawner() {
|
||||
return this.globalSpawner
|
||||
}
|
||||
}
|
||||
|
||||
@provideSingleton(particle.ParticleSpawner)
|
||||
export class BukkitParticleSpawner extends particle.ParticleSpawner {
|
||||
spawnParticle(location: any, particle: any, count: number = 1) {
|
||||
location.getWorld().spawnParticle(particle, location, count)
|
||||
}
|
||||
spawn(location: any, particle: particle.Particle) {
|
||||
spawn(location: org.bukkit.Location, particle: particle.Particle) {
|
||||
location.getWorld().spawnParticle(
|
||||
particle.getParticle(),
|
||||
location,
|
||||
@@ -28,4 +19,15 @@ export class BukkitParticleSpawner extends particle.ParticleSpawner {
|
||||
particle.getData()
|
||||
)
|
||||
}
|
||||
spawnToPlayer(player: org.bukkit.entity.Player, location: org.bukkit.Location, particle: particle.Particle) {
|
||||
player.spawnParticle(
|
||||
particle.getParticle(),
|
||||
location,
|
||||
particle.getCount(),
|
||||
particle.getOffsetX(),
|
||||
particle.getOffsetY(),
|
||||
particle.getOffsetZ(),
|
||||
particle.getExtra(),
|
||||
particle.getData())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/bungee",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript bungee package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -21,12 +21,12 @@
|
||||
"devDependencies": {
|
||||
"@javatypes/bungee-api": "^0.0.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/api": "^0.24.0",
|
||||
"@ccms/common": "^0.24.0",
|
||||
"@ccms/container": "^0.24.0"
|
||||
"@ccms/api": "^0.27.6",
|
||||
"@ccms/common": "^0.27.6",
|
||||
"@ccms/container": "^0.27.6"
|
||||
}
|
||||
}
|
||||
|
||||
1
packages/client/.gitignore
vendored
1
packages/client/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
src/emp.ts
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "@ccms/client",
|
||||
"version": "0.24.0",
|
||||
"description": "MiaoScript client package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
"minecraft",
|
||||
"bukkit",
|
||||
"sponge"
|
||||
],
|
||||
"author": "MiaoWoo <admin@yumc.pw>",
|
||||
"homepage": "https://github.com/circlecloud/ms.git",
|
||||
"license": "ISC",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "ts-node-dev --respawn --debounce=1500 src/index.ts",
|
||||
"clean": "rimraf dist",
|
||||
"watch": "tsc --watch",
|
||||
"build": "yarn clean && tsc",
|
||||
"start": "node dist/index.js",
|
||||
"debug": "DEBUG=minecraft-protocol node dist/index.js",
|
||||
"emp": "node dist/emp.js",
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
"minecraft-protocol": "^1.34.0",
|
||||
"minecraft-protocol-forge": "^1.0.0",
|
||||
"proxy-agent": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.25",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
class MessagePart {
|
||||
text: string
|
||||
color: string
|
||||
clickEvent: MessagePartEvent
|
||||
hoverEvent: MessagePartEvent
|
||||
translate: string
|
||||
with: MessagePart[]
|
||||
extra: MessagePart[]
|
||||
}
|
||||
|
||||
class MessagePartEvent {
|
||||
action: string
|
||||
value: string
|
||||
}
|
||||
|
||||
var colorMap = []
|
||||
colorMap['0'] = '38;5;0'
|
||||
colorMap['1'] = '38;5;4'
|
||||
colorMap['2'] = '38;5;2'
|
||||
colorMap['3'] = '38;5;6'
|
||||
colorMap['4'] = '38;5;1'
|
||||
colorMap['5'] = '38;5;5'
|
||||
colorMap['6'] = '38;5;3'
|
||||
colorMap['7'] = '38;5;7'
|
||||
colorMap['8'] = '38;5;8'
|
||||
colorMap['9'] = '38;5;12'
|
||||
colorMap['a'] = '38;5;10'
|
||||
colorMap['b'] = '38;5;14'
|
||||
colorMap['c'] = '38;5;9'
|
||||
colorMap['d'] = '38;5;13'
|
||||
colorMap['e'] = '38;5;11'
|
||||
colorMap['f'] = '38;5;15'
|
||||
colorMap['r'] = '0'
|
||||
colorMap['l'] = '1'
|
||||
colorMap['n'] = '4'
|
||||
var regexMap = []
|
||||
for (const c in colorMap) {
|
||||
regexMap[colorMap[c]] = new RegExp(`§${c}`, "g")
|
||||
}
|
||||
function mcColor2ANSI(str) {
|
||||
for (const regex in regexMap) {
|
||||
str = str.replace(regexMap[regex], `\u001b[${regex}m`)
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
let jsonColorMap = {
|
||||
"black": '0',
|
||||
"dark_blue": '1',
|
||||
"dark_green": '2',
|
||||
"dark_aqua": '3',
|
||||
"dark_red": '4',
|
||||
"dark_purple": '5',
|
||||
"gold": '6',
|
||||
"gray": '7',
|
||||
"dark_gray": '8',
|
||||
"blue": '9',
|
||||
"green": 'a',
|
||||
"aqua": 'b',
|
||||
"red": 'c',
|
||||
"light_purple": 'd',
|
||||
"yellow": 'e',
|
||||
"white": 'f',
|
||||
"obfuscated": 'k',
|
||||
"bold": 'l',
|
||||
"strikethrough": 'm',
|
||||
"underline": 'n',
|
||||
"italic": 'o',
|
||||
"reset": 'r',
|
||||
};
|
||||
|
||||
function json2text(json: MessagePart): string {
|
||||
let temp = "";
|
||||
if (json.color) {
|
||||
temp += `§${jsonColorMap[json.color]}`
|
||||
}
|
||||
temp += json.text || json.translate || ''
|
||||
if (json.extra) {
|
||||
json.extra.forEach((ext) => {
|
||||
temp += json2text(ext)
|
||||
})
|
||||
}
|
||||
return temp += '§r'
|
||||
}
|
||||
|
||||
function $(input: any) {
|
||||
if (typeof input === "string") {
|
||||
input = JSON.parse(input)
|
||||
}
|
||||
input = json2text(input) + '§r'
|
||||
if (input.startsWith('§卐')) {
|
||||
input = input.substring(2)
|
||||
}
|
||||
return mcColor2ANSI(input)
|
||||
}
|
||||
|
||||
export {
|
||||
json2text,
|
||||
mcColor2ANSI,
|
||||
$
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { $ } from './color'
|
||||
|
||||
export function attachEvents(client) {
|
||||
client.on('chat', (packet) => {
|
||||
// Listen for chat messages and echo them back.
|
||||
var jsonMsg = JSON.parse(packet.message)
|
||||
console.log($(jsonMsg))
|
||||
})
|
||||
client.on('state', (newState, oldState) => {
|
||||
console.log('Client Change State', oldState, 'to', newState)
|
||||
let targetServer = process.argv[6]
|
||||
if (newState == "play" && targetServer) {
|
||||
setTimeout(() => {
|
||||
client.write('chat', {
|
||||
message: '/server ' + targetServer
|
||||
})
|
||||
}, 3000)
|
||||
}
|
||||
})
|
||||
client.on('update_health', (packet) => {
|
||||
if (packet.health <= 0) {
|
||||
console.log("Player Dead Auto Respawn...")
|
||||
client.write('client_command', { payload: 0 })
|
||||
} else if (packet.health > 0) {
|
||||
}
|
||||
})
|
||||
client.on('kick_disconnect', (packet) => {
|
||||
console.log($(packet.reason))
|
||||
})
|
||||
client.on('disconnect', (packet) => {
|
||||
console.log($(packet.reason))
|
||||
})
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
export function attachForge(client) {
|
||||
client.on('custom_payload', function(packet) {
|
||||
if (packet.channel === 'FML|HS') {
|
||||
client.write('custom_payload', {
|
||||
channel: 'FML|HS',
|
||||
data: Buffer.of(0x01, 0x02)
|
||||
});
|
||||
client.write('custom_payload', {
|
||||
channel: 'FML|HS',
|
||||
data: Buffer.of(0x02, 0x00)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
import { createInterface } from 'readline'
|
||||
import { Client, createClient } from 'minecraft-protocol'
|
||||
|
||||
import { attachForge } from './forge'
|
||||
import { attachEvents } from './event'
|
||||
|
||||
let readUserInfo = process.argv[2] || 'Mr_jtb'
|
||||
let realUserInfo = readUserInfo.split(":")
|
||||
let username = realUserInfo[0]
|
||||
let password = realUserInfo[1] || ''
|
||||
let version = process.argv[3] || '1.12.2'
|
||||
let readAddress = process.argv[4] || '192.168.2.25:25565'
|
||||
let realAddress = readAddress.split(":")
|
||||
let address = realAddress[0]
|
||||
let port = parseInt(realAddress[1] || "25565")
|
||||
let client = commandLineCreateClient()
|
||||
|
||||
function commandLineCreateClient() {
|
||||
return createConnection(address, port, username, password)
|
||||
}
|
||||
|
||||
function createConnection(host: string, port: number, username: string, password: string) {
|
||||
let clientOptions: any = {
|
||||
version,
|
||||
host,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
// clientToken: 'd02c7f39-2376-45da-a5a5-50e24fa8b185',
|
||||
//@ts-ignore
|
||||
// authServer: 'https://skin.yumc.pw/api/yggdrasil/authserver',
|
||||
// sessionServer: 'https://skin.yumc.pw/api/yggdrasil/sessionserver'
|
||||
}
|
||||
if (clientOptions.password) {
|
||||
clientOptions.clientToken = 'd02c7f39-2376-45da-a5a5-50e24fa8b185'
|
||||
clientOptions.authServer = 'https://skin.yumc.pw/api/yggdrasil/authserver'
|
||||
clientOptions.sessionServer = 'https://skin.yumc.pw/api/yggdrasil/sessionserver'
|
||||
}
|
||||
let client = createClient(clientOptions)
|
||||
|
||||
attachCommon(client)
|
||||
attachForge(client)
|
||||
attachEvents(client)
|
||||
return client
|
||||
}
|
||||
|
||||
function attachCommon(client: Client) {
|
||||
client.on('login', () => {
|
||||
// client.registerChannel('updater', ['string', []])
|
||||
// client.registerChannel('updater-enabled', ['string', []])
|
||||
// client.registerChannel('dragoncore', ['string', []])
|
||||
// client.registerChannel('dragoncore:main', ['string', []])
|
||||
client.on('REGISTER', (array) => {
|
||||
for (const channel of array) {
|
||||
client.on('channel', console.log)
|
||||
}
|
||||
})
|
||||
// client.on('dragoncore:main', (data) => {
|
||||
// console.log(data)
|
||||
// })
|
||||
})
|
||||
client.on('custom_payload', (data) => {
|
||||
console.log('custom_payload' + JSON.stringify(data))
|
||||
})
|
||||
client.on('error', (error) => {
|
||||
console.log("Client Error", error)
|
||||
})
|
||||
client.on('end', (resone) => {
|
||||
console.log("Client End Resone:", resone)
|
||||
if (`${resone}` != "SocketClosed") {
|
||||
setTimeout(() => {
|
||||
client = commandLineCreateClient()
|
||||
}, 500)
|
||||
} else {
|
||||
process.exit(0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const rl = createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
completer: (line, func) => {
|
||||
let args = line.split(' ')
|
||||
let comp = args[args.length - 1]
|
||||
client.once('tab_complete', (msg) => {
|
||||
let mcts = msg.matches.filter(s => s)
|
||||
func(null, [mcts, comp])
|
||||
})
|
||||
client.write('tab_complete', {
|
||||
text: line
|
||||
})
|
||||
},
|
||||
terminal: true,
|
||||
prompt: ''
|
||||
})
|
||||
|
||||
rl.on('line', function (line) {
|
||||
switch (line) {
|
||||
case "":
|
||||
break
|
||||
case "eval":
|
||||
break
|
||||
case "write":
|
||||
break
|
||||
case "/respawn":
|
||||
client.write('client_command', { payload: 0 })
|
||||
break
|
||||
case "//reco":
|
||||
client.end("")
|
||||
client = commandLineCreateClient()
|
||||
break
|
||||
case "//quit":
|
||||
console.info('Disconnected')
|
||||
client.end("")
|
||||
break
|
||||
case "//end":
|
||||
console.info('Forcibly ended client')
|
||||
process.exit(0)
|
||||
default:
|
||||
client.write('chat', { message: line })
|
||||
}
|
||||
rl.prompt()
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
while :; do
|
||||
yarn emp
|
||||
echo 进程退出 休眠120秒!
|
||||
sleep 120
|
||||
done
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/common",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript common package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -19,11 +19,11 @@
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ccms/nashorn": "^0.24.0",
|
||||
"@ccms/nashorn": "^0.27.6",
|
||||
"@javatypes/jdk": "^0.0.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"gitHead": "562e2d00175c9d3a99c8b672aa07e6d92706a027"
|
||||
}
|
||||
|
||||
@@ -19,12 +19,20 @@ interface RequestConfig {
|
||||
method?: Method
|
||||
headers?: { [key: string]: string }
|
||||
params?: { [key: string]: string }
|
||||
data?: any
|
||||
data?: any,
|
||||
connectTimeout?: number,
|
||||
readTimeout?: number,
|
||||
}
|
||||
|
||||
function request(config: RequestConfig) {
|
||||
// @ts-ignore XMLHttpRequest class only exist nashorn polyfill
|
||||
let xhr = new XMLHttpRequest()
|
||||
if (config.connectTimeout) {
|
||||
xhr.connectTimeout = config.connectTimeout
|
||||
}
|
||||
if (config.readTimeout) {
|
||||
xhr.readTimeout = config.readTimeout
|
||||
}
|
||||
xhr.open(config.method, config.url, false)
|
||||
for (const header in config.headers) {
|
||||
xhr.setRequestHeader(header, config.headers[header])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/compile",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript compile package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/container",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript container package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -19,10 +19,10 @@
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ccms/nashorn": "^0.24.0",
|
||||
"@ccms/nashorn": "^0.27.6",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"inversify": "^6.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/core",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript core package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -20,12 +20,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/api": "^0.24.0",
|
||||
"@ccms/container": "^0.24.0"
|
||||
"@ccms/api": "^0.27.6",
|
||||
"@ccms/container": "^0.27.6"
|
||||
},
|
||||
"gitHead": "781524f83e52cad26d7c480513e3c525df867121"
|
||||
}
|
||||
|
||||
1
packages/core/src/.gitignore
vendored
Normal file
1
packages/core/src/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
script
|
||||
@@ -9,6 +9,7 @@ import * as fs from '@ccms/common/dist/fs'
|
||||
import { VersionUtils } from '@ccms/common/dist/version'
|
||||
|
||||
const UUID = Java.type('java.util.UUID')
|
||||
const MiaoScriptAPI = Java.type('pw.yumc.MiaoScript.api.MiaoScriptAPI')
|
||||
|
||||
@provideSingleton(MiaoScriptCore)
|
||||
class MiaoScriptCore {
|
||||
@@ -24,6 +25,10 @@ class MiaoScriptCore {
|
||||
enable() {
|
||||
process.emit('core.before.enable')
|
||||
this.loadServerConsole()
|
||||
try {
|
||||
MiaoScriptAPI.setPluginManager(this.pluginManager)
|
||||
} catch (error) {
|
||||
}
|
||||
this.loadPlugins()
|
||||
process.emit('core.after.enable')
|
||||
console.i18n("ms.core.engine.completed", {
|
||||
@@ -105,7 +110,7 @@ function loadCoreScript(name) {
|
||||
try {
|
||||
let scriptname = name + (global.debug ? '-debug' : '')
|
||||
engineLoad({
|
||||
script: http.get(`https://ms.yumc.pw/api/plugin/download/name/${scriptname}`),
|
||||
script: http.get(`https://mscript.yumc.pw/api/plugin/download/name/${scriptname}`),
|
||||
name: `core/${scriptname}.js`
|
||||
})
|
||||
} catch (error: any) {
|
||||
@@ -141,6 +146,7 @@ function createCore() {
|
||||
container.bind(server.ServerType).toConstantValue(type)
|
||||
container.bind(server.ServerChecker).toSelf().inSingletonScope()
|
||||
container.bind(server.NativePluginManager).toSelf().inSingletonScope()
|
||||
container.bind(server.NativePluginChecker).toSelf().inSingletonScope()
|
||||
process.emit('core.after.initialize.detect')
|
||||
|
||||
process.emit('core.before.package.initialize')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/database",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript database package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -21,11 +21,11 @@
|
||||
"devDependencies": {
|
||||
"@javatypes/spring-jdbc": "^0.0.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/api": "^0.24.0",
|
||||
"@ccms/container": "^0.24.0"
|
||||
"@ccms/api": "^0.27.6",
|
||||
"@ccms/container": "^0.27.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/i18n",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript i18n package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -19,11 +19,11 @@
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ccms/nashorn": "^0.24.0",
|
||||
"@ccms/nashorn": "^0.27.6",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"js-yaml": "^4.1.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/keyvalue",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript keyvalue package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -19,18 +19,18 @@
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/api": "^0.24.0",
|
||||
"@ccms/common": "^0.24.0",
|
||||
"@ccms/container": "^0.24.0"
|
||||
"@ccms/api": "^0.27.6",
|
||||
"@ccms/common": "^0.27.6",
|
||||
"@ccms/container": "^0.27.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ccms/nashorn": "^0.24.0",
|
||||
"@ccms/nashorn": "^0.27.6",
|
||||
"@javatypes/amqp-client": "^0.0.3",
|
||||
"@javatypes/spring-amqp": "^0.0.3",
|
||||
"@javatypes/spring-rabbit": "^0.0.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"gitHead": "2589633069d24f646ac09261b1b2304c21d4ea75"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/molang",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "A fast parser for Minecraft's MoLang",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -21,9 +21,9 @@
|
||||
},
|
||||
"homepage": "https://github.com/solvedDev/MoLang#readme",
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.25",
|
||||
"rimraf": "^3.0.2",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.6.3"
|
||||
"@types/node": "^18.13.0",
|
||||
"rimraf": "^4.1.2",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/nashorn",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript nashorn package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,18 @@ declare global {
|
||||
* 引擎渠道
|
||||
*/
|
||||
ScriptEngineChannel: string
|
||||
/**
|
||||
* 慢执行检测时间
|
||||
*/
|
||||
ScriptSlowExecuteTime: number
|
||||
ScriptEngineStartTime: number
|
||||
/**
|
||||
* 设置全局对象
|
||||
* @param key 对象名称
|
||||
* @param value 对象值
|
||||
* @param config 对象属性
|
||||
* @returns
|
||||
*/
|
||||
setGlobal: (key: string, value: any, config?: PropertyDescriptor & ThisType<any>) => void
|
||||
noop: () => void
|
||||
console: Console
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/nodejs",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript nodejs package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -19,11 +19,11 @@
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ccms/nashorn": "^0.24.0",
|
||||
"@ccms/nashorn": "^0.27.6",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"gitHead": "781524f83e52cad26d7c480513e3c525df867121"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/nukkit",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript nukkit package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -21,12 +21,12 @@
|
||||
"devDependencies": {
|
||||
"@javatypes/nukkit-api": "^0.0.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/api": "^0.24.0",
|
||||
"@ccms/common": "^0.24.0",
|
||||
"@ccms/container": "^0.24.0"
|
||||
"@ccms/api": "^0.27.6",
|
||||
"@ccms/common": "^0.27.6",
|
||||
"@ccms/container": "^0.27.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/plugin",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript plugin package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -22,15 +22,15 @@
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/api": "^0.24.0",
|
||||
"@ccms/common": "^0.24.0",
|
||||
"@ccms/container": "^0.24.0",
|
||||
"@ccms/i18n": "^0.24.0",
|
||||
"@ccms/verify": "^0.21.1",
|
||||
"@ccms/api": "^0.27.6",
|
||||
"@ccms/common": "^0.27.6",
|
||||
"@ccms/container": "^0.27.6",
|
||||
"@ccms/i18n": "^0.27.6",
|
||||
"@ccms/verify": "^0.25.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"js-yaml": "^4.1.0"
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export class PluginCommandManager {
|
||||
permission = `${pluginInstance.description.name.toLocaleLowerCase()}.${command}.${subcommand || 'main'}`
|
||||
}
|
||||
if (!sender.hasPermission(permission)) {
|
||||
return pluginInstance.logger.sender(sender, `§c你需要 ${permission} 权限 才可执行此命令.`)
|
||||
return pluginInstance.logger.sender(sender, `§c你需要 §4${permission} §c权限 才可执行此命令.`)
|
||||
}
|
||||
}
|
||||
args.shift()
|
||||
@@ -98,7 +98,7 @@ export class PluginCommandManager {
|
||||
return (args.length == 1 ? cmdSubCache : []).concat(originCompleter?.apply(pluginInstance, [sender, command, args]) || [])
|
||||
}
|
||||
}
|
||||
if (!cmdCompleter) { console.warn(`[${pluginInstance.description.name}] command ${cmd.name} is not registry tabCompleter`) }
|
||||
if (!cmdCompleter) { console.debug(`[${pluginInstance.description.name}] command ${cmd.name} is not registry tabCompleter`) }
|
||||
return [cmdExecutor, cmdCompleter]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ import { interfaces } from './interfaces'
|
||||
import { getPluginConfigMetadata } from './utils'
|
||||
|
||||
import { PluginConfigLoader } from './config/interfaces'
|
||||
import './config/loader/json-loader'
|
||||
import './config/loader/yaml-loader'
|
||||
import './config/loader'
|
||||
|
||||
@provideSingleton(PluginConfigManager)
|
||||
export class PluginConfigManager {
|
||||
@@ -68,30 +67,25 @@ export class PluginConfigManager {
|
||||
private loadConfig0(plugin: plugin.Plugin, metadata: interfaces.ConfigMetadata) {
|
||||
try {
|
||||
let defaultValue = metadata.default ?? plugin[metadata.variable]
|
||||
let configValue = defaultValue || {}
|
||||
if (defaultValue) {
|
||||
metadata.file = fs.concat(
|
||||
fs.file(plugin.description.loadMetadata.file).parent,
|
||||
plugin.description.name,
|
||||
metadata.filename
|
||||
)
|
||||
let configLoader = this.getConfigLoader(metadata.format)
|
||||
if (!fs.exists(metadata.file)) {
|
||||
base.save(metadata.file, configLoader.dump(defaultValue))
|
||||
console.i18n("ms.plugin.manager.config.save.default", {
|
||||
plugin: plugin.description.name,
|
||||
name: metadata.name,
|
||||
format: metadata.format
|
||||
})
|
||||
} else if (metadata.migrate) {
|
||||
configValue = configLoader.load(base.read(metadata.file)) || {}
|
||||
if (defaultValue && this.setDefaultValue(configValue, defaultValue, !!metadata.default)) {
|
||||
base.save(metadata.file, configLoader.dump(configValue))
|
||||
}
|
||||
console.debug(`[${plugin.description.name}] Load Config ${metadata.variable} from file ${metadata.file} =>
|
||||
${JSON.stringify(configValue, undefined, 4).substring(0, 500)}`)
|
||||
}
|
||||
metadata.file = fs.concat(
|
||||
fs.file(plugin.description.loadMetadata.file).parent,
|
||||
plugin.description.name,
|
||||
metadata.filename
|
||||
)
|
||||
let configLoader = this.getConfigLoader(metadata.format)
|
||||
if (!fs.exists(metadata.file) && defaultValue) {
|
||||
base.save(metadata.file, configLoader.dump(defaultValue))
|
||||
console.i18n("ms.plugin.manager.config.save.default", {
|
||||
plugin: plugin.description.name,
|
||||
name: metadata.name,
|
||||
format: metadata.format
|
||||
})
|
||||
}
|
||||
let configValue = configLoader.load(base.read(metadata.file)) || {}
|
||||
if (metadata.migrate && defaultValue && this.setDefaultValue(configValue, defaultValue, !!metadata.default)) {
|
||||
base.save(metadata.file, configLoader.dump(configValue))
|
||||
}
|
||||
console.debug(`[${plugin.description.name}] Load Config ${metadata.variable} from file ${metadata.file}`)
|
||||
this.defienConfigProp(plugin, metadata, configValue)
|
||||
} catch (error: any) {
|
||||
console.i18n("ms.plugin.manager.config.load.error", {
|
||||
|
||||
@@ -5,9 +5,11 @@ import { getPluginListenerMetadata } from './utils'
|
||||
@provideSingleton(PluginEventManager)
|
||||
export class PluginEventManager {
|
||||
@Autowired()
|
||||
private EventManager: event.Event
|
||||
private eventManager: event.Event
|
||||
@Autowired()
|
||||
private ServerChecker: server.ServerChecker
|
||||
private serverChecker: server.ServerChecker
|
||||
@Autowired()
|
||||
private nativePluginChecker: server.NativePluginChecker
|
||||
|
||||
constructor() {
|
||||
process.on('plugin.before.enable', this.registryListener.bind(this))
|
||||
@@ -15,26 +17,31 @@ export class PluginEventManager {
|
||||
}
|
||||
|
||||
mapEventName() {
|
||||
return this.EventManager.mapEventName().toFixed(0)
|
||||
return this.eventManager.mapEventName().toFixed(0)
|
||||
}
|
||||
|
||||
private registryListener(pluginInstance: plugin.Plugin) {
|
||||
let events = getPluginListenerMetadata(pluginInstance)
|
||||
for (const event of events) {
|
||||
// ignore space listener
|
||||
if (!this.ServerChecker.check(event.servers)) {
|
||||
console.debug(`[${pluginInstance.description.name}] ${event.target.constructor.name} incompatible event ${event.name} server(${event.servers}) ignore.`)
|
||||
if (!this.serverChecker.check(event.servers)) {
|
||||
console.debug(`[${pluginInstance.description.name}] ${event.target.constructor.name} incompatible server(${event.servers}) ignore event ${event.name}.`)
|
||||
continue
|
||||
}
|
||||
// ignore space listener
|
||||
if (!this.nativePluginChecker.check(event.plugins)) {
|
||||
console.debug(`[${pluginInstance.description.name}] ${event.target.constructor.name} require native plugins(${event.plugins}) ignore event ${event.name}.`)
|
||||
continue
|
||||
}
|
||||
// here must bind this to pluginInstance
|
||||
let exec = event.target[event.executor]
|
||||
let execBinded = exec.bind(pluginInstance)
|
||||
execBinded.executor = event.executor
|
||||
exec.off = this.EventManager.listen(pluginInstance, event.name, execBinded, event.priority, event.ignoreCancel)
|
||||
exec.off = this.eventManager.listen(pluginInstance, event.name, execBinded, event.priority, event.ignoreCancel)
|
||||
}
|
||||
}
|
||||
|
||||
private unregistryListener(pluginInstance: plugin.Plugin) {
|
||||
this.EventManager.disable(pluginInstance)
|
||||
this.eventManager.disable(pluginInstance)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,10 @@ export namespace interfaces {
|
||||
* 是否忽略已取消的事件
|
||||
*/
|
||||
ignoreCancel?: boolean
|
||||
/**
|
||||
* 依赖插件 没有就不加载
|
||||
*/
|
||||
plugins?: string[]
|
||||
}
|
||||
export interface ConfigMetadata extends plugin.BaseMetadata {
|
||||
/**
|
||||
|
||||
@@ -2,12 +2,11 @@ import i18n from '@ccms/i18n'
|
||||
import { plugin, server } from '@ccms/api'
|
||||
import { provideSingleton, Container, ContainerInstance, Autowired } from '@ccms/container'
|
||||
|
||||
import './config'
|
||||
import { interfaces } from './interfaces'
|
||||
import { PluginTaskManager } from './task'
|
||||
import { PluginEventManager } from './event'
|
||||
import { PluginCommandManager } from './command'
|
||||
import { PluginConfigManager } from './config'
|
||||
import { PluginCommandManager } from './command'
|
||||
|
||||
const Thread = Java.type('java.lang.Thread')
|
||||
|
||||
@@ -93,18 +92,17 @@ export class PluginManagerImpl implements plugin.PluginManager {
|
||||
for (const [, scanner] of this.sacnnerMap) {
|
||||
try {
|
||||
console.i18n('ms.plugin.manager.scan', { scanner: scanner.type, folder })
|
||||
let plugins = scanner.scan(folder)
|
||||
console.i18n('ms.plugin.manager.scan.finish', { scanner: scanner.type, folder, size: plugins.length })
|
||||
plugins.forEach(loadMetadata => {
|
||||
let loadMetadatas = scanner.scan(folder)
|
||||
console.i18n('ms.plugin.manager.scan.finish', { scanner: scanner.type, folder, size: loadMetadatas.length })
|
||||
for (const loadMetadata of loadMetadatas) {
|
||||
try {
|
||||
this.loadAndRequirePlugin(loadMetadata)
|
||||
} catch (error: any) {
|
||||
console.error(`plugin scanner ${scanner.type} load ${loadMetadata.file} occurred error ${error}`)
|
||||
console.console(`§c扫描器 §4${scanner.type} §c文件 §4${loadMetadata.file.toString().replace(root, '')} §c编译失败 请提供下列错误给开发者`)
|
||||
console.ex(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`plugin scanner ${scanner.type} occurred error ${error}`)
|
||||
console.ex(error)
|
||||
}
|
||||
}
|
||||
@@ -229,8 +227,16 @@ export class PluginManagerImpl implements plugin.PluginManager {
|
||||
})
|
||||
}
|
||||
|
||||
has(name: string) {
|
||||
return this.instanceMap.has(name)
|
||||
}
|
||||
|
||||
get(name: string) {
|
||||
return this.instanceMap.get(name) || null
|
||||
}
|
||||
|
||||
getPlugin(name: string) {
|
||||
return this.instanceMap.get(name)
|
||||
return this.instanceMap.get(name) || null
|
||||
}
|
||||
|
||||
getPlugins() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/polyfill",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript polyfill package",
|
||||
"author": "MiaoWoo <admin@yumc.pw>",
|
||||
"homepage": "https://github.com/circlecloud/ms.git",
|
||||
@@ -14,14 +14,14 @@
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/i18n": "^0.24.0",
|
||||
"@ccms/nodejs": "^0.24.0",
|
||||
"core-js": "^3.22.1"
|
||||
"@ccms/i18n": "^0.27.6",
|
||||
"@ccms/nodejs": "^0.27.6",
|
||||
"core-js": "^3.27.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ccms/nashorn": "^0.24.0",
|
||||
"@ccms/nashorn": "^0.27.6",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
||||
2134
packages/polyfill/src/buffer.ts
Normal file
2134
packages/polyfill/src/buffer.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@ import 'core-js'
|
||||
process.on('exit', () => require.disable())
|
||||
global.setGlobal('Proxy', require('./proxy').Proxy)
|
||||
global.setGlobal('XMLHttpRequest', require('./xml-http-request').XMLHttpRequest)
|
||||
global.setGlobal('Buffer', require('./buffer').Buffer)
|
||||
global.setGlobal('Blob', require('blob-polyfill').Blob)
|
||||
console.i18n("ms.polyfill.completed", { time: (new Date().getTime() - polyfillStartTime) / 1000 })
|
||||
export default true
|
||||
|
||||
@@ -68,13 +68,14 @@ type EventType =
|
||||
| 'timeout'
|
||||
| 'loadend'
|
||||
| 'loadstart'
|
||||
type HttpHeader = { [key: string]: string }
|
||||
|
||||
type RequestHttpHeader = { [key: string]: string }
|
||||
type HttpHeader = { [key: string]: string[] }
|
||||
|
||||
const executor = Executors.newCachedThreadPool()
|
||||
|
||||
export class XMLHttpRequest {
|
||||
private _timeout: number = 120000;
|
||||
private _connectTimeout: number = 5000;
|
||||
private _readTimeout: number = 120000;
|
||||
private _responseType: ResponseType = 'text';
|
||||
private _withCredentials: boolean
|
||||
|
||||
@@ -84,7 +85,7 @@ export class XMLHttpRequest {
|
||||
private _url: string
|
||||
private _async: boolean
|
||||
private _mimeType: string
|
||||
private _requestHeaders: HttpHeader = {};
|
||||
private _requestHeaders: RequestHttpHeader = {};
|
||||
|
||||
private _status: number = 0;
|
||||
private _statusText: string = null;
|
||||
@@ -96,10 +97,22 @@ export class XMLHttpRequest {
|
||||
private _connection = null;
|
||||
|
||||
get timeout() {
|
||||
return this._timeout
|
||||
return this._readTimeout
|
||||
}
|
||||
set timeout(timeout: number) {
|
||||
this._timeout = timeout
|
||||
this._readTimeout = timeout
|
||||
}
|
||||
get connectTimeout() {
|
||||
return this._connectTimeout
|
||||
}
|
||||
set connectTimeout(timeout: number) {
|
||||
this._connectTimeout = timeout
|
||||
}
|
||||
get readTimeout() {
|
||||
return this._readTimeout
|
||||
}
|
||||
set readTimeout(timeout: number) {
|
||||
this._readTimeout = timeout
|
||||
}
|
||||
get readyState() {
|
||||
return this._readyState
|
||||
@@ -143,7 +156,7 @@ export class XMLHttpRequest {
|
||||
this._requestHeaders[key] = val
|
||||
}
|
||||
getResponseHeader(key: string): string {
|
||||
return this._responseHeaders[key]
|
||||
return this._responseHeaders[key]?.[0]
|
||||
}
|
||||
getAllResponseHeaders(): any {
|
||||
return this._responseHeaders
|
||||
@@ -169,8 +182,8 @@ export class XMLHttpRequest {
|
||||
this._connection.setRequestMethod(this._method)
|
||||
this._connection.setDoOutput(true)
|
||||
this._connection.setDoInput(true)
|
||||
this._connection.setConnectTimeout(this._timeout)
|
||||
this._connection.setReadTimeout(this._timeout)
|
||||
this._connection.setConnectTimeout(this._connectTimeout)
|
||||
this._connection.setReadTimeout(this._readTimeout)
|
||||
|
||||
this.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
|
||||
this.setReadyState(ReadyState.OPENED)
|
||||
@@ -181,7 +194,7 @@ export class XMLHttpRequest {
|
||||
}
|
||||
if (this._readyState !== ReadyState.OPENED) { throw new Error(`Error Status ${this._readyState}!`) }
|
||||
let future = executor.submit(new Callable({ call: () => this._send(body) }))
|
||||
if (!this._async) { future.get(this._timeout, TimeUnit.MILLISECONDS) }
|
||||
if (!this._async) { future.get(this._connectTimeout + this._readTimeout + 100, TimeUnit.MILLISECONDS) }
|
||||
return future
|
||||
}
|
||||
get() {
|
||||
@@ -199,22 +212,23 @@ export class XMLHttpRequest {
|
||||
}
|
||||
abort() {
|
||||
this._connection.disconnect()
|
||||
this.onabort && this.onabort()
|
||||
this.onabort?.()
|
||||
}
|
||||
|
||||
private _send(body?: string | object) {
|
||||
try {
|
||||
this._connection.connect()
|
||||
this.onloadstart && this.onloadstart()
|
||||
this.onloadstart?.()
|
||||
if (body) {
|
||||
let bodyType = Object.prototype.toString.call(body)
|
||||
if (typeof body !== "string") { throw new Error(`body(${bodyType}) must be string!`) }
|
||||
if (typeof body !== "string") { throw new Error(`body(${bodyType}) must be string.`) }
|
||||
var out = this._connection.getOutputStream()
|
||||
out.write(new JavaString(body).getBytes(UTF_8))
|
||||
out.flush()
|
||||
out.close()
|
||||
}
|
||||
this.setReadyState(ReadyState.LOADING)
|
||||
this.onload?.()
|
||||
this._status = this._connection.getResponseCode()
|
||||
this._statusText = this._connection.getResponseMessage()
|
||||
if (this._status >= 0 && this._status < 300) {
|
||||
@@ -224,8 +238,8 @@ export class XMLHttpRequest {
|
||||
} else {
|
||||
this._responseText = this.readOutput(this._connection.getErrorStream())
|
||||
}
|
||||
this.setResponseHeaders(this._connection.getHeaderFields())
|
||||
this.onloadend && this.onloadend()
|
||||
this.setResponseHeaders()
|
||||
this.onloadend?.()
|
||||
} catch (ex: any) {
|
||||
if (ex instanceof SocketTimeoutException && this.ontimeout) {
|
||||
return this.ontimeout(ex)
|
||||
@@ -239,15 +253,15 @@ export class XMLHttpRequest {
|
||||
}
|
||||
}
|
||||
|
||||
private setResponseHeaders(header: any) {
|
||||
header.forEach((key: string | number, value: string | any[]) => {
|
||||
this._responseHeaders[key + ''] = value[value.length - 1] + ''
|
||||
private setResponseHeaders() {
|
||||
this._connection.getHeaderFields().forEach((key: string | number, value: any[]) => {
|
||||
this._responseHeaders[key + ''] = Java.from(value)
|
||||
})
|
||||
}
|
||||
|
||||
private setReadyState(state: ReadyState) {
|
||||
this._readyState = state
|
||||
this.onreadystatechange && this.onreadystatechange()
|
||||
this.onreadystatechange?.()
|
||||
}
|
||||
|
||||
private readOutput(input: any) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/protocol",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript protocol package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
||||
26
packages/qrcode/package.json
Normal file
26
packages/qrcode/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@ccms/qrcode",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript qrcode package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
"minecraft",
|
||||
"bukkit",
|
||||
"sponge"
|
||||
],
|
||||
"author": "MiaoWoo <admin@yumc.pw>",
|
||||
"homepage": "https://github.com/circlecloud/ms.git",
|
||||
"license": "ISC",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"watch": "tsc --watch",
|
||||
"build": "yarn clean && tsc",
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
1243
packages/qrcode/src/index.ts
Normal file
1243
packages/qrcode/src/index.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/sponge",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript sponge package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -21,12 +21,12 @@
|
||||
"devDependencies": {
|
||||
"@javatypes/sponge-api": "^0.0.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/api": "^0.24.0",
|
||||
"@ccms/common": "^0.24.0",
|
||||
"@ccms/container": "^0.24.0"
|
||||
"@ccms/api": "^0.27.6",
|
||||
"@ccms/common": "^0.27.6",
|
||||
"@ccms/container": "^0.27.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,15 @@
|
||||
import { provideSingleton } from '@ccms/container'
|
||||
import { particle, plugin } from '@ccms/api'
|
||||
import { particle } from '@ccms/api'
|
||||
|
||||
@provideSingleton(particle.ParticleManager)
|
||||
export class SpongeParticleManager extends particle.ParticleManager {
|
||||
private globalSpawner = new SpongeParticleSpawner()
|
||||
constructor() {
|
||||
super()
|
||||
particle.ParticleManager.globalSpawner = this.globalSpawner
|
||||
}
|
||||
protected getGlobalSpawner() {
|
||||
return this.globalSpawner
|
||||
}
|
||||
}
|
||||
@provideSingleton(particle.ParticleSpawner)
|
||||
export class SpongeParticleSpawner extends particle.ParticleSpawner {
|
||||
spawnParticle(location: org.spongepowered.api.world.Location<any>, particle: any, count: number = 1) {
|
||||
location.getPosition()
|
||||
// location.getWorld().spawnParticle(particle, location, count)
|
||||
}
|
||||
spawn(location: any, particle: particle.Particle) {
|
||||
location.getWorld().spawnParticle(
|
||||
particle.getParticle(),
|
||||
location,
|
||||
particle.getCount(),
|
||||
particle.getOffsetX(),
|
||||
particle.getOffsetY(),
|
||||
particle.getOffsetZ(),
|
||||
particle.getExtra(),
|
||||
particle.getData()
|
||||
)
|
||||
throw new Error('Not Impl.')
|
||||
}
|
||||
spawnToPlayer(player: any, location: any, particle: particle.Particle) {
|
||||
throw new Error('Not Impl.')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/spring",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript spring package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -20,13 +20,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/api": "^0.24.0",
|
||||
"@ccms/common": "^0.24.0",
|
||||
"@ccms/container": "^0.24.0",
|
||||
"@ccms/database": "^0.24.0"
|
||||
"@ccms/api": "^0.27.6",
|
||||
"@ccms/common": "^0.27.6",
|
||||
"@ccms/container": "^0.27.6",
|
||||
"@ccms/database": "^0.27.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/web",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript web package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -25,11 +25,11 @@
|
||||
"@javatypes/spring-web": "^0.0.3",
|
||||
"@javatypes/tomcat": "^0.0.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/api": "^0.24.0",
|
||||
"@ccms/container": "^0.24.0"
|
||||
"@ccms/api": "^0.27.6",
|
||||
"@ccms/container": "^0.27.6"
|
||||
}
|
||||
}
|
||||
|
||||
4
packages/websocket/.prettierrc
Normal file
4
packages/websocket/.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"semi": true
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ccms/websocket",
|
||||
"version": "0.24.0",
|
||||
"version": "0.27.6",
|
||||
"description": "MiaoScript websocket package",
|
||||
"keywords": [
|
||||
"miaoscript",
|
||||
@@ -19,14 +19,15 @@
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "3.1.0",
|
||||
"backo2": "^1.0.2",
|
||||
"parseuri": "^0.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ccms/nashorn": "^0.24.0",
|
||||
"@ccms/nashorn": "^0.27.6",
|
||||
"@javatypes/tomcat-websocket-api": "^0.0.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.6.3"
|
||||
"rimraf": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export class WebSocket extends EventEmitter {
|
||||
|
||||
private client: Transport
|
||||
|
||||
constructor(url: string, subProtocol: string = '', headers: WebSocketHeader = {}) {
|
||||
constructor(url: string, subProtocol: string | string[] = '', headers: WebSocketHeader = {}) {
|
||||
super()
|
||||
this.manager = manager
|
||||
this._url = url
|
||||
|
||||
@@ -1 +1,57 @@
|
||||
export = (namepsace) => (...args) => { }//console.debug(namepsace, ...args)
|
||||
export = (namepsace) =>
|
||||
(...args) => {
|
||||
console.trace(`[${namepsace}] ` + format(...args))
|
||||
} //console.debug(namepsace, ...args)
|
||||
let formatters: any = {}
|
||||
formatters.s = function (v) {
|
||||
return v
|
||||
}
|
||||
formatters.j = function (v) {
|
||||
try {
|
||||
return JSON.stringify(v)
|
||||
} catch (error: any) {
|
||||
return "[UnexpectedJSONParseError]: " + error.message
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Coerce `val`.
|
||||
*
|
||||
* @param {Mixed} val
|
||||
* @return {Mixed}
|
||||
* @api private
|
||||
*/
|
||||
function coerce(val) {
|
||||
if (val instanceof Error) {
|
||||
return val.stack || val.message
|
||||
}
|
||||
return val
|
||||
}
|
||||
function format(...args) {
|
||||
// Apply any `formatters` transformations
|
||||
args[0] = coerce(args[0])
|
||||
|
||||
if (typeof args[0] !== "string") {
|
||||
// Anything else let's inspect with %O
|
||||
args.unshift("%O")
|
||||
}
|
||||
|
||||
let index = 0
|
||||
args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
|
||||
// If we encounter an escaped % then don't increase the array index
|
||||
if (match === "%%") {
|
||||
return "%"
|
||||
}
|
||||
index++
|
||||
const formatter = formatters[format]
|
||||
if (typeof formatter === "function") {
|
||||
const val = args[index]
|
||||
match = formatter.call(format, val)
|
||||
|
||||
// Now we need to remove `args[index]` since it's inlined in the `format`
|
||||
args.splice(index, 1)
|
||||
index--
|
||||
}
|
||||
return match
|
||||
})
|
||||
return args[0]
|
||||
}
|
||||
|
||||
12
packages/websocket/src/engine.io-client/contrib/has-cors.ts
Normal file
12
packages/websocket/src/engine.io-client/contrib/has-cors.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// imported from https://github.com/component/has-cors
|
||||
let value = false;
|
||||
|
||||
try {
|
||||
value = typeof XMLHttpRequest !== 'undefined' &&
|
||||
'withCredentials' in new XMLHttpRequest();
|
||||
} catch (err) {
|
||||
// if XMLHttp support is disabled in IE then it will throw
|
||||
// when trying to create
|
||||
}
|
||||
|
||||
export const hasCORS = value;
|
||||
38
packages/websocket/src/engine.io-client/contrib/parseqs.ts
Normal file
38
packages/websocket/src/engine.io-client/contrib/parseqs.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// imported from https://github.com/galkn/querystring
|
||||
/**
|
||||
* Compiles a querystring
|
||||
* Returns string representation of the object
|
||||
*
|
||||
* @param {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
export function encode(obj) {
|
||||
let str = ''
|
||||
|
||||
for (let i in obj) {
|
||||
if (obj.hasOwnProperty(i)) {
|
||||
if (str.length) str += '&'
|
||||
str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i])
|
||||
}
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a simple querystring into an object
|
||||
*
|
||||
* @param {String} qs
|
||||
* @api private
|
||||
*/
|
||||
|
||||
export function decode(qs) {
|
||||
let qry = {}
|
||||
let pairs = qs.split('&')
|
||||
for (let i = 0, l = pairs.length; i < l; i++) {
|
||||
let pair = pairs[i].split('=')
|
||||
qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1])
|
||||
}
|
||||
return qry
|
||||
}
|
||||
68
packages/websocket/src/engine.io-client/contrib/parseuri.ts
Normal file
68
packages/websocket/src/engine.io-client/contrib/parseuri.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
// imported from https://github.com/galkn/parseuri
|
||||
/**
|
||||
* Parses an URI
|
||||
*
|
||||
* @author Steven Levithan <stevenlevithan.com> (MIT license)
|
||||
* @api private
|
||||
*/
|
||||
const re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
|
||||
|
||||
const parts = [
|
||||
'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'
|
||||
]
|
||||
|
||||
export function parse(str) {
|
||||
const src = str,
|
||||
b = str.indexOf('['),
|
||||
e = str.indexOf(']')
|
||||
|
||||
if (b != -1 && e != -1) {
|
||||
str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length)
|
||||
}
|
||||
|
||||
let m = re.exec(str || ''),
|
||||
uri = {} as any,
|
||||
i = 14
|
||||
|
||||
while (i--) {
|
||||
uri[parts[i]] = m[i] || ''
|
||||
}
|
||||
|
||||
if (b != -1 && e != -1) {
|
||||
uri.source = src
|
||||
uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':')
|
||||
uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':')
|
||||
uri.ipv6uri = true
|
||||
}
|
||||
|
||||
uri.pathNames = pathNames(uri, uri['path'])
|
||||
uri.queryKey = queryKey(uri, uri['query'])
|
||||
|
||||
return uri
|
||||
}
|
||||
|
||||
function pathNames(obj, path) {
|
||||
const regx = /\/{2,9}/g,
|
||||
names = path.replace(regx, "/").split("/")
|
||||
|
||||
if (path.slice(0, 1) == '/' || path.length === 0) {
|
||||
names.splice(0, 1)
|
||||
}
|
||||
if (path.slice(-1) == '/') {
|
||||
names.splice(names.length - 1, 1)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
function queryKey(uri, query) {
|
||||
const data = {}
|
||||
|
||||
query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, function ($0, $1, $2) {
|
||||
if ($1) {
|
||||
data[$1] = $2
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
62
packages/websocket/src/engine.io-client/contrib/yeast.ts
Normal file
62
packages/websocket/src/engine.io-client/contrib/yeast.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
// imported from https://github.com/unshiftio/yeast
|
||||
'use strict'
|
||||
|
||||
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'.split('')
|
||||
, length = 64
|
||||
, map = {}
|
||||
let seed = 0
|
||||
, i = 0
|
||||
, prev
|
||||
|
||||
/**
|
||||
* Return a string representing the specified number.
|
||||
*
|
||||
* @param {Number} num The number to convert.
|
||||
* @returns {String} The string representation of the number.
|
||||
* @api public
|
||||
*/
|
||||
export function encode(num) {
|
||||
let encoded = ''
|
||||
|
||||
do {
|
||||
encoded = alphabet[num % length] + encoded
|
||||
num = Math.floor(num / length)
|
||||
} while (num > 0)
|
||||
|
||||
return encoded
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the integer value specified by the given string.
|
||||
*
|
||||
* @param {String} str The string to convert.
|
||||
* @returns {Number} The integer value represented by the string.
|
||||
* @api public
|
||||
*/
|
||||
export function decode(str) {
|
||||
let decoded = 0
|
||||
|
||||
for (i = 0; i < str.length; i++) {
|
||||
decoded = decoded * length + map[str.charAt(i)]
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
/**
|
||||
* Yeast: A tiny growing id generator.
|
||||
*
|
||||
* @returns {String} A unique id.
|
||||
* @api public
|
||||
*/
|
||||
export function yeast() {
|
||||
const now = encode(+new Date())
|
||||
|
||||
if (now !== prev) return seed = 0, prev = now
|
||||
return now + '.' + encode(seed++)
|
||||
}
|
||||
|
||||
//
|
||||
// Map each character to its index.
|
||||
//
|
||||
for (; i < length; i++) map[alphabet[i]] = i
|
||||
@@ -1,16 +1,10 @@
|
||||
import { Socket } from './socket'
|
||||
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'
|
||||
export { Socket }
|
||||
export { SocketOptions } from "./socket"
|
||||
export const protocol = Socket.protocol
|
||||
export { Transport } from "./transport"
|
||||
export { transports } from "./transports/index"
|
||||
export { installTimerFunctions } from "./util"
|
||||
export { parse } from "./contrib/parseuri"
|
||||
export { nextTick } from "./transports/websocket-constructor"
|
||||
|
||||
@@ -1,21 +1,295 @@
|
||||
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"
|
||||
// import { transports } from "./transports/index.js";
|
||||
import { transports } from "./transports"
|
||||
import { installTimerFunctions, byteLength } from "./util"
|
||||
import { decode } from "./contrib/parseqs"
|
||||
import { parse } from "./contrib/parseuri"
|
||||
// import debugModule from "debug"; // debug()
|
||||
import { Emitter } from "@socket.io/component-emitter"
|
||||
// import { protocol } from "engine.io-parser";
|
||||
import { protocol } from "../engine.io-parser"
|
||||
import type { Packet, BinaryType, PacketType, RawData } from "../engine.io-parser"
|
||||
import { CloseDetails, Transport } from "./transport"
|
||||
|
||||
// const debug = debugModule("engine.io-client:socket"); // debug()
|
||||
const debug = require('../debug')('engine.io-client:socket')
|
||||
|
||||
export interface SocketOptions {
|
||||
/**
|
||||
* 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 | number
|
||||
|
||||
/**
|
||||
* Any query parameters in our uri. Set from the URI passed when connecting
|
||||
*/
|
||||
query: { [key: string]: any }
|
||||
|
||||
/**
|
||||
* `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 base 64 encoding for polling transport even when XHR2
|
||||
* responseType is available and WebSocket even if the used standard
|
||||
* supports binary.
|
||||
*/
|
||||
forceBase64: 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
|
||||
|
||||
/**
|
||||
* Whether to always use the native timeouts. This allows the client to
|
||||
* reconnect when the native timeout functions are overridden, such as when
|
||||
* mock clocks are installed.
|
||||
* @default false
|
||||
*/
|
||||
useNativeTimers: boolean
|
||||
|
||||
/**
|
||||
* weather we should unref the reconnect timer when it is
|
||||
* create automatically
|
||||
* @default false
|
||||
*/
|
||||
autoUnref: boolean
|
||||
|
||||
/**
|
||||
* parameters of the WebSocket permessage-deflate extension (see ws module api docs). Set to false to disable.
|
||||
* @default false
|
||||
*/
|
||||
perMessageDeflate: { threshold: number }
|
||||
|
||||
/**
|
||||
* The path to get our client file from, in the case of the server
|
||||
* serving it
|
||||
* @default '/engine.io'
|
||||
*/
|
||||
path: string
|
||||
|
||||
/**
|
||||
* Whether we should add a trailing slash to the request path.
|
||||
* @default true
|
||||
*/
|
||||
addTrailingSlash: boolean
|
||||
|
||||
/**
|
||||
* Either a single protocol string or an array of protocol strings. These strings are used to indicate sub-protocols,
|
||||
* so that a single server can implement multiple WebSocket sub-protocols (for example, you might want one server to
|
||||
* be able to handle different types of interactions depending on the specified protocol)
|
||||
* @default []
|
||||
*/
|
||||
protocols: string | string[]
|
||||
}
|
||||
|
||||
interface HandshakeData {
|
||||
sid: string
|
||||
upgrades: string[]
|
||||
pingInterval: number
|
||||
pingTimeout: number
|
||||
maxPayload: number
|
||||
}
|
||||
|
||||
interface SocketReservedEvents {
|
||||
open: () => void
|
||||
handshake: (data: HandshakeData) => void
|
||||
packet: (packet: Packet) => void
|
||||
packetCreate: (packet: Packet) => void
|
||||
data: (data) => void
|
||||
message: (data) => void
|
||||
drain: () => void
|
||||
flush: () => void
|
||||
heartbeat: () => void
|
||||
ping: () => void
|
||||
pong: () => void
|
||||
error: (err: string | Error) => void
|
||||
upgrading: (transport) => void
|
||||
upgrade: (transport) => void
|
||||
upgradeError: (err: Error) => void
|
||||
close: (reason: string, description?: CloseDetails | Error) => void
|
||||
}
|
||||
|
||||
type SocketState = "opening" | "open" | "closing" | "closed"
|
||||
|
||||
export class Socket extends Emitter<{}, {}, SocketReservedEvents> {
|
||||
public id: string
|
||||
public transport: Transport
|
||||
public binaryType: BinaryType
|
||||
public readyState: SocketState
|
||||
public writeBuffer: Packet[] = [];
|
||||
|
||||
private prevBufferLen: number
|
||||
private upgrades
|
||||
private pingInterval: number
|
||||
private pingTimeout: number
|
||||
private pingTimeoutTimer: NodeJS.Timer
|
||||
private setTimeoutFn: typeof setTimeout
|
||||
private clearTimeoutFn: typeof clearTimeout
|
||||
private readonly beforeunloadEventListener: () => void
|
||||
private readonly offlineEventListener: () => void
|
||||
private upgrading: boolean
|
||||
private maxPayload?: number
|
||||
|
||||
private readonly opts: Partial<SocketOptions>
|
||||
private readonly secure: boolean
|
||||
private readonly hostname: string
|
||||
private readonly port: string | number
|
||||
private readonly transports: string[]
|
||||
|
||||
static priorWebsocketSuccess: boolean
|
||||
static protocol = protocol;
|
||||
|
||||
export class Socket extends Emitter {
|
||||
/**
|
||||
* Socket constructor.
|
||||
*
|
||||
* @param {String|Object} uri or options
|
||||
* @param {Object} options
|
||||
* @param {Object} opts - options
|
||||
* @api public
|
||||
*/
|
||||
constructor(uri, opts: any = {}) {
|
||||
constructor(uri, opts: Partial<SocketOptions> = {}) {
|
||||
super()
|
||||
|
||||
if (uri && "object" === typeof uri) {
|
||||
@@ -24,13 +298,13 @@ export class Socket extends Emitter {
|
||||
}
|
||||
|
||||
if (uri) {
|
||||
uri = parseuri(uri)
|
||||
uri = parse(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
|
||||
opts.hostname = parse(opts.host).host
|
||||
}
|
||||
|
||||
installTimerFunctions(this, opts)
|
||||
@@ -53,11 +327,10 @@ export class Socket extends Emitter {
|
||||
(typeof location !== "undefined" && location.port
|
||||
? location.port
|
||||
: this.secure
|
||||
? 443
|
||||
: 80)
|
||||
? "443"
|
||||
: "80")
|
||||
|
||||
this.transports = ["websocket"]
|
||||
this.readyState = ""
|
||||
this.transports = opts.transports || ["polling", "websocket"]
|
||||
this.writeBuffer = []
|
||||
this.prevBufferLen = 0
|
||||
|
||||
@@ -67,9 +340,9 @@ export class Socket extends Emitter {
|
||||
agent: false,
|
||||
withCredentials: false,
|
||||
upgrade: true,
|
||||
jsonp: true,
|
||||
timestampParam: "t",
|
||||
rememberUpgrade: false,
|
||||
addTrailingSlash: true,
|
||||
rejectUnauthorized: true,
|
||||
perMessageDeflate: {
|
||||
threshold: 1024
|
||||
@@ -80,10 +353,12 @@ export class Socket extends Emitter {
|
||||
opts
|
||||
)
|
||||
|
||||
this.opts.path = this.opts.path.replace(/\/$/, "") + "/"
|
||||
this.opts.path =
|
||||
this.opts.path.replace(/\/$/, "") +
|
||||
(this.opts.addTrailingSlash ? "/" : "")
|
||||
|
||||
if (typeof this.opts.query === "string") {
|
||||
this.opts.query = parseqs.decode(this.opts.query)
|
||||
this.opts.query = decode(this.opts.query)
|
||||
}
|
||||
|
||||
// set on handshake
|
||||
@@ -100,21 +375,20 @@ export class Socket extends Emitter {
|
||||
// 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
|
||||
)
|
||||
this.beforeunloadEventListener = () => {
|
||||
if (this.transport) {
|
||||
// silently close the transport
|
||||
this.transport.removeAllListeners()
|
||||
this.transport.close()
|
||||
}
|
||||
}
|
||||
addEventListener("beforeunload", this.beforeunloadEventListener, false)
|
||||
}
|
||||
if (this.hostname !== "localhost") {
|
||||
this.offlineEventListener = () => {
|
||||
this.onClose("transport close")
|
||||
this.onClose("transport close", {
|
||||
description: "network connection lost",
|
||||
})
|
||||
}
|
||||
addEventListener("offline", this.offlineEventListener, false)
|
||||
}
|
||||
@@ -126,19 +400,16 @@ export class Socket extends Emitter {
|
||||
/**
|
||||
* Creates transport of the given type.
|
||||
*
|
||||
* @param {String} transport name
|
||||
* @param {String} name - transport name
|
||||
* @return {Transport}
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
createTransport(name, opt?) {
|
||||
if (name != 'websocket') {
|
||||
throw new Error('Only Support WebSocket in MiaoScript!')
|
||||
}
|
||||
private createTransport(name) {
|
||||
debug('creating transport "%s"', name)
|
||||
const query: any = clone(this.opts.query)
|
||||
const query: any = Object.assign({}, this.opts.query)
|
||||
|
||||
// append engine.io protocol identifier
|
||||
query.EIO = parser.protocol
|
||||
query.EIO = protocol
|
||||
|
||||
// transport name
|
||||
query.transport = name
|
||||
@@ -155,21 +426,21 @@ export class Socket extends Emitter {
|
||||
socket: this,
|
||||
hostname: this.hostname,
|
||||
secure: this.secure,
|
||||
port: this.port
|
||||
port: this.port,
|
||||
}
|
||||
)
|
||||
|
||||
debug("options: %j", JSON.stringify(opts))
|
||||
debug("new func", transports[name])
|
||||
debug("options: %j", opts)
|
||||
|
||||
return new transports[name](opts)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes transport to use and starts probe.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
open() {
|
||||
* Initializes transport to use and starts probe.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private open() {
|
||||
let transport
|
||||
if (
|
||||
this.opts.rememberUpgrade &&
|
||||
@@ -180,7 +451,7 @@ export class Socket extends Emitter {
|
||||
} 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")
|
||||
this.emitReserved("error", "No transports available")
|
||||
}, 0)
|
||||
return
|
||||
} else {
|
||||
@@ -191,8 +462,8 @@ export class Socket extends Emitter {
|
||||
// Retry with the next transport if the transport is disabled (jsonp: false)
|
||||
try {
|
||||
transport = this.createTransport(transport)
|
||||
} catch (error: any) {
|
||||
debug("error while creating transport: %s", error)
|
||||
} catch (e) {
|
||||
debug("error while creating transport: %s", e)
|
||||
this.transports.shift()
|
||||
this.open()
|
||||
return
|
||||
@@ -205,9 +476,9 @@ export class Socket extends Emitter {
|
||||
/**
|
||||
* Sets the current transport. Disables the existing one (if any).
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
setTransport(transport) {
|
||||
private setTransport(transport) {
|
||||
debug("setting transport %s", transport.name)
|
||||
|
||||
if (this.transport) {
|
||||
@@ -223,9 +494,7 @@ export class Socket extends Emitter {
|
||||
.on("drain", this.onDrain.bind(this))
|
||||
.on("packet", this.onPacket.bind(this))
|
||||
.on("error", this.onError.bind(this))
|
||||
.on("close", () => {
|
||||
this.onClose("transport close")
|
||||
})
|
||||
.on("close", (reason) => this.onClose("transport close", reason))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,9 +503,9 @@ export class Socket extends Emitter {
|
||||
* @param {String} transport name
|
||||
* @api private
|
||||
*/
|
||||
probe(name) {
|
||||
private probe(name) {
|
||||
debug('probing transport "%s"', name)
|
||||
let transport = this.createTransport(name, { probe: 1 })
|
||||
let transport = this.createTransport(name)
|
||||
let failed = false
|
||||
|
||||
Socket.priorWebsocketSuccess = false
|
||||
@@ -251,7 +520,7 @@ export class Socket extends Emitter {
|
||||
if ("pong" === msg.type && "probe" === msg.data) {
|
||||
debug('probe transport "%s" pong', name)
|
||||
this.upgrading = true
|
||||
this.emit("upgrading", transport)
|
||||
this.emitReserved("upgrading", transport)
|
||||
if (!transport) return
|
||||
Socket.priorWebsocketSuccess = "websocket" === transport.name
|
||||
|
||||
@@ -265,16 +534,17 @@ export class Socket extends Emitter {
|
||||
|
||||
this.setTransport(transport)
|
||||
transport.send([{ type: "upgrade" }])
|
||||
this.emit("upgrade", transport)
|
||||
this.emitReserved("upgrade", transport)
|
||||
transport = null
|
||||
this.upgrading = false
|
||||
this.flush()
|
||||
})
|
||||
} else {
|
||||
debug('probe transport "%s" failed', name)
|
||||
const err: any = new Error("probe error")
|
||||
const err = new Error("probe error")
|
||||
// @ts-ignore
|
||||
err.transport = transport.name
|
||||
this.emit("upgradeError", err)
|
||||
this.emitReserved("upgradeError", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -293,14 +563,15 @@ export class Socket extends Emitter {
|
||||
|
||||
// Handle any error that happens while probing
|
||||
const onerror = err => {
|
||||
const error: any = new Error("probe error: " + err)
|
||||
const error = new Error("probe error: " + err)
|
||||
// @ts-ignore
|
||||
error.transport = transport.name
|
||||
|
||||
freezeTransport()
|
||||
|
||||
debug('probe transport "%s" failed because of error: %s', name, err)
|
||||
|
||||
this.emit("upgradeError", error)
|
||||
this.emitReserved("upgradeError", error)
|
||||
}
|
||||
|
||||
function onTransportClose() {
|
||||
@@ -325,8 +596,8 @@ export class Socket extends Emitter {
|
||||
transport.removeListener("open", onTransportOpen)
|
||||
transport.removeListener("error", onerror)
|
||||
transport.removeListener("close", onTransportClose)
|
||||
this.removeListener("close", onclose)
|
||||
this.removeListener("upgrading", onupgrade)
|
||||
this.off("close", onclose)
|
||||
this.off("upgrading", onupgrade)
|
||||
}
|
||||
|
||||
transport.once("open", onTransportOpen)
|
||||
@@ -342,13 +613,13 @@ export class Socket extends Emitter {
|
||||
/**
|
||||
* Called when connection is deemed open.
|
||||
*
|
||||
* @api public
|
||||
* @api private
|
||||
*/
|
||||
onOpen() {
|
||||
private onOpen() {
|
||||
debug("socket open")
|
||||
this.readyState = "open"
|
||||
Socket.priorWebsocketSuccess = "websocket" === this.transport.name
|
||||
this.emit("open")
|
||||
this.emitReserved("open")
|
||||
this.flush()
|
||||
|
||||
// we check for `readyState` in case an `open`
|
||||
@@ -372,7 +643,7 @@ export class Socket extends Emitter {
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
onPacket(packet) {
|
||||
private onPacket(packet) {
|
||||
if (
|
||||
"opening" === this.readyState ||
|
||||
"open" === this.readyState ||
|
||||
@@ -380,10 +651,10 @@ export class Socket extends Emitter {
|
||||
) {
|
||||
debug('socket receive: type "%s", data "%s"', packet.type, packet.data)
|
||||
|
||||
this.emit("packet", packet)
|
||||
this.emitReserved("packet", packet)
|
||||
|
||||
// Socket is live - any packet counts
|
||||
this.emit("heartbeat")
|
||||
this.emitReserved("heartbeat")
|
||||
|
||||
switch (packet.type) {
|
||||
case "open":
|
||||
@@ -393,19 +664,20 @@ export class Socket extends Emitter {
|
||||
case "ping":
|
||||
this.resetPingTimeout()
|
||||
this.sendPacket("pong")
|
||||
this.emit("ping")
|
||||
this.emit("pong")
|
||||
this.emitReserved("ping")
|
||||
this.emitReserved("pong")
|
||||
break
|
||||
|
||||
case "error":
|
||||
const err: any = new Error("server error")
|
||||
const err = new Error("server error")
|
||||
// @ts-ignore
|
||||
err.code = packet.data
|
||||
this.onError(err)
|
||||
break
|
||||
|
||||
case "message":
|
||||
this.emit("data", packet.data)
|
||||
this.emit("message", packet.data)
|
||||
this.emitReserved("data", packet.data)
|
||||
this.emitReserved("message", packet.data)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
@@ -416,16 +688,17 @@ export class Socket extends Emitter {
|
||||
/**
|
||||
* Called upon handshake completion.
|
||||
*
|
||||
* @param {Object} handshake obj
|
||||
* @param {Object} data - handshake obj
|
||||
* @api private
|
||||
*/
|
||||
onHandshake(data) {
|
||||
this.emit("handshake", data)
|
||||
private onHandshake(data) {
|
||||
this.emitReserved("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.maxPayload = data.maxPayload
|
||||
this.onOpen()
|
||||
// In case open handler closes socket
|
||||
if ("closed" === this.readyState) return
|
||||
@@ -437,7 +710,7 @@ export class Socket extends Emitter {
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
resetPingTimeout() {
|
||||
private resetPingTimeout() {
|
||||
this.clearTimeoutFn(this.pingTimeoutTimer)
|
||||
this.pingTimeoutTimer = this.setTimeoutFn(() => {
|
||||
this.onClose("ping timeout")
|
||||
@@ -452,7 +725,7 @@ export class Socket extends Emitter {
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
onDrain() {
|
||||
private onDrain() {
|
||||
this.writeBuffer.splice(0, this.prevBufferLen)
|
||||
|
||||
// setting prevBufferLen = 0 is very important
|
||||
@@ -461,7 +734,7 @@ export class Socket extends Emitter {
|
||||
this.prevBufferLen = 0
|
||||
|
||||
if (0 === this.writeBuffer.length) {
|
||||
this.emit("drain")
|
||||
this.emitReserved("drain")
|
||||
} else {
|
||||
this.flush()
|
||||
}
|
||||
@@ -472,22 +745,53 @@ export class Socket extends Emitter {
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
flush() {
|
||||
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)
|
||||
const packets = this.getWritablePackets()
|
||||
debug("flushing %d packets in socket", packets.length)
|
||||
this.transport.send(packets)
|
||||
// keep track of current length of writeBuffer
|
||||
// splice writeBuffer and callbackBuffer on `drain`
|
||||
this.prevBufferLen = this.writeBuffer.length
|
||||
this.emit("flush")
|
||||
this.prevBufferLen = packets.length
|
||||
this.emitReserved("flush")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the encoded size of the writeBuffer is below the maxPayload value sent by the server (only for HTTP
|
||||
* long-polling)
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private getWritablePackets() {
|
||||
const shouldCheckPayloadSize =
|
||||
this.maxPayload &&
|
||||
this.transport.name === "polling" &&
|
||||
this.writeBuffer.length > 1
|
||||
if (!shouldCheckPayloadSize) {
|
||||
return this.writeBuffer
|
||||
}
|
||||
let payloadSize = 1 // first packet type
|
||||
for (let i = 0; i < this.writeBuffer.length; i++) {
|
||||
const data = this.writeBuffer[i].data
|
||||
if (data) {
|
||||
payloadSize += byteLength(data)
|
||||
}
|
||||
if (i > 0 && payloadSize > this.maxPayload) {
|
||||
debug("only send %d out of %d packets", i, this.writeBuffer.length)
|
||||
return this.writeBuffer.slice(0, i)
|
||||
}
|
||||
payloadSize += 2 // separator + packet type
|
||||
}
|
||||
debug("payload size is %d (max: %d)", payloadSize, this.maxPayload)
|
||||
return this.writeBuffer
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
*
|
||||
@@ -497,12 +801,12 @@ export class Socket extends Emitter {
|
||||
* @return {Socket} for chaining.
|
||||
* @api public
|
||||
*/
|
||||
write(msg, options, fn) {
|
||||
public write(msg, options, fn?) {
|
||||
this.sendPacket("message", msg, options, fn)
|
||||
return this
|
||||
}
|
||||
|
||||
send(msg, options, fn) {
|
||||
public send(msg, options, fn?) {
|
||||
this.sendPacket("message", msg, options, fn)
|
||||
return this
|
||||
}
|
||||
@@ -516,7 +820,7 @@ export class Socket extends Emitter {
|
||||
* @param {Function} callback function.
|
||||
* @api private
|
||||
*/
|
||||
sendPacket(type, data?, options?, fn?) {
|
||||
private sendPacket(type, data?, options?, fn?) {
|
||||
if ("function" === typeof data) {
|
||||
fn = data
|
||||
data = undefined
|
||||
@@ -539,7 +843,7 @@ export class Socket extends Emitter {
|
||||
data: data,
|
||||
options: options
|
||||
}
|
||||
this.emit("packetCreate", packet)
|
||||
this.emitReserved("packetCreate", packet)
|
||||
this.writeBuffer.push(packet)
|
||||
if (fn) this.once("flush", fn)
|
||||
this.flush()
|
||||
@@ -548,9 +852,9 @@ export class Socket extends Emitter {
|
||||
/**
|
||||
* Closes the connection.
|
||||
*
|
||||
* @api private
|
||||
* @api public
|
||||
*/
|
||||
close() {
|
||||
public close() {
|
||||
const close = () => {
|
||||
this.onClose("forced close")
|
||||
debug("socket closing - telling transport to close")
|
||||
@@ -558,8 +862,8 @@ export class Socket extends Emitter {
|
||||
}
|
||||
|
||||
const cleanupAndClose = () => {
|
||||
this.removeListener("upgrade", cleanupAndClose)
|
||||
this.removeListener("upgradeError", cleanupAndClose)
|
||||
this.off("upgrade", cleanupAndClose)
|
||||
this.off("upgradeError", cleanupAndClose)
|
||||
close()
|
||||
}
|
||||
|
||||
@@ -595,10 +899,10 @@ export class Socket extends Emitter {
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
onError(err) {
|
||||
private onError(err) {
|
||||
debug("socket error %j", err)
|
||||
Socket.priorWebsocketSuccess = false
|
||||
this.emit("error", err)
|
||||
this.emitReserved("error", err)
|
||||
this.onClose("transport error", err)
|
||||
}
|
||||
|
||||
@@ -607,7 +911,7 @@ export class Socket extends Emitter {
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
onClose(reason, desc?) {
|
||||
private onClose(reason: string, description?: CloseDetails | Error) {
|
||||
if (
|
||||
"opening" === this.readyState ||
|
||||
"open" === this.readyState ||
|
||||
@@ -616,7 +920,6 @@ export class Socket extends Emitter {
|
||||
debug('socket close with reason: "%s"', reason)
|
||||
|
||||
// clear timers
|
||||
this.clearTimeoutFn(this.pingIntervalTimer)
|
||||
this.clearTimeoutFn(this.pingTimeoutTimer)
|
||||
|
||||
// stop event from firing again for transport
|
||||
@@ -629,6 +932,11 @@ export class Socket extends Emitter {
|
||||
this.transport.removeAllListeners()
|
||||
|
||||
if (typeof removeEventListener === "function") {
|
||||
removeEventListener(
|
||||
"beforeunload",
|
||||
this.beforeunloadEventListener,
|
||||
false
|
||||
)
|
||||
removeEventListener("offline", this.offlineEventListener, false)
|
||||
}
|
||||
|
||||
@@ -639,7 +947,7 @@ export class Socket extends Emitter {
|
||||
this.id = null
|
||||
|
||||
// emit close event
|
||||
this.emit("close", reason, desc)
|
||||
this.emitReserved("close", reason, description)
|
||||
|
||||
// clean buffers after, so users can still
|
||||
// grab the buffers on `close` event
|
||||
@@ -655,7 +963,7 @@ export class Socket extends Emitter {
|
||||
* @api private
|
||||
*
|
||||
*/
|
||||
filterUpgrades(upgrades) {
|
||||
private filterUpgrades(upgrades) {
|
||||
const filteredUpgrades = []
|
||||
let i = 0
|
||||
const j = upgrades.length
|
||||
@@ -666,23 +974,3 @@ export class Socket extends Emitter {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,61 +1,103 @@
|
||||
import parser from "../engine.io-parser"
|
||||
const Emitter = require("component-emitter")
|
||||
// import { decodePacket, Packet, RawData } from "engine.io-parser"
|
||||
import { decodePacket, Packet, RawData } from "../engine.io-parser"
|
||||
import { Emitter } from "@socket.io/component-emitter"
|
||||
import { installTimerFunctions } from "./util"
|
||||
const debug = (...args: any) => console.debug('engine.io-client:transport', ...args)//require("debug")("engine.io-client:transport")
|
||||
// import debugModule from "debug"; // debug()
|
||||
import { SocketOptions } from "./socket"
|
||||
|
||||
// const debug = debugModule("engine.io-client:transport"); // debug()
|
||||
const debug = require('../debug')("engine.io-client:transport") // debug()
|
||||
|
||||
class TransportError extends Error {
|
||||
public readonly type = "TransportError";
|
||||
|
||||
constructor(
|
||||
reason: string,
|
||||
readonly description: any,
|
||||
readonly context: any
|
||||
) {
|
||||
super(reason)
|
||||
}
|
||||
}
|
||||
|
||||
export interface CloseDetails {
|
||||
description: string
|
||||
context?: unknown // context should be typed as CloseEvent | XMLHttpRequest, but these types are not available on non-browser platforms
|
||||
}
|
||||
|
||||
interface TransportReservedEvents {
|
||||
open: () => void
|
||||
error: (err: TransportError) => void
|
||||
packet: (packet: Packet) => void
|
||||
close: (details?: CloseDetails) => void
|
||||
poll: () => void
|
||||
pollComplete: () => void
|
||||
drain: () => void
|
||||
}
|
||||
|
||||
type TransportState = "opening" | "open" | "closed" | "pausing" | "paused"
|
||||
|
||||
export abstract class Transport extends Emitter<
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
TransportReservedEvents
|
||||
> {
|
||||
public query: Record<string, string>
|
||||
public writable: boolean = false;
|
||||
|
||||
protected opts: SocketOptions
|
||||
protected supportsBinary: boolean
|
||||
protected readyState: TransportState
|
||||
protected socket: any
|
||||
protected setTimeoutFn: typeof setTimeout
|
||||
|
||||
export class Transport extends Emitter {
|
||||
/**
|
||||
* Transport abstract constructor.
|
||||
*
|
||||
* @param {Object} options.
|
||||
* @api private
|
||||
*/
|
||||
* Transport abstract constructor.
|
||||
*
|
||||
* @param {Object} opts - options
|
||||
* @protected
|
||||
*/
|
||||
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
|
||||
* @param {String} reason
|
||||
* @param description
|
||||
* @param context - the error context
|
||||
* @return {Transport} for chaining
|
||||
* @api public
|
||||
* @protected
|
||||
*/
|
||||
onError(msg, desc) {
|
||||
const err: any = new Error(msg)
|
||||
err.type = "TransportError"
|
||||
err.description = desc
|
||||
this.emit("error", err)
|
||||
protected onError(reason: string, description: any, context?: any) {
|
||||
super.emitReserved(
|
||||
"error",
|
||||
new TransportError(reason, description, context)
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the transport.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
open() {
|
||||
if ("closed" === this.readyState || "" === this.readyState) {
|
||||
this.readyState = "opening"
|
||||
this.doOpen()
|
||||
}
|
||||
public open() {
|
||||
this.readyState = "opening"
|
||||
this.doOpen()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the transport.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
close() {
|
||||
if ("opening" === this.readyState || "open" === this.readyState) {
|
||||
public close() {
|
||||
if (this.readyState === "opening" || this.readyState === "open") {
|
||||
this.doClose()
|
||||
this.onClose()
|
||||
}
|
||||
@@ -67,10 +109,9 @@ export class Transport extends Emitter {
|
||||
* Sends multiple packets.
|
||||
*
|
||||
* @param {Array} packets
|
||||
* @api private
|
||||
*/
|
||||
send(packets) {
|
||||
if ("open" === this.readyState) {
|
||||
public send(packets) {
|
||||
if (this.readyState === "open") {
|
||||
this.write(packets)
|
||||
} else {
|
||||
// this might happen if the transport was silently closed in the beforeunload event handler
|
||||
@@ -81,39 +122,58 @@ export class Transport extends Emitter {
|
||||
/**
|
||||
* Called upon open
|
||||
*
|
||||
* @api private
|
||||
* @protected
|
||||
*/
|
||||
onOpen() {
|
||||
protected onOpen() {
|
||||
this.readyState = "open"
|
||||
this.writable = true
|
||||
this.emit("open")
|
||||
super.emitReserved("open")
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with data.
|
||||
*
|
||||
* @param {String} data
|
||||
* @api private
|
||||
* @protected
|
||||
*/
|
||||
onData(data) {
|
||||
const packet = parser.decodePacket(data, this.socket.binaryType)
|
||||
protected onData(data: RawData) {
|
||||
const packet = decodePacket(data, this.socket.binaryType)
|
||||
this.onPacket(packet)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called with a decoded packet.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
onPacket(packet) {
|
||||
this.emit("packet", packet)
|
||||
protected onPacket(packet: Packet) {
|
||||
super.emitReserved("packet", packet)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon close.
|
||||
*
|
||||
* @api private
|
||||
* @protected
|
||||
*/
|
||||
onClose() {
|
||||
protected onClose(details?: CloseDetails) {
|
||||
this.readyState = "closed"
|
||||
this.emit("close")
|
||||
super.emitReserved("close", details)
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the transport
|
||||
*/
|
||||
public abstract get name(): string
|
||||
|
||||
/**
|
||||
* Pauses the transport, in order not to lose packets during an upgrade.
|
||||
*
|
||||
* @param onPause
|
||||
*/
|
||||
public pause(onPause: () => void) { }
|
||||
|
||||
protected abstract doOpen()
|
||||
protected abstract doClose()
|
||||
protected abstract write(packets: Packet[])
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { WS } from "./websocket"
|
||||
export default {
|
||||
'websocket': WS
|
||||
import { WS } from "./websocket.js"
|
||||
|
||||
export const transports = {
|
||||
websocket: WS,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { WebSocket as ws } from "../../client"
|
||||
|
||||
export const WebSocket = ws
|
||||
export const usingBrowserWebSocket = false
|
||||
export const defaultBinaryType = "nodebuffer"
|
||||
export const nextTick = process.nextTick
|
||||
@@ -1,21 +1,18 @@
|
||||
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")
|
||||
import { Transport } from "../transport"
|
||||
import { encode } from "../contrib/parseqs"
|
||||
import { yeast } from "../contrib/yeast"
|
||||
import { pick } from "../util"
|
||||
import {
|
||||
defaultBinaryType,
|
||||
nextTick,
|
||||
usingBrowserWebSocket,
|
||||
WebSocket
|
||||
} from "./websocket-constructor"
|
||||
// import debugModule from "debug" // debug()
|
||||
import { encodePacket } from "../../engine.io-parser"
|
||||
|
||||
const debug = (...args: any) => console.debug('engine.io-client:websocket', ...args)//require("debug")("engine.io-client:websocket")
|
||||
// const debug = debugModule("engine.io-client:websocket") // debug()
|
||||
const debug = (...args: any) => console.debug('engine.io-client:websocket', ...args)
|
||||
|
||||
// detect ReactNative environment
|
||||
const isReactNative =
|
||||
@@ -24,11 +21,13 @@ const isReactNative =
|
||||
navigator.product.toLowerCase() === "reactnative"
|
||||
|
||||
export class WS extends Transport {
|
||||
private ws: any
|
||||
|
||||
/**
|
||||
* WebSocket transport constructor.
|
||||
*
|
||||
* @api {Object} connection options
|
||||
* @api public
|
||||
* @param {Object} opts - connection options
|
||||
* @protected
|
||||
*/
|
||||
constructor(opts) {
|
||||
super(opts)
|
||||
@@ -36,21 +35,11 @@ export class WS extends Transport {
|
||||
this.supportsBinary = !opts.forceBase64
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport name.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
get name() {
|
||||
override get name() {
|
||||
return "websocket"
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
doOpen() {
|
||||
override doOpen() {
|
||||
if (!this.check()) {
|
||||
// let probe timeout
|
||||
return
|
||||
@@ -86,17 +75,17 @@ export class WS extends Transport {
|
||||
}
|
||||
|
||||
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 =
|
||||
usingBrowserWebSocket && !isReactNative
|
||||
? protocols
|
||||
? new WebSocket(uri, protocols)
|
||||
: new WebSocket(uri)
|
||||
: new WebSocket(uri, protocols, opts)
|
||||
} catch (err: any) {
|
||||
return this.emitReserved("error", err)
|
||||
}
|
||||
|
||||
this.ws.binaryType = this.socket.binaryType || 'arraybuffer'
|
||||
this.ws.binaryType = this.socket.binaryType || defaultBinaryType
|
||||
|
||||
this.addEventListeners()
|
||||
}
|
||||
@@ -104,27 +93,25 @@ export class WS extends Transport {
|
||||
/**
|
||||
* Adds event listeners to the socket
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
addEventListeners() {
|
||||
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)
|
||||
this.ws.onclose = (closeEvent) =>
|
||||
this.onClose({
|
||||
description: "websocket connection closed",
|
||||
context: closeEvent,
|
||||
})
|
||||
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) {
|
||||
override write(packets) {
|
||||
this.writable = false
|
||||
|
||||
// encodePacket efficient as it uses WS framing
|
||||
@@ -133,9 +120,9 @@ export class WS extends Transport {
|
||||
const packet = packets[i]
|
||||
const lastPacket = i === packets.length - 1
|
||||
|
||||
parser.encodePacket(packet, this.supportsBinary, data => {
|
||||
encodePacket(packet, this.supportsBinary, (data) => {
|
||||
// always create a new object (GH-437)
|
||||
const opts: any = {}
|
||||
const opts: { compress?: boolean } = {}
|
||||
if (!usingBrowserWebSocket) {
|
||||
if (packet.options) {
|
||||
opts.compress = packet.options.compress
|
||||
@@ -143,6 +130,7 @@ export class WS extends Transport {
|
||||
|
||||
if (this.opts.perMessageDeflate) {
|
||||
const len =
|
||||
// @ts-ignore
|
||||
"string" === typeof data ? Buffer.byteLength(data) : data.length
|
||||
if (len < this.opts.perMessageDeflate.threshold) {
|
||||
opts.compress = false
|
||||
@@ -160,37 +148,23 @@ export class WS extends Transport {
|
||||
} else {
|
||||
this.ws.send(data, opts)
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (e) {
|
||||
debug("websocket closed before onclose event")
|
||||
}
|
||||
|
||||
if (lastPacket) {
|
||||
// fake drain
|
||||
// defer to next tick to allow Socket to clear writeBuffer
|
||||
process.nextTick(() => {
|
||||
nextTick(() => {
|
||||
this.writable = true
|
||||
this.emit("drain")
|
||||
this.emitReserved("drain")
|
||||
}, this.setTimeoutFn)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon close
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
onClose() {
|
||||
Transport.prototype.onClose.call(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
doClose() {
|
||||
override doClose() {
|
||||
if (typeof this.ws !== "undefined") {
|
||||
this.ws.close()
|
||||
this.ws = null
|
||||
@@ -200,10 +174,10 @@ export class WS extends Transport {
|
||||
/**
|
||||
* Generates uri for connection.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
uri() {
|
||||
let query = this.query || {}
|
||||
let query: { b64?: number } = this.query || {}
|
||||
const schema = this.opts.secure ? "wss" : "ws"
|
||||
let port = ""
|
||||
|
||||
@@ -226,21 +200,16 @@ export class WS extends Transport {
|
||||
query.b64 = 1
|
||||
}
|
||||
|
||||
query = parseqs.encode(query)
|
||||
|
||||
// prepend ? to query
|
||||
if (query.length) {
|
||||
query = "?" + query
|
||||
}
|
||||
|
||||
const encodedQuery = encode(query)
|
||||
const ipv6 = this.opts.hostname.indexOf(":") !== -1
|
||||
|
||||
return (
|
||||
schema +
|
||||
"://" +
|
||||
(ipv6 ? "[" + this.opts.hostname + "]" : this.opts.hostname) +
|
||||
port +
|
||||
this.opts.path +
|
||||
query
|
||||
(encodedQuery.length ? "?" + encodedQuery : "")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -248,12 +217,9 @@ export class WS extends Transport {
|
||||
* Feature detection for WebSocket.
|
||||
*
|
||||
* @return {Boolean} whether this transport is available.
|
||||
* @api public
|
||||
* @private
|
||||
*/
|
||||
check() {
|
||||
return (
|
||||
!!WebSocket &&
|
||||
!("__initialize" in WebSocket && this.name === WS.prototype.name)
|
||||
)
|
||||
private check() {
|
||||
return !!WebSocket
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
const pick = (obj, ...attr) => {
|
||||
// import { globalThisShim as globalThis } from "./globalThis.js"
|
||||
|
||||
export function pick(obj, ...attr) {
|
||||
return attr.reduce((acc, k) => {
|
||||
if (obj.hasOwnProperty(k)) {
|
||||
acc[k] = obj[k]
|
||||
@@ -8,16 +10,46 @@ const pick = (obj, ...attr) => {
|
||||
}
|
||||
|
||||
// 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 NATIVE_SET_TIMEOUT = globalThis.setTimeout
|
||||
const NATIVE_CLEAR_TIMEOUT = globalThis.clearTimeout
|
||||
|
||||
const installTimerFunctions = (obj, opts) => {
|
||||
export function 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)
|
||||
obj.setTimeoutFn = globalThis.setTimeout.bind(globalThis)
|
||||
obj.clearTimeoutFn = globalThis.clearTimeout.bind(globalThis)
|
||||
}
|
||||
}
|
||||
export { pick, installTimerFunctions }
|
||||
|
||||
// base64 encoded buffers are about 33% bigger (https://en.wikipedia.org/wiki/Base64)
|
||||
const BASE64_OVERHEAD = 1.33
|
||||
|
||||
// we could also have used `new Blob([obj]).size`, but it isn't supported in IE9
|
||||
export function byteLength(obj) {
|
||||
if (typeof obj === "string") {
|
||||
return utf8Length(obj)
|
||||
}
|
||||
// arraybuffer or blob
|
||||
return Math.ceil((obj.byteLength || obj.size) * BASE64_OVERHEAD)
|
||||
}
|
||||
|
||||
function utf8Length(str) {
|
||||
let c = 0,
|
||||
length = 0
|
||||
for (let i = 0, l = str.length; i < l; i++) {
|
||||
c = str.charCodeAt(i)
|
||||
if (c < 0x80) {
|
||||
length += 1
|
||||
} else if (c < 0x800) {
|
||||
length += 2
|
||||
} else if (c < 0xd800 || c >= 0xe000) {
|
||||
length += 3
|
||||
} else {
|
||||
i++
|
||||
length += 4
|
||||
}
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
@@ -1,21 +1,39 @@
|
||||
const PACKET_TYPES = Object.create(null) // no Map = no polyfill
|
||||
PACKET_TYPES["open"] = "0"
|
||||
PACKET_TYPES["close"] = "1"
|
||||
PACKET_TYPES["ping"] = "2"
|
||||
PACKET_TYPES["pong"] = "3"
|
||||
PACKET_TYPES["message"] = "4"
|
||||
PACKET_TYPES["upgrade"] = "5"
|
||||
PACKET_TYPES["noop"] = "6"
|
||||
const PACKET_TYPES = Object.create(null); // no Map = no polyfill
|
||||
PACKET_TYPES["open"] = "0";
|
||||
PACKET_TYPES["close"] = "1";
|
||||
PACKET_TYPES["ping"] = "2";
|
||||
PACKET_TYPES["pong"] = "3";
|
||||
PACKET_TYPES["message"] = "4";
|
||||
PACKET_TYPES["upgrade"] = "5";
|
||||
PACKET_TYPES["noop"] = "6";
|
||||
|
||||
const PACKET_TYPES_REVERSE = Object.create(null)
|
||||
const PACKET_TYPES_REVERSE = Object.create(null);
|
||||
Object.keys(PACKET_TYPES).forEach(key => {
|
||||
PACKET_TYPES_REVERSE[PACKET_TYPES[key]] = key
|
||||
})
|
||||
PACKET_TYPES_REVERSE[PACKET_TYPES[key]] = key;
|
||||
});
|
||||
|
||||
const ERROR_PACKET = { type: "error", data: "parser error" }
|
||||
const ERROR_PACKET: Packet = { type: "error", data: "parser error" };
|
||||
|
||||
export = {
|
||||
PACKET_TYPES,
|
||||
PACKET_TYPES_REVERSE,
|
||||
ERROR_PACKET
|
||||
export { PACKET_TYPES, PACKET_TYPES_REVERSE, ERROR_PACKET };
|
||||
|
||||
export type PacketType =
|
||||
| "open"
|
||||
| "close"
|
||||
| "ping"
|
||||
| "pong"
|
||||
| "message"
|
||||
| "upgrade"
|
||||
| "noop"
|
||||
| "error";
|
||||
|
||||
// RawData should be "string | Buffer | ArrayBuffer | ArrayBufferView | Blob", but Blob does not exist in Node.js and
|
||||
// requires to add the dom lib in tsconfig.json
|
||||
export type RawData = any;
|
||||
|
||||
export interface Packet {
|
||||
type: PacketType;
|
||||
options?: { compress: boolean };
|
||||
data?: RawData;
|
||||
}
|
||||
|
||||
export type BinaryType = "nodebuffer" | "arraybuffer" | "blob";
|
||||
|
||||
@@ -1,48 +1,60 @@
|
||||
const { PACKET_TYPES_REVERSE, ERROR_PACKET } = require("./commons")
|
||||
import {
|
||||
ERROR_PACKET,
|
||||
PACKET_TYPES_REVERSE,
|
||||
Packet,
|
||||
BinaryType,
|
||||
RawData
|
||||
} from "./commons.js";
|
||||
|
||||
export const decodePacket = (encodedPacket, binaryType) => {
|
||||
if (typeof encodedPacket !== "string") {
|
||||
return {
|
||||
type: "message",
|
||||
data: mapBinary(encodedPacket, binaryType)
|
||||
}
|
||||
}
|
||||
const type = encodedPacket.charAt(0)
|
||||
if (type === "b") {
|
||||
const buffer = Buffer.from(encodedPacket.substring(1), "base64")
|
||||
return {
|
||||
type: "message",
|
||||
data: mapBinary(buffer, binaryType)
|
||||
}
|
||||
}
|
||||
if (!PACKET_TYPES_REVERSE[type]) {
|
||||
return ERROR_PACKET
|
||||
}
|
||||
return encodedPacket.length > 1
|
||||
? {
|
||||
type: PACKET_TYPES_REVERSE[type],
|
||||
data: encodedPacket.substring(1)
|
||||
}
|
||||
: {
|
||||
type: PACKET_TYPES_REVERSE[type]
|
||||
}
|
||||
}
|
||||
const decodePacket = (
|
||||
encodedPacket: RawData,
|
||||
binaryType?: BinaryType
|
||||
): Packet => {
|
||||
if (typeof encodedPacket !== "string") {
|
||||
return {
|
||||
type: "message",
|
||||
data: mapBinary(encodedPacket, binaryType)
|
||||
};
|
||||
}
|
||||
const type = encodedPacket.charAt(0);
|
||||
if (type === "b") {
|
||||
const buffer = Buffer.from(encodedPacket.substring(1), "base64");
|
||||
return {
|
||||
type: "message",
|
||||
data: mapBinary(buffer, binaryType)
|
||||
};
|
||||
}
|
||||
if (!PACKET_TYPES_REVERSE[type]) {
|
||||
return ERROR_PACKET;
|
||||
}
|
||||
return encodedPacket.length > 1
|
||||
? {
|
||||
type: PACKET_TYPES_REVERSE[type],
|
||||
data: encodedPacket.substring(1)
|
||||
}
|
||||
: {
|
||||
type: PACKET_TYPES_REVERSE[type]
|
||||
};
|
||||
};
|
||||
|
||||
const mapBinary = (data, binaryType) => {
|
||||
switch (binaryType) {
|
||||
case "arraybuffer":
|
||||
return Buffer.isBuffer(data) ? toArrayBuffer(data) : data
|
||||
case "nodebuffer":
|
||||
default:
|
||||
return data // assuming the data is already a Buffer
|
||||
}
|
||||
}
|
||||
const mapBinary = (data: RawData, binaryType?: BinaryType) => {
|
||||
const isBuffer = Buffer.isBuffer(data);
|
||||
switch (binaryType) {
|
||||
case "arraybuffer":
|
||||
return isBuffer ? toArrayBuffer(data) : data;
|
||||
case "nodebuffer":
|
||||
default:
|
||||
return data; // assuming the data is already a Buffer
|
||||
}
|
||||
};
|
||||
|
||||
const toArrayBuffer = buffer => {
|
||||
const arrayBuffer = new ArrayBuffer(buffer.length)
|
||||
const view = new Uint8Array(arrayBuffer)
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
view[i] = buffer[i]
|
||||
}
|
||||
return arrayBuffer
|
||||
}
|
||||
const toArrayBuffer = (buffer: Buffer): ArrayBuffer => {
|
||||
const arrayBuffer = new ArrayBuffer(buffer.length);
|
||||
const view = new Uint8Array(arrayBuffer);
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
view[i] = buffer[i];
|
||||
}
|
||||
return arrayBuffer;
|
||||
};
|
||||
|
||||
export default decodePacket;
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
const { PACKET_TYPES } = require("./commons")
|
||||
import { PACKET_TYPES, Packet, RawData } from "./commons.js";
|
||||
|
||||
export const encodePacket = ({ type, data }, supportsBinary, callback) => {
|
||||
console.trace('encodePacket', type, JSON.stringify(data))
|
||||
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
|
||||
const buffer = toBuffer(data)
|
||||
return callback(encodeBuffer(buffer, supportsBinary))
|
||||
}
|
||||
// plain string
|
||||
return callback(PACKET_TYPES[type] + (data || ""))
|
||||
}
|
||||
const encodePacket = (
|
||||
{ type, data }: Packet,
|
||||
supportsBinary: boolean,
|
||||
callback: (encodedPacket: RawData) => void
|
||||
) => {
|
||||
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
|
||||
const buffer = toBuffer(data);
|
||||
return callback(encodeBuffer(buffer, supportsBinary));
|
||||
}
|
||||
// plain string
|
||||
return callback(PACKET_TYPES[type] + (data || ""));
|
||||
};
|
||||
|
||||
const toBuffer = data => {
|
||||
if (Buffer.isBuffer(data)) {
|
||||
return data
|
||||
} else if (data instanceof ArrayBuffer) {
|
||||
return Buffer.from(data)
|
||||
} else {
|
||||
return Buffer.from(data.buffer, data.byteOffset, data.byteLength)
|
||||
}
|
||||
}
|
||||
if (Buffer.isBuffer(data)) {
|
||||
return data;
|
||||
} else if (data instanceof ArrayBuffer) {
|
||||
return Buffer.from(data);
|
||||
} else {
|
||||
return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
||||
}
|
||||
};
|
||||
|
||||
// only 'message' packets can contain binary, so the type prefix is not needed
|
||||
const encodeBuffer = (data, supportsBinary) => {
|
||||
return supportsBinary ? data : "b" + data.toString("base64")
|
||||
}
|
||||
const encodeBuffer = (data: Buffer, supportsBinary: boolean): RawData => {
|
||||
return supportsBinary ? data : "b" + data.toString("base64");
|
||||
};
|
||||
|
||||
export default encodePacket;
|
||||
|
||||
@@ -1,42 +1,53 @@
|
||||
import { encodePacket } from "./encodePacket"
|
||||
import { decodePacket } from "./decodePacket"
|
||||
import encodePacket from "./encodePacket.js";
|
||||
import decodePacket from "./decodePacket.js";
|
||||
import { Packet, PacketType, RawData, BinaryType } from "./commons.js";
|
||||
|
||||
const SEPARATOR = String.fromCharCode(30) // see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text
|
||||
const SEPARATOR = String.fromCharCode(30); // see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text
|
||||
|
||||
const encodePayload = (packets, callback) => {
|
||||
// some packets may be added to the array while encoding, so the initial length must be saved
|
||||
const length = packets.length
|
||||
const encodedPackets = new Array(length)
|
||||
let count = 0
|
||||
const encodePayload = (
|
||||
packets: Packet[],
|
||||
callback: (encodedPayload: string) => void
|
||||
) => {
|
||||
// some packets may be added to the array while encoding, so the initial length must be saved
|
||||
const length = packets.length;
|
||||
const encodedPackets = new Array(length);
|
||||
let count = 0;
|
||||
|
||||
packets.forEach((packet, i) => {
|
||||
// force base64 encoding for binary packets
|
||||
encodePacket(packet, false, encodedPacket => {
|
||||
encodedPackets[i] = encodedPacket
|
||||
if (++count === length) {
|
||||
callback(encodedPackets.join(SEPARATOR))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
packets.forEach((packet, i) => {
|
||||
// force base64 encoding for binary packets
|
||||
encodePacket(packet, false, encodedPacket => {
|
||||
encodedPackets[i] = encodedPacket;
|
||||
if (++count === length) {
|
||||
callback(encodedPackets.join(SEPARATOR));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const decodePayload = (encodedPayload, binaryType) => {
|
||||
const encodedPackets = encodedPayload.split(SEPARATOR)
|
||||
const packets = []
|
||||
for (let i = 0; i < encodedPackets.length; i++) {
|
||||
const decodedPacket = decodePacket(encodedPackets[i], binaryType)
|
||||
packets.push(decodedPacket)
|
||||
if (decodedPacket.type === "error") {
|
||||
break
|
||||
}
|
||||
const decodePayload = (
|
||||
encodedPayload: string,
|
||||
binaryType?: BinaryType
|
||||
): Packet[] => {
|
||||
const encodedPackets = encodedPayload.split(SEPARATOR);
|
||||
const packets = [];
|
||||
for (let i = 0; i < encodedPackets.length; i++) {
|
||||
const decodedPacket = decodePacket(encodedPackets[i], binaryType);
|
||||
packets.push(decodedPacket);
|
||||
if (decodedPacket.type === "error") {
|
||||
break;
|
||||
}
|
||||
return packets
|
||||
}
|
||||
}
|
||||
return packets;
|
||||
};
|
||||
|
||||
export default {
|
||||
protocol: 4,
|
||||
encodePacket,
|
||||
encodePayload,
|
||||
decodePacket,
|
||||
decodePayload
|
||||
}
|
||||
export const protocol = 4;
|
||||
export {
|
||||
encodePacket,
|
||||
encodePayload,
|
||||
decodePacket,
|
||||
decodePayload,
|
||||
Packet,
|
||||
PacketType,
|
||||
RawData,
|
||||
BinaryType
|
||||
};
|
||||
|
||||
@@ -1,10 +1,45 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
// import { createServer } from "http"
|
||||
import { Server, AttachOptions, ServerOptions } from "./server";
|
||||
import transports from "./transports/index";
|
||||
import * as parser from "../engine.io-parser";
|
||||
|
||||
// const http = require("http")
|
||||
// const Server = require("./server")
|
||||
import { Server } from './server'
|
||||
// export { Server, transports, listen, attach, parser }
|
||||
export { Server, transports, attach, parser };
|
||||
export { AttachOptions, ServerOptions } from "./server";
|
||||
// export { uServer } from "./userver";
|
||||
export { Socket } from "./socket";
|
||||
export { Transport } from "./transport";
|
||||
export const protocol = parser.protocol;
|
||||
|
||||
// /**
|
||||
// * Creates an http.Server exclusively used for WS upgrades.
|
||||
// *
|
||||
// * @param {Number} port
|
||||
// * @param {Function} callback
|
||||
// * @param {Object} options
|
||||
// * @return {Server} websocket.io server
|
||||
// * @api public
|
||||
// */
|
||||
//
|
||||
// function listen(port, options: AttachOptions & ServerOptions, fn) {
|
||||
// if ("function" === typeof options) {
|
||||
// fn = options;
|
||||
// options = {};
|
||||
// }
|
||||
|
||||
// const server = createServer(function(req, res) {
|
||||
// res.writeHead(501);
|
||||
// res.end("Not Implemented");
|
||||
// });
|
||||
|
||||
// // create engine server
|
||||
// const engine = attach(server, options);
|
||||
// engine.httpServer = server;
|
||||
|
||||
// server.listen(port, fn);
|
||||
|
||||
// return engine;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Captures upgrade requests for a http.Server.
|
||||
@@ -15,12 +50,8 @@ import { Server } from './server'
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function attach(srv, options) {
|
||||
const engine = new Server(options)
|
||||
engine.attach(srv, options)
|
||||
return engine
|
||||
}
|
||||
|
||||
export = {
|
||||
attach
|
||||
function attach(server, options: AttachOptions & ServerOptions) {
|
||||
const engine = new Server(options);
|
||||
engine.attach(server, options);
|
||||
return engine;
|
||||
}
|
||||
|
||||
485
packages/websocket/src/engine.io/parser-v3/index.ts
Normal file
485
packages/websocket/src/engine.io/parser-v3/index.ts
Normal file
@@ -0,0 +1,485 @@
|
||||
// imported from https://github.com/socketio/engine.io-parser/tree/2.2.x
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utf8 = require('./utf8');
|
||||
|
||||
/**
|
||||
* Current protocol version.
|
||||
*/
|
||||
export const protocol = 3;
|
||||
|
||||
const hasBinary = (packets) => {
|
||||
for (const packet of packets) {
|
||||
if (packet.data instanceof ArrayBuffer || ArrayBuffer.isView(packet.data)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Packet types.
|
||||
*/
|
||||
|
||||
export const packets = {
|
||||
open: 0 // non-ws
|
||||
, close: 1 // non-ws
|
||||
, ping: 2
|
||||
, pong: 3
|
||||
, message: 4
|
||||
, upgrade: 5
|
||||
, noop: 6
|
||||
};
|
||||
|
||||
var packetslist = Object.keys(packets);
|
||||
|
||||
/**
|
||||
* Premade error packet.
|
||||
*/
|
||||
|
||||
var err = { type: 'error', data: 'parser error' };
|
||||
|
||||
const EMPTY_BUFFER = Buffer.concat([]);
|
||||
|
||||
/**
|
||||
* Encodes a packet.
|
||||
*
|
||||
* <packet type id> [ <data> ]
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* 5hello world
|
||||
* 3
|
||||
* 4
|
||||
*
|
||||
* Binary is encoded in an identical principle
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
export function encodePacket (packet, supportsBinary, utf8encode, callback) {
|
||||
if (typeof supportsBinary === 'function') {
|
||||
callback = supportsBinary;
|
||||
supportsBinary = null;
|
||||
}
|
||||
|
||||
if (typeof utf8encode === 'function') {
|
||||
callback = utf8encode;
|
||||
utf8encode = null;
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(packet.data)) {
|
||||
return encodeBuffer(packet, supportsBinary, callback);
|
||||
} else if (packet.data && (packet.data.buffer || packet.data) instanceof ArrayBuffer) {
|
||||
return encodeBuffer({ type: packet.type, data: arrayBufferToBuffer(packet.data) }, supportsBinary, callback);
|
||||
}
|
||||
|
||||
// Sending data as a utf-8 string
|
||||
var encoded = packets[packet.type];
|
||||
|
||||
// data fragment is optional
|
||||
if (undefined !== packet.data) {
|
||||
encoded += utf8encode ? utf8.encode(String(packet.data), { strict: false }) : String(packet.data);
|
||||
}
|
||||
|
||||
return callback('' + encoded);
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode Buffer data
|
||||
*/
|
||||
|
||||
function encodeBuffer(packet, supportsBinary, callback) {
|
||||
if (!supportsBinary) {
|
||||
return encodeBase64Packet(packet, callback);
|
||||
}
|
||||
|
||||
var data = packet.data;
|
||||
var typeBuffer = Buffer.allocUnsafe(1);
|
||||
typeBuffer[0] = packets[packet.type];
|
||||
return callback(Buffer.concat([typeBuffer, data]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a packet with binary data in a base64 string
|
||||
*
|
||||
* @param {Object} packet, has `type` and `data`
|
||||
* @return {String} base64 encoded message
|
||||
*/
|
||||
|
||||
export function encodeBase64Packet (packet, callback){
|
||||
var data = Buffer.isBuffer(packet.data) ? packet.data : arrayBufferToBuffer(packet.data);
|
||||
var message = 'b' + packets[packet.type];
|
||||
message += data.toString('base64');
|
||||
return callback(message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes a packet. Data also available as an ArrayBuffer if requested.
|
||||
*
|
||||
* @return {Object} with `type` and `data` (if any)
|
||||
* @api private
|
||||
*/
|
||||
|
||||
export function decodePacket (data, binaryType, utf8decode) {
|
||||
if (data === undefined) {
|
||||
return err;
|
||||
}
|
||||
|
||||
var type;
|
||||
|
||||
// String data
|
||||
if (typeof data === 'string') {
|
||||
|
||||
type = data.charAt(0);
|
||||
|
||||
if (type === 'b') {
|
||||
return decodeBase64Packet(data.substr(1), binaryType);
|
||||
}
|
||||
|
||||
if (utf8decode) {
|
||||
data = tryDecode(data);
|
||||
if (data === false) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (Number(type) != type || !packetslist[type]) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (data.length > 1) {
|
||||
return { type: packetslist[type], data: data.substring(1) };
|
||||
} else {
|
||||
return { type: packetslist[type] };
|
||||
}
|
||||
}
|
||||
|
||||
// Binary data
|
||||
if (binaryType === 'arraybuffer') {
|
||||
// wrap Buffer/ArrayBuffer data into an Uint8Array
|
||||
var intArray = new Uint8Array(data);
|
||||
type = intArray[0];
|
||||
return { type: packetslist[type], data: intArray.buffer.slice(1) };
|
||||
}
|
||||
|
||||
if (data instanceof ArrayBuffer) {
|
||||
data = arrayBufferToBuffer(data);
|
||||
}
|
||||
type = data[0];
|
||||
return { type: packetslist[type], data: data.slice(1) };
|
||||
};
|
||||
|
||||
function tryDecode(data) {
|
||||
try {
|
||||
data = utf8.decode(data, { strict: false });
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a packet encoded in a base64 string.
|
||||
*
|
||||
* @param {String} base64 encoded message
|
||||
* @return {Object} with `type` and `data` (if any)
|
||||
*/
|
||||
|
||||
export function decodeBase64Packet (msg, binaryType) {
|
||||
var type = packetslist[msg.charAt(0)];
|
||||
var data = Buffer.from(msg.substr(1), 'base64');
|
||||
if (binaryType === 'arraybuffer') {
|
||||
var abv = new Uint8Array(data.length);
|
||||
for (var i = 0; i < abv.length; i++){
|
||||
abv[i] = data[i];
|
||||
}
|
||||
// @ts-ignore
|
||||
data = abv.buffer;
|
||||
}
|
||||
return { type: type, data: data };
|
||||
};
|
||||
|
||||
/**
|
||||
* Encodes multiple messages (payload).
|
||||
*
|
||||
* <length>:data
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* 11:hello world2:hi
|
||||
*
|
||||
* If any contents are binary, they will be encoded as base64 strings. Base64
|
||||
* encoded strings are marked with a b before the length specifier
|
||||
*
|
||||
* @param {Array} packets
|
||||
* @api private
|
||||
*/
|
||||
|
||||
export function encodePayload (packets, supportsBinary, callback) {
|
||||
if (typeof supportsBinary === 'function') {
|
||||
callback = supportsBinary;
|
||||
supportsBinary = null;
|
||||
}
|
||||
|
||||
if (supportsBinary && hasBinary(packets)) {
|
||||
return encodePayloadAsBinary(packets, callback);
|
||||
}
|
||||
|
||||
if (!packets.length) {
|
||||
return callback('0:');
|
||||
}
|
||||
|
||||
function encodeOne(packet, doneCallback) {
|
||||
encodePacket(packet, supportsBinary, false, function(message) {
|
||||
doneCallback(null, setLengthHeader(message));
|
||||
});
|
||||
}
|
||||
|
||||
map(packets, encodeOne, function(err, results) {
|
||||
return callback(results.join(''));
|
||||
});
|
||||
};
|
||||
|
||||
function setLengthHeader(message) {
|
||||
return message.length + ':' + message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Async array map using after
|
||||
*/
|
||||
|
||||
function map(ary, each, done) {
|
||||
const results = new Array(ary.length);
|
||||
let count = 0;
|
||||
|
||||
for (let i = 0; i < ary.length; i++) {
|
||||
each(ary[i], (error, msg) => {
|
||||
results[i] = msg;
|
||||
if (++count === ary.length) {
|
||||
done(null, results);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Decodes data when a payload is maybe expected. Possible binary contents are
|
||||
* decoded from their base64 representation
|
||||
*
|
||||
* @param {String} data, callback method
|
||||
* @api public
|
||||
*/
|
||||
|
||||
export function decodePayload (data, binaryType, callback) {
|
||||
if (typeof data !== 'string') {
|
||||
return decodePayloadAsBinary(data, binaryType, callback);
|
||||
}
|
||||
|
||||
if (typeof binaryType === 'function') {
|
||||
callback = binaryType;
|
||||
binaryType = null;
|
||||
}
|
||||
|
||||
if (data === '') {
|
||||
// parser error - ignoring payload
|
||||
return callback(err, 0, 1);
|
||||
}
|
||||
|
||||
var length = '', n, msg, packet;
|
||||
|
||||
for (var i = 0, l = data.length; i < l; i++) {
|
||||
var chr = data.charAt(i);
|
||||
|
||||
if (chr !== ':') {
|
||||
length += chr;
|
||||
continue;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (length === '' || (length != (n = Number(length)))) {
|
||||
// parser error - ignoring payload
|
||||
return callback(err, 0, 1);
|
||||
}
|
||||
|
||||
msg = data.substr(i + 1, n);
|
||||
|
||||
if (length != msg.length) {
|
||||
// parser error - ignoring payload
|
||||
return callback(err, 0, 1);
|
||||
}
|
||||
|
||||
if (msg.length) {
|
||||
packet = decodePacket(msg, binaryType, false);
|
||||
|
||||
if (err.type === packet.type && err.data === packet.data) {
|
||||
// parser error in individual packet - ignoring payload
|
||||
return callback(err, 0, 1);
|
||||
}
|
||||
|
||||
var more = callback(packet, i + n, l);
|
||||
if (false === more) return;
|
||||
}
|
||||
|
||||
// advance cursor
|
||||
i += n;
|
||||
length = '';
|
||||
}
|
||||
|
||||
if (length !== '') {
|
||||
// parser error - ignoring payload
|
||||
return callback(err, 0, 1);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Converts a buffer to a utf8.js encoded string
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function bufferToString(buffer) {
|
||||
var str = '';
|
||||
for (var i = 0, l = buffer.length; i < l; i++) {
|
||||
str += String.fromCharCode(buffer[i]);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Converts a utf8.js encoded string to a buffer
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function stringToBuffer(string) {
|
||||
var buf = Buffer.allocUnsafe(string.length);
|
||||
for (var i = 0, l = string.length; i < l; i++) {
|
||||
buf.writeUInt8(string.charCodeAt(i), i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Converts an ArrayBuffer to a Buffer
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function arrayBufferToBuffer(data) {
|
||||
// data is either an ArrayBuffer or ArrayBufferView.
|
||||
var length = data.byteLength || data.length;
|
||||
var offset = data.byteOffset || 0;
|
||||
|
||||
return Buffer.from(data.buffer || data, offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes multiple messages (payload) as binary.
|
||||
*
|
||||
* <1 = binary, 0 = string><number from 0-9><number from 0-9>[...]<number
|
||||
* 255><data>
|
||||
*
|
||||
* Example:
|
||||
* 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers
|
||||
*
|
||||
* @param {Array} packets
|
||||
* @return {Buffer} encoded payload
|
||||
* @api private
|
||||
*/
|
||||
|
||||
export function encodePayloadAsBinary (packets, callback) {
|
||||
if (!packets.length) {
|
||||
return callback(EMPTY_BUFFER);
|
||||
}
|
||||
|
||||
map(packets, encodeOneBinaryPacket, function(err, results) {
|
||||
return callback(Buffer.concat(results));
|
||||
});
|
||||
};
|
||||
|
||||
function encodeOneBinaryPacket(p, doneCallback) {
|
||||
|
||||
function onBinaryPacketEncode(packet) {
|
||||
|
||||
var encodingLength = '' + packet.length;
|
||||
var sizeBuffer;
|
||||
|
||||
if (typeof packet === 'string') {
|
||||
sizeBuffer = Buffer.allocUnsafe(encodingLength.length + 2);
|
||||
sizeBuffer[0] = 0; // is a string (not true binary = 0)
|
||||
for (var i = 0; i < encodingLength.length; i++) {
|
||||
sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
|
||||
}
|
||||
sizeBuffer[sizeBuffer.length - 1] = 255;
|
||||
return doneCallback(null, Buffer.concat([sizeBuffer, stringToBuffer(packet)]));
|
||||
}
|
||||
|
||||
sizeBuffer = Buffer.allocUnsafe(encodingLength.length + 2);
|
||||
sizeBuffer[0] = 1; // is binary (true binary = 1)
|
||||
for (var i = 0; i < encodingLength.length; i++) {
|
||||
sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
|
||||
}
|
||||
sizeBuffer[sizeBuffer.length - 1] = 255;
|
||||
|
||||
doneCallback(null, Buffer.concat([sizeBuffer, packet]));
|
||||
}
|
||||
|
||||
encodePacket(p, true, true, onBinaryPacketEncode);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Decodes data when a payload is maybe expected. Strings are decoded by
|
||||
* interpreting each byte as a key code for entries marked to start with 0. See
|
||||
* description of encodePayloadAsBinary
|
||||
|
||||
* @param {Buffer} data, callback method
|
||||
* @api public
|
||||
*/
|
||||
|
||||
export function decodePayloadAsBinary (data, binaryType, callback) {
|
||||
if (typeof binaryType === 'function') {
|
||||
callback = binaryType;
|
||||
binaryType = null;
|
||||
}
|
||||
|
||||
var bufferTail = data;
|
||||
var buffers = [];
|
||||
var i;
|
||||
|
||||
while (bufferTail.length > 0) {
|
||||
var strLen = '';
|
||||
var isString = bufferTail[0] === 0;
|
||||
for (i = 1; ; i++) {
|
||||
if (bufferTail[i] === 255) break;
|
||||
// 310 = char length of Number.MAX_VALUE
|
||||
if (strLen.length > 310) {
|
||||
return callback(err, 0, 1);
|
||||
}
|
||||
strLen += '' + bufferTail[i];
|
||||
}
|
||||
bufferTail = bufferTail.slice(strLen.length + 1);
|
||||
|
||||
var msgLength = parseInt(strLen, 10);
|
||||
|
||||
var msg = bufferTail.slice(1, msgLength + 1);
|
||||
if (isString) msg = bufferToString(msg);
|
||||
buffers.push(msg);
|
||||
bufferTail = bufferTail.slice(msgLength + 1);
|
||||
}
|
||||
|
||||
var total = buffers.length;
|
||||
for (i = 0; i < total; i++) {
|
||||
var buffer = buffers[i];
|
||||
callback(decodePacket(buffer, binaryType, true), i, total);
|
||||
}
|
||||
};
|
||||
210
packages/websocket/src/engine.io/parser-v3/utf8.ts
Normal file
210
packages/websocket/src/engine.io/parser-v3/utf8.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
/*! https://mths.be/utf8js v2.1.2 by @mathias */
|
||||
|
||||
var stringFromCharCode = String.fromCharCode;
|
||||
|
||||
// Taken from https://mths.be/punycode
|
||||
function ucs2decode(string) {
|
||||
var output = [];
|
||||
var counter = 0;
|
||||
var length = string.length;
|
||||
var value;
|
||||
var extra;
|
||||
while (counter < length) {
|
||||
value = string.charCodeAt(counter++);
|
||||
if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
|
||||
// high surrogate, and there is a next character
|
||||
extra = string.charCodeAt(counter++);
|
||||
if ((extra & 0xFC00) == 0xDC00) { // low surrogate
|
||||
output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
|
||||
} else {
|
||||
// unmatched surrogate; only append this code unit, in case the next
|
||||
// code unit is the high surrogate of a surrogate pair
|
||||
output.push(value);
|
||||
counter--;
|
||||
}
|
||||
} else {
|
||||
output.push(value);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// Taken from https://mths.be/punycode
|
||||
function ucs2encode(array) {
|
||||
var length = array.length;
|
||||
var index = -1;
|
||||
var value;
|
||||
var output = '';
|
||||
while (++index < length) {
|
||||
value = array[index];
|
||||
if (value > 0xFFFF) {
|
||||
value -= 0x10000;
|
||||
output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
|
||||
value = 0xDC00 | value & 0x3FF;
|
||||
}
|
||||
output += stringFromCharCode(value);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function checkScalarValue(codePoint, strict) {
|
||||
if (codePoint >= 0xD800 && codePoint <= 0xDFFF) {
|
||||
if (strict) {
|
||||
throw Error(
|
||||
'Lone surrogate U+' + codePoint.toString(16).toUpperCase() +
|
||||
' is not a scalar value'
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
function createByte(codePoint, shift) {
|
||||
return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80);
|
||||
}
|
||||
|
||||
function encodeCodePoint(codePoint, strict) {
|
||||
if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence
|
||||
return stringFromCharCode(codePoint);
|
||||
}
|
||||
var symbol = '';
|
||||
if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence
|
||||
symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0);
|
||||
}
|
||||
else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence
|
||||
if (!checkScalarValue(codePoint, strict)) {
|
||||
codePoint = 0xFFFD;
|
||||
}
|
||||
symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0);
|
||||
symbol += createByte(codePoint, 6);
|
||||
}
|
||||
else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence
|
||||
symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0);
|
||||
symbol += createByte(codePoint, 12);
|
||||
symbol += createByte(codePoint, 6);
|
||||
}
|
||||
symbol += stringFromCharCode((codePoint & 0x3F) | 0x80);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
function utf8encode(string, opts) {
|
||||
opts = opts || {};
|
||||
var strict = false !== opts.strict;
|
||||
|
||||
var codePoints = ucs2decode(string);
|
||||
var length = codePoints.length;
|
||||
var index = -1;
|
||||
var codePoint;
|
||||
var byteString = '';
|
||||
while (++index < length) {
|
||||
codePoint = codePoints[index];
|
||||
byteString += encodeCodePoint(codePoint, strict);
|
||||
}
|
||||
return byteString;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
function readContinuationByte() {
|
||||
if (byteIndex >= byteCount) {
|
||||
throw Error('Invalid byte index');
|
||||
}
|
||||
|
||||
var continuationByte = byteArray[byteIndex] & 0xFF;
|
||||
byteIndex++;
|
||||
|
||||
if ((continuationByte & 0xC0) == 0x80) {
|
||||
return continuationByte & 0x3F;
|
||||
}
|
||||
|
||||
// If we end up here, it’s not a continuation byte
|
||||
throw Error('Invalid continuation byte');
|
||||
}
|
||||
|
||||
function decodeSymbol(strict) {
|
||||
var byte1;
|
||||
var byte2;
|
||||
var byte3;
|
||||
var byte4;
|
||||
var codePoint;
|
||||
|
||||
if (byteIndex > byteCount) {
|
||||
throw Error('Invalid byte index');
|
||||
}
|
||||
|
||||
if (byteIndex == byteCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read first byte
|
||||
byte1 = byteArray[byteIndex] & 0xFF;
|
||||
byteIndex++;
|
||||
|
||||
// 1-byte sequence (no continuation bytes)
|
||||
if ((byte1 & 0x80) == 0) {
|
||||
return byte1;
|
||||
}
|
||||
|
||||
// 2-byte sequence
|
||||
if ((byte1 & 0xE0) == 0xC0) {
|
||||
byte2 = readContinuationByte();
|
||||
codePoint = ((byte1 & 0x1F) << 6) | byte2;
|
||||
if (codePoint >= 0x80) {
|
||||
return codePoint;
|
||||
} else {
|
||||
throw Error('Invalid continuation byte');
|
||||
}
|
||||
}
|
||||
|
||||
// 3-byte sequence (may include unpaired surrogates)
|
||||
if ((byte1 & 0xF0) == 0xE0) {
|
||||
byte2 = readContinuationByte();
|
||||
byte3 = readContinuationByte();
|
||||
codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3;
|
||||
if (codePoint >= 0x0800) {
|
||||
return checkScalarValue(codePoint, strict) ? codePoint : 0xFFFD;
|
||||
} else {
|
||||
throw Error('Invalid continuation byte');
|
||||
}
|
||||
}
|
||||
|
||||
// 4-byte sequence
|
||||
if ((byte1 & 0xF8) == 0xF0) {
|
||||
byte2 = readContinuationByte();
|
||||
byte3 = readContinuationByte();
|
||||
byte4 = readContinuationByte();
|
||||
codePoint = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0C) |
|
||||
(byte3 << 0x06) | byte4;
|
||||
if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
|
||||
return codePoint;
|
||||
}
|
||||
}
|
||||
|
||||
throw Error('Invalid UTF-8 detected');
|
||||
}
|
||||
|
||||
var byteArray;
|
||||
var byteCount;
|
||||
var byteIndex;
|
||||
function utf8decode(byteString, opts) {
|
||||
opts = opts || {};
|
||||
var strict = false !== opts.strict;
|
||||
|
||||
byteArray = ucs2decode(byteString);
|
||||
byteCount = byteArray.length;
|
||||
byteIndex = 0;
|
||||
var codePoints = [];
|
||||
var tmp;
|
||||
while ((tmp = decodeSymbol(strict)) !== false) {
|
||||
codePoints.push(tmp);
|
||||
}
|
||||
return ucs2encode(codePoints);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
version: '2.1.2',
|
||||
encode: utf8encode,
|
||||
decode: utf8decode
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,121 +1,145 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import parser_v4 from "../engine.io-parser"
|
||||
import type { WebSocketClient } from '../server/client'
|
||||
import { EventEmitter } from "events";
|
||||
import * as parser_v4 from "../engine.io-parser";
|
||||
import * as parser_v3 from "./parser-v3";
|
||||
// import debugModule from "debug"
|
||||
// import { IncomingMessage } from "http"
|
||||
import { Packet } from "../engine.io-parser";
|
||||
|
||||
// const debug = debugModule("engine:transport")
|
||||
const debug = require("../debug")("engine:transport");
|
||||
|
||||
/**
|
||||
* Noop function.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function noop() { }
|
||||
function noop() {}
|
||||
|
||||
export abstract class Transport extends EventEmitter {
|
||||
public sid: string
|
||||
public req /**http.IncomingMessage */
|
||||
public socket: WebSocketClient
|
||||
public writable: boolean
|
||||
public readyState: string
|
||||
public discarded: boolean
|
||||
public protocol: Number
|
||||
public parser: any
|
||||
public perMessageDeflate: any
|
||||
public supportsBinary: boolean = false
|
||||
public sid: string;
|
||||
public writable: boolean;
|
||||
public protocol: number;
|
||||
|
||||
/**
|
||||
* Transport constructor.
|
||||
*
|
||||
* @param {http.IncomingMessage} request
|
||||
* @api public
|
||||
*/
|
||||
constructor(req) {
|
||||
super()
|
||||
this.readyState = "open"
|
||||
this.discarded = false
|
||||
this.protocol = req._query.EIO === "4" ? 4 : 3 // 3rd revision by default
|
||||
this.parser = parser_v4//= this.protocol === 4 ? parser_v4 : parser_v3
|
||||
protected _readyState: string;
|
||||
protected discarded: boolean;
|
||||
protected parser: any;
|
||||
// protected req: IncomingMessage & { cleanup: Function }
|
||||
protected req: { cleanup: Function };
|
||||
protected supportsBinary: boolean;
|
||||
|
||||
get readyState() {
|
||||
return this._readyState;
|
||||
}
|
||||
|
||||
set readyState(state) {
|
||||
debug(
|
||||
"readyState updated from %s to %s (%s)",
|
||||
this._readyState,
|
||||
state,
|
||||
this.name
|
||||
);
|
||||
this._readyState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport constructor.
|
||||
*
|
||||
* @param {http.IncomingMessage} request
|
||||
* @api public
|
||||
*/
|
||||
constructor(req) {
|
||||
super();
|
||||
this.readyState = "open";
|
||||
this.discarded = false;
|
||||
this.protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default
|
||||
this.parser = this.protocol === 4 ? parser_v4 : parser_v3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags the transport as discarded.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
discard() {
|
||||
this.discarded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with an incoming HTTP request.
|
||||
*
|
||||
* @param {http.IncomingMessage} request
|
||||
* @api protected
|
||||
*/
|
||||
protected onRequest(req) {
|
||||
debug("setting request");
|
||||
this.req = req;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the transport.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
close(fn?) {
|
||||
if ("closed" === this.readyState || "closing" === this.readyState) return;
|
||||
|
||||
this.readyState = "closing";
|
||||
this.doClose(fn || noop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with a transport error.
|
||||
*
|
||||
* @param {String} message error
|
||||
* @param {Object} error description
|
||||
* @api protected
|
||||
*/
|
||||
protected onError(msg: string, desc?) {
|
||||
if (this.listeners("error").length) {
|
||||
const err = new Error(msg);
|
||||
// @ts-ignore
|
||||
err.type = "TransportError";
|
||||
// @ts-ignore
|
||||
err.description = desc;
|
||||
this.emit("error", err);
|
||||
} else {
|
||||
debug("ignored transport error %s (%s)", msg, desc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags the transport as discarded.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
discard() {
|
||||
this.discarded = true
|
||||
}
|
||||
/**
|
||||
* Called with parsed out a packets from the data stream.
|
||||
*
|
||||
* @param {Object} packet
|
||||
* @api protected
|
||||
*/
|
||||
protected onPacket(packet: Packet) {
|
||||
this.emit("packet", packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with an incoming HTTP request.
|
||||
*
|
||||
* @param {http.IncomingMessage} request
|
||||
* @api private
|
||||
*/
|
||||
onRequest(req) {
|
||||
console.debug(`engine.io transport ${this.socket.id} setting request`, JSON.stringify(req))
|
||||
this.req = req
|
||||
}
|
||||
/**
|
||||
* Called with the encoded packet data.
|
||||
*
|
||||
* @param {String} data
|
||||
* @api protected
|
||||
*/
|
||||
protected onData(data) {
|
||||
this.onPacket(this.parser.decodePacket(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the transport.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
close(fn?) {
|
||||
if ("closed" === this.readyState || "closing" === this.readyState) return
|
||||
/**
|
||||
* Called upon transport close.
|
||||
*
|
||||
* @api protected
|
||||
*/
|
||||
protected onClose() {
|
||||
this.readyState = "closed";
|
||||
this.emit("close");
|
||||
}
|
||||
|
||||
this.readyState = "closing"
|
||||
this.doClose(fn || noop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with a transport error.
|
||||
*
|
||||
* @param {String} message error
|
||||
* @param {Object} error description
|
||||
* @api private
|
||||
*/
|
||||
onError(msg: string, desc?: string) {
|
||||
if (this.listeners("error").length) {
|
||||
const err: any = new Error(msg)
|
||||
err.type = "TransportError"
|
||||
err.description = desc
|
||||
this.emit("error", err)
|
||||
} else {
|
||||
console.debug(`ignored transport error ${msg} (${desc})`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with parsed out a packets from the data stream.
|
||||
*
|
||||
* @param {Object} packet
|
||||
* @api private
|
||||
*/
|
||||
onPacket(packet) {
|
||||
this.emit("packet", packet)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with the encoded packet data.
|
||||
*
|
||||
* @param {String} data
|
||||
* @api private
|
||||
*/
|
||||
onData(data) {
|
||||
this.onPacket(this.parser.decodePacket(data))
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon transport close.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
onClose() {
|
||||
this.readyState = "closed"
|
||||
this.emit("close")
|
||||
}
|
||||
abstract get supportsFraming()
|
||||
abstract get name()
|
||||
abstract send(...args: any[])
|
||||
abstract doClose(d: Function)
|
||||
abstract get supportsFraming();
|
||||
abstract get name();
|
||||
abstract send(packets);
|
||||
abstract doClose(fn?);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
// import { Polling as XHR } from "./polling"
|
||||
// import { JSONP } from "./polling-jsonp"
|
||||
import { WebSocket } from "./websocket";
|
||||
|
||||
export default {
|
||||
websocket: require("./websocket").WebSocket
|
||||
}
|
||||
// polling: polling,
|
||||
websocket: WebSocket,
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Polling polymorphic constructor.
|
||||
// *
|
||||
// * @api private
|
||||
// */
|
||||
|
||||
// function polling(req) {
|
||||
// if ("string" === typeof req._query.j) {
|
||||
// return new JSONP(req)
|
||||
// } else {
|
||||
// return new XHR(req)
|
||||
// }
|
||||
// }
|
||||
|
||||
// polling.upgradesTo = ["websocket"]
|
||||
|
||||
@@ -1,116 +1,111 @@
|
||||
import { Transport } from '../transport'
|
||||
// const debug = require("debug")("engine:ws")
|
||||
import { Transport } from "../transport";
|
||||
// import debugModule from "debug";
|
||||
|
||||
const debug = require("../../debug")("engine:ws");
|
||||
|
||||
export class WebSocket extends Transport {
|
||||
public perMessageDeflate: any
|
||||
protected perMessageDeflate: any;
|
||||
private socket: any;
|
||||
|
||||
/**
|
||||
* WebSocket transport
|
||||
*
|
||||
* @param {http.IncomingMessage}
|
||||
* @api public
|
||||
*/
|
||||
constructor(req) {
|
||||
super(req)
|
||||
this.socket = req.websocket
|
||||
this.socket.on("message", this.onData.bind(this))
|
||||
this.socket.once("close", this.onClose.bind(this))
|
||||
this.socket.on("error", this.onError.bind(this))
|
||||
this.writable = true
|
||||
this.perMessageDeflate = null
|
||||
/**
|
||||
* WebSocket transport
|
||||
*
|
||||
* @param {http.IncomingMessage}
|
||||
* @api public
|
||||
*/
|
||||
constructor(req) {
|
||||
super(req);
|
||||
this.socket = req.websocket;
|
||||
this.socket.on("message", (data, isBinary) => {
|
||||
const message = isBinary ? data : data.toString();
|
||||
debug('received "%s"', message);
|
||||
super.onData(message);
|
||||
});
|
||||
this.socket.once("close", this.onClose.bind(this));
|
||||
this.socket.on("error", this.onError.bind(this));
|
||||
this.writable = true;
|
||||
this.perMessageDeflate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
get name() {
|
||||
return "websocket";
|
||||
}
|
||||
|
||||
/**
|
||||
* Advertise upgrade support.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
get handlesUpgrades() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advertise framing support.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
get supportsFraming() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a packet payload.
|
||||
*
|
||||
* @param {Array} packets
|
||||
* @api private
|
||||
*/
|
||||
send(packets) {
|
||||
const packet = packets.shift();
|
||||
if (typeof packet === "undefined") {
|
||||
this.writable = true;
|
||||
this.emit("drain");
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
get name() {
|
||||
return "websocket"
|
||||
// always creates a new object since ws modifies it
|
||||
const opts: { compress?: boolean } = {};
|
||||
if (packet.options) {
|
||||
opts.compress = packet.options.compress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advertise upgrade support.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
get handlesUpgrades() {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Advertise framing support.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
get supportsFraming() {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the incoming data.
|
||||
*
|
||||
* @param {String} encoded packet
|
||||
* @api private
|
||||
*/
|
||||
onData(data) {
|
||||
// debug('received "%s"', data)
|
||||
super.onData(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a packet payload.
|
||||
*
|
||||
* @param {Array} packets
|
||||
* @api private
|
||||
*/
|
||||
send(packets) {
|
||||
// console.log('WebSocket send packets', JSON.stringify(packets))
|
||||
const packet = packets.shift()
|
||||
if (typeof packet === "undefined") {
|
||||
this.writable = true
|
||||
this.emit("drain")
|
||||
return
|
||||
const send = data => {
|
||||
if (this.perMessageDeflate) {
|
||||
const len =
|
||||
"string" === typeof data ? Buffer.byteLength(data) : data.length;
|
||||
if (len < this.perMessageDeflate.threshold) {
|
||||
opts.compress = false;
|
||||
}
|
||||
}
|
||||
debug('writing "%s"', data);
|
||||
this.writable = false;
|
||||
|
||||
// always creates a new object since ws modifies it
|
||||
const opts: any = {}
|
||||
if (packet.options) {
|
||||
opts.compress = packet.options.compress
|
||||
}
|
||||
this.socket.send(data, opts, err => {
|
||||
if (err) return this.onError("write error", err.stack);
|
||||
this.send(packets);
|
||||
});
|
||||
};
|
||||
|
||||
const send = data => {
|
||||
if (this.perMessageDeflate) {
|
||||
const len =
|
||||
"string" === typeof data ? Buffer.byteLength(data) : data.length
|
||||
if (len < this.perMessageDeflate.threshold) {
|
||||
opts.compress = false
|
||||
}
|
||||
}
|
||||
console.trace('writing', data)
|
||||
this.writable = false
|
||||
|
||||
this.socket.send(data, opts, err => {
|
||||
if (err) return this.onError("write error", err.stack)
|
||||
this.send(packets)
|
||||
})
|
||||
}
|
||||
|
||||
if (packet.options && typeof packet.options.wsPreEncoded === "string") {
|
||||
send(packet.options.wsPreEncoded)
|
||||
} else {
|
||||
this.parser.encodePacket(packet, this.supportsBinary, send)
|
||||
}
|
||||
if (packet.options && typeof packet.options.wsPreEncoded === "string") {
|
||||
send(packet.options.wsPreEncoded);
|
||||
} else {
|
||||
this.parser.encodePacket(packet, this.supportsBinary, send);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the transport.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
doClose(fn) {
|
||||
// debug("closing")
|
||||
this.socket.close()
|
||||
fn && fn()
|
||||
}
|
||||
/**
|
||||
* Closes the transport.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
doClose(fn) {
|
||||
debug("closing");
|
||||
this.socket.close();
|
||||
fn && fn();
|
||||
}
|
||||
}
|
||||
|
||||
65
packages/websocket/src/socket.io-adapter/contrib/yeast.ts
Normal file
65
packages/websocket/src/socket.io-adapter/contrib/yeast.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
// imported from https://github.com/unshiftio/yeast
|
||||
"use strict";
|
||||
|
||||
const alphabet =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(
|
||||
""
|
||||
),
|
||||
length = 64,
|
||||
map = {};
|
||||
let seed = 0,
|
||||
i = 0,
|
||||
prev;
|
||||
|
||||
/**
|
||||
* Return a string representing the specified number.
|
||||
*
|
||||
* @param {Number} num The number to convert.
|
||||
* @returns {String} The string representation of the number.
|
||||
* @api public
|
||||
*/
|
||||
export function encode(num) {
|
||||
let encoded = "";
|
||||
|
||||
do {
|
||||
encoded = alphabet[num % length] + encoded;
|
||||
num = Math.floor(num / length);
|
||||
} while (num > 0);
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the integer value specified by the given string.
|
||||
*
|
||||
* @param {String} str The string to convert.
|
||||
* @returns {Number} The integer value represented by the string.
|
||||
* @api public
|
||||
*/
|
||||
export function decode(str) {
|
||||
let decoded = 0;
|
||||
|
||||
for (i = 0; i < str.length; i++) {
|
||||
decoded = decoded * length + map[str.charAt(i)];
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Yeast: A tiny growing id generator.
|
||||
*
|
||||
* @returns {String} A unique id.
|
||||
* @api public
|
||||
*/
|
||||
export function yeast() {
|
||||
const now = encode(+new Date());
|
||||
|
||||
if (now !== prev) return (seed = 0), (prev = now);
|
||||
return now + "." + encode(seed++);
|
||||
}
|
||||
|
||||
//
|
||||
// Map each character to its index.
|
||||
//
|
||||
for (; i < length; i++) map[alphabet[i]] = i;
|
||||
@@ -1,279 +1,507 @@
|
||||
import { EventEmitter } from "events"
|
||||
import { EventEmitter } from "events";
|
||||
import { yeast } from "./contrib/yeast";
|
||||
// import WebSocket = require("ws");
|
||||
|
||||
export type SocketId = string
|
||||
export type Room = string
|
||||
// const canPreComputeFrame = typeof WebSocket?.Sender?.frame === "function";
|
||||
|
||||
/**
|
||||
* A public ID, sent by the server at the beginning of the Socket.IO session and which can be used for private messaging
|
||||
*/
|
||||
export type SocketId = string;
|
||||
/**
|
||||
* A private ID, sent by the server at the beginning of the Socket.IO session and used for connection state recovery
|
||||
* upon reconnection
|
||||
*/
|
||||
export type PrivateSessionId = string;
|
||||
|
||||
// we could extend the Room type to "string | number", but that would be a breaking change
|
||||
// related: https://github.com/socketio/socket.io-redis-adapter/issues/418
|
||||
export type Room = string;
|
||||
|
||||
export interface BroadcastFlags {
|
||||
volatile?: boolean
|
||||
compress?: boolean
|
||||
local?: boolean
|
||||
broadcast?: boolean
|
||||
binary?: boolean
|
||||
volatile?: boolean;
|
||||
compress?: boolean;
|
||||
local?: boolean;
|
||||
broadcast?: boolean;
|
||||
binary?: boolean;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export interface BroadcastOptions {
|
||||
rooms: Set<Room>
|
||||
except?: Set<SocketId>
|
||||
flags?: BroadcastFlags
|
||||
rooms: Set<Room>;
|
||||
except?: Set<Room>;
|
||||
flags?: BroadcastFlags;
|
||||
}
|
||||
|
||||
interface SessionToPersist {
|
||||
sid: SocketId;
|
||||
pid: PrivateSessionId;
|
||||
rooms: Room[];
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
export type Session = SessionToPersist & { missedPackets: unknown[][] };
|
||||
|
||||
export class Adapter extends EventEmitter {
|
||||
public rooms: Map<Room, Set<SocketId>> = new Map();
|
||||
public sids: Map<SocketId, Set<Room>> = new Map();
|
||||
private readonly encoder
|
||||
public rooms: Map<Room, Set<SocketId>> = new Map();
|
||||
public sids: Map<SocketId, Set<Room>> = new Map();
|
||||
private readonly encoder;
|
||||
|
||||
/**
|
||||
* In-memory adapter constructor.
|
||||
*
|
||||
* @param {Namespace} nsp
|
||||
*/
|
||||
constructor(readonly nsp: any) {
|
||||
super()
|
||||
this.encoder = nsp.server.encoder
|
||||
/**
|
||||
* In-memory adapter constructor.
|
||||
*
|
||||
* @param {Namespace} nsp
|
||||
*/
|
||||
constructor(readonly nsp: any) {
|
||||
super();
|
||||
this.encoder = nsp.server.encoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be overridden
|
||||
*/
|
||||
public init(): Promise<void> | void {}
|
||||
|
||||
/**
|
||||
* To be overridden
|
||||
*/
|
||||
public close(): Promise<void> | void {}
|
||||
|
||||
/**
|
||||
* Returns the number of Socket.IO servers in the cluster
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public serverCount(): Promise<number> {
|
||||
return Promise.resolve(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a socket to a list of room.
|
||||
*
|
||||
* @param {SocketId} id the socket id
|
||||
* @param {Set<Room>} rooms a set of rooms
|
||||
* @public
|
||||
*/
|
||||
public addAll(id: SocketId, rooms: Set<Room>): Promise<void> | void {
|
||||
if (!this.sids.has(id)) {
|
||||
this.sids.set(id, new Set());
|
||||
}
|
||||
|
||||
/**
|
||||
* To be overridden
|
||||
*/
|
||||
public init(): Promise<void> | void { }
|
||||
for (const room of rooms) {
|
||||
this.sids.get(id).add(room);
|
||||
|
||||
/**
|
||||
* To be overridden
|
||||
*/
|
||||
public close(): Promise<void> | void { }
|
||||
if (!this.rooms.has(room)) {
|
||||
this.rooms.set(room, new Set());
|
||||
this.emit("create-room", room);
|
||||
}
|
||||
if (!this.rooms.get(room).has(id)) {
|
||||
this.rooms.get(room).add(id);
|
||||
this.emit("join-room", room, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a socket to a list of room.
|
||||
*
|
||||
* @param {SocketId} id the socket id
|
||||
* @param {Set<Room>} rooms a set of rooms
|
||||
* @public
|
||||
*/
|
||||
public addAll(id: SocketId, rooms: Set<Room>): Promise<void> | void {
|
||||
if (!this.sids.has(id)) {
|
||||
this.sids.set(id, new Set())
|
||||
}
|
||||
|
||||
for (const room of rooms) {
|
||||
this.sids.get(id).add(room)
|
||||
|
||||
if (!this.rooms.has(room)) {
|
||||
this.rooms.set(room, new Set())
|
||||
this.emit("create-room", room)
|
||||
}
|
||||
if (!this.rooms.get(room).has(id)) {
|
||||
this.rooms.get(room).add(id)
|
||||
this.emit("join-room", room, id)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Removes a socket from a room.
|
||||
*
|
||||
* @param {SocketId} id the socket id
|
||||
* @param {Room} room the room name
|
||||
*/
|
||||
public del(id: SocketId, room: Room): Promise<void> | void {
|
||||
if (this.sids.has(id)) {
|
||||
this.sids.get(id).delete(room);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a socket from a room.
|
||||
*
|
||||
* @param {SocketId} id the socket id
|
||||
* @param {Room} room the room name
|
||||
*/
|
||||
public del(id: SocketId, room: Room): Promise<void> | void {
|
||||
if (this.sids.has(id)) {
|
||||
this.sids.get(id).delete(room)
|
||||
}
|
||||
this._del(room, id);
|
||||
}
|
||||
|
||||
this._del(room, id)
|
||||
private _del(room: Room, id: SocketId) {
|
||||
const _room = this.rooms.get(room);
|
||||
if (_room != null) {
|
||||
const deleted = _room.delete(id);
|
||||
if (deleted) {
|
||||
this.emit("leave-room", room, id);
|
||||
}
|
||||
if (_room.size === 0 && this.rooms.delete(room)) {
|
||||
this.emit("delete-room", room);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a socket from all rooms it's joined.
|
||||
*
|
||||
* @param {SocketId} id the socket id
|
||||
*/
|
||||
public delAll(id: SocketId): void {
|
||||
if (!this.sids.has(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
private _del(room, id) {
|
||||
for (const room of this.sids.get(id)) {
|
||||
this._del(room, id);
|
||||
}
|
||||
|
||||
this.sids.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a packet.
|
||||
*
|
||||
* Options:
|
||||
* - `flags` {Object} flags for this packet
|
||||
* - `except` {Array} sids that should be excluded
|
||||
* - `rooms` {Array} list of rooms to broadcast to
|
||||
*
|
||||
* @param {Object} packet the packet object
|
||||
* @param {Object} opts the options
|
||||
* @public
|
||||
*/
|
||||
public broadcast(packet: any, opts: BroadcastOptions): void {
|
||||
const flags = opts.flags || {};
|
||||
const packetOpts = {
|
||||
preEncoded: true,
|
||||
volatile: flags.volatile,
|
||||
compress: flags.compress,
|
||||
};
|
||||
|
||||
packet.nsp = this.nsp.name;
|
||||
const encodedPackets = this._encode(packet, packetOpts);
|
||||
|
||||
this.apply(opts, (socket) => {
|
||||
if (typeof socket.notifyOutgoingListeners === "function") {
|
||||
socket.notifyOutgoingListeners(packet);
|
||||
}
|
||||
|
||||
socket.client.writeToEngine(encodedPackets, packetOpts);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a packet and expects multiple acknowledgements.
|
||||
*
|
||||
* Options:
|
||||
* - `flags` {Object} flags for this packet
|
||||
* - `except` {Array} sids that should be excluded
|
||||
* - `rooms` {Array} list of rooms to broadcast to
|
||||
*
|
||||
* @param {Object} packet the packet object
|
||||
* @param {Object} opts the options
|
||||
* @param clientCountCallback - the number of clients that received the packet
|
||||
* @param ack - the callback that will be called for each client response
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public broadcastWithAck(
|
||||
packet: any,
|
||||
opts: BroadcastOptions,
|
||||
clientCountCallback: (clientCount: number) => void,
|
||||
ack: (...args: any[]) => void
|
||||
) {
|
||||
const flags = opts.flags || {};
|
||||
const packetOpts = {
|
||||
preEncoded: true,
|
||||
volatile: flags.volatile,
|
||||
compress: flags.compress,
|
||||
};
|
||||
|
||||
packet.nsp = this.nsp.name;
|
||||
// we can use the same id for each packet, since the _ids counter is common (no duplicate)
|
||||
packet.id = this.nsp._ids++;
|
||||
|
||||
const encodedPackets = this._encode(packet, packetOpts);
|
||||
|
||||
let clientCount = 0;
|
||||
|
||||
this.apply(opts, (socket) => {
|
||||
// track the total number of acknowledgements that are expected
|
||||
clientCount++;
|
||||
// call the ack callback for each client response
|
||||
socket.acks.set(packet.id, ack);
|
||||
|
||||
if (typeof socket.notifyOutgoingListeners === "function") {
|
||||
socket.notifyOutgoingListeners(packet);
|
||||
}
|
||||
|
||||
socket.client.writeToEngine(encodedPackets, packetOpts);
|
||||
});
|
||||
|
||||
clientCountCallback(clientCount);
|
||||
}
|
||||
|
||||
private _encode(packet: unknown, packetOpts: Record<string, unknown>) {
|
||||
const encodedPackets = this.encoder.encode(packet);
|
||||
|
||||
// if (
|
||||
// canPreComputeFrame &&
|
||||
// encodedPackets.length === 1 &&
|
||||
// typeof encodedPackets[0] === "string"
|
||||
// ) {
|
||||
// // "4" being the "message" packet type in the Engine.IO protocol
|
||||
// const data = Buffer.from("4" + encodedPackets[0]);
|
||||
// // see https://github.com/websockets/ws/issues/617#issuecomment-283002469
|
||||
// packetOpts.wsPreEncodedFrame = WebSocket.Sender.frame(data, {
|
||||
// readOnly: false,
|
||||
// mask: false,
|
||||
// rsv1: false,
|
||||
// opcode: 1,
|
||||
// fin: true,
|
||||
// });
|
||||
// }
|
||||
|
||||
return encodedPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of sockets by sid.
|
||||
*
|
||||
* @param {Set<Room>} rooms the explicit set of rooms to check.
|
||||
*/
|
||||
public sockets(rooms: Set<Room>): Promise<Set<SocketId>> {
|
||||
const sids = new Set<SocketId>();
|
||||
|
||||
this.apply({ rooms }, (socket) => {
|
||||
sids.add(socket.id);
|
||||
});
|
||||
|
||||
return Promise.resolve(sids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of rooms a given socket has joined.
|
||||
*
|
||||
* @param {SocketId} id the socket id
|
||||
*/
|
||||
public socketRooms(id: SocketId): Set<Room> | undefined {
|
||||
return this.sids.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the matching socket instances
|
||||
*
|
||||
* @param opts - the filters to apply
|
||||
*/
|
||||
public fetchSockets(opts: BroadcastOptions): Promise<any[]> {
|
||||
const sockets = [];
|
||||
|
||||
this.apply(opts, (socket) => {
|
||||
sockets.push(socket);
|
||||
});
|
||||
|
||||
return Promise.resolve(sockets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the matching socket instances join the specified rooms
|
||||
*
|
||||
* @param opts - the filters to apply
|
||||
* @param rooms - the rooms to join
|
||||
*/
|
||||
public addSockets(opts: BroadcastOptions, rooms: Room[]): void {
|
||||
this.apply(opts, (socket) => {
|
||||
socket.join(rooms);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the matching socket instances leave the specified rooms
|
||||
*
|
||||
* @param opts - the filters to apply
|
||||
* @param rooms - the rooms to leave
|
||||
*/
|
||||
public delSockets(opts: BroadcastOptions, rooms: Room[]): void {
|
||||
this.apply(opts, (socket) => {
|
||||
rooms.forEach((room) => socket.leave(room));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the matching socket instances disconnect
|
||||
*
|
||||
* @param opts - the filters to apply
|
||||
* @param close - whether to close the underlying connection
|
||||
*/
|
||||
public disconnectSockets(opts: BroadcastOptions, close: boolean): void {
|
||||
this.apply(opts, (socket) => {
|
||||
socket.disconnect(close);
|
||||
});
|
||||
}
|
||||
|
||||
private apply(opts: BroadcastOptions, callback: (socket) => void): void {
|
||||
const rooms = opts.rooms;
|
||||
const except = this.computeExceptSids(opts.except);
|
||||
|
||||
if (rooms.size) {
|
||||
const ids = new Set();
|
||||
for (const room of rooms) {
|
||||
if (!this.rooms.has(room)) continue;
|
||||
|
||||
for (const id of this.rooms.get(room)) {
|
||||
if (ids.has(id) || except.has(id)) continue;
|
||||
const socket = this.nsp.sockets.get(id);
|
||||
if (socket) {
|
||||
callback(socket);
|
||||
ids.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const [id] of this.sids) {
|
||||
if (except.has(id)) continue;
|
||||
const socket = this.nsp.sockets.get(id);
|
||||
if (socket) callback(socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private computeExceptSids(exceptRooms?: Set<Room>) {
|
||||
const exceptSids = new Set();
|
||||
if (exceptRooms && exceptRooms.size > 0) {
|
||||
for (const room of exceptRooms) {
|
||||
if (this.rooms.has(room)) {
|
||||
const deleted = this.rooms.get(room).delete(id)
|
||||
if (deleted) {
|
||||
this.emit("leave-room", room, id)
|
||||
}
|
||||
if (this.rooms.get(room).size === 0) {
|
||||
this.rooms.delete(room)
|
||||
this.emit("delete-room", room)
|
||||
}
|
||||
this.rooms.get(room).forEach((sid) => exceptSids.add(sid));
|
||||
}
|
||||
}
|
||||
}
|
||||
return exceptSids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a socket from all rooms it's joined.
|
||||
*
|
||||
* @param {SocketId} id the socket id
|
||||
*/
|
||||
public delAll(id: SocketId): void {
|
||||
if (!this.sids.has(id)) {
|
||||
return
|
||||
}
|
||||
/**
|
||||
* Send a packet to the other Socket.IO servers in the cluster
|
||||
* @param packet - an array of arguments, which may include an acknowledgement callback at the end
|
||||
*/
|
||||
public serverSideEmit(packet: any[]): void {
|
||||
console.warn(
|
||||
"this adapter does not support the serverSideEmit() functionality"
|
||||
);
|
||||
}
|
||||
|
||||
for (const room of this.sids.get(id)) {
|
||||
this._del(room, id)
|
||||
}
|
||||
/**
|
||||
* Save the client session in order to restore it upon reconnection.
|
||||
*/
|
||||
public persistSession(session: SessionToPersist) {}
|
||||
|
||||
this.sids.delete(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a packet.
|
||||
*
|
||||
* Options:
|
||||
* - `flags` {Object} flags for this packet
|
||||
* - `except` {Array} sids that should be excluded
|
||||
* - `rooms` {Array} list of rooms to broadcast to
|
||||
*
|
||||
* @param {Object} packet the packet object
|
||||
* @param {Object} opts the options
|
||||
* @public
|
||||
*/
|
||||
public broadcast(packet: any, opts: BroadcastOptions): void {
|
||||
const flags = opts.flags || {}
|
||||
const basePacketOpts = {
|
||||
preEncoded: true,
|
||||
volatile: flags.volatile,
|
||||
compress: flags.compress
|
||||
}
|
||||
|
||||
packet.nsp = this.nsp.name
|
||||
const encodedPackets = this.encoder.encode(packet)
|
||||
|
||||
const packetOpts = encodedPackets.map(encodedPacket => {
|
||||
if (typeof encodedPacket === "string") {
|
||||
return {
|
||||
...basePacketOpts,
|
||||
wsPreEncoded: "4" + encodedPacket // "4" being the "message" packet type in Engine.IO
|
||||
}
|
||||
} else {
|
||||
return basePacketOpts
|
||||
}
|
||||
})
|
||||
|
||||
this.apply(opts, socket => {
|
||||
for (let i = 0; i < encodedPackets.length; i++) {
|
||||
socket.client.writeToEngine(encodedPackets[i], packetOpts[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of sockets by sid.
|
||||
*
|
||||
* @param {Set<Room>} rooms the explicit set of rooms to check.
|
||||
*/
|
||||
public sockets(rooms: Set<Room>): Promise<Set<SocketId>> {
|
||||
const sids = new Set<SocketId>()
|
||||
|
||||
this.apply({ rooms }, socket => {
|
||||
sids.add(socket.id)
|
||||
})
|
||||
|
||||
return Promise.resolve(sids)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of rooms a given socket has joined.
|
||||
*
|
||||
* @param {SocketId} id the socket id
|
||||
*/
|
||||
public socketRooms(id: SocketId): Set<Room> | undefined {
|
||||
return this.sids.get(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the matching socket instances
|
||||
*
|
||||
* @param opts - the filters to apply
|
||||
*/
|
||||
public fetchSockets(opts: BroadcastOptions): Promise<any[]> {
|
||||
const sockets = []
|
||||
|
||||
this.apply(opts, socket => {
|
||||
sockets.push(socket)
|
||||
})
|
||||
|
||||
return Promise.resolve(sockets)
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the matching socket instances join the specified rooms
|
||||
*
|
||||
* @param opts - the filters to apply
|
||||
* @param rooms - the rooms to join
|
||||
*/
|
||||
public addSockets(opts: BroadcastOptions, rooms: Room[]): void {
|
||||
this.apply(opts, socket => {
|
||||
socket.join(rooms)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the matching socket instances leave the specified rooms
|
||||
*
|
||||
* @param opts - the filters to apply
|
||||
* @param rooms - the rooms to leave
|
||||
*/
|
||||
public delSockets(opts: BroadcastOptions, rooms: Room[]): void {
|
||||
this.apply(opts, socket => {
|
||||
rooms.forEach(room => socket.leave(room))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the matching socket instances disconnect
|
||||
*
|
||||
* @param opts - the filters to apply
|
||||
* @param close - whether to close the underlying connection
|
||||
*/
|
||||
public disconnectSockets(opts: BroadcastOptions, close: boolean): void {
|
||||
this.apply(opts, socket => {
|
||||
socket.disconnect(close)
|
||||
})
|
||||
}
|
||||
|
||||
private apply(opts: BroadcastOptions, callback: (socket) => void): void {
|
||||
const rooms = opts.rooms
|
||||
const except = this.computeExceptSids(opts.except)
|
||||
|
||||
if (rooms.size) {
|
||||
const ids = new Set()
|
||||
for (const room of rooms) {
|
||||
if (!this.rooms.has(room)) continue
|
||||
|
||||
for (const id of this.rooms.get(room)) {
|
||||
if (ids.has(id) || except.has(id)) continue
|
||||
const socket = this.nsp.sockets.get(id)
|
||||
if (socket) {
|
||||
callback(socket)
|
||||
ids.add(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const [id] of this.sids) {
|
||||
if (except.has(id)) continue
|
||||
const socket = this.nsp.sockets.get(id)
|
||||
if (socket) callback(socket)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private computeExceptSids(exceptRooms?: Set<Room>) {
|
||||
const exceptSids = new Set()
|
||||
if (exceptRooms && exceptRooms.size > 0) {
|
||||
for (const room of exceptRooms) {
|
||||
if (this.rooms.has(room)) {
|
||||
this.rooms.get(room).forEach(sid => exceptSids.add(sid))
|
||||
}
|
||||
}
|
||||
}
|
||||
return exceptSids
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a packet to the other Socket.IO servers in the cluster
|
||||
* @param packet - an array of arguments, which may include an acknowledgement callback at the end
|
||||
*/
|
||||
public serverSideEmit(packet: any[]): void {
|
||||
throw new Error(
|
||||
"this adapter does not support the serverSideEmit() functionality"
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Restore the session and find the packets that were missed by the client.
|
||||
* @param pid
|
||||
* @param offset
|
||||
*/
|
||||
public restoreSession(
|
||||
pid: PrivateSessionId,
|
||||
offset: string
|
||||
): Session {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
interface PersistedPacket {
|
||||
id: string;
|
||||
emittedAt: number;
|
||||
data: unknown[];
|
||||
opts: BroadcastOptions;
|
||||
}
|
||||
|
||||
type SessionWithTimestamp = SessionToPersist & { disconnectedAt: number };
|
||||
|
||||
export class SessionAwareAdapter extends Adapter {
|
||||
private readonly maxDisconnectionDuration: number;
|
||||
|
||||
private sessions: Map<PrivateSessionId, SessionWithTimestamp> = new Map();
|
||||
private packets: PersistedPacket[] = [];
|
||||
|
||||
constructor(readonly nsp: any) {
|
||||
super(nsp);
|
||||
this.maxDisconnectionDuration =
|
||||
nsp.server.opts.connectionStateRecovery.maxDisconnectionDuration;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
const threshold = Date.now() - this.maxDisconnectionDuration;
|
||||
this.sessions.forEach((session, sessionId) => {
|
||||
const hasExpired = session.disconnectedAt < threshold;
|
||||
if (hasExpired) {
|
||||
this.sessions.delete(sessionId);
|
||||
}
|
||||
});
|
||||
for (let i = this.packets.length - 1; i >= 0; i--) {
|
||||
const hasExpired = this.packets[i].emittedAt < threshold;
|
||||
if (hasExpired) {
|
||||
this.packets.splice(0, i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, 60 * 1000);
|
||||
// prevents the timer from keeping the process alive
|
||||
timer.unref();
|
||||
}
|
||||
|
||||
override persistSession(session: SessionToPersist) {
|
||||
(session as SessionWithTimestamp).disconnectedAt = Date.now();
|
||||
this.sessions.set(session.pid, session as SessionWithTimestamp);
|
||||
}
|
||||
|
||||
override restoreSession(
|
||||
pid: PrivateSessionId,
|
||||
offset: string
|
||||
): Session {
|
||||
const session = this.sessions.get(pid);
|
||||
if (!session) {
|
||||
// the session may have expired
|
||||
return null;
|
||||
}
|
||||
const hasExpired =
|
||||
session.disconnectedAt + this.maxDisconnectionDuration < Date.now();
|
||||
if (hasExpired) {
|
||||
// the session has expired
|
||||
this.sessions.delete(pid);
|
||||
return null;
|
||||
}
|
||||
const index = this.packets.findIndex((packet) => packet.id === offset);
|
||||
if (index === -1) {
|
||||
// the offset may be too old
|
||||
return null;
|
||||
}
|
||||
const missedPackets = [];
|
||||
for (let i = index + 1; i < this.packets.length; i++) {
|
||||
const packet = this.packets[i];
|
||||
if (shouldIncludePacket(session.rooms, packet.opts)) {
|
||||
missedPackets.push(packet.data);
|
||||
}
|
||||
}
|
||||
return {
|
||||
...session,
|
||||
missedPackets,
|
||||
};
|
||||
}
|
||||
|
||||
override broadcast(packet: any, opts: BroadcastOptions) {
|
||||
const isEventPacket = packet.type === 2;
|
||||
// packets with acknowledgement are not stored because the acknowledgement function cannot be serialized and
|
||||
// restored on another server upon reconnection
|
||||
const withoutAcknowledgement = packet.id === undefined;
|
||||
const notVolatile = opts.flags?.volatile === undefined;
|
||||
if (isEventPacket && withoutAcknowledgement && notVolatile) {
|
||||
const id = yeast();
|
||||
// the offset is stored at the end of the data array, so the client knows the ID of the last packet it has
|
||||
// processed (and the format is backward-compatible)
|
||||
packet.data.push(id);
|
||||
this.packets.push({
|
||||
id,
|
||||
opts,
|
||||
data: packet.data,
|
||||
emittedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
super.broadcast(packet, opts);
|
||||
}
|
||||
}
|
||||
|
||||
function shouldIncludePacket(
|
||||
sessionRooms: Room[],
|
||||
opts: BroadcastOptions
|
||||
): boolean {
|
||||
const included =
|
||||
opts.rooms.size === 0 || sessionRooms.some((room) => opts.rooms.has(room));
|
||||
const notExcluded = sessionRooms.every((room) => !opts.except.has(room));
|
||||
return included && notExcluded;
|
||||
}
|
||||
|
||||
77
packages/websocket/src/socket.io-client/contrib/backo2.ts
Normal file
77
packages/websocket/src/socket.io-client/contrib/backo2.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Initialize backoff timer with `opts`.
|
||||
*
|
||||
* - `min` initial timeout in milliseconds [100]
|
||||
* - `max` max timeout [10000]
|
||||
* - `jitter` [0]
|
||||
* - `factor` [2]
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @api public
|
||||
*/
|
||||
|
||||
export function Backoff(this: any, opts) {
|
||||
opts = opts || {}
|
||||
this.ms = opts.min || 100
|
||||
this.max = opts.max || 10000
|
||||
this.factor = opts.factor || 2
|
||||
this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0
|
||||
this.attempts = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the backoff duration.
|
||||
*
|
||||
* @return {Number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Backoff.prototype.duration = function () {
|
||||
var ms = this.ms * Math.pow(this.factor, this.attempts++)
|
||||
if (this.jitter) {
|
||||
var rand = Math.random()
|
||||
var deviation = Math.floor(rand * this.jitter * ms)
|
||||
ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation
|
||||
}
|
||||
return Math.min(ms, this.max) | 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the number of attempts.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Backoff.prototype.reset = function () {
|
||||
this.attempts = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum duration
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Backoff.prototype.setMin = function (min) {
|
||||
this.ms = min
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum duration
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Backoff.prototype.setMax = function (max) {
|
||||
this.max = max
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the jitter
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Backoff.prototype.setJitter = function (jitter) {
|
||||
this.jitter = jitter
|
||||
}
|
||||
@@ -1,19 +1,14 @@
|
||||
import { url } from "./url"
|
||||
import { Manager, ManagerOptions } from "./manager"
|
||||
import { Socket, SocketOptions } from "./socket"
|
||||
// import debugModule from "debug"; // debug()
|
||||
|
||||
const debug = require("../debug")("socket.io-client")
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = exports = lookup
|
||||
|
||||
/**
|
||||
* Managers cache.
|
||||
*/
|
||||
const cache: Record<string, Manager> = (exports.managers = {})
|
||||
const cache: Record<string, Manager> = {}
|
||||
|
||||
/**
|
||||
* Looks up an existing `Manager` for multiplexing.
|
||||
@@ -76,6 +71,15 @@ function lookup(
|
||||
return io.socket(parsed.path, opts)
|
||||
}
|
||||
|
||||
// so that "lookup" can be used both as a function (e.g. `io(...)`) and as a
|
||||
// namespace (e.g. `io.connect(...)`), for backward compatibility
|
||||
Object.assign(lookup, {
|
||||
Manager,
|
||||
Socket,
|
||||
io: lookup,
|
||||
connect: lookup,
|
||||
})
|
||||
|
||||
/**
|
||||
* Protocol version.
|
||||
*
|
||||
@@ -84,22 +88,18 @@ function lookup(
|
||||
|
||||
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
|
||||
export {
|
||||
Manager,
|
||||
ManagerOptions,
|
||||
Socket,
|
||||
SocketOptions,
|
||||
lookup as io,
|
||||
lookup as connect,
|
||||
lookup as default,
|
||||
}
|
||||
|
||||
@@ -1,210 +1,26 @@
|
||||
import eio from "../engine.io-client"
|
||||
import { Socket, SocketOptions } from "./socket"
|
||||
import {
|
||||
Socket as Engine,
|
||||
SocketOptions as EngineOptions,
|
||||
installTimerFunctions,
|
||||
nextTick,
|
||||
} from "../engine.io-client"
|
||||
import { Socket, SocketOptions, DisconnectDescription } from "./socket.js"
|
||||
// import * as parser from "socket.io-parser"
|
||||
import * as parser from "../socket.io-parser"
|
||||
// import { Decoder, Encoder, Packet } from "socket.io-parser"
|
||||
import { Decoder, Encoder, Packet } from "../socket.io-parser"
|
||||
import { on } from "./on"
|
||||
import * as Backoff from "backo2"
|
||||
import { on } from "./on.js"
|
||||
import { Backoff } from "./contrib/backo2.js"
|
||||
import {
|
||||
DefaultEventsMap,
|
||||
EventsMap,
|
||||
StrictEventEmitter,
|
||||
} from "./typed-events"
|
||||
Emitter,
|
||||
} from "@socket.io/component-emitter"
|
||||
// import debugModule from "debug" // debug()
|
||||
|
||||
// const debug = debugModule("socket.io-client:manager") // debug()
|
||||
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?
|
||||
@@ -267,13 +83,6 @@ export interface ManagerOptions extends EngineOptions {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -285,7 +94,7 @@ interface ManagerReservedEvents {
|
||||
error: (err: Error) => void
|
||||
ping: () => void
|
||||
packet: (packet: Packet) => void
|
||||
close: (reason: string) => void
|
||||
close: (reason: string, description?: DisconnectDescription) => void
|
||||
reconnect_failed: () => void
|
||||
reconnect_attempt: (attempt: number) => void
|
||||
reconnect_error: (err: Error) => void
|
||||
@@ -295,13 +104,13 @@ interface ManagerReservedEvents {
|
||||
export class Manager<
|
||||
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||
EmitEvents extends EventsMap = ListenEvents
|
||||
> extends StrictEventEmitter<{}, {}, ManagerReservedEvents> {
|
||||
> extends Emitter<{}, {}, ManagerReservedEvents> {
|
||||
/**
|
||||
* The Engine.IO client instance
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public engine: any
|
||||
public engine: Engine
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@@ -320,7 +129,9 @@ export class Manager<
|
||||
|
||||
private nsps: Record<string, Socket> = {};
|
||||
private subs: Array<ReturnType<typeof on>> = [];
|
||||
// @ts-ignore
|
||||
private backoff: Backoff
|
||||
private setTimeoutFn: typeof setTimeout
|
||||
private _reconnection: boolean
|
||||
private _reconnectionAttempts: number
|
||||
private _reconnectionDelay: number
|
||||
@@ -358,6 +169,7 @@ export class Manager<
|
||||
|
||||
opts.path = opts.path || "/socket.io"
|
||||
this.opts = opts
|
||||
installTimerFunctions(this, opts)
|
||||
this.reconnection(opts.reconnection !== false)
|
||||
this.reconnectionAttempts(opts.reconnectionAttempts || Infinity)
|
||||
this.reconnectionDelay(opts.reconnectionDelay || 1000)
|
||||
@@ -507,8 +319,7 @@ export class Manager<
|
||||
if (~this._readyState.indexOf("open")) return this
|
||||
|
||||
debug("opening %s", this.uri)
|
||||
// @ts-ignore
|
||||
this.engine = eio(this.uri, this.opts)
|
||||
this.engine = new Engine(this.uri, this.opts)
|
||||
const socket = this.engine
|
||||
const self = this
|
||||
this._readyState = "opening"
|
||||
@@ -543,10 +354,11 @@ export class Manager<
|
||||
}
|
||||
|
||||
// set timer
|
||||
const timer = setTimeout(() => {
|
||||
const timer = this.setTimeoutFn(() => {
|
||||
debug("connect attempt timed out after %d", timeout)
|
||||
openSubDestroy()
|
||||
socket.close()
|
||||
// @ts-ignore
|
||||
socket.emit("error", new Error("timeout"))
|
||||
}, timeout)
|
||||
|
||||
@@ -616,7 +428,11 @@ export class Manager<
|
||||
* @private
|
||||
*/
|
||||
private ondata(data): void {
|
||||
this.decoder.add(data)
|
||||
try {
|
||||
this.decoder.add(data)
|
||||
} catch (e) {
|
||||
this.onclose("parse error", e as Error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -625,7 +441,10 @@ export class Manager<
|
||||
* @private
|
||||
*/
|
||||
private ondecoded(packet): void {
|
||||
this.emitReserved("packet", packet)
|
||||
// the nextTick call prevents an exception in a user-provided event listener from triggering a disconnection due to a "parse error"
|
||||
nextTick(() => {
|
||||
this.emitReserved("packet", packet)
|
||||
}, this.setTimeoutFn)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -651,6 +470,10 @@ export class Manager<
|
||||
this.nsps[nsp] = socket
|
||||
}
|
||||
|
||||
if (this._autoConnect) {
|
||||
socket.connect()
|
||||
}
|
||||
|
||||
return socket
|
||||
}
|
||||
|
||||
@@ -713,13 +536,7 @@ export class Manager<
|
||||
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"
|
||||
this.onclose("forced close")
|
||||
if (this.engine) this.engine.close()
|
||||
}
|
||||
|
||||
@@ -737,13 +554,13 @@ export class Manager<
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private onclose(reason: string): void {
|
||||
debug("onclose")
|
||||
private onclose(reason: string, description?: DisconnectDescription): void {
|
||||
debug("closed due to %s", reason)
|
||||
|
||||
this.cleanup()
|
||||
this.backoff.reset()
|
||||
this._readyState = "closed"
|
||||
this.emitReserved("close", reason)
|
||||
this.emitReserved("close", reason, description)
|
||||
|
||||
if (this._reconnection && !this.skipReconnect) {
|
||||
this.reconnect()
|
||||
@@ -770,7 +587,7 @@ export class Manager<
|
||||
debug("will wait %dms before reconnect attempt", delay)
|
||||
|
||||
this._reconnecting = true
|
||||
const timer = setTimeout(() => {
|
||||
const timer = this.setTimeoutFn(() => {
|
||||
if (self.skipReconnect) return
|
||||
|
||||
debug("attempting reconnect")
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// import type * as Emitter from "component-emitter";
|
||||
import { EventEmitter } from "events"
|
||||
import { StrictEventEmitter } from "./typed-events"
|
||||
import { Emitter } from "@socket.io/component-emitter"
|
||||
|
||||
export function on(
|
||||
obj: EventEmitter | StrictEventEmitter<any, any>,
|
||||
obj: Emitter<any, any>,
|
||||
ev: string,
|
||||
fn: (err?: any) => any
|
||||
): VoidFunction {
|
||||
|
||||
@@ -1,21 +1,82 @@
|
||||
// import { Packet, PacketType } from "socket.io-parser"
|
||||
import { Packet, PacketType } from "../socket.io-parser"
|
||||
import { on } from "./on"
|
||||
import { Manager } from "./manager"
|
||||
import { on } from "./on.js"
|
||||
import { Manager } from "./manager.js"
|
||||
import {
|
||||
DefaultEventsMap,
|
||||
EventNames,
|
||||
EventParams,
|
||||
EventsMap,
|
||||
StrictEventEmitter,
|
||||
} from "./typed-events"
|
||||
Emitter,
|
||||
} from "@socket.io/component-emitter"
|
||||
// import debugModule from "debug" // debug()
|
||||
|
||||
// const debug = debugModule("socket.io-client:socket") // debug()
|
||||
const debug = require("../debug")("socket.io-client")
|
||||
|
||||
type PrependTimeoutError<T extends any[]> = {
|
||||
[K in keyof T]: T[K] extends (...args: infer Params) => infer Result
|
||||
? (err: Error, ...args: Params) => Result
|
||||
: T[K]
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility type to decorate the acknowledgement callbacks with a timeout error.
|
||||
*
|
||||
* This is needed because the timeout() flag breaks the symmetry between the sender and the receiver:
|
||||
*
|
||||
* @example
|
||||
* interface Events {
|
||||
* "my-event": (val: string) => void;
|
||||
* }
|
||||
*
|
||||
* socket.on("my-event", (cb) => {
|
||||
* cb("123"); // one single argument here
|
||||
* });
|
||||
*
|
||||
* socket.timeout(1000).emit("my-event", (err, val) => {
|
||||
* // two arguments there (the "err" argument is not properly typed)
|
||||
* });
|
||||
*
|
||||
*/
|
||||
export type DecorateAcknowledgements<E> = {
|
||||
[K in keyof E]: E[K] extends (...args: infer Params) => infer Result
|
||||
? (...args: PrependTimeoutError<Params>) => Result
|
||||
: E[K]
|
||||
}
|
||||
|
||||
export type Last<T extends any[]> = T extends [...infer H, infer L] ? L : any
|
||||
export type AllButLast<T extends any[]> = T extends [...infer H, infer L]
|
||||
? H
|
||||
: any[]
|
||||
export type FirstArg<T> = T extends (arg: infer Param) => infer Result
|
||||
? Param
|
||||
: any
|
||||
|
||||
export interface SocketOptions {
|
||||
/**
|
||||
* the authentication payload sent when connecting to the Namespace
|
||||
*/
|
||||
auth: { [key: string]: any } | ((cb: (data: object) => void) => void)
|
||||
auth?: { [key: string]: any } | ((cb: (data: object) => void) => void)
|
||||
/**
|
||||
* The maximum number of retries. Above the limit, the packet will be discarded.
|
||||
*
|
||||
* Using `Infinity` means the delivery guarantee is "at-least-once" (instead of "at-most-once" by default), but a
|
||||
* smaller value like 10 should be sufficient in practice.
|
||||
*/
|
||||
retries?: number
|
||||
/**
|
||||
* The default timeout in milliseconds used when waiting for an acknowledgement.
|
||||
*/
|
||||
ackTimeout?: number
|
||||
}
|
||||
|
||||
type QueuedPacket = {
|
||||
id: number
|
||||
args: unknown[]
|
||||
flags: Flags
|
||||
pending: boolean
|
||||
tryCount: number
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,58 +96,180 @@ const RESERVED_EVENTS = Object.freeze({
|
||||
interface Flags {
|
||||
compress?: boolean
|
||||
volatile?: boolean
|
||||
timeout?: number
|
||||
fromQueue?: boolean
|
||||
}
|
||||
|
||||
export type DisconnectDescription =
|
||||
| Error
|
||||
| {
|
||||
description: string
|
||||
context?: unknown // context should be typed as CloseEvent | XMLHttpRequest, but these types are not available on non-browser platforms
|
||||
}
|
||||
|
||||
interface SocketReservedEvents {
|
||||
connect: () => void
|
||||
connect_error: (err: Error) => void
|
||||
disconnect: (reason: Socket.DisconnectReason) => void
|
||||
disconnect: (
|
||||
reason: Socket.DisconnectReason,
|
||||
description?: DisconnectDescription
|
||||
) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* A Socket is the fundamental class for interacting with the server.
|
||||
*
|
||||
* A Socket belongs to a certain Namespace (by default /) and uses an underlying {@link Manager} to communicate.
|
||||
*
|
||||
* @example
|
||||
* const socket = io();
|
||||
*
|
||||
* socket.on("connect", () => {
|
||||
* console.log("connected");
|
||||
* });
|
||||
*
|
||||
* // send an event to the server
|
||||
* socket.emit("foo", "bar");
|
||||
*
|
||||
* socket.on("foobar", () => {
|
||||
* // an event was received from the server
|
||||
* });
|
||||
*
|
||||
* // upon disconnection
|
||||
* socket.on("disconnect", (reason) => {
|
||||
* console.log(`disconnected due to ${reason}`);
|
||||
* });
|
||||
*/
|
||||
export class Socket<
|
||||
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||
EmitEvents extends EventsMap = ListenEvents
|
||||
> extends StrictEventEmitter<ListenEvents, EmitEvents, SocketReservedEvents> {
|
||||
> extends Emitter<ListenEvents, EmitEvents, SocketReservedEvents> {
|
||||
public readonly io: Manager<ListenEvents, EmitEvents>
|
||||
|
||||
/**
|
||||
* A unique identifier for the session.
|
||||
*
|
||||
* @example
|
||||
* const socket = io();
|
||||
*
|
||||
* console.log(socket.id); // undefined
|
||||
*
|
||||
* socket.on("connect", () => {
|
||||
* console.log(socket.id); // "G5p5..."
|
||||
* });
|
||||
*/
|
||||
public id: string
|
||||
public connected: boolean
|
||||
public disconnected: boolean
|
||||
|
||||
/**
|
||||
* The session ID used for connection state recovery, which must not be shared (unlike {@link id}).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _pid: string
|
||||
|
||||
/**
|
||||
* The offset of the last received packet, which will be sent upon reconnection to allow for the recovery of the connection state.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _lastOffset: string
|
||||
|
||||
/**
|
||||
* Whether the socket is currently connected to the server.
|
||||
*
|
||||
* @example
|
||||
* const socket = io();
|
||||
*
|
||||
* socket.on("connect", () => {
|
||||
* console.log(socket.connected); // true
|
||||
* });
|
||||
*
|
||||
* socket.on("disconnect", () => {
|
||||
* console.log(socket.connected); // false
|
||||
* });
|
||||
*/
|
||||
public connected: boolean = false;
|
||||
/**
|
||||
* Whether the connection state was recovered after a temporary disconnection. In that case, any missed packets will
|
||||
* be transmitted by the server.
|
||||
*/
|
||||
public recovered: boolean = false;
|
||||
/**
|
||||
* Credentials that are sent when accessing a namespace.
|
||||
*
|
||||
* @example
|
||||
* const socket = io({
|
||||
* auth: {
|
||||
* token: "abcd"
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // or with a function
|
||||
* const socket = io({
|
||||
* auth: (cb) => {
|
||||
* cb({ token: localStorage.token })
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
public auth: { [key: string]: any } | ((cb: (data: object) => void) => void)
|
||||
/**
|
||||
* Buffer for packets received before the CONNECT packet
|
||||
*/
|
||||
public receiveBuffer: Array<ReadonlyArray<any>> = [];
|
||||
/**
|
||||
* Buffer for packets that will be sent once the socket is connected
|
||||
*/
|
||||
public sendBuffer: Array<Packet> = [];
|
||||
/**
|
||||
* The queue of packets to be sent with retry in case of failure.
|
||||
*
|
||||
* Packets are sent one by one, each waiting for the server acknowledgement, in order to guarantee the delivery order.
|
||||
* @private
|
||||
*/
|
||||
private _queue: Array<QueuedPacket> = [];
|
||||
|
||||
private readonly nsp: string
|
||||
private readonly _opts: SocketOptions
|
||||
|
||||
private ids: number = 0;
|
||||
private acks: object = {};
|
||||
private flags: Flags = {};
|
||||
private subs?: Array<VoidFunction>
|
||||
private _anyListeners: Array<(...args: any[]) => void>
|
||||
private _anyOutgoingListeners: 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
|
||||
}
|
||||
this._opts = Object.assign({}, opts)
|
||||
if (this.io._autoConnect) this.open()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the socket is currently disconnected
|
||||
*
|
||||
* @example
|
||||
* const socket = io();
|
||||
*
|
||||
* socket.on("connect", () => {
|
||||
* console.log(socket.disconnected); // false
|
||||
* });
|
||||
*
|
||||
* socket.on("disconnect", () => {
|
||||
* console.log(socket.disconnected); // true
|
||||
* });
|
||||
*/
|
||||
public get disconnected(): boolean {
|
||||
return !this.connected
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to open, close and packet events
|
||||
*
|
||||
@@ -105,7 +288,21 @@ export class Socket<
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the Socket will try to reconnect when its Manager connects or reconnects
|
||||
* Whether the Socket will try to reconnect when its Manager connects or reconnects.
|
||||
*
|
||||
* @example
|
||||
* const socket = io();
|
||||
*
|
||||
* console.log(socket.active); // true
|
||||
*
|
||||
* socket.on("disconnect", (reason) => {
|
||||
* if (reason === "io server disconnect") {
|
||||
* // the disconnection was initiated by the server, you need to manually reconnect
|
||||
* console.log(socket.active); // false
|
||||
* }
|
||||
* // else the socket will automatically try to reconnect
|
||||
* console.log(socket.active); // true
|
||||
* });
|
||||
*/
|
||||
public get active(): boolean {
|
||||
return !!this.subs
|
||||
@@ -114,7 +311,12 @@ export class Socket<
|
||||
/**
|
||||
* "Opens" the socket.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
* const socket = io({
|
||||
* autoConnect: false
|
||||
* });
|
||||
*
|
||||
* socket.connect();
|
||||
*/
|
||||
public connect(): this {
|
||||
if (this.connected) return this
|
||||
@@ -126,7 +328,7 @@ export class Socket<
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for connect()
|
||||
* Alias for {@link connect()}.
|
||||
*/
|
||||
public open(): this {
|
||||
return this.connect()
|
||||
@@ -135,8 +337,17 @@ export class Socket<
|
||||
/**
|
||||
* Sends a `message` event.
|
||||
*
|
||||
* This method mimics the WebSocket.send() method.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send
|
||||
*
|
||||
* @example
|
||||
* socket.send("hello");
|
||||
*
|
||||
* // this is equivalent to
|
||||
* socket.emit("message", "hello");
|
||||
*
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public send(...args: any[]): this {
|
||||
args.unshift("message")
|
||||
@@ -149,18 +360,34 @@ export class Socket<
|
||||
* Override `emit`.
|
||||
* If the event is in `events`, it's emitted normally.
|
||||
*
|
||||
* @example
|
||||
* socket.emit("hello", "world");
|
||||
*
|
||||
* // all serializable datastructures are supported (no need to call JSON.stringify)
|
||||
* socket.emit("hello", 1, "2", { 3: ["4"], 5: Uint8Array.from([6]) });
|
||||
*
|
||||
* // with an acknowledgement from the server
|
||||
* socket.emit("hello", "world", (val) => {
|
||||
* // ...
|
||||
* });
|
||||
*
|
||||
* @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')
|
||||
throw new Error('"' + ev.toString() + '" is a reserved event name')
|
||||
}
|
||||
|
||||
args.unshift(ev)
|
||||
|
||||
if (this._opts.retries && !this.flags.fromQueue && !this.flags.volatile) {
|
||||
this._addToQueue(args)
|
||||
return this
|
||||
}
|
||||
|
||||
const packet: any = {
|
||||
type: PacketType.EVENT,
|
||||
data: args,
|
||||
@@ -171,9 +398,12 @@ export class Socket<
|
||||
|
||||
// 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 id = this.ids++
|
||||
debug("emitting packet with ack id %d", id)
|
||||
|
||||
const ack = args.pop() as Function
|
||||
this._registerAckCallback(id, ack)
|
||||
packet.id = id
|
||||
}
|
||||
|
||||
const isTransportWritable =
|
||||
@@ -186,6 +416,7 @@ export class Socket<
|
||||
if (discardPacket) {
|
||||
debug("discard packet as the transport is not currently writable")
|
||||
} else if (this.connected) {
|
||||
this.notifyOutgoingListeners(packet)
|
||||
this.packet(packet)
|
||||
} else {
|
||||
this.sendBuffer.push(packet)
|
||||
@@ -196,6 +427,152 @@ export class Socket<
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private _registerAckCallback(id: number, ack: Function) {
|
||||
const timeout = this.flags.timeout ?? this._opts.ackTimeout
|
||||
if (timeout === undefined) {
|
||||
this.acks[id] = ack
|
||||
return
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const timer = this.io.setTimeoutFn(() => {
|
||||
delete this.acks[id]
|
||||
for (let i = 0; i < this.sendBuffer.length; i++) {
|
||||
if (this.sendBuffer[i].id === id) {
|
||||
debug("removing packet with ack id %d from the buffer", id)
|
||||
this.sendBuffer.splice(i, 1)
|
||||
}
|
||||
}
|
||||
debug("event with ack id %d has timed out after %d ms", id, timeout)
|
||||
ack.call(this, new Error("operation has timed out"))
|
||||
}, timeout)
|
||||
|
||||
this.acks[id] = (...args) => {
|
||||
// @ts-ignore
|
||||
this.io.clearTimeoutFn(timer)
|
||||
ack.apply(this, [null, ...args])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event and waits for an acknowledgement
|
||||
*
|
||||
* @example
|
||||
* // without timeout
|
||||
* const response = await socket.emitWithAck("hello", "world");
|
||||
*
|
||||
* // with a specific timeout
|
||||
* try {
|
||||
* const response = await socket.timeout(1000).emitWithAck("hello", "world");
|
||||
* } catch (err) {
|
||||
* // the server did not acknowledge the event in the given delay
|
||||
* }
|
||||
*
|
||||
* @return a Promise that will be fulfilled when the server acknowledges the event
|
||||
*/
|
||||
public emitWithAck<Ev extends EventNames<EmitEvents>>(
|
||||
ev: Ev,
|
||||
...args: AllButLast<EventParams<EmitEvents, Ev>>
|
||||
): Promise<FirstArg<Last<EventParams<EmitEvents, Ev>>>> {
|
||||
// the timeout flag is optional
|
||||
const withErr =
|
||||
this.flags.timeout !== undefined || this._opts.ackTimeout !== undefined
|
||||
return new Promise((resolve, reject) => {
|
||||
args.push((arg1, arg2) => {
|
||||
if (withErr) {
|
||||
return arg1 ? reject(arg1) : resolve(arg2)
|
||||
} else {
|
||||
return resolve(arg1)
|
||||
}
|
||||
})
|
||||
this.emit(ev, ...(args as any[] as EventParams<EmitEvents, Ev>))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the packet to the queue.
|
||||
* @param args
|
||||
* @private
|
||||
*/
|
||||
private _addToQueue(args: unknown[]) {
|
||||
let ack
|
||||
if (typeof args[args.length - 1] === "function") {
|
||||
ack = args.pop()
|
||||
}
|
||||
|
||||
const packet = {
|
||||
id: this.ids++,
|
||||
tryCount: 0,
|
||||
pending: false,
|
||||
args,
|
||||
flags: Object.assign({ fromQueue: true }, this.flags),
|
||||
}
|
||||
|
||||
args.push((err, ...responseArgs) => {
|
||||
if (packet !== this._queue[0]) {
|
||||
// the packet has already been acknowledged
|
||||
return
|
||||
}
|
||||
const hasError = err !== null
|
||||
if (hasError) {
|
||||
if (packet.tryCount > this._opts.retries) {
|
||||
debug(
|
||||
"packet [%d] is discarded after %d tries",
|
||||
packet.id,
|
||||
packet.tryCount
|
||||
)
|
||||
this._queue.shift()
|
||||
if (ack) {
|
||||
ack(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug("packet [%d] was successfully sent", packet.id)
|
||||
this._queue.shift()
|
||||
if (ack) {
|
||||
ack(null, ...responseArgs)
|
||||
}
|
||||
}
|
||||
packet.pending = false
|
||||
return this._drainQueue()
|
||||
})
|
||||
|
||||
this._queue.push(packet)
|
||||
this._drainQueue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the first packet of the queue, and wait for an acknowledgement from the server.
|
||||
* @private
|
||||
*/
|
||||
private _drainQueue() {
|
||||
debug("draining queue")
|
||||
if (this._queue.length === 0) {
|
||||
return
|
||||
}
|
||||
const packet = this._queue[0]
|
||||
if (packet.pending) {
|
||||
debug(
|
||||
"packet [%d] has already been sent and is waiting for an ack",
|
||||
packet.id
|
||||
)
|
||||
return
|
||||
}
|
||||
packet.pending = true
|
||||
packet.tryCount++
|
||||
debug("sending packet [%d] (try n°%d)", packet.id, packet.tryCount)
|
||||
const currentId = this.ids
|
||||
this.ids = packet.id // the same id is reused for consecutive retries, in order to allow deduplication on the server side
|
||||
this.flags = packet.flags
|
||||
|
||||
// @ts-ignore
|
||||
this.emit.apply(this, packet.args)
|
||||
this.ids = currentId // restore offset
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet.
|
||||
*
|
||||
@@ -216,13 +593,28 @@ export class Socket<
|
||||
debug("transport is open - connecting")
|
||||
if (typeof this.auth == "function") {
|
||||
this.auth((data) => {
|
||||
this.packet({ type: PacketType.CONNECT, data })
|
||||
this._sendConnectPacket(data as Record<string, unknown>)
|
||||
})
|
||||
} else {
|
||||
this.packet({ type: PacketType.CONNECT, data: this.auth })
|
||||
this._sendConnectPacket(this.auth)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a CONNECT packet to initiate the Socket.IO session.
|
||||
*
|
||||
* @param data
|
||||
* @private
|
||||
*/
|
||||
private _sendConnectPacket(data: Record<string, unknown>) {
|
||||
this.packet({
|
||||
type: PacketType.CONNECT,
|
||||
data: this._pid
|
||||
? Object.assign({ pid: this._pid, offset: this._lastOffset }, data)
|
||||
: data,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon engine or manager `error`.
|
||||
*
|
||||
@@ -239,14 +631,17 @@ export class Socket<
|
||||
* Called upon engine `close`.
|
||||
*
|
||||
* @param reason
|
||||
* @param description
|
||||
* @private
|
||||
*/
|
||||
private onclose(reason: Socket.DisconnectReason): void {
|
||||
private onclose(
|
||||
reason: Socket.DisconnectReason,
|
||||
description?: DisconnectDescription
|
||||
): void {
|
||||
debug("close (%s)", reason)
|
||||
this.connected = false
|
||||
this.disconnected = true
|
||||
delete this.id
|
||||
this.emitReserved("disconnect", reason)
|
||||
this.emitReserved("disconnect", reason, description)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,8 +658,7 @@ export class Socket<
|
||||
switch (packet.type) {
|
||||
case PacketType.CONNECT:
|
||||
if (packet.data && packet.data.sid) {
|
||||
const id = packet.data.sid
|
||||
this.onconnect(id)
|
||||
this.onconnect(packet.data.sid, packet.data.pid)
|
||||
} else {
|
||||
this.emitReserved(
|
||||
"connect_error",
|
||||
@@ -276,17 +670,11 @@ export class Socket<
|
||||
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
|
||||
@@ -296,6 +684,7 @@ export class Socket<
|
||||
break
|
||||
|
||||
case PacketType.CONNECT_ERROR:
|
||||
this.destroy()
|
||||
const err = new Error(packet.data.message)
|
||||
// @ts-ignore
|
||||
err.data = packet.data.data
|
||||
@@ -336,6 +725,9 @@ export class Socket<
|
||||
}
|
||||
// @ts-ignore
|
||||
super.emit.apply(this, args)
|
||||
if (this._pid && args.length && typeof args[args.length - 1] === "string") {
|
||||
this._lastOffset = args[args.length - 1]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,11 +774,12 @@ export class Socket<
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private onconnect(id: string): void {
|
||||
private onconnect(id: string, pid: string) {
|
||||
debug("socket connected with id %s", id)
|
||||
this.id = id
|
||||
this.recovered = pid && this._pid === pid
|
||||
this._pid = pid // defined only if connection state recovery is enabled
|
||||
this.connected = true
|
||||
this.disconnected = false
|
||||
this.emitBuffered()
|
||||
this.emitReserved("connect")
|
||||
}
|
||||
@@ -400,7 +793,10 @@ export class Socket<
|
||||
this.receiveBuffer.forEach((args) => this.emitEvent(args))
|
||||
this.receiveBuffer = []
|
||||
|
||||
this.sendBuffer.forEach((packet) => this.packet(packet))
|
||||
this.sendBuffer.forEach((packet) => {
|
||||
this.notifyOutgoingListeners(packet)
|
||||
this.packet(packet)
|
||||
})
|
||||
this.sendBuffer = []
|
||||
}
|
||||
|
||||
@@ -432,10 +828,20 @@ export class Socket<
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the socket manually.
|
||||
* Disconnects the socket manually. In that case, the socket will not try to reconnect.
|
||||
*
|
||||
* If this is the last active Socket instance of the {@link Manager}, the low-level connection will be closed.
|
||||
*
|
||||
* @example
|
||||
* const socket = io();
|
||||
*
|
||||
* socket.on("disconnect", (reason) => {
|
||||
* // console.log(reason); prints "io client disconnect"
|
||||
* });
|
||||
*
|
||||
* socket.disconnect();
|
||||
*
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public disconnect(): this {
|
||||
if (this.connected) {
|
||||
@@ -454,10 +860,9 @@ export class Socket<
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for disconnect()
|
||||
* Alias for {@link disconnect()}.
|
||||
*
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public close(): this {
|
||||
return this.disconnect()
|
||||
@@ -466,9 +871,11 @@ export class Socket<
|
||||
/**
|
||||
* Sets the compress flag.
|
||||
*
|
||||
* @example
|
||||
* socket.compress(false).emit("hello");
|
||||
*
|
||||
* @param compress - if `true`, compresses the sending data
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public compress(compress: boolean): this {
|
||||
this.flags.compress = compress
|
||||
@@ -479,20 +886,46 @@ export class Socket<
|
||||
* Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not
|
||||
* ready to send messages.
|
||||
*
|
||||
* @example
|
||||
* socket.volatile.emit("hello"); // the server may or may not receive it
|
||||
*
|
||||
* @returns self
|
||||
* @public
|
||||
*/
|
||||
public get volatile(): this {
|
||||
this.flags.volatile = true
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a modifier for a subsequent event emission that the callback will be called with an error when the
|
||||
* given number of milliseconds have elapsed without an acknowledgement from the server:
|
||||
*
|
||||
* @example
|
||||
* socket.timeout(5000).emit("my-event", (err) => {
|
||||
* if (err) {
|
||||
* // the server did not acknowledge the event in the given delay
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @returns self
|
||||
*/
|
||||
public timeout(
|
||||
timeout: number
|
||||
): Socket<ListenEvents, DecorateAcknowledgements<EmitEvents>> {
|
||||
this.flags.timeout = timeout
|
||||
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.
|
||||
*
|
||||
* @example
|
||||
* socket.onAny((event, ...args) => {
|
||||
* console.log(`got ${event}`);
|
||||
* });
|
||||
*
|
||||
* @param listener
|
||||
* @public
|
||||
*/
|
||||
public onAny(listener: (...args: any[]) => void): this {
|
||||
this._anyListeners = this._anyListeners || []
|
||||
@@ -504,8 +937,12 @@ export class Socket<
|
||||
* 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.
|
||||
*
|
||||
* @example
|
||||
* socket.prependAny((event, ...args) => {
|
||||
* console.log(`got event ${event}`);
|
||||
* });
|
||||
*
|
||||
* @param listener
|
||||
* @public
|
||||
*/
|
||||
public prependAny(listener: (...args: any[]) => void): this {
|
||||
this._anyListeners = this._anyListeners || []
|
||||
@@ -516,8 +953,20 @@ export class Socket<
|
||||
/**
|
||||
* Removes the listener that will be fired when any event is emitted.
|
||||
*
|
||||
* @example
|
||||
* const catchAllListener = (event, ...args) => {
|
||||
* console.log(`got event ${event}`);
|
||||
* }
|
||||
*
|
||||
* socket.onAny(catchAllListener);
|
||||
*
|
||||
* // remove a specific listener
|
||||
* socket.offAny(catchAllListener);
|
||||
*
|
||||
* // or remove all listeners
|
||||
* socket.offAny();
|
||||
*
|
||||
* @param listener
|
||||
* @public
|
||||
*/
|
||||
public offAny(listener?: (...args: any[]) => void): this {
|
||||
if (!this._anyListeners) {
|
||||
@@ -540,12 +989,108 @@ export class Socket<
|
||||
/**
|
||||
* 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 || []
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
|
||||
* callback.
|
||||
*
|
||||
* Note: acknowledgements sent to the server are not included.
|
||||
*
|
||||
* @example
|
||||
* socket.onAnyOutgoing((event, ...args) => {
|
||||
* console.log(`sent event ${event}`);
|
||||
* });
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
public onAnyOutgoing(listener: (...args: any[]) => void): this {
|
||||
this._anyOutgoingListeners = this._anyOutgoingListeners || []
|
||||
this._anyOutgoingListeners.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.
|
||||
*
|
||||
* Note: acknowledgements sent to the server are not included.
|
||||
*
|
||||
* @example
|
||||
* socket.prependAnyOutgoing((event, ...args) => {
|
||||
* console.log(`sent event ${event}`);
|
||||
* });
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
public prependAnyOutgoing(listener: (...args: any[]) => void): this {
|
||||
this._anyOutgoingListeners = this._anyOutgoingListeners || []
|
||||
this._anyOutgoingListeners.unshift(listener)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener that will be fired when any event is emitted.
|
||||
*
|
||||
* @example
|
||||
* const catchAllListener = (event, ...args) => {
|
||||
* console.log(`sent event ${event}`);
|
||||
* }
|
||||
*
|
||||
* socket.onAnyOutgoing(catchAllListener);
|
||||
*
|
||||
* // remove a specific listener
|
||||
* socket.offAnyOutgoing(catchAllListener);
|
||||
*
|
||||
* // or remove all listeners
|
||||
* socket.offAnyOutgoing();
|
||||
*
|
||||
* @param [listener] - the catch-all listener (optional)
|
||||
*/
|
||||
public offAnyOutgoing(listener?: (...args: any[]) => void): this {
|
||||
if (!this._anyOutgoingListeners) {
|
||||
return this
|
||||
}
|
||||
if (listener) {
|
||||
const listeners = this._anyOutgoingListeners
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
if (listener === listeners[i]) {
|
||||
listeners.splice(i, 1)
|
||||
return this
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._anyOutgoingListeners = []
|
||||
}
|
||||
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 listenersAnyOutgoing() {
|
||||
return this._anyOutgoingListeners || []
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the listeners for each packet sent
|
||||
*
|
||||
* @param packet
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private notifyOutgoingListeners(packet: Packet) {
|
||||
if (this._anyOutgoingListeners && this._anyOutgoingListeners.length) {
|
||||
const listeners = this._anyOutgoingListeners.slice()
|
||||
for (const listener of listeners) {
|
||||
listener.apply(this, packet.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Socket {
|
||||
@@ -555,4 +1100,5 @@ export namespace Socket {
|
||||
| "ping timeout"
|
||||
| "transport close"
|
||||
| "transport error"
|
||||
| "parse error"
|
||||
}
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
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
|
||||
>[]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import * as parseuri from "parseuri"
|
||||
// import { parse } from "engine.io-client"
|
||||
import { parse } from "../engine.io-client"
|
||||
// import debugModule from "debug" // debug()
|
||||
|
||||
// const debug = debugModule("socket.io-client:url"); // debug()
|
||||
const debug = require("../debug")("socket.io-client")
|
||||
|
||||
type ParsedUrl = {
|
||||
@@ -67,7 +70,7 @@ export function url(
|
||||
|
||||
// parse
|
||||
debug("parse %s", uri)
|
||||
obj = parseuri(uri) as ParsedUrl
|
||||
obj = parse(uri) as ParsedUrl
|
||||
}
|
||||
|
||||
// make sure we treat `localhost:80` and `localhost` equally
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isBinary } from "./is-binary"
|
||||
import { isBinary } from "./is-binary.js";
|
||||
|
||||
/**
|
||||
* Replaces every Buffer | ArrayBuffer | Blob | File in packet with a numbered placeholder.
|
||||
@@ -9,37 +9,37 @@ import { isBinary } from "./is-binary"
|
||||
*/
|
||||
|
||||
export function deconstructPacket(packet) {
|
||||
const buffers = []
|
||||
const packetData = packet.data
|
||||
const pack = packet
|
||||
pack.data = _deconstructPacket(packetData, buffers)
|
||||
pack.attachments = buffers.length // number of binary 'attachments'
|
||||
return { packet: pack, buffers: buffers }
|
||||
const buffers = [];
|
||||
const packetData = packet.data;
|
||||
const pack = packet;
|
||||
pack.data = _deconstructPacket(packetData, buffers);
|
||||
pack.attachments = buffers.length; // number of binary 'attachments'
|
||||
return { packet: pack, buffers: buffers };
|
||||
}
|
||||
|
||||
function _deconstructPacket(data, buffers) {
|
||||
if (!data) return data
|
||||
if (!data) return data;
|
||||
|
||||
if (isBinary(data)) {
|
||||
const placeholder = { _placeholder: true, num: buffers.length }
|
||||
buffers.push(data)
|
||||
return placeholder
|
||||
} else if (Array.isArray(data)) {
|
||||
const newData = new Array(data.length)
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
newData[i] = _deconstructPacket(data[i], buffers)
|
||||
}
|
||||
return newData
|
||||
} else if (typeof data === "object" && !(data instanceof Date)) {
|
||||
const newData = {}
|
||||
for (const key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
newData[key] = _deconstructPacket(data[key], buffers)
|
||||
}
|
||||
}
|
||||
return newData
|
||||
if (isBinary(data)) {
|
||||
const placeholder = { _placeholder: true, num: buffers.length };
|
||||
buffers.push(data);
|
||||
return placeholder;
|
||||
} else if (Array.isArray(data)) {
|
||||
const newData = new Array(data.length);
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
newData[i] = _deconstructPacket(data[i], buffers);
|
||||
}
|
||||
return data
|
||||
return newData;
|
||||
} else if (typeof data === "object" && !(data instanceof Date)) {
|
||||
const newData = {};
|
||||
for (const key in data) {
|
||||
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
newData[key] = _deconstructPacket(data[key], buffers);
|
||||
}
|
||||
}
|
||||
return newData;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,27 +52,35 @@ function _deconstructPacket(data, buffers) {
|
||||
*/
|
||||
|
||||
export function reconstructPacket(packet, buffers) {
|
||||
packet.data = _reconstructPacket(packet.data, buffers)
|
||||
packet.attachments = undefined // no longer useful
|
||||
return packet
|
||||
packet.data = _reconstructPacket(packet.data, buffers);
|
||||
delete packet.attachments; // no longer useful
|
||||
return packet;
|
||||
}
|
||||
|
||||
function _reconstructPacket(data, buffers) {
|
||||
if (!data) return data
|
||||
if (!data) return data;
|
||||
|
||||
if (data && data._placeholder) {
|
||||
return buffers[data.num] // appropriate buffer (should be natural order anyway)
|
||||
} else if (Array.isArray(data)) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
data[i] = _reconstructPacket(data[i], buffers)
|
||||
}
|
||||
} else if (typeof data === "object") {
|
||||
for (const key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
data[key] = _reconstructPacket(data[key], buffers)
|
||||
}
|
||||
}
|
||||
if (data && data._placeholder === true) {
|
||||
const isIndexValid =
|
||||
typeof data.num === "number" &&
|
||||
data.num >= 0 &&
|
||||
data.num < buffers.length;
|
||||
if (isIndexValid) {
|
||||
return buffers[data.num]; // appropriate buffer (should be natural order anyway)
|
||||
} else {
|
||||
throw new Error("illegal attachments");
|
||||
}
|
||||
} else if (Array.isArray(data)) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
data[i] = _reconstructPacket(data[i], buffers);
|
||||
}
|
||||
} else if (typeof data === "object") {
|
||||
for (const key in data) {
|
||||
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
data[key] = _reconstructPacket(data[key], buffers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import EventEmitter = require("events")
|
||||
import { deconstructPacket, reconstructPacket } from "./binary"
|
||||
import { isBinary, hasBinary } from "./is-binary"
|
||||
import { Emitter } from "@socket.io/component-emitter";
|
||||
import { deconstructPacket, reconstructPacket } from "./binary.js";
|
||||
import { isBinary, hasBinary } from "./is-binary.js";
|
||||
// import debugModule from "debug" // debug()
|
||||
|
||||
// const debug = require("debug")("socket.io-parser")
|
||||
const debug = require("../debug")("socket.io-parser");
|
||||
|
||||
/**
|
||||
* Protocol version.
|
||||
@@ -10,24 +11,24 @@ import { isBinary, hasBinary } from "./is-binary"
|
||||
* @public
|
||||
*/
|
||||
|
||||
export const protocol: number = 5
|
||||
export const protocol: number = 5;
|
||||
|
||||
export enum PacketType {
|
||||
CONNECT,
|
||||
DISCONNECT,
|
||||
EVENT,
|
||||
ACK,
|
||||
CONNECT_ERROR,
|
||||
BINARY_EVENT,
|
||||
BINARY_ACK,
|
||||
CONNECT,
|
||||
DISCONNECT,
|
||||
EVENT,
|
||||
ACK,
|
||||
CONNECT_ERROR,
|
||||
BINARY_EVENT,
|
||||
BINARY_ACK,
|
||||
}
|
||||
|
||||
export interface Packet {
|
||||
type: PacketType
|
||||
nsp: string
|
||||
data?: any
|
||||
id?: number
|
||||
attachments?: number
|
||||
type: PacketType;
|
||||
nsp: string;
|
||||
data?: any;
|
||||
id?: number;
|
||||
attachments?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,77 +36,91 @@ export interface Packet {
|
||||
*/
|
||||
|
||||
export class Encoder {
|
||||
/**
|
||||
* Encode a packet as a single string if non-binary, or as a
|
||||
* buffer sequence, depending on packet type.
|
||||
*
|
||||
* @param {Object} obj - packet object
|
||||
*/
|
||||
public encode(obj: Packet) {
|
||||
console.trace("encoding packet", JSON.stringify(obj))
|
||||
/**
|
||||
* Encoder constructor
|
||||
*
|
||||
* @param {function} replacer - custom replacer to pass down to JSON.parse
|
||||
*/
|
||||
constructor(private replacer?: (this: any, key: string, value: any) => any) {}
|
||||
/**
|
||||
* Encode a packet as a single string if non-binary, or as a
|
||||
* buffer sequence, depending on packet type.
|
||||
*
|
||||
* @param {Object} obj - packet object
|
||||
*/
|
||||
public encode(obj: Packet) {
|
||||
debug("encoding packet %j", obj);
|
||||
|
||||
if (obj.type === PacketType.EVENT || obj.type === PacketType.ACK) {
|
||||
if (hasBinary(obj)) {
|
||||
obj.type =
|
||||
obj.type === PacketType.EVENT
|
||||
? PacketType.BINARY_EVENT
|
||||
: PacketType.BINARY_ACK
|
||||
return this.encodeAsBinary(obj)
|
||||
}
|
||||
}
|
||||
return [this.encodeAsString(obj)]
|
||||
if (obj.type === PacketType.EVENT || obj.type === PacketType.ACK) {
|
||||
if (hasBinary(obj)) {
|
||||
return this.encodeAsBinary({
|
||||
type:
|
||||
obj.type === PacketType.EVENT
|
||||
? PacketType.BINARY_EVENT
|
||||
: PacketType.BINARY_ACK,
|
||||
nsp: obj.nsp,
|
||||
data: obj.data,
|
||||
id: obj.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
return [this.encodeAsString(obj)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode packet as string.
|
||||
*/
|
||||
|
||||
private encodeAsString(obj: Packet) {
|
||||
// first is type
|
||||
let str = "" + obj.type;
|
||||
|
||||
// attachments if we have them
|
||||
if (
|
||||
obj.type === PacketType.BINARY_EVENT ||
|
||||
obj.type === PacketType.BINARY_ACK
|
||||
) {
|
||||
str += obj.attachments + "-";
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode packet as string.
|
||||
*/
|
||||
|
||||
private encodeAsString(obj: Packet) {
|
||||
// first is type
|
||||
let str = "" + obj.type
|
||||
|
||||
// attachments if we have them
|
||||
if (
|
||||
obj.type === PacketType.BINARY_EVENT ||
|
||||
obj.type === PacketType.BINARY_ACK
|
||||
) {
|
||||
str += obj.attachments + "-"
|
||||
}
|
||||
|
||||
// if we have a namespace other than `/`
|
||||
// we append it followed by a comma `,`
|
||||
if (obj.nsp && "/" !== obj.nsp) {
|
||||
str += obj.nsp + ","
|
||||
}
|
||||
|
||||
// immediately followed by the id
|
||||
if (null != obj.id) {
|
||||
str += obj.id
|
||||
}
|
||||
|
||||
// json data
|
||||
if (null != obj.data) {
|
||||
str += JSON.stringify(obj.data)
|
||||
}
|
||||
|
||||
console.trace("encoded", JSON.stringify(obj), "as", str)
|
||||
return str
|
||||
// if we have a namespace other than `/`
|
||||
// we append it followed by a comma `,`
|
||||
if (obj.nsp && "/" !== obj.nsp) {
|
||||
str += obj.nsp + ",";
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode packet as 'buffer sequence' by removing blobs, and
|
||||
* deconstructing packet into object with placeholders and
|
||||
* a list of buffers.
|
||||
*/
|
||||
|
||||
private encodeAsBinary(obj: Packet) {
|
||||
const deconstruction = deconstructPacket(obj)
|
||||
const pack = this.encodeAsString(deconstruction.packet)
|
||||
const buffers = deconstruction.buffers
|
||||
|
||||
buffers.unshift(pack) // add packet info to beginning of data list
|
||||
return buffers // write all the buffers
|
||||
// immediately followed by the id
|
||||
if (null != obj.id) {
|
||||
str += obj.id;
|
||||
}
|
||||
|
||||
// json data
|
||||
if (null != obj.data) {
|
||||
str += JSON.stringify(obj.data, this.replacer);
|
||||
}
|
||||
|
||||
debug("encoded %j as %s", obj, str);
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode packet as 'buffer sequence' by removing blobs, and
|
||||
* deconstructing packet into object with placeholders and
|
||||
* a list of buffers.
|
||||
*/
|
||||
|
||||
private encodeAsBinary(obj: Packet) {
|
||||
const deconstruction = deconstructPacket(obj);
|
||||
const pack = this.encodeAsString(deconstruction.packet);
|
||||
const buffers = deconstruction.buffers;
|
||||
|
||||
buffers.unshift(pack); // add packet info to beginning of data list
|
||||
return buffers; // write all the buffers
|
||||
}
|
||||
}
|
||||
|
||||
interface DecoderReservedEvents {
|
||||
decoded: (packet: Packet) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,161 +128,169 @@ export class Encoder {
|
||||
*
|
||||
* @return {Object} decoder
|
||||
*/
|
||||
export class Decoder extends EventEmitter {
|
||||
private reconstructor: BinaryReconstructor
|
||||
export class Decoder extends Emitter<{}, {}, DecoderReservedEvents> {
|
||||
private reconstructor: BinaryReconstructor;
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
/**
|
||||
* Decoder constructor
|
||||
*
|
||||
* @param {function} reviver - custom reviver to pass down to JSON.stringify
|
||||
*/
|
||||
constructor(private reviver?: (this: any, key: string, value: any) => any) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes an encoded packet string into packet JSON.
|
||||
*
|
||||
* @param {String} obj - encoded packet
|
||||
*/
|
||||
|
||||
public add(obj: any) {
|
||||
let packet;
|
||||
if (typeof obj === "string") {
|
||||
if (this.reconstructor) {
|
||||
throw new Error("got plaintext data when reconstructing a packet");
|
||||
}
|
||||
packet = this.decodeString(obj);
|
||||
const isBinaryEvent = packet.type === PacketType.BINARY_EVENT;
|
||||
if (isBinaryEvent || packet.type === PacketType.BINARY_ACK) {
|
||||
packet.type = isBinaryEvent ? PacketType.EVENT : PacketType.ACK;
|
||||
// binary packet's json
|
||||
this.reconstructor = new BinaryReconstructor(packet);
|
||||
|
||||
// no attachments, labeled binary but no binary data to follow
|
||||
if (packet.attachments === 0) {
|
||||
super.emitReserved("decoded", packet);
|
||||
}
|
||||
} else {
|
||||
// non-binary full packet
|
||||
super.emitReserved("decoded", packet);
|
||||
}
|
||||
} else if (isBinary(obj) || obj.base64) {
|
||||
// raw binary data
|
||||
if (!this.reconstructor) {
|
||||
throw new Error("got binary data when not reconstructing a packet");
|
||||
} else {
|
||||
packet = this.reconstructor.takeBinaryData(obj);
|
||||
if (packet) {
|
||||
// received final buffer
|
||||
this.reconstructor = null;
|
||||
super.emitReserved("decoded", packet);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error("Unknown type: " + obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a packet String (JSON data)
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {Object} packet
|
||||
*/
|
||||
private decodeString(str): Packet {
|
||||
let i = 0;
|
||||
// look up type
|
||||
const p: any = {
|
||||
type: Number(str.charAt(0)),
|
||||
};
|
||||
|
||||
if (PacketType[p.type] === undefined) {
|
||||
throw new Error("unknown packet type " + p.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes an encoded packet string into packet JSON.
|
||||
*
|
||||
* @param {String} obj - encoded packet
|
||||
*/
|
||||
|
||||
public add(obj: any) {
|
||||
let packet
|
||||
if (typeof obj === "string") {
|
||||
packet = this.decodeString(obj)
|
||||
if (
|
||||
packet.type === PacketType.BINARY_EVENT ||
|
||||
packet.type === PacketType.BINARY_ACK
|
||||
) {
|
||||
// binary packet's json
|
||||
this.reconstructor = new BinaryReconstructor(packet)
|
||||
|
||||
// no attachments, labeled binary but no binary data to follow
|
||||
if (packet.attachments === 0) {
|
||||
super.emit("decoded", packet)
|
||||
}
|
||||
} else {
|
||||
// non-binary full packet
|
||||
super.emit("decoded", packet)
|
||||
}
|
||||
} else if (isBinary(obj) || obj.base64) {
|
||||
// raw binary data
|
||||
if (!this.reconstructor) {
|
||||
throw new Error("got binary data when not reconstructing a packet")
|
||||
} else {
|
||||
packet = this.reconstructor.takeBinaryData(obj)
|
||||
if (packet) {
|
||||
// received final buffer
|
||||
this.reconstructor = null
|
||||
super.emit("decoded", packet)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error("Unknown type: " + obj)
|
||||
}
|
||||
// look up attachments if type binary
|
||||
if (
|
||||
p.type === PacketType.BINARY_EVENT ||
|
||||
p.type === PacketType.BINARY_ACK
|
||||
) {
|
||||
const start = i + 1;
|
||||
while (str.charAt(++i) !== "-" && i != str.length) {}
|
||||
const buf = str.substring(start, i);
|
||||
if (buf != Number(buf) || str.charAt(i) !== "-") {
|
||||
throw new Error("Illegal attachments");
|
||||
}
|
||||
p.attachments = Number(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a packet String (JSON data)
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {Object} packet
|
||||
*/
|
||||
private decodeString(str): Packet {
|
||||
let i = 0
|
||||
// look up type
|
||||
const p: any = {
|
||||
type: Number(str.charAt(0)),
|
||||
}
|
||||
|
||||
if (PacketType[p.type] === undefined) {
|
||||
throw new Error("unknown packet type " + p.type)
|
||||
}
|
||||
|
||||
// look up attachments if type binary
|
||||
if (
|
||||
p.type === PacketType.BINARY_EVENT ||
|
||||
p.type === PacketType.BINARY_ACK
|
||||
) {
|
||||
const start = i + 1
|
||||
while (str.charAt(++i) !== "-" && i != str.length) { }
|
||||
const buf = str.substring(start, i)
|
||||
if (buf != Number(buf) || str.charAt(i) !== "-") {
|
||||
throw new Error("Illegal attachments")
|
||||
}
|
||||
p.attachments = Number(buf)
|
||||
}
|
||||
|
||||
// look up namespace (if any)
|
||||
if ("/" === str.charAt(i + 1)) {
|
||||
const start = i + 1
|
||||
while (++i) {
|
||||
const c = str.charAt(i)
|
||||
if ("," === c) break
|
||||
if (i === str.length) break
|
||||
}
|
||||
p.nsp = str.substring(start, i)
|
||||
} else {
|
||||
p.nsp = "/"
|
||||
}
|
||||
|
||||
// look up id
|
||||
const next = str.charAt(i + 1)
|
||||
if ("" !== next && Number(next) == next) {
|
||||
const start = i + 1
|
||||
while (++i) {
|
||||
const c = str.charAt(i)
|
||||
if (null == c || Number(c) != c) {
|
||||
--i
|
||||
break
|
||||
}
|
||||
if (i === str.length) break
|
||||
}
|
||||
p.id = Number(str.substring(start, i + 1))
|
||||
}
|
||||
|
||||
// look up json data
|
||||
if (str.charAt(++i)) {
|
||||
const payload = tryParse(str.substr(i))
|
||||
if (Decoder.isPayloadValid(p.type, payload)) {
|
||||
p.data = payload
|
||||
} else {
|
||||
throw new Error("invalid payload")
|
||||
}
|
||||
}
|
||||
|
||||
console.trace("decoded", str, "as", p)
|
||||
return p
|
||||
// look up namespace (if any)
|
||||
if ("/" === str.charAt(i + 1)) {
|
||||
const start = i + 1;
|
||||
while (++i) {
|
||||
const c = str.charAt(i);
|
||||
if ("," === c) break;
|
||||
if (i === str.length) break;
|
||||
}
|
||||
p.nsp = str.substring(start, i);
|
||||
} else {
|
||||
p.nsp = "/";
|
||||
}
|
||||
|
||||
private static isPayloadValid(type: PacketType, payload: any): boolean {
|
||||
switch (type) {
|
||||
case PacketType.CONNECT:
|
||||
return typeof payload === "object"
|
||||
case PacketType.DISCONNECT:
|
||||
return payload === undefined
|
||||
case PacketType.CONNECT_ERROR:
|
||||
return typeof payload === "string" || typeof payload === "object"
|
||||
case PacketType.EVENT:
|
||||
case PacketType.BINARY_EVENT:
|
||||
return Array.isArray(payload) && payload.length > 0
|
||||
case PacketType.ACK:
|
||||
case PacketType.BINARY_ACK:
|
||||
return Array.isArray(payload)
|
||||
// look up id
|
||||
const next = str.charAt(i + 1);
|
||||
if ("" !== next && Number(next) == next) {
|
||||
const start = i + 1;
|
||||
while (++i) {
|
||||
const c = str.charAt(i);
|
||||
if (null == c || Number(c) != c) {
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
if (i === str.length) break;
|
||||
}
|
||||
p.id = Number(str.substring(start, i + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deallocates a parser's resources
|
||||
*/
|
||||
public destroy() {
|
||||
if (this.reconstructor) {
|
||||
this.reconstructor.finishedReconstruction()
|
||||
}
|
||||
// look up json data
|
||||
if (str.charAt(++i)) {
|
||||
const payload = this.tryParse(str.substr(i));
|
||||
if (Decoder.isPayloadValid(p.type, payload)) {
|
||||
p.data = payload;
|
||||
} else {
|
||||
throw new Error("invalid payload");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tryParse(str) {
|
||||
debug("decoded %s as %j", str, p);
|
||||
return p;
|
||||
}
|
||||
|
||||
private tryParse(str) {
|
||||
try {
|
||||
return JSON.parse(str)
|
||||
} catch (error: any) {
|
||||
return false
|
||||
return JSON.parse(str, this.reviver);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static isPayloadValid(type: PacketType, payload: any): boolean {
|
||||
switch (type) {
|
||||
case PacketType.CONNECT:
|
||||
return typeof payload === "object";
|
||||
case PacketType.DISCONNECT:
|
||||
return payload === undefined;
|
||||
case PacketType.CONNECT_ERROR:
|
||||
return typeof payload === "string" || typeof payload === "object";
|
||||
case PacketType.EVENT:
|
||||
case PacketType.BINARY_EVENT:
|
||||
return Array.isArray(payload) && payload.length > 0;
|
||||
case PacketType.ACK:
|
||||
case PacketType.BINARY_ACK:
|
||||
return Array.isArray(payload);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deallocates a parser's resources
|
||||
*/
|
||||
public destroy() {
|
||||
if (this.reconstructor) {
|
||||
this.reconstructor.finishedReconstruction();
|
||||
this.reconstructor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,37 +303,37 @@ function tryParse(str) {
|
||||
*/
|
||||
|
||||
class BinaryReconstructor {
|
||||
private reconPack
|
||||
private buffers: Array<Buffer | ArrayBuffer> = [];
|
||||
private reconPack;
|
||||
private buffers: Array<Buffer | ArrayBuffer> = [];
|
||||
|
||||
constructor(readonly packet: Packet) {
|
||||
this.reconPack = packet
|
||||
}
|
||||
constructor(readonly packet: Packet) {
|
||||
this.reconPack = packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to be called when binary data received from connection
|
||||
* after a BINARY_EVENT packet.
|
||||
*
|
||||
* @param {Buffer | ArrayBuffer} binData - the raw binary data received
|
||||
* @return {null | Object} returns null if more binary data is expected or
|
||||
* a reconstructed packet object if all buffers have been received.
|
||||
*/
|
||||
public takeBinaryData(binData) {
|
||||
this.buffers.push(binData)
|
||||
if (this.buffers.length === this.reconPack.attachments) {
|
||||
// done with buffer list
|
||||
const packet = reconstructPacket(this.reconPack, this.buffers)
|
||||
this.finishedReconstruction()
|
||||
return packet
|
||||
}
|
||||
return null
|
||||
/**
|
||||
* Method to be called when binary data received from connection
|
||||
* after a BINARY_EVENT packet.
|
||||
*
|
||||
* @param {Buffer | ArrayBuffer} binData - the raw binary data received
|
||||
* @return {null | Object} returns null if more binary data is expected or
|
||||
* a reconstructed packet object if all buffers have been received.
|
||||
*/
|
||||
public takeBinaryData(binData) {
|
||||
this.buffers.push(binData);
|
||||
if (this.buffers.length === this.reconPack.attachments) {
|
||||
// done with buffer list
|
||||
const packet = reconstructPacket(this.reconPack, this.buffers);
|
||||
this.finishedReconstruction();
|
||||
return packet;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up binary packet reconstruction variables.
|
||||
*/
|
||||
public finishedReconstruction() {
|
||||
this.reconPack = null
|
||||
this.buffers = []
|
||||
}
|
||||
/**
|
||||
* Cleans up binary packet reconstruction variables.
|
||||
*/
|
||||
public finishedReconstruction() {
|
||||
this.reconPack = null;
|
||||
this.buffers = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
const withNativeArrayBuffer: boolean = typeof ArrayBuffer === "function"
|
||||
const withNativeArrayBuffer: boolean = typeof ArrayBuffer === "function";
|
||||
|
||||
const isView = (obj: any) => {
|
||||
return typeof ArrayBuffer.isView === "function"
|
||||
? ArrayBuffer.isView(obj)
|
||||
: obj.buffer instanceof ArrayBuffer
|
||||
}
|
||||
return typeof ArrayBuffer.isView === "function"
|
||||
? ArrayBuffer.isView(obj)
|
||||
: obj.buffer instanceof ArrayBuffer;
|
||||
};
|
||||
|
||||
const toString = Object.prototype.toString
|
||||
const withNativeBlob = false
|
||||
// typeof Blob === "function" ||
|
||||
// (typeof Blob !== "undefined" &&
|
||||
// toString.call(Blob) === "[object BlobConstructor]")
|
||||
const withNativeFile = false
|
||||
// typeof File === "function" ||
|
||||
// (typeof File !== "undefined" &&
|
||||
// toString.call(File) === "[object FileConstructor]")
|
||||
const toString = Object.prototype.toString;
|
||||
const withNativeBlob =
|
||||
typeof Blob === "function" ||
|
||||
(typeof Blob !== "undefined" &&
|
||||
toString.call(Blob) === "[object BlobConstructor]");
|
||||
const withNativeFile =
|
||||
typeof File === "function" ||
|
||||
(typeof File !== "undefined" &&
|
||||
toString.call(File) === "[object FileConstructor]");
|
||||
|
||||
/**
|
||||
* Returns true if obj is a Buffer, an ArrayBuffer, a Blob or a File.
|
||||
@@ -23,43 +23,44 @@ const withNativeFile = false
|
||||
*/
|
||||
|
||||
export function isBinary(obj: any) {
|
||||
return (
|
||||
(withNativeArrayBuffer && (obj instanceof ArrayBuffer || isView(obj)))
|
||||
// || (withNativeBlob && obj instanceof Blob) || (withNativeFile && obj instanceof File)
|
||||
)
|
||||
return (
|
||||
(withNativeArrayBuffer && (obj instanceof ArrayBuffer || isView(obj))) ||
|
||||
(withNativeBlob && obj instanceof Blob) ||
|
||||
(withNativeFile && obj instanceof File)
|
||||
);
|
||||
}
|
||||
|
||||
export function hasBinary(obj: any, toJSON?: boolean) {
|
||||
if (!obj || typeof obj !== "object") {
|
||||
return false
|
||||
}
|
||||
if (!obj || typeof obj !== "object") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
for (let i = 0, l = obj.length; i < l; i++) {
|
||||
if (hasBinary(obj[i])) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
if (Array.isArray(obj)) {
|
||||
for (let i = 0, l = obj.length; i < l; i++) {
|
||||
if (hasBinary(obj[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isBinary(obj)) {
|
||||
return true
|
||||
if (isBinary(obj)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
obj.toJSON &&
|
||||
typeof obj.toJSON === "function" &&
|
||||
arguments.length === 1
|
||||
) {
|
||||
return hasBinary(obj.toJSON(), true);
|
||||
}
|
||||
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key) && hasBinary(obj[key])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
obj.toJSON &&
|
||||
typeof obj.toJSON === "function" &&
|
||||
arguments.length === 1
|
||||
) {
|
||||
return hasBinary(obj.toJSON(), true)
|
||||
}
|
||||
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key) && hasBinary(obj[key])) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,320 +1,561 @@
|
||||
// import type { BroadcastFlags, Room, SocketId } from "socket.io-adapter"
|
||||
import type { BroadcastFlags, Room, SocketId } from "../socket.io-adapter"
|
||||
import { Handshake, RESERVED_EVENTS, Socket } from "./socket"
|
||||
// import { PacketType } from "socket.io-parser"
|
||||
import { PacketType } from "../socket.io-parser"
|
||||
// import type { Adapter } from "socket.io-adapter"
|
||||
import type { Adapter } from "../socket.io-adapter"
|
||||
import type { BroadcastFlags, Room, SocketId } from "../socket.io-adapter";
|
||||
import { Handshake, RESERVED_EVENTS, Socket } from "./socket";
|
||||
import { PacketType } from "../socket.io-parser";
|
||||
import type { Adapter } from "../socket.io-adapter";
|
||||
import type {
|
||||
EventParams,
|
||||
EventNames,
|
||||
EventsMap,
|
||||
TypedEventBroadcaster,
|
||||
} from "./typed-events"
|
||||
EventParams,
|
||||
EventNames,
|
||||
EventsMap,
|
||||
TypedEventBroadcaster,
|
||||
DecorateAcknowledgements,
|
||||
DecorateAcknowledgementsWithTimeoutAndMultipleResponses,
|
||||
AllButLast,
|
||||
Last,
|
||||
SecondArg,
|
||||
} from "./typed-events";
|
||||
|
||||
export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
implements TypedEventBroadcaster<EmitEvents>
|
||||
export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
|
||||
implements TypedEventBroadcaster<EmitEvents>
|
||||
{
|
||||
constructor(
|
||||
private readonly adapter: Adapter,
|
||||
private readonly rooms: Set<Room> = new Set<Room>(),
|
||||
private readonly exceptRooms: Set<Room> = new Set<Room>(),
|
||||
private readonly flags: BroadcastFlags = {}
|
||||
) { }
|
||||
constructor(
|
||||
private readonly adapter: Adapter,
|
||||
private readonly rooms: Set<Room> = new Set<Room>(),
|
||||
private readonly exceptRooms: Set<Room> = new Set<Room>(),
|
||||
private readonly flags: BroadcastFlags & {
|
||||
expectSingleResponse?: boolean;
|
||||
} = {}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Targets a room when emitting.
|
||||
*
|
||||
* @param room
|
||||
* @return a new BroadcastOperator instance
|
||||
* @public
|
||||
*/
|
||||
public to(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
const rooms = new Set(this.rooms)
|
||||
if (Array.isArray(room)) {
|
||||
room.forEach((r) => rooms.add(r))
|
||||
/**
|
||||
* Targets a room when emitting.
|
||||
*
|
||||
* @example
|
||||
* // the “foo” event will be broadcast to all connected clients in the “room-101” room
|
||||
* io.to("room-101").emit("foo", "bar");
|
||||
*
|
||||
* // with an array of rooms (a client will be notified at most once)
|
||||
* io.to(["room-101", "room-102"]).emit("foo", "bar");
|
||||
*
|
||||
* // with multiple chained calls
|
||||
* io.to("room-101").to("room-102").emit("foo", "bar");
|
||||
*
|
||||
* @param room - a room, or an array of rooms
|
||||
* @return a new {@link BroadcastOperator} instance for chaining
|
||||
*/
|
||||
public to(room: Room | Room[]) {
|
||||
const rooms = new Set(this.rooms);
|
||||
if (Array.isArray(room)) {
|
||||
room.forEach((r) => rooms.add(r));
|
||||
} else {
|
||||
rooms.add(room);
|
||||
}
|
||||
return new BroadcastOperator<EmitEvents, SocketData>(
|
||||
this.adapter,
|
||||
rooms,
|
||||
this.exceptRooms,
|
||||
this.flags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Targets a room when emitting. Similar to `to()`, but might feel clearer in some cases:
|
||||
*
|
||||
* @example
|
||||
* // disconnect all clients in the "room-101" room
|
||||
* io.in("room-101").disconnectSockets();
|
||||
*
|
||||
* @param room - a room, or an array of rooms
|
||||
* @return a new {@link BroadcastOperator} instance for chaining
|
||||
*/
|
||||
public in(room: Room | Room[]) {
|
||||
return this.to(room);
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes a room when emitting.
|
||||
*
|
||||
* @example
|
||||
* // the "foo" event will be broadcast to all connected clients, except the ones that are in the "room-101" room
|
||||
* io.except("room-101").emit("foo", "bar");
|
||||
*
|
||||
* // with an array of rooms
|
||||
* io.except(["room-101", "room-102"]).emit("foo", "bar");
|
||||
*
|
||||
* // with multiple chained calls
|
||||
* io.except("room-101").except("room-102").emit("foo", "bar");
|
||||
*
|
||||
* @param room - a room, or an array of rooms
|
||||
* @return a new {@link BroadcastOperator} instance for chaining
|
||||
*/
|
||||
public except(room: Room | Room[]) {
|
||||
const exceptRooms = new Set(this.exceptRooms);
|
||||
if (Array.isArray(room)) {
|
||||
room.forEach((r) => exceptRooms.add(r));
|
||||
} else {
|
||||
exceptRooms.add(room);
|
||||
}
|
||||
return new BroadcastOperator<EmitEvents, SocketData>(
|
||||
this.adapter,
|
||||
this.rooms,
|
||||
exceptRooms,
|
||||
this.flags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compress flag.
|
||||
*
|
||||
* @example
|
||||
* io.compress(false).emit("hello");
|
||||
*
|
||||
* @param compress - if `true`, compresses the sending data
|
||||
* @return a new BroadcastOperator instance
|
||||
*/
|
||||
public compress(compress: boolean) {
|
||||
const flags = Object.assign({}, this.flags, { compress });
|
||||
return new BroadcastOperator<EmitEvents, SocketData>(
|
||||
this.adapter,
|
||||
this.rooms,
|
||||
this.exceptRooms,
|
||||
flags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
|
||||
* receive messages (because of network slowness or other issues, or because they’re connected through long polling
|
||||
* and is in the middle of a request-response cycle).
|
||||
*
|
||||
* @example
|
||||
* io.volatile.emit("hello"); // the clients may or may not receive it
|
||||
*
|
||||
* @return a new BroadcastOperator instance
|
||||
*/
|
||||
public get volatile() {
|
||||
const flags = Object.assign({}, this.flags, { volatile: true });
|
||||
return new BroadcastOperator<EmitEvents, SocketData>(
|
||||
this.adapter,
|
||||
this.rooms,
|
||||
this.exceptRooms,
|
||||
flags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
|
||||
*
|
||||
* @example
|
||||
* // the “foo” event will be broadcast to all connected clients on this node
|
||||
* io.local.emit("foo", "bar");
|
||||
*
|
||||
* @return a new {@link BroadcastOperator} instance for chaining
|
||||
*/
|
||||
public get local() {
|
||||
const flags = Object.assign({}, this.flags, { local: true });
|
||||
return new BroadcastOperator<EmitEvents, SocketData>(
|
||||
this.adapter,
|
||||
this.rooms,
|
||||
this.exceptRooms,
|
||||
flags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a timeout in milliseconds for the next operation
|
||||
*
|
||||
* @example
|
||||
* io.timeout(1000).emit("some-event", (err, responses) => {
|
||||
* if (err) {
|
||||
* // some clients did not acknowledge the event in the given delay
|
||||
* } else {
|
||||
* console.log(responses); // one response per client
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @param timeout
|
||||
*/
|
||||
public timeout(timeout: number) {
|
||||
const flags = Object.assign({}, this.flags, { timeout });
|
||||
return new BroadcastOperator<
|
||||
DecorateAcknowledgementsWithTimeoutAndMultipleResponses<EmitEvents>,
|
||||
SocketData
|
||||
>(this.adapter, this.rooms, this.exceptRooms, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits to all clients.
|
||||
*
|
||||
* @example
|
||||
* // the “foo” event will be broadcast to all connected clients
|
||||
* io.emit("foo", "bar");
|
||||
*
|
||||
* // the “foo” event will be broadcast to all connected clients in the “room-101” room
|
||||
* io.to("room-101").emit("foo", "bar");
|
||||
*
|
||||
* // with an acknowledgement expected from all connected clients
|
||||
* io.timeout(1000).emit("some-event", (err, responses) => {
|
||||
* if (err) {
|
||||
* // some clients did not acknowledge the event in the given delay
|
||||
* } else {
|
||||
* console.log(responses); // one response per client
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @return Always true
|
||||
*/
|
||||
public emit<Ev extends EventNames<EmitEvents>>(
|
||||
ev: Ev,
|
||||
...args: EventParams<EmitEvents, Ev>
|
||||
): boolean {
|
||||
if (RESERVED_EVENTS.has(ev)) {
|
||||
throw new Error(`"${String(ev)}" is a reserved event name`);
|
||||
}
|
||||
// set up packet object
|
||||
const data = [ev, ...args];
|
||||
const packet = {
|
||||
type: PacketType.EVENT,
|
||||
data: data,
|
||||
};
|
||||
|
||||
const withAck = typeof data[data.length - 1] === "function";
|
||||
|
||||
if (!withAck) {
|
||||
this.adapter.broadcast(packet, {
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const ack = data.pop() as (...args: any[]) => void;
|
||||
let timedOut = false;
|
||||
let responses: any[] = [];
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
ack.apply(this, [
|
||||
new Error("operation has timed out"),
|
||||
this.flags.expectSingleResponse ? null : responses,
|
||||
]);
|
||||
}, this.flags.timeout);
|
||||
|
||||
let expectedServerCount = -1;
|
||||
let actualServerCount = 0;
|
||||
let expectedClientCount = 0;
|
||||
|
||||
const checkCompleteness = () => {
|
||||
if (
|
||||
!timedOut &&
|
||||
expectedServerCount === actualServerCount &&
|
||||
responses.length === expectedClientCount
|
||||
) {
|
||||
clearTimeout(timer);
|
||||
ack.apply(this, [
|
||||
null,
|
||||
this.flags.expectSingleResponse ? null : responses,
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
this.adapter.broadcastWithAck(
|
||||
packet,
|
||||
{
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
},
|
||||
(clientCount) => {
|
||||
// each Socket.IO server in the cluster sends the number of clients that were notified
|
||||
expectedClientCount += clientCount;
|
||||
actualServerCount++;
|
||||
checkCompleteness();
|
||||
},
|
||||
(clientResponse) => {
|
||||
// each client sends an acknowledgement
|
||||
responses.push(clientResponse);
|
||||
checkCompleteness();
|
||||
}
|
||||
);
|
||||
|
||||
this.adapter.serverCount().then((serverCount) => {
|
||||
expectedServerCount = serverCount;
|
||||
checkCompleteness();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event and waits for an acknowledgement from all clients.
|
||||
*
|
||||
* @example
|
||||
* try {
|
||||
* const responses = await io.timeout(1000).emitWithAck("some-event");
|
||||
* console.log(responses); // one response per client
|
||||
* } catch (e) {
|
||||
* // some clients did not acknowledge the event in the given delay
|
||||
* }
|
||||
*
|
||||
* @return a Promise that will be fulfilled when all clients have acknowledged the event
|
||||
*/
|
||||
public emitWithAck<Ev extends EventNames<EmitEvents>>(
|
||||
ev: Ev,
|
||||
...args: AllButLast<EventParams<EmitEvents, Ev>>
|
||||
): Promise<SecondArg<Last<EventParams<EmitEvents, Ev>>>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
args.push((err, responses) => {
|
||||
if (err) {
|
||||
err.responses = responses;
|
||||
return reject(err);
|
||||
} else {
|
||||
rooms.add(room)
|
||||
return resolve(responses);
|
||||
}
|
||||
return new BroadcastOperator(
|
||||
this.adapter,
|
||||
rooms,
|
||||
this.exceptRooms,
|
||||
this.flags
|
||||
)
|
||||
});
|
||||
this.emit(ev, ...(args as any[] as EventParams<EmitEvents, Ev>));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of clients.
|
||||
*
|
||||
* @deprecated this method will be removed in the next major release, please use {@link Server#serverSideEmit} or
|
||||
* {@link fetchSockets} instead.
|
||||
*/
|
||||
public allSockets(): Promise<Set<SocketId>> {
|
||||
if (!this.adapter) {
|
||||
throw new Error(
|
||||
"No adapter for this namespace, are you trying to get the list of clients of a dynamic namespace?"
|
||||
);
|
||||
}
|
||||
return this.adapter.sockets(this.rooms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Targets a room when emitting.
|
||||
*
|
||||
* @param room
|
||||
* @return a new BroadcastOperator instance
|
||||
* @public
|
||||
*/
|
||||
public in(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
return this.to(room)
|
||||
}
|
||||
/**
|
||||
* Returns the matching socket instances. This method works across a cluster of several Socket.IO servers.
|
||||
*
|
||||
* Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
|
||||
*
|
||||
* @example
|
||||
* // return all Socket instances
|
||||
* const sockets = await io.fetchSockets();
|
||||
*
|
||||
* // return all Socket instances in the "room1" room
|
||||
* const sockets = await io.in("room1").fetchSockets();
|
||||
*
|
||||
* for (const socket of sockets) {
|
||||
* console.log(socket.id);
|
||||
* console.log(socket.handshake);
|
||||
* console.log(socket.rooms);
|
||||
* console.log(socket.data);
|
||||
*
|
||||
* socket.emit("hello");
|
||||
* socket.join("room1");
|
||||
* socket.leave("room2");
|
||||
* socket.disconnect();
|
||||
* }
|
||||
*/
|
||||
public fetchSockets(): Promise<RemoteSocket<EmitEvents, SocketData>[]> {
|
||||
return this.adapter
|
||||
.fetchSockets({
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
})
|
||||
.then((sockets) => {
|
||||
return sockets.map((socket) => {
|
||||
if (socket instanceof Socket) {
|
||||
// FIXME the TypeScript compiler complains about missing private properties
|
||||
return socket as unknown as RemoteSocket<EmitEvents, SocketData>;
|
||||
} else {
|
||||
return new RemoteSocket(
|
||||
this.adapter,
|
||||
socket as SocketDetails<SocketData>
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes a room when emitting.
|
||||
*
|
||||
* @param room
|
||||
* @return a new BroadcastOperator instance
|
||||
* @public
|
||||
*/
|
||||
public except(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
const exceptRooms = new Set(this.exceptRooms)
|
||||
if (Array.isArray(room)) {
|
||||
room.forEach((r) => exceptRooms.add(r))
|
||||
} else {
|
||||
exceptRooms.add(room)
|
||||
}
|
||||
return new BroadcastOperator(
|
||||
this.adapter,
|
||||
this.rooms,
|
||||
exceptRooms,
|
||||
this.flags
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Makes the matching socket instances join the specified rooms.
|
||||
*
|
||||
* Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* // make all socket instances join the "room1" room
|
||||
* io.socketsJoin("room1");
|
||||
*
|
||||
* // make all socket instances in the "room1" room join the "room2" and "room3" rooms
|
||||
* io.in("room1").socketsJoin(["room2", "room3"]);
|
||||
*
|
||||
* @param room - a room, or an array of rooms
|
||||
*/
|
||||
public socketsJoin(room: Room | Room[]): void {
|
||||
this.adapter.addSockets(
|
||||
{
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
},
|
||||
Array.isArray(room) ? room : [room]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compress flag.
|
||||
*
|
||||
* @param compress - if `true`, compresses the sending data
|
||||
* @return a new BroadcastOperator instance
|
||||
* @public
|
||||
*/
|
||||
public compress(compress: boolean): BroadcastOperator<EmitEvents> {
|
||||
const flags = Object.assign({}, this.flags, { compress })
|
||||
return new BroadcastOperator(
|
||||
this.adapter,
|
||||
this.rooms,
|
||||
this.exceptRooms,
|
||||
flags
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Makes the matching socket instances leave the specified rooms.
|
||||
*
|
||||
* Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
|
||||
*
|
||||
* @example
|
||||
* // make all socket instances leave the "room1" room
|
||||
* io.socketsLeave("room1");
|
||||
*
|
||||
* // make all socket instances in the "room1" room leave the "room2" and "room3" rooms
|
||||
* io.in("room1").socketsLeave(["room2", "room3"]);
|
||||
*
|
||||
* @param room - a room, or an array of rooms
|
||||
*/
|
||||
public socketsLeave(room: Room | Room[]): void {
|
||||
this.adapter.delSockets(
|
||||
{
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
},
|
||||
Array.isArray(room) ? room : [room]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
|
||||
* receive messages (because of network slowness or other issues, or because they’re connected through long polling
|
||||
* and is in the middle of a request-response cycle).
|
||||
*
|
||||
* @return a new BroadcastOperator instance
|
||||
* @public
|
||||
*/
|
||||
public get volatile(): BroadcastOperator<EmitEvents> {
|
||||
const flags = Object.assign({}, this.flags, { volatile: true })
|
||||
return new BroadcastOperator(
|
||||
this.adapter,
|
||||
this.rooms,
|
||||
this.exceptRooms,
|
||||
flags
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
|
||||
*
|
||||
* @return a new BroadcastOperator instance
|
||||
* @public
|
||||
*/
|
||||
public get local(): BroadcastOperator<EmitEvents> {
|
||||
const flags = Object.assign({}, this.flags, { local: true })
|
||||
return new BroadcastOperator(
|
||||
this.adapter,
|
||||
this.rooms,
|
||||
this.exceptRooms,
|
||||
flags
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits to all clients.
|
||||
*
|
||||
* @return Always true
|
||||
* @public
|
||||
*/
|
||||
public emit<Ev extends EventNames<EmitEvents>>(
|
||||
ev: Ev,
|
||||
...args: EventParams<EmitEvents, Ev>
|
||||
): boolean {
|
||||
if (RESERVED_EVENTS.has(ev)) {
|
||||
throw new Error(`"${ev}" is a reserved event name`)
|
||||
}
|
||||
// set up packet object
|
||||
const data = [ev, ...args]
|
||||
const packet = {
|
||||
type: PacketType.EVENT,
|
||||
data: data,
|
||||
}
|
||||
|
||||
if ("function" == typeof data[data.length - 1]) {
|
||||
throw new Error("Callbacks are not supported when broadcasting")
|
||||
}
|
||||
|
||||
this.adapter.broadcast(packet, {
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of clients.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public allSockets(): Promise<Set<SocketId>> {
|
||||
if (!this.adapter) {
|
||||
throw new Error(
|
||||
"No adapter for this namespace, are you trying to get the list of clients of a dynamic namespace?"
|
||||
)
|
||||
}
|
||||
return this.adapter.sockets(this.rooms)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the matching socket instances
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public fetchSockets(): Promise<RemoteSocket<EmitEvents>[]> {
|
||||
return this.adapter
|
||||
.fetchSockets({
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
})
|
||||
.then((sockets) => {
|
||||
return sockets.map((socket) => {
|
||||
if (socket instanceof Socket) {
|
||||
// FIXME the TypeScript compiler complains about missing private properties
|
||||
return socket as unknown as RemoteSocket<EmitEvents>
|
||||
} else {
|
||||
return new RemoteSocket(this.adapter, socket as SocketDetails)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the matching socket instances join the specified rooms
|
||||
*
|
||||
* @param room
|
||||
* @public
|
||||
*/
|
||||
public socketsJoin(room: Room | Room[]): void {
|
||||
this.adapter.addSockets(
|
||||
{
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
},
|
||||
Array.isArray(room) ? room : [room]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the matching socket instances leave the specified rooms
|
||||
*
|
||||
* @param room
|
||||
* @public
|
||||
*/
|
||||
public socketsLeave(room: Room | Room[]): void {
|
||||
this.adapter.delSockets(
|
||||
{
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
},
|
||||
Array.isArray(room) ? room : [room]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the matching socket instances disconnect
|
||||
*
|
||||
* @param close - whether to close the underlying connection
|
||||
* @public
|
||||
*/
|
||||
public disconnectSockets(close: boolean = false): void {
|
||||
this.adapter.disconnectSockets(
|
||||
{
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
},
|
||||
close
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Makes the matching socket instances disconnect.
|
||||
*
|
||||
* Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
|
||||
*
|
||||
* @example
|
||||
* // make all socket instances disconnect (the connections might be kept alive for other namespaces)
|
||||
* io.disconnectSockets();
|
||||
*
|
||||
* // make all socket instances in the "room1" room disconnect and close the underlying connections
|
||||
* io.in("room1").disconnectSockets(true);
|
||||
*
|
||||
* @param close - whether to close the underlying connection
|
||||
*/
|
||||
public disconnectSockets(close: boolean = false): void {
|
||||
this.adapter.disconnectSockets(
|
||||
{
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
},
|
||||
close
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format of the data when the Socket instance exists on another Socket.IO server
|
||||
*/
|
||||
interface SocketDetails {
|
||||
id: SocketId
|
||||
handshake: Handshake
|
||||
rooms: Room[]
|
||||
data: any
|
||||
interface SocketDetails<SocketData> {
|
||||
id: SocketId;
|
||||
handshake: Handshake;
|
||||
rooms: Room[];
|
||||
data: SocketData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose of subset of the attributes and methods of the Socket class
|
||||
*/
|
||||
export class RemoteSocket<EmitEvents extends EventsMap>
|
||||
implements TypedEventBroadcaster<EmitEvents>
|
||||
export class RemoteSocket<EmitEvents extends EventsMap, SocketData>
|
||||
implements TypedEventBroadcaster<EmitEvents>
|
||||
{
|
||||
public readonly id: SocketId
|
||||
public readonly handshake: Handshake
|
||||
public readonly rooms: Set<Room>
|
||||
public readonly data: any
|
||||
public readonly id: SocketId;
|
||||
public readonly handshake: Handshake;
|
||||
public readonly rooms: Set<Room>;
|
||||
public readonly data: SocketData;
|
||||
|
||||
private readonly operator: BroadcastOperator<EmitEvents>
|
||||
private readonly operator: BroadcastOperator<EmitEvents, SocketData>;
|
||||
|
||||
constructor(adapter: Adapter, details: SocketDetails) {
|
||||
this.id = details.id
|
||||
this.handshake = details.handshake
|
||||
this.rooms = new Set(details.rooms)
|
||||
this.data = details.data
|
||||
this.operator = new BroadcastOperator(adapter, new Set([this.id]))
|
||||
}
|
||||
constructor(adapter: Adapter, details: SocketDetails<SocketData>) {
|
||||
this.id = details.id;
|
||||
this.handshake = details.handshake;
|
||||
this.rooms = new Set(details.rooms);
|
||||
this.data = details.data;
|
||||
this.operator = new BroadcastOperator<EmitEvents, SocketData>(
|
||||
adapter,
|
||||
new Set([this.id]),
|
||||
new Set(),
|
||||
{
|
||||
expectSingleResponse: true, // so that remoteSocket.emit() with acknowledgement behaves like socket.emit()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public emit<Ev extends EventNames<EmitEvents>>(
|
||||
ev: Ev,
|
||||
...args: EventParams<EmitEvents, Ev>
|
||||
): boolean {
|
||||
return this.operator.emit(ev, ...args)
|
||||
}
|
||||
/**
|
||||
* Adds a timeout in milliseconds for the next operation.
|
||||
*
|
||||
* @example
|
||||
* const sockets = await io.fetchSockets();
|
||||
*
|
||||
* for (const socket of sockets) {
|
||||
* if (someCondition) {
|
||||
* socket.timeout(1000).emit("some-event", (err) => {
|
||||
* if (err) {
|
||||
* // the client did not acknowledge the event in the given delay
|
||||
* }
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // note: if possible, using a room instead of looping over all sockets is preferable
|
||||
* io.timeout(1000).to(someConditionRoom).emit("some-event", (err, responses) => {
|
||||
* // ...
|
||||
* });
|
||||
*
|
||||
* @param timeout
|
||||
*/
|
||||
public timeout(timeout: number) {
|
||||
return this.operator.timeout(timeout) as BroadcastOperator<
|
||||
DecorateAcknowledgements<EmitEvents>,
|
||||
SocketData
|
||||
>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins a room.
|
||||
*
|
||||
* @param {String|Array} room - room or array of rooms
|
||||
* @public
|
||||
*/
|
||||
public join(room: Room | Room[]): void {
|
||||
return this.operator.socketsJoin(room)
|
||||
}
|
||||
public emit<Ev extends EventNames<EmitEvents>>(
|
||||
ev: Ev,
|
||||
...args: EventParams<EmitEvents, Ev>
|
||||
): boolean {
|
||||
return this.operator.emit(ev, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaves a room.
|
||||
*
|
||||
* @param {String} room
|
||||
* @public
|
||||
*/
|
||||
public leave(room: Room): void {
|
||||
return this.operator.socketsLeave(room)
|
||||
}
|
||||
/**
|
||||
* Joins a room.
|
||||
*
|
||||
* @param {String|Array} room - room or array of rooms
|
||||
*/
|
||||
public join(room: Room | Room[]): void {
|
||||
return this.operator.socketsJoin(room);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects this client.
|
||||
*
|
||||
* @param {Boolean} close - if `true`, closes the underlying connection
|
||||
* @return {Socket} self
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public disconnect(close = false): this {
|
||||
this.operator.disconnectSockets(close)
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* Leaves a room.
|
||||
*
|
||||
* @param {String} room
|
||||
*/
|
||||
public leave(room: Room): void {
|
||||
return this.operator.socketsLeave(room);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects this client.
|
||||
*
|
||||
* @param {Boolean} close - if `true`, closes the underlying connection
|
||||
* @return {Socket} self
|
||||
*/
|
||||
public disconnect(close = false): this {
|
||||
this.operator.disconnectSockets(close);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,331 +1,353 @@
|
||||
// import { Decoder, Encoder, Packet, PacketType } from "socket.io-parser"
|
||||
import { Decoder, Encoder, Packet, PacketType } from "../socket.io-parser"
|
||||
import { Decoder, Encoder, Packet, PacketType } from "../socket.io-parser";
|
||||
// import debugModule = require("debug")
|
||||
import url = require("url")
|
||||
import url = require("url");
|
||||
// import type { IncomingMessage } from "http"
|
||||
import type { Server } from "./index"
|
||||
import type { Namespace } from "./namespace"
|
||||
import type { EventsMap } from "./typed-events"
|
||||
import type { Socket } from "./socket"
|
||||
// import type { SocketId } from "socket.io-adapter"
|
||||
import type { SocketId } from "../socket.io-adapter"
|
||||
import type { Socket as EngineIOSocket } from '../engine.io/socket'
|
||||
import type { Server } from "./index";
|
||||
import type { Namespace } from "./namespace";
|
||||
import type { EventsMap } from "./typed-events";
|
||||
import type { Socket } from "./socket";
|
||||
import type { SocketId } from "../socket.io-adapter";
|
||||
import type { Socket as RawSocket } from "../engine.io/socket";
|
||||
|
||||
// const debug = debugModule("socket.io:client");
|
||||
const debug = require("../debug")("socket.io:client");
|
||||
|
||||
interface WriteOptions {
|
||||
compress?: boolean
|
||||
volatile?: boolean
|
||||
preEncoded?: boolean
|
||||
wsPreEncoded?: string
|
||||
compress?: boolean;
|
||||
volatile?: boolean;
|
||||
preEncoded?: boolean;
|
||||
wsPreEncoded?: string;
|
||||
}
|
||||
|
||||
type CloseReason =
|
||||
| "transport error"
|
||||
| "transport close"
|
||||
| "forced close"
|
||||
| "ping timeout"
|
||||
| "parse error";
|
||||
|
||||
export class Client<
|
||||
ListenEvents extends EventsMap,
|
||||
EmitEvents extends EventsMap,
|
||||
ServerSideEvents extends EventsMap
|
||||
> {
|
||||
public readonly conn: EngineIOSocket
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
readonly id: string
|
||||
private readonly server: Server<ListenEvents, EmitEvents, ServerSideEvents>
|
||||
private readonly encoder: Encoder
|
||||
private readonly decoder: any
|
||||
private sockets: Map<
|
||||
SocketId,
|
||||
Socket<ListenEvents, EmitEvents, ServerSideEvents>
|
||||
> = new Map()
|
||||
private nsps: Map<
|
||||
string,
|
||||
Socket<ListenEvents, EmitEvents, ServerSideEvents>
|
||||
> = new Map()
|
||||
private connectTimeout: NodeJS.Timeout
|
||||
ListenEvents extends EventsMap,
|
||||
EmitEvents extends EventsMap,
|
||||
ServerSideEvents extends EventsMap,
|
||||
SocketData = any
|
||||
> {
|
||||
public readonly conn: RawSocket;
|
||||
|
||||
/**
|
||||
* Client constructor.
|
||||
*
|
||||
* @param server instance
|
||||
* @param conn
|
||||
* @package
|
||||
*/
|
||||
constructor(
|
||||
server: Server<ListenEvents, EmitEvents, ServerSideEvents>,
|
||||
conn: EngineIOSocket
|
||||
) {
|
||||
this.server = server
|
||||
this.conn = conn
|
||||
this.encoder = server.encoder
|
||||
this.decoder = new server._parser.Decoder()
|
||||
this.id = conn.id
|
||||
this.setup()
|
||||
private readonly id: string;
|
||||
private readonly server: Server<
|
||||
ListenEvents,
|
||||
EmitEvents,
|
||||
ServerSideEvents,
|
||||
SocketData
|
||||
>;
|
||||
private readonly encoder: Encoder;
|
||||
private readonly decoder: Decoder;
|
||||
private sockets: Map<
|
||||
SocketId,
|
||||
Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
> = new Map();
|
||||
private nsps: Map<
|
||||
string,
|
||||
Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
> = new Map();
|
||||
private connectTimeout?: NodeJS.Timeout;
|
||||
|
||||
/**
|
||||
* Client constructor.
|
||||
*
|
||||
* @param server instance
|
||||
* @param conn
|
||||
* @package
|
||||
*/
|
||||
constructor(
|
||||
server: Server<ListenEvents, EmitEvents, ServerSideEvents, SocketData>,
|
||||
conn: any
|
||||
) {
|
||||
this.server = server;
|
||||
this.conn = conn;
|
||||
this.encoder = server.encoder;
|
||||
this.decoder = new server._parser.Decoder();
|
||||
this.id = conn.id;
|
||||
this.setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the reference to the request that originated the Engine.IO connection
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
// public get request(): IncomingMessage {
|
||||
public get request(): any {
|
||||
return this.conn.request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up event listeners.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private setup() {
|
||||
this.onclose = this.onclose.bind(this);
|
||||
this.ondata = this.ondata.bind(this);
|
||||
this.onerror = this.onerror.bind(this);
|
||||
this.ondecoded = this.ondecoded.bind(this);
|
||||
|
||||
// @ts-ignore
|
||||
this.decoder.on("decoded", this.ondecoded);
|
||||
this.conn.on("data", this.ondata);
|
||||
this.conn.on("error", this.onerror);
|
||||
this.conn.on("close", this.onclose);
|
||||
|
||||
this.connectTimeout = setTimeout(() => {
|
||||
if (this.nsps.size === 0) {
|
||||
debug("no namespace joined yet, close the client");
|
||||
this.close();
|
||||
} else {
|
||||
debug("the client has already joined a namespace, nothing to do");
|
||||
}
|
||||
}, this.server._connectTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a client to a namespace.
|
||||
*
|
||||
* @param {String} name - the namespace
|
||||
* @param {Object} auth - the auth parameters
|
||||
* @private
|
||||
*/
|
||||
private connect(name: string, auth: Record<string, unknown> = {}): void {
|
||||
if (this.server._nsps.has(name)) {
|
||||
debug("connecting to namespace %s", name);
|
||||
return this.doConnect(name, auth);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the reference to the request that originated the Engine.IO connection
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public get request(): any/** IncomingMessage */ {
|
||||
return this.conn.request
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up event listeners.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private setup() {
|
||||
console.debug(`socket.io client setup conn ${this.conn.id}`)
|
||||
this.onclose = this.onclose.bind(this)
|
||||
this.ondata = this.ondata.bind(this)
|
||||
this.onerror = this.onerror.bind(this)
|
||||
this.ondecoded = this.ondecoded.bind(this)
|
||||
|
||||
// @ts-ignore
|
||||
this.decoder.on("decoded", this.ondecoded)
|
||||
this.conn.on("data", this.ondata)
|
||||
this.conn.on("error", this.onerror)
|
||||
this.conn.on("close", this.onclose)
|
||||
|
||||
this.connectTimeout = setTimeout(() => {
|
||||
if (this.nsps.size === 0) {
|
||||
console.debug(`no namespace joined yet, close the client ${this.id}`)
|
||||
this.close()
|
||||
} else {
|
||||
console.debug(`the client ${this.id} has already joined a namespace, nothing to do`)
|
||||
}
|
||||
}, this.server._connectTimeout)
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a client to a namespace.
|
||||
*
|
||||
* @param {String} name - the namespace
|
||||
* @param {Object} auth - the auth parameters
|
||||
* @private
|
||||
*/
|
||||
private connect(name: string, auth: object = {}): void {
|
||||
if (this.server._nsps.has(name)) {
|
||||
console.debug(`socket.io client ${this.id} connecting to namespace ${name}`)
|
||||
return this.doConnect(name, auth)
|
||||
}
|
||||
|
||||
this.server._checkNamespace(
|
||||
name,
|
||||
auth,
|
||||
(
|
||||
dynamicNspName:
|
||||
| Namespace<ListenEvents, EmitEvents, ServerSideEvents>
|
||||
| false
|
||||
) => {
|
||||
if (dynamicNspName) {
|
||||
console.debug(`dynamic namespace ${dynamicNspName} was created`)
|
||||
this.doConnect(name, auth)
|
||||
} else {
|
||||
console.debug(`creation of namespace ${name} was denied`)
|
||||
this._packet({
|
||||
type: PacketType.CONNECT_ERROR,
|
||||
nsp: name,
|
||||
data: {
|
||||
message: "Invalid namespace",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a client to a namespace.
|
||||
*
|
||||
* @param name - the namespace
|
||||
* @param {Object} auth - the auth parameters
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private doConnect(name: string, auth: object): void {
|
||||
const nsp = this.server.of(name)
|
||||
|
||||
// @java-patch multi thread need direct callback socket
|
||||
const socket = nsp._add(this, auth, (socket) => {
|
||||
this.sockets.set(socket.id, socket)
|
||||
this.nsps.set(nsp.name, socket)
|
||||
|
||||
if (this.connectTimeout) {
|
||||
clearTimeout(this.connectTimeout)
|
||||
this.connectTimeout = undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from all namespaces and closes transport.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_disconnect(): void {
|
||||
for (const socket of this.sockets.values()) {
|
||||
socket.disconnect()
|
||||
}
|
||||
this.sockets.clear()
|
||||
this.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a socket. Called by each `Socket`.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_remove(socket: Socket<ListenEvents, EmitEvents, ServerSideEvents>): void {
|
||||
if (this.sockets.has(socket.id)) {
|
||||
const nsp = this.sockets.get(socket.id)!.nsp.name
|
||||
this.sockets.delete(socket.id)
|
||||
this.nsps.delete(nsp)
|
||||
this.server._checkNamespace(
|
||||
name,
|
||||
auth,
|
||||
(
|
||||
dynamicNspName:
|
||||
| Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
| false
|
||||
) => {
|
||||
if (dynamicNspName) {
|
||||
this.doConnect(name, auth);
|
||||
} else {
|
||||
console.debug("ignoring remove for", socket.id)
|
||||
debug("creation of namespace %s was denied", name);
|
||||
this._packet({
|
||||
type: PacketType.CONNECT_ERROR,
|
||||
nsp: name,
|
||||
data: {
|
||||
message: "Invalid namespace",
|
||||
},
|
||||
});
|
||||
}
|
||||
// @java-patch disconnect client when no live socket
|
||||
process.nextTick(() => {
|
||||
if (this.sockets.size == 0) {
|
||||
this.onclose('no live socket')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying connection.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private close(): void {
|
||||
console.debug(`client ${this.id} close - reason: forcing transport close`)
|
||||
if ("open" === this.conn.readyState) {
|
||||
console.debug("forcing transport close")
|
||||
this.conn.close()
|
||||
this.onclose("forced server close")
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Connects a client to a namespace.
|
||||
*
|
||||
* @param name - the namespace
|
||||
* @param {Object} auth - the auth parameters
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private doConnect(name: string, auth: Record<string, unknown>): void {
|
||||
const nsp = this.server.of(name);
|
||||
|
||||
/**
|
||||
// @java-patch multi thread need direct callback socket
|
||||
const socket = nsp._add(this, auth, (socket) => {
|
||||
this.sockets.set(socket.id, socket);
|
||||
this.nsps.set(nsp.name, socket);
|
||||
|
||||
if (this.connectTimeout) {
|
||||
clearTimeout(this.connectTimeout);
|
||||
this.connectTimeout = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from all namespaces and closes transport.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_disconnect(): void {
|
||||
for (const socket of this.sockets.values()) {
|
||||
socket.disconnect();
|
||||
}
|
||||
this.sockets.clear();
|
||||
this.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a socket. Called by each `Socket`.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_remove(
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
): void {
|
||||
if (this.sockets.has(socket.id)) {
|
||||
const nsp = this.sockets.get(socket.id)!.nsp.name;
|
||||
this.sockets.delete(socket.id);
|
||||
this.nsps.delete(nsp);
|
||||
} else {
|
||||
debug("ignoring remove for %s", socket.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying connection.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private close(): void {
|
||||
if ("open" === this.conn.readyState) {
|
||||
debug("forcing transport close");
|
||||
this.conn.close();
|
||||
this.onclose("forced server close");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a packet to the transport.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @param {Object} opts
|
||||
* @private
|
||||
*/
|
||||
_packet(packet: Packet | any[], opts: WriteOptions = {}): void {
|
||||
if (this.conn.readyState !== "open") {
|
||||
console.debug(`client ${this.id} ignoring packet write ${JSON.stringify(packet)}`)
|
||||
return
|
||||
}
|
||||
const encodedPackets = opts.preEncoded
|
||||
? (packet as any[]) // previous versions of the adapter incorrectly used socket.packet() instead of writeToEngine()
|
||||
: this.encoder.encode(packet as Packet)
|
||||
for (const encodedPacket of encodedPackets) {
|
||||
this.writeToEngine(encodedPacket, opts)
|
||||
}
|
||||
_packet(packet: Packet | any[], opts: WriteOptions = {}): void {
|
||||
if (this.conn.readyState !== "open") {
|
||||
debug("ignoring packet write %j", packet);
|
||||
return;
|
||||
}
|
||||
const encodedPackets = opts.preEncoded
|
||||
? (packet as any[]) // previous versions of the adapter incorrectly used socket.packet() instead of writeToEngine()
|
||||
: this.encoder.encode(packet as Packet);
|
||||
this.writeToEngine(encodedPackets, opts);
|
||||
}
|
||||
|
||||
private writeToEngine(
|
||||
encodedPacket: String | Buffer,
|
||||
opts: WriteOptions
|
||||
): void {
|
||||
if (opts.volatile && !this.conn.transport.writable) {
|
||||
console.debug(`client ${this.id} volatile packet is discarded since the transport is not currently writable`)
|
||||
return
|
||||
}
|
||||
this.conn.write(encodedPacket, opts)
|
||||
private writeToEngine(
|
||||
encodedPackets: Array<string | Buffer>,
|
||||
opts: WriteOptions
|
||||
): void {
|
||||
if (opts.volatile && !this.conn.transport.writable) {
|
||||
debug(
|
||||
"volatile packet is discarded since the transport is not currently writable"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with incoming transport data.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private ondata(data): void {
|
||||
// try/catch is needed for protocol violations (GH-1880)
|
||||
try {
|
||||
this.decoder.add(data)
|
||||
} catch (error: any) {
|
||||
this.onerror(error)
|
||||
}
|
||||
const packets = Array.isArray(encodedPackets)
|
||||
? encodedPackets
|
||||
: [encodedPackets];
|
||||
for (const encodedPacket of packets) {
|
||||
this.conn.write(encodedPacket, opts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when parser fully decodes a packet.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private ondecoded(packet: Packet): void {
|
||||
if (PacketType.CONNECT === packet.type) {
|
||||
if (this.conn.protocol === 3) {
|
||||
const parsed = url.parse(packet.nsp, true)
|
||||
this.connect(parsed.pathname!, parsed.query)
|
||||
} else {
|
||||
this.connect(packet.nsp, packet.data)
|
||||
}
|
||||
} else {
|
||||
const socket = this.nsps.get(packet.nsp)
|
||||
if (socket) {
|
||||
process.nextTick(function () {
|
||||
socket._onpacket(packet)
|
||||
})
|
||||
} else {
|
||||
console.debug(`client ${this.id} no socket for namespace ${packet.nsp}.`)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Called with incoming transport data.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private ondata(data): void {
|
||||
// try/catch is needed for protocol violations (GH-1880)
|
||||
try {
|
||||
this.decoder.add(data);
|
||||
} catch (e) {
|
||||
debug("invalid packet format");
|
||||
this.onerror(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error.
|
||||
*
|
||||
* @param {Object} err object
|
||||
* @private
|
||||
*/
|
||||
private onerror(err): void {
|
||||
for (const socket of this.sockets.values()) {
|
||||
socket._onerror(err)
|
||||
}
|
||||
this.conn.close()
|
||||
/**
|
||||
* Called when parser fully decodes a packet.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private ondecoded(packet: Packet): void {
|
||||
let namespace: string;
|
||||
let authPayload: Record<string, unknown>;
|
||||
if (this.conn.protocol === 3) {
|
||||
const parsed = url.parse(packet.nsp, true);
|
||||
namespace = parsed.pathname!;
|
||||
authPayload = parsed.query;
|
||||
} else {
|
||||
namespace = packet.nsp;
|
||||
authPayload = packet.data;
|
||||
}
|
||||
const socket = this.nsps.get(namespace);
|
||||
|
||||
/**
|
||||
* Called upon transport close.
|
||||
*
|
||||
* @param reason
|
||||
* @private
|
||||
*/
|
||||
private onclose(reason: string): void {
|
||||
console.debug(`client ${this.id} close with reason ${reason}`)
|
||||
|
||||
// ignore a potential subsequent `close` event
|
||||
this.destroy()
|
||||
|
||||
// `nsps` and `sockets` are cleaned up seamlessly
|
||||
for (const socket of this.sockets.values()) {
|
||||
socket._onclose(reason)
|
||||
}
|
||||
this.sockets.clear()
|
||||
|
||||
this.decoder.destroy() // clean up decoder
|
||||
if (!socket && packet.type === PacketType.CONNECT) {
|
||||
this.connect(namespace, authPayload);
|
||||
} else if (
|
||||
socket &&
|
||||
packet.type !== PacketType.CONNECT &&
|
||||
packet.type !== PacketType.CONNECT_ERROR
|
||||
) {
|
||||
process.nextTick(function () {
|
||||
socket._onpacket(packet);
|
||||
});
|
||||
} else {
|
||||
debug("invalid state (packet type: %s)", packet.type);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up event listeners.
|
||||
* @private
|
||||
*/
|
||||
private destroy(): void {
|
||||
this.conn.removeListener("data", this.ondata)
|
||||
this.conn.removeListener("error", this.onerror)
|
||||
this.conn.removeListener("close", this.onclose)
|
||||
// @ts-ignore
|
||||
this.decoder.removeListener("decoded", this.ondecoded)
|
||||
|
||||
if (this.connectTimeout) {
|
||||
clearTimeout(this.connectTimeout)
|
||||
this.connectTimeout = undefined
|
||||
}
|
||||
/**
|
||||
* Handles an error.
|
||||
*
|
||||
* @param {Object} err object
|
||||
* @private
|
||||
*/
|
||||
private onerror(err): void {
|
||||
for (const socket of this.sockets.values()) {
|
||||
socket._onerror(err);
|
||||
}
|
||||
this.conn.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon transport close.
|
||||
*
|
||||
* @param reason
|
||||
* @param description
|
||||
* @private
|
||||
*/
|
||||
private onclose(
|
||||
reason: CloseReason | "forced server close",
|
||||
description?: any
|
||||
): void {
|
||||
debug("client close with reason %s", reason);
|
||||
|
||||
// ignore a potential subsequent `close` event
|
||||
this.destroy();
|
||||
|
||||
// `nsps` and `sockets` are cleaned up seamlessly
|
||||
for (const socket of this.sockets.values()) {
|
||||
socket._onclose(reason, description);
|
||||
}
|
||||
this.sockets.clear();
|
||||
|
||||
this.decoder.destroy(); // clean up decoder
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up event listeners.
|
||||
* @private
|
||||
*/
|
||||
private destroy(): void {
|
||||
this.conn.removeListener("data", this.ondata);
|
||||
this.conn.removeListener("error", this.onerror);
|
||||
this.conn.removeListener("close", this.onclose);
|
||||
// @ts-ignore
|
||||
this.decoder.removeListener("decoded", this.ondecoded);
|
||||
|
||||
if (this.connectTimeout) {
|
||||
clearTimeout(this.connectTimeout);
|
||||
this.connectTimeout = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user