@scrypted/server 0.123.31 → 0.123.33

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.
Files changed (67) hide show
  1. package/dist/plugin/device.d.ts +60 -0
  2. package/dist/plugin/device.js +249 -0
  3. package/dist/plugin/device.js.map +1 -0
  4. package/dist/plugin/endpoint.d.ts +45 -0
  5. package/dist/plugin/endpoint.js +97 -0
  6. package/dist/plugin/endpoint.js.map +1 -0
  7. package/dist/plugin/plugin-host.d.ts +1 -1
  8. package/dist/plugin/plugin-host.js +11 -10
  9. package/dist/plugin/plugin-host.js.map +1 -1
  10. package/dist/plugin/plugin-remote-worker.js +18 -16
  11. package/dist/plugin/plugin-remote-worker.js.map +1 -1
  12. package/dist/plugin/plugin-remote.d.ts +2 -51
  13. package/dist/plugin/plugin-remote.js +6 -337
  14. package/dist/plugin/plugin-remote.js.map +1 -1
  15. package/dist/plugin/runtime/child-process-worker.d.ts +1 -1
  16. package/dist/plugin/runtime/child-process-worker.js +2 -2
  17. package/dist/plugin/runtime/child-process-worker.js.map +1 -1
  18. package/dist/plugin/runtime/cluster-fork-worker.d.ts +4 -4
  19. package/dist/plugin/runtime/cluster-fork-worker.js +8 -3
  20. package/dist/plugin/runtime/cluster-fork-worker.js.map +1 -1
  21. package/dist/plugin/runtime/custom-worker.d.ts +1 -1
  22. package/dist/plugin/runtime/custom-worker.js +3 -3
  23. package/dist/plugin/runtime/custom-worker.js.map +1 -1
  24. package/dist/plugin/runtime/node-fork-worker.d.ts +1 -1
  25. package/dist/plugin/runtime/node-fork-worker.js +2 -2
  26. package/dist/plugin/runtime/node-fork-worker.js.map +1 -1
  27. package/dist/plugin/runtime/python-worker.d.ts +1 -1
  28. package/dist/plugin/runtime/python-worker.js +3 -3
  29. package/dist/plugin/runtime/python-worker.js.map +1 -1
  30. package/dist/plugin/runtime/runtime-host.d.ts +1 -1
  31. package/dist/plugin/runtime/runtime-host.js +3 -3
  32. package/dist/plugin/runtime/runtime-host.js.map +1 -1
  33. package/dist/plugin/system.js +0 -1
  34. package/dist/plugin/system.js.map +1 -1
  35. package/dist/runtime.d.ts +4 -4
  36. package/dist/runtime.js +1 -1
  37. package/dist/runtime.js.map +1 -1
  38. package/dist/scrypted-cluster-main.d.ts +14 -3
  39. package/dist/scrypted-cluster-main.js +42 -18
  40. package/dist/scrypted-cluster-main.js.map +1 -1
  41. package/dist/scrypted-server-main.js +1 -0
  42. package/dist/scrypted-server-main.js.map +1 -1
  43. package/dist/services/cluster-fork.d.ts +4 -3
  44. package/dist/services/cluster-fork.js +7 -5
  45. package/dist/services/cluster-fork.js.map +1 -1
  46. package/package.json +2 -2
  47. package/python/cluster_labels.py +2 -2
  48. package/python/cluster_setup.py +1 -1
  49. package/python/plugin_console.py +8 -0
  50. package/python/plugin_remote.py +116 -34
  51. package/python/rpc.py +5 -4
  52. package/src/plugin/device.ts +261 -0
  53. package/src/plugin/endpoint.ts +109 -0
  54. package/src/plugin/plugin-host.ts +25 -21
  55. package/src/plugin/plugin-remote-worker.ts +30 -20
  56. package/src/plugin/plugin-remote.ts +6 -364
  57. package/src/plugin/runtime/child-process-worker.ts +3 -1
  58. package/src/plugin/runtime/cluster-fork-worker.ts +20 -12
  59. package/src/plugin/runtime/custom-worker.ts +3 -3
  60. package/src/plugin/runtime/node-fork-worker.ts +2 -2
  61. package/src/plugin/runtime/python-worker.ts +3 -3
  62. package/src/plugin/runtime/runtime-host.ts +4 -4
  63. package/src/plugin/system.ts +0 -1
  64. package/src/runtime.ts +4 -4
  65. package/src/scrypted-cluster-main.ts +54 -29
  66. package/src/scrypted-server-main.ts +1 -0
  67. package/src/services/cluster-fork.ts +10 -8
