@mongosh/node-runtime-worker-thread 2.3.0 → 2.3.2
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/AUTHORS +1 -0
- package/dist/153.js +1 -0
- package/dist/41.js +1 -0
- package/dist/502.js +1 -0
- package/dist/{578.js → 503.js} +1 -1
- package/dist/{43.js → 527.js} +1 -1
- package/dist/534.js +1 -0
- package/dist/711.js +1 -0
- package/dist/739.js +1 -0
- package/dist/index.d.ts +6 -8
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/report.html +2 -2
- package/dist/rpc.d.ts +6 -15
- package/dist/rpc.js +7 -71
- package/dist/rpc.js.map +1 -1
- package/dist/worker-process-mongosh-bus.d.ts +7 -0
- package/dist/{child-process-mongosh-bus.js → worker-process-mongosh-bus.js} +8 -8
- package/dist/worker-process-mongosh-bus.js.map +1 -0
- package/dist/worker-runtime.js +16 -16
- package/dist/worker-runtime.js.map +1 -1
- package/dist/{child-process-evaluation-listener.d.ts → worker-thread-evaluation-listener.d.ts} +2 -4
- package/dist/{child-process-evaluation-listener.js → worker-thread-evaluation-listener.js} +6 -6
- package/dist/worker-thread-evaluation-listener.js.map +1 -0
- package/package.json +13 -12
- package/src/index.spec.ts +148 -147
- package/src/index.ts +98 -120
- package/src/lock.spec.ts +1 -1
- package/src/rpc.spec.ts +24 -90
- package/src/rpc.ts +17 -98
- package/src/{child-process-mongosh-bus.ts → worker-process-mongosh-bus.ts} +5 -6
- package/src/worker-runtime.spec.ts +19 -29
- package/src/worker-runtime.ts +15 -22
- package/src/{child-process-evaluation-listener.ts → worker-thread-evaluation-listener.ts} +3 -4
- package/tests/register-worker.js +1 -0
- package/tsconfig.json +2 -1
- package/webpack.config.js +4 -6
- package/__fixtures__/script-that-throws.js +0 -1
- package/dist/354.js +0 -1
- package/dist/528.js +0 -1
- package/dist/650.js +0 -1
- package/dist/722.js +0 -1
- package/dist/777.js +0 -1
- package/dist/942.js +0 -1
- package/dist/child-process-evaluation-listener.js.map +0 -1
- package/dist/child-process-mongosh-bus.d.ts +0 -9
- package/dist/child-process-mongosh-bus.js.map +0 -1
- package/dist/child-process-proxy.d.ts +0 -1
- package/dist/child-process-proxy.js +0 -1
- package/dist/child-process-proxy.js.map +0 -1
- package/dist/spawn-child-from-source.d.ts +0 -5
- package/dist/spawn-child-from-source.js +0 -74
- package/dist/spawn-child-from-source.js.map +0 -1
- package/src/child-process-proxy.spec.ts +0 -84
- package/src/child-process-proxy.ts +0 -124
- package/src/spawn-child-from-source.spec.ts +0 -90
- package/src/spawn-child-from-source.ts +0 -102
- package/tsconfig.test.json +0 -11
package/src/index.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/* istanbul ignore file */
|
|
2
|
-
/* ^^^ we test the dist directly, so
|
|
2
|
+
/* ^^^ we test the dist directly, so istanbul can't calculate the coverage correctly */
|
|
3
3
|
|
|
4
|
-
import type { ChildProcess, SpawnOptionsWithoutStdio } from 'child_process';
|
|
5
|
-
import { spawn } from 'child_process';
|
|
6
4
|
import type {
|
|
7
5
|
Runtime,
|
|
8
6
|
RuntimeEvaluationListener,
|
|
@@ -11,184 +9,162 @@ import type {
|
|
|
11
9
|
import type { MongoshBus } from '@mongosh/types';
|
|
12
10
|
import path from 'path';
|
|
13
11
|
import { EventEmitter, once } from 'events';
|
|
14
|
-
import {
|
|
12
|
+
import { pathToFileURL } from 'url';
|
|
15
13
|
import type { Caller } from './rpc';
|
|
16
|
-
import { createCaller, cancel } from './rpc';
|
|
17
|
-
import { ChildProcessEvaluationListener } from './child-process-evaluation-listener';
|
|
14
|
+
import { createCaller, cancel, exposeAll } from './rpc';
|
|
18
15
|
import type { WorkerRuntime as WorkerThreadWorkerRuntime } from './worker-runtime';
|
|
19
16
|
import {
|
|
20
17
|
deserializeEvaluationResult,
|
|
21
18
|
serializeConnectOptions,
|
|
22
19
|
} from './serializer';
|
|
23
|
-
import { ChildProcessMongoshBus } from './child-process-mongosh-bus';
|
|
24
20
|
import type { CompassServiceProvider } from '@mongosh/service-provider-server';
|
|
21
|
+
import type { InterruptHandle } from 'interruptor';
|
|
22
|
+
import { interrupt as nativeInterrupt } from 'interruptor';
|
|
23
|
+
import { WorkerThreadEvaluationListener } from './worker-thread-evaluation-listener';
|
|
24
|
+
import { WorkerProcessMongoshBus } from './worker-process-mongosh-bus';
|
|
25
25
|
|
|
26
26
|
type DevtoolsConnectOptions = Parameters<
|
|
27
27
|
(typeof CompassServiceProvider)['connect']
|
|
28
28
|
>[1];
|
|
29
|
-
type
|
|
30
|
-
|
|
31
|
-
function parseStderrToError(str: string): Error | null {
|
|
32
|
-
const [, errorMessageWithStack] = str
|
|
33
|
-
.split(/^\s*\^\s*$/m)
|
|
34
|
-
.map((part) => part.trim());
|
|
35
|
-
|
|
36
|
-
if (errorMessageWithStack) {
|
|
37
|
-
const e = new Error();
|
|
38
|
-
const errorHeader =
|
|
39
|
-
errorMessageWithStack.substring(
|
|
40
|
-
0,
|
|
41
|
-
errorMessageWithStack.search(/^\s*at/m)
|
|
42
|
-
) || errorMessageWithStack;
|
|
43
|
-
|
|
44
|
-
const [name, ...message] = errorHeader.split(': ');
|
|
45
|
-
|
|
46
|
-
e.name = name;
|
|
47
|
-
e.message = message.join(': ').trim();
|
|
48
|
-
e.stack = errorMessageWithStack;
|
|
49
|
-
|
|
50
|
-
return e;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
29
|
+
type WorkerThreadRuntime = Caller<WorkerThreadWorkerRuntime>;
|
|
55
30
|
|
|
56
31
|
class WorkerRuntime implements Runtime {
|
|
57
32
|
private initOptions: {
|
|
58
33
|
uri: string;
|
|
59
34
|
driverOptions: DevtoolsConnectOptions;
|
|
60
35
|
cliOptions: { nodb?: boolean };
|
|
61
|
-
|
|
36
|
+
workerOptions: WorkerOptions;
|
|
62
37
|
};
|
|
63
38
|
|
|
64
39
|
evaluationListener: RuntimeEvaluationListener | null = null;
|
|
65
40
|
|
|
66
41
|
private eventEmitter: MongoshBus;
|
|
67
42
|
|
|
68
|
-
private
|
|
43
|
+
private workerProcess!: Worker;
|
|
69
44
|
|
|
70
|
-
private
|
|
45
|
+
private workerProcessRuntime!: WorkerThreadRuntime;
|
|
71
46
|
|
|
72
|
-
private
|
|
47
|
+
private initWorkerPromise: Promise<void>;
|
|
73
48
|
|
|
74
|
-
private
|
|
49
|
+
private workerThreadEvaluationListener!: WorkerThreadEvaluationListener;
|
|
75
50
|
|
|
76
|
-
private
|
|
51
|
+
private workerProcessMongoshBus!: WorkerProcessMongoshBus;
|
|
77
52
|
|
|
78
|
-
private
|
|
79
|
-
process.env
|
|
80
|
-
.CHILD_PROCESS_PROXY_SRC_PATH_DO_NOT_USE_THIS_EXCEPT_FOR_TESTING ||
|
|
81
|
-
path.resolve(__dirname, 'child-process-proxy.js');
|
|
53
|
+
private workerProcessPath = path.resolve(__dirname, 'worker-runtime.js');
|
|
82
54
|
|
|
83
55
|
constructor(
|
|
84
56
|
uri: string,
|
|
85
57
|
driverOptions: DevtoolsConnectOptions,
|
|
86
58
|
cliOptions: { nodb?: boolean } = {},
|
|
87
|
-
|
|
59
|
+
workerOptions: WorkerOptions = {},
|
|
88
60
|
eventEmitter: MongoshBus = new EventEmitter()
|
|
89
61
|
) {
|
|
90
|
-
this.initOptions = { uri, driverOptions, cliOptions,
|
|
62
|
+
this.initOptions = { uri, driverOptions, cliOptions, workerOptions };
|
|
91
63
|
this.eventEmitter = eventEmitter;
|
|
92
64
|
this.initWorkerPromise = this.initWorker();
|
|
93
65
|
}
|
|
94
66
|
|
|
95
67
|
private async initWorker() {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
process.execPath,
|
|
100
|
-
[this.childProcessProxySrcPath],
|
|
101
|
-
{
|
|
102
|
-
stdio: ['inherit', 'inherit', 'pipe', 'ipc'],
|
|
103
|
-
...spawnOptions,
|
|
104
|
-
}
|
|
68
|
+
const workerProcess = new Worker(
|
|
69
|
+
pathToFileURL(this.workerProcessPath).href,
|
|
70
|
+
this.initOptions.workerOptions
|
|
105
71
|
);
|
|
106
72
|
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
73
|
+
const workerReadyPromise = async (): Promise<void> => {
|
|
74
|
+
const waitForReadyMessage = async () => {
|
|
75
|
+
let msg: {
|
|
76
|
+
data: string;
|
|
77
|
+
};
|
|
78
|
+
while (([msg] = await once(workerProcess, 'message'))) {
|
|
79
|
+
if (msg?.data === 'ready') return;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const waitForError = async () => {
|
|
84
|
+
const [err] = await once(workerProcess, 'error');
|
|
85
|
+
if (err) {
|
|
86
|
+
err.message = `Worker thread failed to start with error: ${
|
|
87
|
+
(err as Error).message
|
|
88
|
+
}`;
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
115
92
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
});
|
|
93
|
+
await Promise.race([waitForReadyMessage(), waitForError()]);
|
|
94
|
+
};
|
|
119
95
|
|
|
120
|
-
|
|
121
|
-
const [exitCode] = await once(this.childProcess, 'exit');
|
|
96
|
+
await workerReadyPromise();
|
|
122
97
|
|
|
123
|
-
|
|
124
|
-
let error = parseStderrToError(spawnError);
|
|
98
|
+
const { interrupt } = createCaller(['interrupt'], workerProcess);
|
|
125
99
|
|
|
126
|
-
|
|
127
|
-
error.message = `Child process failed to start with the following error: ${error.message}`;
|
|
128
|
-
} else {
|
|
129
|
-
error = new Error(
|
|
130
|
-
`Worker runtime failed to start: child process exited with code ${
|
|
131
|
-
exitCode as number | string
|
|
132
|
-
}`
|
|
133
|
-
);
|
|
134
|
-
}
|
|
100
|
+
let interruptHandle: InterruptHandle | null = null;
|
|
135
101
|
|
|
136
|
-
|
|
102
|
+
this.workerProcessRuntime = Object.assign(
|
|
103
|
+
createCaller(
|
|
104
|
+
[
|
|
105
|
+
'init',
|
|
106
|
+
'evaluate',
|
|
107
|
+
'getCompletions',
|
|
108
|
+
'getShellPrompt',
|
|
109
|
+
'setEvaluationListener',
|
|
110
|
+
'interrupt',
|
|
111
|
+
],
|
|
112
|
+
workerProcess
|
|
113
|
+
),
|
|
114
|
+
{
|
|
115
|
+
interrupt(): boolean {
|
|
116
|
+
if (interruptHandle) {
|
|
117
|
+
nativeInterrupt(interruptHandle);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return interrupt();
|
|
122
|
+
},
|
|
137
123
|
}
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
await Promise.race([waitForReadyMessage(), waitForError()]);
|
|
141
|
-
|
|
142
|
-
// We expect the amount of listeners to be more than the default value of 10
|
|
143
|
-
// but probably not more than ~25 (all exposed methods on
|
|
144
|
-
// ChildProcessEvaluationListener and ChildProcessMongoshBus + any
|
|
145
|
-
// concurrent in-flight calls on ChildProcessRuntime) at once
|
|
146
|
-
this.childProcess.setMaxListeners(25);
|
|
147
|
-
|
|
148
|
-
this.childProcessRuntime = createCaller(
|
|
149
|
-
[
|
|
150
|
-
'init',
|
|
151
|
-
'evaluate',
|
|
152
|
-
'getCompletions',
|
|
153
|
-
'setEvaluationListener',
|
|
154
|
-
'getShellPrompt',
|
|
155
|
-
'interrupt',
|
|
156
|
-
],
|
|
157
|
-
this.childProcess
|
|
158
124
|
);
|
|
159
125
|
|
|
160
|
-
this.
|
|
126
|
+
this.workerThreadEvaluationListener = new WorkerThreadEvaluationListener(
|
|
161
127
|
this,
|
|
162
|
-
|
|
128
|
+
workerProcess
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
exposeAll(
|
|
132
|
+
{
|
|
133
|
+
onRunInterruptible(handle: InterruptHandle | null) {
|
|
134
|
+
interruptHandle = handle;
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
workerProcess
|
|
163
138
|
);
|
|
164
139
|
|
|
165
|
-
this.
|
|
140
|
+
this.workerProcessMongoshBus = new WorkerProcessMongoshBus(
|
|
166
141
|
this.eventEmitter,
|
|
167
|
-
|
|
142
|
+
workerProcess
|
|
168
143
|
);
|
|
169
144
|
|
|
170
|
-
await this.
|
|
171
|
-
uri,
|
|
172
|
-
serializeConnectOptions(driverOptions),
|
|
173
|
-
cliOptions
|
|
145
|
+
await this.workerProcessRuntime.init(
|
|
146
|
+
this.initOptions.uri,
|
|
147
|
+
serializeConnectOptions(this.initOptions.driverOptions),
|
|
148
|
+
this.initOptions.cliOptions
|
|
174
149
|
);
|
|
150
|
+
this.workerProcess = workerProcess;
|
|
175
151
|
}
|
|
176
152
|
|
|
177
153
|
async evaluate(code: string): Promise<RuntimeEvaluationResult> {
|
|
178
154
|
await this.initWorkerPromise;
|
|
179
155
|
return deserializeEvaluationResult(
|
|
180
|
-
await this.
|
|
156
|
+
await this.workerProcessRuntime.evaluate(code)
|
|
181
157
|
);
|
|
182
158
|
}
|
|
183
159
|
|
|
184
160
|
async getCompletions(code: string) {
|
|
185
161
|
await this.initWorkerPromise;
|
|
186
|
-
return await this.
|
|
162
|
+
return await this.workerProcessRuntime.getCompletions(code);
|
|
187
163
|
}
|
|
188
164
|
|
|
189
165
|
async getShellPrompt() {
|
|
190
166
|
await this.initWorkerPromise;
|
|
191
|
-
return await this.
|
|
167
|
+
return await this.workerProcessRuntime.getShellPrompt();
|
|
192
168
|
}
|
|
193
169
|
|
|
194
170
|
setEvaluationListener(listener: RuntimeEvaluationListener | null) {
|
|
@@ -201,28 +177,30 @@ class WorkerRuntime implements Runtime {
|
|
|
201
177
|
try {
|
|
202
178
|
await this.initWorkerPromise;
|
|
203
179
|
} catch {
|
|
204
|
-
// In case
|
|
205
|
-
// to clean up whatever possible
|
|
180
|
+
// In case the worker thread encountered an error during init
|
|
181
|
+
// we still want to clean up whatever possible.
|
|
206
182
|
}
|
|
207
183
|
|
|
208
|
-
|
|
184
|
+
if (this.workerProcessRuntime) {
|
|
185
|
+
this.workerProcessRuntime[cancel]();
|
|
186
|
+
}
|
|
209
187
|
|
|
210
|
-
if (this.
|
|
211
|
-
this.
|
|
188
|
+
if (this.workerProcess) {
|
|
189
|
+
this.workerProcess.terminate();
|
|
212
190
|
}
|
|
213
191
|
|
|
214
|
-
if (this.
|
|
215
|
-
this.
|
|
192
|
+
if (this.workerThreadEvaluationListener) {
|
|
193
|
+
this.workerThreadEvaluationListener.terminate();
|
|
216
194
|
}
|
|
217
195
|
|
|
218
|
-
if (this.
|
|
219
|
-
this.
|
|
196
|
+
if (this.workerProcessMongoshBus) {
|
|
197
|
+
this.workerProcessMongoshBus.terminate();
|
|
220
198
|
}
|
|
221
199
|
}
|
|
222
200
|
|
|
223
201
|
async interrupt() {
|
|
224
202
|
await this.initWorkerPromise;
|
|
225
|
-
return this.
|
|
203
|
+
return this.workerProcessRuntime.interrupt();
|
|
226
204
|
}
|
|
227
205
|
|
|
228
206
|
async waitForRuntimeToBeReady() {
|
package/src/lock.spec.ts
CHANGED
package/src/rpc.spec.ts
CHANGED
|
@@ -1,58 +1,24 @@
|
|
|
1
1
|
import { expect } from 'chai';
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
3
|
|
|
4
|
-
import type { Caller, Exposed } from './rpc';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function createMockRpcMesageBus() {
|
|
16
|
-
const bus = new (class Bus extends EventEmitter {
|
|
17
|
-
send(data: any) {
|
|
18
|
-
this.emit('message', data);
|
|
19
|
-
}
|
|
20
|
-
})();
|
|
21
|
-
return bus;
|
|
4
|
+
import type { Caller, Exposed, RPCMessageBus } from './rpc';
|
|
5
|
+
import { createCaller, exposeAll, close, cancel } from './rpc';
|
|
6
|
+
|
|
7
|
+
function createMockRpcMesageBus(): RPCMessageBus {
|
|
8
|
+
const ee = new EventEmitter();
|
|
9
|
+
return {
|
|
10
|
+
addEventListener: ee.on.bind(ee),
|
|
11
|
+
removeEventListener: ee.off.bind(ee),
|
|
12
|
+
postMessage: (data: unknown) => ee.emit('message', data),
|
|
13
|
+
};
|
|
22
14
|
}
|
|
23
15
|
|
|
24
16
|
function sleep(ms: number) {
|
|
25
17
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
26
18
|
}
|
|
27
19
|
|
|
28
|
-
describe('rpc helpers', function () {
|
|
29
|
-
describe('serialize', function () {
|
|
30
|
-
it('returns base64 representation of an input', function () {
|
|
31
|
-
expect(serialize('Hello')).to.match(/data:;base64,\/w[08]iBUhlbGxv/);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('deserialize', function () {
|
|
36
|
-
it("converts base64 representation of input back to it's original form", function () {
|
|
37
|
-
expect(deserialize(serialize('Hello'))).to.equal('Hello');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("returns original string if it's not a base64 data uri", function () {
|
|
41
|
-
expect(deserialize('Hi')).to.equal('Hi');
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
describe('removeTrailingUndefined', function () {
|
|
46
|
-
it('removes trailing undefineds from an array', function () {
|
|
47
|
-
expect(
|
|
48
|
-
removeTrailingUndefined([1, 2, 3, undefined, undefined, undefined])
|
|
49
|
-
).to.deep.equal([1, 2, 3]);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
20
|
describe('rpc', function () {
|
|
55
|
-
let messageBus:
|
|
21
|
+
let messageBus: RPCMessageBus | null;
|
|
56
22
|
let caller: Caller<{
|
|
57
23
|
meow(...args: any[]): string;
|
|
58
24
|
throws(...args: any[]): never;
|
|
@@ -61,22 +27,21 @@ describe('rpc', function () {
|
|
|
61
27
|
woof(...args: any[]): string;
|
|
62
28
|
neverResolves(...args: any[]): void;
|
|
63
29
|
}>;
|
|
64
|
-
let exposed: Exposed<unknown>;
|
|
30
|
+
let exposed: Exposed<unknown>; // adding `| null` breaks TS type inference
|
|
65
31
|
|
|
66
32
|
afterEach(function () {
|
|
67
33
|
if (messageBus) {
|
|
68
|
-
messageBus.removeAllListeners();
|
|
69
34
|
messageBus = null;
|
|
70
35
|
}
|
|
71
36
|
|
|
72
37
|
if (caller) {
|
|
73
38
|
caller[cancel]();
|
|
74
|
-
caller = null;
|
|
39
|
+
caller = null as any;
|
|
75
40
|
}
|
|
76
41
|
|
|
77
42
|
if (exposed) {
|
|
78
43
|
exposed[close]();
|
|
79
|
-
exposed = null;
|
|
44
|
+
exposed = null as any;
|
|
80
45
|
}
|
|
81
46
|
});
|
|
82
47
|
|
|
@@ -109,7 +74,7 @@ describe('rpc', function () {
|
|
|
109
74
|
messageBus
|
|
110
75
|
);
|
|
111
76
|
|
|
112
|
-
let err
|
|
77
|
+
let err!: Error;
|
|
113
78
|
|
|
114
79
|
try {
|
|
115
80
|
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
@@ -126,7 +91,7 @@ describe('rpc', function () {
|
|
|
126
91
|
.match(/TypeError: Uh-oh, error!\r?\n\s+at throws/);
|
|
127
92
|
});
|
|
128
93
|
|
|
129
|
-
it('
|
|
94
|
+
it('allows undefined response', async function () {
|
|
130
95
|
messageBus = createMockRpcMesageBus();
|
|
131
96
|
caller = createCaller(['callMe'], messageBus);
|
|
132
97
|
|
|
@@ -139,21 +104,11 @@ describe('rpc', function () {
|
|
|
139
104
|
messageBus
|
|
140
105
|
);
|
|
141
106
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
await caller.callMe((a: number, b: number) => a + b);
|
|
146
|
-
} catch (e: any) {
|
|
147
|
-
err = e;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
expect(err).to.be.instanceof(Error);
|
|
151
|
-
expect(err)
|
|
152
|
-
.to.have.property('message')
|
|
153
|
-
.match(/could not be cloned/);
|
|
107
|
+
expect(await caller.callMe((a: number, b: number) => a + b)).to.be
|
|
108
|
+
.undefined;
|
|
154
109
|
});
|
|
155
110
|
|
|
156
|
-
it('
|
|
111
|
+
it('allows function response', async function () {
|
|
157
112
|
messageBus = createMockRpcMesageBus();
|
|
158
113
|
caller = createCaller(['returnsFunction'], messageBus);
|
|
159
114
|
|
|
@@ -166,18 +121,7 @@ describe('rpc', function () {
|
|
|
166
121
|
messageBus
|
|
167
122
|
);
|
|
168
123
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
await caller.returnsFunction();
|
|
173
|
-
} catch (e: any) {
|
|
174
|
-
err = e;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
expect(err).to.be.instanceof(Error);
|
|
178
|
-
expect(err)
|
|
179
|
-
.to.have.property('message')
|
|
180
|
-
.match(/could not be cloned/);
|
|
124
|
+
expect(await caller.returnsFunction()).to.be.instanceof(Function);
|
|
181
125
|
});
|
|
182
126
|
|
|
183
127
|
describe('createCaller', function () {
|
|
@@ -192,7 +136,7 @@ describe('rpc', function () {
|
|
|
192
136
|
messageBus = createMockRpcMesageBus();
|
|
193
137
|
caller = createCaller(['meow'], messageBus);
|
|
194
138
|
|
|
195
|
-
messageBus.
|
|
139
|
+
messageBus.addEventListener('message', (data: unknown) => {
|
|
196
140
|
expect(data).to.have.property('func', 'meow');
|
|
197
141
|
done();
|
|
198
142
|
});
|
|
@@ -206,7 +150,7 @@ describe('rpc', function () {
|
|
|
206
150
|
it('stops all in-flight evaluations', async function () {
|
|
207
151
|
messageBus = createMockRpcMesageBus();
|
|
208
152
|
caller = createCaller(['neverResolves'], messageBus);
|
|
209
|
-
let err
|
|
153
|
+
let err!: Error;
|
|
210
154
|
try {
|
|
211
155
|
await Promise.all([
|
|
212
156
|
caller.neverResolves(),
|
|
@@ -238,7 +182,7 @@ describe('rpc', function () {
|
|
|
238
182
|
messageBus
|
|
239
183
|
);
|
|
240
184
|
|
|
241
|
-
messageBus.
|
|
185
|
+
messageBus.addEventListener('message', (data: any) => {
|
|
242
186
|
// Due to how our mocks implemented we have to introduce an if here to
|
|
243
187
|
// skip our own message being received by the message bus
|
|
244
188
|
if (data.sender === 'postmsg-rpc/server') {
|
|
@@ -251,21 +195,11 @@ describe('rpc', function () {
|
|
|
251
195
|
}
|
|
252
196
|
});
|
|
253
197
|
|
|
254
|
-
messageBus.
|
|
198
|
+
messageBus.postMessage({
|
|
255
199
|
sender: 'postmsg-rpc/client',
|
|
256
200
|
func: 'meow',
|
|
257
201
|
id: '123abc',
|
|
258
202
|
});
|
|
259
203
|
});
|
|
260
|
-
|
|
261
|
-
describe('close', function () {
|
|
262
|
-
it('disables all exposed listeners', function () {
|
|
263
|
-
messageBus = createMockRpcMesageBus();
|
|
264
|
-
exposed = exposeAll({ doSomething() {} }, messageBus);
|
|
265
|
-
expect(messageBus.listenerCount('message')).to.equal(1);
|
|
266
|
-
exposed[close]();
|
|
267
|
-
expect(messageBus.listenerCount('message')).to.equal(0);
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
204
|
});
|
|
271
205
|
});
|
package/src/rpc.ts
CHANGED
|
@@ -1,30 +1,12 @@
|
|
|
1
|
-
import v8 from 'v8';
|
|
2
1
|
import { expose, caller } from 'postmsg-rpc';
|
|
3
2
|
import { deserializeError, serializeError } from './serializer';
|
|
4
|
-
import type {
|
|
5
|
-
MessageData,
|
|
6
|
-
PostmsgRpcOptions,
|
|
7
|
-
ServerMessageData,
|
|
8
|
-
ClientMessageData,
|
|
9
|
-
} from 'postmsg-rpc';
|
|
3
|
+
import type { PostmsgRpcOptions } from 'postmsg-rpc';
|
|
10
4
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (/^data:;base64,.+/.test(str)) {
|
|
17
|
-
return v8.deserialize(
|
|
18
|
-
Buffer.from(str.replace('data:;base64,', ''), 'base64')
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
return str;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
type RPCMessageBus = { on: Function; off: Function } & (
|
|
25
|
-
| { postMessage: Function; send?: never }
|
|
26
|
-
| { postMessage?: never; send?: Function }
|
|
27
|
-
);
|
|
5
|
+
export type RPCMessageBus = {
|
|
6
|
+
postMessage: Function;
|
|
7
|
+
addEventListener: Function;
|
|
8
|
+
removeEventListener: Function;
|
|
9
|
+
};
|
|
28
10
|
|
|
29
11
|
enum RPCMessageTypes {
|
|
30
12
|
Message,
|
|
@@ -47,81 +29,15 @@ function isRPCError(data: any): data is RPCError {
|
|
|
47
29
|
);
|
|
48
30
|
}
|
|
49
31
|
|
|
50
|
-
function isMessageData(data: any): data is MessageData {
|
|
51
|
-
return data && typeof data === 'object' && 'id' in data && 'sender' in data;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function isServerMessageData(data: any): data is ServerMessageData {
|
|
55
|
-
return isMessageData(data) && data.sender === 'postmsg-rpc/server';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function isClientMessageData(data: any): data is ClientMessageData {
|
|
59
|
-
return isMessageData(data) && data.sender === 'postmsg-rpc/client';
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function removeTrailingUndefined(arr: unknown[]): unknown[] {
|
|
63
|
-
if (Array.isArray(arr)) {
|
|
64
|
-
arr = [...arr];
|
|
65
|
-
while (arr.length > 0 && arr[arr.length - 1] === undefined) {
|
|
66
|
-
arr.pop();
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return arr;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function send(messageBus: RPCMessageBus, data: any): void {
|
|
73
|
-
if (
|
|
74
|
-
'postMessage' in messageBus &&
|
|
75
|
-
typeof messageBus.postMessage === 'function'
|
|
76
|
-
) {
|
|
77
|
-
messageBus.postMessage(data);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if ('send' in messageBus && typeof messageBus.send === 'function') {
|
|
81
|
-
messageBus.send(data);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
32
|
function getRPCOptions(messageBus: RPCMessageBus): PostmsgRpcOptions {
|
|
86
33
|
return {
|
|
87
|
-
addListener: messageBus.
|
|
88
|
-
removeListener: messageBus.
|
|
34
|
+
addListener: messageBus.addEventListener.bind(messageBus),
|
|
35
|
+
removeListener: messageBus.removeEventListener.bind(messageBus),
|
|
89
36
|
postMessage(data) {
|
|
90
|
-
|
|
91
|
-
data.args = serialize(removeTrailingUndefined(data.args));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (isServerMessageData(data)) {
|
|
95
|
-
// If serialization of the response failed for some reason (e.g., the
|
|
96
|
-
// value is not serializable) we want to propagate the error back to the
|
|
97
|
-
// client that issued the remote call instead of throwing on the server
|
|
98
|
-
// that was executing the method.
|
|
99
|
-
try {
|
|
100
|
-
data.res = serialize(data.res);
|
|
101
|
-
} catch (e: any) {
|
|
102
|
-
data.res = serialize({
|
|
103
|
-
type: RPCMessageTypes.Error,
|
|
104
|
-
payload: serializeError(e),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return send(messageBus, data);
|
|
37
|
+
return messageBus.postMessage(data);
|
|
110
38
|
},
|
|
111
|
-
getMessageData(
|
|
112
|
-
|
|
113
|
-
isClientMessageData(data) &&
|
|
114
|
-
data.args &&
|
|
115
|
-
typeof data.args === 'string'
|
|
116
|
-
) {
|
|
117
|
-
data.args = deserialize(data.args);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (isServerMessageData(data) && typeof data.res === 'string') {
|
|
121
|
-
data.res = deserialize(data.res);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return data;
|
|
39
|
+
getMessageData(event) {
|
|
40
|
+
return (event as { data: unknown }).data ?? event;
|
|
125
41
|
},
|
|
126
42
|
};
|
|
127
43
|
}
|
|
@@ -134,7 +50,10 @@ export type Exposed<T> = { [k in keyof T]: T[k] & { close(): void } } & {
|
|
|
134
50
|
[close]: () => void;
|
|
135
51
|
};
|
|
136
52
|
|
|
137
|
-
export function exposeAll<O
|
|
53
|
+
export function exposeAll<O extends object>(
|
|
54
|
+
obj: O,
|
|
55
|
+
messageBus: RPCMessageBus
|
|
56
|
+
): Exposed<O> {
|
|
138
57
|
Object.entries(obj as Record<string, any>).forEach(([key, val]) => {
|
|
139
58
|
const { close } = expose(
|
|
140
59
|
key,
|
|
@@ -151,7 +70,7 @@ export function exposeAll<O>(obj: O, messageBus: RPCMessageBus): Exposed<O> {
|
|
|
151
70
|
},
|
|
152
71
|
getRPCOptions(messageBus)
|
|
153
72
|
);
|
|
154
|
-
|
|
73
|
+
val.close = close;
|
|
155
74
|
});
|
|
156
75
|
Object.defineProperty(obj, close, {
|
|
157
76
|
enumerable: false,
|
|
@@ -171,7 +90,7 @@ export type Caller<
|
|
|
171
90
|
Keys extends keyof Impl = keyof Impl
|
|
172
91
|
> = CancelableMethods<Pick<Impl, Keys>> & { [cancel]: () => void };
|
|
173
92
|
|
|
174
|
-
export function createCaller<Impl extends
|
|
93
|
+
export function createCaller<Impl extends object>(
|
|
175
94
|
methodNames: Extract<keyof Impl, string>[],
|
|
176
95
|
messageBus: RPCMessageBus,
|
|
177
96
|
processors: Partial<
|