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