@scrypted/server 0.6.24 → 0.7.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.
Potentially problematic release.
This version of @scrypted/server might be problematic. Click here for more details.
- package/dist/http-interfaces.js +4 -1
- package/dist/http-interfaces.js.map +1 -1
- package/dist/listen-zero.js +5 -2
- package/dist/listen-zero.js.map +1 -1
- package/dist/plugin/media.js +25 -20
- package/dist/plugin/media.js.map +1 -1
- package/dist/plugin/plugin-console.js +157 -4
- package/dist/plugin/plugin-console.js.map +1 -1
- package/dist/plugin/plugin-device.js +2 -0
- package/dist/plugin/plugin-device.js.map +1 -1
- package/dist/plugin/plugin-host.js +5 -0
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-remote-stats.js +30 -0
- package/dist/plugin/plugin-remote-stats.js.map +1 -0
- package/dist/plugin/plugin-remote-worker.js +69 -149
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/plugin/plugin-repl.js +4 -1
- package/dist/plugin/plugin-repl.js.map +1 -1
- package/dist/plugin/runtime/python-worker.js +1 -0
- package/dist/plugin/runtime/python-worker.js.map +1 -1
- package/dist/plugin/system.js +4 -0
- package/dist/plugin/system.js.map +1 -1
- package/dist/rpc.js +183 -45
- package/dist/rpc.js.map +1 -1
- package/dist/runtime.js +3 -0
- package/dist/runtime.js.map +1 -1
- package/dist/threading.js +1 -0
- package/dist/threading.js.map +1 -1
- package/package.json +3 -4
- package/python/plugin_remote.py +134 -51
- package/python/rpc-iterator-test.py +45 -0
- package/python/rpc.py +168 -50
- package/python/rpc_reader.py +57 -60
- package/src/http-interfaces.ts +5 -1
- package/src/listen-zero.ts +6 -2
- package/src/plugin/media.ts +38 -35
- package/src/plugin/plugin-api.ts +4 -1
- package/src/plugin/plugin-console.ts +154 -6
- package/src/plugin/plugin-device.ts +3 -0
- package/src/plugin/plugin-host.ts +5 -0
- package/src/plugin/plugin-remote-stats.ts +36 -0
- package/src/plugin/plugin-remote-worker.ts +77 -178
- package/src/plugin/plugin-remote.ts +1 -1
- package/src/plugin/plugin-repl.ts +4 -1
- package/src/plugin/runtime/python-worker.ts +2 -0
- package/src/plugin/system.ts +6 -0
- package/src/rpc.ts +230 -52
- package/src/runtime.ts +3 -0
- package/src/threading.ts +2 -0
- package/test/rpc-iterator-test.ts +46 -0
- package/test/rpc-python-test.ts +44 -0
package/src/rpc.ts
CHANGED
@@ -48,8 +48,18 @@ interface RpcApply extends RpcMessage {
|
|
48
48
|
|
49
49
|
interface RpcResult extends RpcMessage {
|
50
50
|
id: string;
|
51
|
+
// TODO 3/2/2023
|
52
|
+
// deprecate these properties from rpc protocol. treat error results like any other result
|
53
|
+
// and auto serialize them.
|
54
|
+
/**
|
55
|
+
* @deprecated
|
56
|
+
*/
|
51
57
|
stack?: string;
|
58
|
+
/**
|
59
|
+
* @deprecated
|
60
|
+
*/
|
52
61
|
message?: string;
|
62
|
+
throw?: boolean;
|
53
63
|
result?: any;
|
54
64
|
}
|
55
65
|
|
@@ -81,6 +91,12 @@ export interface PrimitiveProxyHandler<T extends object> extends ProxyHandler<T>
|
|
81
91
|
}
|
82
92
|
|
83
93
|
class RpcProxy implements PrimitiveProxyHandler<any> {
|
94
|
+
static iteratorMethods = new Set([
|
95
|
+
'next',
|
96
|
+
'throw',
|
97
|
+
'return',
|
98
|
+
]);
|
99
|
+
|
84
100
|
constructor(public peer: RpcPeer,
|
85
101
|
public entry: LocalProxiedEntry,
|
86
102
|
public constructorName: string,
|
@@ -94,6 +110,18 @@ class RpcProxy implements PrimitiveProxyHandler<any> {
|
|
94
110
|
}
|
95
111
|
|
96
112
|
get(target: any, p: PropertyKey, receiver: any): any {
|
113
|
+
if (p === Symbol.asyncIterator) {
|
114
|
+
if (!this.proxyProps?.[Symbol.asyncIterator.toString()])
|
115
|
+
return;
|
116
|
+
return () => {
|
117
|
+
return new Proxy(() => { }, this);
|
118
|
+
};
|
119
|
+
}
|
120
|
+
if (RpcProxy.iteratorMethods.has(p?.toString())) {
|
121
|
+
const asyncIteratorMethod = this.proxyProps?.[Symbol.asyncIterator.toString()]?.[p];
|
122
|
+
if (asyncIteratorMethod)
|
123
|
+
return new Proxy(() => asyncIteratorMethod, this);
|
124
|
+
}
|
97
125
|
if (p === RpcPeer.PROPERTY_PROXY_ID)
|
98
126
|
return this.entry.id;
|
99
127
|
if (p === '__proxy_constructor')
|
@@ -131,13 +159,18 @@ class RpcProxy implements PrimitiveProxyHandler<any> {
|
|
131
159
|
}
|
132
160
|
|
133
161
|
apply(target: any, thisArg: any, argArray?: any): any {
|
134
|
-
|
135
|
-
|
162
|
+
const method = target() || null;
|
163
|
+
const oneway = this.proxyOneWayMethods?.includes?.(method);
|
164
|
+
|
165
|
+
if (Object.isFrozen(this.peer.pendingResults)) {
|
166
|
+
if (oneway)
|
167
|
+
return Promise.resolve();
|
168
|
+
return Promise.reject(new RPCResultError(this.peer, 'RpcPeer has been killed (apply) ' + target()));
|
169
|
+
}
|
136
170
|
|
137
171
|
// rpc objects can be functions. if the function is a oneway method,
|
138
172
|
// it will have a null in the oneway method list. this is because
|
139
173
|
// undefined is not JSON serializable.
|
140
|
-
const method = target() || null;
|
141
174
|
const args: any[] = [];
|
142
175
|
const serializationContext: any = {};
|
143
176
|
for (const arg of (argArray || [])) {
|
@@ -152,7 +185,7 @@ class RpcProxy implements PrimitiveProxyHandler<any> {
|
|
152
185
|
method,
|
153
186
|
};
|
154
187
|
|
155
|
-
if (
|
188
|
+
if (oneway) {
|
156
189
|
rpcApply.oneway = true;
|
157
190
|
// a oneway callable object doesn't need to be in the JSON payload.
|
158
191
|
if (method === null)
|
@@ -161,13 +194,46 @@ class RpcProxy implements PrimitiveProxyHandler<any> {
|
|
161
194
|
return Promise.resolve();
|
162
195
|
}
|
163
196
|
|
164
|
-
|
197
|
+
const pendingResult = this.peer.createPendingResult((id, reject) => {
|
165
198
|
rpcApply.id = id;
|
166
199
|
this.peer.send(rpcApply, reject, serializationContext);
|
167
|
-
})
|
200
|
+
});
|
201
|
+
|
202
|
+
const asyncIterator = this.proxyProps?.[Symbol.asyncIterator.toString()];
|
203
|
+
if (!asyncIterator || (method !== asyncIterator.next && method !== asyncIterator.return))
|
204
|
+
return pendingResult;
|
205
|
+
|
206
|
+
return pendingResult
|
207
|
+
.then(value => {
|
208
|
+
if (method === asyncIterator.return) {
|
209
|
+
return {
|
210
|
+
done: true,
|
211
|
+
value: undefined,
|
212
|
+
}
|
213
|
+
}
|
214
|
+
return ({
|
215
|
+
value,
|
216
|
+
done: false,
|
217
|
+
});
|
218
|
+
})
|
219
|
+
.catch(e => {
|
220
|
+
if (e.name === 'StopAsyncIteration') {
|
221
|
+
return {
|
222
|
+
done: true,
|
223
|
+
value: undefined,
|
224
|
+
}
|
225
|
+
}
|
226
|
+
throw e;
|
227
|
+
})
|
168
228
|
}
|
169
229
|
}
|
170
230
|
|
231
|
+
interface SerialiedRpcResultError {
|
232
|
+
name: string;
|
233
|
+
stack: string;
|
234
|
+
message: string;
|
235
|
+
}
|
236
|
+
|
171
237
|
// todo: error constructor adds a "cause" variable in Chrome 93, Node v??
|
172
238
|
export class RPCResultError extends Error {
|
173
239
|
constructor(peer: RpcPeer, message: string, public cause?: Error, options?: { name: string, stack: string | undefined }) {
|
@@ -177,7 +243,7 @@ export class RPCResultError extends Error {
|
|
177
243
|
this.name = options?.name;
|
178
244
|
}
|
179
245
|
if (options?.stack) {
|
180
|
-
this.stack = `${peer.peerName}:${peer.selfName}
|
246
|
+
this.stack = `${cause?.stack || options.stack}\n${peer.peerName}:${peer.selfName}`;
|
181
247
|
}
|
182
248
|
}
|
183
249
|
}
|
@@ -188,10 +254,10 @@ function compileFunction(code: string, params?: ReadonlyArray<string>, options?:
|
|
188
254
|
return eval(f);
|
189
255
|
}
|
190
256
|
|
191
|
-
declare class WeakRef {
|
192
|
-
target:
|
257
|
+
declare class WeakRef<T> {
|
258
|
+
target: T;
|
193
259
|
constructor(target: any);
|
194
|
-
deref():
|
260
|
+
deref(): T;
|
195
261
|
}
|
196
262
|
|
197
263
|
try {
|
@@ -225,21 +291,29 @@ interface LocalProxiedEntry {
|
|
225
291
|
finalizerId: string | undefined;
|
226
292
|
}
|
227
293
|
|
294
|
+
interface ErrorType {
|
295
|
+
name: string;
|
296
|
+
message: string;
|
297
|
+
stack?: string;
|
298
|
+
}
|
299
|
+
|
228
300
|
export class RpcPeer {
|
229
301
|
idCounter = 1;
|
230
302
|
params: { [name: string]: any } = {};
|
231
303
|
pendingResults: { [id: string]: Deferred } = {};
|
232
304
|
proxyCounter = 1;
|
233
305
|
localProxied = new Map<any, LocalProxiedEntry>();
|
234
|
-
localProxyMap
|
306
|
+
localProxyMap = new Map<string, any>();
|
235
307
|
// @ts-ignore
|
236
308
|
remoteWeakProxies: { [id: string]: WeakRef<any> } = {};
|
237
309
|
// @ts-ignore
|
238
310
|
finalizers = new FinalizationRegistry(entry => this.finalize(entry as LocalProxiedEntry));
|
239
311
|
nameDeserializerMap = new Map<string, RpcSerializer>();
|
312
|
+
onProxyTypeSerialization = new Map<string, (value: any) => void>();
|
313
|
+
onProxySerialization: (value: any, proxyId: string) => any;
|
240
314
|
constructorSerializerMap = new Map<any, string>();
|
241
315
|
transportSafeArgumentTypes = RpcPeer.getDefaultTransportSafeArgumentTypes();
|
242
|
-
killed: Promise<
|
316
|
+
killed: Promise<string>;
|
243
317
|
killedDeferred: Deferred;
|
244
318
|
tags: any = {};
|
245
319
|
|
@@ -280,6 +354,37 @@ export class RpcPeer {
|
|
280
354
|
}
|
281
355
|
}
|
282
356
|
|
357
|
+
// static setProxyProperties(value: any, properties: any) {
|
358
|
+
// value[RpcPeer.PROPERTY_PROXY_PROPERTIES] = properties;
|
359
|
+
// }
|
360
|
+
|
361
|
+
// static getProxyProperties(value: any) {
|
362
|
+
// return value?.[RpcPeer.PROPERTY_PROXY_PROPERTIES];
|
363
|
+
// }
|
364
|
+
|
365
|
+
static getIteratorNext(target: any): string {
|
366
|
+
if (!target[Symbol.asyncIterator])
|
367
|
+
return;
|
368
|
+
const proxyProps = target[this.PROPERTY_PROXY_PROPERTIES]?.[Symbol.asyncIterator.toString()];
|
369
|
+
return proxyProps?.next || 'next';
|
370
|
+
}
|
371
|
+
|
372
|
+
static prepareProxyProperties(value: any) {
|
373
|
+
let props = value?.[RpcPeer.PROPERTY_PROXY_PROPERTIES];
|
374
|
+
if (!value[Symbol.asyncIterator])
|
375
|
+
return props;
|
376
|
+
props ||= {};
|
377
|
+
if (!props[Symbol.asyncIterator.toString()]) {
|
378
|
+
props[Symbol.asyncIterator.toString()] = {
|
379
|
+
next: 'next',
|
380
|
+
throw: 'throw',
|
381
|
+
return: 'return',
|
382
|
+
};
|
383
|
+
}
|
384
|
+
return props;
|
385
|
+
}
|
386
|
+
|
387
|
+
static readonly RPC_RESULT_ERROR_NAME = 'RPCResultError';
|
283
388
|
static readonly PROPERTY_PROXY_ID = '__proxy_id';
|
284
389
|
static readonly PROPERTY_PROXY_ONEWAY_METHODS = '__proxy_oneway_methods';
|
285
390
|
static readonly PROPERTY_JSON_DISABLE_SERIALIZATION = '__json_disable_serialization';
|
@@ -298,15 +403,22 @@ export class RpcPeer {
|
|
298
403
|
]);
|
299
404
|
|
300
405
|
constructor(public selfName: string, public peerName: string, public send: (message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any) => void) {
|
301
|
-
this.killed = new Promise((resolve, reject) => {
|
406
|
+
this.killed = new Promise<string>((resolve, reject) => {
|
302
407
|
this.killedDeferred = { resolve, reject };
|
303
|
-
});
|
304
|
-
|
408
|
+
}).catch(e => e.message || 'Unknown Error');
|
409
|
+
}
|
410
|
+
|
411
|
+
static isTransportSafe(value: any) {
|
412
|
+
return !value || (!value[RpcPeer.PROPERTY_JSON_DISABLE_SERIALIZATION] && this.getDefaultTransportSafeArgumentTypes().has(value.constructor?.name));
|
413
|
+
}
|
414
|
+
|
415
|
+
isTransportSafe(value: any) {
|
416
|
+
return !value || (!value[RpcPeer.PROPERTY_JSON_DISABLE_SERIALIZATION] && this.transportSafeArgumentTypes.has(value.constructor?.name));
|
305
417
|
}
|
306
418
|
|
307
419
|
createPendingResult(cb: (id: string, reject: (e: Error) => void) => void): Promise<any> {
|
308
420
|
if (Object.isFrozen(this.pendingResults))
|
309
|
-
return Promise.reject(new RPCResultError(this, 'RpcPeer has been killed'));
|
421
|
+
return Promise.reject(new RPCResultError(this, 'RpcPeer has been killed (createPendingResult)'));
|
310
422
|
|
311
423
|
const promise = new Promise((resolve, reject) => {
|
312
424
|
const id = (this.idCounter++).toString();
|
@@ -322,6 +434,8 @@ export class RpcPeer {
|
|
322
434
|
}
|
323
435
|
|
324
436
|
kill(message?: string) {
|
437
|
+
if (Object.isFrozen(this.pendingResults))
|
438
|
+
return;
|
325
439
|
const error = new RPCResultError(this, message || 'peer was killed');
|
326
440
|
this.killedDeferred.reject(error);
|
327
441
|
for (const result of Object.values(this.pendingResults)) {
|
@@ -330,7 +444,7 @@ export class RpcPeer {
|
|
330
444
|
this.pendingResults = Object.freeze({});
|
331
445
|
this.params = Object.freeze({});
|
332
446
|
this.remoteWeakProxies = Object.freeze({});
|
333
|
-
this.localProxyMap
|
447
|
+
this.localProxyMap.clear()
|
334
448
|
this.localProxied.clear();
|
335
449
|
}
|
336
450
|
|
@@ -381,10 +495,16 @@ export class RpcPeer {
|
|
381
495
|
return value;
|
382
496
|
}
|
383
497
|
|
384
|
-
|
385
|
-
|
386
|
-
|
498
|
+
/**
|
499
|
+
* @deprecated
|
500
|
+
* @param result
|
501
|
+
* @param e
|
502
|
+
*/
|
503
|
+
createErrorResult(result: RpcResult, e: ErrorType) {
|
504
|
+
result.result = this.serializeError(e);
|
505
|
+
result.throw = true;
|
387
506
|
result.message = (e as Error).message || 'no message';
|
507
|
+
result.stack = e.stack || 'no stack';
|
388
508
|
}
|
389
509
|
|
390
510
|
deserialize(value: any, deserializationContext: any): any {
|
@@ -401,6 +521,9 @@ export class RpcPeer {
|
|
401
521
|
}
|
402
522
|
|
403
523
|
const { __remote_proxy_id, __remote_proxy_finalizer_id, __local_proxy_id, __remote_constructor_name, __serialized_value, __remote_proxy_props, __remote_proxy_oneway_methods } = value;
|
524
|
+
if (__remote_constructor_name === RpcPeer.RPC_RESULT_ERROR_NAME)
|
525
|
+
return this.deserializeError(__serialized_value);
|
526
|
+
|
404
527
|
if (__remote_proxy_id) {
|
405
528
|
let proxy = this.remoteWeakProxies[__remote_proxy_id]?.deref();
|
406
529
|
if (!proxy)
|
@@ -416,7 +539,7 @@ export class RpcPeer {
|
|
416
539
|
}
|
417
540
|
|
418
541
|
if (__local_proxy_id) {
|
419
|
-
const ret = this.localProxyMap
|
542
|
+
const ret = this.localProxyMap.get(__local_proxy_id);
|
420
543
|
if (!ret)
|
421
544
|
throw new RPCResultError(this, `invalid local proxy id ${__local_proxy_id}`);
|
422
545
|
return ret;
|
@@ -430,6 +553,28 @@ export class RpcPeer {
|
|
430
553
|
return value;
|
431
554
|
}
|
432
555
|
|
556
|
+
deserializeError(e: SerialiedRpcResultError): RPCResultError {
|
557
|
+
const { name, stack, message } = e;
|
558
|
+
return new RPCResultError(this, message, undefined, { name, stack });
|
559
|
+
}
|
560
|
+
|
561
|
+
serializeError(e: ErrorType): RpcRemoteProxyValue {
|
562
|
+
const __serialized_value: SerialiedRpcResultError = {
|
563
|
+
stack: e.stack || '[no stack]',
|
564
|
+
name: e.name || '[no name]',
|
565
|
+
message: e.message || '[no message]',
|
566
|
+
}
|
567
|
+
return {
|
568
|
+
// probably not safe to use constructor.name
|
569
|
+
__remote_constructor_name: RpcPeer.RPC_RESULT_ERROR_NAME,
|
570
|
+
__remote_proxy_id: undefined,
|
571
|
+
__remote_proxy_finalizer_id: undefined,
|
572
|
+
__remote_proxy_oneway_methods: undefined,
|
573
|
+
__remote_proxy_props: undefined,
|
574
|
+
__serialized_value,
|
575
|
+
};
|
576
|
+
}
|
577
|
+
|
433
578
|
serialize(value: any, serializationContext: any): any {
|
434
579
|
if (value?.[RpcPeer.PROPERTY_JSON_COPY_SERIALIZE_CHILDREN] === true) {
|
435
580
|
const ret: any = {};
|
@@ -439,12 +584,33 @@ export class RpcPeer {
|
|
439
584
|
return ret;
|
440
585
|
}
|
441
586
|
|
442
|
-
if (
|
587
|
+
if (this.isTransportSafe(value)) {
|
443
588
|
return value;
|
444
589
|
}
|
445
590
|
|
446
591
|
let __remote_constructor_name = value.__proxy_constructor || value.constructor?.name?.toString();
|
447
592
|
|
593
|
+
if (value instanceof Error)
|
594
|
+
return this.serializeError(value);
|
595
|
+
|
596
|
+
const serializerMapName = this.constructorSerializerMap.get(value.constructor);
|
597
|
+
if (serializerMapName) {
|
598
|
+
__remote_constructor_name = serializerMapName;
|
599
|
+
const serializer = this.nameDeserializerMap.get(serializerMapName);
|
600
|
+
if (!serializer)
|
601
|
+
throw new Error('serializer not found for ' + serializerMapName);
|
602
|
+
const serialized = serializer.serialize(value, serializationContext);
|
603
|
+
const ret: RpcRemoteProxyValue = {
|
604
|
+
__remote_proxy_id: undefined,
|
605
|
+
__remote_proxy_finalizer_id: undefined,
|
606
|
+
__remote_constructor_name,
|
607
|
+
__remote_proxy_props: RpcPeer.prepareProxyProperties(value),
|
608
|
+
__remote_proxy_oneway_methods: value?.[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS],
|
609
|
+
__serialized_value: serialized,
|
610
|
+
}
|
611
|
+
return ret;
|
612
|
+
}
|
613
|
+
|
448
614
|
let proxiedEntry = this.localProxied.get(value);
|
449
615
|
if (proxiedEntry) {
|
450
616
|
const __remote_proxy_finalizer_id = (this.proxyCounter++).toString();
|
@@ -453,7 +619,7 @@ export class RpcPeer {
|
|
453
619
|
__remote_proxy_id: proxiedEntry.id,
|
454
620
|
__remote_proxy_finalizer_id,
|
455
621
|
__remote_constructor_name,
|
456
|
-
__remote_proxy_props:
|
622
|
+
__remote_proxy_props: RpcPeer.prepareProxyProperties(value),
|
457
623
|
__remote_proxy_oneway_methods: value?.[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS],
|
458
624
|
}
|
459
625
|
return ret;
|
@@ -467,23 +633,7 @@ export class RpcPeer {
|
|
467
633
|
return ret;
|
468
634
|
}
|
469
635
|
|
470
|
-
|
471
|
-
if (serializerMapName) {
|
472
|
-
__remote_constructor_name = serializerMapName;
|
473
|
-
const serializer = this.nameDeserializerMap.get(serializerMapName);
|
474
|
-
if (!serializer)
|
475
|
-
throw new Error('serializer not found for ' + serializerMapName);
|
476
|
-
const serialized = serializer.serialize(value, serializationContext);
|
477
|
-
const ret: RpcRemoteProxyValue = {
|
478
|
-
__remote_proxy_id: undefined,
|
479
|
-
__remote_proxy_finalizer_id: undefined,
|
480
|
-
__remote_constructor_name,
|
481
|
-
__remote_proxy_props: value?.[RpcPeer.PROPERTY_PROXY_PROPERTIES],
|
482
|
-
__remote_proxy_oneway_methods: value?.[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS],
|
483
|
-
__serialized_value: serialized,
|
484
|
-
}
|
485
|
-
return ret;
|
486
|
-
}
|
636
|
+
this.onProxyTypeSerialization.get(__remote_constructor_name)?.(value);
|
487
637
|
|
488
638
|
const __remote_proxy_id = (this.proxyCounter++).toString();
|
489
639
|
proxiedEntry = {
|
@@ -491,13 +641,15 @@ export class RpcPeer {
|
|
491
641
|
finalizerId: __remote_proxy_id,
|
492
642
|
};
|
493
643
|
this.localProxied.set(value, proxiedEntry);
|
494
|
-
this.localProxyMap
|
644
|
+
this.localProxyMap.set(__remote_proxy_id, value);
|
645
|
+
|
646
|
+
const __remote_proxy_props = this.onProxySerialization ? this.onProxySerialization(value, __remote_proxy_id) : RpcPeer.prepareProxyProperties(value);
|
495
647
|
|
496
648
|
const ret: RpcRemoteProxyValue = {
|
497
649
|
__remote_proxy_id,
|
498
650
|
__remote_proxy_finalizer_id: __remote_proxy_id,
|
499
651
|
__remote_constructor_name,
|
500
|
-
__remote_proxy_props
|
652
|
+
__remote_proxy_props,
|
501
653
|
__remote_proxy_oneway_methods: value?.[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS],
|
502
654
|
}
|
503
655
|
|
@@ -540,11 +692,19 @@ export class RpcPeer {
|
|
540
692
|
case 'param': {
|
541
693
|
const rpcParam = message as RpcParam;
|
542
694
|
const serializationContext: any = {};
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
695
|
+
let result: RpcResult;
|
696
|
+
try {
|
697
|
+
result = {
|
698
|
+
type: 'result',
|
699
|
+
id: rpcParam.id,
|
700
|
+
result: this.serialize(this.params[rpcParam.param], serializationContext)
|
701
|
+
};
|
702
|
+
}
|
703
|
+
catch (e) {
|
704
|
+
// console.error('failure', rpcApply.method, e);
|
705
|
+
this.createErrorResult(result, e);
|
706
|
+
}
|
707
|
+
|
548
708
|
this.send(result, undefined, serializationContext);
|
549
709
|
break;
|
550
710
|
}
|
@@ -557,7 +717,7 @@ export class RpcPeer {
|
|
557
717
|
const serializationContext: any = {};
|
558
718
|
|
559
719
|
try {
|
560
|
-
const target = this.localProxyMap
|
720
|
+
const target = this.localProxyMap.get(rpcApply.proxyId);
|
561
721
|
if (!target)
|
562
722
|
throw new Error(`proxy id ${rpcApply.proxyId} not found`);
|
563
723
|
|
@@ -572,6 +732,19 @@ export class RpcPeer {
|
|
572
732
|
if (!method)
|
573
733
|
throw new Error(`target ${target?.constructor?.name} does not have method ${rpcApply.method}`);
|
574
734
|
value = await target[rpcApply.method](...args);
|
735
|
+
|
736
|
+
if (RpcPeer.getIteratorNext(target) === rpcApply.method) {
|
737
|
+
if (value.done) {
|
738
|
+
const errorType: ErrorType = {
|
739
|
+
name: 'StopAsyncIteration',
|
740
|
+
message: undefined,
|
741
|
+
};
|
742
|
+
throw errorType;
|
743
|
+
}
|
744
|
+
else {
|
745
|
+
value = value.value;
|
746
|
+
}
|
747
|
+
}
|
575
748
|
}
|
576
749
|
else {
|
577
750
|
value = await target(...args);
|
@@ -589,12 +762,13 @@ export class RpcPeer {
|
|
589
762
|
break;
|
590
763
|
}
|
591
764
|
case 'result': {
|
765
|
+
// console.log(message)
|
592
766
|
const rpcResult = message as RpcResult;
|
593
767
|
const deferred = this.pendingResults[rpcResult.id];
|
594
768
|
delete this.pendingResults[rpcResult.id];
|
595
769
|
if (!deferred)
|
596
770
|
throw new Error(`unknown result ${rpcResult.id}`);
|
597
|
-
if (rpcResult.message || rpcResult.stack) {
|
771
|
+
if ((rpcResult.message || rpcResult.stack) && !rpcResult.throw) {
|
598
772
|
const e = new RPCResultError(this, rpcResult.message || 'no message', undefined, {
|
599
773
|
name: rpcResult.result,
|
600
774
|
stack: rpcResult.stack,
|
@@ -602,19 +776,23 @@ export class RpcPeer {
|
|
602
776
|
deferred.reject(e);
|
603
777
|
return;
|
604
778
|
}
|
605
|
-
|
779
|
+
const deserialized = this.deserialize(rpcResult.result, deserializationContext);
|
780
|
+
if (rpcResult.throw)
|
781
|
+
deferred.reject(deserialized);
|
782
|
+
else
|
783
|
+
deferred.resolve(deserialized);
|
606
784
|
break;
|
607
785
|
}
|
608
786
|
case 'finalize': {
|
609
787
|
const rpcFinalize = message as RpcFinalize;
|
610
|
-
const local = this.localProxyMap
|
788
|
+
const local = this.localProxyMap.get(rpcFinalize.__local_proxy_id);
|
611
789
|
if (local) {
|
612
790
|
const localProxiedEntry = this.localProxied.get(local);
|
613
791
|
// if a finalizer id is specified, it must match.
|
614
792
|
if (rpcFinalize.__local_proxy_finalizer_id && rpcFinalize.__local_proxy_finalizer_id !== localProxiedEntry?.finalizerId) {
|
615
793
|
break;
|
616
794
|
}
|
617
|
-
|
795
|
+
this.localProxyMap.delete(rpcFinalize.__local_proxy_id);
|
618
796
|
this.localProxied.delete(local);
|
619
797
|
}
|
620
798
|
break;
|
package/src/runtime.ts
CHANGED
@@ -39,6 +39,7 @@ import { PluginComponent } from './services/plugin';
|
|
39
39
|
import { ServiceControl } from './services/service-control';
|
40
40
|
import { UsersService } from './services/users';
|
41
41
|
import { getState, ScryptedStateManager, setState } from './state';
|
42
|
+
import crypto from 'crypto';
|
42
43
|
|
43
44
|
interface DeviceProxyPair {
|
44
45
|
handler: PluginDeviceProxyHandler;
|
@@ -54,6 +55,8 @@ interface HttpPluginData {
|
|
54
55
|
}
|
55
56
|
|
56
57
|
export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
58
|
+
clusterId = crypto.randomBytes(3).toString('hex');
|
59
|
+
clusterSecret = crypto.randomBytes(16).toString('hex');
|
57
60
|
datastore: Level;
|
58
61
|
plugins: { [id: string]: PluginHost } = {};
|
59
62
|
pluginDevices: { [id: string]: PluginDevice } = {};
|
package/src/threading.ts
CHANGED
@@ -0,0 +1,46 @@
|
|
1
|
+
import { RpcPeer } from "../src/rpc";
|
2
|
+
import { sleep } from '../src/sleep';
|
3
|
+
|
4
|
+
const p1 = new RpcPeer('p1', 'p2', message => {
|
5
|
+
// console.log('message p1 p2', message);
|
6
|
+
p2.handleMessage(message);
|
7
|
+
});
|
8
|
+
|
9
|
+
const p2 = new RpcPeer('p2', 'p1', message => {
|
10
|
+
// console.log('message p2 p1', message);
|
11
|
+
p1.handleMessage(message);
|
12
|
+
});
|
13
|
+
|
14
|
+
async function* generator() {
|
15
|
+
try {
|
16
|
+
yield 2;
|
17
|
+
yield 3;
|
18
|
+
}
|
19
|
+
catch (e) {
|
20
|
+
console.log('caught', e)
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
p1.params['thing'] = generator();
|
25
|
+
|
26
|
+
async function test() {
|
27
|
+
const foo = await p2.getParam('thing') as AsyncGenerator<number>;
|
28
|
+
if (true) {
|
29
|
+
for await (const c of foo) {
|
30
|
+
console.log(c);
|
31
|
+
}
|
32
|
+
}
|
33
|
+
else {
|
34
|
+
await sleep(0);
|
35
|
+
console.log(await foo.next());
|
36
|
+
await sleep(0);
|
37
|
+
// await foo.throw(new Error('barf'));
|
38
|
+
await foo.return(44);
|
39
|
+
await sleep(0);
|
40
|
+
console.log(await foo.next());
|
41
|
+
console.log(await foo.next());
|
42
|
+
}
|
43
|
+
|
44
|
+
}
|
45
|
+
|
46
|
+
test();
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import child_process from 'child_process';
|
2
|
+
import path from 'path';
|
3
|
+
import type { Readable, Writable } from "stream";
|
4
|
+
import { createDuplexRpcPeer } from '../src/rpc-serializer';
|
5
|
+
import assert from 'assert';
|
6
|
+
import net from 'net';
|
7
|
+
|
8
|
+
async function main() {
|
9
|
+
const server = net.createServer(client => {
|
10
|
+
console.log('got client');
|
11
|
+
client.on('data', b => console.log('data', b.toString()));
|
12
|
+
});
|
13
|
+
server.listen(6666);
|
14
|
+
|
15
|
+
const cp = child_process.spawn('python3', [path.join(__dirname, '../python/rpc-iterator-test.py')], {
|
16
|
+
stdio: ['pipe', 'inherit', 'inherit', 'pipe', 'pipe'],
|
17
|
+
});
|
18
|
+
|
19
|
+
cp.on('exit', code => console.log('exited', code))
|
20
|
+
|
21
|
+
const rpcPeer = createDuplexRpcPeer('node', 'python', cp.stdio[3] as Readable, cp.stdio[4] as Writable);
|
22
|
+
|
23
|
+
async function* test() {
|
24
|
+
yield 1;
|
25
|
+
yield 2;
|
26
|
+
yield 3;
|
27
|
+
}
|
28
|
+
|
29
|
+
rpcPeer.params['test'] = test();
|
30
|
+
|
31
|
+
// const foo = await rpcPeer.getParam('foo');
|
32
|
+
// assert.equal(foo, 3);
|
33
|
+
|
34
|
+
// const bar = await rpcPeer.getParam('bar');
|
35
|
+
// console.log(bar);
|
36
|
+
|
37
|
+
// const ticker = await rpcPeer.getParam('ticker');
|
38
|
+
// for await (const v of ticker) {
|
39
|
+
// console.log(v);
|
40
|
+
// }
|
41
|
+
// process.exit();
|
42
|
+
}
|
43
|
+
|
44
|
+
main();
|