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.
- 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 +186 -0
- package/build/client/index.d.ts.map +1 -0
- package/build/client/index.js +452 -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 +41 -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 +27 -0
- package/build/server/index.d.ts.map +1 -0
- package/build/server/index.js +34 -0
- package/build/utils/single-event-emitter.d.ts +46 -0
- package/build/utils/single-event-emitter.d.ts.map +1 -0
- package/build/utils/single-event-emitter.js +86 -0
- package/package.json +9 -3
- package/eslint.config.js +0 -18
- package/spec/reference-with-proxy-spec.js +0 -101
- package/spec/support/jasmine.json +0 -13
- package/spec/web-socket-javascript-spec.js +0 -66
- package/spec/web-socket-python-spec.js +0 -57
- package/src/client/connections/web-socket/index.js +0 -88
- package/src/client/index.js +0 -469
- package/src/client/reference-proxy.js +0 -53
- package/src/client/reference.js +0 -69
- package/src/index.js +0 -0
- package/src/logger.js +0 -70
- package/src/python-web-socket-runner.js +0 -116
- package/src/server/connections/web-socket/client.js +0 -46
- package/src/server/connections/web-socket/index.js +0 -34
- package/src/server/index.js +0 -33
- package/tsconfig.json +0 -13
package/build/logger.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
export default class Logger {
|
|
3
|
+
/**
|
|
4
|
+
* Creates a new Logger instance
|
|
5
|
+
*
|
|
6
|
+
* @param {string} scopeName The name of the scope for the logger
|
|
7
|
+
*/
|
|
8
|
+
constructor(scopeName) {
|
|
9
|
+
this.debug = false;
|
|
10
|
+
this.scopeName = scopeName;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Enables or disables debug logging
|
|
14
|
+
*
|
|
15
|
+
* @param {boolean} newValue
|
|
16
|
+
*/
|
|
17
|
+
setDebug(newValue) {
|
|
18
|
+
this.debug = newValue;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Logs an error message to the console if debug is enabled
|
|
22
|
+
*
|
|
23
|
+
* @param {...any} args
|
|
24
|
+
*/
|
|
25
|
+
error(...args) {
|
|
26
|
+
return this._sendToConsole("error", ...args);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Logs a message to the console if debug is enabled
|
|
30
|
+
* @param {...any} args
|
|
31
|
+
*/
|
|
32
|
+
log(...args) {
|
|
33
|
+
return this._sendToConsole("log", ...args);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Logs a warning message to the console if debug is enabled
|
|
37
|
+
* @param {...any} args
|
|
38
|
+
*/
|
|
39
|
+
warn(...args) {
|
|
40
|
+
return this._sendToConsole("warn", ...args);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Sends the log message to the console
|
|
44
|
+
* @param {string} logType
|
|
45
|
+
* @param {...any} args
|
|
46
|
+
*/
|
|
47
|
+
_sendToConsole(logType, ...args) {
|
|
48
|
+
if (!this.debug) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (args.length == 1 && typeof args[0] == "function") {
|
|
52
|
+
const callbackArgs = args[0]();
|
|
53
|
+
if (Array.isArray(callbackArgs)) {
|
|
54
|
+
console[logType](this.scopeName, ...callbackArgs);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console[logType](this.scopeName, callbackArgs);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console[logType](this.scopeName, ...args);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default class PythonWebSocketRunner {
|
|
2
|
+
/** @type {Error | null} */
|
|
3
|
+
waitForPidRejectError: Error | null;
|
|
4
|
+
/** @type {((value: any) => void) | null} */
|
|
5
|
+
waitForPidResolve: ((value: any) => void) | null;
|
|
6
|
+
/** @type {((value: any) => void) | null} */
|
|
7
|
+
waitForPidReject: ((value: any) => void) | null;
|
|
8
|
+
runAndWaitForPid(): Promise<any>;
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
onProcessExit: () => void;
|
|
11
|
+
/**
|
|
12
|
+
* @param {number} code
|
|
13
|
+
* @param {string} signal
|
|
14
|
+
*/
|
|
15
|
+
onChildExit: (code: number, signal: string) => void;
|
|
16
|
+
/**
|
|
17
|
+
* @param {string | Buffer} data
|
|
18
|
+
*/
|
|
19
|
+
onChildStderr: (data: string | Buffer) => void;
|
|
20
|
+
/**
|
|
21
|
+
* @param {string | Buffer} data
|
|
22
|
+
*/
|
|
23
|
+
onChildStdout: (data: string | Buffer) => void;
|
|
24
|
+
pid: string;
|
|
25
|
+
close(): void;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=python-web-socket-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python-web-socket-runner.d.ts","sourceRoot":"","sources":["../src/python-web-socket-runner.js"],"names":[],"mappings":"AAUA;IACE,2BAA2B;IAC3B,uBADW,KAAK,GAAG,IAAI,CACK;IAE5B,4CAA4C;IAC5C,mBADW,CAAC,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC,GAAG,IAAI,CAChB;IAExB,4CAA4C;IAC5C,kBADW,CAAC,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC,GAAG,IAAI,CACjB;IAMvB,iCAMC;IAED,qBAQC;IAED,0BAKC;IAED;;;OAGG;IACH,cAAe,MAHJ,MAGQ,EAAE,QAFV,MAEgB,UAkB1B;IAED;;OAEG;IACH,gBAAiB,MAFN,MAAM,GAAG,MAEC,UAMpB;IAED;;OAEG;IACH,gBAAiB,MAFN,MAAM,GAAG,MAEC,UAqBpB;IAfG,YAAmB;IAiBvB,cAIC;CACF"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { exec, spawn } from "child_process";
|
|
3
|
+
import Logger from "./logger.js";
|
|
4
|
+
import { realpath } from "fs/promises";
|
|
5
|
+
const logger = new Logger("Scoundrel PythonWebSocketRunner");
|
|
6
|
+
// logger.setDebug(true)
|
|
7
|
+
export default class PythonWebSocketRunner {
|
|
8
|
+
constructor() {
|
|
9
|
+
/** @type {Error | null} */
|
|
10
|
+
this.waitForPidRejectError = null;
|
|
11
|
+
/** @type {((value: any) => void) | null} */
|
|
12
|
+
this.waitForPidResolve = null;
|
|
13
|
+
/** @type {((value: any) => void) | null} */
|
|
14
|
+
this.waitForPidReject = null;
|
|
15
|
+
this.onProcessExit = () => {
|
|
16
|
+
if (this.pid) {
|
|
17
|
+
this.close();
|
|
18
|
+
logger.log(() => `onProcessExit: Killing Python process with PID ${this.pid}`);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* @param {number} code
|
|
23
|
+
* @param {string} signal
|
|
24
|
+
*/
|
|
25
|
+
this.onChildExit = (code, signal) => {
|
|
26
|
+
logger.log(() => `Child process exited with code ${code} and signal ${signal}`);
|
|
27
|
+
if (this.waitForPidRejectError) {
|
|
28
|
+
if (!this.waitForPidReject) {
|
|
29
|
+
throw new Error("waitForPidReject is undefined while waitForPidRejectError is set");
|
|
30
|
+
}
|
|
31
|
+
this.waitForPidReject(this.waitForPidRejectError);
|
|
32
|
+
this.waitForPidResolve = null;
|
|
33
|
+
this.waitForPidReject = null;
|
|
34
|
+
this.waitForPidRejectError = null;
|
|
35
|
+
}
|
|
36
|
+
else if (this.waitForPidReject) {
|
|
37
|
+
this.waitForPidReject(new Error(`Python process exited before PID was received (code: ${code}, signal: ${signal})`));
|
|
38
|
+
this.waitForPidResolve = null;
|
|
39
|
+
this.waitForPidReject = null;
|
|
40
|
+
this.waitForPidRejectError = null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* @param {string | Buffer} data
|
|
45
|
+
*/
|
|
46
|
+
this.onChildStderr = (data) => {
|
|
47
|
+
logger.error(() => `stderr: ${data}`);
|
|
48
|
+
if (this.waitForPidReject) {
|
|
49
|
+
this.waitForPidRejectError = new Error(`Python process stderr before PID was received: ${data}`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* @param {string | Buffer} data
|
|
54
|
+
*/
|
|
55
|
+
this.onChildStdout = (data) => {
|
|
56
|
+
logger.log(() => `stdout: ${data}`);
|
|
57
|
+
const match = (`${data}`).match(/^Started with PID (\d+) on (.+):(.+)\n$/);
|
|
58
|
+
if (match) {
|
|
59
|
+
this.pid = match[1];
|
|
60
|
+
logger.log(() => `Registered PID ${this.pid}`);
|
|
61
|
+
if (this.waitForPidResolve) {
|
|
62
|
+
if (!this.waitForPidResolve) {
|
|
63
|
+
throw new Error("waitForPidResolve is undefined while waitForPidRejectError is set");
|
|
64
|
+
}
|
|
65
|
+
this.waitForPidResolve(null);
|
|
66
|
+
this.waitForPidResolve = null;
|
|
67
|
+
this.waitForPidReject = null;
|
|
68
|
+
this.waitForPidRejectError = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
process.on("exit", this.onProcessExit);
|
|
73
|
+
}
|
|
74
|
+
runAndWaitForPid() {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
this.waitForPidResolve = resolve;
|
|
77
|
+
this.waitForPidReject = reject;
|
|
78
|
+
this.run();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async run() {
|
|
82
|
+
const filePath = `${process.cwd()}/../python/server/web-socket.py`;
|
|
83
|
+
const fileRealPath = await realpath(filePath);
|
|
84
|
+
const child = spawn("python3", [fileRealPath]);
|
|
85
|
+
child.on("exit", this.onChildExit);
|
|
86
|
+
child.stdout.on("data", this.onChildStdout);
|
|
87
|
+
child.stderr.on("data", this.onChildStderr);
|
|
88
|
+
}
|
|
89
|
+
close() {
|
|
90
|
+
if (this.pid) {
|
|
91
|
+
exec(`kill ${this.pid}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export default class WebSocketClient {
|
|
2
|
+
/**
|
|
3
|
+
* Creates a new WebSocketClient
|
|
4
|
+
* @param {import("ws").WebSocket} ws The WebSocket instance
|
|
5
|
+
*/
|
|
6
|
+
constructor(ws: any);
|
|
7
|
+
ws: any;
|
|
8
|
+
/**
|
|
9
|
+
* @param {(data: any) => void} callback
|
|
10
|
+
*/
|
|
11
|
+
onCommand(callback: (data: any) => void): void;
|
|
12
|
+
onCommandCallback: (data: any) => void;
|
|
13
|
+
/**
|
|
14
|
+
* @param {Error} error
|
|
15
|
+
*/
|
|
16
|
+
onError: (error: Error) => void;
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} rawData
|
|
19
|
+
*/
|
|
20
|
+
onMessage: (rawData: string) => void;
|
|
21
|
+
/**
|
|
22
|
+
* @param {any} data
|
|
23
|
+
*/
|
|
24
|
+
send(data: any): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/server/connections/web-socket/client.js"],"names":[],"mappings":"AAEA;IACE;;;OAGG;IACH,qBAKC;IAJC,QAAY;IAMd;;OAEG;IACH,oBAFW,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,QAI7B;IADC,0BAHgB,GAAG,KAAK,IAAI,CAGK;IAGnC;;OAEG;IACH,UAAW,OAFA,KAEK,UAEf;IAED;;OAEG;IACH,YAAa,SAFF,MAES,UAMnB;IAED;;OAEG;IACH,WAFW,GAAG,iBAIb;CACF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
export default class WebSocketClient {
|
|
3
|
+
/**
|
|
4
|
+
* Creates a new WebSocketClient
|
|
5
|
+
* @param {import("ws").WebSocket} ws The WebSocket instance
|
|
6
|
+
*/
|
|
7
|
+
constructor(ws) {
|
|
8
|
+
/**
|
|
9
|
+
* @param {Error} error
|
|
10
|
+
*/
|
|
11
|
+
this.onError = (error) => {
|
|
12
|
+
console.error("WebSocketClient error", error);
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} rawData
|
|
16
|
+
*/
|
|
17
|
+
this.onMessage = (rawData) => {
|
|
18
|
+
const data = JSON.parse(rawData);
|
|
19
|
+
if (!this.onCommandCallback)
|
|
20
|
+
throw new Error("Command callback hasn't been set");
|
|
21
|
+
this.onCommandCallback(data);
|
|
22
|
+
};
|
|
23
|
+
this.ws = ws;
|
|
24
|
+
ws.on("error", this.onError);
|
|
25
|
+
ws.on("message", this.onMessage);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* @param {(data: any) => void} callback
|
|
29
|
+
*/
|
|
30
|
+
onCommand(callback) {
|
|
31
|
+
this.onCommandCallback = callback;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @param {any} data
|
|
35
|
+
*/
|
|
36
|
+
async send(data) {
|
|
37
|
+
await this.ws.send(JSON.stringify(data));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default class WebSocket {
|
|
2
|
+
/**
|
|
3
|
+
* Creates a new WebSocket connection handler
|
|
4
|
+
* @param {import("ws").Server} webSocketServer The WebSocket server instance
|
|
5
|
+
*/
|
|
6
|
+
constructor(webSocketServer: any);
|
|
7
|
+
wss: any;
|
|
8
|
+
close(): void;
|
|
9
|
+
/**
|
|
10
|
+
* @param {import("ws").WebSocket} ws
|
|
11
|
+
*/
|
|
12
|
+
onConnection: (ws: any) => void;
|
|
13
|
+
/**
|
|
14
|
+
* @param {(client: import("./client.js").default) => void} callback
|
|
15
|
+
*/
|
|
16
|
+
onNewClient(callback: (client: import("./client.js").default) => void): void;
|
|
17
|
+
onNewClientCallback: (client: import("./client.js").default) => void;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/server/connections/web-socket/index.js"],"names":[],"mappings":"AAIA;IACE;;;OAGG;IACH,kCAGC;IAFC,SAA0B;IAI5B,cAA4B;IAE5B;;OAEG;IACH,eAAgB,OAAE,UAIjB;IAED;;OAEG;IACH,sBAFW,CAAC,MAAM,EAAE,OAAO,aAAa,EAAE,OAAO,KAAK,IAAI,QAMzD;IADC,8BALkB,OAAO,aAAa,EAAE,OAAO,KAAK,IAAI,CAKrB;CAEtC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import WebSocketClient from "./client.js";
|
|
3
|
+
export default class WebSocket {
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new WebSocket connection handler
|
|
6
|
+
* @param {import("ws").Server} webSocketServer The WebSocket server instance
|
|
7
|
+
*/
|
|
8
|
+
constructor(webSocketServer) {
|
|
9
|
+
/**
|
|
10
|
+
* @param {import("ws").WebSocket} ws
|
|
11
|
+
*/
|
|
12
|
+
this.onConnection = (ws) => {
|
|
13
|
+
if (!this.onNewClientCallback)
|
|
14
|
+
throw new Error("'onNewClient' hasn't been called");
|
|
15
|
+
this.onNewClientCallback(new WebSocketClient(ws));
|
|
16
|
+
};
|
|
17
|
+
this.wss = webSocketServer;
|
|
18
|
+
this.wss.on("connection", this.onConnection);
|
|
19
|
+
}
|
|
20
|
+
close() { this.wss.close(); }
|
|
21
|
+
/**
|
|
22
|
+
* @param {(client: import("./client.js").default) => void} callback
|
|
23
|
+
*/
|
|
24
|
+
onNewClient(callback) {
|
|
25
|
+
if (!callback)
|
|
26
|
+
throw new Error("No callback was given");
|
|
27
|
+
this.onNewClientCallback = callback;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default class ScoundrelServer {
|
|
2
|
+
/**
|
|
3
|
+
* Creates a new Scoundrel server
|
|
4
|
+
* @param {import("./connections/web-socket/index.js").default} backend The backend connection handler
|
|
5
|
+
*/
|
|
6
|
+
constructor(backend: import("./connections/web-socket/index.js").default);
|
|
7
|
+
backend: import("./connections/web-socket/index.js").default;
|
|
8
|
+
/** @type {Client[]} */
|
|
9
|
+
clients: Client[];
|
|
10
|
+
events: EventEmitter<any>;
|
|
11
|
+
/** @type {SingleEventEmitter<(client: Client) => void>} */
|
|
12
|
+
onNewClientEventEmitter: SingleEventEmitter<(client: Client) => void>;
|
|
13
|
+
onNewClient: (listener: (client: Client) => void) => void;
|
|
14
|
+
/** @returns {void} */
|
|
15
|
+
close(): void;
|
|
16
|
+
/** @returns {Client[]} */
|
|
17
|
+
getClients(): Client[];
|
|
18
|
+
/**
|
|
19
|
+
* @param {import("./connections/web-socket/client.js").default} clientBackend
|
|
20
|
+
* @returns {void}
|
|
21
|
+
*/
|
|
22
|
+
onNewClientFromBackend: (clientBackend: import("./connections/web-socket/client.js").default) => void;
|
|
23
|
+
}
|
|
24
|
+
import Client from "../client/index.js";
|
|
25
|
+
import EventEmitter from "events";
|
|
26
|
+
import SingleEventEmitter from "../utils/single-event-emitter.js";
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.js"],"names":[],"mappings":"AAMA;IACE;;;OAGG;IACH,qBAFW,OAAO,mCAAmC,EAAE,OAAO,EAc7D;IAXC,6DAAsB;IAGtB,uBAAuB;IACvB,SADW,MAAM,EAAE,CACF;IAEjB,0BAAgC;IAEhC,2DAA2D;IAC3D,yBADW,kBAAkB,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC,CACA;IACvD,iCAFuC,MAAM,KAAK,IAAI,UAEK;IAG7D,sBAAsB;IACtB,SADc,IAAI,CACc;IAEhC,0BAA0B;IAC1B,cADc,MAAM,EAAE,CACc;IAEpC;;;OAGG;IACH,yBAA0B,eAHf,OAAO,oCAAoC,EAAE,OAGjB,KAF1B,IAAI,CAShB;CACF;mBAzCkB,oBAAoB;yBACd,QAAQ;+BACF,kCAAkC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import Client from "../client/index.js";
|
|
3
|
+
import EventEmitter from "events";
|
|
4
|
+
import SingleEventEmitter from "../utils/single-event-emitter.js";
|
|
5
|
+
export default class ScoundrelServer {
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new Scoundrel server
|
|
8
|
+
* @param {import("./connections/web-socket/index.js").default} backend The backend connection handler
|
|
9
|
+
*/
|
|
10
|
+
constructor(backend) {
|
|
11
|
+
/**
|
|
12
|
+
* @param {import("./connections/web-socket/client.js").default} clientBackend
|
|
13
|
+
* @returns {void}
|
|
14
|
+
*/
|
|
15
|
+
this.onNewClientFromBackend = (clientBackend) => {
|
|
16
|
+
const client = new Client(clientBackend, { enableServerControl: true });
|
|
17
|
+
this.clients.push(client);
|
|
18
|
+
this.events.emit("newClient", client);
|
|
19
|
+
this.onNewClientEventEmitter.emit([client]);
|
|
20
|
+
};
|
|
21
|
+
this.backend = backend;
|
|
22
|
+
this.backend.onNewClient(this.onNewClientFromBackend);
|
|
23
|
+
/** @type {Client[]} */
|
|
24
|
+
this.clients = [];
|
|
25
|
+
this.events = new EventEmitter();
|
|
26
|
+
/** @type {SingleEventEmitter<(client: Client) => void>} */
|
|
27
|
+
this.onNewClientEventEmitter = new SingleEventEmitter();
|
|
28
|
+
this.onNewClient = this.onNewClientEventEmitter.connector();
|
|
29
|
+
}
|
|
30
|
+
/** @returns {void} */
|
|
31
|
+
close() { this.backend.close(); }
|
|
32
|
+
/** @returns {Client[]} */
|
|
33
|
+
getClients() { return this.clients; }
|
|
34
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SingleEventEmitter: one listener at a time, strongly typed via JSDoc.
|
|
3
|
+
*
|
|
4
|
+
* @template {(...args: any[]) => any} Listener
|
|
5
|
+
*/
|
|
6
|
+
export default class SingleEventEmitter<Listener extends (...args: any[]) => any> {
|
|
7
|
+
/**
|
|
8
|
+
* @param {{ strict?: boolean }=} options
|
|
9
|
+
* - strict (default true): throw if a listener is already connected
|
|
10
|
+
*/
|
|
11
|
+
constructor(options?: {
|
|
12
|
+
strict?: boolean;
|
|
13
|
+
} | undefined);
|
|
14
|
+
/**
|
|
15
|
+
* Connect a listener (type-checked).
|
|
16
|
+
* @param {Listener} listener
|
|
17
|
+
* @returns {void}
|
|
18
|
+
*/
|
|
19
|
+
connect(listener: Listener): void;
|
|
20
|
+
/**
|
|
21
|
+
* Disconnect only if the same listener is currently connected.
|
|
22
|
+
* @param {Listener} listener
|
|
23
|
+
* @returns {void}
|
|
24
|
+
*/
|
|
25
|
+
disconnect(listener: Listener): void;
|
|
26
|
+
/** @returns {void} */
|
|
27
|
+
clear(): void;
|
|
28
|
+
/** @returns {boolean} */
|
|
29
|
+
hasListener(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Emit the event to the connected listener (if any).
|
|
32
|
+
* Arguments are type-checked against Listener parameters.
|
|
33
|
+
* @param {...Parameters<Listener>} args
|
|
34
|
+
* @returns {boolean} true if a listener was called
|
|
35
|
+
*/
|
|
36
|
+
emit(...args: Parameters<Listener>[]): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Create a "connect method" you can expose as `onNewClient` etc.
|
|
39
|
+
* The returned function is type-checked as (listener: Listener) => void.
|
|
40
|
+
*
|
|
41
|
+
* @returns {(listener: Listener) => void}
|
|
42
|
+
*/
|
|
43
|
+
connector(): (listener: Listener) => void;
|
|
44
|
+
#private;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=single-event-emitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"single-event-emitter.d.ts","sourceRoot":"","sources":["../../src/utils/single-event-emitter.js"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wCAFuC,QAAQ,SAAlC,CAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAI;IASpC;;;OAGG;IACH,sBAHW;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,YAAC,EAK/B;IAED;;;;OAIG;IACH,kBAHW,QAAQ,GACN,IAAI,CAOhB;IAED;;;;OAIG;IACH,qBAHW,QAAQ,GACN,IAAI,CAIhB;IAED,sBAAsB;IACtB,SADc,IAAI,CAGjB;IAED,yBAAyB;IACzB,eADc,OAAO,CAGpB;IAED;;;;;OAKG;IACH,cAHc,UAAU,CAAC,QAAQ,CAAC,EAAA,GACrB,OAAO,CAOnB;IAED;;;;;OAKG;IACH,aAFa,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAKxC;;CACF"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _SingleEventEmitter_listener, _SingleEventEmitter_strict;
|
|
14
|
+
/**
|
|
15
|
+
* SingleEventEmitter: one listener at a time, strongly typed via JSDoc.
|
|
16
|
+
*
|
|
17
|
+
* @template {(...args: any[]) => any} Listener
|
|
18
|
+
*/
|
|
19
|
+
class SingleEventEmitter {
|
|
20
|
+
/**
|
|
21
|
+
* @param {{ strict?: boolean }=} options
|
|
22
|
+
* - strict (default true): throw if a listener is already connected
|
|
23
|
+
*/
|
|
24
|
+
constructor(options) {
|
|
25
|
+
/** @type {Listener | null} */
|
|
26
|
+
_SingleEventEmitter_listener.set(this, null
|
|
27
|
+
/** @type {boolean} */
|
|
28
|
+
);
|
|
29
|
+
/** @type {boolean} */
|
|
30
|
+
_SingleEventEmitter_strict.set(this, void 0);
|
|
31
|
+
__classPrivateFieldSet(this, _SingleEventEmitter_strict, options?.strict ?? true, "f");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Connect a listener (type-checked).
|
|
35
|
+
* @param {Listener} listener
|
|
36
|
+
* @returns {void}
|
|
37
|
+
*/
|
|
38
|
+
connect(listener) {
|
|
39
|
+
if (__classPrivateFieldGet(this, _SingleEventEmitter_listener, "f") && __classPrivateFieldGet(this, _SingleEventEmitter_strict, "f")) {
|
|
40
|
+
throw new Error("SingleEventEmitter already has a listener connected");
|
|
41
|
+
}
|
|
42
|
+
__classPrivateFieldSet(this, _SingleEventEmitter_listener, listener, "f");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Disconnect only if the same listener is currently connected.
|
|
46
|
+
* @param {Listener} listener
|
|
47
|
+
* @returns {void}
|
|
48
|
+
*/
|
|
49
|
+
disconnect(listener) {
|
|
50
|
+
if (__classPrivateFieldGet(this, _SingleEventEmitter_listener, "f") === listener)
|
|
51
|
+
__classPrivateFieldSet(this, _SingleEventEmitter_listener, null, "f");
|
|
52
|
+
}
|
|
53
|
+
/** @returns {void} */
|
|
54
|
+
clear() {
|
|
55
|
+
__classPrivateFieldSet(this, _SingleEventEmitter_listener, null, "f");
|
|
56
|
+
}
|
|
57
|
+
/** @returns {boolean} */
|
|
58
|
+
hasListener() {
|
|
59
|
+
return __classPrivateFieldGet(this, _SingleEventEmitter_listener, "f") !== null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Emit the event to the connected listener (if any).
|
|
63
|
+
* Arguments are type-checked against Listener parameters.
|
|
64
|
+
* @param {...Parameters<Listener>} args
|
|
65
|
+
* @returns {boolean} true if a listener was called
|
|
66
|
+
*/
|
|
67
|
+
emit(...args) {
|
|
68
|
+
const fn = __classPrivateFieldGet(this, _SingleEventEmitter_listener, "f");
|
|
69
|
+
if (!fn)
|
|
70
|
+
return false;
|
|
71
|
+
fn(...args);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create a "connect method" you can expose as `onNewClient` etc.
|
|
76
|
+
* The returned function is type-checked as (listener: Listener) => void.
|
|
77
|
+
*
|
|
78
|
+
* @returns {(listener: Listener) => void}
|
|
79
|
+
*/
|
|
80
|
+
connector() {
|
|
81
|
+
// bind() keeps `this`, and the return type is enforced by the JSDoc above
|
|
82
|
+
return this.connect.bind(this);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
_SingleEventEmitter_listener = new WeakMap(), _SingleEventEmitter_strict = new WeakMap();
|
|
86
|
+
export default SingleEventEmitter;
|
package/package.json
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scoundrel-remote-eval",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.10",
|
|
5
5
|
"description": "",
|
|
6
|
-
"main": "
|
|
6
|
+
"main": "build/index.js",
|
|
7
|
+
"types": "build/index.d.ts",
|
|
8
|
+
"files": ["build/**"],
|
|
7
9
|
"scripts": {
|
|
10
|
+
"all-checks": "npm run typecheck && npm run lint && npm run test",
|
|
11
|
+
"build": "rm -rf build && tsc",
|
|
8
12
|
"lint": "eslint",
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
9
14
|
"typecheck": "tsc --noEmit",
|
|
10
|
-
"test": "jasmine"
|
|
15
|
+
"test": "jasmine",
|
|
16
|
+
"watch": "tsc -w"
|
|
11
17
|
},
|
|
12
18
|
"repository": {
|
|
13
19
|
"type": "git",
|
package/eslint.config.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
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
|
-
])
|
|
@@ -1,101 +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 referenceWithProxy from "../src/client/reference-proxy.js"
|
|
6
|
-
import Server from "../src/server/index.js"
|
|
7
|
-
import ServerWebSocket from "../src/server/connections/web-socket/index.js"
|
|
8
|
-
import {WebSocket, WebSocketServer} from "ws"
|
|
9
|
-
|
|
10
|
-
const shared = {}
|
|
11
|
-
|
|
12
|
-
describe("referenceWithProxy", () => {
|
|
13
|
-
beforeEach(async () => {
|
|
14
|
-
shared.wss = new WebSocketServer({port: 8080})
|
|
15
|
-
shared.serverWebSocket = new ServerWebSocket(shared.wss)
|
|
16
|
-
shared.server = new Server(shared.serverWebSocket)
|
|
17
|
-
|
|
18
|
-
shared.ws = new WebSocket("http://localhost:8080")
|
|
19
|
-
shared.clientWebSocket = new ClientWebSocket(shared.ws)
|
|
20
|
-
|
|
21
|
-
await shared.clientWebSocket.waitForOpened()
|
|
22
|
-
|
|
23
|
-
shared.client = new Client(shared.clientWebSocket)
|
|
24
|
-
|
|
25
|
-
shared.serverClient = shared.server.getClients()[0]
|
|
26
|
-
|
|
27
|
-
if (!shared.serverClient) {
|
|
28
|
-
throw new Error("No client connected to server")
|
|
29
|
-
}
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
afterEach(async () => {
|
|
33
|
-
await shared.client.close()
|
|
34
|
-
await shared.server.close()
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it("creates a reference with a proxy", async () => {
|
|
38
|
-
const stringObjectReference = await shared.client.newObjectWithReference("Array")
|
|
39
|
-
const stringObject = referenceWithProxy(stringObjectReference)
|
|
40
|
-
|
|
41
|
-
// @ts-ignore
|
|
42
|
-
await stringObject.push("test1")
|
|
43
|
-
|
|
44
|
-
// @ts-ignore
|
|
45
|
-
await stringObject.push("test2")
|
|
46
|
-
|
|
47
|
-
// @ts-ignore
|
|
48
|
-
const result = await stringObject.__serialize()
|
|
49
|
-
|
|
50
|
-
expect(result).toEqual(["test1", "test2"])
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it("reads attributes from a reference", async () => {
|
|
54
|
-
const testArray = await shared.client.newObjectWithReference("Array")
|
|
55
|
-
|
|
56
|
-
await testArray.callMethod("push", "test1")
|
|
57
|
-
await testArray.callMethod("push", "test2")
|
|
58
|
-
|
|
59
|
-
const result = await testArray.serialize()
|
|
60
|
-
|
|
61
|
-
expect(result).toEqual(["test1", "test2"])
|
|
62
|
-
|
|
63
|
-
const firstValue = await testArray.readAttribute(0)
|
|
64
|
-
const secondValue = await testArray.readAttribute(1)
|
|
65
|
-
|
|
66
|
-
expect(firstValue).toEqual("test1")
|
|
67
|
-
expect(secondValue).toEqual("test2")
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it("calls methods", async () => {
|
|
71
|
-
const stringObjectReference = await shared.client.newObjectWithReference("Array")
|
|
72
|
-
const stringObject = referenceWithProxy(stringObjectReference)
|
|
73
|
-
|
|
74
|
-
// @ts-ignore
|
|
75
|
-
await stringObject.push("test1")
|
|
76
|
-
|
|
77
|
-
// @ts-ignore
|
|
78
|
-
await stringObject.push("test2")
|
|
79
|
-
|
|
80
|
-
// @ts-ignore
|
|
81
|
-
const result = await stringObject.__serialize()
|
|
82
|
-
|
|
83
|
-
expect(result).toEqual(["test1", "test2"])
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it("calls methods on the client from the server", async () => {
|
|
87
|
-
const stringObjectReference = await shared.serverClient.newObjectWithReference("Array")
|
|
88
|
-
const stringObject = referenceWithProxy(stringObjectReference)
|
|
89
|
-
|
|
90
|
-
// @ts-ignore
|
|
91
|
-
await stringObject.push("test1")
|
|
92
|
-
|
|
93
|
-
// @ts-ignore
|
|
94
|
-
await stringObject.push("test2")
|
|
95
|
-
|
|
96
|
-
// @ts-ignore
|
|
97
|
-
const result = await stringObject.__serialize()
|
|
98
|
-
|
|
99
|
-
expect(result).toEqual(["test1", "test2"])
|
|
100
|
-
})
|
|
101
|
-
})
|