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
|
.vscode
|
||||||
.theia
|
.theia
|
||||||
node_modules
|
node_modules
|
||||||
|
|||||||
26
lerna.json
26
lerna.json
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"command": {
|
"command": {
|
||||||
"run": {
|
"run": {
|
||||||
"stream": true
|
"stream": true
|
||||||
}
|
|
||||||
},
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -12,14 +12,16 @@
|
|||||||
"build": "lerna run build",
|
"build": "lerna run build",
|
||||||
"ug": "yarn upgrade-interactive --latest",
|
"ug": "yarn upgrade-interactive --latest",
|
||||||
"np": "./script/push.sh",
|
"np": "./script/push.sh",
|
||||||
"lsp": "npm login --registry=https://registry.npmjs.org --scope=@ccms",
|
"lsp": "npm login -scope=@ccms",
|
||||||
"lp": "lerna publish --registry https://registry.npmjs.org",
|
"lp": "lerna publish --verify-access --force-publish",
|
||||||
"lpb": "lerna publish --registry https://registry.npmjs.org --canary --preid beta --pre-dist-tag beta"
|
"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": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"lerna": "^4.0.0"
|
"lerna": "^6.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/amqp",
|
"name": "@ccms/amqp",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript amqp package",
|
"description": "MiaoScript amqp package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -19,17 +19,17 @@
|
|||||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ccms/api": "^0.24.0",
|
"@ccms/api": "^0.27.6",
|
||||||
"@ccms/common": "^0.24.0",
|
"@ccms/common": "^0.27.6",
|
||||||
"@ccms/container": "^0.24.0"
|
"@ccms/container": "^0.27.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ccms/nashorn": "^0.24.0",
|
"@ccms/nashorn": "^0.27.6",
|
||||||
"@javatypes/amqp-client": "^0.0.3",
|
"@javatypes/amqp-client": "^0.0.3",
|
||||||
"@javatypes/spring-amqp": "^0.0.3",
|
"@javatypes/spring-amqp": "^0.0.3",
|
||||||
"@javatypes/spring-rabbit": "^0.0.3",
|
"@javatypes/spring-rabbit": "^0.0.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/api",
|
"name": "@ccms/api",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript api package",
|
"description": "MiaoScript api package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -19,16 +19,16 @@
|
|||||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ccms/common": "^0.24.0",
|
"@ccms/common": "^0.27.6",
|
||||||
"@ccms/container": "^0.24.0",
|
"@ccms/container": "^0.27.6",
|
||||||
"@ccms/polyfill": "^0.24.0",
|
"@ccms/polyfill": "^0.27.6",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"source-map-builder": "^0.0.7"
|
"source-map-builder": "^0.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/base64-js": "^1.3.0",
|
"@types/base64-js": "^1.3.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,13 +44,13 @@ export namespace jsconsole {
|
|||||||
let file = Paths.get(Paths.get(fileName, '..', sourceMappingURL).toFile().getCanonicalPath()).toFile()
|
let file = Paths.get(Paths.get(fileName, '..', sourceMappingURL).toFile().getCanonicalPath()).toFile()
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
sourceContent = base.read(file)
|
sourceContent = base.read(file)
|
||||||
|
sourceFileMaps[fileName] = file.getCanonicalPath()
|
||||||
} else if (global.debug) {
|
} else if (global.debug) {
|
||||||
console.debug('readSourceMap can\'t found', fileName, 'source map file', sourceMappingURL)
|
console.debug('readSourceMap can\'t found', fileName, 'source map file', sourceMappingURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sourceContent) {
|
if (sourceContent) {
|
||||||
sourceMaps[fileName] = new SourceMapBuilder(JSON.parse(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)
|
let { fileName, lineNumber } = readSourceMap(trace.fileName, trace.lineNumber)
|
||||||
if (fileName.startsWith(root)) { fileName = fileName.split(root)[1] }
|
if (fileName.startsWith(root)) { fileName = fileName.split(root)[1] }
|
||||||
if (color) {
|
if (color) {
|
||||||
cache.push(` §e->§c ${fileName}:${lineNumber} => §4${trace.methodName}`)
|
cache.push(` §e->§c ${fileName}:${lineNumber}(${trace.lineNumber}) => §4${trace.methodName}`)
|
||||||
} else {
|
} else {
|
||||||
cache.push(` -> ${fileName}:${lineNumber} => ${trace.methodName}`)
|
cache.push(` -> ${fileName}:${lineNumber}(${trace.lineNumber}) => ${trace.methodName}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let className = trace.className
|
let className = trace.className
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ export namespace database {
|
|||||||
* 链接属性
|
* 链接属性
|
||||||
*/
|
*/
|
||||||
properties?: { [key: string]: any }
|
properties?: { [key: string]: any }
|
||||||
|
/**
|
||||||
|
* 调试模式
|
||||||
|
*/
|
||||||
|
debug?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export namespace event {
|
|||||||
|
|
||||||
private mapEvent = [];
|
private mapEvent = [];
|
||||||
private listenerMap = [];
|
private listenerMap = [];
|
||||||
|
private cacheSlowEventKey = {};
|
||||||
|
|
||||||
protected baseEventDir = '';
|
protected baseEventDir = '';
|
||||||
|
|
||||||
@@ -92,17 +93,23 @@ export namespace event {
|
|||||||
return eventCls
|
return eventCls
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(name, exec, eventCls) {
|
/**
|
||||||
|
* 创建命令执行器
|
||||||
|
* @param name 插件名称
|
||||||
|
* @param exec 执行方法
|
||||||
|
* @param eventCls 事件类
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
createExecute(name, exec, eventCls) {
|
||||||
return (...args: any[]) => {
|
return (...args: any[]) => {
|
||||||
|
let event = args[args.length - 1]
|
||||||
try {
|
try {
|
||||||
let event = args[args.length - 1]
|
if (!eventCls.isAssignableFrom(event.getClass())) { return }
|
||||||
if (eventCls.isAssignableFrom(event.getClass())) {
|
let time = Date.now(); exec(event); let cost = Date.now() - time
|
||||||
let time = Date.now()
|
if (cost > global.ScriptSlowExecuteTime && !event.async) {
|
||||||
exec(event)
|
let eventKey = `${name}-${this.class2Name(eventCls)}`
|
||||||
let cost = Date.now() - time
|
if (!this.cacheSlowEventKey[eventKey]) { return this.cacheSlowEventKey[eventKey] = cost }
|
||||||
if (cost > global.ScriptSlowExecuteTime) {
|
console.i18n("ms.api.event.execute.slow", { name, event: this.class2Name(eventCls), cost })
|
||||||
console.i18n("ms.api.event.execute.slow", { name, event: this.class2Name(eventCls), cost })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (ex: any) {
|
} catch (ex: any) {
|
||||||
console.i18n("ms.api.event.execute.error", { name, event: this.class2Name(eventCls), ex })
|
console.i18n("ms.api.event.execute.error", { name, event: this.class2Name(eventCls), ex })
|
||||||
@@ -113,11 +120,11 @@ export namespace event {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加事件监听
|
* 添加事件监听
|
||||||
* @param plugin {any}
|
* @param plugin {any} 插件
|
||||||
* @param event {string}
|
* @param event {string} 事件名称
|
||||||
* @param exec {function}
|
* @param exec {function} 事件执行器
|
||||||
* @param priority {string} [LOWEST,LOW,NORMAL,HIGH,HIGHEST,MONITOR]
|
* @param priority {string} [LOWEST,LOW,NORMAL,HIGH,HIGHEST,MONITOR] 优先级
|
||||||
* @param ignoreCancel
|
* @param ignoreCancel 是否忽略已取消事件
|
||||||
*/
|
*/
|
||||||
listen(plugin: any, event: string, exec: (event: any) => void, priority: EventPriority = EventPriority.NORMAL, ignoreCancel = false) {
|
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"))
|
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
|
// noinspection JSUnusedGlobalSymbols
|
||||||
var listener = this.register(
|
var listener = this.register(
|
||||||
eventCls,
|
eventCls,
|
||||||
this.execute(name, exec, eventCls),
|
this.createExecute(name, exec, eventCls),
|
||||||
priority,
|
priority,
|
||||||
ignoreCancel
|
ignoreCancel
|
||||||
)
|
)
|
||||||
@@ -171,7 +178,7 @@ export namespace event {
|
|||||||
disable(plugin: any) {
|
disable(plugin: any) {
|
||||||
var eventCache = this.listenerMap[plugin.description.name]
|
var eventCache = this.listenerMap[plugin.description.name]
|
||||||
if (eventCache) {
|
if (eventCache) {
|
||||||
eventCache.forEach(off => off())
|
eventCache.forEach((off: () => any) => off())
|
||||||
delete this.listenerMap[plugin.description.name]
|
delete this.listenerMap[plugin.description.name]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export namespace item {
|
|||||||
export abstract class Item {
|
export abstract class Item {
|
||||||
abstract builder(): ItemBuilder
|
abstract builder(): ItemBuilder
|
||||||
abstract toJson(item: any): string
|
abstract toJson(item: any): string
|
||||||
abstract fromJSON(json: string): any
|
abstract fromJson(json: string): any
|
||||||
}
|
}
|
||||||
export interface ItemBuilder {
|
export interface ItemBuilder {
|
||||||
from(item: any): ItemBuilder
|
from(item: any): ItemBuilder
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ const UUID = Java.type('java.util.UUID')
|
|||||||
const Math = Java.type('java.lang.Math')
|
const Math = Java.type('java.lang.Math')
|
||||||
|
|
||||||
export namespace particle {
|
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 extra: number = 0;
|
||||||
private data: Object = null;
|
private data: Object = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only Show To Player
|
||||||
|
*/
|
||||||
|
private player: any
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.uuid = UUID.randomUUID().toString()
|
this.uuid = UUID.randomUUID().toString()
|
||||||
}
|
}
|
||||||
@@ -105,6 +115,15 @@ export namespace particle {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPlayer() {
|
||||||
|
return this.player
|
||||||
|
}
|
||||||
|
|
||||||
|
setPlayer(player) {
|
||||||
|
this.player = player
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过给定一个坐标就可以使用已经指定的参数来播放粒子
|
* 通过给定一个坐标就可以使用已经指定的参数来播放粒子
|
||||||
*
|
*
|
||||||
@@ -112,9 +131,14 @@ export namespace particle {
|
|||||||
*/
|
*/
|
||||||
spawn(location: any) {
|
spawn(location: any) {
|
||||||
if (!this.spawner) throw new Error(`particle ${this.uuid} not set spawner can't spawn!`)
|
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() {
|
show() {
|
||||||
for (let i = 0; i < this.length; i += this.step) {
|
for (let i = 0; i < this.length; i += this.step) {
|
||||||
let vectorTemp = this.vector.clone().multiply(i)
|
this.spawn(this.start.clone().add(this.vector.clone().multiply(i)))
|
||||||
this.spawn(this.start.clone().add(vectorTemp))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,15 +257,6 @@ export namespace particle {
|
|||||||
this.length = this.vector.length()
|
this.length = this.vector.length()
|
||||||
this.vector.normalize()
|
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()
|
@injectable()
|
||||||
export abstract class ParticleManager {
|
export abstract class ParticleManager {
|
||||||
public static globalSpawner: ParticleSpawner = undefined
|
|
||||||
@Autowired()
|
@Autowired()
|
||||||
private taskManager: task.TaskManager
|
private taskManager: task.TaskManager
|
||||||
|
@Autowired()
|
||||||
|
private particleSpawner: particle.ParticleSpawner
|
||||||
|
|
||||||
protected taskId: java.util.concurrent.atomic.AtomicInteger
|
protected taskId: java.util.concurrent.atomic.AtomicInteger
|
||||||
protected cacheTasks = new Map<string, ParticleTask>()
|
protected cacheTasks = new Map<string, ParticleTask>()
|
||||||
@@ -354,6 +369,10 @@ export namespace particle {
|
|||||||
return this.taskManager
|
return this.taskManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getParticleSpawner() {
|
||||||
|
return this.particleSpawner
|
||||||
|
}
|
||||||
|
|
||||||
public create(particle: Particle, plugin?: plugin.Plugin) {
|
public create(particle: Particle, plugin?: plugin.Plugin) {
|
||||||
let uuid = particle.getUUID()
|
let uuid = particle.getUUID()
|
||||||
if (this.cacheTasks.has(uuid)) {
|
if (this.cacheTasks.has(uuid)) {
|
||||||
@@ -389,14 +408,12 @@ export namespace particle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected create0(owner: plugin.Plugin, particle: Particle): ParticleTask {
|
protected create0(owner: plugin.Plugin, particle: Particle): ParticleTask {
|
||||||
particle.setSpawner(this.getGlobalSpawner())
|
particle.setSpawner(this.getParticleSpawner())
|
||||||
return new ParticleTask(owner, particle, this)
|
return new ParticleTask(owner, particle, this)
|
||||||
}
|
}
|
||||||
protected abstract getGlobalSpawner(): ParticleSpawner
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ParticleTask {
|
export class ParticleTask {
|
||||||
|
|
||||||
private particle: Particle
|
private particle: Particle
|
||||||
private isAsync: boolean = false
|
private isAsync: boolean = false
|
||||||
private interval: number = 0
|
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
|
origin: any
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export abstract class NativePluginManager {
|
export abstract class NativePluginManager {
|
||||||
list(): NativePlugin[] {
|
list(): NativePlugin[] {
|
||||||
@@ -53,6 +54,7 @@ export namespace server {
|
|||||||
throw new Error("Method not implemented.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MiaoScript Server
|
* MiaoScript Server
|
||||||
*/
|
*/
|
||||||
@@ -98,6 +100,7 @@ export namespace server {
|
|||||||
throw new Error("Method not implemented.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class ServerChecker {
|
export class ServerChecker {
|
||||||
@Autowired(ServerType)
|
@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()
|
@injectable()
|
||||||
export abstract class ReflectServer extends server.Server {
|
export abstract class ReflectServer extends server.Server {
|
||||||
@Autowired(ContainerInstance)
|
@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) {
|
if (this.rootLogger && this.rootLogger.class.name.indexOf('Logger') === -1) {
|
||||||
console.error('Error Logger Class: ' + this.rootLogger.class.name)
|
console.error('Error Logger Class: ' + this.rootLogger.class.name)
|
||||||
this.rootLogger = undefined
|
this.rootLogger = undefined
|
||||||
}
|
}
|
||||||
|
if (!this.rootLogger) { console.error("Can't found rootLogger!") }
|
||||||
// get root logger
|
// get root logger
|
||||||
for (let index = 0; index < 5 && this.rootLogger.parent; index++) {
|
for (let index = 0; index < 5 && this.rootLogger.parent; index++) {
|
||||||
this.rootLogger = this.rootLogger.parent
|
this.rootLogger = this.rootLogger.parent
|
||||||
}
|
}
|
||||||
if (!this.rootLogger) { console.error("Can't found rootLogger!") }
|
|
||||||
this.container.bind(constants.ServiceIdentifier.RootLogger).toConstantValue(this.rootLogger)
|
this.container.bind(constants.ServiceIdentifier.RootLogger).toConstantValue(this.rootLogger)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/bukkit",
|
"name": "@ccms/bukkit",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript bukkit package",
|
"description": "MiaoScript bukkit package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -21,12 +21,12 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@javatypes/spigot-api": "^0.0.3",
|
"@javatypes/spigot-api": "^0.0.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ccms/api": "^0.24.0",
|
"@ccms/api": "^0.27.6",
|
||||||
"@ccms/common": "^0.24.0",
|
"@ccms/common": "^0.27.6",
|
||||||
"@ccms/container": "^0.24.0"
|
"@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')
|
return base.getClass('net.minecraft.network.protocol.Packet')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class BukkitChatInvoke_1_18_2 extends BukkitChatInvoke_1_17_1 {
|
||||||
class BukkitChatInvoke_1_19 extends BukkitChatInvoke_1_17_1 {
|
|
||||||
getSendPacketMethodName(playerConnectionClass: any) {
|
getSendPacketMethodName(playerConnectionClass: any) {
|
||||||
return playerConnectionClass.getMethod('a', this.getPacketClass()).getName()
|
return playerConnectionClass.getMethod('a', this.getPacketClass()).getName()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
class BukkitChatInvoke_1_19 extends BukkitChatInvoke_1_18_2 {
|
||||||
getPacketPlayOutChatClass() {
|
getPacketPlayOutChatClass() {
|
||||||
return base.getClass('net.minecraft.network.protocol.game.ClientboundSystemChatPacket')
|
return base.getClass('net.minecraft.network.protocol.game.ClientboundSystemChatPacket')
|
||||||
}
|
}
|
||||||
getPacketPlayOutChat(sender: any, json: any, type: number) {
|
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]
|
let nmsSubVersion = nmsVersion.split("_")[1]
|
||||||
if (nmsSubVersion >= 19) {
|
if (nmsSubVersion >= 19) {
|
||||||
bukkitChatInvoke = new BukkitChatInvoke_1_19(nmsVersion)
|
bukkitChatInvoke = new BukkitChatInvoke_1_19(nmsVersion)
|
||||||
|
} else if (nmsSubVersion >= 18) {
|
||||||
|
bukkitChatInvoke = new BukkitChatInvoke_1_18_2(nmsVersion)
|
||||||
} else if (nmsSubVersion >= 17) {
|
} else if (nmsSubVersion >= 17) {
|
||||||
bukkitChatInvoke = new BukkitChatInvoke_1_17_1(nmsVersion)
|
bukkitChatInvoke = new BukkitChatInvoke_1_17_1(nmsVersion)
|
||||||
} else if (nmsSubVersion >= 16) {
|
} else if (nmsSubVersion >= 16) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export class BukkitItem extends item.Item {
|
|||||||
private NBTTagCompound: any
|
private NBTTagCompound: any
|
||||||
private nmsSaveNBTMethodName: any
|
private nmsSaveNBTMethodName: any
|
||||||
private MojangsonParser: any
|
private MojangsonParser: any
|
||||||
|
private nmsItemStack: any
|
||||||
private mpParseMethodName: any
|
private mpParseMethodName: any
|
||||||
private nmsVersion: any
|
private nmsVersion: any
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -25,8 +26,8 @@ export class BukkitItem extends item.Item {
|
|||||||
let nbt = new this.NBTTagCompound()
|
let nbt = new this.NBTTagCompound()
|
||||||
return this.CraftItemStack.asNMSCopy(item)[this.nmsSaveNBTMethodName](nbt).toString()
|
return this.CraftItemStack.asNMSCopy(item)[this.nmsSaveNBTMethodName](nbt).toString()
|
||||||
}
|
}
|
||||||
fromJSON(json: string) {
|
fromJson(json: string) {
|
||||||
return this.CraftItemStack.asBukkitCopy(this.MojangsonParser[this.mpParseMethodName](json))
|
return this.CraftItemStack.asBukkitCopy(new this.nmsItemStack(this.MojangsonParser[this.mpParseMethodName](json)))
|
||||||
}
|
}
|
||||||
private obcCls(name: string) {
|
private obcCls(name: string) {
|
||||||
return base.getClass(['org.bukkit.craftbukkit', this.nmsVersion, name].join('.'))
|
return base.getClass(['org.bukkit.craftbukkit', this.nmsVersion, name].join('.'))
|
||||||
@@ -44,6 +45,7 @@ export class BukkitItem extends item.Item {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let asNMSCopyMethod = CraftItemStackClass.getMethod('asNMSCopy', ItemStack.class)
|
let asNMSCopyMethod = CraftItemStackClass.getMethod('asNMSCopy', ItemStack.class)
|
||||||
let nmsItemStackClass = asNMSCopyMethod.getReturnType()
|
let nmsItemStackClass = asNMSCopyMethod.getReturnType()
|
||||||
|
this.nmsItemStack = Java.type(nmsItemStackClass.getName())
|
||||||
let nmsNBTTagCompoundClass = undefined
|
let nmsNBTTagCompoundClass = undefined
|
||||||
for (let method of Java.from(nmsItemStackClass.getMethods())) {
|
for (let method of Java.from(nmsItemStackClass.getMethods())) {
|
||||||
let rt = method.getReturnType()
|
let rt = method.getReturnType()
|
||||||
@@ -61,7 +63,7 @@ export class BukkitItem extends item.Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.MojangsonParser = this.nmsCls('MojangsonParser')
|
this.MojangsonParser = this.nmsCls('MojangsonParser').static
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.MojangsonParser = Java.type('net.minecraft.nbt.MojangsonParser')
|
this.MojangsonParser = Java.type('net.minecraft.nbt.MojangsonParser')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,11 @@ import { particle } from '@ccms/api'
|
|||||||
|
|
||||||
@provideSingleton(particle.ParticleManager)
|
@provideSingleton(particle.ParticleManager)
|
||||||
export class BukkitParticleManager extends 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 {
|
export class BukkitParticleSpawner extends particle.ParticleSpawner {
|
||||||
spawnParticle(location: any, particle: any, count: number = 1) {
|
spawn(location: org.bukkit.Location, particle: particle.Particle) {
|
||||||
location.getWorld().spawnParticle(particle, location, count)
|
|
||||||
}
|
|
||||||
spawn(location: any, particle: particle.Particle) {
|
|
||||||
location.getWorld().spawnParticle(
|
location.getWorld().spawnParticle(
|
||||||
particle.getParticle(),
|
particle.getParticle(),
|
||||||
location,
|
location,
|
||||||
@@ -28,4 +19,15 @@ export class BukkitParticleSpawner extends particle.ParticleSpawner {
|
|||||||
particle.getData()
|
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",
|
"name": "@ccms/bungee",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript bungee package",
|
"description": "MiaoScript bungee package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -21,12 +21,12 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@javatypes/bungee-api": "^0.0.3",
|
"@javatypes/bungee-api": "^0.0.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ccms/api": "^0.24.0",
|
"@ccms/api": "^0.27.6",
|
||||||
"@ccms/common": "^0.24.0",
|
"@ccms/common": "^0.27.6",
|
||||||
"@ccms/container": "^0.24.0"
|
"@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",
|
"name": "@ccms/common",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript common package",
|
"description": "MiaoScript common package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -19,11 +19,11 @@
|
|||||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ccms/nashorn": "^0.24.0",
|
"@ccms/nashorn": "^0.27.6",
|
||||||
"@javatypes/jdk": "^0.0.3",
|
"@javatypes/jdk": "^0.0.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"gitHead": "562e2d00175c9d3a99c8b672aa07e6d92706a027"
|
"gitHead": "562e2d00175c9d3a99c8b672aa07e6d92706a027"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,20 @@ interface RequestConfig {
|
|||||||
method?: Method
|
method?: Method
|
||||||
headers?: { [key: string]: string }
|
headers?: { [key: string]: string }
|
||||||
params?: { [key: string]: string }
|
params?: { [key: string]: string }
|
||||||
data?: any
|
data?: any,
|
||||||
|
connectTimeout?: number,
|
||||||
|
readTimeout?: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
function request(config: RequestConfig) {
|
function request(config: RequestConfig) {
|
||||||
// @ts-ignore XMLHttpRequest class only exist nashorn polyfill
|
// @ts-ignore XMLHttpRequest class only exist nashorn polyfill
|
||||||
let xhr = new XMLHttpRequest()
|
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)
|
xhr.open(config.method, config.url, false)
|
||||||
for (const header in config.headers) {
|
for (const header in config.headers) {
|
||||||
xhr.setRequestHeader(header, config.headers[header])
|
xhr.setRequestHeader(header, config.headers[header])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/compile",
|
"name": "@ccms/compile",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript compile package",
|
"description": "MiaoScript compile package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/container",
|
"name": "@ccms/container",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript container package",
|
"description": "MiaoScript container package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -19,10 +19,10 @@
|
|||||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ccms/nashorn": "^0.24.0",
|
"@ccms/nashorn": "^0.27.6",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"inversify": "^6.0.1",
|
"inversify": "^6.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/core",
|
"name": "@ccms/core",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript core package",
|
"description": "MiaoScript core package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -20,12 +20,12 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ccms/api": "^0.24.0",
|
"@ccms/api": "^0.27.6",
|
||||||
"@ccms/container": "^0.24.0"
|
"@ccms/container": "^0.27.6"
|
||||||
},
|
},
|
||||||
"gitHead": "781524f83e52cad26d7c480513e3c525df867121"
|
"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'
|
import { VersionUtils } from '@ccms/common/dist/version'
|
||||||
|
|
||||||
const UUID = Java.type('java.util.UUID')
|
const UUID = Java.type('java.util.UUID')
|
||||||
|
const MiaoScriptAPI = Java.type('pw.yumc.MiaoScript.api.MiaoScriptAPI')
|
||||||
|
|
||||||
@provideSingleton(MiaoScriptCore)
|
@provideSingleton(MiaoScriptCore)
|
||||||
class MiaoScriptCore {
|
class MiaoScriptCore {
|
||||||
@@ -24,6 +25,10 @@ class MiaoScriptCore {
|
|||||||
enable() {
|
enable() {
|
||||||
process.emit('core.before.enable')
|
process.emit('core.before.enable')
|
||||||
this.loadServerConsole()
|
this.loadServerConsole()
|
||||||
|
try {
|
||||||
|
MiaoScriptAPI.setPluginManager(this.pluginManager)
|
||||||
|
} catch (error) {
|
||||||
|
}
|
||||||
this.loadPlugins()
|
this.loadPlugins()
|
||||||
process.emit('core.after.enable')
|
process.emit('core.after.enable')
|
||||||
console.i18n("ms.core.engine.completed", {
|
console.i18n("ms.core.engine.completed", {
|
||||||
@@ -105,7 +110,7 @@ function loadCoreScript(name) {
|
|||||||
try {
|
try {
|
||||||
let scriptname = name + (global.debug ? '-debug' : '')
|
let scriptname = name + (global.debug ? '-debug' : '')
|
||||||
engineLoad({
|
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`
|
name: `core/${scriptname}.js`
|
||||||
})
|
})
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -141,6 +146,7 @@ function createCore() {
|
|||||||
container.bind(server.ServerType).toConstantValue(type)
|
container.bind(server.ServerType).toConstantValue(type)
|
||||||
container.bind(server.ServerChecker).toSelf().inSingletonScope()
|
container.bind(server.ServerChecker).toSelf().inSingletonScope()
|
||||||
container.bind(server.NativePluginManager).toSelf().inSingletonScope()
|
container.bind(server.NativePluginManager).toSelf().inSingletonScope()
|
||||||
|
container.bind(server.NativePluginChecker).toSelf().inSingletonScope()
|
||||||
process.emit('core.after.initialize.detect')
|
process.emit('core.after.initialize.detect')
|
||||||
|
|
||||||
process.emit('core.before.package.initialize')
|
process.emit('core.before.package.initialize')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/database",
|
"name": "@ccms/database",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript database package",
|
"description": "MiaoScript database package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -21,11 +21,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@javatypes/spring-jdbc": "^0.0.3",
|
"@javatypes/spring-jdbc": "^0.0.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ccms/api": "^0.24.0",
|
"@ccms/api": "^0.27.6",
|
||||||
"@ccms/container": "^0.24.0"
|
"@ccms/container": "^0.27.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/i18n",
|
"name": "@ccms/i18n",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript i18n package",
|
"description": "MiaoScript i18n package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -19,11 +19,11 @@
|
|||||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ccms/nashorn": "^0.24.0",
|
"@ccms/nashorn": "^0.27.6",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"js-yaml": "^4.1.0"
|
"js-yaml": "^4.1.0"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/keyvalue",
|
"name": "@ccms/keyvalue",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript keyvalue package",
|
"description": "MiaoScript keyvalue package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -19,18 +19,18 @@
|
|||||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ccms/api": "^0.24.0",
|
"@ccms/api": "^0.27.6",
|
||||||
"@ccms/common": "^0.24.0",
|
"@ccms/common": "^0.27.6",
|
||||||
"@ccms/container": "^0.24.0"
|
"@ccms/container": "^0.27.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ccms/nashorn": "^0.24.0",
|
"@ccms/nashorn": "^0.27.6",
|
||||||
"@javatypes/amqp-client": "^0.0.3",
|
"@javatypes/amqp-client": "^0.0.3",
|
||||||
"@javatypes/spring-amqp": "^0.0.3",
|
"@javatypes/spring-amqp": "^0.0.3",
|
||||||
"@javatypes/spring-rabbit": "^0.0.3",
|
"@javatypes/spring-rabbit": "^0.0.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"gitHead": "2589633069d24f646ac09261b1b2304c21d4ea75"
|
"gitHead": "2589633069d24f646ac09261b1b2304c21d4ea75"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/molang",
|
"name": "@ccms/molang",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "A fast parser for Minecraft's MoLang",
|
"description": "A fast parser for Minecraft's MoLang",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@@ -21,9 +21,9 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/solvedDev/MoLang#readme",
|
"homepage": "https://github.com/solvedDev/MoLang#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^17.0.25",
|
"@types/node": "^18.13.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.5.0",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/nashorn",
|
"name": "@ccms/nashorn",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript nashorn package",
|
"description": "MiaoScript nashorn package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,8 +64,18 @@ declare global {
|
|||||||
* 引擎渠道
|
* 引擎渠道
|
||||||
*/
|
*/
|
||||||
ScriptEngineChannel: string
|
ScriptEngineChannel: string
|
||||||
|
/**
|
||||||
|
* 慢执行检测时间
|
||||||
|
*/
|
||||||
ScriptSlowExecuteTime: number
|
ScriptSlowExecuteTime: number
|
||||||
ScriptEngineStartTime: number
|
ScriptEngineStartTime: number
|
||||||
|
/**
|
||||||
|
* 设置全局对象
|
||||||
|
* @param key 对象名称
|
||||||
|
* @param value 对象值
|
||||||
|
* @param config 对象属性
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
setGlobal: (key: string, value: any, config?: PropertyDescriptor & ThisType<any>) => void
|
setGlobal: (key: string, value: any, config?: PropertyDescriptor & ThisType<any>) => void
|
||||||
noop: () => void
|
noop: () => void
|
||||||
console: Console
|
console: Console
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/nodejs",
|
"name": "@ccms/nodejs",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript nodejs package",
|
"description": "MiaoScript nodejs package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -19,11 +19,11 @@
|
|||||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ccms/nashorn": "^0.24.0",
|
"@ccms/nashorn": "^0.27.6",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.5.0",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"gitHead": "781524f83e52cad26d7c480513e3c525df867121"
|
"gitHead": "781524f83e52cad26d7c480513e3c525df867121"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/nukkit",
|
"name": "@ccms/nukkit",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript nukkit package",
|
"description": "MiaoScript nukkit package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -21,12 +21,12 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@javatypes/nukkit-api": "^0.0.3",
|
"@javatypes/nukkit-api": "^0.0.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ccms/api": "^0.24.0",
|
"@ccms/api": "^0.27.6",
|
||||||
"@ccms/common": "^0.24.0",
|
"@ccms/common": "^0.27.6",
|
||||||
"@ccms/container": "^0.24.0"
|
"@ccms/container": "^0.27.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/plugin",
|
"name": "@ccms/plugin",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript plugin package",
|
"description": "MiaoScript plugin package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -22,15 +22,15 @@
|
|||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ccms/api": "^0.24.0",
|
"@ccms/api": "^0.27.6",
|
||||||
"@ccms/common": "^0.24.0",
|
"@ccms/common": "^0.27.6",
|
||||||
"@ccms/container": "^0.24.0",
|
"@ccms/container": "^0.27.6",
|
||||||
"@ccms/i18n": "^0.24.0",
|
"@ccms/i18n": "^0.27.6",
|
||||||
"@ccms/verify": "^0.21.1",
|
"@ccms/verify": "^0.25.1",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"js-yaml": "^4.1.0"
|
"js-yaml": "^4.1.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export class PluginCommandManager {
|
|||||||
permission = `${pluginInstance.description.name.toLocaleLowerCase()}.${command}.${subcommand || 'main'}`
|
permission = `${pluginInstance.description.name.toLocaleLowerCase()}.${command}.${subcommand || 'main'}`
|
||||||
}
|
}
|
||||||
if (!sender.hasPermission(permission)) {
|
if (!sender.hasPermission(permission)) {
|
||||||
return pluginInstance.logger.sender(sender, `§c你需要 ${permission} 权限 才可执行此命令.`)
|
return pluginInstance.logger.sender(sender, `§c你需要 §4${permission} §c权限 才可执行此命令.`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
args.shift()
|
args.shift()
|
||||||
@@ -98,7 +98,7 @@ export class PluginCommandManager {
|
|||||||
return (args.length == 1 ? cmdSubCache : []).concat(originCompleter?.apply(pluginInstance, [sender, command, args]) || [])
|
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]
|
return [cmdExecutor, cmdCompleter]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import { interfaces } from './interfaces'
|
|||||||
import { getPluginConfigMetadata } from './utils'
|
import { getPluginConfigMetadata } from './utils'
|
||||||
|
|
||||||
import { PluginConfigLoader } from './config/interfaces'
|
import { PluginConfigLoader } from './config/interfaces'
|
||||||
import './config/loader/json-loader'
|
import './config/loader'
|
||||||
import './config/loader/yaml-loader'
|
|
||||||
|
|
||||||
@provideSingleton(PluginConfigManager)
|
@provideSingleton(PluginConfigManager)
|
||||||
export class PluginConfigManager {
|
export class PluginConfigManager {
|
||||||
@@ -68,30 +67,25 @@ export class PluginConfigManager {
|
|||||||
private loadConfig0(plugin: plugin.Plugin, metadata: interfaces.ConfigMetadata) {
|
private loadConfig0(plugin: plugin.Plugin, metadata: interfaces.ConfigMetadata) {
|
||||||
try {
|
try {
|
||||||
let defaultValue = metadata.default ?? plugin[metadata.variable]
|
let defaultValue = metadata.default ?? plugin[metadata.variable]
|
||||||
let configValue = defaultValue || {}
|
metadata.file = fs.concat(
|
||||||
if (defaultValue) {
|
fs.file(plugin.description.loadMetadata.file).parent,
|
||||||
metadata.file = fs.concat(
|
plugin.description.name,
|
||||||
fs.file(plugin.description.loadMetadata.file).parent,
|
metadata.filename
|
||||||
plugin.description.name,
|
)
|
||||||
metadata.filename
|
let configLoader = this.getConfigLoader(metadata.format)
|
||||||
)
|
if (!fs.exists(metadata.file) && defaultValue) {
|
||||||
let configLoader = this.getConfigLoader(metadata.format)
|
base.save(metadata.file, configLoader.dump(defaultValue))
|
||||||
if (!fs.exists(metadata.file)) {
|
console.i18n("ms.plugin.manager.config.save.default", {
|
||||||
base.save(metadata.file, configLoader.dump(defaultValue))
|
plugin: plugin.description.name,
|
||||||
console.i18n("ms.plugin.manager.config.save.default", {
|
name: metadata.name,
|
||||||
plugin: plugin.description.name,
|
format: metadata.format
|
||||||
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)}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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)
|
this.defienConfigProp(plugin, metadata, configValue)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.i18n("ms.plugin.manager.config.load.error", {
|
console.i18n("ms.plugin.manager.config.load.error", {
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import { getPluginListenerMetadata } from './utils'
|
|||||||
@provideSingleton(PluginEventManager)
|
@provideSingleton(PluginEventManager)
|
||||||
export class PluginEventManager {
|
export class PluginEventManager {
|
||||||
@Autowired()
|
@Autowired()
|
||||||
private EventManager: event.Event
|
private eventManager: event.Event
|
||||||
@Autowired()
|
@Autowired()
|
||||||
private ServerChecker: server.ServerChecker
|
private serverChecker: server.ServerChecker
|
||||||
|
@Autowired()
|
||||||
|
private nativePluginChecker: server.NativePluginChecker
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
process.on('plugin.before.enable', this.registryListener.bind(this))
|
process.on('plugin.before.enable', this.registryListener.bind(this))
|
||||||
@@ -15,26 +17,31 @@ export class PluginEventManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mapEventName() {
|
mapEventName() {
|
||||||
return this.EventManager.mapEventName().toFixed(0)
|
return this.eventManager.mapEventName().toFixed(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private registryListener(pluginInstance: plugin.Plugin) {
|
private registryListener(pluginInstance: plugin.Plugin) {
|
||||||
let events = getPluginListenerMetadata(pluginInstance)
|
let events = getPluginListenerMetadata(pluginInstance)
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
// ignore space listener
|
// ignore space listener
|
||||||
if (!this.ServerChecker.check(event.servers)) {
|
if (!this.serverChecker.check(event.servers)) {
|
||||||
console.debug(`[${pluginInstance.description.name}] ${event.target.constructor.name} incompatible event ${event.name} server(${event.servers}) ignore.`)
|
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
|
continue
|
||||||
}
|
}
|
||||||
// here must bind this to pluginInstance
|
// here must bind this to pluginInstance
|
||||||
let exec = event.target[event.executor]
|
let exec = event.target[event.executor]
|
||||||
let execBinded = exec.bind(pluginInstance)
|
let execBinded = exec.bind(pluginInstance)
|
||||||
execBinded.executor = event.executor
|
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) {
|
private unregistryListener(pluginInstance: plugin.Plugin) {
|
||||||
this.EventManager.disable(pluginInstance)
|
this.eventManager.disable(pluginInstance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ export namespace interfaces {
|
|||||||
* 是否忽略已取消的事件
|
* 是否忽略已取消的事件
|
||||||
*/
|
*/
|
||||||
ignoreCancel?: boolean
|
ignoreCancel?: boolean
|
||||||
|
/**
|
||||||
|
* 依赖插件 没有就不加载
|
||||||
|
*/
|
||||||
|
plugins?: string[]
|
||||||
}
|
}
|
||||||
export interface ConfigMetadata extends plugin.BaseMetadata {
|
export interface ConfigMetadata extends plugin.BaseMetadata {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ import i18n from '@ccms/i18n'
|
|||||||
import { plugin, server } from '@ccms/api'
|
import { plugin, server } from '@ccms/api'
|
||||||
import { provideSingleton, Container, ContainerInstance, Autowired } from '@ccms/container'
|
import { provideSingleton, Container, ContainerInstance, Autowired } from '@ccms/container'
|
||||||
|
|
||||||
import './config'
|
|
||||||
import { interfaces } from './interfaces'
|
import { interfaces } from './interfaces'
|
||||||
import { PluginTaskManager } from './task'
|
import { PluginTaskManager } from './task'
|
||||||
import { PluginEventManager } from './event'
|
import { PluginEventManager } from './event'
|
||||||
import { PluginCommandManager } from './command'
|
|
||||||
import { PluginConfigManager } from './config'
|
import { PluginConfigManager } from './config'
|
||||||
|
import { PluginCommandManager } from './command'
|
||||||
|
|
||||||
const Thread = Java.type('java.lang.Thread')
|
const Thread = Java.type('java.lang.Thread')
|
||||||
|
|
||||||
@@ -93,18 +92,17 @@ export class PluginManagerImpl implements plugin.PluginManager {
|
|||||||
for (const [, scanner] of this.sacnnerMap) {
|
for (const [, scanner] of this.sacnnerMap) {
|
||||||
try {
|
try {
|
||||||
console.i18n('ms.plugin.manager.scan', { scanner: scanner.type, folder })
|
console.i18n('ms.plugin.manager.scan', { scanner: scanner.type, folder })
|
||||||
let plugins = scanner.scan(folder)
|
let loadMetadatas = scanner.scan(folder)
|
||||||
console.i18n('ms.plugin.manager.scan.finish', { scanner: scanner.type, folder, size: plugins.length })
|
console.i18n('ms.plugin.manager.scan.finish', { scanner: scanner.type, folder, size: loadMetadatas.length })
|
||||||
plugins.forEach(loadMetadata => {
|
for (const loadMetadata of loadMetadatas) {
|
||||||
try {
|
try {
|
||||||
this.loadAndRequirePlugin(loadMetadata)
|
this.loadAndRequirePlugin(loadMetadata)
|
||||||
} catch (error: any) {
|
} 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)
|
console.ex(error)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`plugin scanner ${scanner.type} occurred error ${error}`)
|
|
||||||
console.ex(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) {
|
getPlugin(name: string) {
|
||||||
return this.instanceMap.get(name)
|
return this.instanceMap.get(name) || null
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlugins() {
|
getPlugins() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/polyfill",
|
"name": "@ccms/polyfill",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript polyfill package",
|
"description": "MiaoScript polyfill package",
|
||||||
"author": "MiaoWoo <admin@yumc.pw>",
|
"author": "MiaoWoo <admin@yumc.pw>",
|
||||||
"homepage": "https://github.com/circlecloud/ms.git",
|
"homepage": "https://github.com/circlecloud/ms.git",
|
||||||
@@ -14,14 +14,14 @@
|
|||||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ccms/i18n": "^0.24.0",
|
"@ccms/i18n": "^0.27.6",
|
||||||
"@ccms/nodejs": "^0.24.0",
|
"@ccms/nodejs": "^0.27.6",
|
||||||
"core-js": "^3.22.1"
|
"core-js": "^3.27.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ccms/nashorn": "^0.24.0",
|
"@ccms/nashorn": "^0.27.6",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"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())
|
process.on('exit', () => require.disable())
|
||||||
global.setGlobal('Proxy', require('./proxy').Proxy)
|
global.setGlobal('Proxy', require('./proxy').Proxy)
|
||||||
global.setGlobal('XMLHttpRequest', require('./xml-http-request').XMLHttpRequest)
|
global.setGlobal('XMLHttpRequest', require('./xml-http-request').XMLHttpRequest)
|
||||||
|
global.setGlobal('Buffer', require('./buffer').Buffer)
|
||||||
global.setGlobal('Blob', require('blob-polyfill').Blob)
|
global.setGlobal('Blob', require('blob-polyfill').Blob)
|
||||||
console.i18n("ms.polyfill.completed", { time: (new Date().getTime() - polyfillStartTime) / 1000 })
|
console.i18n("ms.polyfill.completed", { time: (new Date().getTime() - polyfillStartTime) / 1000 })
|
||||||
export default true
|
export default true
|
||||||
|
|||||||
@@ -68,13 +68,14 @@ type EventType =
|
|||||||
| 'timeout'
|
| 'timeout'
|
||||||
| 'loadend'
|
| 'loadend'
|
||||||
| 'loadstart'
|
| 'loadstart'
|
||||||
type HttpHeader = { [key: string]: string }
|
type RequestHttpHeader = { [key: string]: string }
|
||||||
|
type HttpHeader = { [key: string]: string[] }
|
||||||
|
|
||||||
const executor = Executors.newCachedThreadPool()
|
const executor = Executors.newCachedThreadPool()
|
||||||
|
|
||||||
export class XMLHttpRequest {
|
export class XMLHttpRequest {
|
||||||
private _timeout: number = 120000;
|
private _connectTimeout: number = 5000;
|
||||||
|
private _readTimeout: number = 120000;
|
||||||
private _responseType: ResponseType = 'text';
|
private _responseType: ResponseType = 'text';
|
||||||
private _withCredentials: boolean
|
private _withCredentials: boolean
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ export class XMLHttpRequest {
|
|||||||
private _url: string
|
private _url: string
|
||||||
private _async: boolean
|
private _async: boolean
|
||||||
private _mimeType: string
|
private _mimeType: string
|
||||||
private _requestHeaders: HttpHeader = {};
|
private _requestHeaders: RequestHttpHeader = {};
|
||||||
|
|
||||||
private _status: number = 0;
|
private _status: number = 0;
|
||||||
private _statusText: string = null;
|
private _statusText: string = null;
|
||||||
@@ -96,10 +97,22 @@ export class XMLHttpRequest {
|
|||||||
private _connection = null;
|
private _connection = null;
|
||||||
|
|
||||||
get timeout() {
|
get timeout() {
|
||||||
return this._timeout
|
return this._readTimeout
|
||||||
}
|
}
|
||||||
set timeout(timeout: number) {
|
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() {
|
get readyState() {
|
||||||
return this._readyState
|
return this._readyState
|
||||||
@@ -143,7 +156,7 @@ export class XMLHttpRequest {
|
|||||||
this._requestHeaders[key] = val
|
this._requestHeaders[key] = val
|
||||||
}
|
}
|
||||||
getResponseHeader(key: string): string {
|
getResponseHeader(key: string): string {
|
||||||
return this._responseHeaders[key]
|
return this._responseHeaders[key]?.[0]
|
||||||
}
|
}
|
||||||
getAllResponseHeaders(): any {
|
getAllResponseHeaders(): any {
|
||||||
return this._responseHeaders
|
return this._responseHeaders
|
||||||
@@ -169,8 +182,8 @@ export class XMLHttpRequest {
|
|||||||
this._connection.setRequestMethod(this._method)
|
this._connection.setRequestMethod(this._method)
|
||||||
this._connection.setDoOutput(true)
|
this._connection.setDoOutput(true)
|
||||||
this._connection.setDoInput(true)
|
this._connection.setDoInput(true)
|
||||||
this._connection.setConnectTimeout(this._timeout)
|
this._connection.setConnectTimeout(this._connectTimeout)
|
||||||
this._connection.setReadTimeout(this._timeout)
|
this._connection.setReadTimeout(this._readTimeout)
|
||||||
|
|
||||||
this.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
|
this.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
|
||||||
this.setReadyState(ReadyState.OPENED)
|
this.setReadyState(ReadyState.OPENED)
|
||||||
@@ -181,7 +194,7 @@ export class XMLHttpRequest {
|
|||||||
}
|
}
|
||||||
if (this._readyState !== ReadyState.OPENED) { throw new Error(`Error Status ${this._readyState}!`) }
|
if (this._readyState !== ReadyState.OPENED) { throw new Error(`Error Status ${this._readyState}!`) }
|
||||||
let future = executor.submit(new Callable({ call: () => this._send(body) }))
|
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
|
return future
|
||||||
}
|
}
|
||||||
get() {
|
get() {
|
||||||
@@ -199,22 +212,23 @@ export class XMLHttpRequest {
|
|||||||
}
|
}
|
||||||
abort() {
|
abort() {
|
||||||
this._connection.disconnect()
|
this._connection.disconnect()
|
||||||
this.onabort && this.onabort()
|
this.onabort?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
private _send(body?: string | object) {
|
private _send(body?: string | object) {
|
||||||
try {
|
try {
|
||||||
this._connection.connect()
|
this._connection.connect()
|
||||||
this.onloadstart && this.onloadstart()
|
this.onloadstart?.()
|
||||||
if (body) {
|
if (body) {
|
||||||
let bodyType = Object.prototype.toString.call(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()
|
var out = this._connection.getOutputStream()
|
||||||
out.write(new JavaString(body).getBytes(UTF_8))
|
out.write(new JavaString(body).getBytes(UTF_8))
|
||||||
out.flush()
|
out.flush()
|
||||||
out.close()
|
out.close()
|
||||||
}
|
}
|
||||||
this.setReadyState(ReadyState.LOADING)
|
this.setReadyState(ReadyState.LOADING)
|
||||||
|
this.onload?.()
|
||||||
this._status = this._connection.getResponseCode()
|
this._status = this._connection.getResponseCode()
|
||||||
this._statusText = this._connection.getResponseMessage()
|
this._statusText = this._connection.getResponseMessage()
|
||||||
if (this._status >= 0 && this._status < 300) {
|
if (this._status >= 0 && this._status < 300) {
|
||||||
@@ -224,8 +238,8 @@ export class XMLHttpRequest {
|
|||||||
} else {
|
} else {
|
||||||
this._responseText = this.readOutput(this._connection.getErrorStream())
|
this._responseText = this.readOutput(this._connection.getErrorStream())
|
||||||
}
|
}
|
||||||
this.setResponseHeaders(this._connection.getHeaderFields())
|
this.setResponseHeaders()
|
||||||
this.onloadend && this.onloadend()
|
this.onloadend?.()
|
||||||
} catch (ex: any) {
|
} catch (ex: any) {
|
||||||
if (ex instanceof SocketTimeoutException && this.ontimeout) {
|
if (ex instanceof SocketTimeoutException && this.ontimeout) {
|
||||||
return this.ontimeout(ex)
|
return this.ontimeout(ex)
|
||||||
@@ -239,15 +253,15 @@ export class XMLHttpRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setResponseHeaders(header: any) {
|
private setResponseHeaders() {
|
||||||
header.forEach((key: string | number, value: string | any[]) => {
|
this._connection.getHeaderFields().forEach((key: string | number, value: any[]) => {
|
||||||
this._responseHeaders[key + ''] = value[value.length - 1] + ''
|
this._responseHeaders[key + ''] = Java.from(value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private setReadyState(state: ReadyState) {
|
private setReadyState(state: ReadyState) {
|
||||||
this._readyState = state
|
this._readyState = state
|
||||||
this.onreadystatechange && this.onreadystatechange()
|
this.onreadystatechange?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
private readOutput(input: any) {
|
private readOutput(input: any) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/protocol",
|
"name": "@ccms/protocol",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript protocol package",
|
"description": "MiaoScript protocol package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"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",
|
"name": "@ccms/sponge",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript sponge package",
|
"description": "MiaoScript sponge package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -21,12 +21,12 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@javatypes/sponge-api": "^0.0.3",
|
"@javatypes/sponge-api": "^0.0.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ccms/api": "^0.24.0",
|
"@ccms/api": "^0.27.6",
|
||||||
"@ccms/common": "^0.24.0",
|
"@ccms/common": "^0.27.6",
|
||||||
"@ccms/container": "^0.24.0"
|
"@ccms/container": "^0.27.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,15 @@
|
|||||||
import { provideSingleton } from '@ccms/container'
|
import { provideSingleton } from '@ccms/container'
|
||||||
import { particle, plugin } from '@ccms/api'
|
import { particle } from '@ccms/api'
|
||||||
|
|
||||||
@provideSingleton(particle.ParticleManager)
|
@provideSingleton(particle.ParticleManager)
|
||||||
export class SpongeParticleManager extends 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 {
|
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) {
|
spawn(location: any, particle: particle.Particle) {
|
||||||
location.getWorld().spawnParticle(
|
throw new Error('Not Impl.')
|
||||||
particle.getParticle(),
|
}
|
||||||
location,
|
spawnToPlayer(player: any, location: any, particle: particle.Particle) {
|
||||||
particle.getCount(),
|
throw new Error('Not Impl.')
|
||||||
particle.getOffsetX(),
|
|
||||||
particle.getOffsetY(),
|
|
||||||
particle.getOffsetZ(),
|
|
||||||
particle.getExtra(),
|
|
||||||
particle.getData()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/spring",
|
"name": "@ccms/spring",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript spring package",
|
"description": "MiaoScript spring package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -20,13 +20,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ccms/api": "^0.24.0",
|
"@ccms/api": "^0.27.6",
|
||||||
"@ccms/common": "^0.24.0",
|
"@ccms/common": "^0.27.6",
|
||||||
"@ccms/container": "^0.24.0",
|
"@ccms/container": "^0.27.6",
|
||||||
"@ccms/database": "^0.24.0"
|
"@ccms/database": "^0.27.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ccms/web",
|
"name": "@ccms/web",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript web package",
|
"description": "MiaoScript web package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
"@javatypes/spring-web": "^0.0.3",
|
"@javatypes/spring-web": "^0.0.3",
|
||||||
"@javatypes/tomcat": "^0.0.3",
|
"@javatypes/tomcat": "^0.0.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ccms/api": "^0.24.0",
|
"@ccms/api": "^0.27.6",
|
||||||
"@ccms/container": "^0.24.0"
|
"@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",
|
"name": "@ccms/websocket",
|
||||||
"version": "0.24.0",
|
"version": "0.27.6",
|
||||||
"description": "MiaoScript websocket package",
|
"description": "MiaoScript websocket package",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"miaoscript",
|
"miaoscript",
|
||||||
@@ -19,14 +19,15 @@
|
|||||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "3.1.0",
|
||||||
"backo2": "^1.0.2",
|
"backo2": "^1.0.2",
|
||||||
"parseuri": "^0.0.6"
|
"parseuri": "^0.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ccms/nashorn": "^0.24.0",
|
"@ccms/nashorn": "^0.27.6",
|
||||||
"@javatypes/tomcat-websocket-api": "^0.0.3",
|
"@javatypes/tomcat-websocket-api": "^0.0.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.1.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^4.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export class WebSocket extends EventEmitter {
|
|||||||
|
|
||||||
private client: Transport
|
private client: Transport
|
||||||
|
|
||||||
constructor(url: string, subProtocol: string = '', headers: WebSocketHeader = {}) {
|
constructor(url: string, subProtocol: string | string[] = '', headers: WebSocketHeader = {}) {
|
||||||
super()
|
super()
|
||||||
this.manager = manager
|
this.manager = manager
|
||||||
this._url = url
|
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)
|
export { Socket }
|
||||||
|
export { SocketOptions } from "./socket"
|
||||||
/**
|
export const protocol = Socket.protocol
|
||||||
* Expose deps for legacy compatibility
|
export { Transport } from "./transport"
|
||||||
* and standalone browser access.
|
export { transports } from "./transports/index"
|
||||||
*/
|
export { installTimerFunctions } from "./util"
|
||||||
const protocol = Socket.protocol // this is an int
|
export { parse } from "./contrib/parseuri"
|
||||||
export { Socket, protocol }
|
export { nextTick } from "./transports/websocket-constructor"
|
||||||
// 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'
|
|
||||||
|
|||||||
@@ -1,21 +1,295 @@
|
|||||||
import transports from "./transports"
|
// import { transports } from "./transports/index.js";
|
||||||
// const transports = require("./transports/index")
|
import { transports } from "./transports"
|
||||||
const Emitter = require("component-emitter")
|
import { installTimerFunctions, byteLength } from "./util"
|
||||||
const debug = (...args: any) => console.debug('engine.io-client:socket', ...args)//require("debug")("engine.io-client:socket")
|
import { decode } from "./contrib/parseqs"
|
||||||
import parser from "../engine.io-parser"
|
import { parse } from "./contrib/parseuri"
|
||||||
const parseuri = require("parseuri")
|
// import debugModule from "debug"; // debug()
|
||||||
const parseqs = require("parseqs")
|
import { Emitter } from "@socket.io/component-emitter"
|
||||||
import { installTimerFunctions } from "./util"
|
// 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.
|
* Socket constructor.
|
||||||
*
|
*
|
||||||
* @param {String|Object} uri or options
|
* @param {String|Object} uri or options
|
||||||
* @param {Object} options
|
* @param {Object} opts - options
|
||||||
* @api public
|
* @api public
|
||||||
*/
|
*/
|
||||||
constructor(uri, opts: any = {}) {
|
constructor(uri, opts: Partial<SocketOptions> = {}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
if (uri && "object" === typeof uri) {
|
if (uri && "object" === typeof uri) {
|
||||||
@@ -24,13 +298,13 @@ export class Socket extends Emitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (uri) {
|
if (uri) {
|
||||||
uri = parseuri(uri)
|
uri = parse(uri)
|
||||||
opts.hostname = uri.host
|
opts.hostname = uri.host
|
||||||
opts.secure = uri.protocol === "https" || uri.protocol === "wss"
|
opts.secure = uri.protocol === "https" || uri.protocol === "wss"
|
||||||
opts.port = uri.port
|
opts.port = uri.port
|
||||||
if (uri.query) opts.query = uri.query
|
if (uri.query) opts.query = uri.query
|
||||||
} else if (opts.host) {
|
} else if (opts.host) {
|
||||||
opts.hostname = parseuri(opts.host).host
|
opts.hostname = parse(opts.host).host
|
||||||
}
|
}
|
||||||
|
|
||||||
installTimerFunctions(this, opts)
|
installTimerFunctions(this, opts)
|
||||||
@@ -53,11 +327,10 @@ export class Socket extends Emitter {
|
|||||||
(typeof location !== "undefined" && location.port
|
(typeof location !== "undefined" && location.port
|
||||||
? location.port
|
? location.port
|
||||||
: this.secure
|
: this.secure
|
||||||
? 443
|
? "443"
|
||||||
: 80)
|
: "80")
|
||||||
|
|
||||||
this.transports = ["websocket"]
|
this.transports = opts.transports || ["polling", "websocket"]
|
||||||
this.readyState = ""
|
|
||||||
this.writeBuffer = []
|
this.writeBuffer = []
|
||||||
this.prevBufferLen = 0
|
this.prevBufferLen = 0
|
||||||
|
|
||||||
@@ -67,9 +340,9 @@ export class Socket extends Emitter {
|
|||||||
agent: false,
|
agent: false,
|
||||||
withCredentials: false,
|
withCredentials: false,
|
||||||
upgrade: true,
|
upgrade: true,
|
||||||
jsonp: true,
|
|
||||||
timestampParam: "t",
|
timestampParam: "t",
|
||||||
rememberUpgrade: false,
|
rememberUpgrade: false,
|
||||||
|
addTrailingSlash: true,
|
||||||
rejectUnauthorized: true,
|
rejectUnauthorized: true,
|
||||||
perMessageDeflate: {
|
perMessageDeflate: {
|
||||||
threshold: 1024
|
threshold: 1024
|
||||||
@@ -80,10 +353,12 @@ export class Socket extends Emitter {
|
|||||||
opts
|
opts
|
||||||
)
|
)
|
||||||
|
|
||||||
this.opts.path = this.opts.path.replace(/\/$/, "") + "/"
|
this.opts.path =
|
||||||
|
this.opts.path.replace(/\/$/, "") +
|
||||||
|
(this.opts.addTrailingSlash ? "/" : "")
|
||||||
|
|
||||||
if (typeof this.opts.query === "string") {
|
if (typeof this.opts.query === "string") {
|
||||||
this.opts.query = parseqs.decode(this.opts.query)
|
this.opts.query = decode(this.opts.query)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set on handshake
|
// 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
|
// 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
|
// ensures every browser behaves the same (no "disconnect" event at the Socket.IO level when the page is
|
||||||
// closed/reloaded)
|
// closed/reloaded)
|
||||||
addEventListener(
|
this.beforeunloadEventListener = () => {
|
||||||
"beforeunload",
|
if (this.transport) {
|
||||||
() => {
|
// silently close the transport
|
||||||
if (this.transport) {
|
this.transport.removeAllListeners()
|
||||||
// silently close the transport
|
this.transport.close()
|
||||||
this.transport.removeAllListeners()
|
}
|
||||||
this.transport.close()
|
}
|
||||||
}
|
addEventListener("beforeunload", this.beforeunloadEventListener, false)
|
||||||
},
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (this.hostname !== "localhost") {
|
if (this.hostname !== "localhost") {
|
||||||
this.offlineEventListener = () => {
|
this.offlineEventListener = () => {
|
||||||
this.onClose("transport close")
|
this.onClose("transport close", {
|
||||||
|
description: "network connection lost",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
addEventListener("offline", this.offlineEventListener, false)
|
addEventListener("offline", this.offlineEventListener, false)
|
||||||
}
|
}
|
||||||
@@ -126,19 +400,16 @@ export class Socket extends Emitter {
|
|||||||
/**
|
/**
|
||||||
* Creates transport of the given type.
|
* Creates transport of the given type.
|
||||||
*
|
*
|
||||||
* @param {String} transport name
|
* @param {String} name - transport name
|
||||||
* @return {Transport}
|
* @return {Transport}
|
||||||
* @api private
|
* @private
|
||||||
*/
|
*/
|
||||||
createTransport(name, opt?) {
|
private createTransport(name) {
|
||||||
if (name != 'websocket') {
|
|
||||||
throw new Error('Only Support WebSocket in MiaoScript!')
|
|
||||||
}
|
|
||||||
debug('creating transport "%s"', 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
|
// append engine.io protocol identifier
|
||||||
query.EIO = parser.protocol
|
query.EIO = protocol
|
||||||
|
|
||||||
// transport name
|
// transport name
|
||||||
query.transport = name
|
query.transport = name
|
||||||
@@ -155,21 +426,21 @@ export class Socket extends Emitter {
|
|||||||
socket: this,
|
socket: this,
|
||||||
hostname: this.hostname,
|
hostname: this.hostname,
|
||||||
secure: this.secure,
|
secure: this.secure,
|
||||||
port: this.port
|
port: this.port,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
debug("options: %j", JSON.stringify(opts))
|
debug("options: %j", opts)
|
||||||
debug("new func", transports[name])
|
|
||||||
return new transports[name](opts)
|
return new transports[name](opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes transport to use and starts probe.
|
* Initializes transport to use and starts probe.
|
||||||
*
|
*
|
||||||
* @api private
|
* @private
|
||||||
*/
|
*/
|
||||||
open() {
|
private open() {
|
||||||
let transport
|
let transport
|
||||||
if (
|
if (
|
||||||
this.opts.rememberUpgrade &&
|
this.opts.rememberUpgrade &&
|
||||||
@@ -180,7 +451,7 @@ export class Socket extends Emitter {
|
|||||||
} else if (0 === this.transports.length) {
|
} else if (0 === this.transports.length) {
|
||||||
// Emit error on next tick so it can be listened to
|
// Emit error on next tick so it can be listened to
|
||||||
this.setTimeoutFn(() => {
|
this.setTimeoutFn(() => {
|
||||||
this.emit("error", "No transports available")
|
this.emitReserved("error", "No transports available")
|
||||||
}, 0)
|
}, 0)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -191,8 +462,8 @@ export class Socket extends Emitter {
|
|||||||
// Retry with the next transport if the transport is disabled (jsonp: false)
|
// Retry with the next transport if the transport is disabled (jsonp: false)
|
||||||
try {
|
try {
|
||||||
transport = this.createTransport(transport)
|
transport = this.createTransport(transport)
|
||||||
} catch (error: any) {
|
} catch (e) {
|
||||||
debug("error while creating transport: %s", error)
|
debug("error while creating transport: %s", e)
|
||||||
this.transports.shift()
|
this.transports.shift()
|
||||||
this.open()
|
this.open()
|
||||||
return
|
return
|
||||||
@@ -205,9 +476,9 @@ export class Socket extends Emitter {
|
|||||||
/**
|
/**
|
||||||
* Sets the current transport. Disables the existing one (if any).
|
* Sets the current transport. Disables the existing one (if any).
|
||||||
*
|
*
|
||||||
* @api private
|
* @private
|
||||||
*/
|
*/
|
||||||
setTransport(transport) {
|
private setTransport(transport) {
|
||||||
debug("setting transport %s", transport.name)
|
debug("setting transport %s", transport.name)
|
||||||
|
|
||||||
if (this.transport) {
|
if (this.transport) {
|
||||||
@@ -223,9 +494,7 @@ export class Socket extends Emitter {
|
|||||||
.on("drain", this.onDrain.bind(this))
|
.on("drain", this.onDrain.bind(this))
|
||||||
.on("packet", this.onPacket.bind(this))
|
.on("packet", this.onPacket.bind(this))
|
||||||
.on("error", this.onError.bind(this))
|
.on("error", this.onError.bind(this))
|
||||||
.on("close", () => {
|
.on("close", (reason) => this.onClose("transport close", reason))
|
||||||
this.onClose("transport close")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -234,9 +503,9 @@ export class Socket extends Emitter {
|
|||||||
* @param {String} transport name
|
* @param {String} transport name
|
||||||
* @api private
|
* @api private
|
||||||
*/
|
*/
|
||||||
probe(name) {
|
private probe(name) {
|
||||||
debug('probing transport "%s"', name)
|
debug('probing transport "%s"', name)
|
||||||
let transport = this.createTransport(name, { probe: 1 })
|
let transport = this.createTransport(name)
|
||||||
let failed = false
|
let failed = false
|
||||||
|
|
||||||
Socket.priorWebsocketSuccess = false
|
Socket.priorWebsocketSuccess = false
|
||||||
@@ -251,7 +520,7 @@ export class Socket extends Emitter {
|
|||||||
if ("pong" === msg.type && "probe" === msg.data) {
|
if ("pong" === msg.type && "probe" === msg.data) {
|
||||||
debug('probe transport "%s" pong', name)
|
debug('probe transport "%s" pong', name)
|
||||||
this.upgrading = true
|
this.upgrading = true
|
||||||
this.emit("upgrading", transport)
|
this.emitReserved("upgrading", transport)
|
||||||
if (!transport) return
|
if (!transport) return
|
||||||
Socket.priorWebsocketSuccess = "websocket" === transport.name
|
Socket.priorWebsocketSuccess = "websocket" === transport.name
|
||||||
|
|
||||||
@@ -265,16 +534,17 @@ export class Socket extends Emitter {
|
|||||||
|
|
||||||
this.setTransport(transport)
|
this.setTransport(transport)
|
||||||
transport.send([{ type: "upgrade" }])
|
transport.send([{ type: "upgrade" }])
|
||||||
this.emit("upgrade", transport)
|
this.emitReserved("upgrade", transport)
|
||||||
transport = null
|
transport = null
|
||||||
this.upgrading = false
|
this.upgrading = false
|
||||||
this.flush()
|
this.flush()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
debug('probe transport "%s" failed', name)
|
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
|
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
|
// Handle any error that happens while probing
|
||||||
const onerror = err => {
|
const onerror = err => {
|
||||||
const error: any = new Error("probe error: " + err)
|
const error = new Error("probe error: " + err)
|
||||||
|
// @ts-ignore
|
||||||
error.transport = transport.name
|
error.transport = transport.name
|
||||||
|
|
||||||
freezeTransport()
|
freezeTransport()
|
||||||
|
|
||||||
debug('probe transport "%s" failed because of error: %s', name, err)
|
debug('probe transport "%s" failed because of error: %s', name, err)
|
||||||
|
|
||||||
this.emit("upgradeError", error)
|
this.emitReserved("upgradeError", error)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTransportClose() {
|
function onTransportClose() {
|
||||||
@@ -325,8 +596,8 @@ export class Socket extends Emitter {
|
|||||||
transport.removeListener("open", onTransportOpen)
|
transport.removeListener("open", onTransportOpen)
|
||||||
transport.removeListener("error", onerror)
|
transport.removeListener("error", onerror)
|
||||||
transport.removeListener("close", onTransportClose)
|
transport.removeListener("close", onTransportClose)
|
||||||
this.removeListener("close", onclose)
|
this.off("close", onclose)
|
||||||
this.removeListener("upgrading", onupgrade)
|
this.off("upgrading", onupgrade)
|
||||||
}
|
}
|
||||||
|
|
||||||
transport.once("open", onTransportOpen)
|
transport.once("open", onTransportOpen)
|
||||||
@@ -342,13 +613,13 @@ export class Socket extends Emitter {
|
|||||||
/**
|
/**
|
||||||
* Called when connection is deemed open.
|
* Called when connection is deemed open.
|
||||||
*
|
*
|
||||||
* @api public
|
* @api private
|
||||||
*/
|
*/
|
||||||
onOpen() {
|
private onOpen() {
|
||||||
debug("socket open")
|
debug("socket open")
|
||||||
this.readyState = "open"
|
this.readyState = "open"
|
||||||
Socket.priorWebsocketSuccess = "websocket" === this.transport.name
|
Socket.priorWebsocketSuccess = "websocket" === this.transport.name
|
||||||
this.emit("open")
|
this.emitReserved("open")
|
||||||
this.flush()
|
this.flush()
|
||||||
|
|
||||||
// we check for `readyState` in case an `open`
|
// we check for `readyState` in case an `open`
|
||||||
@@ -372,7 +643,7 @@ export class Socket extends Emitter {
|
|||||||
*
|
*
|
||||||
* @api private
|
* @api private
|
||||||
*/
|
*/
|
||||||
onPacket(packet) {
|
private onPacket(packet) {
|
||||||
if (
|
if (
|
||||||
"opening" === this.readyState ||
|
"opening" === this.readyState ||
|
||||||
"open" === 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)
|
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
|
// Socket is live - any packet counts
|
||||||
this.emit("heartbeat")
|
this.emitReserved("heartbeat")
|
||||||
|
|
||||||
switch (packet.type) {
|
switch (packet.type) {
|
||||||
case "open":
|
case "open":
|
||||||
@@ -393,19 +664,20 @@ export class Socket extends Emitter {
|
|||||||
case "ping":
|
case "ping":
|
||||||
this.resetPingTimeout()
|
this.resetPingTimeout()
|
||||||
this.sendPacket("pong")
|
this.sendPacket("pong")
|
||||||
this.emit("ping")
|
this.emitReserved("ping")
|
||||||
this.emit("pong")
|
this.emitReserved("pong")
|
||||||
break
|
break
|
||||||
|
|
||||||
case "error":
|
case "error":
|
||||||
const err: any = new Error("server error")
|
const err = new Error("server error")
|
||||||
|
// @ts-ignore
|
||||||
err.code = packet.data
|
err.code = packet.data
|
||||||
this.onError(err)
|
this.onError(err)
|
||||||
break
|
break
|
||||||
|
|
||||||
case "message":
|
case "message":
|
||||||
this.emit("data", packet.data)
|
this.emitReserved("data", packet.data)
|
||||||
this.emit("message", packet.data)
|
this.emitReserved("message", packet.data)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -416,16 +688,17 @@ export class Socket extends Emitter {
|
|||||||
/**
|
/**
|
||||||
* Called upon handshake completion.
|
* Called upon handshake completion.
|
||||||
*
|
*
|
||||||
* @param {Object} handshake obj
|
* @param {Object} data - handshake obj
|
||||||
* @api private
|
* @api private
|
||||||
*/
|
*/
|
||||||
onHandshake(data) {
|
private onHandshake(data) {
|
||||||
this.emit("handshake", data)
|
this.emitReserved("handshake", data)
|
||||||
this.id = data.sid
|
this.id = data.sid
|
||||||
this.transport.query.sid = data.sid
|
this.transport.query.sid = data.sid
|
||||||
this.upgrades = this.filterUpgrades(data.upgrades)
|
this.upgrades = this.filterUpgrades(data.upgrades)
|
||||||
this.pingInterval = data.pingInterval
|
this.pingInterval = data.pingInterval
|
||||||
this.pingTimeout = data.pingTimeout
|
this.pingTimeout = data.pingTimeout
|
||||||
|
this.maxPayload = data.maxPayload
|
||||||
this.onOpen()
|
this.onOpen()
|
||||||
// In case open handler closes socket
|
// In case open handler closes socket
|
||||||
if ("closed" === this.readyState) return
|
if ("closed" === this.readyState) return
|
||||||
@@ -437,7 +710,7 @@ export class Socket extends Emitter {
|
|||||||
*
|
*
|
||||||
* @api private
|
* @api private
|
||||||
*/
|
*/
|
||||||
resetPingTimeout() {
|
private resetPingTimeout() {
|
||||||
this.clearTimeoutFn(this.pingTimeoutTimer)
|
this.clearTimeoutFn(this.pingTimeoutTimer)
|
||||||
this.pingTimeoutTimer = this.setTimeoutFn(() => {
|
this.pingTimeoutTimer = this.setTimeoutFn(() => {
|
||||||
this.onClose("ping timeout")
|
this.onClose("ping timeout")
|
||||||
@@ -452,7 +725,7 @@ export class Socket extends Emitter {
|
|||||||
*
|
*
|
||||||
* @api private
|
* @api private
|
||||||
*/
|
*/
|
||||||
onDrain() {
|
private onDrain() {
|
||||||
this.writeBuffer.splice(0, this.prevBufferLen)
|
this.writeBuffer.splice(0, this.prevBufferLen)
|
||||||
|
|
||||||
// setting prevBufferLen = 0 is very important
|
// setting prevBufferLen = 0 is very important
|
||||||
@@ -461,7 +734,7 @@ export class Socket extends Emitter {
|
|||||||
this.prevBufferLen = 0
|
this.prevBufferLen = 0
|
||||||
|
|
||||||
if (0 === this.writeBuffer.length) {
|
if (0 === this.writeBuffer.length) {
|
||||||
this.emit("drain")
|
this.emitReserved("drain")
|
||||||
} else {
|
} else {
|
||||||
this.flush()
|
this.flush()
|
||||||
}
|
}
|
||||||
@@ -472,22 +745,53 @@ export class Socket extends Emitter {
|
|||||||
*
|
*
|
||||||
* @api private
|
* @api private
|
||||||
*/
|
*/
|
||||||
flush() {
|
private flush() {
|
||||||
if (
|
if (
|
||||||
"closed" !== this.readyState &&
|
"closed" !== this.readyState &&
|
||||||
this.transport.writable &&
|
this.transport.writable &&
|
||||||
!this.upgrading &&
|
!this.upgrading &&
|
||||||
this.writeBuffer.length
|
this.writeBuffer.length
|
||||||
) {
|
) {
|
||||||
debug("flushing %d packets in socket", this.writeBuffer.length)
|
const packets = this.getWritablePackets()
|
||||||
this.transport.send(this.writeBuffer)
|
debug("flushing %d packets in socket", packets.length)
|
||||||
|
this.transport.send(packets)
|
||||||
// keep track of current length of writeBuffer
|
// keep track of current length of writeBuffer
|
||||||
// splice writeBuffer and callbackBuffer on `drain`
|
// splice writeBuffer and callbackBuffer on `drain`
|
||||||
this.prevBufferLen = this.writeBuffer.length
|
this.prevBufferLen = packets.length
|
||||||
this.emit("flush")
|
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.
|
* Sends a message.
|
||||||
*
|
*
|
||||||
@@ -497,12 +801,12 @@ export class Socket extends Emitter {
|
|||||||
* @return {Socket} for chaining.
|
* @return {Socket} for chaining.
|
||||||
* @api public
|
* @api public
|
||||||
*/
|
*/
|
||||||
write(msg, options, fn) {
|
public write(msg, options, fn?) {
|
||||||
this.sendPacket("message", msg, options, fn)
|
this.sendPacket("message", msg, options, fn)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
send(msg, options, fn) {
|
public send(msg, options, fn?) {
|
||||||
this.sendPacket("message", msg, options, fn)
|
this.sendPacket("message", msg, options, fn)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@@ -516,7 +820,7 @@ export class Socket extends Emitter {
|
|||||||
* @param {Function} callback function.
|
* @param {Function} callback function.
|
||||||
* @api private
|
* @api private
|
||||||
*/
|
*/
|
||||||
sendPacket(type, data?, options?, fn?) {
|
private sendPacket(type, data?, options?, fn?) {
|
||||||
if ("function" === typeof data) {
|
if ("function" === typeof data) {
|
||||||
fn = data
|
fn = data
|
||||||
data = undefined
|
data = undefined
|
||||||
@@ -539,7 +843,7 @@ export class Socket extends Emitter {
|
|||||||
data: data,
|
data: data,
|
||||||
options: options
|
options: options
|
||||||
}
|
}
|
||||||
this.emit("packetCreate", packet)
|
this.emitReserved("packetCreate", packet)
|
||||||
this.writeBuffer.push(packet)
|
this.writeBuffer.push(packet)
|
||||||
if (fn) this.once("flush", fn)
|
if (fn) this.once("flush", fn)
|
||||||
this.flush()
|
this.flush()
|
||||||
@@ -548,9 +852,9 @@ export class Socket extends Emitter {
|
|||||||
/**
|
/**
|
||||||
* Closes the connection.
|
* Closes the connection.
|
||||||
*
|
*
|
||||||
* @api private
|
* @api public
|
||||||
*/
|
*/
|
||||||
close() {
|
public close() {
|
||||||
const close = () => {
|
const close = () => {
|
||||||
this.onClose("forced close")
|
this.onClose("forced close")
|
||||||
debug("socket closing - telling transport to close")
|
debug("socket closing - telling transport to close")
|
||||||
@@ -558,8 +862,8 @@ export class Socket extends Emitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cleanupAndClose = () => {
|
const cleanupAndClose = () => {
|
||||||
this.removeListener("upgrade", cleanupAndClose)
|
this.off("upgrade", cleanupAndClose)
|
||||||
this.removeListener("upgradeError", cleanupAndClose)
|
this.off("upgradeError", cleanupAndClose)
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,10 +899,10 @@ export class Socket extends Emitter {
|
|||||||
*
|
*
|
||||||
* @api private
|
* @api private
|
||||||
*/
|
*/
|
||||||
onError(err) {
|
private onError(err) {
|
||||||
debug("socket error %j", err)
|
debug("socket error %j", err)
|
||||||
Socket.priorWebsocketSuccess = false
|
Socket.priorWebsocketSuccess = false
|
||||||
this.emit("error", err)
|
this.emitReserved("error", err)
|
||||||
this.onClose("transport error", err)
|
this.onClose("transport error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -607,7 +911,7 @@ export class Socket extends Emitter {
|
|||||||
*
|
*
|
||||||
* @api private
|
* @api private
|
||||||
*/
|
*/
|
||||||
onClose(reason, desc?) {
|
private onClose(reason: string, description?: CloseDetails | Error) {
|
||||||
if (
|
if (
|
||||||
"opening" === this.readyState ||
|
"opening" === this.readyState ||
|
||||||
"open" === this.readyState ||
|
"open" === this.readyState ||
|
||||||
@@ -616,7 +920,6 @@ export class Socket extends Emitter {
|
|||||||
debug('socket close with reason: "%s"', reason)
|
debug('socket close with reason: "%s"', reason)
|
||||||
|
|
||||||
// clear timers
|
// clear timers
|
||||||
this.clearTimeoutFn(this.pingIntervalTimer)
|
|
||||||
this.clearTimeoutFn(this.pingTimeoutTimer)
|
this.clearTimeoutFn(this.pingTimeoutTimer)
|
||||||
|
|
||||||
// stop event from firing again for transport
|
// stop event from firing again for transport
|
||||||
@@ -629,6 +932,11 @@ export class Socket extends Emitter {
|
|||||||
this.transport.removeAllListeners()
|
this.transport.removeAllListeners()
|
||||||
|
|
||||||
if (typeof removeEventListener === "function") {
|
if (typeof removeEventListener === "function") {
|
||||||
|
removeEventListener(
|
||||||
|
"beforeunload",
|
||||||
|
this.beforeunloadEventListener,
|
||||||
|
false
|
||||||
|
)
|
||||||
removeEventListener("offline", this.offlineEventListener, false)
|
removeEventListener("offline", this.offlineEventListener, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -639,7 +947,7 @@ export class Socket extends Emitter {
|
|||||||
this.id = null
|
this.id = null
|
||||||
|
|
||||||
// emit close event
|
// emit close event
|
||||||
this.emit("close", reason, desc)
|
this.emitReserved("close", reason, description)
|
||||||
|
|
||||||
// clean buffers after, so users can still
|
// clean buffers after, so users can still
|
||||||
// grab the buffers on `close` event
|
// grab the buffers on `close` event
|
||||||
@@ -655,7 +963,7 @@ export class Socket extends Emitter {
|
|||||||
* @api private
|
* @api private
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
filterUpgrades(upgrades) {
|
private filterUpgrades(upgrades) {
|
||||||
const filteredUpgrades = []
|
const filteredUpgrades = []
|
||||||
let i = 0
|
let i = 0
|
||||||
const j = upgrades.length
|
const j = upgrades.length
|
||||||
@@ -666,23 +974,3 @@ export class Socket extends Emitter {
|
|||||||
return filteredUpgrades
|
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"
|
// import { decodePacket, Packet, RawData } from "engine.io-parser"
|
||||||
const Emitter = require("component-emitter")
|
import { decodePacket, Packet, RawData } from "../engine.io-parser"
|
||||||
|
import { Emitter } from "@socket.io/component-emitter"
|
||||||
import { installTimerFunctions } from "./util"
|
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.
|
* Transport abstract constructor.
|
||||||
*
|
*
|
||||||
* @param {Object} options.
|
* @param {Object} opts - options
|
||||||
* @api private
|
* @protected
|
||||||
*/
|
*/
|
||||||
constructor(opts) {
|
constructor(opts) {
|
||||||
super()
|
super()
|
||||||
installTimerFunctions(this, opts)
|
installTimerFunctions(this, opts)
|
||||||
|
|
||||||
this.opts = opts
|
this.opts = opts
|
||||||
this.query = opts.query
|
this.query = opts.query
|
||||||
this.readyState = ""
|
|
||||||
this.socket = opts.socket
|
this.socket = opts.socket
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits an error.
|
* Emits an error.
|
||||||
*
|
*
|
||||||
* @param {String} str
|
* @param {String} reason
|
||||||
|
* @param description
|
||||||
|
* @param context - the error context
|
||||||
* @return {Transport} for chaining
|
* @return {Transport} for chaining
|
||||||
* @api public
|
* @protected
|
||||||
*/
|
*/
|
||||||
onError(msg, desc) {
|
protected onError(reason: string, description: any, context?: any) {
|
||||||
const err: any = new Error(msg)
|
super.emitReserved(
|
||||||
err.type = "TransportError"
|
"error",
|
||||||
err.description = desc
|
new TransportError(reason, description, context)
|
||||||
this.emit("error", err)
|
)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the transport.
|
* Opens the transport.
|
||||||
*
|
|
||||||
* @api public
|
|
||||||
*/
|
*/
|
||||||
open() {
|
public open() {
|
||||||
if ("closed" === this.readyState || "" === this.readyState) {
|
this.readyState = "opening"
|
||||||
this.readyState = "opening"
|
this.doOpen()
|
||||||
this.doOpen()
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the transport.
|
* Closes the transport.
|
||||||
*
|
|
||||||
* @api private
|
|
||||||
*/
|
*/
|
||||||
close() {
|
public close() {
|
||||||
if ("opening" === this.readyState || "open" === this.readyState) {
|
if (this.readyState === "opening" || this.readyState === "open") {
|
||||||
this.doClose()
|
this.doClose()
|
||||||
this.onClose()
|
this.onClose()
|
||||||
}
|
}
|
||||||
@@ -67,10 +109,9 @@ export class Transport extends Emitter {
|
|||||||
* Sends multiple packets.
|
* Sends multiple packets.
|
||||||
*
|
*
|
||||||
* @param {Array} packets
|
* @param {Array} packets
|
||||||
* @api private
|
|
||||||
*/
|
*/
|
||||||
send(packets) {
|
public send(packets) {
|
||||||
if ("open" === this.readyState) {
|
if (this.readyState === "open") {
|
||||||
this.write(packets)
|
this.write(packets)
|
||||||
} else {
|
} else {
|
||||||
// this might happen if the transport was silently closed in the beforeunload event handler
|
// 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
|
* Called upon open
|
||||||
*
|
*
|
||||||
* @api private
|
* @protected
|
||||||
*/
|
*/
|
||||||
onOpen() {
|
protected onOpen() {
|
||||||
this.readyState = "open"
|
this.readyState = "open"
|
||||||
this.writable = true
|
this.writable = true
|
||||||
this.emit("open")
|
super.emitReserved("open")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called with data.
|
* Called with data.
|
||||||
*
|
*
|
||||||
* @param {String} data
|
* @param {String} data
|
||||||
* @api private
|
* @protected
|
||||||
*/
|
*/
|
||||||
onData(data) {
|
protected onData(data: RawData) {
|
||||||
const packet = parser.decodePacket(data, this.socket.binaryType)
|
const packet = decodePacket(data, this.socket.binaryType)
|
||||||
this.onPacket(packet)
|
this.onPacket(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called with a decoded packet.
|
* Called with a decoded packet.
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
*/
|
*/
|
||||||
onPacket(packet) {
|
protected onPacket(packet: Packet) {
|
||||||
this.emit("packet", packet)
|
super.emitReserved("packet", packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called upon close.
|
* Called upon close.
|
||||||
*
|
*
|
||||||
* @api private
|
* @protected
|
||||||
*/
|
*/
|
||||||
onClose() {
|
protected onClose(details?: CloseDetails) {
|
||||||
this.readyState = "closed"
|
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"
|
import { WS } from "./websocket.js"
|
||||||
export default {
|
|
||||||
'websocket': WS
|
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'
|
import { Transport } from "../transport"
|
||||||
// const Transport = require("../transport")
|
import { encode } from "../contrib/parseqs"
|
||||||
import parser from '../../engine.io-parser'
|
import { yeast } from "../contrib/yeast"
|
||||||
// const parser = require("../engine.io-parser")
|
import { pick } from "../util"
|
||||||
const parseqs = require("parseqs")
|
import {
|
||||||
const yeast = require("yeast")
|
defaultBinaryType,
|
||||||
import { pick } from '../util'
|
nextTick,
|
||||||
// const { pick } = require("../util")
|
usingBrowserWebSocket,
|
||||||
import { WebSocket } from '../../client'
|
WebSocket
|
||||||
const usingBrowserWebSocket = true
|
} from "./websocket-constructor"
|
||||||
// const {
|
// import debugModule from "debug" // debug()
|
||||||
// WebSocket,
|
import { encodePacket } from "../../engine.io-parser"
|
||||||
// usingBrowserWebSocket,
|
|
||||||
// defaultBinaryType,
|
|
||||||
// nextTick
|
|
||||||
// } = require("./websocket-constructor")
|
|
||||||
|
|
||||||
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
|
// detect ReactNative environment
|
||||||
const isReactNative =
|
const isReactNative =
|
||||||
@@ -24,11 +21,13 @@ const isReactNative =
|
|||||||
navigator.product.toLowerCase() === "reactnative"
|
navigator.product.toLowerCase() === "reactnative"
|
||||||
|
|
||||||
export class WS extends Transport {
|
export class WS extends Transport {
|
||||||
|
private ws: any
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebSocket transport constructor.
|
* WebSocket transport constructor.
|
||||||
*
|
*
|
||||||
* @api {Object} connection options
|
* @param {Object} opts - connection options
|
||||||
* @api public
|
* @protected
|
||||||
*/
|
*/
|
||||||
constructor(opts) {
|
constructor(opts) {
|
||||||
super(opts)
|
super(opts)
|
||||||
@@ -36,21 +35,11 @@ export class WS extends Transport {
|
|||||||
this.supportsBinary = !opts.forceBase64
|
this.supportsBinary = !opts.forceBase64
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override get name() {
|
||||||
* Transport name.
|
|
||||||
*
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
get name() {
|
|
||||||
return "websocket"
|
return "websocket"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override doOpen() {
|
||||||
* Opens socket.
|
|
||||||
*
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
doOpen() {
|
|
||||||
if (!this.check()) {
|
if (!this.check()) {
|
||||||
// let probe timeout
|
// let probe timeout
|
||||||
return
|
return
|
||||||
@@ -86,17 +75,17 @@ export class WS extends Transport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.ws = new WebSocket(uri, protocols)
|
this.ws =
|
||||||
// usingBrowserWebSocket && !isReactNative
|
usingBrowserWebSocket && !isReactNative
|
||||||
// ? protocols
|
? protocols
|
||||||
// ? new WebSocket(uri, protocols)
|
? new WebSocket(uri, protocols)
|
||||||
// : new WebSocket(uri)
|
: new WebSocket(uri)
|
||||||
// : new WebSocket(uri, protocols, opts)
|
: new WebSocket(uri, protocols, opts)
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
return this.emit("error", err)
|
return this.emitReserved("error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ws.binaryType = this.socket.binaryType || 'arraybuffer'
|
this.ws.binaryType = this.socket.binaryType || defaultBinaryType
|
||||||
|
|
||||||
this.addEventListeners()
|
this.addEventListeners()
|
||||||
}
|
}
|
||||||
@@ -104,27 +93,25 @@ export class WS extends Transport {
|
|||||||
/**
|
/**
|
||||||
* Adds event listeners to the socket
|
* Adds event listeners to the socket
|
||||||
*
|
*
|
||||||
* @api private
|
* @private
|
||||||
*/
|
*/
|
||||||
addEventListeners() {
|
private addEventListeners() {
|
||||||
this.ws.onopen = () => {
|
this.ws.onopen = () => {
|
||||||
if (this.opts.autoUnref) {
|
if (this.opts.autoUnref) {
|
||||||
this.ws._socket.unref()
|
this.ws._socket.unref()
|
||||||
}
|
}
|
||||||
this.onOpen()
|
this.onOpen()
|
||||||
}
|
}
|
||||||
this.ws.onclose = this.onClose.bind(this)
|
this.ws.onclose = (closeEvent) =>
|
||||||
this.ws.onmessage = ev => this.onData(ev.data)
|
this.onClose({
|
||||||
this.ws.onerror = e => this.onError("websocket error", e)
|
description: "websocket connection closed",
|
||||||
|
context: closeEvent,
|
||||||
|
})
|
||||||
|
this.ws.onmessage = (ev) => this.onData(ev.data)
|
||||||
|
this.ws.onerror = (e) => this.onError("websocket error", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override write(packets) {
|
||||||
* Writes data to socket.
|
|
||||||
*
|
|
||||||
* @param {Array} array of packets.
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
write(packets) {
|
|
||||||
this.writable = false
|
this.writable = false
|
||||||
|
|
||||||
// encodePacket efficient as it uses WS framing
|
// encodePacket efficient as it uses WS framing
|
||||||
@@ -133,9 +120,9 @@ export class WS extends Transport {
|
|||||||
const packet = packets[i]
|
const packet = packets[i]
|
||||||
const lastPacket = i === packets.length - 1
|
const lastPacket = i === packets.length - 1
|
||||||
|
|
||||||
parser.encodePacket(packet, this.supportsBinary, data => {
|
encodePacket(packet, this.supportsBinary, (data) => {
|
||||||
// always create a new object (GH-437)
|
// always create a new object (GH-437)
|
||||||
const opts: any = {}
|
const opts: { compress?: boolean } = {}
|
||||||
if (!usingBrowserWebSocket) {
|
if (!usingBrowserWebSocket) {
|
||||||
if (packet.options) {
|
if (packet.options) {
|
||||||
opts.compress = packet.options.compress
|
opts.compress = packet.options.compress
|
||||||
@@ -143,6 +130,7 @@ export class WS extends Transport {
|
|||||||
|
|
||||||
if (this.opts.perMessageDeflate) {
|
if (this.opts.perMessageDeflate) {
|
||||||
const len =
|
const len =
|
||||||
|
// @ts-ignore
|
||||||
"string" === typeof data ? Buffer.byteLength(data) : data.length
|
"string" === typeof data ? Buffer.byteLength(data) : data.length
|
||||||
if (len < this.opts.perMessageDeflate.threshold) {
|
if (len < this.opts.perMessageDeflate.threshold) {
|
||||||
opts.compress = false
|
opts.compress = false
|
||||||
@@ -160,37 +148,23 @@ export class WS extends Transport {
|
|||||||
} else {
|
} else {
|
||||||
this.ws.send(data, opts)
|
this.ws.send(data, opts)
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (e) {
|
||||||
debug("websocket closed before onclose event")
|
debug("websocket closed before onclose event")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastPacket) {
|
if (lastPacket) {
|
||||||
// fake drain
|
// fake drain
|
||||||
// defer to next tick to allow Socket to clear writeBuffer
|
// defer to next tick to allow Socket to clear writeBuffer
|
||||||
process.nextTick(() => {
|
nextTick(() => {
|
||||||
this.writable = true
|
this.writable = true
|
||||||
this.emit("drain")
|
this.emitReserved("drain")
|
||||||
}, this.setTimeoutFn)
|
}, this.setTimeoutFn)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override doClose() {
|
||||||
* Called upon close
|
|
||||||
*
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
onClose() {
|
|
||||||
Transport.prototype.onClose.call(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes socket.
|
|
||||||
*
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
doClose() {
|
|
||||||
if (typeof this.ws !== "undefined") {
|
if (typeof this.ws !== "undefined") {
|
||||||
this.ws.close()
|
this.ws.close()
|
||||||
this.ws = null
|
this.ws = null
|
||||||
@@ -200,10 +174,10 @@ export class WS extends Transport {
|
|||||||
/**
|
/**
|
||||||
* Generates uri for connection.
|
* Generates uri for connection.
|
||||||
*
|
*
|
||||||
* @api private
|
* @private
|
||||||
*/
|
*/
|
||||||
uri() {
|
uri() {
|
||||||
let query = this.query || {}
|
let query: { b64?: number } = this.query || {}
|
||||||
const schema = this.opts.secure ? "wss" : "ws"
|
const schema = this.opts.secure ? "wss" : "ws"
|
||||||
let port = ""
|
let port = ""
|
||||||
|
|
||||||
@@ -226,21 +200,16 @@ export class WS extends Transport {
|
|||||||
query.b64 = 1
|
query.b64 = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
query = parseqs.encode(query)
|
const encodedQuery = encode(query)
|
||||||
|
|
||||||
// prepend ? to query
|
|
||||||
if (query.length) {
|
|
||||||
query = "?" + query
|
|
||||||
}
|
|
||||||
|
|
||||||
const ipv6 = this.opts.hostname.indexOf(":") !== -1
|
const ipv6 = this.opts.hostname.indexOf(":") !== -1
|
||||||
|
|
||||||
return (
|
return (
|
||||||
schema +
|
schema +
|
||||||
"://" +
|
"://" +
|
||||||
(ipv6 ? "[" + this.opts.hostname + "]" : this.opts.hostname) +
|
(ipv6 ? "[" + this.opts.hostname + "]" : this.opts.hostname) +
|
||||||
port +
|
port +
|
||||||
this.opts.path +
|
this.opts.path +
|
||||||
query
|
(encodedQuery.length ? "?" + encodedQuery : "")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,12 +217,9 @@ export class WS extends Transport {
|
|||||||
* Feature detection for WebSocket.
|
* Feature detection for WebSocket.
|
||||||
*
|
*
|
||||||
* @return {Boolean} whether this transport is available.
|
* @return {Boolean} whether this transport is available.
|
||||||
* @api public
|
* @private
|
||||||
*/
|
*/
|
||||||
check() {
|
private check() {
|
||||||
return (
|
return !!WebSocket
|
||||||
!!WebSocket &&
|
|
||||||
!("__initialize" in WebSocket && this.name === WS.prototype.name)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) => {
|
return attr.reduce((acc, k) => {
|
||||||
if (obj.hasOwnProperty(k)) {
|
if (obj.hasOwnProperty(k)) {
|
||||||
acc[k] = obj[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
|
// Keep a reference to the real timeout functions so they can be used when overridden
|
||||||
const NATIVE_SET_TIMEOUT = setTimeout
|
const NATIVE_SET_TIMEOUT = globalThis.setTimeout
|
||||||
const NATIVE_CLEAR_TIMEOUT = clearTimeout
|
const NATIVE_CLEAR_TIMEOUT = globalThis.clearTimeout
|
||||||
|
|
||||||
const installTimerFunctions = (obj, opts) => {
|
export function installTimerFunctions(obj, opts) {
|
||||||
if (opts.useNativeTimers) {
|
if (opts.useNativeTimers) {
|
||||||
obj.setTimeoutFn = NATIVE_SET_TIMEOUT.bind(globalThis)
|
obj.setTimeoutFn = NATIVE_SET_TIMEOUT.bind(globalThis)
|
||||||
obj.clearTimeoutFn = NATIVE_CLEAR_TIMEOUT.bind(globalThis)
|
obj.clearTimeoutFn = NATIVE_CLEAR_TIMEOUT.bind(globalThis)
|
||||||
} else {
|
} else {
|
||||||
obj.setTimeoutFn = setTimeout.bind(globalThis)
|
obj.setTimeoutFn = globalThis.setTimeout.bind(globalThis)
|
||||||
obj.clearTimeoutFn = clearTimeout.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
|
const PACKET_TYPES = Object.create(null); // no Map = no polyfill
|
||||||
PACKET_TYPES["open"] = "0"
|
PACKET_TYPES["open"] = "0";
|
||||||
PACKET_TYPES["close"] = "1"
|
PACKET_TYPES["close"] = "1";
|
||||||
PACKET_TYPES["ping"] = "2"
|
PACKET_TYPES["ping"] = "2";
|
||||||
PACKET_TYPES["pong"] = "3"
|
PACKET_TYPES["pong"] = "3";
|
||||||
PACKET_TYPES["message"] = "4"
|
PACKET_TYPES["message"] = "4";
|
||||||
PACKET_TYPES["upgrade"] = "5"
|
PACKET_TYPES["upgrade"] = "5";
|
||||||
PACKET_TYPES["noop"] = "6"
|
PACKET_TYPES["noop"] = "6";
|
||||||
|
|
||||||
const PACKET_TYPES_REVERSE = Object.create(null)
|
const PACKET_TYPES_REVERSE = Object.create(null);
|
||||||
Object.keys(PACKET_TYPES).forEach(key => {
|
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 = {
|
export { PACKET_TYPES, PACKET_TYPES_REVERSE, ERROR_PACKET };
|
||||||
PACKET_TYPES,
|
|
||||||
PACKET_TYPES_REVERSE,
|
export type PacketType =
|
||||||
ERROR_PACKET
|
| "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) => {
|
const decodePacket = (
|
||||||
if (typeof encodedPacket !== "string") {
|
encodedPacket: RawData,
|
||||||
return {
|
binaryType?: BinaryType
|
||||||
type: "message",
|
): Packet => {
|
||||||
data: mapBinary(encodedPacket, binaryType)
|
if (typeof encodedPacket !== "string") {
|
||||||
}
|
return {
|
||||||
}
|
type: "message",
|
||||||
const type = encodedPacket.charAt(0)
|
data: mapBinary(encodedPacket, binaryType)
|
||||||
if (type === "b") {
|
};
|
||||||
const buffer = Buffer.from(encodedPacket.substring(1), "base64")
|
}
|
||||||
return {
|
const type = encodedPacket.charAt(0);
|
||||||
type: "message",
|
if (type === "b") {
|
||||||
data: mapBinary(buffer, binaryType)
|
const buffer = Buffer.from(encodedPacket.substring(1), "base64");
|
||||||
}
|
return {
|
||||||
}
|
type: "message",
|
||||||
if (!PACKET_TYPES_REVERSE[type]) {
|
data: mapBinary(buffer, binaryType)
|
||||||
return ERROR_PACKET
|
};
|
||||||
}
|
}
|
||||||
return encodedPacket.length > 1
|
if (!PACKET_TYPES_REVERSE[type]) {
|
||||||
? {
|
return ERROR_PACKET;
|
||||||
type: PACKET_TYPES_REVERSE[type],
|
}
|
||||||
data: encodedPacket.substring(1)
|
return encodedPacket.length > 1
|
||||||
}
|
? {
|
||||||
: {
|
type: PACKET_TYPES_REVERSE[type],
|
||||||
type: PACKET_TYPES_REVERSE[type]
|
data: encodedPacket.substring(1)
|
||||||
}
|
}
|
||||||
}
|
: {
|
||||||
|
type: PACKET_TYPES_REVERSE[type]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const mapBinary = (data, binaryType) => {
|
const mapBinary = (data: RawData, binaryType?: BinaryType) => {
|
||||||
switch (binaryType) {
|
const isBuffer = Buffer.isBuffer(data);
|
||||||
case "arraybuffer":
|
switch (binaryType) {
|
||||||
return Buffer.isBuffer(data) ? toArrayBuffer(data) : data
|
case "arraybuffer":
|
||||||
case "nodebuffer":
|
return isBuffer ? toArrayBuffer(data) : data;
|
||||||
default:
|
case "nodebuffer":
|
||||||
return data // assuming the data is already a Buffer
|
default:
|
||||||
}
|
return data; // assuming the data is already a Buffer
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const toArrayBuffer = buffer => {
|
const toArrayBuffer = (buffer: Buffer): ArrayBuffer => {
|
||||||
const arrayBuffer = new ArrayBuffer(buffer.length)
|
const arrayBuffer = new ArrayBuffer(buffer.length);
|
||||||
const view = new Uint8Array(arrayBuffer)
|
const view = new Uint8Array(arrayBuffer);
|
||||||
for (let i = 0; i < buffer.length; i++) {
|
for (let i = 0; i < buffer.length; i++) {
|
||||||
view[i] = buffer[i]
|
view[i] = buffer[i];
|
||||||
}
|
}
|
||||||
return arrayBuffer
|
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) => {
|
const encodePacket = (
|
||||||
console.trace('encodePacket', type, JSON.stringify(data))
|
{ type, data }: Packet,
|
||||||
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
|
supportsBinary: boolean,
|
||||||
const buffer = toBuffer(data)
|
callback: (encodedPacket: RawData) => void
|
||||||
return callback(encodeBuffer(buffer, supportsBinary))
|
) => {
|
||||||
}
|
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
|
||||||
// plain string
|
const buffer = toBuffer(data);
|
||||||
return callback(PACKET_TYPES[type] + (data || ""))
|
return callback(encodeBuffer(buffer, supportsBinary));
|
||||||
}
|
}
|
||||||
|
// plain string
|
||||||
|
return callback(PACKET_TYPES[type] + (data || ""));
|
||||||
|
};
|
||||||
|
|
||||||
const toBuffer = data => {
|
const toBuffer = data => {
|
||||||
if (Buffer.isBuffer(data)) {
|
if (Buffer.isBuffer(data)) {
|
||||||
return data
|
return data;
|
||||||
} else if (data instanceof ArrayBuffer) {
|
} else if (data instanceof ArrayBuffer) {
|
||||||
return Buffer.from(data)
|
return Buffer.from(data);
|
||||||
} else {
|
} else {
|
||||||
return Buffer.from(data.buffer, data.byteOffset, data.byteLength)
|
return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// only 'message' packets can contain binary, so the type prefix is not needed
|
// only 'message' packets can contain binary, so the type prefix is not needed
|
||||||
const encodeBuffer = (data, supportsBinary) => {
|
const encodeBuffer = (data: Buffer, supportsBinary: boolean): RawData => {
|
||||||
return supportsBinary ? data : "b" + data.toString("base64")
|
return supportsBinary ? data : "b" + data.toString("base64");
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default encodePacket;
|
||||||
|
|||||||
@@ -1,42 +1,53 @@
|
|||||||
import { encodePacket } from "./encodePacket"
|
import encodePacket from "./encodePacket.js";
|
||||||
import { decodePacket } from "./decodePacket"
|
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) => {
|
const encodePayload = (
|
||||||
// some packets may be added to the array while encoding, so the initial length must be saved
|
packets: Packet[],
|
||||||
const length = packets.length
|
callback: (encodedPayload: string) => void
|
||||||
const encodedPackets = new Array(length)
|
) => {
|
||||||
let count = 0
|
// 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) => {
|
packets.forEach((packet, i) => {
|
||||||
// force base64 encoding for binary packets
|
// force base64 encoding for binary packets
|
||||||
encodePacket(packet, false, encodedPacket => {
|
encodePacket(packet, false, encodedPacket => {
|
||||||
encodedPackets[i] = encodedPacket
|
encodedPackets[i] = encodedPacket;
|
||||||
if (++count === length) {
|
if (++count === length) {
|
||||||
callback(encodedPackets.join(SEPARATOR))
|
callback(encodedPackets.join(SEPARATOR));
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const decodePayload = (encodedPayload, binaryType) => {
|
const decodePayload = (
|
||||||
const encodedPackets = encodedPayload.split(SEPARATOR)
|
encodedPayload: string,
|
||||||
const packets = []
|
binaryType?: BinaryType
|
||||||
for (let i = 0; i < encodedPackets.length; i++) {
|
): Packet[] => {
|
||||||
const decodedPacket = decodePacket(encodedPackets[i], binaryType)
|
const encodedPackets = encodedPayload.split(SEPARATOR);
|
||||||
packets.push(decodedPacket)
|
const packets = [];
|
||||||
if (decodedPacket.type === "error") {
|
for (let i = 0; i < encodedPackets.length; i++) {
|
||||||
break
|
const decodedPacket = decodePacket(encodedPackets[i], binaryType);
|
||||||
}
|
packets.push(decodedPacket);
|
||||||
|
if (decodedPacket.type === "error") {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return packets
|
}
|
||||||
}
|
return packets;
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export const protocol = 4;
|
||||||
protocol: 4,
|
export {
|
||||||
encodePacket,
|
encodePacket,
|
||||||
encodePayload,
|
encodePayload,
|
||||||
decodePacket,
|
decodePacket,
|
||||||
decodePayload
|
decodePayload,
|
||||||
}
|
Packet,
|
||||||
|
PacketType,
|
||||||
|
RawData,
|
||||||
|
BinaryType
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,10 +1,45 @@
|
|||||||
/**
|
// import { createServer } from "http"
|
||||||
* Module dependencies.
|
import { Server, AttachOptions, ServerOptions } from "./server";
|
||||||
*/
|
import transports from "./transports/index";
|
||||||
|
import * as parser from "../engine.io-parser";
|
||||||
|
|
||||||
// const http = require("http")
|
// export { Server, transports, listen, attach, parser }
|
||||||
// const Server = require("./server")
|
export { Server, transports, attach, parser };
|
||||||
import { Server } from './server'
|
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.
|
* Captures upgrade requests for a http.Server.
|
||||||
@@ -15,12 +50,8 @@ import { Server } from './server'
|
|||||||
* @api public
|
* @api public
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function attach(srv, options) {
|
function attach(server, options: AttachOptions & ServerOptions) {
|
||||||
const engine = new Server(options)
|
const engine = new Server(options);
|
||||||
engine.attach(srv, options)
|
engine.attach(server, options);
|
||||||
return engine
|
return engine;
|
||||||
}
|
|
||||||
|
|
||||||
export = {
|
|
||||||
attach
|
|
||||||
}
|
}
|
||||||
|
|||||||
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 { EventEmitter } from "events";
|
||||||
import parser_v4 from "../engine.io-parser"
|
import * as parser_v4 from "../engine.io-parser";
|
||||||
import type { WebSocketClient } from '../server/client'
|
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.
|
* Noop function.
|
||||||
*
|
*
|
||||||
* @api private
|
* @api private
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function noop() { }
|
function noop() {}
|
||||||
|
|
||||||
export abstract class Transport extends EventEmitter {
|
export abstract class Transport extends EventEmitter {
|
||||||
public sid: string
|
public sid: string;
|
||||||
public req /**http.IncomingMessage */
|
public writable: boolean;
|
||||||
public socket: WebSocketClient
|
public protocol: number;
|
||||||
public writable: boolean
|
|
||||||
public readyState: string
|
|
||||||
public discarded: boolean
|
|
||||||
public protocol: Number
|
|
||||||
public parser: any
|
|
||||||
public perMessageDeflate: any
|
|
||||||
public supportsBinary: boolean = false
|
|
||||||
|
|
||||||
/**
|
protected _readyState: string;
|
||||||
* Transport constructor.
|
protected discarded: boolean;
|
||||||
*
|
protected parser: any;
|
||||||
* @param {http.IncomingMessage} request
|
// protected req: IncomingMessage & { cleanup: Function }
|
||||||
* @api public
|
protected req: { cleanup: Function };
|
||||||
*/
|
protected supportsBinary: boolean;
|
||||||
constructor(req) {
|
|
||||||
super()
|
get readyState() {
|
||||||
this.readyState = "open"
|
return this._readyState;
|
||||||
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
|
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.
|
* Called with parsed out a packets from the data stream.
|
||||||
*
|
*
|
||||||
* @api private
|
* @param {Object} packet
|
||||||
*/
|
* @api protected
|
||||||
discard() {
|
*/
|
||||||
this.discarded = true
|
protected onPacket(packet: Packet) {
|
||||||
}
|
this.emit("packet", packet);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called with an incoming HTTP request.
|
* Called with the encoded packet data.
|
||||||
*
|
*
|
||||||
* @param {http.IncomingMessage} request
|
* @param {String} data
|
||||||
* @api private
|
* @api protected
|
||||||
*/
|
*/
|
||||||
onRequest(req) {
|
protected onData(data) {
|
||||||
console.debug(`engine.io transport ${this.socket.id} setting request`, JSON.stringify(req))
|
this.onPacket(this.parser.decodePacket(data));
|
||||||
this.req = req
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the transport.
|
* Called upon transport close.
|
||||||
*
|
*
|
||||||
* @api private
|
* @api protected
|
||||||
*/
|
*/
|
||||||
close(fn?) {
|
protected onClose() {
|
||||||
if ("closed" === this.readyState || "closing" === this.readyState) return
|
this.readyState = "closed";
|
||||||
|
this.emit("close");
|
||||||
|
}
|
||||||
|
|
||||||
this.readyState = "closing"
|
abstract get supportsFraming();
|
||||||
this.doClose(fn || noop)
|
abstract get name();
|
||||||
}
|
abstract send(packets);
|
||||||
|
abstract doClose(fn?);
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,24 @@
|
|||||||
|
// import { Polling as XHR } from "./polling"
|
||||||
|
// import { JSONP } from "./polling-jsonp"
|
||||||
|
import { WebSocket } from "./websocket";
|
||||||
|
|
||||||
export default {
|
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'
|
import { Transport } from "../transport";
|
||||||
// const debug = require("debug")("engine:ws")
|
// import debugModule from "debug";
|
||||||
|
|
||||||
|
const debug = require("../../debug")("engine:ws");
|
||||||
|
|
||||||
export class WebSocket extends Transport {
|
export class WebSocket extends Transport {
|
||||||
public perMessageDeflate: any
|
protected perMessageDeflate: any;
|
||||||
|
private socket: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebSocket transport
|
* WebSocket transport
|
||||||
*
|
*
|
||||||
* @param {http.IncomingMessage}
|
* @param {http.IncomingMessage}
|
||||||
* @api public
|
* @api public
|
||||||
*/
|
*/
|
||||||
constructor(req) {
|
constructor(req) {
|
||||||
super(req)
|
super(req);
|
||||||
this.socket = req.websocket
|
this.socket = req.websocket;
|
||||||
this.socket.on("message", this.onData.bind(this))
|
this.socket.on("message", (data, isBinary) => {
|
||||||
this.socket.once("close", this.onClose.bind(this))
|
const message = isBinary ? data : data.toString();
|
||||||
this.socket.on("error", this.onError.bind(this))
|
debug('received "%s"', message);
|
||||||
this.writable = true
|
super.onData(message);
|
||||||
this.perMessageDeflate = null
|
});
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// always creates a new object since ws modifies it
|
||||||
* Transport name
|
const opts: { compress?: boolean } = {};
|
||||||
*
|
if (packet.options) {
|
||||||
* @api public
|
opts.compress = packet.options.compress;
|
||||||
*/
|
|
||||||
get name() {
|
|
||||||
return "websocket"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const send = data => {
|
||||||
* Advertise upgrade support.
|
if (this.perMessageDeflate) {
|
||||||
*
|
const len =
|
||||||
* @api public
|
"string" === typeof data ? Buffer.byteLength(data) : data.length;
|
||||||
*/
|
if (len < this.perMessageDeflate.threshold) {
|
||||||
get handlesUpgrades() {
|
opts.compress = false;
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
debug('writing "%s"', data);
|
||||||
|
this.writable = false;
|
||||||
|
|
||||||
// always creates a new object since ws modifies it
|
this.socket.send(data, opts, err => {
|
||||||
const opts: any = {}
|
if (err) return this.onError("write error", err.stack);
|
||||||
if (packet.options) {
|
this.send(packets);
|
||||||
opts.compress = packet.options.compress
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const send = data => {
|
if (packet.options && typeof packet.options.wsPreEncoded === "string") {
|
||||||
if (this.perMessageDeflate) {
|
send(packet.options.wsPreEncoded);
|
||||||
const len =
|
} else {
|
||||||
"string" === typeof data ? Buffer.byteLength(data) : data.length
|
this.parser.encodePacket(packet, this.supportsBinary, send);
|
||||||
if (len < this.perMessageDeflate.threshold) {
|
|
||||||
opts.compress = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.trace('writing', data)
|
|
||||||
this.writable = false
|
|
||||||
|
|
||||||
this.socket.send(data, opts, err => {
|
|
||||||
if (err) return this.onError("write error", err.stack)
|
|
||||||
this.send(packets)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet.options && typeof packet.options.wsPreEncoded === "string") {
|
|
||||||
send(packet.options.wsPreEncoded)
|
|
||||||
} else {
|
|
||||||
this.parser.encodePacket(packet, this.supportsBinary, send)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the transport.
|
* Closes the transport.
|
||||||
*
|
*
|
||||||
* @api private
|
* @api private
|
||||||
*/
|
*/
|
||||||
doClose(fn) {
|
doClose(fn) {
|
||||||
// debug("closing")
|
debug("closing");
|
||||||
this.socket.close()
|
this.socket.close();
|
||||||
fn && fn()
|
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
|
// const canPreComputeFrame = typeof WebSocket?.Sender?.frame === "function";
|
||||||
export type Room = string
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
export interface BroadcastFlags {
|
||||||
volatile?: boolean
|
volatile?: boolean;
|
||||||
compress?: boolean
|
compress?: boolean;
|
||||||
local?: boolean
|
local?: boolean;
|
||||||
broadcast?: boolean
|
broadcast?: boolean;
|
||||||
binary?: boolean
|
binary?: boolean;
|
||||||
|
timeout?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BroadcastOptions {
|
export interface BroadcastOptions {
|
||||||
rooms: Set<Room>
|
rooms: Set<Room>;
|
||||||
except?: Set<SocketId>
|
except?: Set<Room>;
|
||||||
flags?: BroadcastFlags
|
flags?: BroadcastFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SessionToPersist {
|
||||||
|
sid: SocketId;
|
||||||
|
pid: PrivateSessionId;
|
||||||
|
rooms: Room[];
|
||||||
|
data: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Session = SessionToPersist & { missedPackets: unknown[][] };
|
||||||
|
|
||||||
export class Adapter extends EventEmitter {
|
export class Adapter extends EventEmitter {
|
||||||
public rooms: Map<Room, Set<SocketId>> = new Map();
|
public rooms: Map<Room, Set<SocketId>> = new Map();
|
||||||
public sids: Map<SocketId, Set<Room>> = new Map();
|
public sids: Map<SocketId, Set<Room>> = new Map();
|
||||||
private readonly encoder
|
private readonly encoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In-memory adapter constructor.
|
* In-memory adapter constructor.
|
||||||
*
|
*
|
||||||
* @param {Namespace} nsp
|
* @param {Namespace} nsp
|
||||||
*/
|
*/
|
||||||
constructor(readonly nsp: any) {
|
constructor(readonly nsp: any) {
|
||||||
super()
|
super();
|
||||||
this.encoder = nsp.server.encoder
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
for (const room of rooms) {
|
||||||
* To be overridden
|
this.sids.get(id).add(room);
|
||||||
*/
|
|
||||||
public init(): Promise<void> | void { }
|
|
||||||
|
|
||||||
/**
|
if (!this.rooms.has(room)) {
|
||||||
* To be overridden
|
this.rooms.set(room, new Set());
|
||||||
*/
|
this.emit("create-room", room);
|
||||||
public close(): Promise<void> | void { }
|
}
|
||||||
|
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.
|
* Removes a socket from a room.
|
||||||
*
|
*
|
||||||
* @param {SocketId} id the socket id
|
* @param {SocketId} id the socket id
|
||||||
* @param {Set<Room>} rooms a set of rooms
|
* @param {Room} room the room name
|
||||||
* @public
|
*/
|
||||||
*/
|
public del(id: SocketId, room: Room): Promise<void> | void {
|
||||||
public addAll(id: SocketId, rooms: Set<Room>): Promise<void> | void {
|
if (this.sids.has(id)) {
|
||||||
if (!this.sids.has(id)) {
|
this.sids.get(id).delete(room);
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
this._del(room, id);
|
||||||
* Removes a socket from a room.
|
}
|
||||||
*
|
|
||||||
* @param {SocketId} id the socket id
|
|
||||||
* @param {Room} room the room name
|
|
||||||
*/
|
|
||||||
public del(id: SocketId, room: Room): Promise<void> | void {
|
|
||||||
if (this.sids.has(id)) {
|
|
||||||
this.sids.get(id).delete(room)
|
|
||||||
}
|
|
||||||
|
|
||||||
this._del(room, id)
|
private _del(room: 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)) {
|
if (this.rooms.has(room)) {
|
||||||
const deleted = this.rooms.get(room).delete(id)
|
this.rooms.get(room).forEach((sid) => exceptSids.add(sid));
|
||||||
if (deleted) {
|
|
||||||
this.emit("leave-room", room, id)
|
|
||||||
}
|
|
||||||
if (this.rooms.get(room).size === 0) {
|
|
||||||
this.rooms.delete(room)
|
|
||||||
this.emit("delete-room", room)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return exceptSids;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a socket from all rooms it's joined.
|
* 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
|
||||||
* @param {SocketId} id the socket id
|
*/
|
||||||
*/
|
public serverSideEmit(packet: any[]): void {
|
||||||
public delAll(id: SocketId): void {
|
console.warn(
|
||||||
if (!this.sids.has(id)) {
|
"this adapter does not support the serverSideEmit() functionality"
|
||||||
return
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
/**
|
||||||
}
|
* Restore the session and find the packets that were missed by the client.
|
||||||
|
* @param pid
|
||||||
/**
|
* @param offset
|
||||||
* Broadcasts a packet.
|
*/
|
||||||
*
|
public restoreSession(
|
||||||
* Options:
|
pid: PrivateSessionId,
|
||||||
* - `flags` {Object} flags for this packet
|
offset: string
|
||||||
* - `except` {Array} sids that should be excluded
|
): Session {
|
||||||
* - `rooms` {Array} list of rooms to broadcast to
|
return null;
|
||||||
*
|
}
|
||||||
* @param {Object} packet the packet object
|
}
|
||||||
* @param {Object} opts the options
|
|
||||||
* @public
|
interface PersistedPacket {
|
||||||
*/
|
id: string;
|
||||||
public broadcast(packet: any, opts: BroadcastOptions): void {
|
emittedAt: number;
|
||||||
const flags = opts.flags || {}
|
data: unknown[];
|
||||||
const basePacketOpts = {
|
opts: BroadcastOptions;
|
||||||
preEncoded: true,
|
}
|
||||||
volatile: flags.volatile,
|
|
||||||
compress: flags.compress
|
type SessionWithTimestamp = SessionToPersist & { disconnectedAt: number };
|
||||||
}
|
|
||||||
|
export class SessionAwareAdapter extends Adapter {
|
||||||
packet.nsp = this.nsp.name
|
private readonly maxDisconnectionDuration: number;
|
||||||
const encodedPackets = this.encoder.encode(packet)
|
|
||||||
|
private sessions: Map<PrivateSessionId, SessionWithTimestamp> = new Map();
|
||||||
const packetOpts = encodedPackets.map(encodedPacket => {
|
private packets: PersistedPacket[] = [];
|
||||||
if (typeof encodedPacket === "string") {
|
|
||||||
return {
|
constructor(readonly nsp: any) {
|
||||||
...basePacketOpts,
|
super(nsp);
|
||||||
wsPreEncoded: "4" + encodedPacket // "4" being the "message" packet type in Engine.IO
|
this.maxDisconnectionDuration =
|
||||||
}
|
nsp.server.opts.connectionStateRecovery.maxDisconnectionDuration;
|
||||||
} else {
|
|
||||||
return basePacketOpts
|
const timer = setInterval(() => {
|
||||||
}
|
const threshold = Date.now() - this.maxDisconnectionDuration;
|
||||||
})
|
this.sessions.forEach((session, sessionId) => {
|
||||||
|
const hasExpired = session.disconnectedAt < threshold;
|
||||||
this.apply(opts, socket => {
|
if (hasExpired) {
|
||||||
for (let i = 0; i < encodedPackets.length; i++) {
|
this.sessions.delete(sessionId);
|
||||||
socket.client.writeToEngine(encodedPackets[i], packetOpts[i])
|
}
|
||||||
}
|
});
|
||||||
})
|
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);
|
||||||
* Gets a list of sockets by sid.
|
break;
|
||||||
*
|
}
|
||||||
* @param {Set<Room>} rooms the explicit set of rooms to check.
|
}
|
||||||
*/
|
}, 60 * 1000);
|
||||||
public sockets(rooms: Set<Room>): Promise<Set<SocketId>> {
|
// prevents the timer from keeping the process alive
|
||||||
const sids = new Set<SocketId>()
|
timer.unref();
|
||||||
|
}
|
||||||
this.apply({ rooms }, socket => {
|
|
||||||
sids.add(socket.id)
|
override persistSession(session: SessionToPersist) {
|
||||||
})
|
(session as SessionWithTimestamp).disconnectedAt = Date.now();
|
||||||
|
this.sessions.set(session.pid, session as SessionWithTimestamp);
|
||||||
return Promise.resolve(sids)
|
}
|
||||||
}
|
|
||||||
|
override restoreSession(
|
||||||
/**
|
pid: PrivateSessionId,
|
||||||
* Gets the list of rooms a given socket has joined.
|
offset: string
|
||||||
*
|
): Session {
|
||||||
* @param {SocketId} id the socket id
|
const session = this.sessions.get(pid);
|
||||||
*/
|
if (!session) {
|
||||||
public socketRooms(id: SocketId): Set<Room> | undefined {
|
// the session may have expired
|
||||||
return this.sids.get(id)
|
return null;
|
||||||
}
|
}
|
||||||
|
const hasExpired =
|
||||||
/**
|
session.disconnectedAt + this.maxDisconnectionDuration < Date.now();
|
||||||
* Returns the matching socket instances
|
if (hasExpired) {
|
||||||
*
|
// the session has expired
|
||||||
* @param opts - the filters to apply
|
this.sessions.delete(pid);
|
||||||
*/
|
return null;
|
||||||
public fetchSockets(opts: BroadcastOptions): Promise<any[]> {
|
}
|
||||||
const sockets = []
|
const index = this.packets.findIndex((packet) => packet.id === offset);
|
||||||
|
if (index === -1) {
|
||||||
this.apply(opts, socket => {
|
// the offset may be too old
|
||||||
sockets.push(socket)
|
return null;
|
||||||
})
|
}
|
||||||
|
const missedPackets = [];
|
||||||
return Promise.resolve(sockets)
|
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);
|
||||||
* Makes the matching socket instances join the specified rooms
|
}
|
||||||
*
|
}
|
||||||
* @param opts - the filters to apply
|
return {
|
||||||
* @param rooms - the rooms to join
|
...session,
|
||||||
*/
|
missedPackets,
|
||||||
public addSockets(opts: BroadcastOptions, rooms: Room[]): void {
|
};
|
||||||
this.apply(opts, socket => {
|
}
|
||||||
socket.join(rooms)
|
|
||||||
})
|
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
|
||||||
* Makes the matching socket instances leave the specified rooms
|
const withoutAcknowledgement = packet.id === undefined;
|
||||||
*
|
const notVolatile = opts.flags?.volatile === undefined;
|
||||||
* @param opts - the filters to apply
|
if (isEventPacket && withoutAcknowledgement && notVolatile) {
|
||||||
* @param rooms - the rooms to leave
|
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
|
||||||
public delSockets(opts: BroadcastOptions, rooms: Room[]): void {
|
// processed (and the format is backward-compatible)
|
||||||
this.apply(opts, socket => {
|
packet.data.push(id);
|
||||||
rooms.forEach(room => socket.leave(room))
|
this.packets.push({
|
||||||
})
|
id,
|
||||||
}
|
opts,
|
||||||
|
data: packet.data,
|
||||||
/**
|
emittedAt: Date.now(),
|
||||||
* Makes the matching socket instances disconnect
|
});
|
||||||
*
|
}
|
||||||
* @param opts - the filters to apply
|
super.broadcast(packet, opts);
|
||||||
* @param close - whether to close the underlying connection
|
}
|
||||||
*/
|
}
|
||||||
public disconnectSockets(opts: BroadcastOptions, close: boolean): void {
|
|
||||||
this.apply(opts, socket => {
|
function shouldIncludePacket(
|
||||||
socket.disconnect(close)
|
sessionRooms: Room[],
|
||||||
})
|
opts: BroadcastOptions
|
||||||
}
|
): boolean {
|
||||||
|
const included =
|
||||||
private apply(opts: BroadcastOptions, callback: (socket) => void): void {
|
opts.rooms.size === 0 || sessionRooms.some((room) => opts.rooms.has(room));
|
||||||
const rooms = opts.rooms
|
const notExcluded = sessionRooms.every((room) => !opts.except.has(room));
|
||||||
const except = this.computeExceptSids(opts.except)
|
return included && notExcluded;
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
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 { url } from "./url"
|
||||||
import { Manager, ManagerOptions } from "./manager"
|
import { Manager, ManagerOptions } from "./manager"
|
||||||
import { Socket, SocketOptions } from "./socket"
|
import { Socket, SocketOptions } from "./socket"
|
||||||
|
// import debugModule from "debug"; // debug()
|
||||||
|
|
||||||
const debug = require("../debug")("socket.io-client")
|
const debug = require("../debug")("socket.io-client")
|
||||||
|
|
||||||
/**
|
|
||||||
* Module exports.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = exports = lookup
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Managers cache.
|
* Managers cache.
|
||||||
*/
|
*/
|
||||||
const cache: Record<string, Manager> = (exports.managers = {})
|
const cache: Record<string, Manager> = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Looks up an existing `Manager` for multiplexing.
|
* Looks up an existing `Manager` for multiplexing.
|
||||||
@@ -76,6 +71,15 @@ function lookup(
|
|||||||
return io.socket(parsed.path, opts)
|
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.
|
* Protocol version.
|
||||||
*
|
*
|
||||||
@@ -84,22 +88,18 @@ function lookup(
|
|||||||
|
|
||||||
export { protocol } from "../socket.io-parser"
|
export { protocol } from "../socket.io-parser"
|
||||||
|
|
||||||
/**
|
|
||||||
* `connect`.
|
|
||||||
*
|
|
||||||
* @param {String} uri
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.connect = lookup
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose constructors for standalone build.
|
* Expose constructors for standalone build.
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { Manager, ManagerOptions } from "./manager"
|
export {
|
||||||
export { Socket } from "./socket"
|
Manager,
|
||||||
export { lookup as io, SocketOptions }
|
ManagerOptions,
|
||||||
export default lookup
|
Socket,
|
||||||
|
SocketOptions,
|
||||||
|
lookup as io,
|
||||||
|
lookup as connect,
|
||||||
|
lookup as default,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,210 +1,26 @@
|
|||||||
import eio from "../engine.io-client"
|
import {
|
||||||
import { Socket, SocketOptions } from "./socket"
|
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 * as parser from "../socket.io-parser"
|
||||||
|
// import { Decoder, Encoder, Packet } 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 { on } from "./on.js"
|
||||||
import * as Backoff from "backo2"
|
import { Backoff } from "./contrib/backo2.js"
|
||||||
import {
|
import {
|
||||||
DefaultEventsMap,
|
DefaultEventsMap,
|
||||||
EventsMap,
|
EventsMap,
|
||||||
StrictEventEmitter,
|
Emitter,
|
||||||
} from "./typed-events"
|
} 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")
|
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 {
|
export interface ManagerOptions extends EngineOptions {
|
||||||
/**
|
/**
|
||||||
* Should we force a new Manager for this connection?
|
* Should we force a new Manager for this connection?
|
||||||
@@ -267,13 +83,6 @@ export interface ManagerOptions extends EngineOptions {
|
|||||||
*/
|
*/
|
||||||
autoConnect: boolean
|
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.
|
* 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
|
error: (err: Error) => void
|
||||||
ping: () => void
|
ping: () => void
|
||||||
packet: (packet: Packet) => void
|
packet: (packet: Packet) => void
|
||||||
close: (reason: string) => void
|
close: (reason: string, description?: DisconnectDescription) => void
|
||||||
reconnect_failed: () => void
|
reconnect_failed: () => void
|
||||||
reconnect_attempt: (attempt: number) => void
|
reconnect_attempt: (attempt: number) => void
|
||||||
reconnect_error: (err: Error) => void
|
reconnect_error: (err: Error) => void
|
||||||
@@ -295,13 +104,13 @@ interface ManagerReservedEvents {
|
|||||||
export class Manager<
|
export class Manager<
|
||||||
ListenEvents extends EventsMap = DefaultEventsMap,
|
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||||
EmitEvents extends EventsMap = ListenEvents
|
EmitEvents extends EventsMap = ListenEvents
|
||||||
> extends StrictEventEmitter<{}, {}, ManagerReservedEvents> {
|
> extends Emitter<{}, {}, ManagerReservedEvents> {
|
||||||
/**
|
/**
|
||||||
* The Engine.IO client instance
|
* The Engine.IO client instance
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
public engine: any
|
public engine: Engine
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@@ -320,7 +129,9 @@ export class Manager<
|
|||||||
|
|
||||||
private nsps: Record<string, Socket> = {};
|
private nsps: Record<string, Socket> = {};
|
||||||
private subs: Array<ReturnType<typeof on>> = [];
|
private subs: Array<ReturnType<typeof on>> = [];
|
||||||
|
// @ts-ignore
|
||||||
private backoff: Backoff
|
private backoff: Backoff
|
||||||
|
private setTimeoutFn: typeof setTimeout
|
||||||
private _reconnection: boolean
|
private _reconnection: boolean
|
||||||
private _reconnectionAttempts: number
|
private _reconnectionAttempts: number
|
||||||
private _reconnectionDelay: number
|
private _reconnectionDelay: number
|
||||||
@@ -358,6 +169,7 @@ export class Manager<
|
|||||||
|
|
||||||
opts.path = opts.path || "/socket.io"
|
opts.path = opts.path || "/socket.io"
|
||||||
this.opts = opts
|
this.opts = opts
|
||||||
|
installTimerFunctions(this, opts)
|
||||||
this.reconnection(opts.reconnection !== false)
|
this.reconnection(opts.reconnection !== false)
|
||||||
this.reconnectionAttempts(opts.reconnectionAttempts || Infinity)
|
this.reconnectionAttempts(opts.reconnectionAttempts || Infinity)
|
||||||
this.reconnectionDelay(opts.reconnectionDelay || 1000)
|
this.reconnectionDelay(opts.reconnectionDelay || 1000)
|
||||||
@@ -507,8 +319,7 @@ export class Manager<
|
|||||||
if (~this._readyState.indexOf("open")) return this
|
if (~this._readyState.indexOf("open")) return this
|
||||||
|
|
||||||
debug("opening %s", this.uri)
|
debug("opening %s", this.uri)
|
||||||
// @ts-ignore
|
this.engine = new Engine(this.uri, this.opts)
|
||||||
this.engine = eio(this.uri, this.opts)
|
|
||||||
const socket = this.engine
|
const socket = this.engine
|
||||||
const self = this
|
const self = this
|
||||||
this._readyState = "opening"
|
this._readyState = "opening"
|
||||||
@@ -543,10 +354,11 @@ export class Manager<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set timer
|
// set timer
|
||||||
const timer = setTimeout(() => {
|
const timer = this.setTimeoutFn(() => {
|
||||||
debug("connect attempt timed out after %d", timeout)
|
debug("connect attempt timed out after %d", timeout)
|
||||||
openSubDestroy()
|
openSubDestroy()
|
||||||
socket.close()
|
socket.close()
|
||||||
|
// @ts-ignore
|
||||||
socket.emit("error", new Error("timeout"))
|
socket.emit("error", new Error("timeout"))
|
||||||
}, timeout)
|
}, timeout)
|
||||||
|
|
||||||
@@ -616,7 +428,11 @@ export class Manager<
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private ondata(data): void {
|
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
|
||||||
*/
|
*/
|
||||||
private ondecoded(packet): void {
|
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
|
this.nsps[nsp] = socket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._autoConnect) {
|
||||||
|
socket.connect()
|
||||||
|
}
|
||||||
|
|
||||||
return socket
|
return socket
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -713,13 +536,7 @@ export class Manager<
|
|||||||
debug("disconnect")
|
debug("disconnect")
|
||||||
this.skipReconnect = true
|
this.skipReconnect = true
|
||||||
this._reconnecting = false
|
this._reconnecting = false
|
||||||
if ("opening" === this._readyState) {
|
this.onclose("forced close")
|
||||||
// `onclose` will not fire because
|
|
||||||
// an open event never happened
|
|
||||||
this.cleanup()
|
|
||||||
}
|
|
||||||
this.backoff.reset()
|
|
||||||
this._readyState = "closed"
|
|
||||||
if (this.engine) this.engine.close()
|
if (this.engine) this.engine.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -737,13 +554,13 @@ export class Manager<
|
|||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private onclose(reason: string): void {
|
private onclose(reason: string, description?: DisconnectDescription): void {
|
||||||
debug("onclose")
|
debug("closed due to %s", reason)
|
||||||
|
|
||||||
this.cleanup()
|
this.cleanup()
|
||||||
this.backoff.reset()
|
this.backoff.reset()
|
||||||
this._readyState = "closed"
|
this._readyState = "closed"
|
||||||
this.emitReserved("close", reason)
|
this.emitReserved("close", reason, description)
|
||||||
|
|
||||||
if (this._reconnection && !this.skipReconnect) {
|
if (this._reconnection && !this.skipReconnect) {
|
||||||
this.reconnect()
|
this.reconnect()
|
||||||
@@ -770,7 +587,7 @@ export class Manager<
|
|||||||
debug("will wait %dms before reconnect attempt", delay)
|
debug("will wait %dms before reconnect attempt", delay)
|
||||||
|
|
||||||
this._reconnecting = true
|
this._reconnecting = true
|
||||||
const timer = setTimeout(() => {
|
const timer = this.setTimeoutFn(() => {
|
||||||
if (self.skipReconnect) return
|
if (self.skipReconnect) return
|
||||||
|
|
||||||
debug("attempting reconnect")
|
debug("attempting reconnect")
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
// import type * as Emitter from "component-emitter";
|
import { Emitter } from "@socket.io/component-emitter"
|
||||||
import { EventEmitter } from "events"
|
|
||||||
import { StrictEventEmitter } from "./typed-events"
|
|
||||||
|
|
||||||
export function on(
|
export function on(
|
||||||
obj: EventEmitter | StrictEventEmitter<any, any>,
|
obj: Emitter<any, any>,
|
||||||
ev: string,
|
ev: string,
|
||||||
fn: (err?: any) => any
|
fn: (err?: any) => any
|
||||||
): VoidFunction {
|
): VoidFunction {
|
||||||
|
|||||||
@@ -1,21 +1,82 @@
|
|||||||
|
// import { Packet, PacketType } from "socket.io-parser"
|
||||||
import { Packet, PacketType } from "../socket.io-parser"
|
import { Packet, PacketType } from "../socket.io-parser"
|
||||||
import { on } from "./on"
|
import { on } from "./on.js"
|
||||||
import { Manager } from "./manager"
|
import { Manager } from "./manager.js"
|
||||||
import {
|
import {
|
||||||
DefaultEventsMap,
|
DefaultEventsMap,
|
||||||
EventNames,
|
EventNames,
|
||||||
EventParams,
|
EventParams,
|
||||||
EventsMap,
|
EventsMap,
|
||||||
StrictEventEmitter,
|
Emitter,
|
||||||
} from "./typed-events"
|
} 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")
|
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 {
|
export interface SocketOptions {
|
||||||
/**
|
/**
|
||||||
* the authentication payload sent when connecting to the Namespace
|
* 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 {
|
interface Flags {
|
||||||
compress?: boolean
|
compress?: boolean
|
||||||
volatile?: 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 {
|
interface SocketReservedEvents {
|
||||||
connect: () => void
|
connect: () => void
|
||||||
connect_error: (err: Error) => 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<
|
export class Socket<
|
||||||
ListenEvents extends EventsMap = DefaultEventsMap,
|
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||||
EmitEvents extends EventsMap = ListenEvents
|
EmitEvents extends EventsMap = ListenEvents
|
||||||
> extends StrictEventEmitter<ListenEvents, EmitEvents, SocketReservedEvents> {
|
> extends Emitter<ListenEvents, EmitEvents, SocketReservedEvents> {
|
||||||
public readonly io: Manager<ListenEvents, EmitEvents>
|
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 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)
|
public auth: { [key: string]: any } | ((cb: (data: object) => void) => void)
|
||||||
|
/**
|
||||||
|
* Buffer for packets received before the CONNECT packet
|
||||||
|
*/
|
||||||
public receiveBuffer: Array<ReadonlyArray<any>> = [];
|
public receiveBuffer: Array<ReadonlyArray<any>> = [];
|
||||||
|
/**
|
||||||
|
* Buffer for packets that will be sent once the socket is connected
|
||||||
|
*/
|
||||||
public sendBuffer: Array<Packet> = [];
|
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 nsp: string
|
||||||
|
private readonly _opts: SocketOptions
|
||||||
|
|
||||||
private ids: number = 0;
|
private ids: number = 0;
|
||||||
private acks: object = {};
|
private acks: object = {};
|
||||||
private flags: Flags = {};
|
private flags: Flags = {};
|
||||||
private subs?: Array<VoidFunction>
|
private subs?: Array<VoidFunction>
|
||||||
private _anyListeners: Array<(...args: any[]) => void>
|
private _anyListeners: Array<(...args: any[]) => void>
|
||||||
|
private _anyOutgoingListeners: Array<(...args: any[]) => void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `Socket` constructor.
|
* `Socket` constructor.
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
*/
|
||||||
constructor(io: Manager, nsp: string, opts?: Partial<SocketOptions>) {
|
constructor(io: Manager, nsp: string, opts?: Partial<SocketOptions>) {
|
||||||
super()
|
super()
|
||||||
this.io = io
|
this.io = io
|
||||||
this.nsp = nsp
|
this.nsp = nsp
|
||||||
this.ids = 0
|
|
||||||
this.acks = {}
|
|
||||||
this.receiveBuffer = []
|
|
||||||
this.sendBuffer = []
|
|
||||||
this.connected = false
|
|
||||||
this.disconnected = true
|
|
||||||
this.flags = {}
|
|
||||||
if (opts && opts.auth) {
|
if (opts && opts.auth) {
|
||||||
this.auth = opts.auth
|
this.auth = opts.auth
|
||||||
}
|
}
|
||||||
|
this._opts = Object.assign({}, opts)
|
||||||
if (this.io._autoConnect) this.open()
|
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
|
* 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 {
|
public get active(): boolean {
|
||||||
return !!this.subs
|
return !!this.subs
|
||||||
@@ -114,7 +311,12 @@ export class Socket<
|
|||||||
/**
|
/**
|
||||||
* "Opens" the socket.
|
* "Opens" the socket.
|
||||||
*
|
*
|
||||||
* @public
|
* @example
|
||||||
|
* const socket = io({
|
||||||
|
* autoConnect: false
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* socket.connect();
|
||||||
*/
|
*/
|
||||||
public connect(): this {
|
public connect(): this {
|
||||||
if (this.connected) return this
|
if (this.connected) return this
|
||||||
@@ -126,7 +328,7 @@ export class Socket<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alias for connect()
|
* Alias for {@link connect()}.
|
||||||
*/
|
*/
|
||||||
public open(): this {
|
public open(): this {
|
||||||
return this.connect()
|
return this.connect()
|
||||||
@@ -135,8 +337,17 @@ export class Socket<
|
|||||||
/**
|
/**
|
||||||
* Sends a `message` event.
|
* 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
|
* @return self
|
||||||
* @public
|
|
||||||
*/
|
*/
|
||||||
public send(...args: any[]): this {
|
public send(...args: any[]): this {
|
||||||
args.unshift("message")
|
args.unshift("message")
|
||||||
@@ -149,18 +360,34 @@ export class Socket<
|
|||||||
* Override `emit`.
|
* Override `emit`.
|
||||||
* If the event is in `events`, it's emitted normally.
|
* 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
|
* @return self
|
||||||
* @public
|
|
||||||
*/
|
*/
|
||||||
public emit<Ev extends EventNames<EmitEvents>>(
|
public emit<Ev extends EventNames<EmitEvents>>(
|
||||||
ev: Ev,
|
ev: Ev,
|
||||||
...args: EventParams<EmitEvents, Ev>
|
...args: EventParams<EmitEvents, Ev>
|
||||||
): this {
|
): this {
|
||||||
if (RESERVED_EVENTS.hasOwnProperty(ev)) {
|
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)
|
args.unshift(ev)
|
||||||
|
|
||||||
|
if (this._opts.retries && !this.flags.fromQueue && !this.flags.volatile) {
|
||||||
|
this._addToQueue(args)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
const packet: any = {
|
const packet: any = {
|
||||||
type: PacketType.EVENT,
|
type: PacketType.EVENT,
|
||||||
data: args,
|
data: args,
|
||||||
@@ -171,9 +398,12 @@ export class Socket<
|
|||||||
|
|
||||||
// event ack callback
|
// event ack callback
|
||||||
if ("function" === typeof args[args.length - 1]) {
|
if ("function" === typeof args[args.length - 1]) {
|
||||||
debug("emitting packet with ack id %d", this.ids)
|
const id = this.ids++
|
||||||
this.acks[this.ids] = args.pop()
|
debug("emitting packet with ack id %d", id)
|
||||||
packet.id = this.ids++
|
|
||||||
|
const ack = args.pop() as Function
|
||||||
|
this._registerAckCallback(id, ack)
|
||||||
|
packet.id = id
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTransportWritable =
|
const isTransportWritable =
|
||||||
@@ -186,6 +416,7 @@ export class Socket<
|
|||||||
if (discardPacket) {
|
if (discardPacket) {
|
||||||
debug("discard packet as the transport is not currently writable")
|
debug("discard packet as the transport is not currently writable")
|
||||||
} else if (this.connected) {
|
} else if (this.connected) {
|
||||||
|
this.notifyOutgoingListeners(packet)
|
||||||
this.packet(packet)
|
this.packet(packet)
|
||||||
} else {
|
} else {
|
||||||
this.sendBuffer.push(packet)
|
this.sendBuffer.push(packet)
|
||||||
@@ -196,6 +427,152 @@ export class Socket<
|
|||||||
return this
|
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.
|
* Sends a packet.
|
||||||
*
|
*
|
||||||
@@ -216,13 +593,28 @@ export class Socket<
|
|||||||
debug("transport is open - connecting")
|
debug("transport is open - connecting")
|
||||||
if (typeof this.auth == "function") {
|
if (typeof this.auth == "function") {
|
||||||
this.auth((data) => {
|
this.auth((data) => {
|
||||||
this.packet({ type: PacketType.CONNECT, data })
|
this._sendConnectPacket(data as Record<string, unknown>)
|
||||||
})
|
})
|
||||||
} else {
|
} 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`.
|
* Called upon engine or manager `error`.
|
||||||
*
|
*
|
||||||
@@ -239,14 +631,17 @@ export class Socket<
|
|||||||
* Called upon engine `close`.
|
* Called upon engine `close`.
|
||||||
*
|
*
|
||||||
* @param reason
|
* @param reason
|
||||||
|
* @param description
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private onclose(reason: Socket.DisconnectReason): void {
|
private onclose(
|
||||||
|
reason: Socket.DisconnectReason,
|
||||||
|
description?: DisconnectDescription
|
||||||
|
): void {
|
||||||
debug("close (%s)", reason)
|
debug("close (%s)", reason)
|
||||||
this.connected = false
|
this.connected = false
|
||||||
this.disconnected = true
|
|
||||||
delete this.id
|
delete this.id
|
||||||
this.emitReserved("disconnect", reason)
|
this.emitReserved("disconnect", reason, description)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -263,8 +658,7 @@ export class Socket<
|
|||||||
switch (packet.type) {
|
switch (packet.type) {
|
||||||
case PacketType.CONNECT:
|
case PacketType.CONNECT:
|
||||||
if (packet.data && packet.data.sid) {
|
if (packet.data && packet.data.sid) {
|
||||||
const id = packet.data.sid
|
this.onconnect(packet.data.sid, packet.data.pid)
|
||||||
this.onconnect(id)
|
|
||||||
} else {
|
} else {
|
||||||
this.emitReserved(
|
this.emitReserved(
|
||||||
"connect_error",
|
"connect_error",
|
||||||
@@ -276,17 +670,11 @@ export class Socket<
|
|||||||
break
|
break
|
||||||
|
|
||||||
case PacketType.EVENT:
|
case PacketType.EVENT:
|
||||||
this.onevent(packet)
|
|
||||||
break
|
|
||||||
|
|
||||||
case PacketType.BINARY_EVENT:
|
case PacketType.BINARY_EVENT:
|
||||||
this.onevent(packet)
|
this.onevent(packet)
|
||||||
break
|
break
|
||||||
|
|
||||||
case PacketType.ACK:
|
case PacketType.ACK:
|
||||||
this.onack(packet)
|
|
||||||
break
|
|
||||||
|
|
||||||
case PacketType.BINARY_ACK:
|
case PacketType.BINARY_ACK:
|
||||||
this.onack(packet)
|
this.onack(packet)
|
||||||
break
|
break
|
||||||
@@ -296,6 +684,7 @@ export class Socket<
|
|||||||
break
|
break
|
||||||
|
|
||||||
case PacketType.CONNECT_ERROR:
|
case PacketType.CONNECT_ERROR:
|
||||||
|
this.destroy()
|
||||||
const err = new Error(packet.data.message)
|
const err = new Error(packet.data.message)
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
err.data = packet.data.data
|
err.data = packet.data.data
|
||||||
@@ -336,6 +725,9 @@ export class Socket<
|
|||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
super.emit.apply(this, args)
|
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
|
||||||
*/
|
*/
|
||||||
private onconnect(id: string): void {
|
private onconnect(id: string, pid: string) {
|
||||||
debug("socket connected with id %s", id)
|
debug("socket connected with id %s", id)
|
||||||
this.id = 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.connected = true
|
||||||
this.disconnected = false
|
|
||||||
this.emitBuffered()
|
this.emitBuffered()
|
||||||
this.emitReserved("connect")
|
this.emitReserved("connect")
|
||||||
}
|
}
|
||||||
@@ -400,7 +793,10 @@ export class Socket<
|
|||||||
this.receiveBuffer.forEach((args) => this.emitEvent(args))
|
this.receiveBuffer.forEach((args) => this.emitEvent(args))
|
||||||
this.receiveBuffer = []
|
this.receiveBuffer = []
|
||||||
|
|
||||||
this.sendBuffer.forEach((packet) => this.packet(packet))
|
this.sendBuffer.forEach((packet) => {
|
||||||
|
this.notifyOutgoingListeners(packet)
|
||||||
|
this.packet(packet)
|
||||||
|
})
|
||||||
this.sendBuffer = []
|
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
|
* @return self
|
||||||
* @public
|
|
||||||
*/
|
*/
|
||||||
public disconnect(): this {
|
public disconnect(): this {
|
||||||
if (this.connected) {
|
if (this.connected) {
|
||||||
@@ -454,10 +860,9 @@ export class Socket<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alias for disconnect()
|
* Alias for {@link disconnect()}.
|
||||||
*
|
*
|
||||||
* @return self
|
* @return self
|
||||||
* @public
|
|
||||||
*/
|
*/
|
||||||
public close(): this {
|
public close(): this {
|
||||||
return this.disconnect()
|
return this.disconnect()
|
||||||
@@ -466,9 +871,11 @@ export class Socket<
|
|||||||
/**
|
/**
|
||||||
* Sets the compress flag.
|
* Sets the compress flag.
|
||||||
*
|
*
|
||||||
|
* @example
|
||||||
|
* socket.compress(false).emit("hello");
|
||||||
|
*
|
||||||
* @param compress - if `true`, compresses the sending data
|
* @param compress - if `true`, compresses the sending data
|
||||||
* @return self
|
* @return self
|
||||||
* @public
|
|
||||||
*/
|
*/
|
||||||
public compress(compress: boolean): this {
|
public compress(compress: boolean): this {
|
||||||
this.flags.compress = compress
|
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
|
* Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not
|
||||||
* ready to send messages.
|
* ready to send messages.
|
||||||
*
|
*
|
||||||
|
* @example
|
||||||
|
* socket.volatile.emit("hello"); // the server may or may not receive it
|
||||||
|
*
|
||||||
* @returns self
|
* @returns self
|
||||||
* @public
|
|
||||||
*/
|
*/
|
||||||
public get volatile(): this {
|
public get volatile(): this {
|
||||||
this.flags.volatile = true
|
this.flags.volatile = true
|
||||||
return this
|
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
|
* Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
|
||||||
* callback.
|
* callback.
|
||||||
*
|
*
|
||||||
|
* @example
|
||||||
|
* socket.onAny((event, ...args) => {
|
||||||
|
* console.log(`got ${event}`);
|
||||||
|
* });
|
||||||
|
*
|
||||||
* @param listener
|
* @param listener
|
||||||
* @public
|
|
||||||
*/
|
*/
|
||||||
public onAny(listener: (...args: any[]) => void): this {
|
public onAny(listener: (...args: any[]) => void): this {
|
||||||
this._anyListeners = this._anyListeners || []
|
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
|
* 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.
|
* callback. The listener is added to the beginning of the listeners array.
|
||||||
*
|
*
|
||||||
|
* @example
|
||||||
|
* socket.prependAny((event, ...args) => {
|
||||||
|
* console.log(`got event ${event}`);
|
||||||
|
* });
|
||||||
|
*
|
||||||
* @param listener
|
* @param listener
|
||||||
* @public
|
|
||||||
*/
|
*/
|
||||||
public prependAny(listener: (...args: any[]) => void): this {
|
public prependAny(listener: (...args: any[]) => void): this {
|
||||||
this._anyListeners = this._anyListeners || []
|
this._anyListeners = this._anyListeners || []
|
||||||
@@ -516,8 +953,20 @@ export class Socket<
|
|||||||
/**
|
/**
|
||||||
* Removes the listener that will be fired when any event is emitted.
|
* 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
|
* @param listener
|
||||||
* @public
|
|
||||||
*/
|
*/
|
||||||
public offAny(listener?: (...args: any[]) => void): this {
|
public offAny(listener?: (...args: any[]) => void): this {
|
||||||
if (!this._anyListeners) {
|
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,
|
* Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,
|
||||||
* e.g. to remove listeners.
|
* e.g. to remove listeners.
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
*/
|
||||||
public listenersAny() {
|
public listenersAny() {
|
||||||
return this._anyListeners || []
|
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 {
|
export namespace Socket {
|
||||||
@@ -555,4 +1100,5 @@ export namespace Socket {
|
|||||||
| "ping timeout"
|
| "ping timeout"
|
||||||
| "transport close"
|
| "transport close"
|
||||||
| "transport error"
|
| "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")
|
const debug = require("../debug")("socket.io-client")
|
||||||
|
|
||||||
type ParsedUrl = {
|
type ParsedUrl = {
|
||||||
@@ -67,7 +70,7 @@ export function url(
|
|||||||
|
|
||||||
// parse
|
// parse
|
||||||
debug("parse %s", uri)
|
debug("parse %s", uri)
|
||||||
obj = parseuri(uri) as ParsedUrl
|
obj = parse(uri) as ParsedUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure we treat `localhost:80` and `localhost` equally
|
// 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.
|
* 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) {
|
export function deconstructPacket(packet) {
|
||||||
const buffers = []
|
const buffers = [];
|
||||||
const packetData = packet.data
|
const packetData = packet.data;
|
||||||
const pack = packet
|
const pack = packet;
|
||||||
pack.data = _deconstructPacket(packetData, buffers)
|
pack.data = _deconstructPacket(packetData, buffers);
|
||||||
pack.attachments = buffers.length // number of binary 'attachments'
|
pack.attachments = buffers.length; // number of binary 'attachments'
|
||||||
return { packet: pack, buffers: buffers }
|
return { packet: pack, buffers: buffers };
|
||||||
}
|
}
|
||||||
|
|
||||||
function _deconstructPacket(data, buffers) {
|
function _deconstructPacket(data, buffers) {
|
||||||
if (!data) return data
|
if (!data) return data;
|
||||||
|
|
||||||
if (isBinary(data)) {
|
if (isBinary(data)) {
|
||||||
const placeholder = { _placeholder: true, num: buffers.length }
|
const placeholder = { _placeholder: true, num: buffers.length };
|
||||||
buffers.push(data)
|
buffers.push(data);
|
||||||
return placeholder
|
return placeholder;
|
||||||
} else if (Array.isArray(data)) {
|
} else if (Array.isArray(data)) {
|
||||||
const newData = new Array(data.length)
|
const newData = new Array(data.length);
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
newData[i] = _deconstructPacket(data[i], buffers)
|
newData[i] = _deconstructPacket(data[i], buffers);
|
||||||
}
|
|
||||||
return newData
|
|
||||||
} else if (typeof data === "object" && !(data instanceof Date)) {
|
|
||||||
const newData = {}
|
|
||||||
for (const key in data) {
|
|
||||||
if (data.hasOwnProperty(key)) {
|
|
||||||
newData[key] = _deconstructPacket(data[key], buffers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newData
|
|
||||||
}
|
}
|
||||||
return data
|
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) {
|
export function reconstructPacket(packet, buffers) {
|
||||||
packet.data = _reconstructPacket(packet.data, buffers)
|
packet.data = _reconstructPacket(packet.data, buffers);
|
||||||
packet.attachments = undefined // no longer useful
|
delete packet.attachments; // no longer useful
|
||||||
return packet
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _reconstructPacket(data, buffers) {
|
function _reconstructPacket(data, buffers) {
|
||||||
if (!data) return data
|
if (!data) return data;
|
||||||
|
|
||||||
if (data && data._placeholder) {
|
if (data && data._placeholder === true) {
|
||||||
return buffers[data.num] // appropriate buffer (should be natural order anyway)
|
const isIndexValid =
|
||||||
} else if (Array.isArray(data)) {
|
typeof data.num === "number" &&
|
||||||
for (let i = 0; i < data.length; i++) {
|
data.num >= 0 &&
|
||||||
data[i] = _reconstructPacket(data[i], buffers)
|
data.num < buffers.length;
|
||||||
}
|
if (isIndexValid) {
|
||||||
} else if (typeof data === "object") {
|
return buffers[data.num]; // appropriate buffer (should be natural order anyway)
|
||||||
for (const key in data) {
|
} else {
|
||||||
if (data.hasOwnProperty(key)) {
|
throw new Error("illegal attachments");
|
||||||
data[key] = _reconstructPacket(data[key], buffers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} 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 { Emitter } from "@socket.io/component-emitter";
|
||||||
import { deconstructPacket, reconstructPacket } from "./binary"
|
import { deconstructPacket, reconstructPacket } from "./binary.js";
|
||||||
import { isBinary, hasBinary } from "./is-binary"
|
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.
|
* Protocol version.
|
||||||
@@ -10,24 +11,24 @@ import { isBinary, hasBinary } from "./is-binary"
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const protocol: number = 5
|
export const protocol: number = 5;
|
||||||
|
|
||||||
export enum PacketType {
|
export enum PacketType {
|
||||||
CONNECT,
|
CONNECT,
|
||||||
DISCONNECT,
|
DISCONNECT,
|
||||||
EVENT,
|
EVENT,
|
||||||
ACK,
|
ACK,
|
||||||
CONNECT_ERROR,
|
CONNECT_ERROR,
|
||||||
BINARY_EVENT,
|
BINARY_EVENT,
|
||||||
BINARY_ACK,
|
BINARY_ACK,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Packet {
|
export interface Packet {
|
||||||
type: PacketType
|
type: PacketType;
|
||||||
nsp: string
|
nsp: string;
|
||||||
data?: any
|
data?: any;
|
||||||
id?: number
|
id?: number;
|
||||||
attachments?: number
|
attachments?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,77 +36,91 @@ export interface Packet {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export class Encoder {
|
export class Encoder {
|
||||||
/**
|
/**
|
||||||
* Encode a packet as a single string if non-binary, or as a
|
* Encoder constructor
|
||||||
* buffer sequence, depending on packet type.
|
*
|
||||||
*
|
* @param {function} replacer - custom replacer to pass down to JSON.parse
|
||||||
* @param {Object} obj - packet object
|
*/
|
||||||
*/
|
constructor(private replacer?: (this: any, key: string, value: any) => any) {}
|
||||||
public encode(obj: Packet) {
|
/**
|
||||||
console.trace("encoding packet", JSON.stringify(obj))
|
* 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 (obj.type === PacketType.EVENT || obj.type === PacketType.ACK) {
|
||||||
if (hasBinary(obj)) {
|
if (hasBinary(obj)) {
|
||||||
obj.type =
|
return this.encodeAsBinary({
|
||||||
obj.type === PacketType.EVENT
|
type:
|
||||||
? PacketType.BINARY_EVENT
|
obj.type === PacketType.EVENT
|
||||||
: PacketType.BINARY_ACK
|
? PacketType.BINARY_EVENT
|
||||||
return this.encodeAsBinary(obj)
|
: PacketType.BINARY_ACK,
|
||||||
}
|
nsp: obj.nsp,
|
||||||
}
|
data: obj.data,
|
||||||
return [this.encodeAsString(obj)]
|
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 + "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// if we have a namespace other than `/`
|
||||||
* Encode packet as string.
|
// we append it followed by a comma `,`
|
||||||
*/
|
if (obj.nsp && "/" !== obj.nsp) {
|
||||||
|
str += obj.nsp + ",";
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// immediately followed by the id
|
||||||
* Encode packet as 'buffer sequence' by removing blobs, and
|
if (null != obj.id) {
|
||||||
* deconstructing packet into object with placeholders and
|
str += obj.id;
|
||||||
* 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
* @return {Object} decoder
|
||||||
*/
|
*/
|
||||||
export class Decoder extends EventEmitter {
|
export class Decoder extends Emitter<{}, {}, DecoderReservedEvents> {
|
||||||
private reconstructor: BinaryReconstructor
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// look up attachments if type binary
|
||||||
* Decodes an encoded packet string into packet JSON.
|
if (
|
||||||
*
|
p.type === PacketType.BINARY_EVENT ||
|
||||||
* @param {String} obj - encoded packet
|
p.type === PacketType.BINARY_ACK
|
||||||
*/
|
) {
|
||||||
|
const start = i + 1;
|
||||||
public add(obj: any) {
|
while (str.charAt(++i) !== "-" && i != str.length) {}
|
||||||
let packet
|
const buf = str.substring(start, i);
|
||||||
if (typeof obj === "string") {
|
if (buf != Number(buf) || str.charAt(i) !== "-") {
|
||||||
packet = this.decodeString(obj)
|
throw new Error("Illegal attachments");
|
||||||
if (
|
}
|
||||||
packet.type === PacketType.BINARY_EVENT ||
|
p.attachments = Number(buf);
|
||||||
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 namespace (if any)
|
||||||
* Decode a packet String (JSON data)
|
if ("/" === str.charAt(i + 1)) {
|
||||||
*
|
const start = i + 1;
|
||||||
* @param {String} str
|
while (++i) {
|
||||||
* @return {Object} packet
|
const c = str.charAt(i);
|
||||||
*/
|
if ("," === c) break;
|
||||||
private decodeString(str): Packet {
|
if (i === str.length) break;
|
||||||
let i = 0
|
}
|
||||||
// look up type
|
p.nsp = str.substring(start, i);
|
||||||
const p: any = {
|
} else {
|
||||||
type: Number(str.charAt(0)),
|
p.nsp = "/";
|
||||||
}
|
|
||||||
|
|
||||||
if (PacketType[p.type] === undefined) {
|
|
||||||
throw new Error("unknown packet type " + p.type)
|
|
||||||
}
|
|
||||||
|
|
||||||
// look up attachments if type binary
|
|
||||||
if (
|
|
||||||
p.type === PacketType.BINARY_EVENT ||
|
|
||||||
p.type === PacketType.BINARY_ACK
|
|
||||||
) {
|
|
||||||
const start = i + 1
|
|
||||||
while (str.charAt(++i) !== "-" && i != str.length) { }
|
|
||||||
const buf = str.substring(start, i)
|
|
||||||
if (buf != Number(buf) || str.charAt(i) !== "-") {
|
|
||||||
throw new Error("Illegal attachments")
|
|
||||||
}
|
|
||||||
p.attachments = Number(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// look up namespace (if any)
|
|
||||||
if ("/" === str.charAt(i + 1)) {
|
|
||||||
const start = i + 1
|
|
||||||
while (++i) {
|
|
||||||
const c = str.charAt(i)
|
|
||||||
if ("," === c) break
|
|
||||||
if (i === str.length) break
|
|
||||||
}
|
|
||||||
p.nsp = str.substring(start, i)
|
|
||||||
} else {
|
|
||||||
p.nsp = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
// look up id
|
|
||||||
const next = str.charAt(i + 1)
|
|
||||||
if ("" !== next && Number(next) == next) {
|
|
||||||
const start = i + 1
|
|
||||||
while (++i) {
|
|
||||||
const c = str.charAt(i)
|
|
||||||
if (null == c || Number(c) != c) {
|
|
||||||
--i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (i === str.length) break
|
|
||||||
}
|
|
||||||
p.id = Number(str.substring(start, i + 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// look up json data
|
|
||||||
if (str.charAt(++i)) {
|
|
||||||
const payload = tryParse(str.substr(i))
|
|
||||||
if (Decoder.isPayloadValid(p.type, payload)) {
|
|
||||||
p.data = payload
|
|
||||||
} else {
|
|
||||||
throw new Error("invalid payload")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.trace("decoded", str, "as", p)
|
|
||||||
return p
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static isPayloadValid(type: PacketType, payload: any): boolean {
|
// look up id
|
||||||
switch (type) {
|
const next = str.charAt(i + 1);
|
||||||
case PacketType.CONNECT:
|
if ("" !== next && Number(next) == next) {
|
||||||
return typeof payload === "object"
|
const start = i + 1;
|
||||||
case PacketType.DISCONNECT:
|
while (++i) {
|
||||||
return payload === undefined
|
const c = str.charAt(i);
|
||||||
case PacketType.CONNECT_ERROR:
|
if (null == c || Number(c) != c) {
|
||||||
return typeof payload === "string" || typeof payload === "object"
|
--i;
|
||||||
case PacketType.EVENT:
|
break;
|
||||||
case PacketType.BINARY_EVENT:
|
|
||||||
return Array.isArray(payload) && payload.length > 0
|
|
||||||
case PacketType.ACK:
|
|
||||||
case PacketType.BINARY_ACK:
|
|
||||||
return Array.isArray(payload)
|
|
||||||
}
|
}
|
||||||
|
if (i === str.length) break;
|
||||||
|
}
|
||||||
|
p.id = Number(str.substring(start, i + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// look up json data
|
||||||
* Deallocates a parser's resources
|
if (str.charAt(++i)) {
|
||||||
*/
|
const payload = this.tryParse(str.substr(i));
|
||||||
public destroy() {
|
if (Decoder.isPayloadValid(p.type, payload)) {
|
||||||
if (this.reconstructor) {
|
p.data = payload;
|
||||||
this.reconstructor.finishedReconstruction()
|
} else {
|
||||||
}
|
throw new Error("invalid payload");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function tryParse(str) {
|
debug("decoded %s as %j", str, p);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
private tryParse(str) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(str)
|
return JSON.parse(str, this.reviver);
|
||||||
} catch (error: any) {
|
} catch (e) {
|
||||||
return false
|
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 {
|
class BinaryReconstructor {
|
||||||
private reconPack
|
private reconPack;
|
||||||
private buffers: Array<Buffer | ArrayBuffer> = [];
|
private buffers: Array<Buffer | ArrayBuffer> = [];
|
||||||
|
|
||||||
constructor(readonly packet: Packet) {
|
constructor(readonly packet: Packet) {
|
||||||
this.reconPack = packet
|
this.reconPack = packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to be called when binary data received from connection
|
* Method to be called when binary data received from connection
|
||||||
* after a BINARY_EVENT packet.
|
* after a BINARY_EVENT packet.
|
||||||
*
|
*
|
||||||
* @param {Buffer | ArrayBuffer} binData - the raw binary data received
|
* @param {Buffer | ArrayBuffer} binData - the raw binary data received
|
||||||
* @return {null | Object} returns null if more binary data is expected or
|
* @return {null | Object} returns null if more binary data is expected or
|
||||||
* a reconstructed packet object if all buffers have been received.
|
* a reconstructed packet object if all buffers have been received.
|
||||||
*/
|
*/
|
||||||
public takeBinaryData(binData) {
|
public takeBinaryData(binData) {
|
||||||
this.buffers.push(binData)
|
this.buffers.push(binData);
|
||||||
if (this.buffers.length === this.reconPack.attachments) {
|
if (this.buffers.length === this.reconPack.attachments) {
|
||||||
// done with buffer list
|
// done with buffer list
|
||||||
const packet = reconstructPacket(this.reconPack, this.buffers)
|
const packet = reconstructPacket(this.reconPack, this.buffers);
|
||||||
this.finishedReconstruction()
|
this.finishedReconstruction();
|
||||||
return packet
|
return packet;
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleans up binary packet reconstruction variables.
|
* Cleans up binary packet reconstruction variables.
|
||||||
*/
|
*/
|
||||||
public finishedReconstruction() {
|
public finishedReconstruction() {
|
||||||
this.reconPack = null
|
this.reconPack = null;
|
||||||
this.buffers = []
|
this.buffers = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
const withNativeArrayBuffer: boolean = typeof ArrayBuffer === "function"
|
const withNativeArrayBuffer: boolean = typeof ArrayBuffer === "function";
|
||||||
|
|
||||||
const isView = (obj: any) => {
|
const isView = (obj: any) => {
|
||||||
return typeof ArrayBuffer.isView === "function"
|
return typeof ArrayBuffer.isView === "function"
|
||||||
? ArrayBuffer.isView(obj)
|
? ArrayBuffer.isView(obj)
|
||||||
: obj.buffer instanceof ArrayBuffer
|
: obj.buffer instanceof ArrayBuffer;
|
||||||
}
|
};
|
||||||
|
|
||||||
const toString = Object.prototype.toString
|
const toString = Object.prototype.toString;
|
||||||
const withNativeBlob = false
|
const withNativeBlob =
|
||||||
// typeof Blob === "function" ||
|
typeof Blob === "function" ||
|
||||||
// (typeof Blob !== "undefined" &&
|
(typeof Blob !== "undefined" &&
|
||||||
// toString.call(Blob) === "[object BlobConstructor]")
|
toString.call(Blob) === "[object BlobConstructor]");
|
||||||
const withNativeFile = false
|
const withNativeFile =
|
||||||
// typeof File === "function" ||
|
typeof File === "function" ||
|
||||||
// (typeof File !== "undefined" &&
|
(typeof File !== "undefined" &&
|
||||||
// toString.call(File) === "[object FileConstructor]")
|
toString.call(File) === "[object FileConstructor]");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if obj is a Buffer, an ArrayBuffer, a Blob or a File.
|
* 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) {
|
export function isBinary(obj: any) {
|
||||||
return (
|
return (
|
||||||
(withNativeArrayBuffer && (obj instanceof ArrayBuffer || isView(obj)))
|
(withNativeArrayBuffer && (obj instanceof ArrayBuffer || isView(obj))) ||
|
||||||
// || (withNativeBlob && obj instanceof Blob) || (withNativeFile && obj instanceof File)
|
(withNativeBlob && obj instanceof Blob) ||
|
||||||
)
|
(withNativeFile && obj instanceof File)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasBinary(obj: any, toJSON?: boolean) {
|
export function hasBinary(obj: any, toJSON?: boolean) {
|
||||||
if (!obj || typeof obj !== "object") {
|
if (!obj || typeof obj !== "object") {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
for (let i = 0, l = obj.length; i < l; i++) {
|
for (let i = 0, l = obj.length; i < l; i++) {
|
||||||
if (hasBinary(obj[i])) {
|
if (hasBinary(obj[i])) {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (isBinary(obj)) {
|
if (isBinary(obj)) {
|
||||||
return true
|
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 (
|
return false;
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,320 +1,561 @@
|
|||||||
// import type { BroadcastFlags, Room, SocketId } from "socket.io-adapter"
|
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 { 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 { PacketType } from "../socket.io-parser"
|
|
||||||
// import type { Adapter } from "socket.io-adapter"
|
|
||||||
import type { Adapter } from "../socket.io-adapter"
|
|
||||||
import type {
|
import type {
|
||||||
EventParams,
|
EventParams,
|
||||||
EventNames,
|
EventNames,
|
||||||
EventsMap,
|
EventsMap,
|
||||||
TypedEventBroadcaster,
|
TypedEventBroadcaster,
|
||||||
} from "./typed-events"
|
DecorateAcknowledgements,
|
||||||
|
DecorateAcknowledgementsWithTimeoutAndMultipleResponses,
|
||||||
|
AllButLast,
|
||||||
|
Last,
|
||||||
|
SecondArg,
|
||||||
|
} from "./typed-events";
|
||||||
|
|
||||||
export class BroadcastOperator<EmitEvents extends EventsMap>
|
export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
|
||||||
implements TypedEventBroadcaster<EmitEvents>
|
implements TypedEventBroadcaster<EmitEvents>
|
||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
private readonly adapter: Adapter,
|
private readonly adapter: Adapter,
|
||||||
private readonly rooms: Set<Room> = new Set<Room>(),
|
private readonly rooms: Set<Room> = new Set<Room>(),
|
||||||
private readonly exceptRooms: Set<Room> = new Set<Room>(),
|
private readonly exceptRooms: Set<Room> = new Set<Room>(),
|
||||||
private readonly flags: BroadcastFlags = {}
|
private readonly flags: BroadcastFlags & {
|
||||||
) { }
|
expectSingleResponse?: boolean;
|
||||||
|
} = {}
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Targets a room when emitting.
|
* Targets a room when emitting.
|
||||||
*
|
*
|
||||||
* @param room
|
* @example
|
||||||
* @return a new BroadcastOperator instance
|
* // the “foo” event will be broadcast to all connected clients in the “room-101” room
|
||||||
* @public
|
* io.to("room-101").emit("foo", "bar");
|
||||||
*/
|
*
|
||||||
public to(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
* // with an array of rooms (a client will be notified at most once)
|
||||||
const rooms = new Set(this.rooms)
|
* io.to(["room-101", "room-102"]).emit("foo", "bar");
|
||||||
if (Array.isArray(room)) {
|
*
|
||||||
room.forEach((r) => rooms.add(r))
|
* // 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 {
|
} else {
|
||||||
rooms.add(room)
|
return resolve(responses);
|
||||||
}
|
}
|
||||||
return new BroadcastOperator(
|
});
|
||||||
this.adapter,
|
this.emit(ev, ...(args as any[] as EventParams<EmitEvents, Ev>));
|
||||||
rooms,
|
});
|
||||||
this.exceptRooms,
|
}
|
||||||
this.flags
|
|
||||||
)
|
/**
|
||||||
|
* 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.
|
* Returns the matching socket instances. This method works across a cluster of several Socket.IO servers.
|
||||||
*
|
*
|
||||||
* @param room
|
* Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
|
||||||
* @return a new BroadcastOperator instance
|
*
|
||||||
* @public
|
* @example
|
||||||
*/
|
* // return all Socket instances
|
||||||
public in(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
* const sockets = await io.fetchSockets();
|
||||||
return this.to(room)
|
*
|
||||||
}
|
* // 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.
|
* Makes the matching socket instances join the specified rooms.
|
||||||
*
|
*
|
||||||
* @param room
|
* Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
|
||||||
* @return a new BroadcastOperator instance
|
*
|
||||||
* @public
|
* @example
|
||||||
*/
|
*
|
||||||
public except(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
* // make all socket instances join the "room1" room
|
||||||
const exceptRooms = new Set(this.exceptRooms)
|
* io.socketsJoin("room1");
|
||||||
if (Array.isArray(room)) {
|
*
|
||||||
room.forEach((r) => exceptRooms.add(r))
|
* // make all socket instances in the "room1" room join the "room2" and "room3" rooms
|
||||||
} else {
|
* io.in("room1").socketsJoin(["room2", "room3"]);
|
||||||
exceptRooms.add(room)
|
*
|
||||||
}
|
* @param room - a room, or an array of rooms
|
||||||
return new BroadcastOperator(
|
*/
|
||||||
this.adapter,
|
public socketsJoin(room: Room | Room[]): void {
|
||||||
this.rooms,
|
this.adapter.addSockets(
|
||||||
exceptRooms,
|
{
|
||||||
this.flags
|
rooms: this.rooms,
|
||||||
)
|
except: this.exceptRooms,
|
||||||
}
|
flags: this.flags,
|
||||||
|
},
|
||||||
|
Array.isArray(room) ? room : [room]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the compress flag.
|
* Makes the matching socket instances leave the specified rooms.
|
||||||
*
|
*
|
||||||
* @param compress - if `true`, compresses the sending data
|
* Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
|
||||||
* @return a new BroadcastOperator instance
|
*
|
||||||
* @public
|
* @example
|
||||||
*/
|
* // make all socket instances leave the "room1" room
|
||||||
public compress(compress: boolean): BroadcastOperator<EmitEvents> {
|
* io.socketsLeave("room1");
|
||||||
const flags = Object.assign({}, this.flags, { compress })
|
*
|
||||||
return new BroadcastOperator(
|
* // make all socket instances in the "room1" room leave the "room2" and "room3" rooms
|
||||||
this.adapter,
|
* io.in("room1").socketsLeave(["room2", "room3"]);
|
||||||
this.rooms,
|
*
|
||||||
this.exceptRooms,
|
* @param room - a room, or an array of rooms
|
||||||
flags
|
*/
|
||||||
)
|
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
|
* Makes the matching socket instances disconnect.
|
||||||
* 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).
|
* Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
|
||||||
*
|
*
|
||||||
* @return a new BroadcastOperator instance
|
* @example
|
||||||
* @public
|
* // make all socket instances disconnect (the connections might be kept alive for other namespaces)
|
||||||
*/
|
* io.disconnectSockets();
|
||||||
public get volatile(): BroadcastOperator<EmitEvents> {
|
*
|
||||||
const flags = Object.assign({}, this.flags, { volatile: true })
|
* // make all socket instances in the "room1" room disconnect and close the underlying connections
|
||||||
return new BroadcastOperator(
|
* io.in("room1").disconnectSockets(true);
|
||||||
this.adapter,
|
*
|
||||||
this.rooms,
|
* @param close - whether to close the underlying connection
|
||||||
this.exceptRooms,
|
*/
|
||||||
flags
|
public disconnectSockets(close: boolean = false): void {
|
||||||
)
|
this.adapter.disconnectSockets(
|
||||||
}
|
{
|
||||||
|
rooms: this.rooms,
|
||||||
/**
|
except: this.exceptRooms,
|
||||||
* Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
|
flags: this.flags,
|
||||||
*
|
},
|
||||||
* @return a new BroadcastOperator instance
|
close
|
||||||
* @public
|
);
|
||||||
*/
|
}
|
||||||
public get local(): BroadcastOperator<EmitEvents> {
|
|
||||||
const flags = Object.assign({}, this.flags, { local: true })
|
|
||||||
return new BroadcastOperator(
|
|
||||||
this.adapter,
|
|
||||||
this.rooms,
|
|
||||||
this.exceptRooms,
|
|
||||||
flags
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emits to all clients.
|
|
||||||
*
|
|
||||||
* @return Always true
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
public emit<Ev extends EventNames<EmitEvents>>(
|
|
||||||
ev: Ev,
|
|
||||||
...args: EventParams<EmitEvents, Ev>
|
|
||||||
): boolean {
|
|
||||||
if (RESERVED_EVENTS.has(ev)) {
|
|
||||||
throw new Error(`"${ev}" is a reserved event name`)
|
|
||||||
}
|
|
||||||
// set up packet object
|
|
||||||
const data = [ev, ...args]
|
|
||||||
const packet = {
|
|
||||||
type: PacketType.EVENT,
|
|
||||||
data: data,
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("function" == typeof data[data.length - 1]) {
|
|
||||||
throw new Error("Callbacks are not supported when broadcasting")
|
|
||||||
}
|
|
||||||
|
|
||||||
this.adapter.broadcast(packet, {
|
|
||||||
rooms: this.rooms,
|
|
||||||
except: this.exceptRooms,
|
|
||||||
flags: this.flags,
|
|
||||||
})
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a list of clients.
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
public allSockets(): Promise<Set<SocketId>> {
|
|
||||||
if (!this.adapter) {
|
|
||||||
throw new Error(
|
|
||||||
"No adapter for this namespace, are you trying to get the list of clients of a dynamic namespace?"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return this.adapter.sockets(this.rooms)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the matching socket instances
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
public fetchSockets(): Promise<RemoteSocket<EmitEvents>[]> {
|
|
||||||
return this.adapter
|
|
||||||
.fetchSockets({
|
|
||||||
rooms: this.rooms,
|
|
||||||
except: this.exceptRooms,
|
|
||||||
})
|
|
||||||
.then((sockets) => {
|
|
||||||
return sockets.map((socket) => {
|
|
||||||
if (socket instanceof Socket) {
|
|
||||||
// FIXME the TypeScript compiler complains about missing private properties
|
|
||||||
return socket as unknown as RemoteSocket<EmitEvents>
|
|
||||||
} else {
|
|
||||||
return new RemoteSocket(this.adapter, socket as SocketDetails)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes the matching socket instances join the specified rooms
|
|
||||||
*
|
|
||||||
* @param room
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
public socketsJoin(room: Room | Room[]): void {
|
|
||||||
this.adapter.addSockets(
|
|
||||||
{
|
|
||||||
rooms: this.rooms,
|
|
||||||
except: this.exceptRooms,
|
|
||||||
},
|
|
||||||
Array.isArray(room) ? room : [room]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes the matching socket instances leave the specified rooms
|
|
||||||
*
|
|
||||||
* @param room
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
public socketsLeave(room: Room | Room[]): void {
|
|
||||||
this.adapter.delSockets(
|
|
||||||
{
|
|
||||||
rooms: this.rooms,
|
|
||||||
except: this.exceptRooms,
|
|
||||||
},
|
|
||||||
Array.isArray(room) ? room : [room]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes the matching socket instances disconnect
|
|
||||||
*
|
|
||||||
* @param close - whether to close the underlying connection
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
public disconnectSockets(close: boolean = false): void {
|
|
||||||
this.adapter.disconnectSockets(
|
|
||||||
{
|
|
||||||
rooms: this.rooms,
|
|
||||||
except: this.exceptRooms,
|
|
||||||
},
|
|
||||||
close
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format of the data when the Socket instance exists on another Socket.IO server
|
* Format of the data when the Socket instance exists on another Socket.IO server
|
||||||
*/
|
*/
|
||||||
interface SocketDetails {
|
interface SocketDetails<SocketData> {
|
||||||
id: SocketId
|
id: SocketId;
|
||||||
handshake: Handshake
|
handshake: Handshake;
|
||||||
rooms: Room[]
|
rooms: Room[];
|
||||||
data: any
|
data: SocketData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose of subset of the attributes and methods of the Socket class
|
* Expose of subset of the attributes and methods of the Socket class
|
||||||
*/
|
*/
|
||||||
export class RemoteSocket<EmitEvents extends EventsMap>
|
export class RemoteSocket<EmitEvents extends EventsMap, SocketData>
|
||||||
implements TypedEventBroadcaster<EmitEvents>
|
implements TypedEventBroadcaster<EmitEvents>
|
||||||
{
|
{
|
||||||
public readonly id: SocketId
|
public readonly id: SocketId;
|
||||||
public readonly handshake: Handshake
|
public readonly handshake: Handshake;
|
||||||
public readonly rooms: Set<Room>
|
public readonly rooms: Set<Room>;
|
||||||
public readonly data: any
|
public readonly data: SocketData;
|
||||||
|
|
||||||
private readonly operator: BroadcastOperator<EmitEvents>
|
private readonly operator: BroadcastOperator<EmitEvents, SocketData>;
|
||||||
|
|
||||||
constructor(adapter: Adapter, details: SocketDetails) {
|
constructor(adapter: Adapter, details: SocketDetails<SocketData>) {
|
||||||
this.id = details.id
|
this.id = details.id;
|
||||||
this.handshake = details.handshake
|
this.handshake = details.handshake;
|
||||||
this.rooms = new Set(details.rooms)
|
this.rooms = new Set(details.rooms);
|
||||||
this.data = details.data
|
this.data = details.data;
|
||||||
this.operator = new BroadcastOperator(adapter, new Set([this.id]))
|
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,
|
* Adds a timeout in milliseconds for the next operation.
|
||||||
...args: EventParams<EmitEvents, Ev>
|
*
|
||||||
): boolean {
|
* @example
|
||||||
return this.operator.emit(ev, ...args)
|
* 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
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
public emit<Ev extends EventNames<EmitEvents>>(
|
||||||
* Joins a room.
|
ev: Ev,
|
||||||
*
|
...args: EventParams<EmitEvents, Ev>
|
||||||
* @param {String|Array} room - room or array of rooms
|
): boolean {
|
||||||
* @public
|
return this.operator.emit(ev, ...args);
|
||||||
*/
|
}
|
||||||
public join(room: Room | Room[]): void {
|
|
||||||
return this.operator.socketsJoin(room)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Leaves a room.
|
* Joins a room.
|
||||||
*
|
*
|
||||||
* @param {String} room
|
* @param {String|Array} room - room or array of rooms
|
||||||
* @public
|
*/
|
||||||
*/
|
public join(room: Room | Room[]): void {
|
||||||
public leave(room: Room): void {
|
return this.operator.socketsJoin(room);
|
||||||
return this.operator.socketsLeave(room)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnects this client.
|
* Leaves a room.
|
||||||
*
|
*
|
||||||
* @param {Boolean} close - if `true`, closes the underlying connection
|
* @param {String} room
|
||||||
* @return {Socket} self
|
*/
|
||||||
*
|
public leave(room: Room): void {
|
||||||
* @public
|
return this.operator.socketsLeave(room);
|
||||||
*/
|
}
|
||||||
public disconnect(close = false): this {
|
|
||||||
this.operator.disconnectSockets(close)
|
/**
|
||||||
return this
|
* 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 debugModule = require("debug")
|
||||||
import url = require("url")
|
import url = require("url");
|
||||||
// import type { IncomingMessage } from "http"
|
// import type { IncomingMessage } from "http"
|
||||||
import type { Server } from "./index"
|
import type { Server } from "./index";
|
||||||
import type { Namespace } from "./namespace"
|
import type { Namespace } from "./namespace";
|
||||||
import type { EventsMap } from "./typed-events"
|
import type { EventsMap } from "./typed-events";
|
||||||
import type { Socket } from "./socket"
|
import type { Socket } from "./socket";
|
||||||
// import type { SocketId } from "socket.io-adapter"
|
import type { SocketId } from "../socket.io-adapter";
|
||||||
import type { SocketId } from "../socket.io-adapter"
|
import type { Socket as RawSocket } from "../engine.io/socket";
|
||||||
import type { Socket as EngineIOSocket } from '../engine.io/socket'
|
|
||||||
|
|
||||||
// const debug = debugModule("socket.io:client");
|
const debug = require("../debug")("socket.io:client");
|
||||||
|
|
||||||
interface WriteOptions {
|
interface WriteOptions {
|
||||||
compress?: boolean
|
compress?: boolean;
|
||||||
volatile?: boolean
|
volatile?: boolean;
|
||||||
preEncoded?: boolean
|
preEncoded?: boolean;
|
||||||
wsPreEncoded?: string
|
wsPreEncoded?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CloseReason =
|
||||||
|
| "transport error"
|
||||||
|
| "transport close"
|
||||||
|
| "forced close"
|
||||||
|
| "ping timeout"
|
||||||
|
| "parse error";
|
||||||
|
|
||||||
export class Client<
|
export class Client<
|
||||||
ListenEvents extends EventsMap,
|
ListenEvents extends EventsMap,
|
||||||
EmitEvents extends EventsMap,
|
EmitEvents extends EventsMap,
|
||||||
ServerSideEvents extends EventsMap
|
ServerSideEvents extends EventsMap,
|
||||||
> {
|
SocketData = any
|
||||||
public readonly conn: EngineIOSocket
|
> {
|
||||||
/**
|
public readonly conn: RawSocket;
|
||||||
* @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
|
|
||||||
|
|
||||||
/**
|
private readonly id: string;
|
||||||
* Client constructor.
|
private readonly server: Server<
|
||||||
*
|
ListenEvents,
|
||||||
* @param server instance
|
EmitEvents,
|
||||||
* @param conn
|
ServerSideEvents,
|
||||||
* @package
|
SocketData
|
||||||
*/
|
>;
|
||||||
constructor(
|
private readonly encoder: Encoder;
|
||||||
server: Server<ListenEvents, EmitEvents, ServerSideEvents>,
|
private readonly decoder: Decoder;
|
||||||
conn: EngineIOSocket
|
private sockets: Map<
|
||||||
) {
|
SocketId,
|
||||||
this.server = server
|
Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||||
this.conn = conn
|
> = new Map();
|
||||||
this.encoder = server.encoder
|
private nsps: Map<
|
||||||
this.decoder = new server._parser.Decoder()
|
string,
|
||||||
this.id = conn.id
|
Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||||
this.setup()
|
> = 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
this.server._checkNamespace(
|
||||||
* @return the reference to the request that originated the Engine.IO connection
|
name,
|
||||||
*
|
auth,
|
||||||
* @public
|
(
|
||||||
*/
|
dynamicNspName:
|
||||||
public get request(): any/** IncomingMessage */ {
|
| Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||||
return this.conn.request
|
| false
|
||||||
}
|
) => {
|
||||||
|
if (dynamicNspName) {
|
||||||
/**
|
this.doConnect(name, auth);
|
||||||
* Sets up event listeners.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private setup() {
|
|
||||||
console.debug(`socket.io client setup conn ${this.conn.id}`)
|
|
||||||
this.onclose = this.onclose.bind(this)
|
|
||||||
this.ondata = this.ondata.bind(this)
|
|
||||||
this.onerror = this.onerror.bind(this)
|
|
||||||
this.ondecoded = this.ondecoded.bind(this)
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
this.decoder.on("decoded", this.ondecoded)
|
|
||||||
this.conn.on("data", this.ondata)
|
|
||||||
this.conn.on("error", this.onerror)
|
|
||||||
this.conn.on("close", this.onclose)
|
|
||||||
|
|
||||||
this.connectTimeout = setTimeout(() => {
|
|
||||||
if (this.nsps.size === 0) {
|
|
||||||
console.debug(`no namespace joined yet, close the client ${this.id}`)
|
|
||||||
this.close()
|
|
||||||
} else {
|
|
||||||
console.debug(`the client ${this.id} has already joined a namespace, nothing to do`)
|
|
||||||
}
|
|
||||||
}, this.server._connectTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects a client to a namespace.
|
|
||||||
*
|
|
||||||
* @param {String} name - the namespace
|
|
||||||
* @param {Object} auth - the auth parameters
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private connect(name: string, auth: object = {}): void {
|
|
||||||
if (this.server._nsps.has(name)) {
|
|
||||||
console.debug(`socket.io client ${this.id} connecting to namespace ${name}`)
|
|
||||||
return this.doConnect(name, auth)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.server._checkNamespace(
|
|
||||||
name,
|
|
||||||
auth,
|
|
||||||
(
|
|
||||||
dynamicNspName:
|
|
||||||
| Namespace<ListenEvents, EmitEvents, ServerSideEvents>
|
|
||||||
| false
|
|
||||||
) => {
|
|
||||||
if (dynamicNspName) {
|
|
||||||
console.debug(`dynamic namespace ${dynamicNspName} was created`)
|
|
||||||
this.doConnect(name, auth)
|
|
||||||
} else {
|
|
||||||
console.debug(`creation of namespace ${name} was denied`)
|
|
||||||
this._packet({
|
|
||||||
type: PacketType.CONNECT_ERROR,
|
|
||||||
nsp: name,
|
|
||||||
data: {
|
|
||||||
message: "Invalid namespace",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects a client to a namespace.
|
|
||||||
*
|
|
||||||
* @param name - the namespace
|
|
||||||
* @param {Object} auth - the auth parameters
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private doConnect(name: string, auth: object): void {
|
|
||||||
const nsp = this.server.of(name)
|
|
||||||
|
|
||||||
// @java-patch multi thread need direct callback socket
|
|
||||||
const socket = nsp._add(this, auth, (socket) => {
|
|
||||||
this.sockets.set(socket.id, socket)
|
|
||||||
this.nsps.set(nsp.name, socket)
|
|
||||||
|
|
||||||
if (this.connectTimeout) {
|
|
||||||
clearTimeout(this.connectTimeout)
|
|
||||||
this.connectTimeout = undefined
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects from all namespaces and closes transport.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_disconnect(): void {
|
|
||||||
for (const socket of this.sockets.values()) {
|
|
||||||
socket.disconnect()
|
|
||||||
}
|
|
||||||
this.sockets.clear()
|
|
||||||
this.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a socket. Called by each `Socket`.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_remove(socket: Socket<ListenEvents, EmitEvents, ServerSideEvents>): void {
|
|
||||||
if (this.sockets.has(socket.id)) {
|
|
||||||
const nsp = this.sockets.get(socket.id)!.nsp.name
|
|
||||||
this.sockets.delete(socket.id)
|
|
||||||
this.nsps.delete(nsp)
|
|
||||||
} else {
|
} 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.
|
* Connects a client to a namespace.
|
||||||
*
|
*
|
||||||
* @private
|
* @param name - the namespace
|
||||||
*/
|
* @param {Object} auth - the auth parameters
|
||||||
private close(): void {
|
*
|
||||||
console.debug(`client ${this.id} close - reason: forcing transport close`)
|
* @private
|
||||||
if ("open" === this.conn.readyState) {
|
*/
|
||||||
console.debug("forcing transport close")
|
private doConnect(name: string, auth: Record<string, unknown>): void {
|
||||||
this.conn.close()
|
const nsp = this.server.of(name);
|
||||||
this.onclose("forced server close")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// @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.
|
* Writes a packet to the transport.
|
||||||
*
|
*
|
||||||
* @param {Object} packet object
|
* @param {Object} packet object
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_packet(packet: Packet | any[], opts: WriteOptions = {}): void {
|
_packet(packet: Packet | any[], opts: WriteOptions = {}): void {
|
||||||
if (this.conn.readyState !== "open") {
|
if (this.conn.readyState !== "open") {
|
||||||
console.debug(`client ${this.id} ignoring packet write ${JSON.stringify(packet)}`)
|
debug("ignoring packet write %j", packet);
|
||||||
return
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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(
|
private writeToEngine(
|
||||||
encodedPacket: String | Buffer,
|
encodedPackets: Array<string | Buffer>,
|
||||||
opts: WriteOptions
|
opts: WriteOptions
|
||||||
): void {
|
): void {
|
||||||
if (opts.volatile && !this.conn.transport.writable) {
|
if (opts.volatile && !this.conn.transport.writable) {
|
||||||
console.debug(`client ${this.id} volatile packet is discarded since the transport is not currently writable`)
|
debug(
|
||||||
return
|
"volatile packet is discarded since the transport is not currently writable"
|
||||||
}
|
);
|
||||||
this.conn.write(encodedPacket, opts)
|
return;
|
||||||
}
|
}
|
||||||
|
const packets = Array.isArray(encodedPackets)
|
||||||
/**
|
? encodedPackets
|
||||||
* Called with incoming transport data.
|
: [encodedPackets];
|
||||||
*
|
for (const encodedPacket of packets) {
|
||||||
* @private
|
this.conn.write(encodedPacket, opts);
|
||||||
*/
|
|
||||||
private ondata(data): void {
|
|
||||||
// try/catch is needed for protocol violations (GH-1880)
|
|
||||||
try {
|
|
||||||
this.decoder.add(data)
|
|
||||||
} catch (error: any) {
|
|
||||||
this.onerror(error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when parser fully decodes a packet.
|
* Called with incoming transport data.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private ondecoded(packet: Packet): void {
|
private ondata(data): void {
|
||||||
if (PacketType.CONNECT === packet.type) {
|
// try/catch is needed for protocol violations (GH-1880)
|
||||||
if (this.conn.protocol === 3) {
|
try {
|
||||||
const parsed = url.parse(packet.nsp, true)
|
this.decoder.add(data);
|
||||||
this.connect(parsed.pathname!, parsed.query)
|
} catch (e) {
|
||||||
} else {
|
debug("invalid packet format");
|
||||||
this.connect(packet.nsp, packet.data)
|
this.onerror(e);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const socket = this.nsps.get(packet.nsp)
|
|
||||||
if (socket) {
|
|
||||||
process.nextTick(function () {
|
|
||||||
socket._onpacket(packet)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
console.debug(`client ${this.id} no socket for namespace ${packet.nsp}.`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles an error.
|
* Called when parser fully decodes a packet.
|
||||||
*
|
*
|
||||||
* @param {Object} err object
|
* @private
|
||||||
* @private
|
*/
|
||||||
*/
|
private ondecoded(packet: Packet): void {
|
||||||
private onerror(err): void {
|
let namespace: string;
|
||||||
for (const socket of this.sockets.values()) {
|
let authPayload: Record<string, unknown>;
|
||||||
socket._onerror(err)
|
if (this.conn.protocol === 3) {
|
||||||
}
|
const parsed = url.parse(packet.nsp, true);
|
||||||
this.conn.close()
|
namespace = parsed.pathname!;
|
||||||
|
authPayload = parsed.query;
|
||||||
|
} else {
|
||||||
|
namespace = packet.nsp;
|
||||||
|
authPayload = packet.data;
|
||||||
}
|
}
|
||||||
|
const socket = this.nsps.get(namespace);
|
||||||
|
|
||||||
/**
|
if (!socket && packet.type === PacketType.CONNECT) {
|
||||||
* Called upon transport close.
|
this.connect(namespace, authPayload);
|
||||||
*
|
} else if (
|
||||||
* @param reason
|
socket &&
|
||||||
* @private
|
packet.type !== PacketType.CONNECT &&
|
||||||
*/
|
packet.type !== PacketType.CONNECT_ERROR
|
||||||
private onclose(reason: string): void {
|
) {
|
||||||
console.debug(`client ${this.id} close with reason ${reason}`)
|
process.nextTick(function () {
|
||||||
|
socket._onpacket(packet);
|
||||||
// ignore a potential subsequent `close` event
|
});
|
||||||
this.destroy()
|
} else {
|
||||||
|
debug("invalid state (packet type: %s)", packet.type);
|
||||||
// `nsps` and `sockets` are cleaned up seamlessly
|
this.close();
|
||||||
for (const socket of this.sockets.values()) {
|
|
||||||
socket._onclose(reason)
|
|
||||||
}
|
|
||||||
this.sockets.clear()
|
|
||||||
|
|
||||||
this.decoder.destroy() // clean up decoder
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleans up event listeners.
|
* Handles an error.
|
||||||
* @private
|
*
|
||||||
*/
|
* @param {Object} err object
|
||||||
private destroy(): void {
|
* @private
|
||||||
this.conn.removeListener("data", this.ondata)
|
*/
|
||||||
this.conn.removeListener("error", this.onerror)
|
private onerror(err): void {
|
||||||
this.conn.removeListener("close", this.onclose)
|
for (const socket of this.sockets.values()) {
|
||||||
// @ts-ignore
|
socket._onerror(err);
|
||||||
this.decoder.removeListener("decoded", this.ondecoded)
|
|
||||||
|
|
||||||
if (this.connectTimeout) {
|
|
||||||
clearTimeout(this.connectTimeout)
|
|
||||||
this.connectTimeout = undefined
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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