@scrypted/server 0.0.116 → 0.0.121

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 (56) hide show
  1. package/.vscode/launch.json +1 -0
  2. package/dist/http-interfaces.js +11 -4
  3. package/dist/http-interfaces.js.map +1 -1
  4. package/dist/level.js.map +1 -1
  5. package/dist/plugin/media.js +4 -4
  6. package/dist/plugin/media.js.map +1 -1
  7. package/dist/plugin/plugin-console.js.map +1 -1
  8. package/dist/plugin/plugin-device.js +13 -3
  9. package/dist/plugin/plugin-device.js.map +1 -1
  10. package/dist/plugin/plugin-host-api.js +2 -2
  11. package/dist/plugin/plugin-host-api.js.map +1 -1
  12. package/dist/plugin/plugin-host.js +110 -235
  13. package/dist/plugin/plugin-host.js.map +1 -1
  14. package/dist/plugin/plugin-http.js +1 -0
  15. package/dist/plugin/plugin-http.js.map +1 -1
  16. package/dist/plugin/plugin-lazy-remote.js.map +1 -1
  17. package/dist/plugin/plugin-remote-worker.js +252 -0
  18. package/dist/plugin/plugin-remote-worker.js.map +1 -0
  19. package/dist/plugin/plugin-remote.js +38 -14
  20. package/dist/plugin/plugin-remote.js.map +1 -1
  21. package/dist/plugin/plugin-repl.js.map +1 -1
  22. package/dist/plugin/system.js +7 -5
  23. package/dist/plugin/system.js.map +1 -1
  24. package/dist/rpc.js +13 -3
  25. package/dist/rpc.js.map +1 -1
  26. package/dist/runtime.js +18 -7
  27. package/dist/runtime.js.map +1 -1
  28. package/dist/scrypted-main.js +4 -437
  29. package/dist/scrypted-main.js.map +1 -1
  30. package/dist/scrypted-plugin-main.js +8 -0
  31. package/dist/scrypted-plugin-main.js.map +1 -0
  32. package/dist/scrypted-server-main.js +429 -0
  33. package/dist/scrypted-server-main.js.map +1 -0
  34. package/package.json +7 -6
  35. package/python/media.py +1 -12
  36. package/python/plugin-remote.py +22 -7
  37. package/python/rpc.py +5 -4
  38. package/src/http-interfaces.ts +12 -5
  39. package/src/level.ts +0 -2
  40. package/src/plugin/media.ts +4 -4
  41. package/src/plugin/plugin-api.ts +9 -1
  42. package/src/plugin/plugin-console.ts +0 -1
  43. package/src/plugin/plugin-device.ts +10 -3
  44. package/src/plugin/plugin-host-api.ts +2 -2
  45. package/src/plugin/plugin-host.ts +121 -264
  46. package/src/plugin/plugin-http.ts +3 -2
  47. package/src/plugin/plugin-lazy-remote.ts +1 -1
  48. package/src/plugin/plugin-remote-worker.ts +271 -0
  49. package/src/plugin/plugin-remote.ts +45 -16
  50. package/src/plugin/plugin-repl.ts +0 -1
  51. package/src/plugin/system.ts +8 -6
  52. package/src/rpc.ts +13 -2
  53. package/src/runtime.ts +18 -7
  54. package/src/scrypted-main.ts +5 -508
  55. package/src/scrypted-plugin-main.ts +6 -0
  56. package/src/scrypted-server-main.ts +498 -0
@@ -17,7 +17,7 @@ import { PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
17
17
  })();
18
18
  }
19
19
 
