@scrypted/server 0.6.26 → 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/plugin/media.js +15 -17
- 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 +180 -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 +133 -50
- 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/plugin/media.ts +16 -17
- 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 +225 -50
- 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,19 +291,26 @@ 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>();
|
240
|
-
|
312
|
+
onProxyTypeSerialization = new Map<string, (value: any) => void>();
|
313
|
+
onProxySerialization: (value: any, proxyId: string) => any;
|
241
314
|
constructorSerializerMap = new Map<any, string>();
|
242
315
|
transportSafeArgumentTypes = RpcPeer.getDefaultTransportSafeArgumentTypes();
|
243
316
|
killed: Promise<string>;
|
@@ -281,6 +354,37 @@ export class RpcPeer {
|
|
281
354
|
}
|
282
355
|
}
|
283
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';
|
284
388
|
static readonly PROPERTY_PROXY_ID = '__proxy_id';
|
285
389
|
static readonly PROPERTY_PROXY_ONEWAY_METHODS = '__proxy_oneway_methods';
|
286
390
|
static readonly PROPERTY_JSON_DISABLE_SERIALIZATION = '__json_disable_serialization';
|
@@ -304,9 +408,17 @@ export class RpcPeer {
|
|
304
408
|
}).catch(e => e.message || 'Unknown Error');
|
305
409
|
}
|
306
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));
|
417
|
+
}
|
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();
|
@@ -332,7 +444,7 @@ export class RpcPeer {
|
|
332
444
|
this.pendingResults = Object.freeze({});
|
333
445
|
this.params = Object.freeze({});
|
334
446
|
this.remoteWeakProxies = Object.freeze({});
|
335
|
-
this.localProxyMap
|
447
|
+
this.localProxyMap.clear()
|
336
448
|
this.localProxied.clear();
|
337
449
|
}
|
338
450
|
|
@@ -383,10 +495,16 @@ export class RpcPeer {
|
|
383
495
|
return value;
|
384
496
|
}
|
385
497
|
|
386
|
-
|
387
|
-
|
388
|
-
|
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;
|
389
506
|
result.message = (e as Error).message || 'no message';
|
507
|
+
result.stack = e.stack || 'no stack';
|
390
508
|
}
|
391
509
|
|
392
510
|
deserialize(value: any, deserializationContext: any): any {
|
@@ -403,6 +521,9 @@ export class RpcPeer {
|
|
403
521
|
}
|
404
522
|
|
405
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
|
+
|
406
527
|
if (__remote_proxy_id) {
|
407
528
|
let proxy = this.remoteWeakProxies[__remote_proxy_id]?.deref();
|
408
529
|
if (!proxy)
|
@@ -418,7 +539,7 @@ export class RpcPeer {
|
|
418
539
|
}
|
419
540
|
|
420
541
|
if (__local_proxy_id) {
|
421
|
-
const ret = this.localProxyMap
|
542
|
+
const ret = this.localProxyMap.get(__local_proxy_id);
|
422
543
|
if (!ret)
|
423
544
|
throw new RPCResultError(this, `invalid local proxy id ${__local_proxy_id}`);
|
424
545
|
return ret;
|
@@ -432,6 +553,28 @@ export class RpcPeer {
|
|
432
553
|
return value;
|
433
554
|
}
|
434
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
|
+
|
435
578
|
serialize(value: any, serializationContext: any): any {
|
436
579
|
if (value?.[RpcPeer.PROPERTY_JSON_COPY_SERIALIZE_CHILDREN] === true) {
|
437
580
|
const ret: any = {};
|
@@ -441,12 +584,32 @@ export class RpcPeer {
|
|
441
584
|
return ret;
|
442
585
|
}
|
443
586
|
|
444
|
-
if (
|
587
|
+
if (this.isTransportSafe(value)) {
|
445
588
|
return value;
|
446
589
|
}
|
447
590
|
|
448
591
|
let __remote_constructor_name = value.__proxy_constructor || value.constructor?.name?.toString();
|
449
|
-
|
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
|
+
}
|
450
613
|
|
451
614
|
let proxiedEntry = this.localProxied.get(value);
|
452
615
|
if (proxiedEntry) {
|
@@ -456,7 +619,7 @@ export class RpcPeer {
|
|
456
619
|
__remote_proxy_id: proxiedEntry.id,
|
457
620
|
__remote_proxy_finalizer_id,
|
458
621
|
__remote_constructor_name,
|
459
|
-
__remote_proxy_props:
|
622
|
+
__remote_proxy_props: RpcPeer.prepareProxyProperties(value),
|
460
623
|
__remote_proxy_oneway_methods: value?.[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS],
|
461
624
|
}
|
462
625
|
return ret;
|
@@ -470,23 +633,7 @@ export class RpcPeer {
|
|
470
633
|
return ret;
|
471
634
|
}
|
472
635
|
|
473
|
-
|
474
|
-
if (serializerMapName) {
|
475
|
-
__remote_constructor_name = serializerMapName;
|
476
|
-
const serializer = this.nameDeserializerMap.get(serializerMapName);
|
477
|
-
if (!serializer)
|
478
|
-
throw new Error('serializer not found for ' + serializerMapName);
|
479
|
-
const serialized = serializer.serialize(value, serializationContext);
|
480
|
-
const ret: RpcRemoteProxyValue = {
|
481
|
-
__remote_proxy_id: undefined,
|
482
|
-
__remote_proxy_finalizer_id: undefined,
|
483
|
-
__remote_constructor_name,
|
484
|
-
__remote_proxy_props: value?.[RpcPeer.PROPERTY_PROXY_PROPERTIES],
|
485
|
-
__remote_proxy_oneway_methods: value?.[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS],
|
486
|
-
__serialized_value: serialized,
|
487
|
-
}
|
488
|
-
return ret;
|
489
|
-
}
|
636
|
+
this.onProxyTypeSerialization.get(__remote_constructor_name)?.(value);
|
490
637
|
|
491
638
|
const __remote_proxy_id = (this.proxyCounter++).toString();
|
492
639
|
proxiedEntry = {
|
@@ -494,13 +641,15 @@ export class RpcPeer {
|
|
494
641
|
finalizerId: __remote_proxy_id,
|
495
642
|
};
|
496
643
|
this.localProxied.set(value, proxiedEntry);
|
497
|
-
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);
|
498
647
|
|
499
648
|
const ret: RpcRemoteProxyValue = {
|
500
649
|
__remote_proxy_id,
|
501
650
|
__remote_proxy_finalizer_id: __remote_proxy_id,
|
502
651
|
__remote_constructor_name,
|
503
|
-
__remote_proxy_props
|
652
|
+
__remote_proxy_props,
|
504
653
|
__remote_proxy_oneway_methods: value?.[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS],
|
505
654
|
}
|
506
655
|
|
@@ -543,11 +692,19 @@ export class RpcPeer {
|
|
543
692
|
case 'param': {
|
544
693
|
const rpcParam = message as RpcParam;
|
545
694
|
const serializationContext: any = {};
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
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
|
+
|
551
708
|
this.send(result, undefined, serializationContext);
|
552
709
|
break;
|
553
710
|
}
|
@@ -560,7 +717,7 @@ export class RpcPeer {
|
|
560
717
|
const serializationContext: any = {};
|
561
718
|
|
562
719
|
try {
|
563
|
-
const target = this.localProxyMap
|
720
|
+
const target = this.localProxyMap.get(rpcApply.proxyId);
|
564
721
|
if (!target)
|
565
722
|
throw new Error(`proxy id ${rpcApply.proxyId} not found`);
|
566
723
|
|
@@ -575,6 +732,19 @@ export class RpcPeer {
|
|
575
732
|
if (!method)
|
576
733
|
throw new Error(`target ${target?.constructor?.name} does not have method ${rpcApply.method}`);
|
577
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
|
+
}
|
578
748
|
}
|
579
749
|
else {
|
580
750
|
value = await target(...args);
|
@@ -592,12 +762,13 @@ export class RpcPeer {
|
|
592
762
|
break;
|
593
763
|
}
|
594
764
|
case 'result': {
|
765
|
+
// console.log(message)
|
595
766
|
const rpcResult = message as RpcResult;
|
596
767
|
const deferred = this.pendingResults[rpcResult.id];
|
597
768
|
delete this.pendingResults[rpcResult.id];
|
598
769
|
if (!deferred)
|
599
770
|
throw new Error(`unknown result ${rpcResult.id}`);
|
600
|
-
if (rpcResult.message || rpcResult.stack) {
|
771
|
+
if ((rpcResult.message || rpcResult.stack) && !rpcResult.throw) {
|
601
772
|
const e = new RPCResultError(this, rpcResult.message || 'no message', undefined, {
|
602
773
|
name: rpcResult.result,
|
603
774
|
stack: rpcResult.stack,
|
@@ -605,19 +776,23 @@ export class RpcPeer {
|
|
605
776
|
deferred.reject(e);
|
606
777
|
return;
|
607
778
|
}
|
608
|
-
|
779
|
+
const deserialized = this.deserialize(rpcResult.result, deserializationContext);
|
780
|
+
if (rpcResult.throw)
|
781
|
+
deferred.reject(deserialized);
|
782
|
+
else
|
783
|
+
deferred.resolve(deserialized);
|
609
784
|
break;
|
610
785
|
}
|
611
786
|
case 'finalize': {
|
612
787
|
const rpcFinalize = message as RpcFinalize;
|
613
|
-
const local = this.localProxyMap
|
788
|
+
const local = this.localProxyMap.get(rpcFinalize.__local_proxy_id);
|
614
789
|
if (local) {
|
615
790
|
const localProxiedEntry = this.localProxied.get(local);
|
616
791
|
// if a finalizer id is specified, it must match.
|
617
792
|
if (rpcFinalize.__local_proxy_finalizer_id && rpcFinalize.__local_proxy_finalizer_id !== localProxiedEntry?.finalizerId) {
|
618
793
|
break;
|
619
794
|
}
|
620
|
-
|
795
|
+
this.localProxyMap.delete(rpcFinalize.__local_proxy_id);
|
621
796
|
this.localProxied.delete(local);
|
622
797
|
}
|
623
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();
|