@scrypted/server 0.1.5 → 0.1.8
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/listen-zero.js +38 -0
- package/dist/listen-zero.js.map +1 -0
- package/dist/plugin/media.js +1 -1
- package/dist/plugin/media.js.map +1 -1
- package/dist/plugin/plugin-console.js +1 -1
- package/dist/plugin/plugin-console.js.map +1 -1
- package/dist/plugin/plugin-host.js +33 -45
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-http.js +7 -2
- package/dist/plugin/plugin-http.js.map +1 -1
- package/dist/plugin/plugin-npm-dependencies.js +3 -1
- package/dist/plugin/plugin-npm-dependencies.js.map +1 -1
- package/dist/plugin/plugin-repl.js +1 -1
- package/dist/plugin/plugin-repl.js.map +1 -1
- package/dist/plugin/runtime/node-fork-worker.js +8 -1
- package/dist/plugin/runtime/node-fork-worker.js.map +1 -1
- package/dist/rpc-serializer.js +121 -0
- package/dist/rpc-serializer.js.map +1 -0
- package/dist/runtime.js +2 -38
- package/dist/runtime.js.map +1 -1
- package/dist/scrypted-server-main.js +13 -11
- package/dist/scrypted-server-main.js.map +1 -1
- package/dist/services/plugin.js +4 -10
- package/dist/services/plugin.js.map +1 -1
- package/dist/threading.js +6 -28
- package/dist/threading.js.map +1 -1
- package/package.json +1 -1
- package/src/listen-zero.ts +34 -0
- package/src/plugin/media.ts +1 -1
- package/src/plugin/plugin-console.ts +1 -1
- package/src/plugin/plugin-host.ts +42 -43
- package/src/plugin/plugin-http.ts +8 -3
- package/src/plugin/plugin-npm-dependencies.ts +3 -1
- package/src/plugin/plugin-repl.ts +1 -1
- package/src/plugin/runtime/node-fork-worker.ts +8 -1
- package/src/plugin/runtime/runtime-worker.ts +2 -1
- package/src/rpc-serializer.ts +141 -0
- package/src/runtime.ts +3 -43
- package/src/scrypted-server-main.ts +16 -12
- package/src/services/plugin.ts +4 -10
- package/src/threading.ts +8 -35
- package/test/rpc-duplex-test.ts +30 -0
- package/test/threading-test.ts +29 -0
- package/dist/plugin/listen-zero.js +0 -23
- package/dist/plugin/listen-zero.js.map +0 -1
- package/src/plugin/listen-zero.ts +0 -21
@@ -4,13 +4,16 @@ import crypto from 'crypto';
|
|
4
4
|
import * as io from 'engine.io';
|
5
5
|
import fs from 'fs';
|
6
6
|
import mkdirp from 'mkdirp';
|
7
|
+
import net from 'net';
|
7
8
|
import path from 'path';
|
8
9
|
import rimraf from 'rimraf';
|
10
|
+
import { Duplex } from 'stream';
|
9
11
|
import WebSocket, { once } from 'ws';
|
10
12
|
import { Plugin } from '../db-types';
|
11
13
|
import { IOServer, IOServerSocket } from '../io';
|
12
14
|
import { Logger } from '../logger';
|
13
15
|
import { RpcPeer } from '../rpc';
|
16
|
+
import { createDuplexRpcPeer, createRpcSerializer } from '../rpc-serializer';
|
14
17
|
import { ScryptedRuntime } from '../runtime';
|
15
18
|
import { sleep } from '../sleep';
|
16
19
|
import { SidebandBufferSerializer } from './buffer-serializer';
|
@@ -134,23 +137,8 @@ export class PluginHost {
|
|
134
137
|
try {
|
135
138
|
if (socket.request.url.indexOf('/api') !== -1) {
|
136
139
|
if (socket.request.url.indexOf('/public') !== -1) {
|
137
|
-
socket.
|
138
|
-
|
139
|
-
id: socket.id,
|
140
|
-
}));
|
141
|
-
const timeout = new Promise((_, rj) => setTimeout(() => rj(new Error('timeout')), 10000));
|
142
|
-
try {
|
143
|
-
await Promise.race([
|
144
|
-
once(socket, '/api/activate'),
|
145
|
-
timeout,
|
146
|
-
]);
|
147
|
-
// client will send a start request when it's ready to process events.
|
148
|
-
await once(socket, 'message');
|
149
|
-
}
|
150
|
-
catch (e) {
|
151
|
-
socket.close();
|
152
|
-
return;
|
153
|
-
}
|
140
|
+
socket.close();
|
141
|
+
return;
|
154
142
|
}
|
155
143
|
|
156
144
|
await this.createRpcIoPeer(socket);
|
@@ -344,51 +332,62 @@ export class PluginHost {
|
|
344
332
|
disconnect();
|
345
333
|
});
|
346
334
|
|
335
|
+
this.worker.on('rpc', (message, sendHandle) => {
|
336
|
+
this.createRpcPeer(sendHandle as net.Socket);
|
337
|
+
});
|
338
|
+
|
347
339
|
this.peer.params.updateStats = (stats: any) => {
|
348
340
|
this.stats = stats;
|
349
341
|
}
|
350
342
|
}
|
351
343
|
|
352
344
|
async createRpcIoPeer(socket: IOServerSocket) {
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
reject?.(new Error('peer disconnected'));
|
357
|
-
return;
|
358
|
-
}
|
359
|
-
const buffers = serializationContext?.buffers;
|
360
|
-
if (buffers) {
|
361
|
-
for (const buffer of buffers) {
|
362
|
-
socket.send(buffer);
|
363
|
-
}
|
364
|
-
}
|
365
|
-
socket.send(JSON.stringify(message))
|
345
|
+
const serializer = createRpcSerializer({
|
346
|
+
sendMessageBuffer: buffer => socket.send(buffer),
|
347
|
+
sendMessageFinish: message => socket.send(JSON.stringify(message)),
|
366
348
|
});
|
367
|
-
|
349
|
+
|
368
350
|
socket.on('message', data => {
|
369
351
|
if (data.constructor === Buffer || data.constructor === ArrayBuffer) {
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
352
|
+
serializer.onMessageBuffer(Buffer.from(data));
|
353
|
+
}
|
354
|
+
else {
|
355
|
+
serializer.onMessageFinish(JSON.parse(data as string));
|
356
|
+
}
|
357
|
+
});
|
358
|
+
|
359
|
+
const rpcPeer = new RpcPeer(`api/${this.pluginId}`, 'engine.io', (message, reject, serializationContext) => {
|
360
|
+
try {
|
361
|
+
serializer.sendMessage(message, reject, serializationContext);
|
362
|
+
}
|
363
|
+
catch (e) {
|
364
|
+
reject?.(e);
|
376
365
|
}
|
377
|
-
const messageSerializationContext = pendingSerializationContext;
|
378
|
-
pendingSerializationContext = undefined;
|
379
|
-
rpcPeer.handleMessage(JSON.parse(data as string), messageSerializationContext);
|
380
366
|
});
|
367
|
+
serializer.setupRpcPeer(rpcPeer);
|
368
|
+
|
381
369
|
// wrap the host api with a connection specific api that can be torn down on disconnect
|
382
370
|
const api = new PluginAPIProxy(this.api, await this.peer.getParam('mediaManager'));
|
383
371
|
const kill = () => {
|
384
|
-
|
385
|
-
rpcPeer.kill('engine.io connection closed.')
|
372
|
+
serializer.onDisconnected();
|
386
373
|
api.removeListeners();
|
387
374
|
}
|
388
375
|
socket.on('close', kill);
|
389
376
|
socket.on('error', kill);
|
390
377
|
|
391
|
-
rpcPeer
|
378
|
+
return setupPluginRemote(rpcPeer, api, null, () => this.scrypted.stateManager.getSystemState());
|
379
|
+
}
|
380
|
+
|
381
|
+
async createRpcPeer(duplex: Duplex) {
|
382
|
+
const rpcPeer = createDuplexRpcPeer(`api/${this.pluginId}`, 'duplex', duplex, duplex);
|
383
|
+
|
384
|
+
// wrap the host api with a connection specific api that can be torn down on disconnect
|
385
|
+
const api = new PluginAPIProxy(this.api, await this.peer.getParam('mediaManager'));
|
386
|
+
const kill = () => {
|
387
|
+
api.removeListeners();
|
388
|
+
};
|
389
|
+
duplex.on('close', kill);
|
390
|
+
|
392
391
|
return setupPluginRemote(rpcPeer, api, null, () => this.scrypted.stateManager.getSystemState());
|
393
392
|
}
|
394
393
|
}
|
@@ -1,9 +1,14 @@
|
|
1
1
|
import { HttpRequest } from '@scrypted/types';
|
2
2
|
import bodyParser from 'body-parser';
|
3
3
|
import { Request, Response, Router } from 'express';
|
4
|
-
import { ServerResponse } from 'http';
|
4
|
+
import { ServerResponse, IncomingHttpHeaders } from 'http';
|
5
5
|
import WebSocket, { Server as WebSocketServer } from "ws";
|
6
6
|
|
7
|
+
export function isConnectionUpgrade(headers: IncomingHttpHeaders) {
|
8
|
+
// connection:'keep-alive, Upgrade'
|
9
|
+
return headers.connection?.toLowerCase().includes('upgrade');
|
10
|
+
}
|
11
|
+
|
7
12
|
export abstract class PluginHttp<T> {
|
8
13
|
wss = new WebSocketServer({ noServer: true });
|
9
14
|
|
@@ -30,7 +35,7 @@ export abstract class PluginHttp<T> {
|
|
30
35
|
this.endpointHandler(req, res, false, false, this.handleRequestEndpoint.bind(this))
|
31
36
|
});
|
32
37
|
}
|
33
|
-
|
38
|
+
|
34
39
|
abstract handleEngineIOEndpoint(req: Request, res: ServerResponse, endpointRequest: HttpRequest, pluginData: T): void;
|
35
40
|
abstract handleRequestEndpoint(req: Request, res: Response, endpointRequest: HttpRequest, pluginData: T): void;
|
36
41
|
abstract getEndpointPluginData(endpoint: string, isUpgrade: boolean, isEngineIOEndpoint: boolean): Promise<T>;
|
@@ -39,7 +44,7 @@ export abstract class PluginHttp<T> {
|
|
39
44
|
async endpointHandler(req: Request, res: Response, isPublicEndpoint: boolean, isEngineIOEndpoint: boolean,
|
40
45
|
handler: (req: Request, res: Response, endpointRequest: HttpRequest, pluginData: T) => void) {
|
41
46
|
|
42
|
-
const isUpgrade = req.headers
|
47
|
+
const isUpgrade = isConnectionUpgrade(req.headers);
|
43
48
|
|
44
49
|
const end = (code: number, message: string) => {
|
45
50
|
if (isUpgrade) {
|
@@ -5,10 +5,12 @@ import path from 'path';
|
|
5
5
|
import { once } from 'events';
|
6
6
|
import process from 'process';
|
7
7
|
import mkdirp from "mkdirp";
|
8
|
+
import semver from 'semver';
|
8
9
|
|
9
10
|
export function getPluginNodePath(name: string) {
|
10
11
|
const pluginVolume = ensurePluginVolume(name);
|
11
|
-
const
|
12
|
+
const nodeMajorVersion = semver.parse(process.version).major;
|
13
|
+
const nodePrefix = path.join(pluginVolume, `${process.platform}-${process.arch}-${nodeMajorVersion}`);
|
12
14
|
return nodePrefix;
|
13
15
|
}
|
14
16
|
|
@@ -30,7 +30,14 @@ export class NodeForkWorker extends ChildProcessWorker {
|
|
30
30
|
}
|
31
31
|
|
32
32
|
setupRpcPeer(peer: RpcPeer): void {
|
33
|
-
this.worker.on('message', message =>
|
33
|
+
this.worker.on('message', (message, sendHandle) => {
|
34
|
+
if (sendHandle) {
|
35
|
+
this.emit('rpc', message, sendHandle);
|
36
|
+
}
|
37
|
+
else {
|
38
|
+
peer.handleMessage(message as any);
|
39
|
+
}
|
40
|
+
});
|
34
41
|
peer.transportSafeArgumentTypes.add(Buffer.name);
|
35
42
|
}
|
36
43
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { RpcMessage, RpcPeer } from "../../rpc";
|
2
2
|
import { PluginDebug } from "../plugin-debug";
|
3
3
|
import {Readable} from "stream";
|
4
|
+
import net from "net";
|
4
5
|
|
5
6
|
export interface RuntimeWorkerOptions {
|
6
7
|
pluginDebug: PluginDebug;
|
@@ -15,6 +16,7 @@ export interface RuntimeWorker {
|
|
15
16
|
|
16
17
|
kill(): void;
|
17
18
|
|
19
|
+
on(event: 'rpc', listener: (message: any, sendHandle: net.Socket) => void): this;
|
18
20
|
on(event: 'error', listener: (err: Error) => void): this;
|
19
21
|
on(event: 'close', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
|
20
22
|
on(event: 'disconnect', listener: () => void): this;
|
@@ -25,4 +27,3 @@ export interface RuntimeWorker {
|
|
25
27
|
|
26
28
|
setupRpcPeer(peer: RpcPeer): void;
|
27
29
|
}
|
28
|
-
|
@@ -0,0 +1,141 @@
|
|
1
|
+
import type { Readable, Writable } from "stream";
|
2
|
+
import { SidebandBufferSerializer } from "./plugin/buffer-serializer";
|
3
|
+
import { RpcPeer } from "./rpc";
|
4
|
+
|
5
|
+
export function createDuplexRpcPeer(selfName: string, peerName: string, readable: Readable, writable: Writable) {
|
6
|
+
const serializer = createRpcDuplexSerializer(readable, writable);
|
7
|
+
|
8
|
+
const rpcPeer = new RpcPeer(selfName, peerName, (message, reject, serializationContext) => {
|
9
|
+
try {
|
10
|
+
serializer.sendMessage(message, reject, serializationContext);
|
11
|
+
}
|
12
|
+
catch (e) {
|
13
|
+
reject?.(e);
|
14
|
+
readable.destroy();
|
15
|
+
}
|
16
|
+
});
|
17
|
+
|
18
|
+
serializer.setupRpcPeer(rpcPeer);
|
19
|
+
readable.on('close', serializer.onDisconnected);
|
20
|
+
readable.on('error', serializer.onDisconnected);
|
21
|
+
return rpcPeer;
|
22
|
+
}
|
23
|
+
|
24
|
+
export function createRpcSerializer(options: {
|
25
|
+
sendMessageBuffer: (buffer: Buffer) => void,
|
26
|
+
sendMessageFinish: (message: any) => void,
|
27
|
+
}) {
|
28
|
+
let rpcPeer: RpcPeer;
|
29
|
+
|
30
|
+
const { sendMessageBuffer, sendMessageFinish } = options;
|
31
|
+
let connected = true;
|
32
|
+
const onDisconnected = () => {
|
33
|
+
connected = false;
|
34
|
+
rpcPeer.kill('connection closed.');
|
35
|
+
}
|
36
|
+
|
37
|
+
const sendMessage = (message: any, reject: (e: Error) => void, serializationContext: any, ) => {
|
38
|
+
if (!connected) {
|
39
|
+
reject?.(new Error('peer disconnected'));
|
40
|
+
return;
|
41
|
+
}
|
42
|
+
|
43
|
+
const buffers = serializationContext?.buffers;
|
44
|
+
if (buffers) {
|
45
|
+
for (const buffer of buffers) {
|
46
|
+
sendMessageBuffer(buffer);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
sendMessageFinish(message);
|
50
|
+
}
|
51
|
+
|
52
|
+
let pendingSerializationContext: any = {};
|
53
|
+
const setupRpcPeer = (peer: RpcPeer) => {
|
54
|
+
rpcPeer = peer;
|
55
|
+
rpcPeer.addSerializer(Buffer, 'Buffer', new SidebandBufferSerializer());
|
56
|
+
}
|
57
|
+
|
58
|
+
const onMessageBuffer = (buffer: Buffer) => {
|
59
|
+
pendingSerializationContext = pendingSerializationContext || {
|
60
|
+
buffers: [],
|
61
|
+
};
|
62
|
+
const buffers: Buffer[] = pendingSerializationContext.buffers;
|
63
|
+
buffers.push(buffer);
|
64
|
+
};
|
65
|
+
|
66
|
+
const onMessageFinish = (message: any) => {
|
67
|
+
const messageSerializationContext = pendingSerializationContext;
|
68
|
+
pendingSerializationContext = undefined;
|
69
|
+
rpcPeer.handleMessage(message, messageSerializationContext);
|
70
|
+
}
|
71
|
+
|
72
|
+
return {
|
73
|
+
sendMessage,
|
74
|
+
setupRpcPeer,
|
75
|
+
onMessageBuffer,
|
76
|
+
onMessageFinish,
|
77
|
+
onDisconnected,
|
78
|
+
};
|
79
|
+
}
|
80
|
+
|
81
|
+
export function createRpcDuplexSerializer(readable: Readable, writable: Writable) {
|
82
|
+
const socketSend = (type: number, data: Buffer) => {
|
83
|
+
const header = Buffer.alloc(5);
|
84
|
+
header.writeUInt32BE(data.length + 1, 0);
|
85
|
+
header.writeUInt8(type, 4);
|
86
|
+
|
87
|
+
writable.write(Buffer.concat([header, data]));
|
88
|
+
}
|
89
|
+
|
90
|
+
const createSocketSend = (type: number) => {
|
91
|
+
return (data: Buffer) => {
|
92
|
+
return socketSend(type, data);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
const sendMessageBuffer = createSocketSend(1);
|
97
|
+
const sendMessageFinish = createSocketSend(0);
|
98
|
+
|
99
|
+
const serializer = createRpcSerializer({
|
100
|
+
sendMessageBuffer,
|
101
|
+
sendMessageFinish: (message) => sendMessageFinish(Buffer.from(JSON.stringify(message))),
|
102
|
+
});
|
103
|
+
|
104
|
+
let header: Buffer;
|
105
|
+
const readMessages = () => {
|
106
|
+
while (true) {
|
107
|
+
if (!header) {
|
108
|
+
header = readable.read(5);
|
109
|
+
if (!header)
|
110
|
+
return;
|
111
|
+
}
|
112
|
+
|
113
|
+
const length = header.readUInt32BE(0);
|
114
|
+
const type = header.readUInt8(4);
|
115
|
+
const payload: Buffer = readable.read(length - 1);
|
116
|
+
if (!payload)
|
117
|
+
return;
|
118
|
+
|
119
|
+
header = undefined;
|
120
|
+
|
121
|
+
const data = payload;
|
122
|
+
|
123
|
+
if (type === 0) {
|
124
|
+
const message = JSON.parse(data.toString());
|
125
|
+
serializer.onMessageFinish(message);
|
126
|
+
}
|
127
|
+
else {
|
128
|
+
serializer.onMessageBuffer(data);
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
readable.on('readable', readMessages);
|
134
|
+
readMessages();
|
135
|
+
|
136
|
+
return {
|
137
|
+
setupRpcPeer: serializer.setupRpcPeer,
|
138
|
+
sendMessage: serializer.sendMessage,
|
139
|
+
onDisconnected: serializer.onDisconnected,
|
140
|
+
};
|
141
|
+
}
|
package/src/runtime.ts
CHANGED
@@ -29,7 +29,7 @@ import * as io from 'engine.io';
|
|
29
29
|
import { spawn as ptySpawn } from 'node-pty';
|
30
30
|
import rimraf from 'rimraf';
|
31
31
|
import { getPluginVolume } from './plugin/plugin-volume';
|
32
|
-
import { PluginHttp } from './plugin/plugin-http';
|
32
|
+
import { isConnectionUpgrade, PluginHttp } from './plugin/plugin-http';
|
33
33
|
import AdmZip from 'adm-zip';
|
34
34
|
import path from 'path';
|
35
35
|
import { CORSControl, CORSServer } from './services/cors';
|
@@ -78,46 +78,6 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
78
78
|
|
79
79
|
app.disable('x-powered-by');
|
80
80
|
|
81
|
-
this.app.options(['/endpoint/@:owner/:pkg/engine.io/api/activate', '/endpoint/@:owner/:pkg/engine.io/api/activate'], (req, res) => {
|
82
|
-
this.addAccessControlHeaders(req, res);
|
83
|
-
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
84
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With');
|
85
|
-
res.send(200);
|
86
|
-
});
|
87
|
-
|
88
|
-
this.app.post(['/endpoint/@:owner/:pkg/engine.io/api/activate', '/endpoint/@:owner/:pkg/engine.io/api/activate'], (req, res) => {
|
89
|
-
const { username } = (req as any);
|
90
|
-
if (!username) {
|
91
|
-
res.status(401);
|
92
|
-
res.send('Not Authorized');
|
93
|
-
return;
|
94
|
-
}
|
95
|
-
|
96
|
-
const { owner, pkg } = req.params;
|
97
|
-
let endpoint = pkg;
|
98
|
-
if (owner)
|
99
|
-
endpoint = `@${owner}/${endpoint}`;
|
100
|
-
|
101
|
-
const { id } = req.body;
|
102
|
-
try {
|
103
|
-
const host = this.plugins?.[endpoint];
|
104
|
-
if (!host)
|
105
|
-
throw new Error('invalid plugin');
|
106
|
-
// @ts-expect-error
|
107
|
-
const socket: IOServerSocket = host.io.clients[id];
|
108
|
-
if (!socket)
|
109
|
-
throw new Error('invalid socket');
|
110
|
-
socket.emit('/api/activate');
|
111
|
-
res.send({
|
112
|
-
id,
|
113
|
-
})
|
114
|
-
}
|
115
|
-
catch (e) {
|
116
|
-
res.status(500);
|
117
|
-
res.end();
|
118
|
-
}
|
119
|
-
});
|
120
|
-
|
121
81
|
this.addMiddleware();
|
122
82
|
|
123
83
|
app.get('/web/oauth/callback', (req, res) => {
|
@@ -297,7 +257,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
297
257
|
}
|
298
258
|
|
299
259
|
async shellHandler(req: Request, res: Response) {
|
300
|
-
const isUpgrade = req.headers
|
260
|
+
const isUpgrade = isConnectionUpgrade(req.headers);
|
301
261
|
|
302
262
|
const end = (code: number, message: string) => {
|
303
263
|
if (isUpgrade) {
|
@@ -432,7 +392,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
432
392
|
handleRequestEndpoint(req: Request, res: Response, endpointRequest: HttpRequest, pluginData: HttpPluginData) {
|
433
393
|
const { pluginHost, pluginDevice } = pluginData;
|
434
394
|
const handler = this.getDevice<HttpRequestHandler>(pluginDevice._id);
|
435
|
-
if (handler.interfaces.includes(ScryptedInterface.EngineIOHandler) && req.headers
|
395
|
+
if (handler.interfaces.includes(ScryptedInterface.EngineIOHandler) && isConnectionUpgrade(req.headers) && req.headers.upgrade?.toLowerCase() === 'websocket') {
|
436
396
|
this.wss.handleUpgrade(req, req.socket, null, ws => {
|
437
397
|
console.log(ws);
|
438
398
|
});
|
@@ -2,7 +2,7 @@ import path from 'path';
|
|
2
2
|
import process from 'process';
|
3
3
|
import http from 'http';
|
4
4
|
import https from 'https';
|
5
|
-
import express from 'express';
|
5
|
+
import express, { Request } from 'express';
|
6
6
|
import bodyParser from 'body-parser';
|
7
7
|
import net from 'net';
|
8
8
|
import { ScryptedRuntime } from './runtime';
|
@@ -160,7 +160,7 @@ async function start() {
|
|
160
160
|
// only basic auth will fail with 401. it is up to the endpoints to manage
|
161
161
|
// lack of login from cookie auth.
|
162
162
|
|
163
|
-
const
|
163
|
+
const login_user_token = getSignedLoginUserToken(req);
|
164
164
|
if (login_user_token) {
|
165
165
|
const userTokenParts = login_user_token.split('#');
|
166
166
|
const username = userTokenParts[0];
|
@@ -353,8 +353,16 @@ async function start() {
|
|
353
353
|
});
|
354
354
|
});
|
355
355
|
|
356
|
+
const getLoginUserToken = (reqSecure: boolean) => {
|
357
|
+
return reqSecure ? 'login_user_token' : 'login_user_token_inseucre';
|
358
|
+
};
|
359
|
+
|
360
|
+
const getSignedLoginUserToken = (req: Request<any>): string => {
|
361
|
+
return req.signedCookies[getLoginUserToken(req.secure)];
|
362
|
+
};
|
363
|
+
|
356
364
|
app.get('/logout', (req, res) => {
|
357
|
-
res.clearCookie(
|
365
|
+
res.clearCookie(getLoginUserToken(req.secure));
|
358
366
|
res.send({});
|
359
367
|
});
|
360
368
|
|
@@ -398,13 +406,11 @@ async function start() {
|
|
398
406
|
}
|
399
407
|
|
400
408
|
const login_user_token = `${username}#${timestamp}`;
|
401
|
-
res.cookie(
|
409
|
+
res.cookie(getLoginUserToken(req.secure), login_user_token, {
|
402
410
|
maxAge,
|
403
|
-
|
404
|
-
// secure: true,
|
411
|
+
secure: req.secure,
|
405
412
|
signed: true,
|
406
413
|
httpOnly: true,
|
407
|
-
sameSite: 'none',
|
408
414
|
});
|
409
415
|
|
410
416
|
if (change_password) {
|
@@ -441,13 +447,11 @@ async function start() {
|
|
441
447
|
hasLogin = true;
|
442
448
|
|
443
449
|
const login_user_token = `${username}#${timestamp}`
|
444
|
-
res.cookie(
|
450
|
+
res.cookie(getLoginUserToken(req.secure), login_user_token, {
|
445
451
|
maxAge,
|
446
|
-
|
447
|
-
// secure: true,
|
452
|
+
secure: req.secure,
|
448
453
|
signed: true,
|
449
454
|
httpOnly: true,
|
450
|
-
sameSite: 'none',
|
451
455
|
});
|
452
456
|
|
453
457
|
res.send({
|
@@ -484,7 +488,7 @@ async function start() {
|
|
484
488
|
return;
|
485
489
|
}
|
486
490
|
|
487
|
-
const
|
491
|
+
const login_user_token = getSignedLoginUserToken(req);
|
488
492
|
if (!login_user_token) {
|
489
493
|
res.send({
|
490
494
|
error: 'Not logged in.',
|
package/src/services/plugin.ts
CHANGED
@@ -51,26 +51,20 @@ export class PluginComponent {
|
|
51
51
|
this.scrypted.getDevice(id);
|
52
52
|
await this.scrypted.devices[id]?.handler?.ensureProxy();
|
53
53
|
}
|
54
|
-
async getMixins(id: string) {
|
55
|
-
console.warn('legacy use of getMixins, use the mixins property');
|
56
|
-
const pluginDevice = this.scrypted.findPluginDeviceById(id);
|
57
|
-
return getState(pluginDevice, ScryptedInterfaceProperty.mixins) || [];
|
58
|
-
}
|
59
54
|
async getIdForPluginId(pluginId: string) {
|
60
55
|
return this.scrypted.findPluginDevice(pluginId)?._id;
|
61
56
|
}
|
62
57
|
async getIdForNativeId(pluginId: string, nativeId: ScryptedNativeId) {
|
63
58
|
return this.scrypted.findPluginDevice(pluginId, nativeId)?._id;
|
64
59
|
}
|
60
|
+
/**
|
61
|
+
* @deprecated available as device.pluginId now.
|
62
|
+
* Remove at some point after core/ui rolls out 6/20/2022.
|
63
|
+
*/
|
65
64
|
async getPluginId(id: string) {
|
66
65
|
const pluginDevice = this.scrypted.findPluginDeviceById(id);
|
67
66
|
return pluginDevice.pluginId;
|
68
67
|
}
|
69
|
-
async getPluginProcessId(pluginId: string) {
|
70
|
-
if (this.scrypted.plugins[pluginId]?.worker?.killed)
|
71
|
-
return 'killed';
|
72
|
-
return this.scrypted.plugins[pluginId]?.worker?.pid;
|
73
|
-
}
|
74
68
|
async reload(pluginId: string) {
|
75
69
|
const plugin = await this.scrypted.datastore.tryGet(Plugin, pluginId);
|
76
70
|
await this.scrypted.runPlugin(plugin);
|
package/src/threading.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import worker_threads from 'worker_threads';
|
2
2
|
import { getEvalSource, RpcPeer } from './rpc';
|
3
3
|
import v8 from 'v8';
|
4
|
+
import vm from 'vm';
|
4
5
|
|
5
6
|
export async function newThread<T>(thread: () => Promise<T>): Promise<T>;
|
6
7
|
export async function newThread<V, T>(params: V, thread: (params: V) => Promise<T>): Promise<T>;
|
@@ -28,22 +29,22 @@ export async function newThread<T>(...args: any[]): Promise<T> {
|
|
28
29
|
const g = global as any;
|
29
30
|
g[customRequire] = g.require;
|
30
31
|
}
|
31
|
-
const v8 = global.require('v8');
|
32
|
-
const worker_threads = global.require('worker_threads');
|
33
|
-
const vm = global.require('vm');
|
34
|
-
const mainPeer = new RpcPeer('thread', 'main', (message: any, reject: any) => {
|
32
|
+
const thread_v8: typeof v8 = global.require('v8');
|
33
|
+
const thread_worker_threads: typeof worker_threads = global.require('worker_threads');
|
34
|
+
const thread_vm: typeof vm = global.require('vm');
|
35
|
+
const mainPeer: RpcPeer = new RpcPeer('thread', 'main', (message: any, reject: any) => {
|
35
36
|
try {
|
36
|
-
|
37
|
+
thread_worker_threads.parentPort.postMessage(thread_v8.serialize(message));
|
37
38
|
}
|
38
39
|
catch (e) {
|
39
40
|
reject?.(e);
|
40
41
|
}
|
41
42
|
});
|
42
43
|
mainPeer.transportSafeArgumentTypes.add(Buffer.name);
|
43
|
-
|
44
|
+
thread_worker_threads.parentPort.on('message', (message: any) => mainPeer.handleMessage(thread_v8.deserialize(message)));
|
44
45
|
|
45
46
|
mainPeer.params.eval = async (script: string, moduleNames: string[], paramNames: string[], ...paramValues: any[]) => {
|
46
|
-
const f =
|
47
|
+
const f = thread_vm.compileFunction(`return (${script})`, paramNames, {
|
47
48
|
filename: 'script.js',
|
48
49
|
});
|
49
50
|
const params: any = {};
|
@@ -92,31 +93,3 @@ export async function newThread<T>(...args: any[]): Promise<T> {
|
|
92
93
|
worker.terminate();
|
93
94
|
}
|
94
95
|
}
|
95
|
-
|
96
|
-
async function test() {
|
97
|
-
const foo = 5;
|
98
|
-
const bar = 6;
|
99
|
-
|
100
|
-
console.log(await newThread({
|
101
|
-
foo, bar,
|
102
|
-
}, async () => {
|
103
|
-
return foo + bar;
|
104
|
-
}));
|
105
|
-
|
106
|
-
|
107
|
-
console.log(await newThread({
|
108
|
-
foo, bar,
|
109
|
-
}, async ({foo,bar}) => {
|
110
|
-
return foo + bar;
|
111
|
-
}));
|
112
|
-
|
113
|
-
const sayHelloInMainThread = () => console.log('hello! main thread:', worker_threads.isMainThread);
|
114
|
-
await newThread({
|
115
|
-
sayHelloInMainThread,
|
116
|
-
}, async () => {
|
117
|
-
sayHelloInMainThread();
|
118
|
-
})
|
119
|
-
}
|
120
|
-
|
121
|
-
// if (true)
|
122
|
-
// test();
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import net from 'net';
|
2
|
+
import { listenZeroSingleClient } from "../src/listen-zero";
|
3
|
+
import { createDuplexRpcPeer } from "../src/rpc-serializer";
|
4
|
+
|
5
|
+
async function test() {
|
6
|
+
const { port, clientPromise } = await listenZeroSingleClient();
|
7
|
+
|
8
|
+
|
9
|
+
const n1 = net.connect({
|
10
|
+
port,
|
11
|
+
host: '127.0.0.1',
|
12
|
+
});
|
13
|
+
|
14
|
+
const n2 = await clientPromise;
|
15
|
+
console.log('connected');
|
16
|
+
|
17
|
+
const p1 = createDuplexRpcPeer('p1', 'p2', n1, n1);
|
18
|
+
const p2 = createDuplexRpcPeer('p2', 'p1', n2, n2);
|
19
|
+
|
20
|
+
p1.params.test = () => console.log('p1 test');
|
21
|
+
p2.params.test = () => console.log('p2 test');
|
22
|
+
|
23
|
+
await (await p1.getParam('test'))();
|
24
|
+
await (await p2.getParam('test'))();
|
25
|
+
|
26
|
+
n1.destroy();
|
27
|
+
n2.destroy();
|
28
|
+
}
|
29
|
+
|
30
|
+
test();
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import worker_threads from 'worker_threads';
|
2
|
+
import { newThread } from "../src/threading";
|
3
|
+
|
4
|
+
async function test() {
|
5
|
+
const foo = 5;
|
6
|
+
const bar = 6;
|
7
|
+
|
8
|
+
console.log(await newThread({
|
9
|
+
foo, bar,
|
10
|
+
}, async () => {
|
11
|
+
return foo + bar;
|
12
|
+
}));
|
13
|
+
|
14
|
+
|
15
|
+
console.log(await newThread({
|
16
|
+
foo, bar,
|
17
|
+
}, async ({ foo, bar }) => {
|
18
|
+
return foo + bar;
|
19
|
+
}));
|
20
|
+
|
21
|
+
const sayHelloInMainThread = () => console.log('hello! main thread:', worker_threads.isMainThread);
|
22
|
+
await newThread({
|
23
|
+
sayHelloInMainThread,
|
24
|
+
}, async () => {
|
25
|
+
sayHelloInMainThread();
|
26
|
+
})
|
27
|
+
}
|
28
|
+
|
29
|
+
test();
|