@scrypted/server 0.0.105 → 0.0.109

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 +2 -2
  2. package/dist/http-interfaces.js.map +1 -1
  3. package/dist/plugin/listen-zero.js +13 -1
  4. package/dist/plugin/listen-zero.js.map +1 -1
  5. package/dist/plugin/plugin-device.js +11 -2
  6. package/dist/plugin/plugin-device.js.map +1 -1
  7. package/dist/plugin/plugin-host-api.js +8 -7
  8. package/dist/plugin/plugin-host-api.js.map +1 -1
  9. package/dist/plugin/plugin-host.js +36 -87
  10. package/dist/plugin/plugin-host.js.map +1 -1
  11. package/dist/plugin/plugin-http.js +100 -0
  12. package/dist/plugin/plugin-http.js.map +1 -0
  13. package/dist/plugin/plugin-lazy-remote.js +73 -0
  14. package/dist/plugin/plugin-lazy-remote.js.map +1 -0
  15. package/dist/plugin/plugin-npm-dependencies.js +16 -16
  16. package/dist/plugin/plugin-npm-dependencies.js.map +1 -1
  17. package/dist/plugin/plugin-remote-websocket.js +40 -34
  18. package/dist/plugin/plugin-remote-websocket.js.map +1 -1
  19. package/dist/plugin/plugin-remote.js +35 -26
  20. package/dist/plugin/plugin-remote.js.map +1 -1
  21. package/dist/rpc.js +53 -26
  22. package/dist/rpc.js.map +1 -1
  23. package/dist/runtime.js +45 -111
  24. package/dist/runtime.js.map +1 -1
  25. package/dist/scrypted-main.js +3 -0
  26. package/dist/scrypted-main.js.map +1 -1
  27. package/dist/services/plugin.js +2 -2
  28. package/dist/services/plugin.js.map +1 -1
  29. package/dist/state.js +2 -8
  30. package/dist/state.js.map +1 -1
  31. package/package.json +2 -2
  32. package/python/plugin-remote.py +4 -4
  33. package/python/rpc.py +68 -26
  34. package/src/http-interfaces.ts +3 -3
  35. package/src/plugin/listen-zero.ts +13 -0
  36. package/src/plugin/plugin-api.ts +1 -1
  37. package/src/plugin/plugin-device.ts +11 -2
  38. package/src/plugin/plugin-host-api.ts +9 -8
  39. package/src/plugin/plugin-host.ts +40 -95
  40. package/src/plugin/plugin-http.ts +117 -0
  41. package/src/plugin/plugin-lazy-remote.ts +70 -0
  42. package/src/plugin/plugin-npm-dependencies.ts +19 -18
  43. package/src/plugin/plugin-remote-websocket.ts +55 -60
  44. package/src/plugin/plugin-remote.ts +45 -38
  45. package/src/rpc.ts +62 -25
  46. package/src/runtime.ts +55 -128
  47. package/src/scrypted-main.ts +4 -0
  48. package/src/services/plugin.ts +2 -2
  49. package/src/state.ts +2 -10
