@scrypted/server 0.0.106 → 0.0.110

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 (57) hide show
  1. package/dist/event-registry.js +2 -2
  2. package/dist/event-registry.js.map +1 -1
  3. package/dist/http-interfaces.js +2 -2
  4. package/dist/http-interfaces.js.map +1 -1
  5. package/dist/plugin/listen-zero.js +13 -1
  6. package/dist/plugin/listen-zero.js.map +1 -1
  7. package/dist/plugin/plugin-api.js +2 -2
  8. package/dist/plugin/plugin-api.js.map +1 -1
  9. package/dist/plugin/plugin-device.js +2 -1
  10. package/dist/plugin/plugin-device.js.map +1 -1
  11. package/dist/plugin/plugin-host-api.js +8 -7
  12. package/dist/plugin/plugin-host-api.js.map +1 -1
  13. package/dist/plugin/plugin-host.js +36 -87
  14. package/dist/plugin/plugin-host.js.map +1 -1
  15. package/dist/plugin/plugin-http.js +100 -0
  16. package/dist/plugin/plugin-http.js.map +1 -0
  17. package/dist/plugin/plugin-lazy-remote.js +73 -0
  18. package/dist/plugin/plugin-lazy-remote.js.map +1 -0
  19. package/dist/plugin/plugin-npm-dependencies.js +16 -16
  20. package/dist/plugin/plugin-npm-dependencies.js.map +1 -1
  21. package/dist/plugin/plugin-remote-websocket.js +40 -34
  22. package/dist/plugin/plugin-remote-websocket.js.map +1 -1
  23. package/dist/plugin/plugin-remote.js +35 -26
  24. package/dist/plugin/plugin-remote.js.map +1 -1
  25. package/dist/plugin/system.js +11 -3
  26. package/dist/plugin/system.js.map +1 -1
  27. package/dist/rpc.js +57 -27
  28. package/dist/rpc.js.map +1 -1
  29. package/dist/runtime.js +45 -111
  30. package/dist/runtime.js.map +1 -1
  31. package/dist/scrypted-main.js +3 -0
  32. package/dist/scrypted-main.js.map +1 -1
  33. package/dist/services/plugin.js +2 -2
  34. package/dist/services/plugin.js.map +1 -1
  35. package/dist/state.js +2 -8
  36. package/dist/state.js.map +1 -1
  37. package/package.json +2 -2
  38. package/python/plugin-remote.py +4 -4
  39. package/python/rpc.py +68 -26
  40. package/src/event-registry.ts +5 -5
  41. package/src/http-interfaces.ts +3 -3
  42. package/src/plugin/listen-zero.ts +13 -0
  43. package/src/plugin/plugin-api.ts +5 -5
  44. package/src/plugin/plugin-device.ts +3 -2
  45. package/src/plugin/plugin-host-api.ts +9 -9
  46. package/src/plugin/plugin-host.ts +40 -95
  47. package/src/plugin/plugin-http.ts +117 -0
  48. package/src/plugin/plugin-lazy-remote.ts +70 -0
  49. package/src/plugin/plugin-npm-dependencies.ts +19 -18
  50. package/src/plugin/plugin-remote-websocket.ts +55 -60
  51. package/src/plugin/plugin-remote.ts +45 -38
  52. package/src/plugin/system.ts +15 -6
  53. package/src/rpc.ts +66 -26
  54. package/src/runtime.ts +55 -128
  55. package/src/scrypted-main.ts +4 -0
  56. package/src/services/plugin.ts +2 -2
  57. package/src/state.ts +4 -12
