@scrypted/server 0.0.80 → 0.0.84

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.

Files changed (38) hide show
  1. package/dist/plugin/plugin-console.js +93 -0
  2. package/dist/plugin/plugin-console.js.map +1 -0
  3. package/dist/plugin/plugin-device.js +6 -1
  4. package/dist/plugin/plugin-device.js.map +1 -1
  5. package/dist/plugin/plugin-host-api.js +1 -1
  6. package/dist/plugin/plugin-host-api.js.map +1 -1
  7. package/dist/plugin/plugin-host.js +74 -174
  8. package/dist/plugin/plugin-host.js.map +1 -1
  9. package/dist/plugin/plugin-npm-dependencies.js +2 -2
  10. package/dist/plugin/plugin-npm-dependencies.js.map +1 -1
  11. package/dist/plugin/plugin-remote.js +7 -9
  12. package/dist/plugin/plugin-remote.js.map +1 -1
  13. package/dist/plugin/plugin-repl.js +69 -0
  14. package/dist/plugin/plugin-repl.js.map +1 -0
  15. package/dist/runtime.js +2 -21
  16. package/dist/runtime.js.map +1 -1
  17. package/dist/scrypted-main.js +21 -15
  18. package/dist/scrypted-main.js.map +1 -1
  19. package/dist/services/plugin.js +8 -0
  20. package/dist/services/plugin.js.map +1 -1
  21. package/dist/state.js +1 -1
  22. package/dist/state.js.map +1 -1
  23. package/package.json +2 -2
  24. package/python/plugin-remote.py +45 -20
  25. package/python/rpc.py +1 -1
  26. package/src/plugin/plugin-api.ts +2 -2
  27. package/src/plugin/plugin-console.ts +115 -0
  28. package/src/plugin/plugin-device.ts +7 -1
  29. package/src/plugin/plugin-host-api.ts +1 -1
  30. package/src/plugin/plugin-host.ts +72 -198
  31. package/src/plugin/plugin-npm-dependencies.ts +2 -3
  32. package/src/plugin/plugin-remote.ts +11 -10
  33. package/src/plugin/plugin-repl.ts +74 -0
  34. package/src/runtime.ts +2 -28
  35. package/src/scrypted-main.ts +24 -17
  36. package/src/services/plugin.ts +8 -0
  37. package/src/state.ts +1 -1
  38. package/test/test-cert.json +4 -0
@@ -5,15 +5,11 @@ import { ScryptedRuntime } from '../runtime';
5
5
  import { Plugin } from '../db-types';
6
6
  import io from 'engine.io';
7
7
  import { attachPluginRemote, setupPluginRemote } from './plugin-remote';