@@ -0,0 +1,117 @@
1
+ import { Request, Response, Router } from 'express';
2
+ import bodyParser from 'body-parser';
3
+ import { HttpRequest } from '@scrypted/sdk/types';
4
+ import WebSocket, { Server as WebSocketServer } from "ws";
5
+ import { ServerResponse } from 'http';
6
+
7
+ export abstract class PluginHttp<T> {
8
+ wss = new WebSocketServer({ noServer: true });
9
+
10
+ constructor(public app: Router) {
11
+ app.all(['/endpoint/@:owner/:pkg/public/engine.io/*', '/endpoint/:pkg/public/engine.io/*'], (req, res) => {
12
+ this.endpointHandler(req, res, true, true, this.handleEngineIOEndpoint.bind(this))
13
+ });
14
+
15
+ app.all(['/endpoint/@:owner/:pkg/engine.io/*', '/endpoint/@:owner/:pkg/engine.io/*'], (req, res) => {
16
+ this.endpointHandler(req, res, false, true, this.handleEngineIOEndpoint.bind(this))
17
+ });
18
+
19
+ // stringify all http endpoints
20
+ app.all(['/endpoint/@:owner/:pkg/public', '/endpoint/@:owner/:pkg/public/*', '/endpoint/:pkg', '/endpoint/:pkg/*'], bodyParser.text() as any);
21
+
22
+ app.all(['/endpoint/@:owner/:pkg/public', '/endpoint/@:owner/:pkg/public/*', '/endpoint/:pkg/public', '/endpoint/:pkg/public/*'], (req, res) => {
23
+ this.endpointHandler(req, res, true, false, this.handleRequestEndpoint.bind(this))
24
+ });
25
+
26
+ app.all(['/endpoint/@:owner/:pkg', '/endpoint/@:owner/:pkg/*', '/endpoint/:pkg', '/endpoint/:pkg/*'], (req, res) => {
27
+ this.endpointHandler(req, res, false, false, this.handleRequestEndpoint.bind(this))
28
+ });
29
+ }
30
+
31
+ abstract handleEngineIOEndpoint(req: Request, res: ServerResponse, endpointRequest: HttpRequest, pluginData: T): Promise<void>;
32
+ abstract handleRequestEndpoint(req: Request, res: Response, endpointRequest: HttpRequest, pluginData: T): Promise<void>;
33
+ abstract getEndpointPluginData(endpoint: string, isUpgrade: boolean, isEngineIOEndpoint: boolean): Promise<T>;
34
+ abstract handleWebSocket(endpoint: string, httpRequest: HttpRequest, ws: WebSocket, pluginData: T): Promise<void>;
35
+
36
+ async endpointHandler(req: Request, res: Response, isPublicEndpoint: boolean, isEngineIOEndpoint: boolean,
37
+ handler: (req: Request, res: Response, endpointRequest: HttpRequest, pluginData: T) => void) {
38
+
39
+ const isUpgrade = req.headers.connection?.toLowerCase() === 'upgrade';
40
+
41
+ const end = (code: number, message: string) => {
42
+ if (isUpgrade) {
43
+ const socket = res.socket;
44
+ socket.write(`HTTP/1.1 ${code} ${message}\r\n` +
45
+ '\r\n');
46
+ socket.destroy();
47
+ }
48
+ else {
49
+ res.status(code);
50
+ res.send(message);
51
+ }
52
+ };
53
+
54
+ if (!isPublicEndpoint && !res.locals.username) {
55
+ end(401, 'Not Authorized');
56
+ return;
57
+ }
58
+
59
+ const { owner, pkg } = req.params;
60
+ let endpoint = pkg;
61
+ if (owner)
62
+ endpoint = `@${owner}/${endpoint}`;
63
+
64
+ if (isUpgrade && req.headers.upgrade?.toLowerCase() !== 'websocket') {
65
+ end(404, 'Not Found');
66
+ return;
67
+ }
68
+
69
+ const pluginData = await this.getEndpointPluginData(endpoint, isUpgrade, isEngineIOEndpoint);
70
+ if (!pluginData) {
71
+ end(404, 'Not Found');
72
+ return;
73
+ }
74
+
75
+ let rootPath = `/endpoint/${endpoint}`;
76
+ if (isPublicEndpoint)
77
+ rootPath += '/public'
78
+
79
+ const body = req.body && typeof req.body !== 'string' ? JSON.stringify(req.body) : req.body;
80
+
81
+ const httpRequest: HttpRequest = {
82
+ body,
83
+ headers: req.headers,
84
+ method: req.method,
85
+ rootPath,
86
+ url: req.url,
87
+ isPublicEndpoint,
88
+ username: res.locals.username,
89
+ };
90
+
91
+ if (isEngineIOEndpoint && !isUpgrade && isPublicEndpoint) {
92
+ res.header("Access-Control-Allow-Origin", '*');
93
+ }
94
+
95
+ if (!isEngineIOEndpoint && isUpgrade) {
96
+ this.wss.handleUpgrade(req, req.socket, (req as any).upgradeHead, async (ws) => {
97
+ try {
98
+ await this.handleWebSocket(endpoint, httpRequest, ws, pluginData);
99
+ }
100
+ catch (e) {
101
+ console.error('websocket plugin error', e);
102
+ ws.close();
103
+ }
104
+ });
105
+ }
106
+ else {
107
+ try {
108
+ handler(req, res, httpRequest, pluginData);
109
+ }
110
+ catch (e) {
111
+ res.status(500);
112
+ res.send(e.toString());
113
+ console.error(e);
114
+ }
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,70 @@
1
+ import { ScryptedNativeId, SystemDeviceState } from '@scrypted/sdk/types'
2
+ import { PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
3
+
4
+ /**
5
+ * This remote is necessary as the host needs to create a remote synchronously
6
+ * in the constructor and immediately begin queueing commands.
7
+ * Warning: do not await in any of these methods unless necessary, otherwise
8
+ * execution order of state reporting may fail.
9
+ */
10
+ export class LazyRemote implements PluginRemote {
11
+ remote: PluginRemote;
12
+
13
+ constructor(public remotePromise: Promise<PluginRemote>, public remoteReadyPromise: Promise<PluginRemote>) {
14
+ this.remoteReadyPromise = (async () => {
15
+ this.remote = await remoteReadyPromise;
16
+ return this.remote;
17
+ })();
18
+ }
19
+
20
+ async loadZip(packageJson: any, zipData: Buffer, options?: PluginRemoteLoadZipOptions): Promise<any> {
21
+ if (!this.remote)
22
+ await this.remoteReadyPromise;
23
+ return this.remote.loadZip(packageJson, zipData, options);
24
+ }
25
+ async setSystemState(state: { [id: string]: { [property: string]: SystemDeviceState; }; }): Promise<void> {
26
+ if (!this.remote)
27
+ await this.remoteReadyPromise;
28
+ return this.remote.setSystemState(state);
29
+ }
30
+ async setNativeId(nativeId: ScryptedNativeId, id: string, storage: { [key: string]: any; }): Promise<void> {
31
+ if (!this.remote)
32
+ await this.remoteReadyPromise;
33
+ return this.remote.setNativeId(nativeId, id, storage);
34
+ }
35
+ async updateDeviceState(id: string, state: { [property: string]: SystemDeviceState; }): Promise<void> {
36
+ try {
37
+ if (!this.remote)
38
+ await this.remoteReadyPromise;
39
+ }
40
+ catch (e) {
41
+ return;
42
+ }
43
+ return this.remote.updateDeviceState(id, state);
44
+ }
45
+ async notify(id: string, eventTime: number, eventInterface: string, property: string, propertyState: SystemDeviceState, changed?: boolean): Promise<void> {
46
+ try {
47
+ if (!this.remote)
48
+ await this.remoteReadyPromise;
49
+ }
50
+ catch (e) {
51
+ return;
52
+ }
53
+ return this.remote.notify(id, eventTime, eventInterface, property, propertyState, changed);
54
+ }
55
+ async ioEvent(id: string, event: string, message?: any): Promise<void> {
56
+ if (!this.remote)
57
+ await this.remoteReadyPromise;
58
+ return this.remote.ioEvent(id, event, message);
59
+ }
60
+ async createDeviceState(id: string, setState: (property: string, value: any) => Promise<void>): Promise<any> {
61
+ if (!this.remote)
62
+ await this.remoteReadyPromise;
63
+ return this.remote.createDeviceState(id, setState);
64
+ }
65
+
66
+ async getServicePort(name: string, ...args: any[]): Promise<number> {
67
+ const remote = await this.remotePromise;
68
+ return remote.getServicePort(name, ...args);
69
+ }
70
+ }
@@ -3,14 +3,18 @@ import fs from 'fs';
3
3
  import child_process from 'child_process';
4
4
  import path from 'path';
5
5
  import { once } from 'events';
6
+ import process from 'process';
7
+ import mkdirp from "mkdirp";
6
8
 
7
9
  export async function installOptionalDependencies(console: Console, packageJson: any) {
8
10
  const pluginVolume = ensurePluginVolume(packageJson.name);
9
- const optPj = path.join(pluginVolume, 'package.json');
11
+ const nodePrefix = path.join(pluginVolume, `${process.platform}-${process.arch}`);
12
+ const packageJsonPath = path.join(nodePrefix, 'package.json');
13
+ const currentInstalledPackageJsonPath = path.join(nodePrefix, 'package.installed.json');
10
14
 
11
15
  let currentPackageJson: any;
12
16
  try {
13
- currentPackageJson = JSON.parse(fs.readFileSync(optPj).toString());
17
+ currentPackageJson = JSON.parse(fs.readFileSync(currentInstalledPackageJsonPath).toString());
14
18
  }
15
19
  catch (e) {
16
20
  }
@@ -34,21 +38,18 @@ export async function installOptionalDependencies(console: Console, packageJson:
34
38
  delete reduced.optionalDependencies;
35
39
  delete reduced.devDependencies;
36
40
 
37
- try {
38
- fs.writeFileSync(optPj, JSON.stringify(reduced));
39
-
40
- const cp = child_process.spawn('npm', ['--prefix', pluginVolume, 'install'], {
41
- cwd: pluginVolume,
42
- stdio: 'inherit',
43
- });
44
-
45
- await once(cp, 'exit');
46
- if (cp.exitCode !== 0)
47
- throw new Error('npm installation failed with exit code ' + cp.exitCode);
48
- }
49
- catch (e) {
50
- fs.rmSync(optPj);
51
- throw e;
52
- }
41
+ mkdirp.sync(nodePrefix);
42
+ fs.writeFileSync(packageJsonPath, JSON.stringify(reduced));
43
+
44
+ const cp = child_process.spawn('npm', ['--prefix', nodePrefix, 'install'], {
45
+ cwd: nodePrefix,
46
+ stdio: 'inherit',
47
+ });
48
+
49
+ await once(cp, 'exit');
50
+ if (cp.exitCode !== 0)
51
+ throw new Error('npm installation failed with exit code ' + cp.exitCode);
52
+
53
+ fs.writeFileSync(currentInstalledPackageJsonPath, JSON.stringify(reduced));
53
54
  console.log('native dependencies installed.');
54
55
  }
@@ -2,7 +2,7 @@ interface WebSocketEvent {
2
2
  type: string;
3
3
  reason?: string;
4
4
  message?: string;
5
- data?: string|ArrayBufferLike;
5
+ data?: string | ArrayBufferLike;
6
6
  source?: any;
7
7
  }
8
8
 
@@ -12,7 +12,7 @@ interface WebSocketEventListener {
12
12
 
13
13
  // @ts-ignore
14
14
  class WebSocketEventTarget {
15
- events: { [type: string]: WebSocketEventListener[]} = {};
15
+ events: { [type: string]: WebSocketEventListener[] } = {};
16
16
 
17
17
  dispatchEvent(event: WebSocketEvent) {
18
18
  const list = this.events[event.type];
@@ -53,32 +53,20 @@ function defineEventAttribute(p: any, type: string) {
53
53
  });
54
54
  }
55
55
 
56
- interface WebSocketEndCallback {
57
- (): void;
56
+ export interface WebSocketConnectCallbacks {
57
+ connect(e: Error, ws: WebSocketMethods): void;
58
+ end(): void;
59
+ error(e: Error): void;
60
+ data(data: string | ArrayBufferLike): void;
58
61
  }
59
62
 
60
- interface WebSocketErrorCallback {
61
- (e: Error): void;
63
+ export interface WebSocketConnect {
64
+ (url: string, protocols: string[], callbacks: WebSocketConnectCallbacks): void;
62
65
  }
63
66
 
64
- interface WebSocketDataCallback {
65
- (data: string | ArrayBufferLike): void;
66
- }
67
-
68
- interface WebSocketSend {
69
- (message: string|ArrayBufferLike): void;
70
- }
71
-
72
- interface WebSocketConnectCallback {
73
- (e: Error, ws: any, send: WebSocketSend): void;
74
- }
75
-
76
- interface WebSocketConnect {
77
- (url: string, protocols: string[],
78
- connect: WebSocketConnectCallback,
79
- end: WebSocketEndCallback,
80
- error: WebSocketErrorCallback,
81
- data: WebSocketDataCallback): void;
67
+ export interface WebSocketMethods {
68
+ send(message: string | ArrayBufferLike): void;
69
+ close(message: string): void;
82
70
  }
83
71
 
84
72
  export function createWebSocketClass(__websocketConnect: WebSocketConnect) {
@@ -88,8 +76,7 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect) {
88
76
  _url: string;
89
77
  _protocols: string[];
90
78
  readyState: number;
91
- send: (message: string|ArrayBufferLike) => void;
92
- _ws: any;
79
+ _ws: WebSocketMethods;
93
80
 
94
81
  constructor(url: string, protocols?: string[]) {
95
82
  super();
@@ -97,44 +84,52 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect) {
97
84
  this._protocols = protocols;
98
85
  this.readyState = 0;
99
86
 
100
- __websocketConnect(url, protocols, (e, ws, send) => {
101
- // connect
102
- if (e != null) {
87
+ __websocketConnect(url, protocols, {
88
+ connect: (e, ws) => {
89
+ // connect
90
+ if (e != null) {
91
+ this.dispatchEvent({
92
+ type: 'error',
93
+ message: e.toString(),
94
+ });
95
+ return;
96
+ }
97
+
98
+ this._ws = ws;
99
+ this.readyState = 1;
100
+ this.dispatchEvent({
101
+ type: 'open',
102
+ });
103
+ },
104
+ end: () => {
105
+ // end
106
+ this.readyState = 3;
107
+ this.dispatchEvent({
108
+ type: 'close',
109
+ reason: 'closed',
110
+ });
111
+ },
112
+ error: (e: Error) => {
113
+ // error
114
+ this.readyState = 3;
103
115
  this.dispatchEvent({
104
116
  type: 'error',
105
117
  message: e.toString(),
106
118
  });
107
- return;
119
+ },
120
+ data: (data: string | ArrayBufferLike) => {
121
+ // data
122
+ this.dispatchEvent({
123
+ type: 'message',
124
+ data: data,
125
+ source: this,
126
+ });
108
127
  }
128
+ })
129
+ }
109
130
 
110
- this._ws = ws;
111
- this.send = send;
112
- this.readyState = 1;
113
- this.dispatchEvent({
114
- type: 'open',
115
- });
116
- }, () => {
117
- // end
118
- this.readyState = 3;
119
- this.dispatchEvent({
120
- type: 'close',
121
- reason: 'closed',
122
- });
123
- }, (e: Error) => {
124
- // error
125
- this.readyState = 3;
126
- this.dispatchEvent({
127
- type: 'error',
128
- message: e.toString(),
129
- });
130
- }, (data: string | ArrayBufferLike) => {
131
- // data
132
- this.dispatchEvent({
133
- type: 'message',
134
- data: data,
135
- source: this,
136
- });
137
- });
131
+ send(message: string | ArrayBufferLike) {
132
+ this._ws.send(message);
138
133
  }
139
134
 
140
135
  get url() {
@@ -145,8 +140,8 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect) {
145
140
  return "";
146
141
  }
147
142
 
148
- close() {
149
- this._ws.close();
143
+ close(reason: string) {
144
+ this._ws.close(reason);
150
145
  }
151
146
  }
152
147
 
@@ -6,8 +6,7 @@ import { PluginAPI, PluginLogger, PluginRemote, PluginRemoteLoadZipOptions } fro
6
6
  import { SystemManagerImpl } from './system';
7
7
  import { RpcPeer, RPCResultError, PROPERTY_PROXY_ONEWAY_METHODS, PROPERTY_JSON_DISABLE_SERIALIZATION } from '../rpc';
8
8
  import { BufferSerializer } from './buffer-serializer';
9
- import { EventEmitter } from 'events';
10
- import { createWebSocketClass } from './plugin-remote-websocket';
9
+ import { createWebSocketClass, WebSocketConnectCallbacks, WebSocketMethods } from './plugin-remote-websocket';
11
10
 
12
11
  class DeviceLogger implements Logger {
13
12
  nativeId: ScryptedNativeId;
@@ -262,14 +261,7 @@ class StorageImpl implements Storage {
262
261
  }
263
262
  }
264
263
 
265
- interface WebSocketCallbacks {
266
- end: any;
267
- error: any;
268
- data: any;
269
- }
270
-
271
-
272
- export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId: string): Promise<PluginRemote> {
264
+ export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId: string, getSystemState: () => { [id: string]: { [property: string]: SystemDeviceState } }): Promise<PluginRemote> {
273
265
  try {
274
266
  // the host/remote connection can be from server to plugin (node to node),
275
267
  // core plugin to web (node to browser).
@@ -277,16 +269,46 @@ export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId:
277
269
  // but in plugin-host, mark Buffer as transport safe.
278
270
  peer.addSerializer(Buffer, 'Buffer', new BufferSerializer());
279
271
  const getRemote = await peer.getParam('getRemote');
280
- return await getRemote(api, pluginId);
272
+ const remote = await getRemote(api, pluginId);
273
+
274
+ await remote.setSystemState(getSystemState());
275
+ api.listen((id, eventDetails, eventData) => {
276
+ // ScryptedDevice events will be handled specially and repropagated by the remote.
277
+ if (eventDetails.eventInterface === ScryptedInterface.ScryptedDevice) {
278
+ if (eventDetails.property === ScryptedInterfaceProperty.id) {
279
+ // a change on the id property means device was deleted
280
+ remote.updateDeviceState(eventData, undefined);
281
+ }
282
+ else {
283
+ // a change on anything else is a descriptor update
284
+ remote.updateDeviceState(id, getSystemState()[id]);
285
+ }
286
+ return;
287
+ }
288
+
289
+ if (eventDetails.property) {
290
+ remote.notify(id, eventDetails.eventTime, eventDetails.eventInterface, eventDetails.property, getSystemState()[id]?.[eventDetails.property], eventDetails.changed);
291
+ }
292
+ else {
293
+ remote.notify(id, eventDetails.eventTime, eventDetails.eventInterface, eventDetails.property, eventData, eventDetails.changed);
294
+ }
295
+ });
296
+
297
+ return remote;
281
298
  }
282
299
  catch (e) {
283
300
  throw new RPCResultError(peer, 'error while retrieving PluginRemote', e);
284
301
  }
285
302
  }
286
303
 
304
+ export interface WebSocketCustomHandler {
305
+ id: string,
306
+ methods: WebSocketMethods;
307
+ }
308
+
287
309
  export interface PluginRemoteAttachOptions {
288
310
  createMediaManager?: (systemManager: SystemManager) => Promise<MediaManager>;
289
- getServicePort?: (name: string) => Promise<number>;
311
+ getServicePort?: (name: string, ...args: any[]) => Promise<number>;
290
312
  getDeviceConsole?: (nativeId?: ScryptedNativeId) => Console;
291
313
  getPluginConsole?: () => Console;
292
314
  getMixinConsole?: (id: string, nativeId?: ScryptedNativeId) => Console;
@@ -309,8 +331,9 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
309
331
  const systemManager = new SystemManagerImpl();
310
332
  const deviceManager = new DeviceManagerImpl(systemManager, getDeviceConsole, getMixinConsole);
311
333
  const endpointManager = new EndpointManagerImpl();
312
- const ioSockets: { [id: string]: WebSocketCallbacks } = {};
313
334
  const mediaManager = await api.getMediaManager() || await createMediaManager(systemManager);
335
+ peer.params['mediaManager'] = mediaManager;
336
+ const ioSockets: { [id: string]: WebSocketConnectCallbacks } = {};
314
337
 
315
338
  systemManager.api = api;
316
339
  deviceManager.api = api;
@@ -383,11 +406,11 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
383
406
  async updateDeviceState(id: string, state: { [property: string]: SystemDeviceState }) {
384
407
  if (!state) {
385
408
  delete systemManager.state[id];
386
- systemManager.events.notify(id, Date.now(), ScryptedInterface.ScryptedDevice, ScryptedInterfaceProperty.id, id, true);
409
+ systemManager.events.notify(undefined, undefined, ScryptedInterface.ScryptedDevice, ScryptedInterfaceProperty.id, id, true);
387
410
  }
388
411
  else {
389
412
  systemManager.state[id] = state;
390
- systemManager.events.notify(id, Date.now(), ScryptedInterface.ScryptedDevice, undefined, undefined, true);
413
+ systemManager.events.notify(id, undefined, ScryptedInterface.ScryptedDevice, undefined, state, true);
391
414
  }
392
415
  },
393
416
 
@@ -432,32 +455,16 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
432
455
  volume.writeFileSync(name, entry.getData());
433
456
  }
434
457
 
435
- function websocketConnect(url: string, protocols: any, connect: any, end: any, error: any, data: any) {
436
- if (url.startsWith('io://')) {
437
- const id = url.substring('io://'.length);
438
-
439
- ioSockets[id] = {
440
- data,
441
- error,
442
- end
443
- };
444
-
445
- connect(undefined, {
446
- close: () => api.ioClose(id),
447
- }, (message: string) => api.ioSend(id, message));
448
- }
449
- else if (url.startsWith('ws://')) {
450
- const id = url.substring('ws://'.length);
458
+ function websocketConnect(url: string, protocols: any, callbacks: WebSocketConnectCallbacks) {
459
+ if (url.startsWith('io://') || url.startsWith('ws://')) {
460
+ const id = url.substring('xx://'.length);
451
461
 
452
- ioSockets[id] = {
453
- data,
454
- error,
455
- end
456
- };
462
+ ioSockets[id] = callbacks;
457
463
 
458
- connect(undefined, {
464
+ callbacks.connect(undefined, {
459
465
  close: () => api.ioClose(id),
460
- }, (message: string) => api.ioSend(id, message));
466
+ send: (message: string) => api.ioSend(id, message),
467
+ });
461
468
  }
462
469
  else {
463
470
  throw new Error('unsupported websocket');