@scrypted/server 0.123.30 → 0.123.32

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 (43) hide show
  1. package/dist/cluster/cluster-labels.js +1 -0
  2. package/dist/cluster/cluster-labels.js.map +1 -1
  3. package/dist/plugin/device.d.ts +60 -0
  4. package/dist/plugin/device.js +249 -0
  5. package/dist/plugin/device.js.map +1 -0
  6. package/dist/plugin/endpoint.d.ts +45 -0
  7. package/dist/plugin/endpoint.js +97 -0
  8. package/dist/plugin/endpoint.js.map +1 -0
  9. package/dist/plugin/plugin-remote-worker.js +8 -7
  10. package/dist/plugin/plugin-remote-worker.js.map +1 -1
  11. package/dist/plugin/plugin-remote.d.ts +2 -51
  12. package/dist/plugin/plugin-remote.js +6 -337
  13. package/dist/plugin/plugin-remote.js.map +1 -1
  14. package/dist/plugin/runtime/cluster-fork-worker.d.ts +3 -3
  15. package/dist/plugin/runtime/cluster-fork-worker.js +5 -1
  16. package/dist/plugin/runtime/cluster-fork-worker.js.map +1 -1
  17. package/dist/plugin/system.js +0 -1
  18. package/dist/plugin/system.js.map +1 -1
  19. package/dist/runtime.d.ts +4 -4
  20. package/dist/runtime.js +1 -1
  21. package/dist/runtime.js.map +1 -1
  22. package/dist/scrypted-cluster-main.d.ts +12 -2
  23. package/dist/scrypted-cluster-main.js +30 -4
  24. package/dist/scrypted-cluster-main.js.map +1 -1
  25. package/dist/services/cluster-fork.d.ts +2 -2
  26. package/dist/services/cluster-fork.js +4 -3
  27. package/dist/services/cluster-fork.js.map +1 -1
  28. package/package.json +2 -2
  29. package/python/cluster_labels.py +2 -2
  30. package/python/cluster_setup.py +1 -1
  31. package/python/plugin_console.py +8 -0
  32. package/python/plugin_remote.py +105 -34
  33. package/python/rpc.py +5 -4
  34. package/src/cluster/cluster-labels.ts +1 -0
  35. package/src/plugin/device.ts +261 -0
  36. package/src/plugin/endpoint.ts +109 -0
  37. package/src/plugin/plugin-remote-worker.ts +14 -10
  38. package/src/plugin/plugin-remote.ts +6 -364
  39. package/src/plugin/runtime/cluster-fork-worker.ts +10 -5
  40. package/src/plugin/system.ts +0 -1
  41. package/src/runtime.ts +4 -4
  42. package/src/scrypted-cluster-main.ts +39 -9
  43. package/src/services/cluster-fork.ts +5 -5
@@ -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';
@@ -21,6 +22,9 @@ 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
24
  import { RuntimeWorker } 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')
@@ -270,17 +274,17 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
270
274
  }
271
275
  }
272
276
 
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
277
  const localPeer = new RpcPeer('main', 'thread', (message, reject, serializationContext) => runtimeWorker.send(message, reject, serializationContext));
280
278
  runtimeWorker.setupRpcPeer(localPeer);
281
279
  forkPeer = Promise.resolve(localPeer);
282
280
  }
283
281
 
