scoundrel-remote-eval 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/eslint.config.js +18 -0
- package/package.json +10 -2
- package/spec/reference-with-proxy-spec.js +14 -0
- package/spec/web-socket-javascript-spec.js +2 -0
- package/spec/web-socket-python-spec.js +2 -0
- package/src/client/connections/web-socket/index.js +38 -1
- package/src/client/index.js +51 -5
- package/src/client/reference-proxy.js +26 -2
- package/src/client/reference.js +2 -0
- package/src/logger.js +15 -1
- package/src/python-web-socket-runner.js +31 -2
- package/src/server/connections/web-socket/client.js +18 -0
- package/src/server/connections/web-socket/index.js +12 -0
- package/src/server/index.js +12 -0
- package/tsconfig.json +13 -0
package/eslint.config.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import js from "@eslint/js"
|
|
2
|
+
import globals from "globals"
|
|
3
|
+
import {defineConfig} from "eslint/config"
|
|
4
|
+
|
|
5
|
+
export default defineConfig([
|
|
6
|
+
{
|
|
7
|
+
files: ["**/*.{js,mjs,cjs}"],
|
|
8
|
+
plugins: {js},
|
|
9
|
+
extends: ["js/recommended"],
|
|
10
|
+
languageOptions: {
|
|
11
|
+
globals: {
|
|
12
|
+
...globals.browser,
|
|
13
|
+
...globals.node,
|
|
14
|
+
...globals.jasmine
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
])
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scoundrel-remote-eval",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.8",
|
|
5
5
|
"description": "",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"scripts": {
|
|
8
|
+
"lint": "eslint",
|
|
9
|
+
"typecheck": "tsc --noEmit",
|
|
8
10
|
"test": "jasmine"
|
|
9
11
|
},
|
|
10
12
|
"repository": {
|
|
@@ -21,7 +23,13 @@
|
|
|
21
23
|
},
|
|
22
24
|
"homepage": "https://github.com/kaspernj/scoundrel#readme",
|
|
23
25
|
"devDependencies": {
|
|
24
|
-
"
|
|
26
|
+
"@eslint/js": "^9.39.2",
|
|
27
|
+
"@types/jasmine": "^5.1.13",
|
|
28
|
+
"@types/node": "^25.0.2",
|
|
29
|
+
"eslint": "^9.39.2",
|
|
30
|
+
"globals": "^16.5.0",
|
|
31
|
+
"jasmine": "^5.1.0",
|
|
32
|
+
"typescript": "^5.9.3"
|
|
25
33
|
},
|
|
26
34
|
"dependencies": {
|
|
27
35
|
"ws": "^8.14.2"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import Client from "../src/client/index.js"
|
|
2
4
|
import ClientWebSocket from "../src/client/connections/web-socket/index.js"
|
|
3
5
|
import referenceWithProxy from "../src/client/reference-proxy.js"
|
|
@@ -36,9 +38,13 @@ describe("referenceWithProxy", () => {
|
|
|
36
38
|
const stringObjectReference = await shared.client.newObjectWithReference("Array")
|
|
37
39
|
const stringObject = referenceWithProxy(stringObjectReference)
|
|
38
40
|
|
|
41
|
+
// @ts-ignore
|
|
39
42
|
await stringObject.push("test1")
|
|
43
|
+
|
|
44
|
+
// @ts-ignore
|
|
40
45
|
await stringObject.push("test2")
|
|
41
46
|
|
|
47
|
+
// @ts-ignore
|
|
42
48
|
const result = await stringObject.__serialize()
|
|
43
49
|
|
|
44
50
|
expect(result).toEqual(["test1", "test2"])
|
|
@@ -65,9 +71,13 @@ describe("referenceWithProxy", () => {
|
|
|
65
71
|
const stringObjectReference = await shared.client.newObjectWithReference("Array")
|
|
66
72
|
const stringObject = referenceWithProxy(stringObjectReference)
|
|
67
73
|
|
|
74
|
+
// @ts-ignore
|
|
68
75
|
await stringObject.push("test1")
|
|
76
|
+
|
|
77
|
+
// @ts-ignore
|
|
69
78
|
await stringObject.push("test2")
|
|
70
79
|
|
|
80
|
+
// @ts-ignore
|
|
71
81
|
const result = await stringObject.__serialize()
|
|
72
82
|
|
|
73
83
|
expect(result).toEqual(["test1", "test2"])
|
|
@@ -77,9 +87,13 @@ describe("referenceWithProxy", () => {
|
|
|
77
87
|
const stringObjectReference = await shared.serverClient.newObjectWithReference("Array")
|
|
78
88
|
const stringObject = referenceWithProxy(stringObjectReference)
|
|
79
89
|
|
|
90
|
+
// @ts-ignore
|
|
80
91
|
await stringObject.push("test1")
|
|
92
|
+
|
|
93
|
+
// @ts-ignore
|
|
81
94
|
await stringObject.push("test2")
|
|
82
95
|
|
|
96
|
+
// @ts-ignore
|
|
83
97
|
const result = await stringObject.__serialize()
|
|
84
98
|
|
|
85
99
|
expect(result).toEqual(["test1", "test2"])
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import Logger from "../../../logger.js"
|
|
2
4
|
|
|
3
5
|
const logger = new Logger("Scoundrel WebSocket")
|
|
@@ -5,10 +7,20 @@ const logger = new Logger("Scoundrel WebSocket")
|
|
|
5
7
|
// logger.setDebug(true)
|
|
6
8
|
|
|
7
9
|
export default class WebSocket {
|
|
10
|
+
/**
|
|
11
|
+
* Creates a new WebSocket connection handler
|
|
12
|
+
* @param {WebSocket} ws The WebSocket instance
|
|
13
|
+
*/
|
|
8
14
|
constructor(ws) {
|
|
9
15
|
this.ws = ws
|
|
16
|
+
|
|
17
|
+
// @ts-ignore
|
|
10
18
|
this.ws.addEventListener("error", this.onSocketError)
|
|
19
|
+
|
|
20
|
+
// @ts-ignore
|
|
11
21
|
this.ws.addEventListener("open", this.onSocketOpen)
|
|
22
|
+
|
|
23
|
+
// @ts-ignore
|
|
12
24
|
this.ws.addEventListener("message", this.onSocketMessage)
|
|
13
25
|
|
|
14
26
|
this.commands = {}
|
|
@@ -19,33 +31,58 @@ export default class WebSocket {
|
|
|
19
31
|
await this.ws.close()
|
|
20
32
|
}
|
|
21
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @param {(data: any) => void} callback
|
|
36
|
+
*/
|
|
22
37
|
onCommand(callback) {
|
|
23
38
|
this.onCommandCallback = callback
|
|
24
39
|
}
|
|
25
40
|
|
|
41
|
+
/**
|
|
42
|
+
* @param {Event} event
|
|
43
|
+
*/
|
|
26
44
|
onSocketError = (event) => {
|
|
27
45
|
logger.error(() => ["onSocketError", event])
|
|
28
46
|
}
|
|
29
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @param {MessageEvent} event
|
|
50
|
+
*/
|
|
30
51
|
onSocketMessage = (event) => {
|
|
31
52
|
const data = JSON.parse(event.data)
|
|
32
53
|
|
|
33
54
|
logger.log(() => ["Client::Connections::WebSocket onSocketMessage", data])
|
|
55
|
+
|
|
56
|
+
if (!this.onCommandCallback) {
|
|
57
|
+
throw new Error("No onCommand callback set, ignoring message")
|
|
58
|
+
}
|
|
59
|
+
|
|
34
60
|
this.onCommandCallback(data)
|
|
35
61
|
}
|
|
36
62
|
|
|
37
|
-
|
|
63
|
+
/**
|
|
64
|
+
* @param {Event} _event
|
|
65
|
+
*/
|
|
66
|
+
onSocketOpen = (_event) => { // eslint-disable-line no-unused-vars
|
|
38
67
|
logger.log("onSocketOpen")
|
|
39
68
|
}
|
|
40
69
|
|
|
70
|
+
/**
|
|
71
|
+
* @param {Record<string, any>} data
|
|
72
|
+
*/
|
|
41
73
|
send(data) {
|
|
42
74
|
const sendData = JSON.stringify(data)
|
|
43
75
|
logger.log(() => ["Sending", sendData])
|
|
76
|
+
|
|
77
|
+
// @ts-ignore
|
|
44
78
|
this.ws.send(sendData)
|
|
45
79
|
}
|
|
46
80
|
|
|
47
81
|
waitForOpened = () => new Promise((resolve, reject) => {
|
|
82
|
+
// @ts-ignore
|
|
48
83
|
this.ws.addEventListener("open", resolve)
|
|
84
|
+
|
|
85
|
+
// @ts-ignore
|
|
49
86
|
this.ws.addEventListener("error", reject)
|
|
50
87
|
})
|
|
51
88
|
}
|
package/src/client/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import Logger from "../logger.js"
|
|
2
4
|
import Reference from "./reference.js"
|
|
3
5
|
|
|
@@ -15,14 +17,23 @@ export default class Client {
|
|
|
15
17
|
this.backend = backend
|
|
16
18
|
this.backend.onCommand(this.onCommand)
|
|
17
19
|
|
|
20
|
+
/** @type {Record<number, any>} */
|
|
18
21
|
this.outgoingCommands = {}
|
|
19
22
|
this.incomingCommands = {}
|
|
20
23
|
this.outgoingCommandsCount = 0
|
|
21
24
|
|
|
25
|
+
/** @type {Record<string, any>} */
|
|
22
26
|
this._classes = {}
|
|
27
|
+
|
|
28
|
+
/** @type {Record<string, any>} */
|
|
23
29
|
this._objects = {}
|
|
30
|
+
|
|
31
|
+
/** @type {Record<string, Reference>} */
|
|
24
32
|
this.references = {}
|
|
33
|
+
|
|
34
|
+
/** @type {Record<number, any>} */
|
|
25
35
|
this.objects = {}
|
|
36
|
+
|
|
26
37
|
this.objectsCount = 0
|
|
27
38
|
}
|
|
28
39
|
|
|
@@ -130,7 +141,8 @@ export default class Client {
|
|
|
130
141
|
/**
|
|
131
142
|
* Spawns a new reference to an object
|
|
132
143
|
*
|
|
133
|
-
* @param {string}
|
|
144
|
+
* @param {string} className
|
|
145
|
+
* @param {...any} args
|
|
134
146
|
* @returns {Promise<Reference>}
|
|
135
147
|
*/
|
|
136
148
|
async newObjectWithReference(className, ...args) {
|
|
@@ -148,6 +160,11 @@ export default class Client {
|
|
|
148
160
|
return this.spawnReference(id)
|
|
149
161
|
}
|
|
150
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Checks if the input is a plain object
|
|
165
|
+
* @param {any} input
|
|
166
|
+
* @returns {boolean}
|
|
167
|
+
*/
|
|
151
168
|
isPlainObject(input) {
|
|
152
169
|
if (input && typeof input === "object" && !Array.isArray(input)) {
|
|
153
170
|
return true
|
|
@@ -156,6 +173,15 @@ export default class Client {
|
|
|
156
173
|
return false
|
|
157
174
|
}
|
|
158
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Handles an incoming command from the backend
|
|
178
|
+
* @param {object} args
|
|
179
|
+
* @param {string} args.command
|
|
180
|
+
* @param {number} args.command_id
|
|
181
|
+
* @param {any} args.data
|
|
182
|
+
* @param {string} [args.error]
|
|
183
|
+
* @param {string} [args.errorStack]
|
|
184
|
+
*/
|
|
159
185
|
onCommand = ({command, command_id: commandID, data, error, errorStack, ...restArgs}) => {
|
|
160
186
|
logger.log(() => ["onCommand", {command, commandID, data, error, errorStack, restArgs}])
|
|
161
187
|
|
|
@@ -169,7 +195,7 @@ export default class Client {
|
|
|
169
195
|
if (serverObject) {
|
|
170
196
|
object = serverObject
|
|
171
197
|
} else {
|
|
172
|
-
object =
|
|
198
|
+
object = globalThis[data.object_name]
|
|
173
199
|
|
|
174
200
|
if (!object) throw new Error(`No such object: ${data.object_name}`)
|
|
175
201
|
}
|
|
@@ -183,7 +209,7 @@ export default class Client {
|
|
|
183
209
|
let object
|
|
184
210
|
|
|
185
211
|
if (typeof className == "string") {
|
|
186
|
-
const ClassInstance = this.getClass(className) ||
|
|
212
|
+
const ClassInstance = this.getClass(className) || globalThis[className]
|
|
187
213
|
|
|
188
214
|
if (!ClassInstance) throw new Error(`No such class: ${className}`)
|
|
189
215
|
|
|
@@ -259,7 +285,11 @@ export default class Client {
|
|
|
259
285
|
throw new Error(`Unknown command: ${command}`)
|
|
260
286
|
}
|
|
261
287
|
} catch (error) {
|
|
262
|
-
|
|
288
|
+
if (error instanceof Error) {
|
|
289
|
+
this.send({command: "command_response", command_id: commandID, error: error.message, errorStack: error.stack})
|
|
290
|
+
} else {
|
|
291
|
+
this.send({command: "command_response", command_id: commandID, error: String(error)})
|
|
292
|
+
}
|
|
263
293
|
|
|
264
294
|
logger.error(error)
|
|
265
295
|
}
|
|
@@ -280,6 +310,7 @@ export default class Client {
|
|
|
280
310
|
__scoundrel_type: "reference"
|
|
281
311
|
}
|
|
282
312
|
} else if (this.isPlainObject(arg)) {
|
|
313
|
+
/** @type {Record<any, any>} */
|
|
283
314
|
const newObject = {}
|
|
284
315
|
|
|
285
316
|
for (const key in arg) {
|
|
@@ -372,10 +403,21 @@ export default class Client {
|
|
|
372
403
|
return this._objects[objectName]
|
|
373
404
|
}
|
|
374
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Responds to a command from the backend
|
|
408
|
+
* @param {number} commandId
|
|
409
|
+
* @param {any} data
|
|
410
|
+
*/
|
|
375
411
|
respondToCommand(commandId, data) {
|
|
376
412
|
this.sendCommand("command_response", {command_id: commandId, data})
|
|
377
413
|
}
|
|
378
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Sends a command to the backend and returns a promise that resolves with the response
|
|
417
|
+
* @param {string} command
|
|
418
|
+
* @param {any} data
|
|
419
|
+
* @returns {Promise<any>}
|
|
420
|
+
*/
|
|
379
421
|
sendCommand(command, data) {
|
|
380
422
|
return new Promise((resolve, reject) => {
|
|
381
423
|
const outgoingCommandCount = ++this.outgoingCommandsCount
|
|
@@ -391,6 +433,10 @@ export default class Client {
|
|
|
391
433
|
})
|
|
392
434
|
}
|
|
393
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Sends data to the backend
|
|
438
|
+
* @param {any} data
|
|
439
|
+
*/
|
|
394
440
|
send(data) {
|
|
395
441
|
this.backend.send(data)
|
|
396
442
|
}
|
|
@@ -411,7 +457,7 @@ export default class Client {
|
|
|
411
457
|
* Spawns a new reference to an object
|
|
412
458
|
*
|
|
413
459
|
* @param {string} id
|
|
414
|
-
* @returns {
|
|
460
|
+
* @returns {Reference}
|
|
415
461
|
*/
|
|
416
462
|
spawnReference(id) {
|
|
417
463
|
const reference = new Reference(this, id)
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {import("./reference.js").default} reference
|
|
5
|
+
* @param {string} prop
|
|
6
|
+
* @returns {(...args: any[]) => Promise<any>}
|
|
7
|
+
*/
|
|
1
8
|
const proxyMethodSpawner = (reference, prop) => (...args) => reference.callMethodWithReference(prop, ...args)
|
|
2
9
|
|
|
3
10
|
const proxyObjectHandler = {
|
|
11
|
+
/**
|
|
12
|
+
* @param {import("./reference.js").default|(() => import("./reference.js").default)} reference
|
|
13
|
+
* @param {string} prop
|
|
14
|
+
* @returns {any}
|
|
15
|
+
*/
|
|
4
16
|
get(reference, prop) {
|
|
5
17
|
if (typeof reference == "function") reference = reference()
|
|
6
18
|
|
|
@@ -14,16 +26,28 @@ const proxyObjectHandler = {
|
|
|
14
26
|
return proxyMethodSpawner(reference, prop)
|
|
15
27
|
},
|
|
16
28
|
|
|
29
|
+
/**
|
|
30
|
+
* @param {import("./reference.js").default|(() => import("./reference.js").default)} receiver
|
|
31
|
+
* @param {string} prop
|
|
32
|
+
* @param {any} newValue
|
|
33
|
+
*/
|
|
17
34
|
set(receiver, prop, newValue) {
|
|
18
35
|
throw new Error("set property isn't supported yet")
|
|
19
36
|
|
|
20
|
-
|
|
21
|
-
if (
|
|
37
|
+
// @ts-expect-error
|
|
38
|
+
if (typeof receiver == "function") receiver = receiver() // eslint-disable-line no-unreachable
|
|
39
|
+
|
|
40
|
+
// @ts-expect-error
|
|
41
|
+
if (!(prop in receiver)) throw new PropertyNotFoundError(`Property not found: ${prop}`) // eslint-disable-line no-undef
|
|
22
42
|
|
|
23
43
|
return Reflect.set(receiver, prop, newValue)
|
|
24
44
|
}
|
|
25
45
|
}
|
|
26
46
|
|
|
47
|
+
/**
|
|
48
|
+
* @param {any} wrappedObject
|
|
49
|
+
* @returns {Proxy}
|
|
50
|
+
*/
|
|
27
51
|
const referenceProxy = (wrappedObject) => new Proxy(wrappedObject, proxyObjectHandler)
|
|
28
52
|
|
|
29
53
|
export default referenceProxy
|
package/src/client/reference.js
CHANGED
package/src/logger.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
export default class Logger {
|
|
2
4
|
/**
|
|
3
5
|
* Creates a new Logger instance
|
|
@@ -29,13 +31,25 @@ export default class Logger {
|
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
33
|
* Logs a message to the console if debug is enabled
|
|
32
|
-
*
|
|
33
34
|
* @param {...any} args
|
|
34
35
|
*/
|
|
35
36
|
log(...args) {
|
|
36
37
|
return this._sendToConsole("log", ...args)
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Logs a warning message to the console if debug is enabled
|
|
42
|
+
* @param {...any} args
|
|
43
|
+
*/
|
|
44
|
+
warn(...args) {
|
|
45
|
+
return this._sendToConsole("warn", ...args)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Sends the log message to the console
|
|
50
|
+
* @param {string} logType
|
|
51
|
+
* @param {...any} args
|
|
52
|
+
*/
|
|
39
53
|
_sendToConsole(logType, ...args) {
|
|
40
54
|
if (!this.debug) {
|
|
41
55
|
return
|
|
@@ -1,12 +1,23 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import {exec, spawn} from "child_process"
|
|
2
4
|
import Logger from "./logger.js"
|
|
3
|
-
import {realpath} from "
|
|
5
|
+
import {realpath} from "fs/promises"
|
|
4
6
|
|
|
5
7
|
const logger = new Logger("Scoundrel PythonWebSocketRunner")
|
|
6
8
|
|
|
7
9
|
// logger.setDebug(true)
|
|
8
10
|
|
|
9
11
|
export default class PythonWebSocketRunner {
|
|
12
|
+
/** @type {Error | null} */
|
|
13
|
+
waitForPidRejectError = null
|
|
14
|
+
|
|
15
|
+
/** @type {((value: any) => void) | null} */
|
|
16
|
+
waitForPidResolve = null
|
|
17
|
+
|
|
18
|
+
/** @type {((value: any) => void) | null} */
|
|
19
|
+
waitForPidReject = null
|
|
20
|
+
|
|
10
21
|
constructor() {
|
|
11
22
|
process.on("exit", this.onProcessExit)
|
|
12
23
|
}
|
|
@@ -36,10 +47,18 @@ export default class PythonWebSocketRunner {
|
|
|
36
47
|
}
|
|
37
48
|
}
|
|
38
49
|
|
|
50
|
+
/**
|
|
51
|
+
* @param {number} code
|
|
52
|
+
* @param {string} signal
|
|
53
|
+
*/
|
|
39
54
|
onChildExit = (code, signal) => {
|
|
40
55
|
logger.log(() => `Child process exited with code ${code} and signal ${signal}`)
|
|
41
56
|
|
|
42
57
|
if (this.waitForPidRejectError) {
|
|
58
|
+
if (!this.waitForPidReject) {
|
|
59
|
+
throw new Error("waitForPidReject is undefined while waitForPidRejectError is set")
|
|
60
|
+
}
|
|
61
|
+
|
|
43
62
|
this.waitForPidReject(this.waitForPidRejectError)
|
|
44
63
|
this.waitForPidResolve = null
|
|
45
64
|
this.waitForPidReject = null
|
|
@@ -52,6 +71,9 @@ export default class PythonWebSocketRunner {
|
|
|
52
71
|
}
|
|
53
72
|
}
|
|
54
73
|
|
|
74
|
+
/**
|
|
75
|
+
* @param {string | Buffer} data
|
|
76
|
+
*/
|
|
55
77
|
onChildStderr = (data) => {
|
|
56
78
|
logger.error(() => `stderr: ${data}`)
|
|
57
79
|
|
|
@@ -60,6 +82,9 @@ export default class PythonWebSocketRunner {
|
|
|
60
82
|
}
|
|
61
83
|
}
|
|
62
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @param {string | Buffer} data
|
|
87
|
+
*/
|
|
63
88
|
onChildStdout = (data) => {
|
|
64
89
|
logger.log(() => `stdout: ${data}`)
|
|
65
90
|
|
|
@@ -71,7 +96,11 @@ export default class PythonWebSocketRunner {
|
|
|
71
96
|
logger.log(() => `Registered PID ${this.pid}`)
|
|
72
97
|
|
|
73
98
|
if (this.waitForPidResolve) {
|
|
74
|
-
this.waitForPidResolve
|
|
99
|
+
if (!this.waitForPidResolve) {
|
|
100
|
+
throw new Error("waitForPidResolve is undefined while waitForPidRejectError is set")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.waitForPidResolve(null)
|
|
75
104
|
this.waitForPidResolve = null
|
|
76
105
|
this.waitForPidReject = null
|
|
77
106
|
this.waitForPidRejectError = null
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
export default class WebSocketClient {
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new WebSocketClient
|
|
6
|
+
* @param {import("ws").WebSocket} ws The WebSocket instance
|
|
7
|
+
*/
|
|
2
8
|
constructor(ws) {
|
|
3
9
|
this.ws = ws
|
|
4
10
|
|
|
@@ -6,14 +12,23 @@ export default class WebSocketClient {
|
|
|
6
12
|
ws.on("message", this.onMessage)
|
|
7
13
|
}
|
|
8
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @param {(data: any) => void} callback
|
|
17
|
+
*/
|
|
9
18
|
onCommand(callback) {
|
|
10
19
|
this.onCommandCallback = callback
|
|
11
20
|
}
|
|
12
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @param {Error} error
|
|
24
|
+
*/
|
|
13
25
|
onError = (error) => {
|
|
14
26
|
console.error("WebSocketClient error", error)
|
|
15
27
|
}
|
|
16
28
|
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} rawData
|
|
31
|
+
*/
|
|
17
32
|
onMessage = (rawData) => {
|
|
18
33
|
const data = JSON.parse(rawData)
|
|
19
34
|
|
|
@@ -22,6 +37,9 @@ export default class WebSocketClient {
|
|
|
22
37
|
this.onCommandCallback(data)
|
|
23
38
|
}
|
|
24
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @param {any} data
|
|
42
|
+
*/
|
|
25
43
|
async send(data) {
|
|
26
44
|
await this.ws.send(JSON.stringify(data))
|
|
27
45
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import WebSocketClient from "./client.js"
|
|
2
4
|
|
|
3
5
|
export default class WebSocket {
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new WebSocket connection handler
|
|
8
|
+
* @param {import("ws").Server} webSocketServer The WebSocket server instance
|
|
9
|
+
*/
|
|
4
10
|
constructor(webSocketServer) {
|
|
5
11
|
this.wss = webSocketServer
|
|
6
12
|
this.wss.on("connection", this.onConnection)
|
|
@@ -8,12 +14,18 @@ export default class WebSocket {
|
|
|
8
14
|
|
|
9
15
|
close() { this.wss.close() }
|
|
10
16
|
|
|
17
|
+
/**
|
|
18
|
+
* @param {import("ws").WebSocket} ws
|
|
19
|
+
*/
|
|
11
20
|
onConnection = (ws) => {
|
|
12
21
|
if (!this.onNewClientCallback) throw new Error("'onNewClient' hasn't been called")
|
|
13
22
|
|
|
14
23
|
this.onNewClientCallback(new WebSocketClient(ws))
|
|
15
24
|
}
|
|
16
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @param {(client: import("./client.js").default) => void} callback
|
|
28
|
+
*/
|
|
17
29
|
onNewClient(callback) {
|
|
18
30
|
if (!callback) throw new Error("No callback was given")
|
|
19
31
|
|
package/src/server/index.js
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import Client from "../client/index.js"
|
|
2
4
|
import EventEmitter from "events"
|
|
3
5
|
|
|
4
6
|
export default class ScoundrelServer {
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new Scoundrel server
|
|
9
|
+
* @param {import("./connections/web-socket/index.js").default} backend The backend connection handler
|
|
10
|
+
*/
|
|
5
11
|
constructor(backend) {
|
|
6
12
|
this.backend = backend
|
|
7
13
|
this.backend.onNewClient(this.onNewClient)
|
|
14
|
+
|
|
15
|
+
/** @type {Client[]} */
|
|
8
16
|
this.clients = []
|
|
17
|
+
|
|
9
18
|
this.events = new EventEmitter()
|
|
10
19
|
}
|
|
11
20
|
|
|
12
21
|
close() { this.backend.close() }
|
|
13
22
|
getClients() { return this.clients }
|
|
14
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @param {import("./connections/web-socket/client.js").default} clientBackend
|
|
26
|
+
*/
|
|
15
27
|
onNewClient = (clientBackend) => {
|
|
16
28
|
const client = new Client(clientBackend)
|
|
17
29
|
|