package/src/runtime.ts CHANGED
@@ -4,9 +4,8 @@ import { ScryptedNativeId, Device, EngineIOHandler, HttpRequest, HttpRequestHand
4
4
  import { PluginDeviceProxyHandler } from './plugin/plugin-device';
5
5
  import { Plugin, PluginDevice, ScryptedAlert } from './db-types';
6
6
  import { getState, ScryptedStateManager, setState } from './state';
7
- import { Request, Response, Router } from 'express';
7
+ import { Request, Response } from 'express';
8
8
  import { createResponseInterface } from './http-interfaces';
9
- import bodyParser from 'body-parser';
10
9
  import http, { ServerResponse } from 'http';
11
10
  import https from 'https';
12
11
  import express from 'express';
@@ -15,7 +14,7 @@ import { getDisplayName, getDisplayRoom, getDisplayType, getProvidedNameOrDefaul
15
14
  import { URL } from "url";
16
15
  import qs from "query-string";
17
16
  import { PluginComponent } from './services/plugin';
18
- import { Server as WebSocketServer } from "ws";
17
+ import WebSocket, { Server as WebSocketServer } from "ws";
19
18
  import axios from 'axios';
20
19
  import tar from 'tar';
21
20
  import { once } from 'events';
@@ -30,6 +29,7 @@ import io from 'engine.io';
30
29
  import { spawn as ptySpawn } from 'node-pty';
31
30
  import rimraf from 'rimraf';
32
31
  import { getPluginVolume } from './plugin/plugin-volume';
32
+ import { PluginHttp } from './plugin/plugin-http';
33
33
 
34
34
  interface DeviceProxyPair {
35
35
  handler: PluginDeviceProxyHandler;
@@ -39,13 +39,17 @@ interface DeviceProxyPair {
39
39
  const MIN_SCRYPTED_CORE_VERSION = 'v0.0.146';
40
40
  const PLUGIN_DEVICE_STATE_VERSION = 2;
41
41
 
42
- export class ScryptedRuntime {
42
+ interface HttpPluginData {
43
+ pluginHost: PluginHost;
44
+ pluginDevice: PluginDevice
45
+ }
46
+
47
+ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
43
48
  datastore: Level;
44
49
  plugins: { [id: string]: PluginHost } = {};
45
50
  pluginDevices: { [id: string]: PluginDevice } = {};
46
51
  devices: { [id: string]: DeviceProxyPair } = {};
47
52
  stateManager = new ScryptedStateManager(this);
48
- app: Router;
49
53
  logger = new Logger(this, '', 'Scrypted');
50
54
  devicesLogger = this.logger.getLogger('device', 'Devices');
51
55
  wss = new WebSocketServer({ noServer: true });
@@ -55,30 +59,12 @@ export class ScryptedRuntime {
55
59
  });
56
60
 
57
61
  constructor(datastore: Level, insecure: http.Server, secure: https.Server, app: express.Application) {
62
+ super(app);
58
63
  this.datastore = datastore;
59
64
  this.app = app;
60
65
 
61
66
  app.disable('x-powered-by');
62
67
 
63
- app.all(['/endpoint/@:owner/:pkg/public/engine.io/*', '/endpoint/:pkg/public/engine.io/*'], (req, res) => {
64
- this.endpointHandler(req, res, true, true, this.handleEngineIOEndpoint.bind(this))
65
- });
66
-
67
- app.all(['/endpoint/@:owner/:pkg/engine.io/*', '/endpoint/@:owner/:pkg/engine.io/*'], (req, res) => {
68
- this.endpointHandler(req, res, false, true, this.handleEngineIOEndpoint.bind(this))
69
- });
70
-
71
- // stringify all http endpoints
72
- app.all(['/endpoint/@:owner/:pkg/public', '/endpoint/@:owner/:pkg/public/*', '/endpoint/:pkg', '/endpoint/:pkg/*'], bodyParser.text() as any);
73
-
74
- app.all(['/endpoint/@:owner/:pkg/public', '/endpoint/@:owner/:pkg/public/*', '/endpoint/:pkg/public', '/endpoint/:pkg/public/*'], (req, res) => {
75
- this.endpointHandler(req, res, true, false, this.handleRequestEndpoint.bind(this))
76
- });
77
-
78
- app.all(['/endpoint/@:owner/:pkg', '/endpoint/@:owner/:pkg/*', '/endpoint/:pkg', '/endpoint/:pkg/*'], (req, res) => {
79
- this.endpointHandler(req, res, false, false, this.handleRequestEndpoint.bind(this))
80
- });
81
-
82
68
  app.get('/web/oauth/callback', (req, res) => {
83
69
  this.oauthCallback(req, res);
84
70
  });
@@ -190,7 +176,7 @@ export class ScryptedRuntime {
190
176
  }
191
177
  }
192
178
 
193
- async getPluginForEndpoint(endpoint: string) {
179
+ async getPluginForEndpoint(endpoint: string): Promise<HttpPluginData> {
194
180
  let pluginHost = this.plugins[endpoint] ?? this.getPluginHostForDeviceId(endpoint);
195
181
  if (endpoint === '@scrypted/core') {
196
182
  // enforce a minimum version on @scrypted/core
@@ -254,110 +240,55 @@ export class ScryptedRuntime {
254
240
  this.shellio.handleRequest(req, res);
255
241
  }
256
242
 
257
- async endpointHandler(req: Request, res: Response, isPublicEndpoint: boolean, isEngineIOEndpoint: boolean,
258
- handler: (req: Request, res: Response, endpointRequest: HttpRequest, pluginHost: PluginHost, pluginDevice: PluginDevice) => void) {
259
-
260
- const isUpgrade = req.headers.connection?.toLowerCase() === 'upgrade';
261
-
262
- const end = (code: number, message: string) => {
263
- if (isUpgrade) {
264
- const socket = res.socket;
265
- socket.write(`HTTP/1.1 ${code} ${message}\r\n` +
266
- '\r\n');
267
- socket.destroy();
268
- }
269
- else {
270
- res.status(code);
271
- res.send(message);
272
- }
273
- };
274
-
275
- if (!isPublicEndpoint && !res.locals.username) {
276
- end(401, 'Not Authorized');
277
- return;
278
- }
279
-
280
- const { owner, pkg } = req.params;
281
- let endpoint = pkg;
282
- if (owner)
283
- endpoint = `@${owner}/${endpoint}`;
284
-
285
- const { pluginHost, pluginDevice } = await this.getPluginForEndpoint(endpoint);
243
+ async getEndpointPluginData(endpoint: string, isUpgrade: boolean, isEngineIOEndpoint: boolean): Promise<HttpPluginData> {
244
+ const ret = await this.getPluginForEndpoint(endpoint);
245
+ const { pluginDevice } = ret;
286
246
 
287
247
  // check if upgrade requests can be handled. must be websocket.
288
248
  if (isUpgrade) {
289
- if (req.headers.upgrade?.toLowerCase() !== 'websocket' || !pluginDevice?.state.interfaces.value.includes(ScryptedInterface.EngineIOHandler)) {
290
- end(404, 'Not Found');
249
+ if (!pluginDevice?.state.interfaces.value.includes(ScryptedInterface.EngineIOHandler)) {
291
250
  return;
292
251
  }
293
252
  }
294
253
  else {
295
254
  if (!isEngineIOEndpoint && !pluginDevice?.state.interfaces.value.includes(ScryptedInterface.HttpRequestHandler)) {
296
- end(404, 'Not Found');
297
255
  return;
298
256
  }
299
257
  }
300
258
 
301
- let rootPath = `/endpoint/${endpoint}`;
302
- if (isPublicEndpoint)
303
- rootPath += '/public'
304
-
305
- const body = req.body && typeof req.body !== 'string' ? JSON.stringify(req.body) : req.body;
259
+ return ret;
260
+ }
306
261
 
307
- const httpRequest: HttpRequest = {
308
- body,
309
- headers: req.headers,
310
- method: req.method,
311
- rootPath,
312
- url: req.url,
313
- isPublicEndpoint,
314
- username: res.locals.username,
315
- };
262
+ async handleWebSocket(endpoint: string, httpRequest: HttpRequest, ws: WebSocket, pluginData: HttpPluginData): Promise<void> {
263
+ const { pluginDevice } = pluginData;
316
264
 
317
- if (isEngineIOEndpoint && !isUpgrade && isPublicEndpoint) {
318
- res.header("Access-Control-Allow-Origin", '*');
265
+ const handler = this.getDevice<EngineIOHandler>(pluginDevice._id);
266
+ const id = 'ws-' + this.wsAtomic++;
267
+ const pluginHost = this.plugins[endpoint] ?? this.getPluginHostForDeviceId(endpoint);
268
+ if (!pluginHost) {
269
+ ws.close();
270
+ return;
319
271
  }
272
+ pluginHost.ws[id] = ws;
320
273
 
321
- if (!isEngineIOEndpoint && isUpgrade) {
322
- this.wss.handleUpgrade(req, req.socket, (req as any).upgradeHead, async (ws) => {
323
- try {
324
- const handler = this.getDevice<EngineIOHandler>(pluginDevice._id);
325
- const id = 'ws-' + this.wsAtomic++;
326
- const pluginHost = this.plugins[endpoint] ?? this.getPluginHostForDeviceId(endpoint);
327
- if (!pluginHost) {
328
- ws.close();
329
- return;
330
- }
331
- pluginHost.ws[id] = ws;
332
-
333
- ws.on('message', async (message) => {
334
- try {
335
- pluginHost.remote.ioEvent(id, 'message', message)
336
- }
337
- catch (e) {
338
- ws.close();
339
- }
340
- });
341
- ws.on('close', async (reason) => {
342
- try {
343
- pluginHost.remote.ioEvent(id, 'close');
344
- }
345
- catch (e) {
346
- }
347
- delete pluginHost.ws[id];
348
- });
349
-
350
- await handler.onConnection(httpRequest, `ws://${id}`);
351
- }
352
- catch (e) {
353
- console.error('websocket plugin error', e);
354
- ws.close();
355
- }
356
- });
357
- }
358
- else {
359
- handler(req, res, httpRequest, pluginHost, pluginDevice);
360
- }
274
+ ws.on('message', async (message) => {
275
+ try {
276
+ pluginHost.remote.ioEvent(id, 'message', message)
277
+ }
278
+ catch (e) {
279
+ ws.close();
280
+ }
281
+ });
282
+ ws.on('close', async (reason) => {
283
+ try {
284
+ pluginHost.remote.ioEvent(id, 'close');
285
+ }
286
+ catch (e) {
287
+ }
288
+ delete pluginHost.ws[id];
289
+ });
290
+
291
+ await handler.onConnection(httpRequest, `ws://${id}`);
361
292
  }
362
293
 
363
294
  async getComponent(componentId: string): Promise<any> {
@@ -381,7 +312,9 @@ export class ScryptedRuntime {
381
312
  }
382
313
  }
383
314
 
384
- async handleEngineIOEndpoint(req: Request, res: ServerResponse, endpointRequest: HttpRequest, pluginHost: PluginHost, pluginDevice: PluginDevice) {
315
+ async handleEngineIOEndpoint(req: Request, res: ServerResponse, endpointRequest: HttpRequest, pluginData: HttpPluginData) {
316
+ const { pluginHost, pluginDevice } = pluginData;
317
+
385
318
  (req as any).scrypted = {
386
319
  endpointRequest,
387
320
  pluginDevice,
@@ -392,21 +325,15 @@ export class ScryptedRuntime {
392
325
  pluginHost.io.handleRequest(req, res);
393
326
  }
394
327
 
395
- async handleRequestEndpoint(req: Request, res: Response, endpointRequest: HttpRequest, pluginHost: PluginHost, pluginDevice: PluginDevice) {
396
- try {
397
- const handler = this.getDevice<HttpRequestHandler>(pluginDevice._id);
398
- if (handler.interfaces.includes(ScryptedInterface.EngineIOHandler) && req.headers.connection === 'upgrade' && req.headers.upgrade?.toLowerCase() === 'websocket') {
399
- this.wss.handleUpgrade(req, req.socket, null, ws => {
400
- console.log(ws);
401
- });
402
- }
403
- handler.onRequest(endpointRequest, createResponseInterface(res, pluginHost));
404
- }
405
- catch (e) {
406
- res.status(500);
407
- res.send(e.toString());
408
- console.error(e);
328
+ async handleRequestEndpoint(req: Request, res: Response, endpointRequest: HttpRequest, pluginData: HttpPluginData) {
329
+ const { pluginHost, pluginDevice } = pluginData;
330
+ const handler = this.getDevice<HttpRequestHandler>(pluginDevice._id);
331
+ if (handler.interfaces.includes(ScryptedInterface.EngineIOHandler) && req.headers.connection === 'upgrade' && req.headers.upgrade?.toLowerCase() === 'websocket') {
332
+ this.wss.handleUpgrade(req, req.socket, null, ws => {
333
+ console.log(ws);
334
+ });
409
335
  }
336
+ handler.onRequest(endpointRequest, createResponseInterface(res, pluginHost.zip));
410
337
  }
411
338
 
412
339
  killPlugin(plugin: Plugin) {
@@ -116,6 +116,10 @@ else {
116
116
  const db = level(dbPath);
117
117
  await db.open();
118
118
 
119
+ if (process.env.SCRYPTED_RESET_ALL_USERS === 'true') {
120
+ await db.removeAll(ScryptedUser);
121
+ }
122
+
119
123
  let certSetting = await db.tryGet(Settings, 'certificate') as Settings;
120
124
 
121
125
  if (certSetting?.value?.version !== CURRENT_SELF_SIGNED_CERTIFICATE_VERSION) {
@@ -120,7 +120,7 @@ export class PluginComponent {
120
120
  console.log('done updating plugins');
121
121
  }
122
122
 
123
- async getRemoteServicePort(pluginId: string, name: string): Promise<number> {
123
+ async getRemoteServicePort(pluginId: string, name: string, ...args: any[]): Promise<number> {
124
124
  if (name === 'console') {
125
125
  const consoleServer = await this.scrypted.plugins[pluginId].consoleServer;
126
126
  return consoleServer.readPort;
@@ -129,6 +129,6 @@ export class PluginComponent {
129
129
  const consoleServer = await this.scrypted.plugins[pluginId].consoleServer;
130
130
  return consoleServer.writePort;
131
131
  }
132
- return this.scrypted.plugins[pluginId].remote.getServicePort(name);
132
+ return this.scrypted.plugins[pluginId].remote.getServicePort(name, ...args);
133
133
  }
134
134
  }
package/src/state.ts CHANGED
@@ -72,23 +72,15 @@ export class ScryptedStateManager extends EventRegistry {
72
72
  }
73
73
 
74
74
  updateDescriptor(device: PluginDevice) {
75
- for (const plugin of Object.values(this.scrypted.plugins)) {
76
- plugin.remote?.updateDeviceState(device._id, device.state);
77
- }
75
+ this.notify(device._id, undefined, ScryptedInterface.ScryptedDevice, undefined, device.state, true);
78
76
  }
79
77
 
80
78
  removeDevice(id: string) {
81
- for (const plugin of Object.values(this.scrypted.plugins)) {
82
- plugin.remote?.updateDeviceState(id, undefined);
83
- }
84
-
85
79
  this.notify(undefined, undefined, ScryptedInterface.ScryptedDevice, ScryptedInterfaceProperty.id, id, true);
86
80
  }
87
81
 
88
82
  notifyInterfaceEvent(device: PluginDevice, eventInterface: ScryptedInterface | string, value: any) {
89
- const eventTime = Date.now();
90
-
91
- this.notify(device?._id, eventTime, eventInterface, undefined, value, true);
83
+ this.notify(device?._id, Date.now(), eventInterface, undefined, value, true);
92
84
  }
93
85
 
94
86
  setState(id: string, property: string, value: any) {
@@ -107,7 +99,7 @@ export class ScryptedStateManager extends EventRegistry {
107
99
  return systemState;
108
100
  }
109
101
 
110
- listenDevice(id: string, options: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: object) => void): EventListenerRegister {
102
+ listenDevice(id: string, options: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: any) => void): EventListenerRegister {
111
103
  let { denoise, event, watch } = (options || {}) as EventListenerOptions;
112
104
  if (!event && typeof options === 'string')
113
105
  event = options as string;
@@ -132,7 +124,7 @@ export class ScryptedStateManager extends EventRegistry {
132
124
  }
133
125
 
134
126
  let lastData: any = undefined;
135
- let cb = (eventDetails: EventDetails, eventData: object) => {
127
+ let cb = (eventDetails: EventDetails, eventData: any) => {
136
128
  if (denoise && lastData === eventData)
137
129
  return;
138
130
  callback(eventDetails, eventData);