282
+ // thread workers inherit main console. pipe anything else.
283
+ if (!(runtimeWorker instanceof NodeThreadWorker)) {
284
+ const console = options?.id ? getMixinConsole(options.id, options.nativeId) : undefined;
285
+ pipeWorkerConsole(runtimeWorker, console);
286
+ }
287
+
284
288
  const result = (async () => {
285
289
  const threadPeer = await forkPeer;
286
290
 
@@ -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,
@@ -1,16 +1,17 @@
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
8
  import type { RuntimeWorker } from "./runtime-worker";
8
9
 
9
10
  export function createClusterForkWorker(
10
- forkComponentPromise: Promise<ClusterFork>,
11
+ forkComponentPromise: Promise<ClusterForkService>,
11
12
  zipHash: string,
12
13
  getZip: () => Promise<Buffer>,
13
- options: ClusterForkOptions,
14
+ options: Partial<ClusterFork>,
14
15
  packageJson: any,
15
16
  connectRPCObject: (o: any) => Promise<any>) {
16
17
  const waitKilled = new Deferred<void>();
@@ -37,7 +38,11 @@ export function createClusterForkWorker(
37
38
  });
38
39
 
39
40
  const peerLiveness = new PeerLiveness(waitKilled.promise);
40
- const clusterForkResultPromise = forkComponentPromise.then(forkComponent => forkComponent.fork(peerLiveness, options, packageJson, zipHash, getZip));
41
+ const clusterForkResultPromise = forkComponentPromise.then(forkComponent => forkComponent.fork(peerLiveness, {
42
+ runtime: options.runtime || 'node',
43
+ id: options.id,
44
+ ...options,
45
+ }, packageJson, zipHash, getZip));
41
46
  clusterForkResultPromise.catch(() => {});
42
47
 
43
48
  const clusterWorkerId = clusterForkResultPromise.then(clusterForkResult => clusterForkResult.clusterWorkerId);
@@ -1,7 +1,6 @@
1
1
  import { EventListener, EventListenerOptions, EventListenerRegister, Logger, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptor, ScryptedInterfaceDescriptors, ScryptedInterfaceProperty, ScryptedNativeId, SystemDeviceState, SystemManager } from "@scrypted/types";
2
2
  import { EventRegistry } from "../event-registry";
3
3
  import { PrimitiveProxyHandler, RpcPeer } from '../rpc';
4
- // import type { PluginComponent } from "../services/plugin";
5
4
  import { getInterfaceMethods, getInterfaceProperties, getPropertyInterfaces, isValidInterfaceMethod, propertyInterfaces } from "./descriptor";
6
5
  import { PluginAPI } from "./plugin-api";
7
6
 
package/src/runtime.ts CHANGED
@@ -37,7 +37,7 @@ import { getIpAddress, SCRYPTED_INSECURE_PORT, SCRYPTED_SECURE_PORT } from './se
37
37
  import { AddressSettings } from './services/addresses';
38
38
  import { Alerts } from './services/alerts';
39
39
  import { Backup } from './services/backup';
40
- import { ClusterFork } from './services/cluster-fork';
40
+ import { ClusterForkService } from './services/cluster-fork';
41
41
  import { CORSControl } from './services/cors';
42
42
  import { Info } from './services/info';
43
43
  import { getNpmPackageInfo, PluginComponent } from './services/plugin';
@@ -45,7 +45,7 @@ import { ServiceControl } from './services/service-control';
45
45
  import { UsersService } from './services/users';
46
46
  import { getState, ScryptedStateManager, setState } from './state';
47
47
  import { isClusterAddress } from './cluster/cluster-setup';
48
- import { ClusterWorker } from './scrypted-cluster-main';
48
+ import { RunningClusterWorker } from './scrypted-cluster-main';
49
49
 
50
50
  interface DeviceProxyPair {
51
51
  handler: PluginDeviceProxyHandler;
@@ -63,7 +63,7 @@ interface HttpPluginData {
63
63
  export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
64
64
  clusterId = crypto.randomBytes(3).toString('hex');
65
65
  clusterSecret = process.env.SCRYPTED_CLUSTER_SECRET || crypto.randomBytes(16).toString('hex');
66
- clusterWorkers = new Map<string, ClusterWorker>();
66
+ clusterWorkers = new Map<string, RunningClusterWorker>();
67
67
  plugins: { [id: string]: PluginHost } = {};
68
68
  pluginDevices: { [id: string]: PluginDevice } = {};
69
69
  devices: { [id: string]: DeviceProxyPair } = {};
@@ -89,7 +89,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
89
89
  corsControl = new CORSControl(this);
90
90
  addressSettings = new AddressSettings(this);
91
91
  usersService = new UsersService(this);
92
- clusterFork = new ClusterFork(this);
92
+ clusterFork = new ClusterForkService(this);
93
93
  info = new Info();
94
94
  backup = new Backup(this);
95
95
  pluginHosts = getBuiltinRuntimeHosts();
@@ -1,4 +1,4 @@
1
- import type { ForkOptions } from '@scrypted/types';
1
+ import type { ClusterManager, ClusterWorker, ForkOptions } from '@scrypted/types';
2
2
  import crypto from 'crypto';
3
3
  import { once } from 'events';
4
4
  import net from 'net';
@@ -11,13 +11,15 @@ import { computeClusterObjectHash } from './cluster/cluster-hash';
11
11
  import { getClusterLabels } from './cluster/cluster-labels';
12
12
  import { getScryptedClusterMode, InitializeCluster, setupCluster } from './cluster/cluster-setup';
13
13
  import type { ClusterObject } from './cluster/connect-rpc-object';
14
+ import type { PluginAPI } from './plugin/plugin-api';
14
15
  import { getPluginVolume, getScryptedVolume } from './plugin/plugin-volume';
15
16
  import { prepareZip } from './plugin/runtime/node-worker-common';
16
17
  import { getBuiltinRuntimeHosts } from './plugin/runtime/runtime-host';
17
- import { RuntimeWorker } from './plugin/runtime/runtime-worker';
18
+ import type { RuntimeWorker } from './plugin/runtime/runtime-worker';
18
19
  import { RpcPeer } from './rpc';
19
20
  import { createRpcDuplexSerializer } from './rpc-serializer';
20
21
  import type { ScryptedRuntime } from './runtime';
22
+ import type { ClusterForkService } from './services/cluster-fork';
21
23
  import { sleep } from './sleep';
22
24
 
23
25
  installSourceMapSupport({
@@ -64,13 +66,13 @@ export interface ClusterForkOptions {
64
66
  clusterWorkerId?: ForkOptions['clusterWorkerId'];
65
67
  }
66
68
 
67
- type ConnectForkWorker = (auth: ClusterObject, properties: ClusterWorkerProperties) => Promise<{ clusterId: string }>;
69
+ type ConnectForkWorker = (auth: ClusterObject, properties: ClusterWorkerProperties) => Promise<{ clusterId: string, clusterWorkerId: string }>;
68
70
 
69
71
  export interface ClusterWorkerProperties {
70
72
  labels: string[];
71
73
  }
72
74
 
73
- export interface ClusterWorker extends ClusterWorkerProperties {
75
+ export interface RunningClusterWorker extends ClusterWorkerProperties {
74
76
  id: string;
75
77
  peer: RpcPeer;
76
78
  forks: Set<ClusterForkOptions>;
@@ -157,12 +159,11 @@ export function startClusterClient(mainFilename: string) {
157
159
  });
158
160
 
159
161
  try {
160
-
161
162
  const connectForkWorker: ConnectForkWorker = await peer.getParam('connectForkWorker');
162
163
  const auth: ClusterObject = {
163
164
  address: socket.localAddress,
164
165
  port: socket.localPort,
165
- id: process.env.SCRYPTED_CLUSTER_ID || os.hostname(),
166
+ id: process.env.SCRYPTED_CLUSTER_CLIENT_NAME || os.hostname(),
166
167
  proxyId: undefined,
167
168
  sourceKey: undefined,
168
169
  sha256: undefined,
@@ -173,7 +174,8 @@ export function startClusterClient(mainFilename: string) {
173
174
  labels,
174
175
  };
175
176
 
176
- const { clusterId } = await connectForkWorker(auth, properties);
177
+ const { clusterId, clusterWorkerId } = await connectForkWorker(auth, properties);
178
+ process.env.SCRYPTED_CLUSTER_WORKER_ID = clusterWorkerId;
177
179
  const clusterPeerSetup = setupCluster(peer);
178
180
  await clusterPeerSetup.initializeCluster({ clusterId, clusterSecret });
179
181
 
@@ -207,6 +209,8 @@ export function startClusterClient(mainFilename: string) {
207
209
  unzippedPath,
208
210
  zipHash,
209
211
  }, undefined);
212
+ runtimeWorker.stdout.on('data', data => console.log(data.toString()));
213
+ runtimeWorker.stderr.on('data', data => console.error(data.toString()));
210
214
 
211
215
  const threadPeer = new RpcPeer('main', 'thread', (message, reject, serializationContext) => runtimeWorker.send(message, reject, serializationContext));
212
216
  runtimeWorker.setupRpcPeer(threadPeer);
@@ -288,6 +292,7 @@ export function createClusterServer(runtime: ScryptedRuntime, certificate: Retur
288
292
  const peer = preparePeer(socket, 'server');
289
293
 
290
294
  const connectForkWorker: ConnectForkWorker = async (auth: ClusterObject, properties: ClusterWorkerProperties) => {
295
+ const id = crypto.randomUUID();
291
296
  try {
292
297
  const sha256 = computeClusterObjectHash(auth, runtime.clusterSecret);
293
298
  if (sha256 !== auth.sha256)
@@ -301,8 +306,7 @@ export function createClusterServer(runtime: ScryptedRuntime, certificate: Retur
301
306
  if (auth.port !== socket.remotePort || !socket.remoteAddress.endsWith(auth.address))
302
307
  throw new Error('cluster object address mismatch');
303
308
  }
304
- const id = crypto.randomUUID();
305
- const worker: ClusterWorker = {
309
+ const worker: RunningClusterWorker = {
306
310
  ...properties,
307
311
  // generate a random uuid.
308
312
  id,
@@ -325,6 +329,7 @@ export function createClusterServer(runtime: ScryptedRuntime, certificate: Retur
325
329
 
326
330
  return {
327
331
  clusterId: runtime.clusterId,
332
+ clusterWorkerId: id,
328
333
  }
329
334
  }
330
335
  peer.params['connectForkWorker'] = connectForkWorker;
@@ -332,3 +337,28 @@ export function createClusterServer(runtime: ScryptedRuntime, certificate: Retur
332
337
 
333
338
  return server;
334
339
  }
340
+
341
+ export class ClusterManagerImpl implements ClusterManager {
342
+ private clusterServicePromise: Promise<ClusterForkService>;
343
+
344
+ constructor(private api: PluginAPI) {
345
+ }
346
+
347
+ getClusterWorkerId(): string {
348
+ return process.env.SCRYPTED_CLUSTER_WORKER_ID;
349
+ }
350
+
351
+ getClusterMode(): 'server' | 'client' | undefined {
352
+ return getScryptedClusterMode()[0];
353
+ }
354
+
355
+ async getClusterWorkers(): Promise<Record<string, ClusterWorker>> {
356
+ const clusterFork = await this.getClusterService();
357
+ return clusterFork.getClusterWorkers();
358
+ }
359
+
360
+ private getClusterService() {
361
+ this.clusterServicePromise ||= this.api.getComponent('cluster-fork');
362
+ return this.clusterServicePromise;
363
+ }
364
+ }