scoundrel-remote-eval 1.0.8 → 1.0.10

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.
Files changed (50) hide show
  1. package/build/client/connections/web-socket/index.d.ts +34 -0
  2. package/build/client/connections/web-socket/index.d.ts.map +1 -0
  3. package/build/client/connections/web-socket/index.js +68 -0
  4. package/build/client/index.d.ts +186 -0
  5. package/build/client/index.d.ts.map +1 -0
  6. package/build/client/index.js +452 -0
  7. package/build/client/reference-proxy.d.ts +7 -0
  8. package/build/client/reference-proxy.d.ts.map +1 -0
  9. package/build/client/reference-proxy.js +41 -0
  10. package/build/client/reference.d.ts +50 -0
  11. package/build/client/reference.d.ts.map +1 -0
  12. package/build/client/reference.js +63 -0
  13. package/build/index.d.ts +2 -0
  14. package/build/index.d.ts.map +1 -0
  15. package/build/index.js +1 -0
  16. package/build/logger.d.ts +39 -0
  17. package/build/logger.d.ts.map +1 -0
  18. package/build/logger.js +64 -0
  19. package/build/python-web-socket-runner.d.ts +27 -0
  20. package/build/python-web-socket-runner.d.ts.map +1 -0
  21. package/build/python-web-socket-runner.js +94 -0
  22. package/build/server/connections/web-socket/client.d.ts +26 -0
  23. package/build/server/connections/web-socket/client.d.ts.map +1 -0
  24. package/build/server/connections/web-socket/client.js +39 -0
  25. package/build/server/connections/web-socket/index.d.ts +19 -0
  26. package/build/server/connections/web-socket/index.d.ts.map +1 -0
  27. package/build/server/connections/web-socket/index.js +29 -0
  28. package/build/server/index.d.ts +27 -0
  29. package/build/server/index.d.ts.map +1 -0
  30. package/build/server/index.js +34 -0
  31. package/build/utils/single-event-emitter.d.ts +46 -0
  32. package/build/utils/single-event-emitter.d.ts.map +1 -0
  33. package/build/utils/single-event-emitter.js +86 -0
  34. package/package.json +9 -3
  35. package/eslint.config.js +0 -18
  36. package/spec/reference-with-proxy-spec.js +0 -101
  37. package/spec/support/jasmine.json +0 -13
  38. package/spec/web-socket-javascript-spec.js +0 -66
  39. package/spec/web-socket-python-spec.js +0 -57
  40. package/src/client/connections/web-socket/index.js +0 -88
  41. package/src/client/index.js +0 -469
  42. package/src/client/reference-proxy.js +0 -53
  43. package/src/client/reference.js +0 -69
  44. package/src/index.js +0 -0
  45. package/src/logger.js +0 -70
  46. package/src/python-web-socket-runner.js +0 -116
  47. package/src/server/connections/web-socket/client.js +0 -46
  48. package/src/server/connections/web-socket/index.js +0 -34
  49. package/src/server/index.js +0 -33
  50. package/tsconfig.json +0 -13
