@scrypted/server 0.1.15 → 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.

Files changed (39) hide show
  1. package/dist/http-interfaces.js +11 -0
  2. package/dist/http-interfaces.js.map +1 -1
  3. package/dist/plugin/media.js +57 -34
  4. package/dist/plugin/media.js.map +1 -1
  5. package/dist/plugin/plugin-host.js +6 -4
  6. package/dist/plugin/plugin-host.js.map +1 -1
  7. package/dist/plugin/plugin-http.js +1 -1
  8. package/dist/plugin/plugin-http.js.map +1 -1
  9. package/dist/plugin/plugin-remote-worker.js +118 -7
  10. package/dist/plugin/plugin-remote-worker.js.map +1 -1
  11. package/dist/plugin/plugin-remote.js +11 -81
  12. package/dist/plugin/plugin-remote.js.map +1 -1
  13. package/dist/plugin/runtime/node-fork-worker.js +11 -3
  14. package/dist/plugin/runtime/node-fork-worker.js.map +1 -1
  15. package/dist/plugin/socket-serializer.js +17 -0
  16. package/dist/plugin/socket-serializer.js.map +1 -0
  17. package/dist/rpc.js +1 -1
  18. package/dist/rpc.js.map +1 -1
  19. package/dist/runtime.js +3 -1
  20. package/dist/runtime.js.map +1 -1
  21. package/dist/scrypted-plugin-main.js +4 -1
  22. package/dist/scrypted-plugin-main.js.map +1 -1
  23. package/dist/scrypted-server-main.js +45 -8
  24. package/dist/scrypted-server-main.js.map +1 -1
  25. package/package.json +2 -2
  26. package/src/http-interfaces.ts +13 -0
  27. package/src/plugin/media.ts +66 -34
  28. package/src/plugin/plugin-api.ts +1 -0
  29. package/src/plugin/plugin-host.ts +6 -4
  30. package/src/plugin/plugin-http.ts +2 -2
  31. package/src/plugin/plugin-remote-worker.ts +138 -10
  32. package/src/plugin/plugin-remote.ts +14 -88
  33. package/src/plugin/runtime/node-fork-worker.ts +11 -3
  34. package/src/plugin/runtime/runtime-worker.ts +1 -1
  35. package/src/plugin/socket-serializer.ts +15 -0
  36. package/src/rpc.ts +1 -1
  37. package/src/runtime.ts +4 -1
  38. package/src/scrypted-plugin-main.ts +4 -1
  39. package/src/scrypted-server-main.ts +51 -9
@@ -1,16 +1,22 @@
1
1
  import { DeviceManager, ScryptedNativeId, ScryptedStatic, SystemManager } from '@scrypted/types';
2
+ import AdmZip from 'adm-zip';
2
3
  import { Console } from 'console';
4
+ import fs from 'fs';
5
+ import { Volume } from 'memfs';
3
6
  import net from 'net';
7
+ import path from 'path';
4
8
  import { install as installSourceMapSupport } from 'source-map-support';
5
9
  import { PassThrough } from 'stream';
6
10
  import { RpcMessage, RpcPeer } from '../rpc';
7
11
  import { MediaManagerImpl } from './media';
8
- import { PluginAPI } from './plugin-api';
12
+ import { PluginAPI, PluginRemoteLoadZipOptions } from './plugin-api';
9
13
  import { installOptionalDependencies } from './plugin-npm-dependencies';
10
- import { attachPluginRemote, PluginReader } from './plugin-remote';
14
+ import { attachPluginRemote, PluginReader, setupPluginRemote } from './plugin-remote';
11
15
  import { createREPLServer } from './plugin-repl';
16
+ import { NodeThreadWorker } from './runtime/node-thread-worker';
17
+ const { link } = require('linkfs');
12
18
 
13
- export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessage, reject?: (e: Error) => void) => void) {
19
+ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any) => void) {
14
20
  const peer = new RpcPeer('unknown', 'host', peerSend);
15
21
 
16
22
  let systemManager: SystemManager;
@@ -66,13 +72,18 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
66
72
  return pluginsPromise;
67
73
  }