20
- async loadZip(packageJson: any, zipData: Buffer, options?: PluginRemoteLoadZipOptions): Promise<any> {
20
+ async loadZip(packageJson: any, zipData: Buffer|string, options?: PluginRemoteLoadZipOptions): Promise<any> {
21
21
  if (!this.remote)
22
22
  await this.remoteReadyPromise;
23
23
  return this.remote.loadZip(packageJson, zipData, options);
@@ -0,0 +1,271 @@
1
+ import { RpcMessage, RpcPeer } from '../rpc';
2
+ import { SystemManager, DeviceManager, ScryptedNativeId } from '@scrypted/sdk/types'
3
+ import { attachPluginRemote, PluginReader } from './plugin-remote';
4
+ import { PluginAPI } from './plugin-api';
5
+ import { MediaManagerImpl } from './media';
6
+ import { PassThrough } from 'stream';
7
+ import { Console } from 'console'
8
+ import { install as installSourceMapSupport } from 'source-map-support';
9
+ import net from 'net'
10
+ import { installOptionalDependencies } from './plugin-npm-dependencies';
11
+ import { createREPLServer } from './plugin-repl';
12
+
13
+ export function startSharedPluginRemote() {
14
+ process.setMaxListeners(100);
15
+ process.on('message', (message: any) => {
16
+ if (message.type === 'start')
17
+ startPluginRemote(message.pluginId)
18
+ });
19
+ }
20
+
21
+ export function startPluginRemote(pluginId: string) {
22
+ let peerSend: (message: RpcMessage, reject?: (e: Error) => void) => void;
23
+ let peerListener: NodeJS.MessageListener;
24
+
25
+ if (process.argv[3] === '@scrypted/shared') {
26
+ peerSend = (message, reject) => process.send({
27
+ pluginId,
28
+ message,
29
+ }, undefined, {
30
+ swallowErrors: !reject,
31
+ }, e => {
32
+ if (e)
33
+ reject?.(e);
34
+ });
35
+ peerListener = (message: any) => {
36
+ if (message.type === 'message' && message.pluginId === pluginId)
37
+ peer.handleMessage(message.message);
38
+ }
39
+ }
40
+ else {
41
+ peerSend = (message, reject) => process.send(message, undefined, {
42
+ swallowErrors: !reject,
43
+ }, e => {
44
+ if (e)
45
+ reject?.(e);
46
+ });
47
+ peerListener = message => peer.handleMessage(message as RpcMessage);
48
+ }
49
+
50
+ const peer = new RpcPeer('unknown', 'host', peerSend);
51
+ peer.transportSafeArgumentTypes.add(Buffer.name);
52
+ process.on('message', peerListener);
53
+ process.on('disconnect', () => {
54
+ console.error('peer host disconnected, exiting.');
55
+ process.exit(1);
56
+ });
57
+
58
+ let systemManager: SystemManager;
59
+ let deviceManager: DeviceManager;
60
+ let api: PluginAPI;
61
+
62
+ const getConsole = (hook: (stdout: PassThrough, stderr: PassThrough) => Promise<void>,
63
+ also?: Console, alsoPrefix?: string) => {
64
+
65
+ const stdout = new PassThrough();
66
+ const stderr = new PassThrough();
67
+
68
+ hook(stdout, stderr);
69
+
70
+ const ret = new Console(stdout, stderr);
71
+
72
+ const methods = [
73
+ 'log', 'warn',
74
+ 'dir', 'time',
75
+ 'timeEnd', 'timeLog',
76
+ 'trace', 'assert',
77
+ 'clear', 'count',
78
+ 'countReset', 'group',
79
+ 'groupEnd', 'table',
80
+ 'debug', 'info',
81
+ 'dirxml', 'error',
82
+ 'groupCollapsed',
83
+ ];
84
+
85
+ const printers = ['log', 'info', 'debug', 'trace', 'warn', 'error'];
86
+ for (const m of methods) {
87
+ const old = (ret as any)[m].bind(ret);
88
+ (ret as any)[m] = (...args: any[]) => {
89
+ // prefer the mixin version for local/remote console dump.
90
+ if (also && alsoPrefix && printers.includes(m)) {
91
+ (also as any)[m](alsoPrefix, ...args);
92
+ }
93
+ else {
94
+ (console as any)[m](...args);
95
+ }
96
+ // call through to old method to ensure it gets written
97
+ // to log buffer.
98
+ old(...args);
99
+ }
100
+ }
101
+
102
+ return ret;
103
+ }
104
+
105
+ let pluginsPromise: Promise<any>;
106
+ function getPlugins() {
107
+ if (!pluginsPromise)
108
+ pluginsPromise = api.getComponent('plugins');
109
+ return pluginsPromise;
110
+ }
111
+
112
+ const getDeviceConsole = (nativeId?: ScryptedNativeId) => {
113
+ // the the plugin console is simply the default console
114
+ // and gets read from stderr/stdout.
115
+ if (!nativeId)
116
+ return console;
117
+
118
+ return getConsole(async (stdout, stderr) => {
119
+ const connect = async () => {
120
+ const plugins = await getPlugins();
121
+ const port = await plugins.getRemoteServicePort(peer.selfName, 'console-writer');
122
+ const socket = net.connect(port);
123
+ socket.write(nativeId + '\n');
124
+ const writer = (data: Buffer) => {
125
+ socket.write(data);
126
+ };
127
+ stdout.on('data', writer);
128
+ stderr.on('data', writer);
129
+ socket.on('error', () => {
130
+ stdout.removeAllListeners();
131
+ stderr.removeAllListeners();
132
+ stdout.pause();
133
+ stderr.pause();
134
+ setTimeout(connect, 10000);
135
+ });
136
+ };
137
+ connect();
138
+ }, undefined, undefined);
139
+ }
140
+
141
+ const getMixinConsole = (mixinId: string, nativeId: ScryptedNativeId) => {
142
+ return getConsole(async (stdout, stderr) => {
143
+ if (!mixinId) {
144
+ return;
145
+ }
146
+ // todo: fix this. a mixin provider can mixin another device to make it a mixin provider itself.
147
+ // so the mixin id in the mixin table will be incorrect.
148
+ // there's no easy way to fix this from the remote.
149
+ // if (!systemManager.getDeviceById(mixinId).mixins.includes(idForNativeId(nativeId))) {
150
+ // return;
151
+ // }
152
+ const reconnect = () => {
153
+ stdout.removeAllListeners();
154
+ stderr.removeAllListeners();
155
+ stdout.pause();
156
+ stderr.pause();
157
+ setTimeout(tryConnect, 10000);
158
+ };
159
+
160
+ const connect = async () => {
161
+ const ds = deviceManager.getDeviceState(nativeId);
162
+ if (!ds) {
163
+ // deleted?
164
+ return;
165
+ }
166
+
167
+ const plugins = await getPlugins();
168
+ const { pluginId, nativeId: mixinNativeId } = await plugins.getDeviceInfo(mixinId);
169
+ const port = await plugins.getRemoteServicePort(pluginId, 'console-writer');
170
+ const socket = net.connect(port);
171
+ socket.write(mixinNativeId + '\n');
172
+ const writer = (data: Buffer) => {
173
+ let str = data.toString().trim();
174
+ str = str.replaceAll('\n', `\n[${ds.name}]: `);
175
+ str = `[${ds.name}]: ` + str + '\n';
176
+ socket.write(str);
177
+ };
178
+ stdout.on('data', writer);
179
+ stderr.on('data', writer);
180
+ socket.on('close', reconnect);
181
+ };
182
+
183
+ const tryConnect = async () => {
184
+ try {
185
+ await connect();
186
+ }
187
+ catch (e) {
188
+ reconnect();
189
+ }
190
+ }
191
+ tryConnect();
192
+ }, getDeviceConsole(nativeId), `[${systemManager.getDeviceById(mixinId)?.name}]`);
193
+ }
194
+
195
+ let lastCpuUsage: NodeJS.CpuUsage;
196
+ setInterval(() => {
197
+ const cpuUsage = process.cpuUsage(lastCpuUsage);
198
+ lastCpuUsage = cpuUsage;
199
+ peer.sendOob({
200
+ type: 'stats',
201
+ cpu: cpuUsage,
202
+ memoryUsage: process.memoryUsage(),
203
+ });
204
+ }, 10000);
205
+
206
+ let replPort: Promise<number>;
207
+
208
+ let _pluginConsole: Console;
209
+ const getPluginConsole = () => {
210
+ if (!_pluginConsole)
211
+ _pluginConsole = getDeviceConsole(undefined);
212
+ return _pluginConsole;
213
+ }
214
+
215
+ attachPluginRemote(peer, {
216
+ createMediaManager: async (sm) => {
217
+ systemManager = sm;
218
+ return new MediaManagerImpl(systemManager, getPluginConsole());
219
+ },
220
+ onGetRemote: async (_api, _pluginId) => {
221
+ api = _api;
222
+ peer.selfName = pluginId;
223
+ },
224
+ onPluginReady: async (scrypted, params, plugin) => {
225
+ replPort = createREPLServer(scrypted, params, plugin);
226
+ },
227
+ getPluginConsole,
228
+ getDeviceConsole,
229
+ getMixinConsole,
230
+ async getServicePort(name, ...args: any[]) {
231
+ if (name === 'repl') {
232
+ if (!replPort)
233
+ throw new Error('REPL unavailable: Plugin not loaded.')
234
+ return replPort;
235
+ }
236
+ throw new Error(`unknown service ${name}`);
237
+ },
238
+ async onLoadZip(pluginReader: PluginReader, packageJson: any) {
239
+ const entry = pluginReader('main.nodejs.js.map')
240
+ const map = entry?.toString();
241
+
242
+ installSourceMapSupport({
243
+ environment: 'node',
244
+ retrieveSourceMap(source) {
245
+ if (source === '/plugin/main.nodejs.js' || source === `/${pluginId}/main.nodejs.js`) {
246
+ if (!map)
247
+ return null;
248
+ return {
249
+ url: '/plugin/main.nodejs.js',
250
+ map,
251
+ }
252
+ }
253
+ return null;
254
+ }
255
+ });
256
+ await installOptionalDependencies(getPluginConsole(), packageJson);
257
+ }
258
+ }).then(scrypted => {
259
+ systemManager = scrypted.systemManager;
260
+ deviceManager = scrypted.deviceManager;
261
+
262
+ process.on('uncaughtException', e => {
263
+ getPluginConsole().error('uncaughtException', e);
264
+ scrypted.log.e('uncaughtException ' + e?.toString());
265
+ });
266
+ process.on('unhandledRejection', e => {
267
+ getPluginConsole().error('unhandledRejection', e);
268
+ scrypted.log.e('unhandledRejection ' + e?.toString());
269
+ });
270
+ })
271
+ }
@@ -7,6 +7,8 @@ import { SystemManagerImpl } from './system';
7
7
  import { RpcPeer, RPCResultError, PROPERTY_PROXY_ONEWAY_METHODS, PROPERTY_JSON_DISABLE_SERIALIZATION } from '../rpc';
8
8
  import { BufferSerializer } from './buffer-serializer';
9
9
  import { createWebSocketClass, WebSocketConnectCallbacks, WebSocketMethods } from './plugin-remote-websocket';
10
+ import fs from 'fs';
11
+ const {link} = require('linkfs');
10
12
 
11
13
  class DeviceLogger implements Logger {
12
14
  nativeId: ScryptedNativeId;
@@ -306,13 +308,15 @@ export interface WebSocketCustomHandler {
306
308
  methods: WebSocketMethods;
307
309
  }
308
310
 
311
+ export type PluginReader = (name: string) => Buffer;
312
+
309
313
  export interface PluginRemoteAttachOptions {
310
314
  createMediaManager?: (systemManager: SystemManager) => Promise<MediaManager>;
311
315
  getServicePort?: (name: string, ...args: any[]) => Promise<number>;
312
316
  getDeviceConsole?: (nativeId?: ScryptedNativeId) => Console;
313
317
  getPluginConsole?: () => Console;
314
318
  getMixinConsole?: (id: string, nativeId?: ScryptedNativeId) => Console;
315
- onLoadZip?: (zip: AdmZip, packageJson: any) => Promise<void>;
319
+ onLoadZip?: (pluginReader: PluginReader, packageJson: any) => Promise<void>;
316
320
  onGetRemote?: (api: PluginAPI, pluginId: string) => Promise<void>;
317
321
  onPluginReady?: (scrypted: ScryptedStatic, params: any, plugin: any) => Promise<void>;
318
322
  }
@@ -434,26 +438,51 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
434
438
  done(ret);
435
439
  },
436
440
 
437
- async loadZip(packageJson: any, zipData: Buffer, zipOptions?: PluginRemoteLoadZipOptions) {
441
+ async loadZip(packageJson: any, zipData: Buffer | string, zipOptions?: PluginRemoteLoadZipOptions) {
438
442
  const pluginConsole = getPluginConsole?.();
439
- const zip = new AdmZip(zipData);
440
- await options?.onLoadZip?.(zip, packageJson);
441
- const main = zip.getEntry('main.nodejs.js');
442
- const script = main.getData().toString();
443
+
444
+ let volume: any;
445
+ let pluginReader: PluginReader;
446
+ if (zipOptions?.unzippedPath && fs.existsSync(zipOptions?.unzippedPath)) {
447
+ volume = link(fs, ['', path.join(zipOptions.unzippedPath, 'fs')]);
448
+ pluginReader = name => {
449
+ const filename = path.join(zipOptions.unzippedPath, name);
450
+ if (!fs.existsSync(filename))
451
+ return;
452
+ return fs.readFileSync(filename);
453
+ };
454
+ }
455
+ else {
456
+ const admZip = new AdmZip(zipData);
457
+ volume = new Volume();
458
+ for (const entry of admZip.getEntries()) {
459
+ if (entry.isDirectory)
460
+ continue;
461
+ if (!entry.entryName.startsWith('fs/'))
462
+ continue;
463
+ const name = entry.entryName.substring('fs/'.length);
464
+ volume.mkdirpSync(path.dirname(name));
465
+ const data = entry.getData();
466
+ volume.writeFileSync(name, data);
467
+ }
468
+
469
+ pluginReader = name => {
470
+ const entry = admZip.getEntry(name);
471
+ if (!entry)
472
+ return;
473
+ return entry.getData();
474
+ }
475
+ }
476
+ zipData = undefined;
477
+
478
+ await options?.onLoadZip?.(pluginReader, packageJson);
479
+ const main = pluginReader('main.nodejs.js');
480
+ pluginReader = undefined;
481
+ const script = main.toString();
443
482
  const window: any = {};
444
483
  const exports: any = window;
445
484
  window.exports = exports;
446
485
 
447
- const volume = new Volume();
448
- for (const entry of zip.getEntries()) {
449
- if (entry.isDirectory)
450
- continue;
451
- if (!entry.entryName.startsWith('fs/'))
452
- continue;
453
- const name = entry.entryName.substr('fs/'.length);
454
- volume.mkdirpSync(path.dirname(name));
455
- volume.writeFileSync(name, entry.getData());
456
- }
457
486
 
458
487
  function websocketConnect(url: string, protocols: any, callbacks: WebSocketConnectCallbacks) {
459
488
  if (url.startsWith('io://') || url.startsWith('ws://')) {
@@ -1,4 +1,3 @@
1
- import { EventEmitter } from 'ws';
2
1
  import { listenZero } from './listen-zero';
3
2
  import { Server } from 'net';
4
3
  import { once } from 'events';
@@ -12,7 +12,7 @@ function newDeviceProxy(id: string, systemManager: SystemManagerImpl) {
12
12
 
13
13
 
14
14
  class DeviceProxyHandler implements PrimitiveProxyHandler<any>, ScryptedDevice {
15
- device: ScryptedDevice;
15
+ device: Promise<ScryptedDevice>;
16
16
  constructor(public id: string, public systemManager: SystemManagerImpl) {
17
17
  }
18
18
 
@@ -41,19 +41,20 @@ class DeviceProxyHandler implements PrimitiveProxyHandler<any>, ScryptedDevice {
41
41
  return new Proxy(() => p, this);
42
42
  }
43
43
 
44
- async ensureDevice() {
44
+ ensureDevice() {
45
45
  if (!this.device)
46
- this.device = await this.systemManager.api.getDeviceById(this.id);
46
+ this.device = this.systemManager.api.getDeviceById(this.id);
47
+ return this.device;
47
48
  }
48
49
 
49
50
  async apply(target: any, thisArg: any, argArray?: any) {
50
51
  const method = target();
51
- await this.ensureDevice();
52
+ const device = await this.ensureDevice();
52
53
  if (false && method === 'refresh') {
53
54
  const name = this.systemManager.state[this.id]?.[ScryptedInterfaceProperty.name].value;
54
55
  this.systemManager.log.i(`requested refresh ${name}`);
55
56
  }
56
- return (this.device as any)[method](...argArray);
57
+ return (device as any)[method](...argArray);
57
58
  }
58
59
 
59
60
  listen(event: string | EventListenerOptions, callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any) => void): EventListenerRegister {
@@ -84,7 +85,8 @@ class EventListenerRegisterImpl implements EventListenerRegister {
84
85
  async removeListener(): Promise<void> {
85
86
  try {
86
87
  const register = await this.promise;
87
- register.removeListener();
88
+ this.promise = undefined;
89
+ register?.removeListener();
88
90
  }
89
91
  catch (e) {
90
92
  console.error('removeListener', e);
package/src/rpc.ts CHANGED
@@ -12,6 +12,16 @@ function getDefaultTransportSafeArgumentTypes() {
12
12
  return jsonSerializable;
13
13
  }
14
14
 
15
+ export function startPeriodicGarbageCollection() {
16
+ if (!global.gc) {
17
+ console.warn('rpc peer garbage collection not available: global.gc is not exposed.');
18
+ return;
19
+ }
20
+ return setInterval(() => {
21
+ global?.gc();
22
+ }, 10000);
23
+ }
24
+
15
25
  export interface RpcMessage {
16
26
  type: string;
17
27
  }
@@ -135,6 +145,9 @@ class RpcProxy implements PrimitiveProxyHandler<any> {
135
145
  }
136
146
 
137
147
  apply(target: any, thisArg: any, argArray?: any): any {
148
+ if (Object.isFrozen(this.peer.pendingResults))
149
+ return Promise.reject(new RPCResultError(this.peer, 'RpcPeer has been killed'));
150
+
138
151
  // rpc objects can be functions. if the function is a oneway method,
139
152
  // it will have a null in the oneway method list. this is because
140
153
  // undefined is not JSON serializable.
@@ -429,7 +442,6 @@ export class RpcPeer {
429
442
  const weakref = new WeakRef(proxy);
430
443
  this.remoteWeakProxies[proxyId] = weakref;
431
444
  this.finalizers.register(rpc, localProxiedEntry);
432
- global.gc?.();
433
445
  return proxy;
434
446
  }
435
447
 
@@ -509,7 +521,6 @@ export class RpcPeer {
509
521
  const localProxiedEntry = this.localProxied.get(local);
510
522
  // if a finalizer id is specified, it must match.
511
523
  if (rpcFinalize.__local_proxy_finalizer_id && rpcFinalize.__local_proxy_finalizer_id !== localProxiedEntry?.finalizerId) {
512
- console.error(this.selfName, this.peerName, 'finalizer mismatch')
513
524
  break;
514
525
  }
515
526
  delete this.localProxyMap[rpcFinalize.__local_proxy_id];
package/src/runtime.ts CHANGED
@@ -313,7 +313,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
313
313
  }
314
314
  }
315
315
 
316
- async handleEngineIOEndpoint(req: Request, res: ServerResponse, endpointRequest: HttpRequest, pluginData: HttpPluginData) {
316
+ handleEngineIOEndpoint(req: Request, res: ServerResponse, endpointRequest: HttpRequest, pluginData: HttpPluginData) {
317
317
  const { pluginHost, pluginDevice } = pluginData;
318
318
 
319
319
  (req as any).scrypted = {
@@ -326,7 +326,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
326
326
  pluginHost.io.handleRequest(req, res);
327
327
  }
328
328
 
329
- async handleRequestEndpoint(req: Request, res: Response, endpointRequest: HttpRequest, pluginData: HttpPluginData) {
329
+ handleRequestEndpoint(req: Request, res: Response, endpointRequest: HttpRequest, pluginData: HttpPluginData) {
330
330
  const { pluginHost, pluginDevice } = pluginData;
331
331
  const handler = this.getDevice<HttpRequestHandler>(pluginDevice._id);
332
332
  if (handler.interfaces.includes(ScryptedInterface.EngineIOHandler) && req.headers.connection === 'upgrade' && req.headers.upgrade?.toLowerCase() === 'websocket') {
@@ -334,7 +334,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
334
334
  console.log(ws);
335
335
  });
336
336
  }
337
- handler.onRequest(endpointRequest, createResponseInterface(res, pluginHost.zip));
337
+ handler.onRequest(endpointRequest, createResponseInterface(res, pluginHost.unzippedPath));
338
338
  }
339
339
 
340
340
  killPlugin(plugin: Plugin) {
@@ -465,7 +465,8 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
465
465
  if (!device.interfaces.includes(ScryptedInterface.Readme)) {
466
466
  const zipData = Buffer.from(plugin.zip, 'base64');
467
467
  const adm = new AdmZip(zipData);
468
- if (adm.getEntry('README.md')) {
468
+ const entry = adm.getEntry('README.md');
469
+ if (entry) {
469
470
  device.interfaces = device.interfaces.slice();
470
471
  device.interfaces.push(ScryptedInterface.Readme);
471
472
  }
@@ -477,7 +478,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
477
478
  return this.runPlugin(plugin, pluginDebug);
478
479
  }
479
480
 
480
- async runPlugin(plugin: Plugin, pluginDebug?: PluginDebug) {
481
+ runPlugin(plugin: Plugin, pluginDebug?: PluginDebug) {
481
482
  this.killPlugin(plugin);
482
483
 
483
484
  const pluginDevices = this.findPluginDevices(plugin._id);
@@ -504,7 +505,12 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
504
505
  return;
505
506
  }
506
507
 
507
- this.runPlugin(plugin).catch(e => console.error('error restarting plugin', plugin._id, e));
508
+ try {
509
+ this.runPlugin(plugin);
510
+ }
511
+ catch (e) {
512
+ console.error('error restarting plugin', plugin._id, e);
513
+ }
508
514
  }, 60000);
509
515
  })
510
516
  this.plugins[plugin._id] = pluginHost;
@@ -689,7 +695,12 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
689
695
  }
690
696
 
691
697
  for await (const plugin of this.datastore.getAll(Plugin)) {
692
- this.runPlugin(plugin).catch(e => console.error('error starting plugin', plugin._id, e));
698
+ try {
699
+ this.runPlugin(plugin);
700
+ }
701
+ catch (e) {
702
+ console.error('error starting plugin', plugin._id, e);
703
+ }
693
704
  }
694
705
  }
695
706
  }