scoundrel-remote-eval 1.0.7 → 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.
- package/build/client/connections/web-socket/index.d.ts +34 -0
- package/build/client/connections/web-socket/index.d.ts.map +1 -0
- package/build/client/connections/web-socket/index.js +68 -0
- package/build/client/index.d.ts +180 -0
- package/build/client/index.d.ts.map +1 -0
- package/build/client/index.js +413 -0
- package/build/client/reference-proxy.d.ts +7 -0
- package/build/client/reference-proxy.d.ts.map +1 -0
- package/build/client/reference-proxy.js +45 -0
- package/build/client/reference.d.ts +50 -0
- package/build/client/reference.d.ts.map +1 -0
- package/build/client/reference.js +63 -0
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +1 -0
- package/build/logger.d.ts +39 -0
- package/build/logger.d.ts.map +1 -0
- package/build/logger.js +64 -0
- package/build/python-web-socket-runner.d.ts +27 -0
- package/build/python-web-socket-runner.d.ts.map +1 -0
- package/build/python-web-socket-runner.js +94 -0
- package/build/server/connections/web-socket/client.d.ts +26 -0
- package/build/server/connections/web-socket/client.d.ts.map +1 -0
- package/build/server/connections/web-socket/client.js +39 -0
- package/build/server/connections/web-socket/index.d.ts +19 -0
- package/build/server/connections/web-socket/index.d.ts.map +1 -0
- package/build/server/connections/web-socket/index.js +29 -0
- package/build/server/index.d.ts +20 -0
- package/build/server/index.d.ts.map +1 -0
- package/build/server/index.js +26 -0
- package/package.json +17 -4
- package/spec/reference-with-proxy-spec.js +0 -87
- package/spec/support/jasmine.json +0 -13
- package/spec/web-socket-javascript-spec.js +0 -64
- package/spec/web-socket-python-spec.js +0 -55
- package/src/client/connections/web-socket/index.js +0 -51
- package/src/client/index.js +0 -423
- package/src/client/reference-proxy.js +0 -29
- package/src/client/reference.js +0 -67
- package/src/index.js +0 -0
- package/src/logger.js +0 -56
- package/src/python-web-socket-runner.js +0 -87
- package/src/server/connections/web-socket/client.js +0 -28
- package/src/server/connections/web-socket/index.js +0 -22
- package/src/server/index.js +0 -21
|
@@ -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
|
+
}
|