@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.
- package/dist/plugin/device.d.ts +60 -0
- package/dist/plugin/device.js +249 -0
- package/dist/plugin/device.js.map +1 -0
- package/dist/plugin/endpoint.d.ts +45 -0
- package/dist/plugin/endpoint.js +97 -0
- package/dist/plugin/endpoint.js.map +1 -0
- package/dist/plugin/plugin-host.d.ts +1 -1
- package/dist/plugin/plugin-host.js +11 -10
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +18 -16
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/plugin/plugin-remote.d.ts +2 -51
- package/dist/plugin/plugin-remote.js +6 -337
- package/dist/plugin/plugin-remote.js.map +1 -1
- package/dist/plugin/runtime/child-process-worker.d.ts +1 -1
- package/dist/plugin/runtime/child-process-worker.js +2 -2
- package/dist/plugin/runtime/child-process-worker.js.map +1 -1
- package/dist/plugin/runtime/cluster-fork-worker.d.ts +4 -4
- package/dist/plugin/runtime/cluster-fork-worker.js +8 -3
- package/dist/plugin/runtime/cluster-fork-worker.js.map +1 -1
- package/dist/plugin/runtime/custom-worker.d.ts +1 -1
- package/dist/plugin/runtime/custom-worker.js +3 -3
- package/dist/plugin/runtime/custom-worker.js.map +1 -1
- package/dist/plugin/runtime/node-fork-worker.d.ts +1 -1
- package/dist/plugin/runtime/node-fork-worker.js +2 -2
- package/dist/plugin/runtime/node-fork-worker.js.map +1 -1
- package/dist/plugin/runtime/python-worker.d.ts +1 -1
- package/dist/plugin/runtime/python-worker.js +3 -3
- package/dist/plugin/runtime/python-worker.js.map +1 -1
- package/dist/plugin/runtime/runtime-host.d.ts +1 -1
- package/dist/plugin/runtime/runtime-host.js +3 -3
- package/dist/plugin/runtime/runtime-host.js.map +1 -1
- package/dist/plugin/system.js +0 -1
- package/dist/plugin/system.js.map +1 -1
- package/dist/runtime.d.ts +4 -4
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/scrypted-cluster-main.d.ts +14 -3
- package/dist/scrypted-cluster-main.js +42 -18
- package/dist/scrypted-cluster-main.js.map +1 -1
- package/dist/scrypted-server-main.js +1 -0
- package/dist/scrypted-server-main.js.map +1 -1
- package/dist/services/cluster-fork.d.ts +4 -3
- package/dist/services/cluster-fork.js +7 -5
- package/dist/services/cluster-fork.js.map +1 -1
- package/package.json +2 -2
- package/python/cluster_labels.py +2 -2
- package/python/cluster_setup.py +1 -1
- package/python/plugin_console.py +8 -0
- package/python/plugin_remote.py +116 -34
- package/python/rpc.py +5 -4
- package/src/plugin/device.ts +261 -0
- package/src/plugin/endpoint.ts +109 -0
- package/src/plugin/plugin-host.ts +25 -21
- package/src/plugin/plugin-remote-worker.ts +30 -20
- package/src/plugin/plugin-remote.ts +6 -364
- package/src/plugin/runtime/child-process-worker.ts +3 -1
- package/src/plugin/runtime/cluster-fork-worker.ts +20 -12
- package/src/plugin/runtime/custom-worker.ts +3 -3
- package/src/plugin/runtime/node-fork-worker.ts +2 -2
- package/src/plugin/runtime/python-worker.ts +3 -3
- package/src/plugin/runtime/runtime-host.ts +4 -4
- package/src/plugin/system.ts +0 -1
- package/src/runtime.ts +4 -4
- package/src/scrypted-cluster-main.ts +54 -29
- package/src/scrypted-server-main.ts +1 -0
- 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.
|
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(
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
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,
|
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<
|
51
|
+
let pluginsPromise: Promise<PluginComponent>;
|
48
52
|
function getPlugins() {
|
49
|
-
|
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
|
-
|
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,
|
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 {
|
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 {
|
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(
|
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 {
|
5
|
-
import type {
|
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
|
-
|
11
|
-
|
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(
|
46
|
+
const clusterForkResultPromise = forkComponentPromise.then(forkComponent => forkComponent.fork(runtimeWorkerOptions, {
|
41
47
|
runtime: options.runtime || 'node',
|
48
|
+
id: options.id,
|
42
49
|
...options,
|
43
|
-
},
|
44
|
-
|
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(
|
15
|
-
super(
|
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,
|