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

Files changed (49) 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-npm-dependencies.js +5 -1
  10. package/dist/plugin/plugin-npm-dependencies.js.map +1 -1
  11. package/dist/plugin/plugin-remote-worker.js +118 -7
  12. package/dist/plugin/plugin-remote-worker.js.map +1 -1
  13. package/dist/plugin/plugin-remote.js +11 -81
  14. package/dist/plugin/plugin-remote.js.map +1 -1
  15. package/dist/plugin/runtime/node-fork-worker.js +11 -3
  16. package/dist/plugin/runtime/node-fork-worker.js.map +1 -1
  17. package/dist/plugin/runtime/python-worker.js +2 -1
  18. package/dist/plugin/runtime/python-worker.js.map +1 -1
  19. package/dist/plugin/socket-serializer.js +17 -0
  20. package/dist/plugin/socket-serializer.js.map +1 -0
  21. package/dist/rpc-serializer.js +23 -10
  22. package/dist/rpc-serializer.js.map +1 -1
  23. package/dist/rpc.js +1 -1
  24. package/dist/rpc.js.map +1 -1
  25. package/dist/runtime.js +26 -24
  26. package/dist/runtime.js.map +1 -1
  27. package/dist/scrypted-plugin-main.js +4 -1
  28. package/dist/scrypted-plugin-main.js.map +1 -1
  29. package/dist/scrypted-server-main.js +45 -8
  30. package/dist/scrypted-server-main.js.map +1 -1
  31. package/package.json +3 -3
  32. package/python/plugin-remote.py +28 -19
  33. package/src/http-interfaces.ts +13 -0
  34. package/src/plugin/media.ts +66 -34
  35. package/src/plugin/plugin-api.ts +1 -0
  36. package/src/plugin/plugin-host.ts +6 -4
  37. package/src/plugin/plugin-http.ts +2 -2
  38. package/src/plugin/plugin-npm-dependencies.ts +5 -1
  39. package/src/plugin/plugin-remote-worker.ts +138 -10
  40. package/src/plugin/plugin-remote.ts +14 -89
  41. package/src/plugin/runtime/node-fork-worker.ts +11 -3
  42. package/src/plugin/runtime/python-worker.ts +3 -1
  43. package/src/plugin/runtime/runtime-worker.ts +1 -1
  44. package/src/plugin/socket-serializer.ts +15 -0
  45. package/src/rpc-serializer.ts +30 -13
  46. package/src/rpc.ts +1 -1
  47. package/src/runtime.ts +32 -30
  48. package/src/scrypted-plugin-main.ts +4 -1
  49. package/src/scrypted-server-main.ts +51 -9
@@ -1,16 +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
- import _ from 'lodash';
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?: (pluginReader: PluginReader, packageJson: any) => Promise<void>;
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, getPluginConsole } = options || {};
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 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);
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.console = pluginConsole;
504
+ params.pluginRuntimeAPI = ret;
562
505
 
563
- try {
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, undefined, e => {
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
- this.worker = child_process.spawn('python3', args, {
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
+ }
@@ -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(readable, writable);
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(readable: Readable, writable: Writable) {
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
- const readMessages = () => {
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 = readable.read(5);
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 = readable.read(length - 1);
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(data.toString());
143
+ const message = JSON.parse(payload.toString());
125
144
  serializer.onMessageFinish(message);
126
145
  }
127
146
  else {
128
- serializer.onMessageBuffer(data);
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 { Level } from './level';
2
- import { PluginHost } from './plugin/plugin-host';
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 tar from 'tar';
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 { IOServer, IOServerSocket } from './io';
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, 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,