@scrypted/server 0.1.14 → 0.2.1

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 (57) hide show
  1. package/dist/event-registry.js +3 -4
  2. package/dist/event-registry.js.map +1 -1
  3. package/dist/http-interfaces.js +11 -0
  4. package/dist/http-interfaces.js.map +1 -1
  5. package/dist/plugin/media.js +78 -67
  6. package/dist/plugin/media.js.map +1 -1
  7. package/dist/plugin/plugin-api.js +1 -1
  8. package/dist/plugin/plugin-api.js.map +1 -1
  9. package/dist/plugin/plugin-device.js +25 -11
  10. package/dist/plugin/plugin-device.js.map +1 -1
  11. package/dist/plugin/plugin-host-api.js.map +1 -1
  12. package/dist/plugin/plugin-host.js +11 -6
  13. package/dist/plugin/plugin-host.js.map +1 -1
  14. package/dist/plugin/plugin-http.js +1 -1
  15. package/dist/plugin/plugin-http.js.map +1 -1
  16. package/dist/plugin/plugin-remote-worker.js +170 -17
  17. package/dist/plugin/plugin-remote-worker.js.map +1 -1
  18. package/dist/plugin/plugin-remote.js +25 -85
  19. package/dist/plugin/plugin-remote.js.map +1 -1
  20. package/dist/plugin/runtime/node-fork-worker.js +11 -3
  21. package/dist/plugin/runtime/node-fork-worker.js.map +1 -1
  22. package/dist/plugin/socket-serializer.js +17 -0
  23. package/dist/plugin/socket-serializer.js.map +1 -0
  24. package/dist/rpc-serializer.js +23 -10
  25. package/dist/rpc-serializer.js.map +1 -1
  26. package/dist/rpc.js +3 -3
  27. package/dist/rpc.js.map +1 -1
  28. package/dist/runtime.js +36 -33
  29. package/dist/runtime.js.map +1 -1
  30. package/dist/scrypted-plugin-main.js +4 -1
  31. package/dist/scrypted-plugin-main.js.map +1 -1
  32. package/dist/scrypted-server-main.js +53 -12
  33. package/dist/scrypted-server-main.js.map +1 -1
  34. package/dist/server-settings.js +5 -1
  35. package/dist/server-settings.js.map +1 -1
  36. package/dist/state.js +2 -1
  37. package/dist/state.js.map +1 -1
  38. package/package.json +5 -12
  39. package/src/event-registry.ts +3 -4
  40. package/src/http-interfaces.ts +13 -0
  41. package/src/plugin/media.ts +94 -75
  42. package/src/plugin/plugin-api.ts +5 -4
  43. package/src/plugin/plugin-device.ts +25 -11
  44. package/src/plugin/plugin-host-api.ts +1 -1
  45. package/src/plugin/plugin-host.ts +6 -5
  46. package/src/plugin/plugin-http.ts +2 -2
  47. package/src/plugin/plugin-remote-worker.ts +211 -23
  48. package/src/plugin/plugin-remote.ts +31 -95
  49. package/src/plugin/runtime/node-fork-worker.ts +11 -3
  50. package/src/plugin/runtime/runtime-worker.ts +1 -1
  51. package/src/plugin/socket-serializer.ts +15 -0
  52. package/src/rpc-serializer.ts +30 -13
  53. package/src/rpc.ts +3 -2
  54. package/src/runtime.ts +37 -38
  55. package/src/scrypted-plugin-main.ts +4 -1
  56. package/src/scrypted-server-main.ts +59 -13
  57. package/src/state.ts +2 -1
@@ -1,20 +1,32 @@
1
1
  import { DeviceManager, ScryptedNativeId, ScryptedStatic, SystemManager } from '@scrypted/types';
2
+ import AdmZip from 'adm-zip';
2
3
  import { Console } from 'console';
4
+ import fs from 'fs';
5
+ import { Volume } from 'memfs';
3
6
  import net from 'net';
7
+ import path from 'path';
4
8
  import { install as installSourceMapSupport } from 'source-map-support';
5
9
  import { PassThrough } from 'stream';
6
10
  import { RpcMessage, RpcPeer } from '../rpc';
7
11
  import { MediaManagerImpl } from './media';