68
74
 
75
+ const deviceConsoles = new Map<string, Console>();
69
76
  const getDeviceConsole = (nativeId?: ScryptedNativeId) => {
70
77
  // the the plugin console is simply the default console
71
78
  // and gets read from stderr/stdout.
72
79
  if (!nativeId)
73
80
  return console;
74
81
 
75
- return getConsole(async (stdout, stderr) => {
82
+ let ret = deviceConsoles.get(nativeId);
83
+ if (ret)
84
+ return ret;
85
+
86
+ ret = getConsole(async (stdout, stderr) => {
76
87
  const connect = async () => {
77
88
  const plugins = await getPlugins();
78
89
  const port = await plugins.getRemoteServicePort(peer.selfName, 'console-writer');
@@ -93,10 +104,25 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
93
104
  };
94
105
  connect();
95
106
  }, undefined, undefined);
107
+
108
+ deviceConsoles.set(nativeId, ret);
109
+ return ret;
96
110
  }
97
111
 
112
+ const mixinConsoles = new Map<string, Map<string, Console>>();
113
+
98
114
  const getMixinConsole = (mixinId: string, nativeId: ScryptedNativeId) => {
99
- return getConsole(async (stdout, stderr) => {
115
+ let nativeIdConsoles = mixinConsoles.get(nativeId);
116
+ if (!nativeIdConsoles) {
117
+ nativeIdConsoles = new Map();
118
+ mixinConsoles.set(nativeId, nativeIdConsoles);
119
+ }
120
+
121
+ let ret = nativeIdConsoles.get(mixinId);
122
+ if (ret)
123
+ return ret;
124
+
125
+ ret = getConsole(async (stdout, stderr) => {
100
126
  if (!mixinId) {
101
127
  return;
102
128
  }
@@ -147,6 +173,9 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
147
173
  }
148
174
  tryConnect();
149
175
  }, getDeviceConsole(nativeId), `[${systemManager.getDeviceById(mixinId)?.name}]`);
176
+
177
+ nativeIdConsoles.set(mixinId, ret);
178
+ return ret;
150
179
  }
151
180
 
152
181
  peer.getParam('updateStats').then((updateStats: (stats: any) => void) => {
@@ -183,10 +212,6 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
183
212
  api = _api;
184
213
  peer.selfName = pluginId;
185
214
  },
186
- onPluginReady: async (scrypted, params, plugin) => {
187
- replPort = createREPLServer(scrypted, params, plugin);
188
- postInstallSourceMapSupport(scrypted);
189
- },
190
215
  getPluginConsole,
191
216
  getDeviceConsole,
192
217
  getMixinConsole,
@@ -198,7 +223,59 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
198
223
  }
199
224
  throw new Error(`unknown service ${name}`);
200
225
  },
201
- async onLoadZip(pluginReader: PluginReader, packageJson: any) {
226
+ async onLoadZip(scrypted: ScryptedStatic, params: any, packageJson: any, zipData: Buffer | string, zipOptions?: PluginRemoteLoadZipOptions) {
227
+ let volume: any;
228
+ let pluginReader: PluginReader;
229
+ if (zipOptions?.unzippedPath && fs.existsSync(zipOptions?.unzippedPath)) {
230
+ volume = link(fs, ['', path.join(zipOptions.unzippedPath, 'fs')]);
231
+ pluginReader = name => {
232
+ const filename = path.join(zipOptions.unzippedPath, name);
233
+ if (!fs.existsSync(filename))
234
+ return;
235
+ return fs.readFileSync(filename);
236
+ };
237
+ }
238
+ else {
239
+ const admZip = new AdmZip(zipData);
240
+ volume = new Volume();
241
+ for (const entry of admZip.getEntries()) {
242
+ if (entry.isDirectory)
243
+ continue;
244
+ if (!entry.entryName.startsWith('fs/'))
245
+ continue;
246
+ const name = entry.entryName.substring('fs/'.length);
247
+ volume.mkdirpSync(path.dirname(name));
248
+ const data = entry.getData();
249
+ volume.writeFileSync(name, data);
250
+ }
251
+
252
+ pluginReader = name => {
253
+ const entry = admZip.getEntry(name);
254
+ if (!entry)
255
+ return;
256
+ return entry.getData();
257
+ }
258
+ }
259
+ zipData = undefined;
260
+
261
+ const pluginConsole = getPluginConsole?.();
262
+ params.console = pluginConsole;
263
+ params.require = (name: string) => {
264
+ if (name === 'fakefs' || (name === 'fs' && !packageJson.scrypted.realfs)) {
265
+ return volume;
266
+ }
267
+ if (name === 'realfs') {
268
+ return require('fs');
269
+ }
270
+ const module = require(name);
271
+ return module;
272
+ };
273
+ const window: any = {};
274
+ const exports: any = window;
275
+ window.exports = exports;
276
+ params.window = window;
277
+ params.exports = exports;
278
+
202
279
  const entry = pluginReader('main.nodejs.js.map')
203
280
  const map = entry?.toString();
204
281
 
@@ -234,6 +311,57 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
234
311
  };
235
312
 
236
313
  await installOptionalDependencies(getPluginConsole(), packageJson);
314
+
315
+ const main = pluginReader('main.nodejs.js');
316
+ pluginReader = undefined;
317
+ const script = main.toString();
318
+
319
+ scrypted.fork = async () => {
320
+ const ntw = new NodeThreadWorker(pluginId, {
321
+ env: process.env,
322
+ pluginDebug: undefined,
323
+ });
324
+ const threadPeer = new RpcPeer('main', 'thread', (message, reject) => ntw.send(message, reject));
325
+ threadPeer.params.updateStats = (stats: any) => {
326
+ // todo: merge.
327
+ // this.stats = stats;
328
+ }
329
+ ntw.setupRpcPeer(threadPeer);
330
+
331
+ const remote = await setupPluginRemote(threadPeer, api, pluginId, () => systemManager.getSystemState());
332
+ const forkOptions = Object.assign({}, zipOptions);
333
+ forkOptions.fork = true;
334
+ return remote.loadZip(packageJson, zipData, forkOptions)
335
+ }
336
+
337
+ try {
338
+ peer.evalLocal(script, zipOptions?.filename || '/plugin/main.nodejs.js', params);
339
+ pluginConsole?.log('plugin successfully loaded');
340
+
341
+ if (zipOptions?.fork) {
342
+ const fork = exports.fork;
343
+ const ret = await fork();
344
+ ret[RpcPeer.PROPERTY_JSON_DISABLE_SERIALIZATION] = true;
345
+ return ret;
346
+ }
347
+
348
+ let pluginInstance = exports.default;
349
+ // support exporting a plugin class, plugin main function,
350
+ // or a plugin instance
351
+ if (pluginInstance.toString().startsWith('class '))
352
+ pluginInstance = new pluginInstance();
353
+ if (typeof pluginInstance === 'function')
354
+ pluginInstance = await pluginInstance();
355
+
356
+ replPort = createREPLServer(scrypted, params, pluginInstance);
357
+ postInstallSourceMapSupport(scrypted);
358
+
359
+ return pluginInstance;
360
+ }
361
+ catch (e) {
362
+ pluginConsole?.error('plugin failed to start', e);
363
+ throw e;
364
+ }
237
365
  }
238
366
  }).then(scrypted => {
239
367
  systemManager = scrypted.systemManager;
@@ -1,15 +1,10 @@
1
- import AdmZip from 'adm-zip';
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
- const { link } = require('linkfs');
7
+ import { SystemManagerImpl } from './system';
13
8
 
14
9
  class DeviceLogger implements Logger {
15
10
  nativeId: ScryptedNativeId;
@@ -347,13 +342,12 @@ export interface PluginRemoteAttachOptions {
347
342
  getDeviceConsole?: (nativeId?: ScryptedNativeId) => Console;
348
343
  getPluginConsole?: () => Console;
349
344
  getMixinConsole?: (id: string, nativeId?: ScryptedNativeId) => Console;
350
- onLoadZip?: (pluginReader: PluginReader, packageJson: any) => Promise<void>;
345
+ onLoadZip?: (scrypted: ScryptedStatic, params: any, packageJson: any, zipData: Buffer | string, zipOptions?: PluginRemoteLoadZipOptions) => Promise<any>;
351
346
  onGetRemote?: (api: PluginAPI, pluginId: string) => Promise<void>;
352
- onPluginReady?: (scrypted: ScryptedStatic, params: any, plugin: any) => Promise<void>;
353
347
  }
354
348
 
355
349
  export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOptions): Promise<ScryptedStatic> {
356
- const { createMediaManager, getServicePort, getDeviceConsole, getMixinConsole, getPluginConsole } = options || {};
350
+ const { createMediaManager, getServicePort, getDeviceConsole, getMixinConsole } = options || {};
357
351
 
358
352
  if (!peer.constructorSerializerMap.get(Buffer))
359
353
  peer.addSerializer(Buffer, 'Buffer', new BufferSerializer());
@@ -367,7 +361,11 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
367
361
  const systemManager = new SystemManagerImpl();
368
362
  const deviceManager = new DeviceManagerImpl(systemManager, getDeviceConsole, getMixinConsole);
369
363
  const endpointManager = new EndpointManagerImpl();
370
- const mediaManager = await api.getMediaManager() || await createMediaManager(systemManager, deviceManager);
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);
371
369
  peer.params['mediaManager'] = mediaManager;
372
370
  const ioSockets: { [id: string]: WebSocketConnectCallbacks } = {};
373
371
 
@@ -382,6 +380,7 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
382
380
  endpointManager,
383
381
  mediaManager,
384
382
  log,
383
+ pluginHostAPI: api,
385
384
  }
386
385
 
387
386
  delete peer.params.getRemote;
@@ -472,50 +471,6 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
472
471
  },
