@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.

Files changed (46) hide show
  1. package/dist/listen-zero.js +38 -0
  2. package/dist/listen-zero.js.map +1 -0
  3. package/dist/plugin/media.js +1 -1
  4. package/dist/plugin/media.js.map +1 -1
  5. package/dist/plugin/plugin-console.js +1 -1
  6. package/dist/plugin/plugin-console.js.map +1 -1
  7. package/dist/plugin/plugin-host.js +33 -45
  8. package/dist/plugin/plugin-host.js.map +1 -1
  9. package/dist/plugin/plugin-http.js +7 -2
  10. package/dist/plugin/plugin-http.js.map +1 -1
  11. package/dist/plugin/plugin-npm-dependencies.js +3 -1
  12. package/dist/plugin/plugin-npm-dependencies.js.map +1 -1
  13. package/dist/plugin/plugin-repl.js +1 -1
  14. package/dist/plugin/plugin-repl.js.map +1 -1
  15. package/dist/plugin/runtime/node-fork-worker.js +8 -1
  16. package/dist/plugin/runtime/node-fork-worker.js.map +1 -1
  17. package/dist/rpc-serializer.js +121 -0
  18. package/dist/rpc-serializer.js.map +1 -0
  19. package/dist/runtime.js +2 -38
  20. package/dist/runtime.js.map +1 -1
  21. package/dist/scrypted-server-main.js +13 -11
  22. package/dist/scrypted-server-main.js.map +1 -1
  23. package/dist/services/plugin.js +4 -10
  24. package/dist/services/plugin.js.map +1 -1
  25. package/dist/threading.js +6 -28
  26. package/dist/threading.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/listen-zero.ts +34 -0
  29. package/src/plugin/media.ts +1 -1
  30. package/src/plugin/plugin-console.ts +1 -1
  31. package/src/plugin/plugin-host.ts +42 -43
  32. package/src/plugin/plugin-http.ts +8 -3
  33. package/src/plugin/plugin-npm-dependencies.ts +3 -1
  34. package/src/plugin/plugin-repl.ts +1 -1
  35. package/src/plugin/runtime/node-fork-worker.ts +8 -1
  36. package/src/plugin/runtime/runtime-worker.ts +2 -1
  37. package/src/rpc-serializer.ts +141 -0
  38. package/src/runtime.ts +3 -43
  39. package/src/scrypted-server-main.ts +16 -12
  40. package/src/services/plugin.ts +4 -10
  41. package/src/threading.ts +8 -35
  42. package/test/rpc-duplex-test.ts +30 -0
  43. package/test/threading-test.ts +29 -0
  44. package/dist/plugin/listen-zero.js +0 -23
  45. package/dist/plugin/listen-zero.js.map +0 -1
  46. 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.send(JSON.stringify({
138
- // @ts-expect-error
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
- let connected = true;
354
- const rpcPeer = new RpcPeer(`api/${this.pluginId}`, 'web', (message, reject, serializationContext) => {
355
- if (!connected) {
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
- let pendingSerializationContext: any = {};
349
+
368
350
  socket.on('message', data => {
369
351
  if (data.constructor === Buffer || data.constructor === ArrayBuffer) {
370
- pendingSerializationContext = pendingSerializationContext || {
371
- buffers: [],
372
- };
373
- const buffers: Buffer[] = pendingSerializationContext.buffers;
374
- buffers.push(Buffer.from(data));
375
- return;
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
- connected = false;
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.addSerializer(Buffer, 'Buffer', new SidebandBufferSerializer());
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.connection?.toLowerCase() === 'upgrade';
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 nodePrefix = path.join(pluginVolume, `${process.platform}-${process.arch}`);
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
 
@@ -1,4 +1,4 @@
1
- import { listenZero } from './listen-zero';
1
+ import { listenZero } from '../listen-zero';
2
2
  import { Server } from 'net';
3
3
  import { once } from 'events';
4
4
  import repl from 'repl';
@@ -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 => peer.handleMessage(message as any));
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.connection?.toLowerCase() === 'upgrade';
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.connection === 'upgrade' && req.headers.upgrade?.toLowerCase() === 'websocket') {
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 { login_user_token } = req.signedCookies;
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('login_user_token');
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('login_user_token', login_user_token, {
409
+ res.cookie(getLoginUserToken(req.secure), login_user_token, {
402
410
  maxAge,
403
- // enabling this will disable insecure http login...
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('login_user_token', login_user_token, {
450
+ res.cookie(getLoginUserToken(req.secure), login_user_token, {
445
451
  maxAge,
446
- // enabling this will disable insecure http login...
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 { login_user_token } = req.signedCookies;
491
+ const login_user_token = getSignedLoginUserToken(req);
488
492
  if (!login_user_token) {
489
493
  res.send({
490
494
  error: 'Not logged in.',
@@ -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
- worker_threads.parentPort.postMessage(v8.serialize(message));
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
- worker_threads.parentPort.on('message', (message: any) => mainPeer.handleMessage(v8.deserialize(message)));
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 = vm.compileFunction(`return (${script})`, paramNames, {
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();