@scrypted/server 0.94.22 → 0.94.24
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/plugin-api.d.ts +3 -10
- package/dist/plugin/plugin-host.d.ts +2 -0
- package/dist/plugin/plugin-host.js +30 -48
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-lazy-remote.d.ts +1 -1
- package/dist/plugin/plugin-lazy-remote.js +2 -2
- package/dist/plugin/plugin-lazy-remote.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +26 -48
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/plugin/plugin-remote.d.ts +1 -2
- package/dist/plugin/plugin-remote.js +2 -2
- package/dist/plugin/plugin-remote.js.map +1 -1
- package/dist/plugin/runtime/custom-worker.d.ts +12 -0
- package/dist/plugin/runtime/custom-worker.js +72 -0
- package/dist/plugin/runtime/custom-worker.js.map +1 -0
- package/dist/plugin/runtime/node-thread-worker.d.ts +1 -1
- package/dist/plugin/runtime/node-thread-worker.js +2 -2
- package/dist/plugin/runtime/node-thread-worker.js.map +1 -1
- package/dist/plugin/runtime/node-worker-common.d.ts +13 -0
- package/dist/plugin/runtime/node-worker-common.js +76 -0
- package/dist/plugin/runtime/node-worker-common.js.map +1 -0
- package/dist/plugin/runtime/python-worker.js.map +1 -1
- package/dist/plugin/runtime/runtime-worker.d.ts +3 -0
- package/dist/runtime.d.ts +2 -4
- package/dist/runtime.js +3 -72
- package/dist/runtime.js.map +1 -1
- package/package.json +3 -3
- package/python/plugin_remote.py +34 -27
- package/python/plugin_volume.py +45 -0
- package/src/plugin/plugin-api.ts +3 -10
- package/src/plugin/plugin-host.ts +35 -50
- package/src/plugin/plugin-lazy-remote.ts +2 -2
- package/src/plugin/plugin-remote-worker.ts +28 -53
- package/src/plugin/plugin-remote.ts +3 -5
- package/src/plugin/runtime/custom-worker.ts +81 -0
- package/src/plugin/runtime/node-thread-worker.ts +2 -4
- package/src/plugin/runtime/node-worker-common.ts +77 -0
- package/src/plugin/runtime/python-worker.ts +1 -1
- package/src/plugin/runtime/runtime-worker.ts +3 -0
- package/src/runtime.ts +4 -81
@@ -1,10 +1,10 @@
|
|
1
1
|
import { ScryptedStatic, SystemManager } from '@scrypted/types';
|
2
|
-
import AdmZip from 'adm-zip';
|
3
2
|
import { once } from 'events';
|
4
3
|
import fs from 'fs';
|
5
4
|
import net from 'net';
|
6
5
|
import path from 'path';
|
7
6
|
import { install as installSourceMapSupport } from 'source-map-support';
|
7
|
+
import worker_threads from 'worker_threads';
|
8
8
|
import { computeClusterObjectHash } from '../cluster/cluster-hash';
|
9
9
|
import { ClusterObject, ConnectRPCObject } from '../cluster/connect-rpc-object';
|
10
10
|
import { listenZero } from '../listen-zero';
|
@@ -14,11 +14,12 @@ import { MediaManagerImpl } from './media';
|
|
14
14
|
import { PluginAPI, PluginAPIProxy, PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
|
15
15
|
import { prepareConsoles } from './plugin-console';
|
16
16
|
import { getPluginNodePath, installOptionalDependencies } from './plugin-npm-dependencies';
|
17
|
-
import { DeviceManagerImpl,
|
17
|
+
import { DeviceManagerImpl, attachPluginRemote, setupPluginRemote } from './plugin-remote';
|
18
18
|
import { PluginStats, startStatsUpdater } from './plugin-remote-stats';
|
19
19
|
import { createREPLServer } from './plugin-repl';
|
20
|
+
import { getPluginVolume } from './plugin-volume';
|
20
21
|
import { NodeThreadWorker } from './runtime/node-thread-worker';
|
21
|
-
import
|
22
|
+
import { prepareZip } from './runtime/node-worker-common';
|
22
23
|
|
23
24
|
const serverVersion = require('../../package.json').version;
|
24
25
|
|
@@ -75,8 +76,9 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
|
75
76
|
}
|
76
77
|
throw new Error(`unknown service ${name}`);
|
77
78
|
},
|
78
|
-
async onLoadZip(scrypted: ScryptedStatic, params: any, packageJson: any,
|
79
|
-
const { clusterId, clusterSecret } = zipOptions;
|
79
|
+
async onLoadZip(scrypted: ScryptedStatic, params: any, packageJson: any, getZip: () => Promise<Buffer>, zipOptions: PluginRemoteLoadZipOptions) {
|
80
|
+
const { clusterId, clusterSecret, zipHash } = zipOptions;
|
81
|
+
const { zipFile, unzippedPath } = await prepareZip(getPluginVolume(pluginId), zipHash, getZip);
|
80
82
|
|
81
83
|
const onProxySerialization = (value: any, proxyId: string, sourcePeerPort?: number) => {
|
82
84
|
const properties = RpcPeer.prepareProxyProperties(value) || {};
|
@@ -194,50 +196,20 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
|
194
196
|
return value;
|
195
197
|
}
|
196
198
|
}
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
if (fs.existsSync(fsDir))
|
204
|
-
process.chdir(fsDir);
|
205
|
-
else
|
206
|
-
process.chdir(zipOptions.unzippedPath);
|
207
|
-
}
|
208
|
-
|
209
|
-
// volume = link(fs, ['', path.join(zipOptions.unzippedPath, 'fs')]);
|
210
|
-
pluginReader = name => {
|
211
|
-
const filename = path.join(zipOptions.unzippedPath, name);
|
212
|
-
if (!fs.existsSync(filename))
|
213
|
-
return;
|
214
|
-
return fs.readFileSync(filename);
|
215
|
-
};
|
199
|
+
if (worker_threads.isMainThread) {
|
200
|
+
const fsDir = path.join(unzippedPath, 'fs')
|
201
|
+
if (fs.existsSync(fsDir))
|
202
|
+
process.chdir(fsDir);
|
203
|
+
else
|
204
|
+
process.chdir(unzippedPath);
|
216
205
|
}
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
// continue;
|
225
|
-
// if (!entry.entryName.startsWith('fs/'))
|
226
|
-
// continue;
|
227
|
-
// const name = entry.entryName.substring('fs/'.length);
|
228
|
-
// volume.mkdirpSync(path.dirname(name));
|
229
|
-
// const data = entry.getData();
|
230
|
-
// volume.writeFileSync(name, data);
|
231
|
-
// }
|
232
|
-
|
233
|
-
pluginReader = name => {
|
234
|
-
const entry = admZip.getEntry(name);
|
235
|
-
if (!entry)
|
236
|
-
return;
|
237
|
-
return entry.getData();
|
238
|
-
}
|
239
|
-
}
|
240
|
-
zipData = undefined;
|
206
|
+
|
207
|
+
const pluginReader = (name: string) => {
|
208
|
+
const filename = path.join(unzippedPath, name);
|
209
|
+
if (!fs.existsSync(filename))
|
210
|
+
return;
|
211
|
+
return fs.readFileSync(filename);
|
212
|
+
};
|
241
213
|
|
242
214
|
const pluginConsole = getPluginConsole?.();
|
243
215
|
params.console = pluginConsole;
|
@@ -248,9 +220,9 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
|
248
220
|
return require('fs');
|
249
221
|
}
|
250
222
|
try {
|
251
|
-
if (name.startsWith('.') &&
|
223
|
+
if (name.startsWith('.') && unzippedPath) {
|
252
224
|
try {
|
253
|
-
const c = path.join(
|
225
|
+
const c = path.join(unzippedPath, name);
|
254
226
|
const module = require(c);
|
255
227
|
return module;
|
256
228
|
}
|
@@ -314,7 +286,6 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
|
314
286
|
peer.getParam('updateStats').then(updateStats => startStatsUpdater(allMemoryStats, updateStats));
|
315
287
|
|
316
288
|
const main = pluginReader('main.nodejs.js');
|
317
|
-
pluginReader = undefined;
|
318
289
|
const script = main.toString();
|
319
290
|
|
320
291
|
scrypted.connect = (socket, options) => {
|
@@ -328,6 +299,9 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
|
328
299
|
packageJson,
|
329
300
|
env: process.env,
|
330
301
|
pluginDebug: undefined,
|
302
|
+
zipFile,
|
303
|
+
unzippedPath,
|
304
|
+
zipHash,
|
331
305
|
});
|
332
306
|
|
333
307
|
const result = (async () => {
|
@@ -368,7 +342,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
|
368
342
|
|
369
343
|
const forkOptions = Object.assign({}, zipOptions);
|
370
344
|
forkOptions.fork = true;
|
371
|
-
return remote.loadZip(packageJson,
|
345
|
+
return remote.loadZip(packageJson, getZip, forkOptions)
|
372
346
|
})();
|
373
347
|
|
374
348
|
result.catch(() => ntw.kill());
|
@@ -380,7 +354,8 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
|
380
354
|
}
|
381
355
|
|
382
356
|
try {
|
383
|
-
|
357
|
+
const filename = zipOptions?.debug ? '/plugin/main.nodejs.js' : `/${pluginId}/main.nodejs.js`;
|
358
|
+
peer.evalLocal(script, filename, params);
|
384
359
|
|
385
360
|
if (zipOptions?.fork) {
|
386
361
|
// pluginConsole?.log('plugin forked');
|
@@ -455,15 +455,13 @@ export interface WebSocketCustomHandler {
|
|
455
455
|
methods: WebSocketMethods;
|
456
456
|
}
|
457
457
|
|
458
|
-
export type PluginReader = (name: string) => Buffer;
|
459
|
-
|
460
458
|
export interface PluginRemoteAttachOptions {
|
461
459
|
createMediaManager?: (systemManager: SystemManager, deviceManager: DeviceManagerImpl) => Promise<MediaManager>;
|
462
460
|
getServicePort?: (name: string, ...args: any[]) => Promise<number>;
|
463
461
|
getDeviceConsole?: (nativeId?: ScryptedNativeId) => Console;
|
464
462
|
getPluginConsole?: () => Console;
|
465
463
|
getMixinConsole?: (id: string, nativeId?: ScryptedNativeId) => Console;
|
466
|
-
onLoadZip?: (scrypted: ScryptedStatic, params: any, packageJson: any,
|
464
|
+
onLoadZip?: (scrypted: ScryptedStatic, params: any, packageJson: any, getZip: () => Promise<Buffer>, zipOptions: PluginRemoteLoadZipOptions) => Promise<any>;
|
467
465
|
onGetRemote?: (api: PluginAPI, pluginId: string) => Promise<void>;
|
468
466
|
}
|
469
467
|
|
@@ -637,7 +635,7 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
|
|
637
635
|
done(ret);
|
638
636
|
},
|
639
637
|
|
640
|
-
async loadZip(packageJson: any,
|
638
|
+
async loadZip(packageJson: any, getZip: () => Promise<Buffer>, zipOptions?: PluginRemoteLoadZipOptions) {
|
641
639
|
const params: any = {
|
642
640
|
__filename: undefined,
|
643
641
|
deviceManager,
|
@@ -660,7 +658,7 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
|
|
660
658
|
params.pluginRuntimeAPI = ret;
|
661
659
|
|
662
660
|
try {
|
663
|
-
return await options.onLoadZip(ret, params, packageJson,
|
661
|
+
return await options.onLoadZip(ret, params, packageJson, getZip, zipOptions);
|
664
662
|
}
|
665
663
|
catch (e) {
|
666
664
|
console.error('plugin start/fork failed', e)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import { ScryptedRuntimeArguments } from '@scrypted/types';
|
2
|
+
import child_process from 'child_process';
|
3
|
+
import { Readable, Writable } from 'stream';
|
4
|
+
import { RpcMessage, RpcPeer } from "../../rpc";
|
5
|
+
import { createRpcDuplexSerializer } from '../../rpc-serializer';
|
6
|
+
import type { ScryptedRuntime } from '../../runtime';
|
7
|
+
import { ChildProcessWorker } from "./child-process-worker";
|
8
|
+
import { RuntimeWorkerOptions } from "./runtime-worker";
|
9
|
+
|
10
|
+
export class CustomRuntimeWorker extends ChildProcessWorker {
|
11
|
+
serializer: ReturnType<typeof createRpcDuplexSerializer>;
|
12
|
+
fork: boolean;
|
13
|
+
|
14
|
+
constructor(pluginId: string, options: RuntimeWorkerOptions, runtime: ScryptedRuntime) {
|
15
|
+
super(pluginId, options);
|
16
|
+
|
17
|
+
const pluginDevice = runtime.findPluginDevice(this.pluginId);
|
18
|
+
const scryptedRuntimeArguments: ScryptedRuntimeArguments = pluginDevice.state.scryptedRuntimeArguments?.value;
|
19
|
+
if (!scryptedRuntimeArguments)
|
20
|
+
throw new Error('custom runtime requires scryptedRuntimeArguments');
|
21
|
+
|
22
|
+
const { env, pluginDebug } = options;
|
23
|
+
|
24
|
+
const args = [...scryptedRuntimeArguments.arguments || []];
|
25
|
+
|
26
|
+
const opts: child_process.ForkOptions | child_process.SpawnOptions = {
|
27
|
+
// stdin, stdout, stderr, peer in, peer out
|
28
|
+
stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe', 'pipe'],
|
29
|
+
env: Object.assign({
|
30
|
+
SCRYYPTED_PLUGIN_ID: pluginId,
|
31
|
+
SCRYPTED_DEBUG_PORT: pluginDebug?.inspectPort?.toString(),
|
32
|
+
SCRYPTED_UNZIPPED_PATH: options.unzippedPath,
|
33
|
+
SCRYPTED_ZIP_FILE: options.zipFile,
|
34
|
+
SCRYPTED_ZIP_HASH: options.zipHash,
|
35
|
+
}, process.env, env),
|
36
|
+
};
|
37
|
+
|
38
|
+
if (!scryptedRuntimeArguments.executable) {
|
39
|
+
this.fork = true;
|
40
|
+
const modulePath = args.shift();
|
41
|
+
if (!modulePath)
|
42
|
+
throw new Error('fork runtime requires modulePath in first argument.');
|
43
|
+
|
44
|
+
(opts.stdio as any)[5] = 'ipc';
|
45
|
+
this.worker = child_process.fork(modulePath, args, opts);
|
46
|
+
}
|
47
|
+
else {
|
48
|
+
this.worker = child_process.spawn(scryptedRuntimeArguments.executable, args, opts);
|
49
|
+
}
|
50
|
+
|
51
|
+
this.setupWorker();
|
52
|
+
}
|
53
|
+
|
54
|
+
setupRpcPeer(peer: RpcPeer): void {
|
55
|
+
const peerin = this.worker.stdio[3] as Writable;
|
56
|
+
const peerout = this.worker.stdio[this.fork ? 3 : 4] as Readable;
|
57
|
+
|
58
|
+
const serializer = this.serializer = createRpcDuplexSerializer(peerin);
|
59
|
+
serializer.setupRpcPeer(peer);
|
60
|
+
peerout.on('data', data => serializer.onData(data));
|
61
|
+
peerin.on('error', e => {
|
62
|
+
this.emit('error', e);
|
63
|
+
serializer.onDisconnected();
|
64
|
+
});
|
65
|
+
peerout.on('error', e => {
|
66
|
+
this.emit('error', e)
|
67
|
+
serializer.onDisconnected();
|
68
|
+
});
|
69
|
+
}
|
70
|
+
|
71
|
+
send(message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any): void {
|
72
|
+
try {
|
73
|
+
if (!this.worker)
|
74
|
+
throw new Error('python worker has been killed');
|
75
|
+
this.serializer.sendMessage(message, reject, serializationContext);
|
76
|
+
}
|
77
|
+
catch (e) {
|
78
|
+
reject?.(e);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
@@ -1,10 +1,8 @@
|
|
1
|
+
import v8 from 'v8';
|
2
|
+
import worker_threads from "worker_threads";
|
1
3
|
import { EventEmitter } from "ws";
|
2
4
|
import { RpcMessage, RpcPeer } from "../../rpc";
|
3
5
|
import { RuntimeWorker, RuntimeWorkerOptions } from "./runtime-worker";
|
4
|
-
import worker_threads from "worker_threads";
|
5
|
-
import path from 'path';
|
6
|
-
import { getPluginNodePath } from "../plugin-npm-dependencies";
|
7
|
-
import v8 from 'v8';
|
8
6
|
|
9
7
|
export class NodeThreadWorker extends EventEmitter implements RuntimeWorker {
|
10
8
|
terminated: boolean;
|
@@ -0,0 +1,77 @@
|
|
1
|
+
import AdmZip from 'adm-zip';
|
2
|
+
import fs from 'fs';
|
3
|
+
import path from 'path';
|
4
|
+
|
5
|
+
function createAdmZipHash(hash: string) {
|
6
|
+
const extractVersion = "1-";
|
7
|
+
return extractVersion + hash;
|
8
|
+
}
|
9
|
+
|
10
|
+
function prep(pluginVolume: string, hash: string) {
|
11
|
+
hash = createAdmZipHash(hash);
|
12
|
+
|
13
|
+
const zipFilename = `${hash}.zip`;
|
14
|
+
const zipDir = path.join(pluginVolume, 'zip');
|
15
|
+
const zipFile = path.join(zipDir, zipFilename);
|
16
|
+
const unzippedPath = path.join(zipDir, 'unzipped')
|
17
|
+
const zipDirTmp = zipDir + '.tmp';
|
18
|
+
|
19
|
+
return {
|
20
|
+
unzippedPath,
|
21
|
+
zipFilename,
|
22
|
+
zipDir,
|
23
|
+
zipFile,
|
24
|
+
zipDirTmp,
|
25
|
+
};
|
26
|
+
}
|
27
|
+
|
28
|
+
export async function prepareZip(pluginVolume: string, h: string, getZip: () => Promise<Buffer>) {
|
29
|
+
const { zipFile, unzippedPath } = prep(pluginVolume, h);
|
30
|
+
if (fs.existsSync(zipFile)) {
|
31
|
+
return {
|
32
|
+
zipFile,
|
33
|
+
unzippedPath,
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
const zipBuffer = await getZip();
|
38
|
+
return extractZip(pluginVolume, h, zipBuffer);
|
39
|
+
}
|
40
|
+
|
41
|
+
export function prepareZipSync(pluginVolume: string, h: string, getZip: () => Buffer) {
|
42
|
+
const { zipFile, unzippedPath } = prep(pluginVolume, h);
|
43
|
+
if (fs.existsSync(zipFile)) {
|
44
|
+
return {
|
45
|
+
zipFile,
|
46
|
+
unzippedPath,
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
const zipBuffer = getZip();
|
51
|
+
return extractZip(pluginVolume, h, zipBuffer);
|
52
|
+
}
|
53
|
+
|
54
|
+
export function extractZip(pluginVolume: string, h: string, zipBuffer: Buffer) {
|
55
|
+
const { zipDir, zipDirTmp, zipFilename, zipFile, unzippedPath } = prep(pluginVolume, h);
|
56
|
+
|
57
|
+
fs.rmSync(zipDirTmp, {
|
58
|
+
recursive: true,
|
59
|
+
force: true,
|
60
|
+
});
|
61
|
+
fs.rmSync(zipDir, {
|
62
|
+
recursive: true,
|
63
|
+
force: true,
|
64
|
+
});
|
65
|
+
fs.mkdirSync(zipDirTmp, {
|
66
|
+
recursive: true,
|
67
|
+
});
|
68
|
+
fs.writeFileSync(path.join(zipDirTmp, zipFilename), zipBuffer);
|
69
|
+
const admZip = new AdmZip(zipBuffer);
|
70
|
+
admZip.extractAllTo(path.join(zipDirTmp, 'unzipped'), true);
|
71
|
+
fs.renameSync(zipDirTmp, zipDir);
|
72
|
+
|
73
|
+
return {
|
74
|
+
zipFile,
|
75
|
+
unzippedPath,
|
76
|
+
}
|
77
|
+
}
|
@@ -2,12 +2,12 @@ import child_process from 'child_process';
|
|
2
2
|
import fs from "fs";
|
3
3
|
import os from "os";
|
4
4
|
import path from 'path';
|
5
|
+
import type { PortablePython as PortablePythonType } from 'py';
|
5
6
|
import { Readable, Writable } from 'stream';
|
6
7
|
import { RpcMessage, RpcPeer } from "../../rpc";
|
7
8
|
import { createRpcDuplexSerializer } from '../../rpc-serializer';
|
8
9
|
import { ChildProcessWorker } from "./child-process-worker";
|
9
10
|
import { RuntimeWorkerOptions } from "./runtime-worker";
|
10
|
-
import type {PortablePython as PortablePythonType} from 'py'
|
11
11
|
|
12
12
|
export class PythonRuntimeWorker extends ChildProcessWorker {
|
13
13
|
static {
|
package/src/runtime.ts
CHANGED
@@ -9,7 +9,6 @@ import fs from 'fs';
|
|
9
9
|
import http, { ServerResponse } from 'http';
|
10
10
|
import https from 'https';
|
11
11
|
import net from 'net';
|
12
|
-
import type { spawn as ptySpawn } from '@homebridge/node-pty-prebuilt-multiarch';
|
13
12
|
import path from 'path';
|
14
13
|
import { ParsedQs } from 'qs';
|
15
14
|
import semver from 'semver';
|
@@ -40,13 +39,14 @@ import { RuntimeWorker, RuntimeWorkerOptions } from './plugin/runtime/runtime-wo
|
|
40
39
|
import { getIpAddress, SCRYPTED_INSECURE_PORT, SCRYPTED_SECURE_PORT } from './server-settings';
|
41
40
|
import { AddressSettings } from './services/addresses';
|
42
41
|
import { Alerts } from './services/alerts';
|
42
|
+
import { Backup } from './services/backup';
|
43
43
|
import { CORSControl } from './services/cors';
|
44
44
|
import { Info } from './services/info';
|
45
45
|
import { getNpmPackageInfo, PluginComponent } from './services/plugin';
|
46
46
|
import { ServiceControl } from './services/service-control';
|
47
47
|
import { UsersService } from './services/users';
|
48
48
|
import { getState, ScryptedStateManager, setState } from './state';
|
49
|
-
import {
|
49
|
+
import { CustomRuntimeWorker } from './plugin/runtime/custom-worker';
|
50
50
|
|
51
51
|
interface DeviceProxyPair {
|
52
52
|
handler: PluginDeviceProxyHandler;
|
@@ -61,7 +61,7 @@ interface HttpPluginData {
|
|
61
61
|
pluginDevice: PluginDevice
|
62
62
|
}
|
63
63
|
|
64
|
-
export type RuntimeHost = (mainFilename: string, pluginId: string, options: RuntimeWorkerOptions) => RuntimeWorker;
|
64
|
+
export type RuntimeHost = (mainFilename: string, pluginId: string, options: RuntimeWorkerOptions, runtime: ScryptedRuntime) => RuntimeWorker;
|
65
65
|
|
66
66
|
export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
67
67
|
clusterId = crypto.randomBytes(3).toString('hex');
|
@@ -74,17 +74,6 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
74
74
|
devicesLogger = this.logger.getLogger('device', 'Devices');
|
75
75
|
wss = new WebSocketServer({ noServer: true });
|
76
76
|
wsAtomic = 0;
|
77
|
-
shellio: IOServer = new io.Server({
|
78
|
-
pingTimeout: 120000,
|
79
|
-
perMessageDeflate: true,
|
80
|
-
cors: (req, callback) => {
|
81
|
-
const header = this.getAccessControlAllowOrigin(req.headers);
|
82
|
-
callback(undefined, {
|
83
|
-
origin: header,
|
84
|
-
credentials: true,
|
85
|
-
})
|
86
|
-
},
|
87
|
-
});
|
88
77
|
connectRPCObjectIO: IOServer = new io.Server({
|
89
78
|
pingTimeout: 120000,
|
90
79
|
perMessageDeflate: true,
|
@@ -111,6 +100,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
111
100
|
// ensure that all the users are loaded from the db.
|
112
101
|
this.usersService.getAllUsers();
|
113
102
|
|
103
|
+
this.pluginHosts.set('custom', (_, pluginId, options, runtime) => new CustomRuntimeWorker(pluginId, options, runtime));
|
114
104
|
this.pluginHosts.set('python', (_, pluginId, options) => new PythonRuntimeWorker(pluginId, options));
|
115
105
|
this.pluginHosts.set('node', (mainFilename, pluginId, options) => new NodeForkWorker(mainFilename, pluginId, options));
|
116
106
|
|
@@ -118,46 +108,6 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
118
108
|
|
119
109
|
this.addMiddleware();
|
120
110
|
|
121
|
-
app.all('/engine.io/shell', (req, res) => {
|
122
|
-
if (res.locals.aclId) {
|
123
|
-
res.writeHead(401);
|
124
|
-
res.end();
|
125
|
-
return;
|
126
|
-
}
|
127
|
-
this.shellHandler(req, res);
|
128
|
-
});
|
129
|
-
|
130
|
-
this.shellio.on('connection', connection => {
|
131
|
-
try {
|
132
|
-
const spawn = require('@homebridge/node-pty-prebuilt-multiarch').spawn as typeof ptySpawn;
|
133
|
-
const cp = spawn(process.env.SHELL, [], {
|
134
|
-
});
|
135
|
-
cp.onData(data => connection.send(data));
|
136
|
-
connection.on('message', message => {
|
137
|
-
if (Buffer.isBuffer(message)) {
|
138
|
-
cp.write(message.toString());
|
139
|
-
return;
|
140
|
-
}
|
141
|
-
|
142
|
-
try {
|
143
|
-
const parsed = JSON.parse(message.toString());
|
144
|
-
if (parsed.dim) {
|
145
|
-
cp.resize(parsed.dim.cols, parsed.dim.rows);
|
146
|
-
}
|
147
|
-
} catch (e) {
|
148
|
-
// we should only get here if an outdated core plugin
|
149
|
-
// is sending us string data instead of buffer data
|
150
|
-
cp.write(message.toString());
|
151
|
-
}
|
152
|
-
});
|
153
|
-
connection.on('close', () => cp.kill());
|
154
|
-
cp.onExit(() => connection.close());
|
155
|
-
}
|
156
|
-
catch (e) {
|
157
|
-
connection.close();
|
158
|
-
}
|
159
|
-
});
|
160
|
-
|
161
111
|
app.all('/engine.io/connectRPCObject', (req, res) => this.connectRPCObjectHandler(req, res));
|
162
112
|
|
163
113
|
/*
|
@@ -305,33 +255,6 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
305
255
|
};
|
306
256
|
}
|
307
257
|
|
308
|
-
async shellHandler(req: Request, res: Response) {
|
309
|
-
const isUpgrade = isConnectionUpgrade(req.headers);
|
310
|
-
|
311
|
-
const end = (code: number, message: string) => {
|
312
|
-
if (isUpgrade) {
|
313
|
-
const socket = res.socket;
|
314
|
-
socket.write(`HTTP/1.1 ${code} ${message}\r\n` +
|
315
|
-
'\r\n');
|
316
|
-
socket.destroy();
|
317
|
-
}
|
318
|
-
else {
|
319
|
-
res.status(code);
|
320
|
-
res.send(message);
|
321
|
-
}
|
322
|
-
};
|
323
|
-
|
324
|
-
if (!res.locals.username) {
|
325
|
-
end(401, 'Not Authorized');
|
326
|
-
return;
|
327
|
-
}
|
328
|
-
|
329
|
-
if ((req as any).upgradeHead)
|
330
|
-
this.shellio.handleUpgrade(req, res.socket, (req as any).upgradeHead)
|
331
|
-
else
|
332
|
-
this.shellio.handleRequest(req, res);
|
333
|
-
}
|
334
|
-
|
335
258
|
async connectRPCObjectHandler(req: Request, res: Response) {
|
336
259
|
const isUpgrade = isConnectionUpgrade(req.headers);
|
337
260
|
|