@@ -25,7 +25,7 @@ import { WebSocketConnection } from './plugin-remote-websocket';
25
25
  import { ensurePluginVolume, getScryptedVolume } from './plugin-volume';
26
26
  import { createClusterForkWorker } from './runtime/cluster-fork-worker';
27
27
  import { prepareZipSync } from './runtime/node-worker-common';
28
- import { RuntimeWorker } from './runtime/runtime-worker';
28
+ import type { RuntimeWorker, RuntimeWorkerOptions } from './runtime/runtime-worker';
29
29
 
30
30
  const serverVersion = require('../../package.json').version;
31
31
 
@@ -341,7 +341,15 @@ export class PluginHost {
341
341
  if (!workerHost)
342
342
  throw new UnsupportedRuntimeError(this.packageJson.scrypted.runtime);
343
343
 
344
- let peer: Promise<RpcPeer>
344
+ let peer: Promise<RpcPeer>;
345
+ const runtimeWorkerOptions: RuntimeWorkerOptions = {
346
+ packageJson: this.packageJson,
347
+ env,
348
+ pluginDebug,
349
+ unzippedPath: this.unzippedPath,
350
+ zipFile: this.zipFile,
351
+ zipHash: this.zipHash,
352
+ };
345
353
  if (!needsClusterForkWorker(this.packageJson.scrypted)) {
346
354
  this.peer = new RpcPeer('host', this.pluginId, (message, reject, serializationContext) => {
347
355
  if (connected) {
@@ -354,14 +362,7 @@ export class PluginHost {
354
362
 
355
363
  peer = Promise.resolve(this.peer);
356
364
 
357
- this.worker = workerHost(this.scrypted.mainFilename, this.pluginId, {
358
- packageJson: this.packageJson,
359
- env,
360
- pluginDebug,
361
- unzippedPath: this.unzippedPath,
362
- zipFile: this.zipFile,
363
- zipHash: this.zipHash,
364
- }, this.scrypted);
365
+ this.worker = workerHost(this.scrypted.mainFilename, runtimeWorkerOptions, this.scrypted);
365
366
 
366
367
  this.worker.setupRpcPeer(this.peer);
367
368
 
@@ -379,25 +380,28 @@ export class PluginHost {
379
380
  });
380
381
 
381
382
  const clusterSetup = setupCluster(this.peer);
382
- const { runtimeWorker, forkPeer, clusterWorkerId } = createClusterForkWorker((async () => {
383
- await clusterSetup.initializeCluster({
384
- clusterId: this.scrypted.clusterId,
385
- clusterSecret: this.scrypted.clusterSecret,
386
- });
387
- return this.scrypted.clusterFork;
388
- })(),
389
- this.zipHash, async () => fs.promises.readFile(this.zipFile),
390
- this.packageJson.scrypted, this.packageJson, clusterSetup.connectRPCObject);
383
+ const { runtimeWorker, forkPeer, clusterWorkerId } = createClusterForkWorker(
384
+ runtimeWorkerOptions,
385
+ this.packageJson.scrypted,
386
+ (async () => {
387
+ await clusterSetup.initializeCluster({
388
+ clusterId: this.scrypted.clusterId,
389
+ clusterSecret: this.scrypted.clusterSecret,
390
+ });
391
+ return this.scrypted.clusterFork;
392
+ })(),
393
+ async () => fs.promises.readFile(this.zipFile),
394
+ clusterSetup.connectRPCObject);
391
395
 
392
396
  forkPeer.then(peer => {
393
397
  const originalPeer = this.peer;
394
398
  originalPeer.killedSafe.finally(() => peer.kill());
395
399
  this.peer = peer;
396
400
  peer.killedSafe.finally(() => originalPeer.kill());
397
- }).catch(() => {});
401
+ }).catch(() => { });
398
402
  clusterWorkerId.then(clusterWorkerId => {
399
403
  console.log('cluster worker id', clusterWorkerId);
400
- }).catch(() => {});
404
+ }).catch(() => { });
401
405
 
402
406
  this.worker = runtimeWorker;
403
407
  peer = forkPeer;
@@ -8,11 +8,12 @@ import { needsClusterForkWorker } from '../cluster/cluster-labels';
8
8
  import { setupCluster } from '../cluster/cluster-setup';
9
9
  import { RpcMessage, RpcPeer } from '../rpc';
10
10
  import { evalLocal } from '../rpc-peer-eval';
11
+ import type { DeviceManagerImpl } from './device';
11
12
  import { MediaManagerImpl } from './media';
12
13
  import { PluginAPI, PluginAPIProxy, PluginRemote, PluginRemoteLoadZipOptions, PluginZipAPI } from './plugin-api';
13
14
  import { pipeWorkerConsole, prepareConsoles } from './plugin-console';
14
15
  import { getPluginNodePath, installOptionalDependencies } from './plugin-npm-dependencies';
15
- import { attachPluginRemote, DeviceManagerImpl, setupPluginRemote } from './plugin-remote';
16
+ import { attachPluginRemote, setupPluginRemote } from './plugin-remote';
16
17
  import { createREPLServer } from './plugin-repl';
17
18
  import { getPluginVolume } from './plugin-volume';
18
19
  import { ChildProcessWorker } from './runtime/child-process-worker';
@@ -20,7 +21,10 @@ import { createClusterForkWorker } from './runtime/cluster-fork-worker';
20
21
  import { NodeThreadWorker } from './runtime/node-thread-worker';
21
22
  import { prepareZip } from './runtime/node-worker-common';
22
23
  import { getBuiltinRuntimeHosts } from './runtime/runtime-host';
23
- import { RuntimeWorker } from './runtime/runtime-worker';
24
+ import { RuntimeWorker, RuntimeWorkerOptions } from './runtime/runtime-worker';
25
+ import type { ClusterForkService } from '../services/cluster-fork';
26
+ import type { PluginComponent } from '../services/plugin';
27
+ import { ClusterManagerImpl } from '../scrypted-cluster-main';
24
28
 
25
29
  const serverVersion = require('../../package.json').version;
26
30
 
@@ -44,10 +48,9 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
44
48
  let deviceManager: DeviceManagerImpl;
45
49
  let api: PluginAPI;
46
50
 
47
- let pluginsPromise: Promise<any>;
51
+ let pluginsPromise: Promise<PluginComponent>;
48
52
  function getPlugins() {
49
- if (!pluginsPromise)
50
- pluginsPromise = api.getComponent('plugins');
53
+ pluginsPromise ||= api.getComponent('plugins');
51
54
  return pluginsPromise;
52
55
  }
53
56
 
@@ -113,6 +116,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
113
116
  await initializeCluster(zipOptions);
114
117
 
115
118
  scrypted.connectRPCObject = connectRPCObject;
119
+ scrypted.clusterManager = new ClusterManagerImpl(api);
116
120
 
117
121
  if (worker_threads.isMainThread) {
118
122
  const fsDir = path.join(unzippedPath, 'fs')
@@ -212,10 +216,23 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
212
216
  let nativeWorker: child_process.ChildProcess | worker_threads.Worker;
213
217
  let clusterWorkerId: Promise<string>;
214
218
 
219
+ const runtimeWorkerOptions: RuntimeWorkerOptions = {
220
+ packageJson,
221
+ env: undefined,
222
+ pluginDebug: undefined,
223
+ zipFile,
224
+ unzippedPath,
225
+ zipHash,
226
+ };
227
+
215
228
  // if running in a cluster, fork to a matching cluster worker only if necessary.
216
229
  if (needsClusterForkWorker(options)) {
217
230
  ({ runtimeWorker, forkPeer, clusterWorkerId } = createClusterForkWorker(
218
- api.getComponent('cluster-fork'), zipHash, () => zipAPI.getZip(), options, packageJson, scrypted.connectRPCObject)
231
+ runtimeWorkerOptions,
232
+ options,
233
+ api.getComponent('cluster-fork'),
234
+ () => zipAPI.getZip(),
235
+ scrypted.connectRPCObject)
219
236
  );
220
237
  }
221
238
  else {
@@ -224,14 +241,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
224
241
  const runtime = builtins.get(options.runtime);
225
242
  if (!runtime)
226
243
  throw new Error('unknown runtime ' + options.runtime);
227
- runtimeWorker = runtime(mainFilename, pluginId, {
228
- packageJson,
229
- env: undefined,
230
- pluginDebug: undefined,
231
- zipFile,
232
- unzippedPath,
233
- zipHash,
234
- }, undefined);
244
+ runtimeWorker = runtime(mainFilename, runtimeWorkerOptions, undefined);
235
245
 
236
246
  if (runtimeWorker instanceof ChildProcessWorker) {
237
247
  nativeWorker = runtimeWorker.childProcess;
@@ -270,17 +280,17 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
270
280
  }
271
281
  }
272
282
 
273
- // thread workers inherit main console. pipe anything else.
274
- if (!(runtimeWorker instanceof NodeThreadWorker)) {
275
- const console = options?.id ? getMixinConsole(options.id, options.nativeId) : undefined;
276
- pipeWorkerConsole(nativeWorker, console);
277
- }
278
-
279
283
  const localPeer = new RpcPeer('main', 'thread', (message, reject, serializationContext) => runtimeWorker.send(message, reject, serializationContext));
280
284
  runtimeWorker.setupRpcPeer(localPeer);
281
285
  forkPeer = Promise.resolve(localPeer);
282
286
  }