473
472
 
474
473
  async loadZip(packageJson: any, zipData: Buffer | string, zipOptions?: PluginRemoteLoadZipOptions) {
475
- const pluginConsole = getPluginConsole?.();
476
-
477
- let volume: any;
478
- let pluginReader: PluginReader;
479
- if (zipOptions?.unzippedPath && fs.existsSync(zipOptions?.unzippedPath)) {
480
- volume = link(fs, ['', path.join(zipOptions.unzippedPath, 'fs')]);
481
- pluginReader = name => {
482
- const filename = path.join(zipOptions.unzippedPath, name);
483
- if (!fs.existsSync(filename))
484
- return;
485
- return fs.readFileSync(filename);
486
- };
487
- }
488
- else {
489
- const admZip = new AdmZip(zipData);
490
- volume = new Volume();
491
- for (const entry of admZip.getEntries()) {
492
- if (entry.isDirectory)
493
- continue;
494
- if (!entry.entryName.startsWith('fs/'))
495
- continue;
496
- const name = entry.entryName.substring('fs/'.length);
497
- volume.mkdirpSync(path.dirname(name));
498
- const data = entry.getData();
499
- volume.writeFileSync(name, data);
500
- }
501
-
502
- pluginReader = name => {
503
- const entry = admZip.getEntry(name);
504
- if (!entry)
505
- return;
506
- return entry.getData();
507
- }
508
- }
509
- zipData = undefined;
510
-
511
- await options?.onLoadZip?.(pluginReader, packageJson);
512
- const main = pluginReader('main.nodejs.js');
513
- pluginReader = undefined;
514
- const script = main.toString();
515
- const window: any = {};
516
- const exports: any = window;
517
- window.exports = exports;
518
-
519
474
 
520
475
  function websocketConnect(url: string, protocols: any, callbacks: WebSocketConnectCallbacks) {
521
476
  if (url.startsWith('io://') || url.startsWith('ws://')) {
@@ -535,18 +490,6 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
535
490
 
536
491
  const params: any = {
537
492
  __filename: undefined,
538
- exports,
539
- window,
540
- require: (name: string) => {
541
- if (name === 'fakefs' || (name === 'fs' && !packageJson.scrypted.realfs)) {
542
- return volume;
543
- }
544
- if (name === 'realfs') {
545
- return require('fs');
546
- }
547
- const module = require(name);
548
- return module;
549
- },
550
493
  deviceManager,
551
494
  systemManager,
552
495
  mediaManager,
@@ -555,29 +498,12 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
555
498
  localStorage,
556
499
  pluginHostAPI: api,
557
500
  WebSocket: createWebSocketClass(websocketConnect),
501
+ pluginRuntimeAPI: ret,
558
502
  };
559
503
 
560
- params.console = pluginConsole;
504
+ params.pluginRuntimeAPI = ret;
561
505
 
562
- try {
563
- peer.evalLocal(script, zipOptions?.filename || '/plugin/main.nodejs.js', params);
564
- pluginConsole?.log('plugin successfully loaded');
565
-
566
- let pluginInstance = exports.default;
567
- // support exporting a plugin class, plugin main function,
568
- // or a plugin instance
569
- if (pluginInstance.toString().startsWith('class '))
570
- pluginInstance = new pluginInstance();
571
- if (typeof pluginInstance === 'function')
572
- pluginInstance = await pluginInstance();
573
-
574
- await options?.onPluginReady?.(ret, params, pluginInstance);
575
- return pluginInstance;
576
- }
577
- catch (e) {
578
- pluginConsole?.error('plugin failed to start', e);
579
- throw e;
580
- }
506
+ return options.onLoadZip(ret, params, packageJson, zipData, zipOptions);
581
507
  },
582
508
  }
583
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, undefined, e => {
56
+ this.worker.send(message, serializationContext?.sendHandle, e => {
49
57
  if (e && reject)
50
58
  reject(e);
51
59
  });
@@ -23,7 +23,7 @@ export interface RuntimeWorker {
23
23
  on(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
24
24
  once(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
25
25
 
26
- send(message: RpcMessage, reject?: (e: Error) => void): void;
26
+ send(message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any): void;
27
27
 
28
28
  setupRpcPeer(peer: RpcPeer): void;
29
29
  }
@@ -0,0 +1,15 @@
1
+ import { RpcSerializer } from "../rpc";
2
+
3
+ export class SidebandSocketSerializer implements RpcSerializer {
4
+ serialize(value: any, serializationContext?: any) {
5
+ if (!serializationContext)
6
+ throw new Error('socket serialization context unavailable');
7
+ serializationContext.sendHandle = value;
8
+ }
9
+
10
+ deserialize(serialized: any, serializationContext?: any) {
11
+ if (!serializationContext)
12
+ throw new Error('socket deserialization context unavailable');
13
+ return serializationContext.sendHandle;
14
+ }
15
+ }
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
@@ -282,8 +282,11 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
282
282
  this.shellio.handleRequest(req, res);
283
283
  }
284
284
 
285
- async getEndpointPluginData(endpoint: string, isUpgrade: boolean, isEngineIOEndpoint: boolean): Promise<HttpPluginData> {
285
+ async getEndpointPluginData(req: Request, endpoint: string, isUpgrade: boolean, isEngineIOEndpoint: boolean): Promise<HttpPluginData> {
286
286
  const ret = await this.getPluginForEndpoint(endpoint);
287
+ if (req.url.indexOf('/engine.io/api') !== -1)
288
+ return ret;
289
+
287
290
  const { pluginDevice } = ret;
288
291
 
289
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, undefined, {
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
- app.all('*', async (req, res, next) => {
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
- (req as any).username = username;
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' : 'login_user_token_inseucre';
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,