@scrypted/server 0.1.13 → 0.1.16
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/http-interfaces.js +11 -0
- package/dist/http-interfaces.js.map +1 -1
- package/dist/plugin/media.js +57 -34
- package/dist/plugin/media.js.map +1 -1
- package/dist/plugin/plugin-host.js +6 -4
- 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-npm-dependencies.js +5 -1
- package/dist/plugin/plugin-npm-dependencies.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +118 -7
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/plugin/plugin-remote.js +11 -81
- 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/runtime/python-worker.js +2 -1
- package/dist/plugin/runtime/python-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 +1 -1
- package/dist/rpc.js.map +1 -1
- package/dist/runtime.js +26 -24
- 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 +45 -8
- package/dist/scrypted-server-main.js.map +1 -1
- package/package.json +3 -3
- package/python/plugin-remote.py +28 -19
- package/src/http-interfaces.ts +13 -0
- package/src/plugin/media.ts +66 -34
- package/src/plugin/plugin-api.ts +1 -0
- package/src/plugin/plugin-host.ts +6 -4
- package/src/plugin/plugin-http.ts +2 -2
- package/src/plugin/plugin-npm-dependencies.ts +5 -1
- package/src/plugin/plugin-remote-worker.ts +138 -10
- package/src/plugin/plugin-remote.ts +14 -89
- package/src/plugin/runtime/node-fork-worker.ts +11 -3
- package/src/plugin/runtime/python-worker.ts +3 -1
- 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 +1 -1
- package/src/runtime.ts +32 -30
- package/src/scrypted-plugin-main.ts +4 -1
- package/src/scrypted-server-main.ts +51 -9
@@ -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;
|
@@ -348,13 +342,12 @@ export interface PluginRemoteAttachOptions {
|
|
348
342
|
getDeviceConsole?: (nativeId?: ScryptedNativeId) => Console;
|
349
343
|
getPluginConsole?: () => Console;
|
350
344
|
getMixinConsole?: (id: string, nativeId?: ScryptedNativeId) => Console;
|
351
|
-
onLoadZip?: (
|
345
|
+
onLoadZip?: (scrypted: ScryptedStatic, params: any, packageJson: any, zipData: Buffer | string, zipOptions?: PluginRemoteLoadZipOptions) => Promise<any>;
|
352
346
|
onGetRemote?: (api: PluginAPI, pluginId: string) => Promise<void>;
|
353
|
-
onPluginReady?: (scrypted: ScryptedStatic, params: any, plugin: any) => Promise<void>;
|
354
347
|
}
|
355
348
|
|
356
349
|
export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOptions): Promise<ScryptedStatic> {
|
357
|
-
const { createMediaManager, getServicePort, getDeviceConsole, getMixinConsole
|
350
|
+
const { createMediaManager, getServicePort, getDeviceConsole, getMixinConsole } = options || {};
|
358
351
|
|
359
352
|
if (!peer.constructorSerializerMap.get(Buffer))
|
360
353
|
peer.addSerializer(Buffer, 'Buffer', new BufferSerializer());
|
@@ -368,7 +361,11 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
|
|
368
361
|
const systemManager = new SystemManagerImpl();
|
369
362
|
const deviceManager = new DeviceManagerImpl(systemManager, getDeviceConsole, getMixinConsole);
|
370
363
|
const endpointManager = new EndpointManagerImpl();
|
371
|
-
const
|
364
|
+
const hostMediaManager = await api.getMediaManager();
|
365
|
+
if (!hostMediaManager) {
|
366
|
+
peer.params['createMediaManager'] = async () => createMediaManager(systemManager, deviceManager);
|
367
|
+
}
|
368
|
+
const mediaManager = hostMediaManager || await createMediaManager(systemManager, deviceManager);
|
372
369
|
peer.params['mediaManager'] = mediaManager;
|
373
370
|
const ioSockets: { [id: string]: WebSocketConnectCallbacks } = {};
|
374
371
|
|
@@ -383,6 +380,7 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
|
|
383
380
|
endpointManager,
|
384
381
|
mediaManager,
|
385
382
|
log,
|
383
|
+
pluginHostAPI: api,
|
386
384
|
}
|
387
385
|
|
388
386
|
delete peer.params.getRemote;
|
@@ -473,50 +471,6 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
|
|
473
471
|
},
|
474
472
|
|
475
473
|
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
474
|
|
521
475
|
function websocketConnect(url: string, protocols: any, callbacks: WebSocketConnectCallbacks) {
|
522
476
|
if (url.startsWith('io://') || url.startsWith('ws://')) {
|
@@ -536,18 +490,6 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
|
|
536
490
|
|
537
491
|
const params: any = {
|
538
492
|
__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
493
|
deviceManager,
|
552
494
|
systemManager,
|
553
495
|
mediaManager,
|
@@ -556,29 +498,12 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
|
|
556
498
|
localStorage,
|
557
499
|
pluginHostAPI: api,
|
558
500
|
WebSocket: createWebSocketClass(websocketConnect),
|
501
|
+
pluginRuntimeAPI: ret,
|
559
502
|
};
|
560
503
|
|
561
|
-
params.
|
504
|
+
params.pluginRuntimeAPI = ret;
|
562
505
|
|
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
|
-
}
|
506
|
+
return options.onLoadZip(ret, params, packageJson, zipData, zipOptions);
|
582
507
|
},
|
583
508
|
}
|
584
509
|
|
@@ -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
|
});
|
@@ -45,7 +45,9 @@ export class PythonRuntimeWorker extends ChildProcessWorker {
|
|
45
45
|
}
|
46
46
|
}
|
47
47
|
|
48
|
-
|
48
|
+
const pythonPath = os.platform() === 'win32' ? 'py.exe' : 'python3';
|
49
|
+
|
50
|
+
this.worker = child_process.spawn(pythonPath, args, {
|
49
51
|
// stdin, stdout, stderr, peer in, peer out
|
50
52
|
stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe'],
|
51
53
|
env: Object.assign({
|
@@ -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
|
+
}
|
package/src/rpc-serializer.ts
CHANGED
@@ -3,7 +3,7 @@ import { SidebandBufferSerializer } from "./plugin/buffer-serializer";
|
|
3
3
|
import { RpcPeer } from "./rpc";
|
4
4
|
|
5
5
|
export function createDuplexRpcPeer(selfName: string, peerName: string, readable: Readable, writable: Writable) {
|
6
|
-
const serializer = createRpcDuplexSerializer(
|
6
|
+
const serializer = createRpcDuplexSerializer(writable);
|
7
7
|
|
8
8
|
const rpcPeer = new RpcPeer(selfName, peerName, (message, reject, serializationContext) => {
|
9
9
|
try {
|
@@ -16,6 +16,7 @@ export function createDuplexRpcPeer(selfName: string, peerName: string, readable
|
|
16
16
|
});
|
17
17
|
|
18
18
|
serializer.setupRpcPeer(rpcPeer);
|
19
|
+
readable.on('data', data => serializer.onData(data));
|
19
20
|
readable.on('close', serializer.onDisconnected);
|
20
21
|
readable.on('error', serializer.onDisconnected);
|
21
22
|
return rpcPeer;
|
@@ -34,7 +35,7 @@ export function createRpcSerializer(options: {
|
|
34
35
|
rpcPeer.kill('connection closed.');
|
35
36
|
}
|
36
37
|
|
37
|
-
const sendMessage = (message: any, reject: (e: Error) => void, serializationContext: any,
|
38
|
+
const sendMessage = (message: any, reject: (e: Error) => void, serializationContext: any,) => {
|
38
39
|
if (!connected) {
|
39
40
|
reject?.(new Error('peer disconnected'));
|
40
41
|
return;
|
@@ -78,7 +79,9 @@ export function createRpcSerializer(options: {
|
|
78
79
|
};
|
79
80
|
}
|
80
81
|
|
81
|
-
export function createRpcDuplexSerializer(
|
82
|
+
export function createRpcDuplexSerializer(writable: {
|
83
|
+
write: (data: Buffer) => void;
|
84
|
+
}) {
|
82
85
|
const socketSend = (type: number, data: Buffer) => {
|
83
86
|
const header = Buffer.alloc(5);
|
84
87
|
header.writeUInt32BE(data.length + 1, 0);
|
@@ -102,38 +105,52 @@ export function createRpcDuplexSerializer(readable: Readable, writable: Writable
|
|
102
105
|
});
|
103
106
|
|
104
107
|
let header: Buffer;
|
105
|
-
|
108
|
+
let pending: Buffer;
|
109
|
+
|
110
|
+
const readPending = (length: number) => {
|
111
|
+
if (!pending || pending.length < length)
|
112
|
+
return;
|
113
|
+
|
114
|
+
const ret = pending.slice(0, length);
|
115
|
+
pending = pending.slice(length);
|
116
|
+
if (!pending.length)
|
117
|
+
pending = undefined;
|
118
|
+
return ret;
|
119
|
+
}
|
120
|
+
|
121
|
+
const onData = (data: Buffer) => {
|
122
|
+
if (!pending)
|
123
|
+
pending = data;
|
124
|
+
else
|
125
|
+
pending = Buffer.concat([pending, data]);
|
126
|
+
|
106
127
|
while (true) {
|
107
128
|
if (!header) {
|
108
|
-
header =
|
129
|
+
header = readPending(5);
|
109
130
|
if (!header)
|
110
131
|
return;
|
111
132
|
}
|
112
133
|
|
113
134
|
const length = header.readUInt32BE(0);
|
114
135
|
const type = header.readUInt8(4);
|
115
|
-
const payload: Buffer =
|
136
|
+
const payload: Buffer = readPending(length - 1);
|
116
137
|
if (!payload)
|
117
138
|
return;
|
118
139
|
|
119
140
|
header = undefined;
|
120
141
|
|
121
|
-
const data = payload;
|
122
|
-
|
123
142
|
if (type === 0) {
|
124
|
-
const message = JSON.parse(
|
143
|
+
const message = JSON.parse(payload.toString());
|
125
144
|
serializer.onMessageFinish(message);
|
126
145
|
}
|
127
146
|
else {
|
128
|
-
serializer.onMessageBuffer(
|
147
|
+
serializer.onMessageBuffer(payload);
|
129
148
|
}
|
130
149
|
}
|
131
150
|
}
|
132
151
|
|
133
|
-
readable.on('readable', readMessages);
|
134
|
-
readMessages();
|
135
|
-
|
136
152
|
return {
|
153
|
+
onData,
|
137
154
|
setupRpcPeer: serializer.setupRpcPeer,
|
138
155
|
sendMessage: serializer.sendMessage,
|
139
156
|
onDisconnected: serializer.onDisconnected,
|
package/src/rpc.ts
CHANGED
@@ -138,7 +138,7 @@ class RpcProxy implements PrimitiveProxyHandler<any> {
|
|
138
138
|
|
139
139
|
if (this.proxyOneWayMethods?.includes?.(method)) {
|
140
140
|
rpcApply.oneway = true;
|
141
|
-
this.peer.send(rpcApply);
|
141
|
+
this.peer.send(rpcApply, undefined, serializationContext);
|
142
142
|
return Promise.resolve();
|
143
143
|
}
|
144
144
|
|
package/src/runtime.ts
CHANGED
@@ -1,39 +1,38 @@
|
|
1
|
-
import {
|
2
|
-
import
|
3
|
-
import { ScryptedNativeId, Device, EngineIOHandler, HttpRequest, HttpRequestHandler, OauthClient, PushHandler, ScryptedDevice, ScryptedInterface, ScryptedInterfaceProperty, DeviceInformation } from '@scrypted/types';
|
4
|
-
import { PluginDeviceProxyHandler } from './plugin/plugin-device';
|
5
|
-
import { Plugin, PluginDevice, ScryptedAlert } from './db-types';
|
6
|
-
import { getState, ScryptedStateManager, setState } from './state';
|
7
|
-
import { Request, Response } from 'express';
|
8
|
-
import { createResponseInterface } from './http-interfaces';
|
9
|
-
import http, { ServerResponse, IncomingHttpHeaders } from 'http';
|
10
|
-
import https from 'https';
|
11
|
-
import express from 'express';
|
12
|
-
import { LogEntry, Logger, makeAlertId } from './logger';
|
13
|
-
import { getDisplayName, getDisplayRoom, getDisplayType, getProvidedNameOrDefault, getProvidedRoomOrDefault, getProvidedTypeOrDefault } from './infer-defaults';
|
14
|
-
import { URL } from "url";
|
15
|
-
import qs from "query-string";
|
16
|
-
import { PluginComponent } from './services/plugin';
|
17
|
-
import WebSocket, { Server as WebSocketServer } from "ws";
|
1
|
+
import { Device, DeviceInformation, EngineIOHandler, HttpRequest, HttpRequestHandler, OauthClient, PushHandler, ScryptedDevice, ScryptedInterface, ScryptedInterfaceProperty, ScryptedNativeId } from '@scrypted/types';
|
2
|
+
import AdmZip from 'adm-zip';
|
18
3
|
import axios from 'axios';
|
19
|
-
import
|
4
|
+
import * as io from 'engine.io';
|
20
5
|
import { once } from 'events';
|
6
|
+
import express, { Request, Response } from 'express';
|
7
|
+
import http, { ServerResponse } from 'http';
|
8
|
+
import https from 'https';
|
9
|
+
import { spawn as ptySpawn } from 'node-pty-prebuilt-multiarch';
|
10
|
+
import path from 'path';
|
11
|
+
import qs from "query-string";
|
12
|
+
import rimraf from 'rimraf';
|
13
|
+
import semver from 'semver';
|
21
14
|
import { PassThrough } from 'stream';
|
15
|
+
import tar from 'tar';
|
16
|
+
import { URL } from "url";
|
17
|
+
import WebSocket, { Server as WebSocketServer } from "ws";
|
18
|
+
import { Plugin, PluginDevice, ScryptedAlert } from './db-types';
|
19
|
+
import { createResponseInterface } from './http-interfaces';
|
20
|
+
import { getDisplayName, getDisplayRoom, getDisplayType, getProvidedNameOrDefault, getProvidedRoomOrDefault, getProvidedTypeOrDefault } from './infer-defaults';
|
21
|
+
import { IOServer } from './io';
|
22
|
+
import { Level } from './level';
|
23
|
+
import { LogEntry, Logger, makeAlertId } from './logger';
|
22
24
|
import { PluginDebug } from './plugin/plugin-debug';
|
25
|
+
import { PluginDeviceProxyHandler } from './plugin/plugin-device';
|
26
|
+
import { PluginHost } from './plugin/plugin-host';
|
27
|
+
import { isConnectionUpgrade, PluginHttp } from './plugin/plugin-http';
|
28
|
+
import { getPluginVolume } from './plugin/plugin-volume';
|
23
29
|
import { getIpAddress, SCRYPTED_INSECURE_PORT, SCRYPTED_SECURE_PORT } from './server-settings';
|
24
|
-
import semver from 'semver';
|
25
|
-
import { ServiceControl } from './services/service-control';
|
26
30
|
import { Alerts } from './services/alerts';
|
27
|
-
import { Info } from './services/info';
|
28
|
-
import * as io from 'engine.io';
|
29
|
-
import { spawn as ptySpawn } from 'node-pty';
|
30
|
-
import rimraf from 'rimraf';
|
31
|
-
import { getPluginVolume } from './plugin/plugin-volume';
|
32
|
-
import { isConnectionUpgrade, PluginHttp } from './plugin/plugin-http';
|
33
|
-
import AdmZip from 'adm-zip';
|
34
|
-
import path from 'path';
|
35
31
|
import { CORSControl, CORSServer } from './services/cors';
|
36
|
-
import {
|
32
|
+
import { Info } from './services/info';
|
33
|
+
import { PluginComponent } from './services/plugin';
|
34
|
+
import { ServiceControl } from './services/service-control';
|
35
|
+
import { getState, ScryptedStateManager, setState } from './state';
|
37
36
|
|
38
37
|
interface DeviceProxyPair {
|
39
38
|
handler: PluginDeviceProxyHandler;
|
@@ -283,8 +282,11 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
283
282
|
this.shellio.handleRequest(req, res);
|
284
283
|
}
|
285
284
|
|
286
|
-
async getEndpointPluginData(endpoint: string, isUpgrade: boolean, isEngineIOEndpoint: boolean): Promise<HttpPluginData> {
|
285
|
+
async getEndpointPluginData(req: Request, endpoint: string, isUpgrade: boolean, isEngineIOEndpoint: boolean): Promise<HttpPluginData> {
|
287
286
|
const ret = await this.getPluginForEndpoint(endpoint);
|
287
|
+
if (req.url.indexOf('/engine.io/api') !== -1)
|
288
|
+
return ret;
|
289
|
+
|
288
290
|
const { pluginDevice } = ret;
|
289
291
|
|
290
292
|
// check if upgrade requests can be handled. must be websocket.
|
@@ -2,6 +2,8 @@ import { startPluginRemote } from "./plugin/plugin-remote-worker";
|
|
2
2
|
import { RpcMessage } from "./rpc";
|
3
3
|
import worker_threads from "worker_threads";
|
4
4
|
import v8 from 'v8';
|
5
|
+
import net from 'net';
|
6
|
+
import { SidebandSocketSerializer } from "./plugin/socket-serializer";
|
5
7
|
|
6
8
|
if (process.argv[2] === 'child-thread') {
|
7
9
|
const peer = startPluginRemote(process.argv[3], (message, reject) => {
|
@@ -16,7 +18,7 @@ if (process.argv[2] === 'child-thread') {
|
|
16
18
|
worker_threads.parentPort.on('message', message => peer.handleMessage(v8.deserialize(message)));
|
17
19
|
}
|
18
20
|
else {
|
19
|
-
const peer = startPluginRemote(process.argv[3], (message, reject) => process.send(message,
|
21
|
+
const peer = startPluginRemote(process.argv[3], (message, reject, serializationContext) => process.send(message, serializationContext?.sendHandle, {
|
20
22
|
swallowErrors: !reject,
|
21
23
|
}, e => {
|
22
24
|
if (e)
|
@@ -24,6 +26,7 @@ else {
|
|
24
26
|
}));
|
25
27
|
|
26
28
|
peer.transportSafeArgumentTypes.add(Buffer.name);
|
29
|
+
peer.addSerializer(net.Socket, net.Socket.name, new SidebandSocketSerializer());
|
27
30
|
process.on('message', message => peer.handleMessage(message as RpcMessage));
|
28
31
|
process.on('disconnect', () => {
|
29
32
|
console.error('peer host disconnected, exiting.');
|
@@ -155,7 +155,30 @@ async function start() {
|
|
155
155
|
// use a hash of the private key as the cookie secret.
|
156
156
|
app.use(cookieParser(crypto.createHash('sha256').update(certSetting.value.serviceKey).digest().toString('hex')));
|
157
157
|
|
158
|
-
|
158
|
+
// trap to add access control headers.
|
159
|
+
app.use((req, res, next) => {
|
160
|
+
if (!req.headers.upgrade)
|
161
|
+
scrypted.addAccessControlHeaders(req, res);
|
162
|
+
next();
|
163
|
+
})
|
164
|
+
|
165
|
+
app.options('*', (req, res) => {
|
166
|
+
// add more?
|
167
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
168
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With');
|
169
|
+
res.send(200);
|
170
|
+
});
|
171
|
+
|
172
|
+
const authSalt = crypto.randomBytes(16);
|
173
|
+
const createAuthorizationToken = (login_user_token: string) => {
|
174
|
+
const salted = login_user_token + authSalt;
|
175
|
+
const hash = crypto.createHash('sha256');
|
176
|
+
hash.update(salted);
|
177
|
+
const sha = hash.digest().toString('hex');
|
178
|
+
return `Bearer ${sha}#${login_user_token}`;
|
179
|
+
}
|
180
|
+
|
181
|
+
app.use(async (req, res, next) => {
|
159
182
|
// this is a trap for all auth.
|
160
183
|
// only basic auth will fail with 401. it is up to the endpoints to manage
|
161
184
|
// lack of login from cookie auth.
|
@@ -165,10 +188,8 @@ async function start() {
|
|
165
188
|
const userTokenParts = login_user_token.split('#');
|
166
189
|
const username = userTokenParts[0];
|
167
190
|
const timestamp = parseInt(userTokenParts[1]);
|
168
|
-
if (timestamp + 86400000 < Date.now())
|
169
|
-
console.warn('login expired');
|
191
|
+
if (timestamp + 86400000 < Date.now())
|
170
192
|
return next();
|
171
|
-
}
|
172
193
|
|
173
194
|
// this database lookup on every web request is not necessary, the cookie
|
174
195
|
// itself is the auth, and is signed. furthermore, this is currently
|
@@ -182,7 +203,27 @@ async function start() {
|
|
182
203
|
// }
|
183
204
|
|
184
205
|
res.locals.username = username;
|
185
|
-
|
206
|
+
}
|
207
|
+
else if (req.headers.authorization?.startsWith('Bearer ')) {
|
208
|
+
const splits = req.headers.authorization.substring('Bearer '.length).split('#');
|
209
|
+
const login_user_token = splits[1] + '#' + splits[2];
|
210
|
+
if (login_user_token) {
|
211
|
+
const check = splits[0];
|
212
|
+
|
213
|
+
const salted = login_user_token + authSalt;
|
214
|
+
const hash = crypto.createHash('sha256');
|
215
|
+
hash.update(salted);
|
216
|
+
const sha = hash.digest().toString('hex');
|
217
|
+
|
218
|
+
if (check === sha) {
|
219
|
+
const splits2 = login_user_token.split('#');
|
220
|
+
const username = splits2[0];
|
221
|
+
const timestamp = parseInt(splits2[1]);
|
222
|
+
if (timestamp + 86400000 < Date.now())
|
223
|
+
return next();
|
224
|
+
res.locals.username = username;
|
225
|
+
}
|
226
|
+
}
|
186
227
|
}
|
187
228
|
next();
|
188
229
|
});
|
@@ -355,7 +396,7 @@ async function start() {
|
|
355
396
|
});
|
356
397
|
|
357
398
|
const getLoginUserToken = (reqSecure: boolean) => {
|
358
|
-
return reqSecure ? 'login_user_token' : '
|
399
|
+
return reqSecure ? 'login_user_token' : 'login_user_token_insecure';
|
359
400
|
};
|
360
401
|
|
361
402
|
const getSignedLoginUserToken = (req: Request<any>): string => {
|
@@ -370,15 +411,12 @@ async function start() {
|
|
370
411
|
let hasLogin = await db.getCount(ScryptedUser) > 0;
|
371
412
|
|
372
413
|
app.options('/login', (req, res) => {
|
373
|
-
scrypted.addAccessControlHeaders(req, res);
|
374
414
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
375
415
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With');
|
376
416
|
res.send(200);
|
377
417
|
});
|
378
418
|
|
379
419
|
app.post('/login', async (req, res) => {
|
380
|
-
scrypted.addAccessControlHeaders(req, res);
|
381
|
-
|
382
420
|
const { username, password, change_password } = req.body;
|
383
421
|
const timestamp = Date.now();
|
384
422
|
const maxAge = 86400000;
|
@@ -422,6 +460,7 @@ async function start() {
|
|
422
460
|
}
|
423
461
|
|
424
462
|
res.send({
|
463
|
+
authorization: createAuthorizationToken(login_user_token),
|
425
464
|
username,
|
426
465
|
expiration: maxAge,
|
427
466
|
addresses,
|
@@ -456,6 +495,7 @@ async function start() {
|
|
456
495
|
});
|
457
496
|
|
458
497
|
res.send({
|
498
|
+
authorization: createAuthorizationToken(login_user_token),
|
459
499
|
username,
|
460
500
|
token: user.token,
|
461
501
|
expiration: maxAge,
|
@@ -463,6 +503,7 @@ async function start() {
|
|
463
503
|
});
|
464
504
|
});
|
465
505
|
|
506
|
+
|
466
507
|
app.get('/login', async (req, res) => {
|
467
508
|
scrypted.addAccessControlHeaders(req, res);
|
468
509
|
|
@@ -510,6 +551,7 @@ async function start() {
|
|
510
551
|
}
|
511
552
|
|
512
553
|
res.send({
|
554
|
+
authorization: createAuthorizationToken(login_user_token),
|
513
555
|
expiration: 86400000 - (Date.now() - timestamp),
|
514
556
|
username,
|
515
557
|
addresses,
|