283
287
 
288
+ // thread workers inherit main console. pipe anything else.
289
+ if (!(runtimeWorker instanceof NodeThreadWorker)) {
290
+ const console = options?.id ? getMixinConsole(options.id, options.nativeId) : undefined;
291
+ pipeWorkerConsole(runtimeWorker, console);
292
+ }
293
+
284
294
  const result = (async () => {
285
295
  const threadPeer = await forkPeer;
286
296
 
@@ -1,372 +1,13 @@
1
- import { Device, DeviceManager, DeviceManifest, DeviceState, EndpointAccessControlAllowOrigin, EndpointManager, EventDetails, Logger, MediaManager, ScryptedInterface, ScryptedInterfaceProperty, ScryptedMimeTypes, ScryptedNativeId, ScryptedStatic, SystemDeviceState, SystemManager, WritableDeviceState } from '@scrypted/types';
1
+ import { EventDetails, MediaManager, ScryptedInterface, ScryptedInterfaceProperty, ScryptedNativeId, ScryptedStatic, SystemDeviceState, SystemManager } from '@scrypted/types';
2
2
  import { RpcPeer, RPCResultError } from '../rpc';
3
- import { AccessControls } from './acl';
4
3
  import { BufferSerializer } from '../rpc-buffer-serializer';
5
- import { PluginAPI, PluginHostInfo, PluginLogger, PluginRemote, PluginRemoteLoadZipOptions, PluginZipAPI } from './plugin-api';
4
+ import { AccessControls } from './acl';
5
+ import { DeviceManagerImpl, StorageImpl } from './device';
6
+ import { EndpointManagerImpl } from './endpoint';
7
+ import { PluginAPI, PluginHostInfo, PluginRemote, PluginRemoteLoadZipOptions, PluginZipAPI } from './plugin-api';
6
8
  import { createWebSocketClass, WebSocketConnectCallbacks, WebSocketConnection, WebSocketMethods, WebSocketSerializer } from './plugin-remote-websocket';
7
- import { checkProperty } from './plugin-state-check';
8
9
  import { SystemManagerImpl } from './system';
9
10
 
10
- class DeviceLogger implements Logger {
11
- nativeId: ScryptedNativeId;
12
- api: PluginAPI;
13
- logger: Promise<PluginLogger>;
14
-
15
- constructor(api: PluginAPI, nativeId: ScryptedNativeId, public console: any) {
16
- this.api = api;
17
- this.nativeId = nativeId;
18
- }
19
-
20
- async ensureLogger(): Promise<PluginLogger> {
21
- if (!this.logger)
22
- this.logger = this.api.getLogger(this.nativeId);
23
- return await this.logger;
24
- }
25
-
26
- async log(level: string, message: string) {
27
- (await this.ensureLogger()).log(level, message);
28
- }
29
-
30
- a(msg: string): void {
31
- this.log('a', msg);
32
- }
33
- async clear() {
34
- (await this.ensureLogger()).clear();
35
- }
36
- async clearAlert(msg: string) {
37
- (await this.ensureLogger()).clearAlert(msg);
38
- }
39
- async clearAlerts() {
40
- (await this.ensureLogger()).clearAlerts();
41
- }
42
- d(msg: string): void {
43
- this.log('d', msg);
44
- }
45
- e(msg: string): void {
46
- this.log('e', msg);
47
- }
48
- i(msg: string): void {
49
- this.log('i', msg);
50
- }
51
- v(msg: string): void {
52
- this.log('v', msg);
53
- }
54
- w(msg: string): void {
55
- this.log('w', msg);
56
- }
57
- }
58
-
59
- class EndpointManagerImpl implements EndpointManager {
60
- deviceManager: DeviceManagerImpl;
61
- api: PluginAPI;
62
- pluginId: string;
63
- mediaManager: MediaManager;
64
-
65
- getEndpoint(nativeId?: ScryptedNativeId) {
66
- if (!nativeId)
67
- return this.pluginId;
68
- const id = this.deviceManager.nativeIds.get(nativeId)?.id;
69
- if (!id)
70
- throw new Error('invalid nativeId ' + nativeId);
71
- if (!nativeId)
72
- return this.pluginId;
73
- return id;
74
- }
75
-
76
- async getUrlSafeIp() {
77
- // ipv6 addresses have colons and need to be bracketed for url safety
78
- const ip: string = await this.api.getComponent('SCRYPTED_IP_ADDRESS')
79
- return ip?.includes(':') ? `[${ip}]` : ip;
80
- }
81
-
82
- /**
83
- * @deprecated
84
- */
85
- async getAuthenticatedPath(nativeId?: ScryptedNativeId): Promise<string> {
86
- return this.getPath(nativeId);
87
- }
88
-
89
- /**
90
- * @deprecated
91
- */
92
- async getInsecurePublicLocalEndpoint(nativeId?: ScryptedNativeId): Promise<string> {
93
- return this.getLocalEndpoint(nativeId, {
94
- insecure: true,
95
- public: true,
96
- })
97
- }
98
-
99
- /**
100
- * @deprecated
101
- */
102
- async getPublicCloudEndpoint(nativeId?: ScryptedNativeId): Promise<string> {
103
- return this.getCloudEndpoint(nativeId, {
104
- public: true,
105
- });
106
- }
107
-
108
- /**
109
- * @deprecated
110
- */
111
- async getPublicLocalEndpoint(nativeId?: ScryptedNativeId): Promise<string> {
112
- return this.getLocalEndpoint(nativeId, {
113
- public: true,
114
- })
115
- }
116
-
117
- /**
118
- * @deprecated
119
- */
120
- async getPublicPushEndpoint(nativeId?: ScryptedNativeId): Promise<string> {
121
- const mo = await this.mediaManager.createMediaObject(Buffer.from(this.getEndpoint(nativeId)), ScryptedMimeTypes.PushEndpoint);
122
- return this.mediaManager.convertMediaObjectToUrl(mo, ScryptedMimeTypes.PushEndpoint);
123
- }
124
-
125
- async getPath(nativeId?: string, options?: { public?: boolean; }): Promise<string> {
126
- return `/endpoint/${this.getEndpoint(nativeId)}/${options?.public ? 'public/' : ''}`
127
- }
128
-
129
- async getLocalEndpoint(nativeId?: string, options?: { public?: boolean; insecure?: boolean; }): Promise<string> {
130
- const protocol = options?.insecure ? 'http' : 'https';
131
- const port = await this.api.getComponent(options?.insecure ? 'SCRYPTED_INSECURE_PORT' : 'SCRYPTED_SECURE_PORT');
132
- const path = await this.getPath(nativeId, options);
133
- const url = `${protocol}://${await this.getUrlSafeIp()}:${port}${path}`;
134
- return url;
135
- }
136
-
137
- async getCloudEndpoint(nativeId?: string, options?: { public?: boolean; }): Promise<string> {
138
- const local = await this.getLocalEndpoint(nativeId, options);
139
- const mo = await this.mediaManager.createMediaObject(Buffer.from(local), ScryptedMimeTypes.LocalUrl);
140
- return this.mediaManager.convertMediaObjectToUrl(mo, ScryptedMimeTypes.LocalUrl);
141
- }
142
-
143
- async getCloudPushEndpoint(nativeId?: string): Promise<string> {
144
- const mo = await this.mediaManager.createMediaObject(Buffer.from(this.getEndpoint(nativeId)), ScryptedMimeTypes.PushEndpoint);
145
- return this.mediaManager.convertMediaObjectToUrl(mo, ScryptedMimeTypes.PushEndpoint);
146
- }
147
-
148
- async setLocalAddresses(addresses: string[]): Promise<void> {
149
- const addressSettings = await this.api.getComponent('addresses');
150
- return addressSettings.setLocalAddresses(addresses);
151
- }
152
-
153
- async getLocalAddresses(): Promise<string[]> {
154
- const addressSettings = await this.api.getComponent('addresses');
155
- return await addressSettings.getLocalAddresses() as string[];
156
- }
157
-
158
- async setAccessControlAllowOrigin(options: EndpointAccessControlAllowOrigin): Promise<void> {
159
- const self = this;
160
- const setAccessControlAllowOrigin = await this.deviceManager.systemManager.getComponent('setAccessControlAllowOrigin') as typeof self.setAccessControlAllowOrigin;
161
- return setAccessControlAllowOrigin(options);
162
- }
163
- }
164
-
165
- class DeviceStateProxyHandler implements ProxyHandler<any> {
166
- constructor(public deviceManager: DeviceManagerImpl, public id: string,
167
- public setState: (property: string, value: any) => Promise<void>) {
168
- }
169
-
170
- get?(target: any, p: PropertyKey, receiver: any) {
171
- if (p === 'id')
172
- return this.id;
173
- if (p === RpcPeer.PROPERTY_PROXY_PROPERTIES)
174
- return { id: this.id }
175
- if (p === 'setState')
176
- return this.setState;
177
- return this.deviceManager.systemManager.state[this.id][p as string]?.value;
178
- }
179
-
180
- set?(target: any, p: PropertyKey, value: any, receiver: any) {
181
- checkProperty(p.toString(), value);
182
- this.deviceManager.systemManager.state[this.id][p as string] = {
183
- value,
184
- };
185
- this.setState(p.toString(), value);
186
- return true;
187
- }
188
- }
189
-
190
- interface DeviceManagerDevice {
191
- id: string;
192
- storage: { [key: string]: any };
193
- }
194
-
195
- export class DeviceManagerImpl implements DeviceManager {
196
- api: PluginAPI;
197
- nativeIds = new Map<string, DeviceManagerDevice>();
198
- deviceStorage = new Map<string, StorageImpl>();
199
- mixinStorage = new Map<string, Map<string, StorageImpl>>();
200
-
201
- constructor(public systemManager: SystemManagerImpl,
202
- public getDeviceConsole: (nativeId?: ScryptedNativeId) => Console,
203
- public getMixinConsole: (mixinId: string, nativeId?: ScryptedNativeId) => Console) {
204
- }
205
-
206
- async requestRestart() {
207
- return this.api.requestRestart();
208
- }
209
-
210
- getDeviceLogger(nativeId?: ScryptedNativeId): Logger {
211
- return new DeviceLogger(this.api, nativeId, this.getDeviceConsole?.(nativeId) || console);
212
- }
213
-
214
- getDeviceState(nativeId?: any): DeviceState {
215
- const handler = new DeviceStateProxyHandler(this, this.nativeIds.get(nativeId).id,
216
- (property, value) => this.api.setState(nativeId, property, value));
217
- return new Proxy(handler, handler);
218
- }
219
-
220
- createDeviceState(id: string, setState: (property: string, value: any) => Promise<void>): WritableDeviceState {
221
- const handler = new DeviceStateProxyHandler(this, id, setState);
222
- return new Proxy(handler, handler);
223
- }
224
-
225
- getDeviceStorage(nativeId?: any): StorageImpl {
226
- let ret = this.deviceStorage.get(nativeId);
227
- if (!ret) {
228
- ret = new StorageImpl(this, nativeId);
229
- this.deviceStorage.set(nativeId, ret);
230
- }
231
- return ret;
232
- }
233
- getMixinStorage(id: string, nativeId?: ScryptedNativeId) {
234
- let ms = this.mixinStorage.get(nativeId);
235
- if (!ms) {
236
- ms = new Map();
237
- this.mixinStorage.set(nativeId, ms);
238
- }
239
- let ret = ms.get(id);
240
- if (!ret) {
241
- ret = new StorageImpl(this, nativeId, `mixin:${id}:`);
242
- ms.set(id, ret);
243
- }
244
- return ret;
245
- }
246
- pruneMixinStorage() {
247
- for (const nativeId of this.nativeIds.keys()) {
248
- const storage = this.nativeIds.get(nativeId).storage;
249
- for (const key of Object.keys(storage)) {
250
- if (!key.startsWith('mixin:'))
251
- continue;
252
- const [, id,] = key.split(':');
253
- // there's no rush to persist this, it will happen automatically on the plugin
254
- // persisting something at some point.
255
- // the key itself is unreachable due to the device no longer existing.
256
- if (id && !this.systemManager.state[id])
257
- delete storage[key];
258
- }
259
- }
260
- }
261
- async onMixinEvent(id: string, nativeId: ScryptedNativeId, eventInterface: string, eventData: any) {
262
- return this.api.onMixinEvent(id, nativeId, eventInterface, eventData);
263
- }
264
- getNativeIds(): string[] {
265
- return Array.from(this.nativeIds.keys());
266
- }
267
- async onDeviceDiscovered(device: Device) {
268
- return this.api.onDeviceDiscovered(device);
269
- }
270
- async onDeviceRemoved(nativeId: string) {
271
- return this.api.onDeviceRemoved(nativeId);
272
- }
273
- async onDeviceEvent(nativeId: any, eventInterface: any, eventData?: any) {
274
- return this.api.onDeviceEvent(nativeId, eventInterface, eventData);
275
- }
276
- async onDevicesChanged(devices: DeviceManifest) {
277
- return this.api.onDevicesChanged(devices);
278
- }
279
- }
280
-
281
- function toStorageString(value: any) {
282
- if (value === null)
283
- return 'null';
284
- if (value === undefined)
285
- return 'undefined';
286
-
287
- return value.toString();
288
- }
289
-
290
- class StorageImpl implements Storage {
291
- api: PluginAPI;
292
- [name: string]: any;
293
-
294
- private static allowedMethods = [
295
- 'length',
296
- 'clear',
297
- 'getItem',
298
- 'setItem',
299
- 'key',
300
- 'removeItem',
301
- ];
302
- private static indexedHandler: ProxyHandler<StorageImpl> = {
303
- get(target, property) {
304
- const keyString = property.toString();
305
- if (StorageImpl.allowedMethods.includes(keyString)) {
306
- const f = target[keyString];
307
- if (keyString === 'length')
308
- return f;
309
- return f.bind(target);
310
- }
311
- return target.getItem(toStorageString(property));
312
- },
313
- set(target, property, value): boolean {
314
- target.setItem(toStorageString(property), value);
315
- return true;
316
- }
317
- };
318
-
319
- constructor(public deviceManager: DeviceManagerImpl, public nativeId: ScryptedNativeId, public prefix?: string) {
320
- this.deviceManager = deviceManager;
321
- this.api = deviceManager.api;
322
- this.nativeId = nativeId;
323
- if (!this.prefix)
324
- this.prefix = '';
325
-
326
- return new Proxy(this, StorageImpl.indexedHandler);
327
- }
328
-
329
- get storage(): { [key: string]: any } {
330
- return this.deviceManager.nativeIds.get(this.nativeId).storage;
331
- }
332
-
333
- get length(): number {
334
- return Object.keys(this.storage).filter(key => key.startsWith(this.prefix)).length;
335
- }
336
-
337
- clear(): void {
338
- if (!this.prefix) {
339
- this.deviceManager.nativeIds.get(this.nativeId).storage = {};
340
- }
341
- else {
342
- const storage = this.storage;
343
- Object.keys(this.storage).filter(key => key.startsWith(this.prefix)).forEach(key => delete storage[key]);
344
- }
345
- this.api.setStorage(this.nativeId, this.storage);
346
- }
347
-
348
- getItem(key: string): string {
349
- return this.storage[this.prefix + key];
350
- }
351
- key(index: number): string {
352
- if (!this.prefix) {
353
- return Object.keys(this.storage)[index];
354
- }
355
- return Object.keys(this.storage).filter(key => key.startsWith(this.prefix))[index].substring(this.prefix.length);
356
- }
357
- removeItem(key: string): void {
358
- delete this.storage[this.prefix + key];
359
- this.api.setStorage(this.nativeId, this.storage);
360
- }
361
- setItem(key: string, value: string): void {
362
- key = toStorageString(key);
363
- value = toStorageString(value);
364
- if (this.storage[this.prefix + key] === value)
365
- return;
366
- this.storage[this.prefix + key] = value;
367
- this.api.setStorage(this.nativeId, this.storage);
368
- }
369
- }
370
11
 
371
12
  export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId: string, hostInfo: PluginHostInfo, getSystemState: () => { [id: string]: { [property: string]: SystemDeviceState } }): Promise<PluginRemote> {
372
13
  try {
@@ -517,6 +158,7 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
517
158
  deviceManager,
518
159
  endpointManager,
519
160
  mediaManager,
161
+ clusterManager: undefined,
520
162
  log,
521
163
  pluginHostAPI: api,
522
164
  pluginRemoteAPI: undefined,
@@ -4,14 +4,16 @@ import { RpcMessage, RpcPeer } from "../../rpc";
4
4
  import { RuntimeWorker, RuntimeWorkerOptions } from "./runtime-worker";
5
5
 
6
6
  export abstract class ChildProcessWorker extends EventEmitter implements RuntimeWorker {
7
+ public pluginId: string;
7
8
  protected worker: child_process.ChildProcess;
8
9
 
9
10
  get childProcess() {
10
11
  return this.worker;
11
12
  }
12
13
 
13
- constructor(public pluginId: string, options: RuntimeWorkerOptions) {
14
+ constructor(options: RuntimeWorkerOptions) {
14
15
  super();
16
+ this.pluginId = options.packageJson.name;
15
17
  }
16
18
 
17
19
  setupWorker() {
@@ -1,18 +1,24 @@
1
+ import type { ClusterFork } from "@scrypted/types";
1
2
  import { EventEmitter, PassThrough } from "stream";
2
3
  import { Deferred } from "../../deferred";
3
4
  import { RpcPeer } from "../../rpc";
4
- import { ClusterForkOptions, PeerLiveness } from "../../scrypted-cluster-main";
5
- import type { ClusterFork } from "../../services/cluster-fork";
5
+ import { PeerLiveness } from "../../scrypted-cluster-main";
6
+ import type { ClusterForkService } from "../../services/cluster-fork";
6
7
  import { writeWorkerGenerator } from "../plugin-console";
7
- import type { RuntimeWorker } from "./runtime-worker";
8
+ import type { RuntimeWorker, RuntimeWorkerOptions } from "./runtime-worker";
8
9
 
9
10
  export function createClusterForkWorker(
10
- forkComponentPromise: Promise<ClusterFork>,
11
- zipHash: string,
11
+ runtimeWorkerOptions: RuntimeWorkerOptions,
12
+ options: Partial<ClusterFork>,
13
+ forkComponentPromise: Promise<ClusterForkService>,
12
14
  getZip: () => Promise<Buffer>,
13
- options: ClusterForkOptions,
14
- packageJson: any,
15
15
  connectRPCObject: (o: any) => Promise<any>) {
16
+
17
+ // these are specific to the cluster worker host
18
+ // and will be set there.
19
+ delete runtimeWorkerOptions.zipFile;
20
+ delete runtimeWorkerOptions.unzippedPath;
21
+
16
22
  const waitKilled = new Deferred<void>();
17
23
  waitKilled.promise.finally(() => events.emit('exit'));
18
24
  const events = new EventEmitter();
@@ -37,20 +43,22 @@ export function createClusterForkWorker(
37
43
  });
38
44
 
39
45
  const peerLiveness = new PeerLiveness(waitKilled.promise);
40
- const clusterForkResultPromise = forkComponentPromise.then(forkComponent => forkComponent.fork(peerLiveness, {
46
+ const clusterForkResultPromise = forkComponentPromise.then(forkComponent => forkComponent.fork(runtimeWorkerOptions, {
41
47
  runtime: options.runtime || 'node',
48
+ id: options.id,
42
49
  ...options,
43
- }, packageJson, zipHash, getZip));
44
- clusterForkResultPromise.catch(() => {});
50
+ }, peerLiveness,
51
+ getZip));
52
+ clusterForkResultPromise.catch(() => { });
45
53
 
46
54
  const clusterWorkerId = clusterForkResultPromise.then(clusterForkResult => clusterForkResult.clusterWorkerId);
47
- clusterWorkerId.catch(() => {});
55
+ clusterWorkerId.catch(() => { });
48
56
 
49
57
  const forkPeer = (async () => {
50
58
  const clusterForkResult = await clusterForkResultPromise;
51
59
  waitKilled.promise.finally(() => {
52
60
  runtimeWorker.pid = undefined;
53
- clusterForkResult.kill().catch(() => {});
61
+ clusterForkResult.kill().catch(() => { });
54
62
  });
55
63
  clusterForkResult.waitKilled().catch(() => { })
56
64
  .finally(() => {
@@ -11,8 +11,8 @@ export class CustomRuntimeWorker extends ChildProcessWorker {
11
11
  serializer: ReturnType<typeof createRpcDuplexSerializer>;
12
12
  fork: boolean;
13
13
 
14
- constructor(pluginId: string, options: RuntimeWorkerOptions, runtime: ScryptedRuntime) {
15
- super(pluginId, options);
14
+ constructor(options: RuntimeWorkerOptions, runtime: ScryptedRuntime) {
15
+ super(options);
16
16
 
17
17
  const pluginDevice = runtime.findPluginDevice(this.pluginId);
18
18
  const scryptedRuntimeArguments: ScryptedRuntimeArguments = pluginDevice.state.scryptedRuntimeArguments?.value;
@@ -27,7 +27,7 @@ export class CustomRuntimeWorker extends ChildProcessWorker {
27
27
  // stdin, stdout, stderr, peer in, peer out
28
28
  stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe', 'pipe'],
29
29
  env: Object.assign({}, process.env, env, {
30
- SCRYYPTED_PLUGIN_ID: pluginId,
30
+ SCRYYPTED_PLUGIN_ID: this.pluginId,
31
31
  SCRYPTED_DEBUG_PORT: pluginDebug?.inspectPort?.toString(),
32
32
  SCRYPTED_UNZIPPED_PATH: options.unzippedPath,
33
33
  SCRYPTED_ZIP_FILE: options.zipFile,