8
- import { PluginAPI } from './plugin-api';
12
+ import { PluginAPI, PluginAPIProxy, PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
9
13
  import { installOptionalDependencies } from './plugin-npm-dependencies';
10
- import { attachPluginRemote, PluginReader } from './plugin-remote';
14
+ import { attachPluginRemote, DeviceManagerImpl, PluginReader, setupPluginRemote } from './plugin-remote';
11
15
  import { createREPLServer } from './plugin-repl';
16
+ import { NodeThreadWorker } from './runtime/node-thread-worker';
17
+ const { link } = require('linkfs');
12
18
 
13
- export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessage, reject?: (e: Error) => void) => void) {
19
+ interface PluginStats {
20
+ type: 'stats',
21
+ cpu: NodeJS.CpuUsage;
22
+ memoryUsage: NodeJS.MemoryUsage;
23
+ }
24
+
25
+ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any) => void) {
14
26
  const peer = new RpcPeer('unknown', 'host', peerSend);
15
27
 
16
28
  let systemManager: SystemManager;
17
- let deviceManager: DeviceManager;
29
+ let deviceManager: DeviceManagerImpl;
18
30
  let api: PluginAPI;
19
31
 
20
32
  const getConsole = (hook: (stdout: PassThrough, stderr: PassThrough) => Promise<void>,
@@ -66,13 +78,18 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
66
78
  return pluginsPromise;
67
79
  }
68
80
 
81
+ const deviceConsoles = new Map<string, Console>();
69
82
  const getDeviceConsole = (nativeId?: ScryptedNativeId) => {
70
83
  // the the plugin console is simply the default console
71
84
  // and gets read from stderr/stdout.
72
85
  if (!nativeId)
73
86
  return console;
74
87
 
75
- return getConsole(async (stdout, stderr) => {
88
+ let ret = deviceConsoles.get(nativeId);
89
+ if (ret)
90
+ return ret;
91
+
92
+ ret = getConsole(async (stdout, stderr) => {
76
93
  const connect = async () => {
77
94
  const plugins = await getPlugins();
78
95
  const port = await plugins.getRemoteServicePort(peer.selfName, 'console-writer');
@@ -93,19 +110,28 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
93
110
  };
94
111
  connect();
95
112
  }, undefined, undefined);
113
+
114
+ deviceConsoles.set(nativeId, ret);
115
+ return ret;
96
116
  }
97
117
 
118
+ const mixinConsoles = new Map<string, Map<string, Console>>();
119
+
98
120
  const getMixinConsole = (mixinId: string, nativeId: ScryptedNativeId) => {
99
- return getConsole(async (stdout, stderr) => {
121
+ let nativeIdConsoles = mixinConsoles.get(nativeId);
122
+ if (!nativeIdConsoles) {
123
+ nativeIdConsoles = new Map();
124
+ mixinConsoles.set(nativeId, nativeIdConsoles);
125
+ }
126
+
127
+ let ret = nativeIdConsoles.get(mixinId);
128
+ if (ret)
129
+ return ret;
130
+
131
+ ret = getConsole(async (stdout, stderr) => {
100
132
  if (!mixinId) {
101
133
  return;
102
134
  }
103
- // todo: fix this. a mixin provider can mixin another device to make it a mixin provider itself.
104
- // so the mixin id in the mixin table will be incorrect.
105
- // there's no easy way to fix this from the remote.
106
- // if (!systemManager.getDeviceById(mixinId).mixins.includes(idForNativeId(nativeId))) {
107
- // return;
108
- // }
109
135
  const reconnect = () => {
110
136
  stdout.removeAllListeners();
111
137
  stderr.removeAllListeners();
@@ -147,17 +173,40 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
147
173
  }
148
174
  tryConnect();
149
175
  }, getDeviceConsole(nativeId), `[${systemManager.getDeviceById(mixinId)?.name}]`);
176
+
177
+ nativeIdConsoles.set(mixinId, ret);
178
+ return ret;
150
179
  }
151
180
 
152
- peer.getParam('updateStats').then((updateStats: (stats: any) => void) => {
153
- let lastCpuUsage: NodeJS.CpuUsage;
181
+ // process.cpuUsage is for the entire process.
182
+ // process.memoryUsage is per thread.
183
+ const allMemoryStats = new Map<NodeThreadWorker, NodeJS.MemoryUsage>();
184
+
185
+ peer.getParam('updateStats').then((updateStats: (stats: PluginStats) => void) => {
154
186
  setInterval(() => {
155
- const cpuUsage = process.cpuUsage(lastCpuUsage);
156
- lastCpuUsage = cpuUsage;
187
+ const cpuUsage = process.cpuUsage();
188
+ allMemoryStats.set(undefined, process.memoryUsage());
189
+
190
+ const memoryUsage: NodeJS.MemoryUsage = {
191
+ rss: 0,
192
+ heapTotal: 0,
193
+ heapUsed: 0,
194
+ external: 0,
195
+ arrayBuffers: 0,
196
+ }
197
+
198
+ for (const mu of allMemoryStats.values()) {
199
+ memoryUsage.rss += mu.rss;
200
+ memoryUsage.heapTotal += mu.heapTotal;
201
+ memoryUsage.heapUsed += mu.heapUsed;
202
+ memoryUsage.external += mu.external;
203
+ memoryUsage.arrayBuffers += mu.arrayBuffers;
204
+ }
205
+
157
206
  updateStats({
158
207
  type: 'stats',
159
208
  cpu: cpuUsage,
160
- memoryUsage: process.memoryUsage(),
209
+ memoryUsage,
161
210
  });
162
211
  }, 10000);
163
212
  });
@@ -183,10 +232,6 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
183
232
  api = _api;
184
233
  peer.selfName = pluginId;
185
234
  },
186
- onPluginReady: async (scrypted, params, plugin) => {
187
- replPort = createREPLServer(scrypted, params, plugin);
188
- postInstallSourceMapSupport(scrypted);
189
- },
190
235
  getPluginConsole,
191
236
  getDeviceConsole,
192
237
  getMixinConsole,
@@ -198,7 +243,59 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
198
243
  }
199
244
  throw new Error(`unknown service ${name}`);
200
245
  },
201
- async onLoadZip(pluginReader: PluginReader, packageJson: any) {
246
+ async onLoadZip(scrypted: ScryptedStatic, params: any, packageJson: any, zipData: Buffer | string, zipOptions?: PluginRemoteLoadZipOptions) {
247
+ let volume: any;
248
+ let pluginReader: PluginReader;
249
+ if (zipOptions?.unzippedPath && fs.existsSync(zipOptions?.unzippedPath)) {
250
+ volume = link(fs, ['', path.join(zipOptions.unzippedPath, 'fs')]);
251
+ pluginReader = name => {
252
+ const filename = path.join(zipOptions.unzippedPath, name);
253
+ if (!fs.existsSync(filename))
254
+ return;
255
+ return fs.readFileSync(filename);
256
+ };
257
+ }
258
+ else {
259
+ const admZip = new AdmZip(zipData);
260
+ volume = new Volume();
261
+ for (const entry of admZip.getEntries()) {
262
+ if (entry.isDirectory)
263
+ continue;
264
+ if (!entry.entryName.startsWith('fs/'))
265
+ continue;
266
+ const name = entry.entryName.substring('fs/'.length);
267
+ volume.mkdirpSync(path.dirname(name));
268
+ const data = entry.getData();
269
+ volume.writeFileSync(name, data);
270
+ }
271
+
272
+ pluginReader = name => {
273
+ const entry = admZip.getEntry(name);
274
+ if (!entry)
275
+ return;
276
+ return entry.getData();
277
+ }
278
+ }
279
+ zipData = undefined;
280
+
281
+ const pluginConsole = getPluginConsole?.();
282
+ params.console = pluginConsole;
283
+ params.require = (name: string) => {
284
+ if (name === 'fakefs' || (name === 'fs' && !packageJson.scrypted.realfs)) {
285
+ return volume;
286
+ }
287
+ if (name === 'realfs') {
288
+ return require('fs');
289
+ }
290
+ const module = require(name);
291
+ return module;
292
+ };
293
+ const window: any = {};
294
+ const exports: any = window;
295
+ window.exports = exports;
296
+ params.window = window;
297
+ params.exports = exports;
298
+
202
299
  const entry = pluginReader('main.nodejs.js.map')
203
300
  const map = entry?.toString();
204
301
 
@@ -234,10 +331,101 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
234
331
  };
235
332
 
236
333
  await installOptionalDependencies(getPluginConsole(), packageJson);
334
+
335
+ const main = pluginReader('main.nodejs.js');
336
+ pluginReader = undefined;
337
+ const script = main.toString();
338
+
339
+
340
+ const forks = new Set<PluginRemote>();
341
+
342
+ scrypted.fork = () => {
343
+ const ntw = new NodeThreadWorker(pluginId, {
344
+ env: process.env,
345
+ pluginDebug: undefined,
346
+ });
347
+
348
+ const result = (async () => {
349
+ const threadPeer = new RpcPeer('main', 'thread', (message, reject) => ntw.send(message, reject));
350
+ threadPeer.params.updateStats = (stats: PluginStats) => {
351
+ allMemoryStats.set(ntw, stats.memoryUsage);
352
+ }
353
+ ntw.setupRpcPeer(threadPeer);
354
+
355
+ class PluginForkAPI extends PluginAPIProxy {
356
+ [RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS] = (api as any)[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS];
357
+
358
+ setStorage(nativeId: string, storage: { [key: string]: any; }): Promise<void> {
359
+ const id = deviceManager.nativeIds.get(nativeId).id;
360
+ (scrypted.pluginRemoteAPI as PluginRemote).setNativeId(nativeId, id, storage);
361
+ for (const r of forks) {
362
+ if (r === remote)
363
+ continue;
364
+ r.setNativeId(nativeId, id, storage);
365
+ }
366
+ return super.setStorage(nativeId, storage);
367
+ }
368
+ }
369
+ const forkApi = new PluginForkAPI(api);
370
+
371
+ const remote = await setupPluginRemote(threadPeer, forkApi, pluginId, () => systemManager.getSystemState());
372
+ forks.add(remote);
373
+ ntw.worker.on('exit', () => {
374
+ forkApi.removeListeners();
375
+ forks.delete(remote);
376
+ allMemoryStats.delete(ntw);
377
+ });
378
+
379
+ for (const [nativeId, dmd] of deviceManager.nativeIds.entries()) {
380
+ await remote.setNativeId(nativeId, dmd.id, dmd.storage);
381
+ }
382
+
383
+ const forkOptions = Object.assign({}, zipOptions);
384
+ forkOptions.fork = true;
385
+ return remote.loadZip(packageJson, zipData, forkOptions)
386
+ })();
387
+
388
+ result.catch(() => ntw.kill());
389
+
390
+ return {
391
+ worker: ntw.worker,
392
+ result,
393
+ }
394
+ }
395
+
396
+ try {
397
+ peer.evalLocal(script, zipOptions?.filename || '/plugin/main.nodejs.js', params);
398
+
399
+ if (zipOptions?.fork) {
400
+ pluginConsole?.log('plugin forked');
401
+ const fork = exports.fork;
402
+ const forked = await fork();
403
+ forked[RpcPeer.PROPERTY_JSON_DISABLE_SERIALIZATION] = true;
404
+ return forked;
405
+ }
406
+
407
+ pluginConsole?.log('plugin loaded');
408
+ let pluginInstance = exports.default;
409
+ // support exporting a plugin class, plugin main function,
410
+ // or a plugin instance
411
+ if (pluginInstance.toString().startsWith('class '))
412
+ pluginInstance = new pluginInstance();
413
+ if (typeof pluginInstance === 'function')
414
+ pluginInstance = await pluginInstance();
415
+
416
+ replPort = createREPLServer(scrypted, params, pluginInstance);
417
+ postInstallSourceMapSupport(scrypted);
418
+
419
+ return pluginInstance;
420
+ }
421
+ catch (e) {
422
+ pluginConsole?.error('plugin failed to start', e);
423
+ throw e;
424
+ }
237
425
  }
238
426
  }).then(scrypted => {
239
427
  systemManager = scrypted.systemManager;
240
- deviceManager = scrypted.deviceManager;
428
+ deviceManager = scrypted.deviceManager as DeviceManagerImpl;
241
429
  });
242
430
 
243
431
  return peer;
@@ -1,16 +1,10 @@
1
- import AdmZip from 'adm-zip';
2
- import { Volume } from 'memfs';
3
- import path from 'path';
4
- import { ScryptedNativeId, DeviceManager, Logger, Device, DeviceManifest, DeviceState, EndpointManager, SystemDeviceState, ScryptedStatic, SystemManager, MediaManager, ScryptedMimeTypes, ScryptedInterface, ScryptedInterfaceProperty, HttpRequest } from '@scrypted/types'
5
- import { PluginAPI, PluginLogger, PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
6
- import { SystemManagerImpl } from './system';
1
+ import { Device, DeviceManager, DeviceManifest, DeviceState, EndpointManager, HttpRequest, Logger, MediaManager, ScryptedInterface, ScryptedInterfaceProperty, ScryptedMimeTypes, ScryptedNativeId, ScryptedStatic, SystemDeviceState, SystemManager } from '@scrypted/types';
7
2
  import { RpcPeer, RPCResultError } from '../rpc';
8
3
  import { BufferSerializer } from './buffer-serializer';
4
+ import { PluginAPI, PluginLogger, PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
9
5
  import { createWebSocketClass, WebSocketConnectCallbacks, WebSocketMethods } from './plugin-remote-websocket';
10
- import fs from 'fs';
11
6
  import { checkProperty } from './plugin-state-check';
12
- import _ from 'lodash';
13
- const { link } = require('linkfs');
7
+ import { SystemManagerImpl } from './system';
14
8
 
15
9
  class DeviceLogger implements Logger {
16
10
  nativeId: ScryptedNativeId;
@@ -113,6 +107,10 @@ class DeviceStateProxyHandler implements ProxyHandler<any> {
113
107
  get?(target: any, p: PropertyKey, receiver: any) {
114
108
  if (p === 'id')
115
109
  return this.id;
110
+ if (p === RpcPeer.PROPERTY_PROXY_PROPERTIES)
111
+ return { id: this.id }
112
+ if (p === 'setState')
113
+ return this.setState;
116
114
  return this.deviceManager.systemManager.state[this.id][p as string]?.value;
117
115
  }
118
116
 
@@ -134,7 +132,7 @@ interface DeviceManagerDevice {
134
132
  storage: { [key: string]: any };
135
133
  }
136
134
 
137
- class DeviceManagerImpl implements DeviceManager {
135
+ export class DeviceManagerImpl implements DeviceManager {
138
136
  api: PluginAPI;
139
137
  nativeIds = new Map<string, DeviceManagerDevice>();
140
138
  deviceStorage = new Map<string, StorageImpl>();
@@ -159,6 +157,11 @@ class DeviceManagerImpl implements DeviceManager {
159
157
  return new Proxy(handler, handler);
160
158
  }
161
159
 
160
+ createDeviceState(id: string, setState: (property: string, value: any) => Promise<void>): DeviceState {
161
+ const handler = new DeviceStateProxyHandler(this, id, setState);
162
+ return new Proxy(handler, handler);
163
+ }
164
+
162
165
  getDeviceStorage(nativeId?: any): StorageImpl {
163
166
  let ret = this.deviceStorage.get(nativeId);
164
167
  if (!ret) {
@@ -303,7 +306,7 @@ export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId:
303
306
  if (!peer.constructorSerializerMap.get(Buffer))
304
307
  peer.addSerializer(Buffer, 'Buffer', new BufferSerializer());
305
308
  const getRemote = await peer.getParam('getRemote');
306
- const remote = await getRemote(api, pluginId);
309
+ const remote = await getRemote(api, pluginId) as PluginRemote;
307
310
 
308
311
  await remote.setSystemState(getSystemState());
309
312
  api.listen((id, eventDetails, eventData) => {
@@ -343,18 +346,17 @@ export interface WebSocketCustomHandler {
343
346
  export type PluginReader = (name: string) => Buffer;
344
347
 
345
348
  export interface PluginRemoteAttachOptions {
346
- createMediaManager?: (systemManager: SystemManager, deviceManager: DeviceManager) => Promise<MediaManager>;
349
+ createMediaManager?: (systemManager: SystemManager, deviceManager: DeviceManagerImpl) => Promise<MediaManager>;
347
350
  getServicePort?: (name: string, ...args: any[]) => Promise<number>;
348
351
  getDeviceConsole?: (nativeId?: ScryptedNativeId) => Console;
349
352
  getPluginConsole?: () => Console;
350
353
  getMixinConsole?: (id: string, nativeId?: ScryptedNativeId) => Console;
351
- onLoadZip?: (pluginReader: PluginReader, packageJson: any) => Promise<void>;
354
+ onLoadZip?: (scrypted: ScryptedStatic, params: any, packageJson: any, zipData: Buffer | string, zipOptions?: PluginRemoteLoadZipOptions) => Promise<any>;
352
355
  onGetRemote?: (api: PluginAPI, pluginId: string) => Promise<void>;
353
- onPluginReady?: (scrypted: ScryptedStatic, params: any, plugin: any) => Promise<void>;
354
356
  }
355
357
 
356
358
  export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOptions): Promise<ScryptedStatic> {
357
- const { createMediaManager, getServicePort, getDeviceConsole, getMixinConsole, getPluginConsole } = options || {};
359
+ const { createMediaManager, getServicePort, getDeviceConsole, getMixinConsole } = options || {};
358
360
 
359
361
  if (!peer.constructorSerializerMap.get(Buffer))
360
362
  peer.addSerializer(Buffer, 'Buffer', new BufferSerializer());
@@ -368,7 +370,11 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
368
370
  const systemManager = new SystemManagerImpl();
369
371
  const deviceManager = new DeviceManagerImpl(systemManager, getDeviceConsole, getMixinConsole);
370
372
  const endpointManager = new EndpointManagerImpl();
371
- const mediaManager = await api.getMediaManager() || await createMediaManager(systemManager, deviceManager);
373
+ const hostMediaManager = await api.getMediaManager();
374
+ if (!hostMediaManager) {
375
+ peer.params['createMediaManager'] = async () => createMediaManager(systemManager, deviceManager);
376
+ }
377
+ const mediaManager = hostMediaManager || await createMediaManager(systemManager, deviceManager);
372
378
  peer.params['mediaManager'] = mediaManager;
373
379
  const ioSockets: { [id: string]: WebSocketConnectCallbacks } = {};
374
380
 
@@ -383,6 +389,8 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
383
389
  endpointManager,
384
390
  mediaManager,
385
391
  log,
392
+ pluginHostAPI: api,
393
+ pluginRemoteAPI: undefined,
386
394
  }
387
395
 
388
396
  delete peer.params.getRemote;
@@ -404,9 +412,8 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
404
412
  'setNativeId',
405
413
  ],
406
414
  getServicePort,
407
- createDeviceState(id: string, setState: (property: string, value: any) => Promise<void>) {
408
- const handler = new DeviceStateProxyHandler(deviceManager, id, setState);
409
- return new Proxy(handler, handler);
415
+ async createDeviceState(id: string, setState: (property: string, value: any) => Promise<void>) {
416
+ return deviceManager.createDeviceState(id, setState);
410
417
  },
411
418
 
412
419
  async ioEvent(id: string, event: string, message?: any) {
@@ -473,50 +480,6 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
473
480
  },
474
481
 
475
482
  async loadZip(packageJson: any, zipData: Buffer | string, zipOptions?: PluginRemoteLoadZipOptions) {
476
- const pluginConsole = getPluginConsole?.();
477
-
478
- let volume: any;
479
- let pluginReader: PluginReader;
480
- if (zipOptions?.unzippedPath && fs.existsSync(zipOptions?.unzippedPath)) {
481
- volume = link(fs, ['', path.join(zipOptions.unzippedPath, 'fs')]);
482
- pluginReader = name => {
483
- const filename = path.join(zipOptions.unzippedPath, name);
484
- if (!fs.existsSync(filename))
485
- return;
486
- return fs.readFileSync(filename);
487
- };
488
- }
489
- else {
490
- const admZip = new AdmZip(zipData);
491
- volume = new Volume();
492
- for (const entry of admZip.getEntries()) {
493
- if (entry.isDirectory)
494
- continue;
495
- if (!entry.entryName.startsWith('fs/'))
496
- continue;
497
- const name = entry.entryName.substring('fs/'.length);
498
- volume.mkdirpSync(path.dirname(name));
499
- const data = entry.getData();
500
- volume.writeFileSync(name, data);
501
- }
502
-
503
- pluginReader = name => {
504
- const entry = admZip.getEntry(name);
505
- if (!entry)
506
- return;
507
- return entry.getData();
508
- }
509
- }
510
- zipData = undefined;
511
-
512
- await options?.onLoadZip?.(pluginReader, packageJson);
513
- const main = pluginReader('main.nodejs.js');
514
- pluginReader = undefined;
515
- const script = main.toString();
516
- const window: any = {};
517
- const exports: any = window;
518
- window.exports = exports;
519
-
520
483
 
521
484
  function websocketConnect(url: string, protocols: any, callbacks: WebSocketConnectCallbacks) {
522
485
  if (url.startsWith('io://') || url.startsWith('ws://')) {
@@ -536,18 +499,6 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
536
499
 
537
500
  const params: any = {
538
501
  __filename: undefined,
539
- exports,
540
- window,
541
- require: (name: string) => {
542
- if (name === 'fakefs' || (name === 'fs' && !packageJson.scrypted.realfs)) {
543
- return volume;
544
- }
545
- if (name === 'realfs') {
546
- return require('fs');
547
- }
548
- const module = require(name);
549
- return module;
550
- },
551
502
  deviceManager,
552
503
  systemManager,
553
504
  mediaManager,
@@ -556,32 +507,17 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
556
507
  localStorage,
557
508
  pluginHostAPI: api,
558
509
  WebSocket: createWebSocketClass(websocketConnect),
510
+ pluginRuntimeAPI: ret,
559
511
  };
560
512
 
561
- params.console = pluginConsole;
513
+ params.pluginRuntimeAPI = ret;
562
514
 
563
- try {
564
- peer.evalLocal(script, zipOptions?.filename || '/plugin/main.nodejs.js', params);
565
- pluginConsole?.log('plugin successfully loaded');
566
-
567
- let pluginInstance = exports.default;
568
- // support exporting a plugin class, plugin main function,
569
- // or a plugin instance
570
- if (pluginInstance.toString().startsWith('class '))
571
- pluginInstance = new pluginInstance();
572
- if (typeof pluginInstance === 'function')
573
- pluginInstance = await pluginInstance();
574
-
575
- await options?.onPluginReady?.(ret, params, pluginInstance);
576
- return pluginInstance;
577
- }
578
- catch (e) {
579
- pluginConsole?.error('plugin failed to start', e);
580
- throw e;
581
- }
515
+ return options.onLoadZip(ret, params, packageJson, zipData, zipOptions);
582
516
  },
583
517
  }
584
518
 
519
+ ret.pluginRemoteAPI = remote;
520
+
585
521
  return remote;
586
522
  }
587
523
 
@@ -4,6 +4,8 @@ import path from 'path';
4
4
  import { RpcMessage, RpcPeer } from "../../rpc";
5
5
  import { ChildProcessWorker } from "./child-process-worker";
6
6
  import { getPluginNodePath } from "../plugin-npm-dependencies";
7
+ import { SidebandSocketSerializer } from "../socket-serializer";
8
+ import net from "net";
7
9
 
8
10
  export class NodeForkWorker extends ChildProcessWorker {
9
11
 
@@ -31,7 +33,12 @@ export class NodeForkWorker extends ChildProcessWorker {
31
33
 
32
34
  setupRpcPeer(peer: RpcPeer): void {
33
35
  this.worker.on('message', (message, sendHandle) => {
34
- if (sendHandle) {
36
+ if ((message as any).type && sendHandle) {
37
+ peer.handleMessage(message as any, {
38
+ sendHandle,
39
+ });
40
+ }
41
+ else if (sendHandle) {
35
42
  this.emit('rpc', message, sendHandle);
36
43
  }
37
44
  else {
@@ -39,13 +46,14 @@ export class NodeForkWorker extends ChildProcessWorker {
39
46
  }
40
47
  });
41
48
  peer.transportSafeArgumentTypes.add(Buffer.name);
49
+ peer.addSerializer(net.Socket, net.Socket.name, new SidebandSocketSerializer());
42
50
  }
43
51
 
44
- send(message: RpcMessage, reject?: (e: Error) => void): void {
52
+ send(message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any): void {
45
53
  try {
46
54
  if (!this.worker)
47
55
  throw new Error('worked has been killed');
48
- this.worker.send(message, undefined, e => {
56
+ this.worker.send(message, serializationContext?.sendHandle, e => {
49
57
  if (e && reject)
50
58
  reject(e);
51
59
  });
@@ -23,7 +23,7 @@ export interface RuntimeWorker {
23
23
  on(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
24
24
  once(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
25
25
 
26
- send(message: RpcMessage, reject?: (e: Error) => void): void;
26
+ send(message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any): void;
27
27
 
28
28
  setupRpcPeer(peer: RpcPeer): void;
29
29
  }
@@ -0,0 +1,15 @@
1
+ import { RpcSerializer } from "../rpc";
2
+
3
+ export class SidebandSocketSerializer implements RpcSerializer {
4
+ serialize(value: any, serializationContext?: any) {
5
+ if (!serializationContext)
6
+ throw new Error('socket serialization context unavailable');
7
+ serializationContext.sendHandle = value;
8
+ }
9
+
10
+ deserialize(serialized: any, serializationContext?: any) {
11
+ if (!serializationContext)
12
+ throw new Error('socket deserialization context unavailable');
13
+ return serializationContext.sendHandle;
14
+ }
15
+ }