scoundrel-remote-eval 1.0.8 → 1.0.9

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 (47) 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 +180 -0
  5. package/build/client/index.d.ts.map +1 -0
  6. package/build/client/index.js +413 -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 +45 -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 +20 -0
  29. package/build/server/index.d.ts.map +1 -0
  30. package/build/server/index.js +26 -0
  31. package/package.json +8 -3
  32. package/eslint.config.js +0 -18
  33. package/spec/reference-with-proxy-spec.js +0 -101
  34. package/spec/support/jasmine.json +0 -13
  35. package/spec/web-socket-javascript-spec.js +0 -66
  36. package/spec/web-socket-python-spec.js +0 -57
  37. package/src/client/connections/web-socket/index.js +0 -88
  38. package/src/client/index.js +0 -469
  39. package/src/client/reference-proxy.js +0 -53
  40. package/src/client/reference.js +0 -69
  41. package/src/index.js +0 -0
  42. package/src/logger.js +0 -70
  43. package/src/python-web-socket-runner.js +0 -116
  44. package/src/server/connections/web-socket/client.js +0 -46
  45. package/src/server/connections/web-socket/index.js +0 -34
  46. package/src/server/index.js +0 -33
  47. package/tsconfig.json +0 -13
@@ -0,0 +1,34 @@
1
+ export default class WebSocket {
2
+ /**
3
+ * Creates a new WebSocket connection handler
4
+ * @param {WebSocket} ws The WebSocket instance
5
+ */
6
+ constructor(ws: WebSocket);
7
+ ws: WebSocket;
8
+ commands: {};
9
+ commandsCount: number;
10
+ close(): Promise<void>;
11
+ /**
12
+ * @param {(data: any) => void} callback
13
+ */
14
+ onCommand(callback: (data: any) => void): void;
15
+ onCommandCallback: (data: any) => void;
16
+ /**
17
+ * @param {Event} event
18
+ */
19
+ onSocketError: (event: Event) => void;
20
+ /**
21
+ * @param {MessageEvent} event
22
+ */
23
+ onSocketMessage: (event: MessageEvent) => void;
24
+ /**
25
+ * @param {Event} _event
26
+ */
27
+ onSocketOpen: (_event: Event) => void;
28
+ /**
29
+ * @param {Record<string, any>} data
30
+ */
31
+ send(data: Record<string, any>): void;
32
+ waitForOpened: () => Promise<any>;
33
+ }
34
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/client/connections/web-socket/index.js"],"names":[],"mappings":"AAQA;IACE;;;OAGG;IACH,gBAFW,SAAS,EAgBnB;IAbC,cAAY;IAWZ,aAAkB;IAClB,sBAAsB;IAGxB,uBAEC;IAED;;OAEG;IACH,oBAFW,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,QAI7B;IADC,0BAHgB,GAAG,KAAK,IAAI,CAGK;IAGnC;;OAEG;IACH,gBAAiB,OAFN,KAEW,UAErB;IAED;;OAEG;IACH,kBAAmB,OAFR,YAEa,UAUvB;IAED;;OAEG;IACH,eAAgB,QAFL,KAEW,UAErB;IAED;;OAEG;IACH,WAFW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,QAQ7B;IAED,kCAME;CACH"}
@@ -0,0 +1,68 @@
1
+ // @ts-check
2
+ import Logger from "../../../logger.js";
3
+ const logger = new Logger("Scoundrel WebSocket");
4
+ // logger.setDebug(true)
5
+ export default class WebSocket {
6
+ /**
7
+ * Creates a new WebSocket connection handler
8
+ * @param {WebSocket} ws The WebSocket instance
9
+ */
10
+ constructor(ws) {
11
+ /**
12
+ * @param {Event} event
13
+ */
14
+ this.onSocketError = (event) => {
15
+ logger.error(() => ["onSocketError", event]);
16
+ };
17
+ /**
18
+ * @param {MessageEvent} event
19
+ */
20
+ this.onSocketMessage = (event) => {
21
+ const data = JSON.parse(event.data);
22
+ logger.log(() => ["Client::Connections::WebSocket onSocketMessage", data]);
23
+ if (!this.onCommandCallback) {
24
+ throw new Error("No onCommand callback set, ignoring message");
25
+ }
26
+ this.onCommandCallback(data);
27
+ };
28
+ /**
29
+ * @param {Event} _event
30
+ */
31
+ this.onSocketOpen = (_event) => {
32
+ logger.log("onSocketOpen");
33
+ };
34
+ this.waitForOpened = () => new Promise((resolve, reject) => {
35
+ // @ts-ignore
36
+ this.ws.addEventListener("open", resolve);
37
+ // @ts-ignore
38
+ this.ws.addEventListener("error", reject);
39
+ });
40
+ this.ws = ws;
41
+ // @ts-ignore
42
+ this.ws.addEventListener("error", this.onSocketError);
43
+ // @ts-ignore
44
+ this.ws.addEventListener("open", this.onSocketOpen);
45
+ // @ts-ignore
46
+ this.ws.addEventListener("message", this.onSocketMessage);
47
+ this.commands = {};
48
+ this.commandsCount = 0;
49
+ }
50
+ async close() {
51
+ await this.ws.close();
52
+ }
53
+ /**
54
+ * @param {(data: any) => void} callback
55
+ */
56
+ onCommand(callback) {
57
+ this.onCommandCallback = callback;
58
+ }
59
+ /**
60
+ * @param {Record<string, any>} data
61
+ */
62
+ send(data) {
63
+ const sendData = JSON.stringify(data);
64
+ logger.log(() => ["Sending", sendData]);
65
+ // @ts-ignore
66
+ this.ws.send(sendData);
67
+ }
68
+ }
@@ -0,0 +1,180 @@
1
+ export default class Client {
2
+ /**
3
+ * Creates a new Scoundrel Client
4
+ *
5
+ * @param {any} backend The backend connection (e.g., WebSocket)
6
+ */
7
+ constructor(backend: any);
8
+ backend: any;
9
+ /** @type {Record<number, any>} */
10
+ outgoingCommands: Record<number, any>;
11
+ incomingCommands: {};
12
+ outgoingCommandsCount: number;
13
+ /** @type {Record<string, any>} */
14
+ _classes: Record<string, any>;
15
+ /** @type {Record<string, any>} */
16
+ _objects: Record<string, any>;
17
+ /** @type {Record<string, Reference>} */
18
+ references: Record<string, Reference>;
19
+ /** @type {Record<number, any>} */
20
+ objects: Record<number, any>;
21
+ objectsCount: number;
22
+ /**
23
+ * Closes the client connection
24
+ */
25
+ close(): Promise<void>;
26
+ /**
27
+ * Calls a method on a reference and returns the result directly
28
+ *
29
+ * @param {number} referenceId
30
+ * @param {string} methodName
31
+ * @param {...any} args
32
+ * @returns {Promise<any>}
33
+ */
34
+ callMethodOnReference(referenceId: number, methodName: string, ...args: any[]): Promise<any>;
35
+ /**
36
+ * Calls a method on a reference and returns a new reference
37
+ *
38
+ * @param {number} referenceId
39
+ * @param {string} methodName
40
+ * @param {...any} args
41
+ * @returns {Promise<Reference>}
42
+ */
43
+ callMethodOnReferenceWithReference(referenceId: number, methodName: string, ...args: any[]): Promise<Reference>;
44
+ /**
45
+ * Evaluates a string and returns a new reference
46
+ *
47
+ * @param {string} evalString
48
+ * @returns {Promise<Reference>}
49
+ */
50
+ evalWithReference(evalString: string): Promise<Reference>;
51
+ /**
52
+ * Imports a module and returns a reference to it
53
+ *
54
+ * @param {string} importName
55
+ * @returns {Promise<Reference>}
56
+ */
57
+ import(importName: string): Promise<Reference>;
58
+ /**
59
+ * Gets a registered object by name
60
+ *
61
+ * @param {string} objectName
62
+ * @returns {Promise<Reference>}
63
+ */
64
+ getObject(objectName: string): Promise<Reference>;
65
+ /**
66
+ * Spawns a new reference to an object
67
+ *
68
+ * @param {string} className
69
+ * @param {...any} args
70
+ * @returns {Promise<Reference>}
71
+ */
72
+ newObjectWithReference(className: string, ...args: any[]): Promise<Reference>;
73
+ /**
74
+ * Checks if the input is a plain object
75
+ * @param {any} input
76
+ * @returns {boolean}
77
+ */
78
+ isPlainObject(input: any): boolean;
79
+ /**
80
+ * Handles an incoming command from the backend
81
+ * @param {object} args
82
+ * @param {string} args.command
83
+ * @param {number} args.command_id
84
+ * @param {any} args.data
85
+ * @param {string} [args.error]
86
+ * @param {string} [args.errorStack]
87
+ */
88
+ onCommand: ({ command, command_id: commandID, data, error, errorStack, ...restArgs }: {
89
+ command: string;
90
+ command_id: number;
91
+ data: any;
92
+ error?: string;
93
+ errorStack?: string;
94
+ }) => void;
95
+ /**
96
+ * Parases an argument for sending to the server
97
+ *
98
+ * @param {any} arg
99
+ * @returns {any}
100
+ */
101
+ parseArg(arg: any): any;
102
+ /**
103
+ * Reads an attribute on a reference and returns a new reference
104
+ *
105
+ * @param {number} referenceId
106
+ * @param {string} attributeName
107
+ * @returns {Promise<Reference>}
108
+ */
109
+ readAttributeOnReferenceWithReference(referenceId: number, attributeName: string): Promise<Reference>;
110
+ /**
111
+ * Reads an attribute on a reference and returns the result directly
112
+ *
113
+ * @param {number} referenceId
114
+ * @param {string} attributeName
115
+ * @returns {Promise<any>}
116
+ */
117
+ readAttributeOnReference(referenceId: number, attributeName: string): Promise<any>;
118
+ /**
119
+ * Registers a class by name
120
+ *
121
+ * @param {string} className
122
+ * @param {any} classInstance
123
+ */
124
+ registerClass(className: string, classInstance: any): void;
125
+ /**
126
+ * Gets a registered class by name
127
+ *
128
+ * @param {string} className
129
+ * @returns {any}
130
+ */
131
+ getClass(className: string): any;
132
+ /**
133
+ * Registers an object by name
134
+ *
135
+ * @param {string} objectName
136
+ * @param {any} objectInstance
137
+ */
138
+ registerObject(objectName: string, objectInstance: any): void;
139
+ /**
140
+ * Gets a registered object by name
141
+ *
142
+ * @param {string} objectName
143
+ * @returns {any}
144
+ */
145
+ _getRegisteredObject(objectName: string): any;
146
+ /**
147
+ * Responds to a command from the backend
148
+ * @param {number} commandId
149
+ * @param {any} data
150
+ */
151
+ respondToCommand(commandId: number, data: any): void;
152
+ /**
153
+ * Sends a command to the backend and returns a promise that resolves with the response
154
+ * @param {string} command
155
+ * @param {any} data
156
+ * @returns {Promise<any>}
157
+ */
158
+ sendCommand(command: string, data: any): Promise<any>;
159
+ /**
160
+ * Sends data to the backend
161
+ * @param {any} data
162
+ */
163
+ send(data: any): void;
164
+ /**
165
+ * Serializes a reference and returns the result directly
166
+ *
167
+ * @param {number} referenceId
168
+ * @returns {Promise<any>}
169
+ */
170
+ serializeReference(referenceId: number): Promise<any>;
171
+ /**
172
+ * Spawns a new reference to an object
173
+ *
174
+ * @param {string} id
175
+ * @returns {Reference}
176
+ */
177
+ spawnReference(id: string): Reference;
178
+ }
179
+ import Reference from "./reference.js";
180
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.js"],"names":[],"mappings":"AASA;IACE;;;;OAIG;IACH,qBAFW,GAAG,EAwBb;IArBC,aAAsB;IAGtB,kCAAkC;IAClC,kBADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACJ;IAC1B,qBAA0B;IAC1B,8BAA8B;IAE9B,kCAAkC;IAClC,UADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACZ;IAElB,kCAAkC;IAClC,UADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACZ;IAElB,wCAAwC;IACxC,YADW,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAChB;IAEpB,kCAAkC;IAClC,SADW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CACb;IAEjB,qBAAqB;IAGvB;;OAEG;IACH,uBAEC;IAED;;;;;;;OAOG;IACH,mCALW,MAAM,cACN,MAAM,WACF,GAAG,EAAA,GACL,OAAO,CAAC,GAAG,CAAC,CAWxB;IAED;;;;;;;OAOG;IACH,gDALW,MAAM,cACN,MAAM,WACF,GAAG,EAAA,GACL,OAAO,CAAC,SAAS,CAAC,CAY9B;IAED;;;;;OAKG;IACH,8BAHW,MAAM,GACJ,OAAO,CAAC,SAAS,CAAC,CAU9B;IAED;;;;;OAKG;IACH,mBAHW,MAAM,GACJ,OAAO,CAAC,SAAS,CAAC,CAe9B;IAED;;;;;OAKG;IACH,sBAHW,MAAM,GACJ,OAAO,CAAC,SAAS,CAAC,CAY9B;IAED;;;;;;OAMG;IACH,kCAJW,MAAM,WACF,GAAG,EAAA,GACL,OAAO,CAAC,SAAS,CAAC,CAe9B;IAED;;;;OAIG;IACH,qBAHW,GAAG,GACD,OAAO,CAQnB;IAED;;;;;;;;OAQG;IACH,YAAa,0EANV;QAAqB,OAAO,EAApB,MAAM;QACO,UAAU,EAAvB,MAAM;QACI,IAAI,EAAd,GAAG;QACW,KAAK,GAAnB,MAAM;QACQ,UAAU,GAAxB,MAAM;KAEkE,UA+GlF;IAED;;;;;OAKG;IACH,cAHW,GAAG,GACD,GAAG,CAwBf;IAED;;;;;;OAMG;IACH,mDAJW,MAAM,iBACN,MAAM,GACJ,OAAO,CAAC,SAAS,CAAC,CAW9B;IAED;;;;;;OAMG;IACH,sCAJW,MAAM,iBACN,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,CASxB;IAED;;;;;OAKG;IACH,yBAHW,MAAM,iBACN,GAAG,QAMb;IAED;;;;;OAKG;IACH,oBAHW,MAAM,GACJ,GAAG,CAIf;IAED;;;;;OAKG;IACH,2BAHW,MAAM,kBACN,GAAG,QAMb;IAED;;;;;OAKG;IACH,iCAHW,MAAM,GACJ,GAAG,CAIf;IAED;;;;OAIG;IACH,4BAHW,MAAM,QACN,GAAG,QAIb;IAED;;;;;OAKG;IACH,qBAJW,MAAM,QACN,GAAG,GACD,OAAO,CAAC,GAAG,CAAC,CAexB;IAED;;;OAGG;IACH,WAFW,GAAG,QAIb;IAED;;;;;OAKG;IACH,gCAHW,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,CAMxB;IAED;;;;;OAKG;IACH,mBAHW,MAAM,GACJ,SAAS,CAQrB;CACF;sBAjdqB,gBAAgB"}
@@ -0,0 +1,413 @@
1
+ // @ts-check
2
+ import Logger from "../logger.js";
3
+ import Reference from "./reference.js";
4
+ const logger = new Logger("Scoundrel Client");
5
+ // logger.setDebug(true)
6
+ export default class Client {
7
+ /**
8
+ * Creates a new Scoundrel Client
9
+ *
10
+ * @param {any} backend The backend connection (e.g., WebSocket)
11
+ */
12
+ constructor(backend) {
13
+ /**
14
+ * Handles an incoming command from the backend
15
+ * @param {object} args
16
+ * @param {string} args.command
17
+ * @param {number} args.command_id
18
+ * @param {any} args.data
19
+ * @param {string} [args.error]
20
+ * @param {string} [args.errorStack]
21
+ */
22
+ this.onCommand = ({ command, command_id: commandID, data, error, errorStack, ...restArgs }) => {
23
+ logger.log(() => ["onCommand", { command, commandID, data, error, errorStack, restArgs }]);
24
+ try {
25
+ if (!command) {
26
+ throw new Error(`No command key given in data: ${Object.keys(restArgs).join(", ")}`);
27
+ }
28
+ else if (command == "get_object") {
29
+ const serverObject = this._getRegisteredObject(data.object_name);
30
+ let object;
31
+ if (serverObject) {
32
+ object = serverObject;
33
+ }
34
+ else {
35
+ object = globalThis[data.object_name];
36
+ if (!object)
37
+ throw new Error(`No such object: ${data.object_name}`);
38
+ }
39
+ const objectId = ++this.objectsCount;
40
+ this.objects[objectId] = object;
41
+ this.respondToCommand(commandID, { object_id: objectId });
42
+ }
43
+ else if (command == "new_object_with_reference") {
44
+ const className = data.class_name;
45
+ let object;
46
+ if (typeof className == "string") {
47
+ const ClassInstance = this.getClass(className) || globalThis[className];
48
+ if (!ClassInstance)
49
+ throw new Error(`No such class: ${className}`);
50
+ object = new ClassInstance(...data.args);
51
+ }
52
+ else {
53
+ throw new Error(`Don't know how to handle class name: ${typeof className}`);
54
+ }
55
+ const objectId = ++this.objectsCount;
56
+ this.objects[objectId] = object;
57
+ this.respondToCommand(commandID, { object_id: objectId });
58
+ }
59
+ else if (command == "call_method_on_reference") {
60
+ const referenceId = data.reference_id;
61
+ const object = this.objects[referenceId];
62
+ if (!object)
63
+ throw new Error(`No object by that ID: ${referenceId}`);
64
+ const method = object[data.method_name];
65
+ if (!method)
66
+ throw new Error(`No method called '${data.method_name}' on a '${object.constructor.name}'`);
67
+ const response = method.call(object, ...data.args);
68
+ this.respondToCommand(commandID, { response });
69
+ }
70
+ else if (command == "serialize_reference") {
71
+ const referenceId = data.reference_id;
72
+ const object = this.objects[referenceId];
73
+ if (!object)
74
+ throw new Error(`No object by that ID: ${referenceId}`);
75
+ this.respondToCommand(commandID, JSON.stringify(object));
76
+ }
77
+ else if (command == "read_attribute") {
78
+ const attributeName = data.attribute_name;
79
+ const referenceId = data.reference_id;
80
+ const returnWith = data.with;
81
+ const object = this.objects[referenceId];
82
+ if (!object)
83
+ throw new Error(`No object by that ID: ${referenceId}`);
84
+ const attribute = object[attributeName];
85
+ if (returnWith == "reference") {
86
+ const objectId = ++this.objectsCount;
87
+ this.objects[objectId] = attribute;
88
+ this.respondToCommand(commandID, { response: objectId });
89
+ }
90
+ else {
91
+ this.respondToCommand(commandID, { response: attribute });
92
+ }
93
+ }
94
+ else if (command == "command_response") {
95
+ if (!(commandID in this.outgoingCommands)) {
96
+ throw new Error(`Outgoing command ${commandID} not found: ${Object.keys(this.outgoingCommands).join(", ")}`);
97
+ }
98
+ const savedCommand = this.outgoingCommands[commandID];
99
+ delete this.outgoingCommands[commandID];
100
+ if (error) {
101
+ const errorToThrow = new Error(error);
102
+ if (errorStack) {
103
+ errorToThrow.stack = `${errorStack}\n\n${errorToThrow.stack}`;
104
+ }
105
+ savedCommand.reject(errorToThrow);
106
+ }
107
+ else {
108
+ logger.log(() => [`Resolving command ${commandID} with data`, data]);
109
+ savedCommand.resolve(data.data);
110
+ }
111
+ }
112
+ else {
113
+ throw new Error(`Unknown command: ${command}`);
114
+ }
115
+ }
116
+ catch (error) {
117
+ if (error instanceof Error) {
118
+ this.send({ command: "command_response", command_id: commandID, error: error.message, errorStack: error.stack });
119
+ }
120
+ else {
121
+ this.send({ command: "command_response", command_id: commandID, error: String(error) });
122
+ }
123
+ logger.error(error);
124
+ }
125
+ };
126
+ this.backend = backend;
127
+ this.backend.onCommand(this.onCommand);
128
+ /** @type {Record<number, any>} */
129
+ this.outgoingCommands = {};
130
+ this.incomingCommands = {};
131
+ this.outgoingCommandsCount = 0;
132
+ /** @type {Record<string, any>} */
133
+ this._classes = {};
134
+ /** @type {Record<string, any>} */
135
+ this._objects = {};
136
+ /** @type {Record<string, Reference>} */
137
+ this.references = {};
138
+ /** @type {Record<number, any>} */
139
+ this.objects = {};
140
+ this.objectsCount = 0;
141
+ }
142
+ /**
143
+ * Closes the client connection
144
+ */
145
+ async close() {
146
+ this.backend.close();
147
+ }
148
+ /**
149
+ * Calls a method on a reference and returns the result directly
150
+ *
151
+ * @param {number} referenceId
152
+ * @param {string} methodName
153
+ * @param {...any} args
154
+ * @returns {Promise<any>}
155
+ */
156
+ async callMethodOnReference(referenceId, methodName, ...args) {
157
+ const result = await this.sendCommand("call_method_on_reference", {
158
+ args: this.parseArg(args),
159
+ method_name: methodName,
160
+ reference_id: referenceId,
161
+ with: "result"
162
+ });
163
+ return result.response;
164
+ }
165
+ /**
166
+ * Calls a method on a reference and returns a new reference
167
+ *
168
+ * @param {number} referenceId
169
+ * @param {string} methodName
170
+ * @param {...any} args
171
+ * @returns {Promise<Reference>}
172
+ */
173
+ async callMethodOnReferenceWithReference(referenceId, methodName, ...args) {
174
+ const result = await this.sendCommand("call_method_on_reference", {
175
+ args: this.parseArg(args),
176
+ method_name: methodName,
177
+ reference_id: referenceId,
178
+ with: "reference"
179
+ });
180
+ const id = result.response;
181
+ return this.spawnReference(id);
182
+ }
183
+ /**
184
+ * Evaluates a string and returns a new reference
185
+ *
186
+ * @param {string} evalString
187
+ * @returns {Promise<Reference>}
188
+ */
189
+ async evalWithReference(evalString) {
190
+ const result = await this.sendCommand("eval", {
191
+ eval_string: evalString,
192
+ with_reference: true
193
+ });
194
+ const id = result.object_id;
195
+ return this.spawnReference(id);
196
+ }
197
+ /**
198
+ * Imports a module and returns a reference to it
199
+ *
200
+ * @param {string} importName
201
+ * @returns {Promise<Reference>}
202
+ */
203
+ async import(importName) {
204
+ const result = await this.sendCommand("import", {
205
+ import_name: importName
206
+ });
207
+ logger.log(() => ["import", { result }]);
208
+ if (!result)
209
+ throw new Error("No result given");
210
+ if (!result.object_id)
211
+ throw new Error(`No object ID given in result: ${JSON.stringify(result)}`);
212
+ const id = result.object_id;
213
+ return this.spawnReference(id);
214
+ }
215
+ /**
216
+ * Gets a registered object by name
217
+ *
218
+ * @param {string} objectName
219
+ * @returns {Promise<Reference>}
220
+ */
221
+ async getObject(objectName) {
222
+ const result = await this.sendCommand("get_object", {
223
+ object_name: objectName
224
+ });
225
+ if (!result)
226
+ throw new Error("Blank result given");
227
+ const id = result.object_id;
228
+ return this.spawnReference(id);
229
+ }
230
+ /**
231
+ * Spawns a new reference to an object
232
+ *
233
+ * @param {string} className
234
+ * @param {...any} args
235
+ * @returns {Promise<Reference>}
236
+ */
237
+ async newObjectWithReference(className, ...args) {
238
+ const result = await this.sendCommand("new_object_with_reference", {
239
+ args: this.parseArg(args),
240
+ class_name: className
241
+ });
242
+ if (!result)
243
+ throw new Error("Blank result given");
244
+ const id = result.object_id;
245
+ if (!id)
246
+ throw new Error(`No object ID given in result: ${JSON.stringify(result)}`);
247
+ return this.spawnReference(id);
248
+ }
249
+ /**
250
+ * Checks if the input is a plain object
251
+ * @param {any} input
252
+ * @returns {boolean}
253
+ */
254
+ isPlainObject(input) {
255
+ if (input && typeof input === "object" && !Array.isArray(input)) {
256
+ return true;
257
+ }
258
+ return false;
259
+ }
260
+ /**
261
+ * Parases an argument for sending to the server
262
+ *
263
+ * @param {any} arg
264
+ * @returns {any}
265
+ */
266
+ parseArg(arg) {
267
+ if (Array.isArray(arg)) {
268
+ return arg.map((argInArray) => this.parseArg(argInArray));
269
+ }
270
+ else if (arg instanceof Reference) {
271
+ return {
272
+ __scoundrel_object_id: arg.id,
273
+ __scoundrel_type: "reference"
274
+ };
275
+ }
276
+ else if (this.isPlainObject(arg)) {
277
+ /** @type {Record<any, any>} */
278
+ const newObject = {};
279
+ for (const key in arg) {
280
+ const value = arg[key];
281
+ newObject[key] = this.parseArg(value);
282
+ }
283
+ return newObject;
284
+ }
285
+ return arg;
286
+ }
287
+ /**
288
+ * Reads an attribute on a reference and returns a new reference
289
+ *
290
+ * @param {number} referenceId
291
+ * @param {string} attributeName
292
+ * @returns {Promise<Reference>}
293
+ */
294
+ async readAttributeOnReferenceWithReference(referenceId, attributeName) {
295
+ const result = await this.sendCommand("read_attribute", {
296
+ attribute_name: attributeName,
297
+ reference_id: referenceId,
298
+ with: "reference"
299
+ });
300
+ const id = result.response;
301
+ return this.spawnReference(id);
302
+ }
303
+ /**
304
+ * Reads an attribute on a reference and returns the result directly
305
+ *
306
+ * @param {number} referenceId
307
+ * @param {string} attributeName
308
+ * @returns {Promise<any>}
309
+ */
310
+ async readAttributeOnReference(referenceId, attributeName) {
311
+ const result = await this.sendCommand("read_attribute", {
312
+ attribute_name: attributeName,
313
+ reference_id: referenceId,
314
+ with: "result"
315
+ });
316
+ return result.response;
317
+ }
318
+ /**
319
+ * Registers a class by name
320
+ *
321
+ * @param {string} className
322
+ * @param {any} classInstance
323
+ */
324
+ registerClass(className, classInstance) {
325
+ if (className in this._classes)
326
+ throw new Error(`Class already exists: ${className}`);
327
+ this._classes[className] = classInstance;
328
+ }
329
+ /**
330
+ * Gets a registered class by name
331
+ *
332
+ * @param {string} className
333
+ * @returns {any}
334
+ */
335
+ getClass(className) {
336
+ return this._classes[className];
337
+ }
338
+ /**
339
+ * Registers an object by name
340
+ *
341
+ * @param {string} objectName
342
+ * @param {any} objectInstance
343
+ */
344
+ registerObject(objectName, objectInstance) {
345
+ if (objectName in this._objects)
346
+ throw new Error(`Object already exists: ${objectName}`);
347
+ this._objects[objectName] = objectInstance;
348
+ }
349
+ /**
350
+ * Gets a registered object by name
351
+ *
352
+ * @param {string} objectName
353
+ * @returns {any}
354
+ */
355
+ _getRegisteredObject(objectName) {
356
+ return this._objects[objectName];
357
+ }
358
+ /**
359
+ * Responds to a command from the backend
360
+ * @param {number} commandId
361
+ * @param {any} data
362
+ */
363
+ respondToCommand(commandId, data) {
364
+ this.sendCommand("command_response", { command_id: commandId, data });
365
+ }
366
+ /**
367
+ * Sends a command to the backend and returns a promise that resolves with the response
368
+ * @param {string} command
369
+ * @param {any} data
370
+ * @returns {Promise<any>}
371
+ */
372
+ sendCommand(command, data) {
373
+ return new Promise((resolve, reject) => {
374
+ const outgoingCommandCount = ++this.outgoingCommandsCount;
375
+ const commandData = {
376
+ command,
377
+ command_id: outgoingCommandCount,
378
+ data
379
+ };
380
+ this.outgoingCommands[outgoingCommandCount] = { resolve, reject };
381
+ logger.log(() => ["Sending", commandData]);
382
+ this.send(commandData);
383
+ });
384
+ }
385
+ /**
386
+ * Sends data to the backend
387
+ * @param {any} data
388
+ */
389
+ send(data) {
390
+ this.backend.send(data);
391
+ }
392
+ /**
393
+ * Serializes a reference and returns the result directly
394
+ *
395
+ * @param {number} referenceId
396
+ * @returns {Promise<any>}
397
+ */
398
+ async serializeReference(referenceId) {
399
+ const json = await this.sendCommand("serialize_reference", { reference_id: referenceId });
400
+ return JSON.parse(json);
401
+ }
402
+ /**
403
+ * Spawns a new reference to an object
404
+ *
405
+ * @param {string} id
406
+ * @returns {Reference}
407
+ */
408
+ spawnReference(id) {
409
+ const reference = new Reference(this, id);
410
+ this.references[id] = reference;
411
+ return reference;
412
+ }
413
+ }