scoundrel-remote-eval 1.0.6 → 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 +17 -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 +63 -13
- 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 Client from "../src/client/index.js"
|
|
2
4
|
import ClientWebSocket from "../src/client/connections/web-socket/index.js"
|
|
3
5
|
import Server from "../src/server/index.js"
|
|
@@ -46,4 +48,19 @@ describe("scoundrel - web-socket - javascript", () => {
|
|
|
46
48
|
|
|
47
49
|
expect(result).toEqual("test1, test2")
|
|
48
50
|
})
|
|
51
|
+
|
|
52
|
+
it("handles errors from method calls", async () => {
|
|
53
|
+
const stringObject = await shared.client.newObjectWithReference("Array")
|
|
54
|
+
|
|
55
|
+
let caughtError = null
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
await stringObject.callMethod("nonExistentMethod")
|
|
59
|
+
} catch (error) {
|
|
60
|
+
caughtError = error
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
expect(caughtError).toBeInstanceOf(Error)
|
|
64
|
+
expect(caughtError.message).toEqual("No method called 'nonExistentMethod' on a 'Array'")
|
|
65
|
+
})
|
|
49
66
|
})
|
|
@@ -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,7 +173,18 @@ export default class Client {
|
|
|
156
173
|
return false
|
|
157
174
|
}
|
|
158
175
|
|
|
159
|
-
|
|
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
|
+
*/
|
|
185
|
+
onCommand = ({command, command_id: commandID, data, error, errorStack, ...restArgs}) => {
|
|
186
|
+
logger.log(() => ["onCommand", {command, commandID, data, error, errorStack, restArgs}])
|
|
187
|
+
|
|
160
188
|
try {
|
|
161
189
|
if (!command) {
|
|
162
190
|
throw new Error(`No command key given in data: ${Object.keys(restArgs).join(", ")}`)
|
|
@@ -167,7 +195,7 @@ export default class Client {
|
|
|
167
195
|
if (serverObject) {
|
|
168
196
|
object = serverObject
|
|
169
197
|
} else {
|
|
170
|
-
object =
|
|
198
|
+
object = globalThis[data.object_name]
|
|
171
199
|
|
|
172
200
|
if (!object) throw new Error(`No such object: ${data.object_name}`)
|
|
173
201
|
}
|
|
@@ -181,7 +209,7 @@ export default class Client {
|
|
|
181
209
|
let object
|
|
182
210
|
|
|
183
211
|
if (typeof className == "string") {
|
|
184
|
-
const ClassInstance = this.getClass(className) ||
|
|
212
|
+
const ClassInstance = this.getClass(className) || globalThis[className]
|
|
185
213
|
|
|
186
214
|
if (!ClassInstance) throw new Error(`No such class: ${className}`)
|
|
187
215
|
|
|
@@ -233,20 +261,22 @@ export default class Client {
|
|
|
233
261
|
this.respondToCommand(commandID, {response: attribute})
|
|
234
262
|
}
|
|
235
263
|
} else if (command == "command_response") {
|
|
236
|
-
if (!(commandID in this.outgoingCommands))
|
|
264
|
+
if (!(commandID in this.outgoingCommands)) {
|
|
265
|
+
throw new Error(`Outgoing command ${commandID} not found: ${Object.keys(this.outgoingCommands).join(", ")}`)
|
|
266
|
+
}
|
|
237
267
|
|
|
238
268
|
const savedCommand = this.outgoingCommands[commandID]
|
|
239
269
|
|
|
240
270
|
delete this.outgoingCommands[commandID]
|
|
241
271
|
|
|
242
|
-
if (
|
|
243
|
-
const
|
|
272
|
+
if (error) {
|
|
273
|
+
const errorToThrow = new Error(error)
|
|
244
274
|
|
|
245
|
-
if (
|
|
246
|
-
|
|
275
|
+
if (errorStack) {
|
|
276
|
+
errorToThrow.stack = `${errorStack}\n\n${errorToThrow.stack}`
|
|
247
277
|
}
|
|
248
278
|
|
|
249
|
-
savedCommand.reject(
|
|
279
|
+
savedCommand.reject(errorToThrow)
|
|
250
280
|
} else {
|
|
251
281
|
logger.log(() => [`Resolving command ${commandID} with data`, data])
|
|
252
282
|
savedCommand.resolve(data.data)
|
|
@@ -255,9 +285,13 @@ export default class Client {
|
|
|
255
285
|
throw new Error(`Unknown command: ${command}`)
|
|
256
286
|
}
|
|
257
287
|
} catch (error) {
|
|
258
|
-
|
|
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
|
+
}
|
|
259
293
|
|
|
260
|
-
|
|
294
|
+
logger.error(error)
|
|
261
295
|
}
|
|
262
296
|
}
|
|
263
297
|
|
|
@@ -276,6 +310,7 @@ export default class Client {
|
|
|
276
310
|
__scoundrel_type: "reference"
|
|
277
311
|
}
|
|
278
312
|
} else if (this.isPlainObject(arg)) {
|
|
313
|
+
/** @type {Record<any, any>} */
|
|
279
314
|
const newObject = {}
|
|
280
315
|
|
|
281
316
|
for (const key in arg) {
|
|
@@ -368,10 +403,21 @@ export default class Client {
|
|
|
368
403
|
return this._objects[objectName]
|
|
369
404
|
}
|
|
370
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Responds to a command from the backend
|
|
408
|
+
* @param {number} commandId
|
|
409
|
+
* @param {any} data
|
|
410
|
+
*/
|
|
371
411
|
respondToCommand(commandId, data) {
|
|
372
412
|
this.sendCommand("command_response", {command_id: commandId, data})
|
|
373
413
|
}
|
|
374
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
|
+
*/
|
|
375
421
|
sendCommand(command, data) {
|
|
376
422
|
return new Promise((resolve, reject) => {
|
|
377
423
|
const outgoingCommandCount = ++this.outgoingCommandsCount
|
|
@@ -387,6 +433,10 @@ export default class Client {
|
|
|
387
433
|
})
|
|
388
434
|
}
|
|
389
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Sends data to the backend
|
|
438
|
+
* @param {any} data
|
|
439
|
+
*/
|
|
390
440
|
send(data) {
|
|
391
441
|
this.backend.send(data)
|
|
392
442
|
}
|
|
@@ -407,7 +457,7 @@ export default class Client {
|
|
|
407
457
|
* Spawns a new reference to an object
|
|
408
458
|
*
|
|
409
459
|
* @param {string} id
|
|
410
|
-
* @returns {
|
|
460
|
+
* @returns {Reference}
|
|
411
461
|
*/
|
|
412
462
|
spawnReference(id) {
|
|
413
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
|
|