@@ -1,13 +0,0 @@
1
- {
2
- "spec_dir": "spec",
3
- "spec_files": [
4
- "**/*[sS]pec.?(m)js"
5
- ],
6
- "helpers": [
7
- "helpers/**/*.?(m)js"
8
- ],
9
- "env": {
10
- "stopSpecOnExpectationFailure": false,
11
- "random": true
12
- }
13
- }
@@ -1,66 +0,0 @@
1
- // @ts-check
2
-
3
- import Client from "../src/client/index.js"
4
- import ClientWebSocket from "../src/client/connections/web-socket/index.js"
5
- import Server from "../src/server/index.js"
6
- import ServerWebSocket from "../src/server/connections/web-socket/index.js"
7
- import {WebSocket, WebSocketServer} from "ws"
8
-
9
- const shared = {}
10
-
11
- describe("scoundrel - web-socket - javascript", () => {
12
- beforeEach(async () => {
13
- shared.wss = new WebSocketServer({port: 8080})
14
- shared.serverWebSocket = new ServerWebSocket(shared.wss)
15
- shared.server = new Server(shared.serverWebSocket)
16
-
17
- shared.ws = new WebSocket("http://localhost:8080")
18
- shared.clientWebSocket = new ClientWebSocket(shared.ws)
19
-
20
- await shared.clientWebSocket.waitForOpened()
21
-
22
- shared.client = new Client(shared.clientWebSocket)
23
- })
24
-
25
- afterEach(async () => {
26
- await shared.client.close()
27
- await shared.server.close()
28
- })
29
-
30
- it("creates a server and connects to it with the client", async () => {
31
- const stringObject = await shared.client.newObjectWithReference("Array")
32
-
33
- await stringObject.callMethod("push", "test1")
34
- await stringObject.callMethod("push", "test2")
35
-
36
- const result = await stringObject.serialize()
37
-
38
- expect(result).toEqual(["test1", "test2"])
39
- })
40
-
41
- it("returns results from method calls", async () => {
42
- const stringObject = await shared.client.newObjectWithReference("Array")
43
-
44
- await stringObject.callMethod("push", "test1")
45
- await stringObject.callMethod("push", "test2")
46
-
47
- const result = await stringObject.callMethod("join", ", ")
48
-
49
- expect(result).toEqual("test1, test2")
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
- })
66
- })
@@ -1,57 +0,0 @@
1
- // @ts-check
2
-
3
- import Client from "../src/client/index.js"
4
- import ClientWebSocket from "../src/client/connections/web-socket/index.js"
5
- import Logger from "../src/logger.js"
6
- import PythonWebSocketRunner from "../src/python-web-socket-runner.js"
7
- import {WebSocket} from "ws"
8
-
9
- const shared = {}
10
- const logger = new Logger("Scoundrel WebSocket Python Spec")
11
-
12
- describe("scoundrel - web-socket - python", () => {
13
- beforeEach(async () => {
14
- logger.log("Starting Python with client")
15
- shared.pythonWebSocketRunner = new PythonWebSocketRunner()
16
-
17
- logger.log("Running Python WebSocket runner and waiting for PID")
18
- await shared.pythonWebSocketRunner.runAndWaitForPid()
19
-
20
- logger.log("Starting WebSocket client connection")
21
- const ws = new WebSocket("ws://localhost:53874")
22
-
23
- logger.log("Creating ClientWebSocket")
24
- const clientWebSocket = new ClientWebSocket(ws)
25
-
26
- logger.log("Waiting for WebSocket to open")
27
- await clientWebSocket.waitForOpened()
28
-
29
- logger.log("Creating Scoundrel Client")
30
- shared.client = new Client(clientWebSocket)
31
- })
32
-
33
- afterEach(async () => {
34
- shared.client?.close()
35
- shared.pythonWebSocketRunner?.close()
36
- })
37
-
38
- it("creates a server and connects to it with the client", async () => {
39
- const stringObject = await shared.client.newObjectWithReference("[]")
40
-
41
- await stringObject.callMethod("append", "test1")
42
- await stringObject.callMethod("append", "test2")
43
-
44
- const result = await stringObject.serialize()
45
-
46
- expect(result).toEqual(["test1", "test2"])
47
- })
48
-
49
- it("imports classes and uses them", async () => {
50
- const math = await shared.client.import("math")
51
- const pi = await math.readAttributeWithReference("pi")
52
- const cosOfPi = await math.callMethodWithReference("cos", pi)
53
- const result = await cosOfPi.serialize()
54
-
55
- expect(result).toEqual(-1)
56
- })
57
- })
@@ -1,88 +0,0 @@
1
- // @ts-check
2
-
3
- import Logger from "../../../logger.js"
4
-
5
- const logger = new Logger("Scoundrel WebSocket")
6
-
7
- // logger.setDebug(true)
8
-
9
- export default class WebSocket {
10
- /**
11
- * Creates a new WebSocket connection handler
12
- * @param {WebSocket} ws The WebSocket instance
13
- */
14
- constructor(ws) {
15
- this.ws = ws
16
-
17
- // @ts-ignore
18
- this.ws.addEventListener("error", this.onSocketError)
19
-
20
- // @ts-ignore
21
- this.ws.addEventListener("open", this.onSocketOpen)
22
-
23
- // @ts-ignore
24
- this.ws.addEventListener("message", this.onSocketMessage)
25
-
26
- this.commands = {}
27
- this.commandsCount = 0
28
- }
29
-
30
- async close() {
31
- await this.ws.close()
32
- }
33
-
34
- /**
35
- * @param {(data: any) => void} callback
36
- */
37
- onCommand(callback) {
38
- this.onCommandCallback = callback
39
- }
40
-
41
- /**
42
- * @param {Event} event
43
- */
44
- onSocketError = (event) => {
45
- logger.error(() => ["onSocketError", event])
46
- }
47
-
48
- /**
49
- * @param {MessageEvent} event
50
- */
51
- onSocketMessage = (event) => {
52
- const data = JSON.parse(event.data)
53
-
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
-
60
- this.onCommandCallback(data)
61
- }
62
-
63
- /**
64
- * @param {Event} _event
65
- */
66
- onSocketOpen = (_event) => { // eslint-disable-line no-unused-vars
67
- logger.log("onSocketOpen")
68
- }
69
-
70
- /**
71
- * @param {Record<string, any>} data
72
- */
73
- send(data) {
74
- const sendData = JSON.stringify(data)
75
- logger.log(() => ["Sending", sendData])
76
-
77
- // @ts-ignore
78
- this.ws.send(sendData)
79
- }
80
-
81
- waitForOpened = () => new Promise((resolve, reject) => {
82
- // @ts-ignore
83
- this.ws.addEventListener("open", resolve)
84
-
85
- // @ts-ignore
86
- this.ws.addEventListener("error", reject)
87
- })
88
- }
@@ -1,469 +0,0 @@
1
- // @ts-check
2
-
3
- import Logger from "../logger.js"
4
- import Reference from "./reference.js"
5
-
6
- const logger = new Logger("Scoundrel Client")
7
-
8
- // logger.setDebug(true)
9
-
10
- export default class Client {
11
- /**
12
- * Creates a new Scoundrel Client
13
- *
14
- * @param {any} backend The backend connection (e.g., WebSocket)
15
- */
16
- constructor(backend) {
17
- this.backend = backend
18
- this.backend.onCommand(this.onCommand)
19
-
20
- /** @type {Record<number, any>} */
21
- this.outgoingCommands = {}
22
- this.incomingCommands = {}
23
- this.outgoingCommandsCount = 0
24
-
25
- /** @type {Record<string, any>} */
26
- this._classes = {}
27
-
28
- /** @type {Record<string, any>} */
29
- this._objects = {}
30
-
31
- /** @type {Record<string, Reference>} */
32
- this.references = {}
33
-
34
- /** @type {Record<number, any>} */
35
- this.objects = {}
36
-
37
- this.objectsCount = 0
38
- }
39
-
40
- /**
41
- * Closes the client connection
42
- */
43
- async close() {
44
- this.backend.close()
45
- }
46
-
47
- /**
48
- * Calls a method on a reference and returns the result directly
49
- *
50
- * @param {number} referenceId
51
- * @param {string} methodName
52
- * @param {...any} args
53
- * @returns {Promise<any>}
54
- */
55
- async callMethodOnReference(referenceId, methodName, ...args) {
56
- const result = await this.sendCommand("call_method_on_reference", {
57
- args: this.parseArg(args),
58
- method_name: methodName,
59
- reference_id: referenceId,
60
- with: "result"
61
- })
62
-
63
- return result.response
64
- }
65
-
66
- /**
67
- * Calls a method on a reference and returns a new reference
68
- *
69
- * @param {number} referenceId
70
- * @param {string} methodName
71
- * @param {...any} args
72
- * @returns {Promise<Reference>}
73
- */
74
- async callMethodOnReferenceWithReference(referenceId, methodName, ...args) {
75
- const result = await this.sendCommand("call_method_on_reference", {
76
- args: this.parseArg(args),
77
- method_name: methodName,
78
- reference_id: referenceId,
79
- with: "reference"
80
- })
81
- const id = result.response
82
-
83
- return this.spawnReference(id)
84
- }
85
-
86
- /**
87
- * Evaluates a string and returns a new reference
88
- *
89
- * @param {string} evalString
90
- * @returns {Promise<Reference>}
91
- */
92
- async evalWithReference(evalString) {
93
- const result = await this.sendCommand("eval", {
94
- eval_string: evalString,
95
- with_reference: true
96
- })
97
- const id = result.object_id
98
-
99
- return this.spawnReference(id)
100
- }
101
-
102
- /**
103
- * Imports a module and returns a reference to it
104
- *
105
- * @param {string} importName
106
- * @returns {Promise<Reference>}
107
- */
108
- async import(importName) {
109
- const result = await this.sendCommand("import", {
110
- import_name: importName
111
- })
112
-
113
- logger.log(() => ["import", {result}])
114
-
115
- if (!result) throw new Error("No result given")
116
- if (!result.object_id) throw new Error(`No object ID given in result: ${JSON.stringify(result)}`)
117
-
118
- const id = result.object_id
119
-
120
- return this.spawnReference(id)
121
- }
122
-
123
- /**
124
- * Gets a registered object by name
125
- *
126
- * @param {string} objectName
127
- * @returns {Promise<Reference>}
128
- */
129
- async getObject(objectName) {
130
- const result = await this.sendCommand("get_object", {
131
- object_name: objectName
132
- })
133
-
134
- if (!result) throw new Error("Blank result given")
135
-
136
- const id = result.object_id
137
-
138
- return this.spawnReference(id)
139
- }
140
-
141
- /**
142
- * Spawns a new reference to an object
143
- *
144
- * @param {string} className
145
- * @param {...any} args
146
- * @returns {Promise<Reference>}
147
- */
148
- async newObjectWithReference(className, ...args) {
149
- const result = await this.sendCommand("new_object_with_reference", {
150
- args: this.parseArg(args),
151
- class_name: className
152
- })
153
-
154
- if (!result) throw new Error("Blank result given")
155
-
156
- const id = result.object_id
157
-
158
- if (!id) throw new Error(`No object ID given in result: ${JSON.stringify(result)}`)
159
-
160
- return this.spawnReference(id)
161
- }
162
-
163
- /**
164
- * Checks if the input is a plain object
165
- * @param {any} input
166
- * @returns {boolean}
167
- */
168
- isPlainObject(input) {
169
- if (input && typeof input === "object" && !Array.isArray(input)) {
170
- return true
171
- }
172
-
173
- return false
174
- }
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
- */
185
- onCommand = ({command, command_id: commandID, data, error, errorStack, ...restArgs}) => {
186
- logger.log(() => ["onCommand", {command, commandID, data, error, errorStack, restArgs}])
187
-
188
- try {
189
- if (!command) {
190
- throw new Error(`No command key given in data: ${Object.keys(restArgs).join(", ")}`)
191
- } else if (command == "get_object") {
192
- const serverObject = this._getRegisteredObject(data.object_name)
193
- let object
194
-
195
- if (serverObject) {
196
- object = serverObject
197
- } else {
198
- object = globalThis[data.object_name]
199
-
200
- if (!object) throw new Error(`No such object: ${data.object_name}`)
201
- }
202
-
203
- const objectId = ++this.objectsCount
204
-
205
- this.objects[objectId] = object
206
- this.respondToCommand(commandID, {object_id: objectId})
207
- } else if (command == "new_object_with_reference") {
208
- const className = data.class_name
209
- let object
210
-
211
- if (typeof className == "string") {
212
- const ClassInstance = this.getClass(className) || globalThis[className]
213
-
214
- if (!ClassInstance) throw new Error(`No such class: ${className}`)
215
-
216
- object = new ClassInstance(...data.args)
217
- } else {
218
- throw new Error(`Don't know how to handle class name: ${typeof className}`)
219
- }
220
-
221
- const objectId = ++this.objectsCount
222
-
223
- this.objects[objectId] = object
224
- this.respondToCommand(commandID, {object_id: objectId})
225
- } else if (command == "call_method_on_reference") {
226
- const referenceId = data.reference_id
227
- const object = this.objects[referenceId]
228
-
229
- if (!object) throw new Error(`No object by that ID: ${referenceId}`)
230
-
231
- const method = object[data.method_name]
232
-
233
- if (!method) throw new Error(`No method called '${data.method_name}' on a '${object.constructor.name}'`)
234
-
235
- const response = method.call(object, ...data.args)
236
-
237
- this.respondToCommand(commandID, {response})
238
- } else if (command == "serialize_reference") {
239
- const referenceId = data.reference_id
240
- const object = this.objects[referenceId]
241
-
242
- if (!object) throw new Error(`No object by that ID: ${referenceId}`)
243
-
244
- this.respondToCommand(commandID, JSON.stringify(object))
245
- } else if (command == "read_attribute") {
246
- const attributeName = data.attribute_name
247
- const referenceId = data.reference_id
248
- const returnWith = data.with
249
- const object = this.objects[referenceId]
250
-
251
- if (!object) throw new Error(`No object by that ID: ${referenceId}`)
252
-
253
- const attribute = object[attributeName]
254
-
255
- if (returnWith == "reference") {
256
- const objectId = ++this.objectsCount
257
-
258
- this.objects[objectId] = attribute
259
- this.respondToCommand(commandID, {response: objectId})
260
- } else {
261
- this.respondToCommand(commandID, {response: attribute})
262
- }
263
- } else if (command == "command_response") {
264
- if (!(commandID in this.outgoingCommands)) {
265
- throw new Error(`Outgoing command ${commandID} not found: ${Object.keys(this.outgoingCommands).join(", ")}`)
266
- }
267
-
268
- const savedCommand = this.outgoingCommands[commandID]
269
-
270
- delete this.outgoingCommands[commandID]
271
-
272
- if (error) {
273
- const errorToThrow = new Error(error)
274
-
275
- if (errorStack) {
276
- errorToThrow.stack = `${errorStack}\n\n${errorToThrow.stack}`
277
- }
278
-
279
- savedCommand.reject(errorToThrow)
280
- } else {
281
- logger.log(() => [`Resolving command ${commandID} with data`, data])
282
- savedCommand.resolve(data.data)
283
- }
284
- } else {
285
- throw new Error(`Unknown command: ${command}`)
286
- }
287
- } catch (error) {
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
- }
293
-
294
- logger.error(error)
295
- }
296
- }
297
-
298
- /**
299
- * Parases an argument for sending to the server
300
- *
301
- * @param {any} arg
302
- * @returns {any}
303
- */
304
- parseArg(arg) {
305
- if (Array.isArray(arg)) {
306
- return arg.map((argInArray) => this.parseArg(argInArray))
307
- } else if (arg instanceof Reference) {
308
- return {
309
- __scoundrel_object_id: arg.id,
310
- __scoundrel_type: "reference"
311
- }
312
- } else if (this.isPlainObject(arg)) {
313
- /** @type {Record<any, any>} */
314
- const newObject = {}
315
-
316
- for (const key in arg) {
317
- const value = arg[key]
318
-
319
- newObject[key] = this.parseArg(value)
320
- }
321
-
322
- return newObject
323
- }
324
-
325
- return arg
326
- }
327
-
328
- /**
329
- * Reads an attribute on a reference and returns a new reference
330
- *
331
- * @param {number} referenceId
332
- * @param {string} attributeName
333
- * @returns {Promise<Reference>}
334
- */
335
- async readAttributeOnReferenceWithReference(referenceId, attributeName) {
336
- const result = await this.sendCommand("read_attribute", {
337
- attribute_name: attributeName,
338
- reference_id: referenceId,
339
- with: "reference"
340
- })
341
- const id = result.response
342
-
343
- return this.spawnReference(id)
344
- }
345
-
346
- /**
347
- * Reads an attribute on a reference and returns the result directly
348
- *
349
- * @param {number} referenceId
350
- * @param {string} attributeName
351
- * @returns {Promise<any>}
352
- */
353
- async readAttributeOnReference(referenceId, attributeName) {
354
- const result = await this.sendCommand("read_attribute", {
355
- attribute_name: attributeName,
356
- reference_id: referenceId,
357
- with: "result"
358
- })
359
- return result.response
360
- }
361
-
362
- /**
363
- * Registers a class by name
364
- *
365
- * @param {string} className
366
- * @param {any} classInstance
367
- */
368
- registerClass(className, classInstance) {
369
- if (className in this._classes) throw new Error(`Class already exists: ${className}`)
370
-
371
- this._classes[className] = classInstance
372
- }
373
-
374
- /**
375
- * Gets a registered class by name
376
- *
377
- * @param {string} className
378
- * @returns {any}
379
- */
380
- getClass(className) {
381
- return this._classes[className]
382
- }
383
-
384
- /**
385
- * Registers an object by name
386
- *
387
- * @param {string} objectName
388
- * @param {any} objectInstance
389
- */
390
- registerObject(objectName, objectInstance) {
391
- if (objectName in this._objects) throw new Error(`Object already exists: ${objectName}`)
392
-
393
- this._objects[objectName] = objectInstance
394
- }
395
-
396
- /**
397
- * Gets a registered object by name
398
- *
399
- * @param {string} objectName
400
- * @returns {any}
401
- */
402
- _getRegisteredObject(objectName) {
403
- return this._objects[objectName]
404
- }
405
-
406
- /**
407
- * Responds to a command from the backend
408
- * @param {number} commandId
409
- * @param {any} data
410
- */
411
- respondToCommand(commandId, data) {
412
- this.sendCommand("command_response", {command_id: commandId, data})
413
- }
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
- */
421
- sendCommand(command, data) {
422
- return new Promise((resolve, reject) => {
423
- const outgoingCommandCount = ++this.outgoingCommandsCount
424
- const commandData = {
425
- command,
426
- command_id: outgoingCommandCount,
427
- data
428
- }
429
-
430
- this.outgoingCommands[outgoingCommandCount] = {resolve, reject}
431
- logger.log(() => ["Sending", commandData])
432
- this.send(commandData)
433
- })
434
- }
435
-
436
- /**
437
- * Sends data to the backend
438
- * @param {any} data
439
- */
440
- send(data) {
441
- this.backend.send(data)
442
- }
443
-
444
- /**
445
- * Serializes a reference and returns the result directly
446
- *
447
- * @param {number} referenceId
448
- * @returns {Promise<any>}
449
- */
450
- async serializeReference(referenceId) {
451
- const json = await this.sendCommand("serialize_reference", {reference_id: referenceId})
452
-
453
- return JSON.parse(json)
454
- }
455
-
456
- /**
457
- * Spawns a new reference to an object
458
- *
459
- * @param {string} id
460
- * @returns {Reference}
461
- */
462
- spawnReference(id) {
463
- const reference = new Reference(this, id)
464
-
465
- this.references[id] = reference
466
-
467
- return reference
468
- }
469
- }