8
- import { PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
8
+ import { PluginAPI, PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
9
9
  import { Logger } from '../logger';
10
10
  import { MediaManagerHostImpl, MediaManagerImpl } from './media';
11
11
  import { getState } from '../state';
12
12
  import WebSocket, { EventEmitter } from 'ws';
13
- import { listenZero } from './listen-zero';
14
- import { Server } from 'net';
15
- import repl from 'repl';
16
- import { once } from 'events';
17
13
  import { PassThrough } from 'stream';
18
14
  import { Console } from 'console'
19
15
  import { sleep } from '../sleep';
@@ -27,6 +23,8 @@ import readline from 'readline';
27
23
  import { Readable, Writable } from 'stream';
28
24
  import { ensurePluginVolume } from './plugin-volume';
29
25
  import { installOptionalDependencies } from './plugin-npm-dependencies';
26
+ import { ConsoleServer, createConsoleServer } from './plugin-console';
27
+ import { createREPLServer } from './plugin-repl';
30
28
 
31
29
  export class PluginHost {
32
30
  worker: child_process.ChildProcess;
@@ -49,6 +47,7 @@ export class PluginHost {
49
47
  memoryUsage: NodeJS.MemoryUsage,
50
48
  };
51
49
  killed = false;
50
+ consoleServer: Promise<ConsoleServer>;
52
51
 
53
52
  kill() {
54
53
  this.killed = true;
@@ -73,6 +72,14 @@ export class PluginHost {
73
72
  }
74
73
  }
75
74
  }
75
+
76
+ this.consoleServer?.then(server => {
77
+ server.readServer.close();
78
+ server.writeServer.close();
79
+ for (const s of server.sockets) {
80
+ s.destroy();
81
+ }
82
+ });
76
83
  setTimeout(() => this.peer.kill('plugin killed'), 500);
77
84
  }
78
85
 
@@ -84,6 +91,7 @@ export class PluginHost {
84
91
  async upsertDevice(upsert: Device) {
85
92
  const pi = await this.scrypted.upsertDevice(this.pluginId, upsert, true);
86
93
  await this.remote.setNativeId(pi.nativeId, pi._id, pi.storage || {});
94
+ return pi._id;
87
95
  }
88
96
 
89
97
  constructor(scrypted: ScryptedRuntime, plugin: Plugin, public pluginDebug?: PluginDebug) {
@@ -96,7 +104,7 @@ export class PluginHost {
96
104
  const volume = path.join(process.cwd(), 'volume');
97
105
  const cwd = ensurePluginVolume(this.pluginId);
98
106
 
99
- this.startPluginClusterHost(logger, {
107
+ this.startPluginHost(logger, {
100
108
  NODE_PATH: path.join(cwd, 'node_modules'),
101
109
  SCRYPTED_PLUGIN_VOLUME: cwd,
102
110
  }, plugin.packageJson.scrypted.runtime);
@@ -207,7 +215,7 @@ export class PluginHost {
207
215
  });
208
216
  }
209
217
 
210
- startPluginClusterHost(logger: Logger, env?: any, runtime?: string) {
218
+ startPluginHost(logger: Logger, env?: any, runtime?: string) {
211
219
  let connected = true;
212
220
 
213
221
  if (runtime === 'python') {
@@ -227,9 +235,9 @@ export class PluginHost {
227
235
  path.join(__dirname, '../../python', 'plugin-remote.py'),
228
236
  )
229
237
 
230
- this.worker = child_process.spawn('python', args, {
238
+ this.worker = child_process.spawn('python3', args, {
231
239
  // stdin, stdout, stderr, peer in, peer out
232
- stdio: ['pipe', 'inherit', 'inherit', 'pipe', 'pipe'],
240
+ stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe'],
233
241
  env: Object.assign({}, process.env, env),
234
242
  });
235
243
 
@@ -263,7 +271,7 @@ export class PluginHost {
263
271
  }
264
272
 
265
273
  this.worker = child_process.fork(require.main.filename, ['child'], {
266
- stdio: ['pipe', 'inherit', 'inherit', 'ipc'],
274
+ stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
267
275
  env: Object.assign({}, process.env, env),
268
276
  serialization: 'advanced',
269
277
  execArgv,
@@ -285,8 +293,9 @@ export class PluginHost {
285
293
  this.worker.on('message', message => this.peer.handleMessage(message as any));
286
294
  }
287
295
 
288
- // this.worker.stdout.on('data', data => console.log(data.toString()));
289
- // this.worker.stderr.on('data', data => console.error(data.toString()));
296
+ this.worker.stdout.on('data', data => console.log(data.toString()));
297
+ this.worker.stderr.on('data', data => console.error(data.toString()));
298
+ this.consoleServer = createConsoleServer(this.worker.stdout, this.worker.stderr);
290
299
 
291
300
  this.worker.on('disconnect', () => {
292
301
  connected = false;
@@ -309,171 +318,20 @@ export class PluginHost {
309
318
  }
310
319
  }
311
320
 
312
- async function createConsoleServer(events: EventEmitter): Promise<number[]> {
313
- const outputs = new Map<string, Buffer[]>();
314
- const appendOutput = (data: Buffer, nativeId: ScryptedNativeId) => {
315
- if (!nativeId)
316
- nativeId = undefined;
317
- let buffers = outputs.get(nativeId);
318
- if (!buffers) {
319
- buffers = [];
320
- outputs.set(nativeId, buffers);
321
- }
322
- buffers.push(data);
323
- // when we're over 4000 lines or whatever these buffer are,
324
- // truncate down to 2000.
325
- if (buffers.length > 4000)
326
- outputs.set(nativeId, buffers.slice(buffers.length - 2000))
327
- };
328
- events.on('stdout', appendOutput);
329
- events.on('stderr', appendOutput);
330
-
331
- const server = new Server(async (socket) => {
332
- let [filter] = await once(socket, 'data');
333
- filter = filter.toString().trim();
334
- if (filter === 'undefined')
335
- filter = undefined;
336
-
337
- const buffers = outputs.get(filter);
338
- if (buffers) {
339
- const concat = Buffer.concat(buffers);
340
- outputs.set(filter, [concat]);
341
- socket.write(concat);
342
- }
343
-
344
- const cb = (data: Buffer, nativeId: ScryptedNativeId) => {
345
- if (nativeId !== filter)
346
- return;
347
- socket.write(data);
348
- };
349
- events.on('stdout', cb)
350
- events.on('stderr', cb)
351
-
352
- const cleanup = () => {
353
- events.removeListener('stdout', cb);
354
- events.removeListener('stderr', cb);
355
- };
356
-
357
- socket.on('close', cleanup);
358
- socket.on('error', cleanup);
359
- socket.on('end', cleanup);
360
- });
361
-
362
-
363
- const writeServer = new Server(async (socket) => {
364
- const [data] = await once(socket, 'data');
365
- let filter: string = data.toString();
366
- const newline = filter.indexOf('\n');
367
- if (newline !== -1) {
368
- socket.unshift(Buffer.from(filter.substring(newline + 1)));
369
- }
370
- filter = filter.substring(0, newline);
371
-
372
- if (filter === 'undefined')
373
- filter = undefined;
374
-
375
- const cb = (data: Buffer) => events.emit('stdout', data, filter);
376
-
377
- socket.on('data', cb);
378
-
379
- const cleanup = () => {
380
- events.removeListener('data', cb);
381
- };
382
-
383
- socket.on('close', cleanup);
384
- socket.on('error', cleanup);
385
- socket.on('end', cleanup);
386
- });
387
- const consoleReader = await listenZero(server);
388
- const consoleWriter = await listenZero(writeServer);
389
-
390
- return [consoleReader, consoleWriter];
391
- }
392
-
393
- async function createREPLServer(events: EventEmitter): Promise<number> {
394
- const [[scrypted], [params], [plugin]] = await Promise.all([once(events, 'scrypted'), once(events, 'params'), once(events, 'plugin')]);
395
- const { deviceManager, systemManager } = scrypted;
396
- const server = new Server(async (socket) => {
397
- let [filter] = await once(socket, 'data');
398
- filter = filter.toString().trim();
399
- if (filter === 'undefined')
400
- filter = undefined;
401
-
402
- const chain: string[] = [];
403
- const nativeIds: Map<string, any> = deviceManager.nativeIds;
404
- const reversed = new Map<string, string>();
405
- for (const nativeId of nativeIds.keys()) {
406
- reversed.set(nativeIds.get(nativeId).id, nativeId);
407
- }
408
-
409
- while (filter) {
410
- const { id } = nativeIds.get(filter);
411
- const d = await systemManager.getDeviceById(id);
412
- chain.push(filter);
413
- filter = reversed.get(d.providerId);
414
- }
415
-
416
- chain.reverse();
417
- let device = plugin;
418
- for (const c of chain) {
419
- device = await device.getDevice(c);
420
- }
421
-
422
-
423
- const ctx = Object.assign(params, {
424
- device
425
- });
426
- delete ctx.console;
427
- delete ctx.window;
428
- delete ctx.WebSocket;
429
- delete ctx.pluginHostAPI;
430
-
431
- const replFilter = new Set<string>(['require', 'localStorage'])
432
- const replVariables = Object.keys(ctx).filter(key => !replFilter.has(key));
433
-
434
- const welcome = `JavaScript REPL variables:\n${replVariables.map(key => ' ' + key).join('\n')}\n\n`;
435
- socket.write(welcome);
436
-
437
- const r = repl.start({
438
- terminal: true,
439
- input: socket,
440
- output: socket,
441
- // writer(this: REPLServer, obj: any) {
442
- // const ret = util.inspect(obj, {
443
- // colors: true,
444
- // });
445
- // return ret;//.replaceAll('\n', '\r\n');
446
- // },
447
- preview: false,
448
- });
449
-
450
- Object.assign(r.context, ctx);
451
-
452
- const cleanup = () => {
453
- r.close();
454
- };
455
-
456
- socket.on('close', cleanup);
457
- socket.on('error', cleanup);
458
- socket.on('end', cleanup);
459
- });
460
- return listenZero(server);
461
- }
462
-
463
- export function startPluginClusterWorker() {
321
+ export function startPluginRemote() {
464
322
  const peer = new RpcPeer('unknown', 'host', (message, reject) => process.send(message, undefined, {
465
323
  swallowErrors: !reject,
466
324
  }, e => {
467
325
  if (e)
468
- reject(e);
326
+ reject?.(e);
469
327
  }));
470
328
  peer.transportSafeArgumentTypes.add(Buffer.name);
471
329
  process.on('message', message => peer.handleMessage(message as RpcMessage));
472
330
 
473
- const events = new EventEmitter();
474
-
475
331
  let systemManager: SystemManager;
476
332
  let deviceManager: DeviceManager;
333
+ let api: PluginAPI;
334
+ let pluginId: string;
477
335
 
478
336
  function idForNativeId(nativeId: ScryptedNativeId) {
479
337
  if (!deviceManager)
@@ -527,16 +385,31 @@ export function startPluginClusterWorker() {
527
385
 
528
386
  const getDeviceConsole = (nativeId?: ScryptedNativeId) => {
529
387
  return getConsole(async (stdout, stderr) => {
530
- stdout.on('data', data => events.emit('stdout', data, nativeId));
531
- stderr.on('data', data => events.emit('stderr', data, nativeId));
388
+ const plugins = await api.getComponent('plugins');
389
+ const connect = async () => {
390
+ const port = await plugins.getRemoteServicePort(peer.selfName, 'console-writer');
391
+ const socket = net.connect(port);
392
+ socket.write(nativeId + '\n');
393
+ const writer = (data: Buffer) => {
394
+ socket.write(data);
395
+ };
396
+ stdout.on('data', writer);
397
+ stderr.on('data', writer);
398
+ socket.on('error', () => {
399
+ stdout.removeAllListeners();
400
+ stderr.removeAllListeners();
401
+ stdout.pause();
402
+ stderr.pause();
403
+ setTimeout(connect, 10000);
404
+ });
405
+ };
406
+ connect();
532
407
  }, undefined, undefined);
533
408
  }
534
409
 
535
410
  const getMixinConsole = (mixinId: string, nativeId?: ScryptedNativeId) => {
536
411
  return getConsole(async (stdout, stderr) => {
537
412
  if (!mixinId || !systemManager.getDeviceById(mixinId).mixins.includes(idForNativeId(nativeId))) {
538
- stdout.on('data', data => events.emit('stdout', data, nativeId));
539
- stderr.on('data', data => events.emit('stderr', data, nativeId));
540
413
  return;
541
414
  }
542
415
  const plugins = await systemManager.getComponent('plugins');
@@ -582,28 +455,40 @@ export function startPluginClusterWorker() {
582
455
  global?.gc();
583
456
  }, 10000);
584
457
 
585
- const consolePorts = createConsoleServer(events);
586
- const replPort = createREPLServer(events);
458
+ let replPort: Promise<number>;
587
459
 
588
- const pluginConsole = getDeviceConsole(undefined);
460
+ let _pluginConsole: Console;
461
+ const getPluginConsole = () => {
462
+ if (_pluginConsole)
463
+ return _pluginConsole;
464
+ _pluginConsole = getDeviceConsole(undefined);
465
+ }
589
466
 
590
467
  attachPluginRemote(peer, {
591
- createMediaManager: async (systemManager) => new MediaManagerImpl(systemManager, pluginConsole),
592
- events,
468
+ createMediaManager: async (sm) => {
469
+ systemManager = sm;
470
+ return new MediaManagerImpl(systemManager, getPluginConsole());
471
+ },
472
+ onGetRemote: async (_api, _pluginId) => {
473
+ api = _api;
474
+ pluginId = _pluginId;
475
+ peer.selfName = pluginId;
476
+ },
477
+ onPluginReady: async(scrypted, params, plugin) => {
478
+ replPort = createREPLServer(scrypted, params, plugin);
479
+ },
480
+ getPluginConsole,
593
481
  getDeviceConsole,
594
482
  getMixinConsole,
595
483
  async getServicePort(name) {
596
- if (name === 'repl')
484
+ if (name === 'repl') {
485
+ if (!replPort)
486
+ throw new Error('REPL unavailable: Plugin not loaded.')
597
487
  return replPort;
598
- if (name === 'console')
599
- return (await consolePorts)[0];
600
- if (name === 'console-writer')
601
- return (await consolePorts)[1];
488
+ }
602
489
  throw new Error(`unknown service ${name}`);
603
490
  },
604
- async beforeLoadZip(zip: AdmZip, packageJson: any) {
605
- const pluginId = packageJson.name;
606
- peer.selfName = pluginId;
491
+ async onLoadZip(zip: AdmZip, packageJson: any) {
607
492
  installSourceMapSupport({
608
493
  environment: 'node',
609
494
  retrieveSourceMap(source) {
@@ -620,29 +505,18 @@ export function startPluginClusterWorker() {
620
505
  return null;
621
506
  }
622
507
  });
623
- const cp = await consolePorts;
624
- const writer = cp[1];
625
- const socket = net.connect(writer);
626
- await once(socket, 'connect');
627
- try {
628
- await installOptionalDependencies(pluginConsole, socket, packageJson);
629
- }
630
- finally {
631
- socket.destroy();
632
- }
508
+ await installOptionalDependencies(getPluginConsole(), packageJson);
633
509
  }
634
510
  }).then(scrypted => {
635
511
  systemManager = scrypted.systemManager;
636
512
  deviceManager = scrypted.deviceManager;
637
513
 
638
- events.emit('scrypted', scrypted);
639
-
640
514
  process.on('uncaughtException', e => {
641
- pluginConsole.error('uncaughtException', e);
515
+ getPluginConsole().error('uncaughtException', e);
642
516
  scrypted.log.e('uncaughtException ' + e?.toString());
643
517
  });
644
518
  process.on('unhandledRejection', e => {
645
- pluginConsole.error('unhandledRejection', e);
519
+ getPluginConsole().error('unhandledRejection', e);
646
520
  scrypted.log.e('unhandledRejection ' + e?.toString());
647
521
  });
648
522
  })
@@ -3,9 +3,8 @@ import fs from 'fs';
3
3
  import child_process from 'child_process';
4
4
  import path from 'path';
5
5
  import { once } from 'events';
6
- import { Socket } from "net";
7
6
 
8
- export async function installOptionalDependencies(console: Console, socket: Socket, packageJson: any) {
7
+ export async function installOptionalDependencies(console: Console, packageJson: any) {
9
8
  const pluginVolume = ensurePluginVolume(packageJson.name);
10
9
  const optPj = path.join(pluginVolume, 'package.json');
11
10
 
@@ -40,7 +39,7 @@ export async function installOptionalDependencies(console: Console, socket: Sock
40
39
 
41
40
  const cp = child_process.spawn('npm', ['--prefix', pluginVolume, 'install'], {
42
41
  cwd: pluginVolume,
43
- stdio: ['inherit', socket, socket],
42
+ stdio: 'inherit',
44
43
  });
45
44
 
46
45
  await once(cp, 'exit');
@@ -135,7 +135,6 @@ class DeviceManagerImpl implements DeviceManager {
135
135
  nativeIds = new Map<string, DeviceManagerDevice>();
136
136
 
137
137
  constructor(public systemManager: SystemManagerImpl,
138
- public events?: EventEmitter,
139
138
  public getDeviceConsole?: (nativeId?: ScryptedNativeId) => Console,
140
139
  public getMixinConsole?: (mixinId: string, nativeId?: ScryptedNativeId) => Console) {
141
140
  }
@@ -283,13 +282,15 @@ export interface PluginRemoteAttachOptions {
283
282
  createMediaManager?: (systemManager: SystemManager) => Promise<MediaManager>;
284
283
  getServicePort?: (name: string) => Promise<number>;
285
284
  getDeviceConsole?: (nativeId?: ScryptedNativeId) => Console;
285
+ getPluginConsole?: () => Console;
286
286
  getMixinConsole?: (id: string, nativeId?: ScryptedNativeId) => Console;
287
- events?: EventEmitter;
288
- beforeLoadZip?: (zip: AdmZip, packageJson: any) => Promise<void>;
287
+ onLoadZip?: (zip: AdmZip, packageJson: any) => Promise<void>;
288
+ onGetRemote?: (api: PluginAPI, pluginId: string) => Promise<void>;
289
+ onPluginReady?: (scrypted: ScryptedStatic, params: any, plugin: any) => Promise<void>;
289
290
  }
290
291
 
291
292
  export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOptions): Promise<ScryptedStatic> {
292
- const { createMediaManager, getServicePort, events, getDeviceConsole, getMixinConsole } = options || {};
293
+ const { createMediaManager, getServicePort, getDeviceConsole, getMixinConsole, getPluginConsole } = options || {};
293
294
 
294
295
  peer.addSerializer(Buffer, 'Buffer', new BufferSerializer());
295
296
 
@@ -297,8 +298,10 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
297
298
  const retPromise = new Promise<ScryptedStatic>(resolve => done = resolve);
298
299
 
299
300
  peer.params.getRemote = async (api: PluginAPI, pluginId: string) => {
301
+ await options?.onGetRemote?.(api, pluginId);
302
+
300
303
  const systemManager = new SystemManagerImpl();
301
- const deviceManager = new DeviceManagerImpl(systemManager, events, getDeviceConsole, getMixinConsole);
304
+ const deviceManager = new DeviceManagerImpl(systemManager, getDeviceConsole, getMixinConsole);
302
305
  const endpointManager = new EndpointManagerImpl();
303
306
  const ioSockets: { [id: string]: WebSocketCallbacks } = {};
304
307
  const mediaManager = await api.getMediaManager() || await createMediaManager(systemManager);
@@ -403,10 +406,10 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
403
406
  },
404
407
 
405
408
  async loadZip(packageJson: any, zipData: Buffer, zipOptions?: PluginRemoteLoadZipOptions) {
406
- const pluginConsole = getDeviceConsole?.(undefined);
409
+ const pluginConsole = getPluginConsole?.();
407
410
  pluginConsole?.log('starting plugin', pluginId, packageJson.version);
408
411
  const zip = new AdmZip(zipData);
409
- await options?.beforeLoadZip?.(zip, packageJson);
412
+ await options?.onLoadZip?.(zip, packageJson);
410
413
  const main = zip.getEntry('main.nodejs.js');
411
414
  const script = main.getData().toString();
412
415
  const window: any = {};
@@ -482,12 +485,10 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
482
485
 
483
486
  params.console = pluginConsole;
484
487
 
485
- events?.emit('params', params);
486
-
487
488
  try {
488
489
  peer.evalLocal(script, zipOptions?.filename || '/plugin/main.nodejs.js', params);
489
- events?.emit('plugin', exports.default);
490
490
  pluginConsole?.log('plugin successfully loaded');
491
+ await options?.onPluginReady?.(ret, params, exports.default);
491
492
  return exports.default;
492
493
  }
493
494
  catch (e) {
@@ -0,0 +1,74 @@
1
+ import { EventEmitter } from 'ws';
2
+ import { listenZero } from './listen-zero';
3
+ import { Server } from 'net';
4
+ import { once } from 'events';
5
+ import repl from 'repl';
6
+ import { ScryptedStatic } from '@scrypted/sdk';
7
+
8
+ export async function createREPLServer(scrypted: ScryptedStatic, params: any, plugin: any): Promise<number> {
9
+ const { deviceManager, systemManager } = scrypted;
10
+ const server = new Server(async (socket) => {
11
+ let [filter] = await once(socket, 'data');
12
+ filter = filter.toString().trim();
13
+ if (filter === 'undefined')
14
+ filter = undefined;
15
+
16
+ const chain: string[] = [];
17
+ const nativeIds: Map<string, any> = (deviceManager as any).nativeIds;
18
+ const reversed = new Map<string, string>();
19
+ for (const nativeId of nativeIds.keys()) {
20
+ reversed.set(nativeIds.get(nativeId).id, nativeId);
21
+ }
22
+
23
+ while (filter) {
24
+ const { id } = nativeIds.get(filter);
25
+ const d = await systemManager.getDeviceById(id);
26
+ chain.push(filter);
27
+ filter = reversed.get(d.providerId);
28
+ }
29
+
30
+ chain.reverse();
31
+ let device = plugin;
32
+ for (const c of chain) {
33
+ device = await device.getDevice(c);
34
+ }
35
+
36
+ const ctx = Object.assign(params, {
37
+ device
38
+ });
39
+ delete ctx.console;
40
+ delete ctx.window;
41
+ delete ctx.WebSocket;
42
+ delete ctx.pluginHostAPI;
43
+
44
+ const replFilter = new Set<string>(['require', 'localStorage'])
45
+ const replVariables = Object.keys(ctx).filter(key => !replFilter.has(key));
46
+
47
+ const welcome = `JavaScript REPL variables:\n${replVariables.map(key => ' ' + key).join('\n')}\n\n`;
48
+ socket.write(welcome);
49
+
50
+ const r = repl.start({
51
+ terminal: true,
52
+ input: socket,
53
+ output: socket,
54
+ // writer(this: REPLServer, obj: any) {
55
+ // const ret = util.inspect(obj, {
56
+ // colors: true,
57
+ // });
58
+ // return ret;//.replaceAll('\n', '\r\n');
59
+ // },
60
+ preview: false,
61
+ });
62
+
63
+ Object.assign(r.context, ctx);
64
+
65
+ const cleanup = () => {
66
+ r.close();
67
+ };
68
+
69
+ socket.on('close', cleanup);
70
+ socket.on('error', cleanup);
71
+ socket.on('end', cleanup);
72
+ });
73
+ return listenZero(server);
74
+ }
package/src/runtime.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Level } from './level';
2
2
  import { PluginHost } from './plugin/plugin-host';
3
- import cluster from 'cluster';
4
3
  import { ScryptedNativeId, Device, EngineIOHandler, HttpRequest, HttpRequestHandler, OauthClient, PushHandler, ScryptedDevice, ScryptedInterface, ScryptedInterfaceProperty } from '@scrypted/sdk/types';
5
4
  import { PluginDeviceProxyHandler } from './plugin/plugin-device';
6
5
  import { Plugin, PluginDevice, ScryptedAlert } from './db-types';
@@ -29,10 +28,6 @@ import { Alerts } from './services/alerts';
29
28
  import { Info } from './services/info';
30
29
  import io from 'engine.io';
31
30
  import {spawn as ptySpawn} from 'node-pty';
32
- import child_process from 'child_process';
33
- import fs from 'fs';
34
- import path from 'path';
35
- import { ensurePluginVolume } from './plugin/plugin-volume';
36
31
 
37
32
  interface DeviceProxyPair {
38
33
  handler: PluginDeviceProxyHandler;
@@ -420,6 +415,7 @@ export class ScryptedRuntime {
420
415
  }
421
416
  }
422
417
 
418
+ // should this be async?
423
419
  invalidatePluginDevice(id: string) {
424
420
  const proxyPair = this.devices[id];
425
421
  if (!proxyPair)
@@ -491,29 +487,6 @@ export class ScryptedRuntime {
491
487
  this.invalidatePluginDevice(pluginDevice._id);
492
488
  }
493
489
 
494
- const execArgv = [
495
- '--expose-gc',
496
- ];
497
- if (process.argv[0].endsWith('ts-node'))
498
- execArgv.push('-r', 'ts-node/register');
499
-
500
- if (pluginDebug) {
501
- console.log('plugin inspect port', pluginDebug.inspectPort)
502
- execArgv.push('--inspect');
503
-
504
- cluster.setupMaster({
505
- silent: true,
506
- inspectPort: pluginDebug.inspectPort,
507
- execArgv,
508
- });
509
- }
510
- else {
511
- cluster.setupMaster({
512
- silent: true,
513
- execArgv,
514
- });
515
- }
516
-
517
490
  const pluginHost = new PluginHost(this, plugin, pluginDebug);
518
491
  pluginHost.worker.once('exit', () => {
519
492
  if (pluginHost.killed)
@@ -593,6 +566,7 @@ export class ScryptedRuntime {
593
566
  }
594
567
  device.state = undefined;
595
568
 
569
+ this.invalidatePluginDevice(device._id);
596
570
  delete this.pluginDevices[device._id];
597
571
  await this.datastore.remove(device);
598
572
  if (providerId == null || providerId === device._id) {