@scrypted/server 0.4.6 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @scrypted/server might be problematic. Click here for more details.

Files changed (49) hide show
  1. package/dist/db-types.js +1 -2
  2. package/dist/db-types.js.map +1 -1
  3. package/dist/plugin/acl.js +83 -0
  4. package/dist/plugin/acl.js.map +1 -0
  5. package/dist/plugin/plugin-api.js +28 -11
  6. package/dist/plugin/plugin-api.js.map +1 -1
  7. package/dist/plugin/plugin-device.js +4 -0
  8. package/dist/plugin/plugin-device.js.map +1 -1
  9. package/dist/plugin/plugin-host-api.js +0 -16
  10. package/dist/plugin/plugin-host-api.js.map +1 -1
  11. package/dist/plugin/plugin-host.js +13 -7
  12. package/dist/plugin/plugin-host.js.map +1 -1
  13. package/dist/plugin/plugin-http.js +1 -0
  14. package/dist/plugin/plugin-http.js.map +1 -1
  15. package/dist/plugin/plugin-remote-websocket.js +25 -14
  16. package/dist/plugin/plugin-remote-websocket.js.map +1 -1
  17. package/dist/plugin/plugin-remote-worker.js +3 -0
  18. package/dist/plugin/plugin-remote-worker.js.map +1 -1
  19. package/dist/plugin/plugin-remote.js +41 -8
  20. package/dist/plugin/plugin-remote.js.map +1 -1
  21. package/dist/rpc.js +37 -8
  22. package/dist/rpc.js.map +1 -1
  23. package/dist/runtime.js +44 -14
  24. package/dist/runtime.js.map +1 -1
  25. package/dist/scrypted-server-main.js +11 -11
  26. package/dist/scrypted-server-main.js.map +1 -1
  27. package/dist/services/plugin.js +1 -12
  28. package/dist/services/plugin.js.map +1 -1
  29. package/dist/services/users.js +46 -0
  30. package/dist/services/users.js.map +1 -0
  31. package/dist/usertoken.js +9 -5
  32. package/dist/usertoken.js.map +1 -1
  33. package/package.json +2 -2
  34. package/src/db-types.ts +2 -2
  35. package/src/plugin/acl.ts +104 -0
  36. package/src/plugin/plugin-api.ts +41 -25
  37. package/src/plugin/plugin-device.ts +6 -0
  38. package/src/plugin/plugin-host-api.ts +1 -20
  39. package/src/plugin/plugin-host.ts +21 -12
  40. package/src/plugin/plugin-http.ts +1 -0
  41. package/src/plugin/plugin-remote-websocket.ts +26 -17
  42. package/src/plugin/plugin-remote-worker.ts +3 -0
  43. package/src/plugin/plugin-remote.ts +49 -11
  44. package/src/rpc.ts +43 -9
  45. package/src/runtime.ts +48 -20
  46. package/src/scrypted-server-main.ts +11 -11
  47. package/src/services/plugin.ts +2 -12
  48. package/src/services/users.ts +43 -0
  49. package/src/usertoken.ts +13 -6
package/src/db-types.ts CHANGED
@@ -20,7 +20,7 @@ export class ScryptedUser extends ScryptedDocument {
20
20
  passwordHash: string;
21
21
  token: string;
22
22
  salt: string;
23
- restricted: boolean;
23
+ aclId: string;
24
24
  }
25
25
 
26
26
  export class ScryptedAlert extends ScryptedDocument {
@@ -28,7 +28,7 @@ export class ScryptedAlert extends ScryptedDocument {
28
28
  title: string;
29
29
  path: string;
30
30
  message: string;
31
- }``
31
+ }
32
32
 
33
33
  export class PluginDevice extends ScryptedDocument {
34
34
  constructor(id?: string) {
@@ -0,0 +1,104 @@
1
+ import { EventDetails, ScryptedInterface, ScryptedUserAccessControl } from "@scrypted/types";
2
+
3
+ /**
4
+ * Scrypted Access Controls allow selective reading of state, subscription to evemts,
5
+ * and invocation of methods.
6
+ * Everything else should be rejected.
7
+ */
8
+ export class AccessControls {
9
+ constructor(public acl: ScryptedUserAccessControl) {
10
+ }
11
+
12
+ deny(reason: string = 'User does not have permission') {
13
+ throw new Error(reason);
14
+ }
15
+
16
+ shouldRejectDevice(id: string) {
17
+ if (this.acl.devicesAccessControls === null)
18
+ return false;
19
+
20
+ if (!this.acl.devicesAccessControls)
21
+ return true;
22
+
23
+ const dacls = this.acl.devicesAccessControls.filter(dacl => dacl.id === id);
24
+ return !dacls.length;
25
+ }
26
+
27
+ shouldRejectProperty(id: string, property: string) {
28
+ if (this.acl.devicesAccessControls === null)
29
+ return false;
30
+
31
+ if (!this.acl.devicesAccessControls)
32
+ return true;
33
+
34
+ const dacls = this.acl.devicesAccessControls.filter(dacl => dacl.id === id);
35
+
36
+ for (const dacl of dacls) {
37
+ if (!dacl.properties || dacl.properties.includes(property))
38
+ return false;
39
+ }
40
+
41
+ return true;
42
+ }
43
+
44
+ shouldRejectEvent(id: string, eventDetails: EventDetails) {
45
+ if (this.acl.devicesAccessControls === null)
46
+ return false;
47
+
48
+ if (!this.acl.devicesAccessControls)
49
+ return true;
50
+
51
+ const dacls = this.acl.devicesAccessControls.filter(dacl => dacl.id === id);
52
+
53
+ const { property } = eventDetails;
54
+ if (property) {
55
+ for (const dacl of dacls) {
56
+ if (!dacl.properties || dacl.properties.includes(property))
57
+ return false;
58
+ }
59
+ }
60
+
61
+ const { eventInterface } = eventDetails;
62
+
63
+ for (const dacl of dacls) {
64
+ if (!dacl.interfaces || dacl.interfaces.includes(eventInterface))
65
+ return false;
66
+ }
67
+
68
+ return true;
69
+ }
70
+
71
+ shouldRejectInterface(id: string, scryptedInterface: ScryptedInterface) {
72
+ if (this.acl.devicesAccessControls === null)
73
+ return false;
74
+
75
+ if (!this.acl.devicesAccessControls)
76
+ return true;
77
+
78
+ const dacls = this.acl.devicesAccessControls.filter(dacl => dacl.id === id);
79
+
80
+ for (const dacl of dacls) {
81
+ if (!dacl.interfaces || dacl.interfaces.includes(scryptedInterface))
82
+ return false;
83
+ }
84
+
85
+ return true;
86
+ }
87
+
88
+ shouldRejectMethod(id: string, method: string) {
89
+ if (this.acl.devicesAccessControls === null)
90
+ return false;
91
+
92
+ if (!this.acl.devicesAccessControls)
93
+ return true;
94
+
95
+ const dacls = this.acl.devicesAccessControls.filter(dacl => dacl.id === id);
96
+
97
+ for (const dacl of dacls) {
98
+ if (!dacl.methods || dacl.methods.includes(method))
99
+ return false;
100
+ }
101
+
102
+ return true;
103
+ }
104
+ }
@@ -1,4 +1,5 @@
1
- import type { ScryptedNativeId, ScryptedDevice, Device, DeviceManifest, EventDetails, EventListenerOptions, EventListenerRegister, ScryptedInterfaceProperty, MediaObject, SystemDeviceState, MediaManager, HttpRequest, ScryptedInterfaceDescriptor } from '@scrypted/types'
1
+ import type { Device, DeviceManifest, EventDetails, EventListenerOptions, EventListenerRegister, MediaManager, MediaObject, ScryptedDevice, ScryptedInterfaceDescriptor, ScryptedInterfaceProperty, ScryptedNativeId, SystemDeviceState } from '@scrypted/types';
2
+ import { AccessControls } from './acl';
2
3
 
3
4
  export interface PluginLogger {
4
5
  log(level: string, message: string): Promise<void>;
@@ -14,7 +15,7 @@ export interface PluginAPI {
14
15
  onDeviceEvent(nativeId: ScryptedNativeId, eventInterface: string, eventData?: any): Promise<void>;
15
16
  onMixinEvent(id: string, nativeId: ScryptedNativeId, eventInterface: string, eventData?: any): Promise<void>;
16
17
  onDeviceRemoved(nativeId: string): Promise<void>;
17
- setStorage(nativeId: string, storage: {[key: string]: any}): Promise<void>;
18
+ setStorage(nativeId: string, storage: { [key: string]: any }): Promise<void>;
18
19
 
19
20
  getDeviceById(id: string): Promise<ScryptedDevice>;
20
21
  setDeviceProperty(id: string, property: ScryptedInterfaceProperty, value: any): Promise<void>;
@@ -22,14 +23,9 @@ export interface PluginAPI {
22
23
  listen(EventListener: (id: string, eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister>;
23
24
  listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister>;
24
25
 
25
- ioClose(id: string): Promise<void>;
26
- ioSend(id: string, message: string): Promise<void>;
27
-
28
- deliverPush(endpoint: string, request: HttpRequest): Promise<void>;
29
-
30
26
  getLogger(nativeId: ScryptedNativeId): Promise<PluginLogger>;
31
27
 
32
- getComponent(id: string): Promise<any>;
28
+ getComponent(id: string): Promise<any>;
33
29
 
34
30
  getMediaManager(): Promise<MediaManager>;
35
31
 
@@ -69,63 +65,82 @@ export class PluginAPIManagedListeners {
69
65
  }
70
66
 
71
67
  export class PluginAPIProxy extends PluginAPIManagedListeners implements PluginAPI {
68
+ acl: AccessControls;
69
+
72
70
  constructor(public api: PluginAPI, public mediaManager?: MediaManager) {
73
71
  super();
74
72
  }
75
73
 
76
74
  setScryptedInterfaceDescriptors(typesVersion: string, descriptors: { [scryptedInterface: string]: ScryptedInterfaceDescriptor }): Promise<void> {
75
+ this.acl?.deny();
77
76
  return this.api.setScryptedInterfaceDescriptors(typesVersion, descriptors);
78
77
  }
79
78
 
80
79
  setState(nativeId: ScryptedNativeId, key: string, value: any): Promise<void> {
80
+ this.acl?.deny();
81
81
  return this.api.setState(nativeId, key, value);
82
82
  }
83
83
  onDevicesChanged(deviceManifest: DeviceManifest): Promise<void> {
84
+ this.acl?.deny();
84
85
  return this.api.onDevicesChanged(deviceManifest);
85
86
  }
86
87
  onDeviceDiscovered(device: Device): Promise<string> {
88
+ this.acl?.deny();
87
89
  return this.api.onDeviceDiscovered(device);
88
90
  }
89
91
  onDeviceEvent(nativeId: ScryptedNativeId, eventInterface: any, eventData?: any): Promise<void> {
92
+ this.acl?.deny();
90
93
  return this.api.onDeviceEvent(nativeId, eventInterface, eventData);
91
94
  }
92
95
  onMixinEvent(id: string, nativeId: ScryptedNativeId, eventInterface: string, eventData?: any): Promise<void> {
96
+ this.acl?.deny();
93
97
  return this.api.onMixinEvent(id, nativeId, eventInterface, eventData);
94
98
  }
95
99
  onDeviceRemoved(nativeId: string): Promise<void> {
100
+ this.acl?.deny();
96
101
  return this.api.onDeviceRemoved(nativeId);
97
102
  }
98
103
  setStorage(nativeId: ScryptedNativeId, storage: { [key: string]: any; }): Promise<void> {
104
+ this.acl?.deny();
99
105
  return this.api.setStorage(nativeId, storage);
100
106
  }
101
107
  getDeviceById(id: string): Promise<ScryptedDevice> {
108
+ if (this.acl?.shouldRejectDevice(id))
109
+ return;
102
110
  return this.api.getDeviceById(id);
103
111
  }
104
112
  setDeviceProperty(id: string, property: ScryptedInterfaceProperty, value: any): Promise<void> {
113
+ this.acl?.deny();
105
114
  return this.api.setDeviceProperty(id, property, value);
106
115
  }
107
116
  removeDevice(id: string): Promise<void> {
117
+ this.acl?.deny();
108
118
  return this.api.removeDevice(id);
109
119
  }
110
120
  async listen(callback: (id: string, eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister> {
111
- return this.manageListener(await this.api.listen(callback));
121
+ if (!this.acl)
122
+ return this.manageListener(await this.api.listen(callback));
123
+
124
+ return this.manageListener(await this.api.listen((id, details, data) => {
125
+ if (!this.acl.shouldRejectEvent(id, details))
126
+ callback(id, details, data);
127
+ }));
112
128
  }
113
129
  async listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister> {
114
- return this.manageListener(await this.api.listenDevice(id, event, callback));
115
- }
116
- ioClose(id: string): Promise<void> {
117
- return this.api.ioClose(id);
118
- }
119
- ioSend(id: string, message: string): Promise<void> {
120
- return this.api.ioSend(id, message);
121
- }
122
- deliverPush(endpoint: string, request: HttpRequest): Promise<void> {
123
- return this.api.deliverPush(endpoint, request);
130
+ if (!this.acl)
131
+ return this.manageListener(await this.api.listenDevice(id, event, callback));
132
+
133
+ return this.manageListener(await this.api.listenDevice(id, event, (details, data) => {
134
+ if (!this.acl.shouldRejectEvent(id, details))
135
+ callback(details, data);
136
+ }));
124
137
  }
125
138
  getLogger(nativeId: ScryptedNativeId): Promise<PluginLogger> {
139
+ this.acl?.deny();
126
140
  return this.api.getLogger(nativeId);
127
141
  }
128
142
  getComponent(id: string): Promise<any> {
143
+ this.acl?.deny();
129
144
  return this.api.getComponent(id);
130
145
  }
131
146
  async getMediaManager(): Promise<MediaManager> {
@@ -133,6 +148,7 @@ export class PluginAPIProxy extends PluginAPIManagedListeners implements PluginA
133
148
  }
134
149
 
135
150
  async requestRestart() {
151
+ this.acl?.deny();
136
152
  return this.api.requestRestart();
137
153
  }
138
154
  }
@@ -151,11 +167,11 @@ export interface PluginRemoteLoadZipOptions {
151
167
  }
152
168
 
153
169
  export interface PluginRemote {
154
- loadZip(packageJson: any, zipData: Buffer|string, options?: PluginRemoteLoadZipOptions): Promise<any>;
155
- setSystemState(state: {[id: string]: {[property: string]: SystemDeviceState}}): Promise<void>;
156
- setNativeId(nativeId: ScryptedNativeId, id: string, storage: {[key: string]: any}): Promise<void>;
157
- updateDeviceState(id: string, state: {[property: string]: SystemDeviceState}): Promise<void>;
158
- notify(id: string, eventTime: number, eventInterface: string, property: string|undefined, value: SystemDeviceState|any, changed?: boolean): Promise<void>;
170
+ loadZip(packageJson: any, zipData: Buffer | string, options?: PluginRemoteLoadZipOptions): Promise<any>;
171
+ setSystemState(state: { [id: string]: { [property: string]: SystemDeviceState } }): Promise<void>;
172
+ setNativeId(nativeId: ScryptedNativeId, id: string, storage: { [key: string]: any }): Promise<void>;
173
+ updateDeviceState(id: string, state: { [property: string]: SystemDeviceState }): Promise<void>;
174
+ notify(id: string, eventTime: number, eventInterface: string, property: string | undefined, value: SystemDeviceState | any, changed?: boolean): Promise<void>;
159
175
 
160
176
  ioEvent(id: string, event: string, message?: any): Promise<void>;
161
177
 
@@ -165,5 +181,5 @@ export interface PluginRemote {
165
181
  }
166
182
 
167
183
  export interface MediaObjectRemote extends MediaObject {
168
- getData(): Promise<Buffer|string>;
184
+ getData(): Promise<Buffer | string>;
169
185
  }
@@ -7,6 +7,7 @@ import { PrimitiveProxyHandler, RpcPeer } from "../rpc";
7
7
  import { ScryptedRuntime } from "../runtime";
8
8
  import { sleep } from "../sleep";
9
9
  import { getState } from "../state";
10
+ import { AccessControls } from "./acl";
10
11
  import { allInterfaceProperties, getInterfaceMethods, getPropertyInterfaces } from "./descriptor";
11
12
  import { PluginError } from "./plugin-error";
12
13
 
@@ -396,6 +397,11 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
396
397
  async apply(target: any, thisArg: any, argArray?: any): Promise<any> {
397
398
  const method = target();
398
399
 
400
+ const { activeRpcPeer } = RpcPeer;
401
+ const acl: AccessControls = activeRpcPeer?.tags?.acl;
402
+ if (acl?.shouldRejectMethod(this.id, method))
403
+ acl.deny();
404
+
399
405
  this.ensureProxy();
400
406
  const pluginDevice = this.scrypted.findPluginDeviceById(this.id);
401
407
 
@@ -1,4 +1,4 @@
1
- import { Device, DeviceManifest, EventDetails, EventListenerOptions, EventListenerRegister, HttpRequest, MediaManager, ScryptedDevice, ScryptedInterfaceDescriptor, ScryptedInterfaceProperty, ScryptedNativeId } from '@scrypted/types';
1
+ import { Device, DeviceManifest, EventDetails, EventListenerOptions, EventListenerRegister, MediaManager, ScryptedDevice, ScryptedInterfaceDescriptor, ScryptedInterfaceProperty, ScryptedNativeId } from '@scrypted/types';
2
2
  import debounce from 'lodash/debounce';
3
3
  import { Plugin } from '../db-types';
4
4
  import { Logger } from '../logger';
@@ -20,10 +20,7 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
20
20
  'onMixinEvent',
21
21
  'onDeviceEvent',
22
22
  'setStorage',
23
- 'ioSend',
24
- 'ioClose',
25
23
  'setDeviceProperty',
26
- 'deliverPush',
27
24
  'requestRestart',
28
25
  "setState",
29
26
  ];
@@ -78,10 +75,6 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
78
75
  return this.mediaManager;
79
76
  }
80
77
 
81
- async deliverPush(endpoint: string, httpRequest: HttpRequest) {
82
- return this.scrypted.deliverPush(endpoint, httpRequest);
83
- }
84
-
85
78
  async getLogger(nativeId: ScryptedNativeId): Promise<Logger> {
86
79
  const device = this.scrypted.findPluginDevice(this.pluginId, nativeId);
87
80
  return this.scrypted.getDeviceLogger(device);
@@ -105,18 +98,6 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
105
98
  }
106
99
  }
107
100
 
108
- async ioClose(id: string) {
109
- // @ts-expect-error
110
- this.pluginHost.io.clients[id]?.close();
111
- this.pluginHost.ws[id]?.close();
112
- }
113
-
114
- async ioSend(id: string, message: string) {
115
- // @ts-expect-error
116
- this.pluginHost.io.clients[id]?.send(message);
117
- this.pluginHost.ws[id]?.send(message);
118
- }
119
-
120
101
  async setState(nativeId: ScryptedNativeId, key: string, value: any) {
121
102
  checkProperty(key, value);
122
103
  this.scrypted.stateManager.setPluginState(this.pluginId, nativeId, this.propertyInterfaces?.[key], key, value);
@@ -9,13 +9,14 @@ import path from 'path';
9
9
  import rimraf from 'rimraf';
10
10
  import { Duplex } from 'stream';
11
11
  import WebSocket from 'ws';
12
- import { Plugin } from '../db-types';
12
+ import { Plugin, ScryptedUser } from '../db-types';
13
13
  import { IOServer, IOServerSocket } from '../io';
14
14
  import { Logger } from '../logger';
15
15
  import { RpcPeer } from '../rpc';
16
16
  import { createDuplexRpcPeer, createRpcSerializer } from '../rpc-serializer';
17
17
  import { ScryptedRuntime } from '../runtime';
18
18
  import { sleep } from '../sleep';
19
+ import { AccessControls } from './acl';
19
20
  import { MediaManagerHostImpl } from './media';
20
21
  import { PluginAPIProxy, PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
21
22
  import { ConsoleServer, createConsoleServer } from './plugin-console';
@@ -134,6 +135,12 @@ export class PluginHost {
134
135
 
135
136
  this.io.on('connection', async (socket) => {
136
137
  try {
138
+ const {
139
+ accessControls,
140
+ endpointRequest,
141
+ pluginDevice,
142
+ } = (socket.request as any).scrypted;
143
+
137
144
  try {
138
145
  if (socket.request.url.indexOf('/engine.io/api') !== -1) {
139
146
  if (socket.request.url.indexOf('/public') !== -1) {
@@ -141,7 +148,7 @@ export class PluginHost {
141
148
  return;
142
149
  }
143
150
 
144
- await this.createRpcIoPeer(socket);
151
+ await this.createRpcIoPeer(socket, accessControls);
145
152
  return;
146
153
  }
147
154
  }
@@ -150,10 +157,6 @@ export class PluginHost {
150
157
  return;
151
158
  }
152
159
 
153
- const {
154
- endpointRequest,
155
- pluginDevice,
156
- } = (socket.request as any).scrypted;
157
160
 
158
161
  const handler = this.scrypted.getDevice<EngineIOHandler>(pluginDevice._id);
159
162
 
@@ -168,7 +171,14 @@ export class PluginHost {
168
171
  });
169
172
 
170
173
  // @ts-expect-error
171
- await handler.onConnection(endpointRequest, new WebSocketConnection(`io://${id}`));
174
+ await handler.onConnection(endpointRequest, new WebSocketConnection(`io://${id}`, {
175
+ send(message) {
176
+ socket.send(message);
177
+ },
178
+ close(message) {
179
+ socket.close();
180
+ },
181
+ }));
172
182
  }
173
183
  catch (e) {
174
184
  console.error('engine.io plugin error', e);
@@ -304,7 +314,6 @@ export class PluginHost {
304
314
  });
305
315
 
306
316
  this.worker.setupRpcPeer(this.peer);
307
- this.peer.addSerializer(WebSocketConnection, WebSocketConnection.name, new WebSocketSerializer());
308
317
 
309
318
  this.worker.stdout.on('data', data => console.log(data.toString()));
310
319
  this.worker.stderr.on('data', data => console.error(data.toString()));
@@ -334,7 +343,7 @@ export class PluginHost {
334
343
  });
335
344
 
336
345
  this.worker.on('rpc', (message, sendHandle) => {
337
- const socket = sendHandle as net.Socket;
346
+ const socket = sendHandle as net.Socket;
338
347
  const { pluginId } = message;
339
348
  const host = this.scrypted.plugins[pluginId];
340
349
  if (!host) {
@@ -349,7 +358,7 @@ export class PluginHost {
349
358
  }
350
359
  }
351
360
 
352
- async createRpcIoPeer(socket: IOServerSocket) {
361
+ async createRpcIoPeer(socket: IOServerSocket, accessControls: AccessControls) {
353
362
  const serializer = createRpcSerializer({
354
363
  sendMessageBuffer: buffer => socket.send(buffer),
355
364
  sendMessageFinish: message => socket.send(JSON.stringify(message)),
@@ -372,12 +381,13 @@ export class PluginHost {
372
381
  reject?.(e);
373
382
  }
374
383
  });
384
+ rpcPeer.tags.acl = accessControls;
375
385
  serializer.setupRpcPeer(rpcPeer);
376
- rpcPeer.addSerializer(WebSocketConnection, WebSocketConnection.name, new WebSocketSerializer());
377
386
 
378
387
  // wrap the host api with a connection specific api that can be torn down on disconnect
379
388
  const createMediaManager = await this.peer.getParam('createMediaManager');
380
389
  const api = new PluginAPIProxy(this.api, await createMediaManager());
390
+ api.acl = accessControls;
381
391
  const kill = () => {
382
392
  serializer.onDisconnected();
383
393
  api.removeListeners();
@@ -390,7 +400,6 @@ export class PluginHost {
390
400
 
391
401
  async createRpcPeer(duplex: Duplex) {
392
402
  const rpcPeer = createDuplexRpcPeer(`api/${this.pluginId}`, 'duplex', duplex, duplex);
393
- rpcPeer.addSerializer(WebSocketConnection, WebSocketConnection.name, new WebSocketSerializer());
394
403
 
395
404
  // wrap the host api with a connection specific api that can be torn down on disconnect
396
405
  const createMediaManager = await this.peer.getParam('createMediaManager');
@@ -95,6 +95,7 @@ export abstract class PluginHttp<T> {
95
95
  url: req.url,
96
96
  isPublicEndpoint,
97
97
  username: res.locals.username,
98
+ aclId: res.locals.aclId,
98
99
  };
99
100
 
100
101
  if (isEngineIOEndpoint && !isUpgrade && isPublicEndpoint) {
@@ -62,7 +62,7 @@ export interface WebSocketConnectCallbacks {
62
62
  }
63
63
 
64
64
  export interface WebSocketConnect {
65
- (url: string, callbacks: WebSocketConnectCallbacks): void;
65
+ (connection: WebSocketConnection, callbacks: WebSocketConnectCallbacks): void;
66
66
  }
67
67
 
68
68
  export interface WebSocketMethods {
@@ -76,15 +76,14 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect): any
76
76
  _url: string;
77
77
  _protocols: string[];
78
78
  readyState: number;
79
- _ws: WebSocketMethods;
80
79
 
81
- constructor(url: string, protocols?: string[]) {
80
+ constructor(public connection: WebSocketConnection, protocols?: string[]) {
82
81
  super();
83
- this._url = url;
82
+ this._url = connection.url;
84
83
  this._protocols = protocols;
85
84
  this.readyState = 0;
86
85
 
87
- __websocketConnect(url, {
86
+ __websocketConnect(connection, {
88
87
  connect: (e, ws) => {
89
88
  // connect
90
89
  if (e != null) {
@@ -95,7 +94,6 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect): any
95
94
  return;
96
95
  }
97
96
 
98
- this._ws = ws;
99
97
  this.readyState = 1;
100
98
  this.dispatchEvent({
101
99
  type: 'open',
@@ -129,7 +127,7 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect): any
129
127
  }
130
128
 
131
129
  send(message: string | ArrayBufferLike) {
132
- this._ws.send(message);
130
+ this.connection.send(message);
133
131
  }
134
132
 
135
133
  get url() {
@@ -141,7 +139,7 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect): any
141
139
  }
142
140
 
143
141
  close(reason: string) {
144
- this._ws.close(reason);
142
+ this.connection.close(reason);
145
143
  }
146
144
  }
147
145
 
@@ -153,10 +151,25 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect): any
153
151
  return WebSocket;
154
152
  }
155
153
 
156
- export class WebSocketConnection {
154
+ export class WebSocketConnection implements WebSocketMethods {
157
155
  [RpcPeer.PROPERTY_PROXY_PROPERTIES]: any;
158
156
 
159
- constructor(public url: string) {
157
+ [RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS] = [
158
+ "send",
159
+ "close",
160
+ ];
161
+
162
+ constructor(public url: string, public websocketMethods: WebSocketMethods) {
163
+ this[RpcPeer.PROPERTY_PROXY_PROPERTIES] = {
164
+ url,
165
+ }
166
+ }
167
+
168
+ send(message: string | ArrayBufferLike): void {
169
+ return this.websocketMethods.send(message);
170
+ }
171
+ close(message: string): void {
172
+ return this.websocketMethods.close(message);
160
173
  }
161
174
  }
162
175
 
@@ -164,16 +177,12 @@ export class WebSocketSerializer implements RpcSerializer {
164
177
  WebSocket: ReturnType<typeof createWebSocketClass>;
165
178
 
166
179
  serialize(value: any, serializationContext?: any) {
167
- const connection = value as WebSocketConnection;
168
- connection[RpcPeer.PROPERTY_PROXY_PROPERTIES] = {
169
- url: connection.url,
170
- }
171
- return connection;
180
+ throw new Error("WebSocketSerializer should only be used for deserialization.");
172
181
  }
173
182
 
174
- deserialize(serialized: any, serializationContext?: any) {
183
+ deserialize(serialized: WebSocketConnection, serializationContext?: any) {
175
184
  if (!this.WebSocket)
176
185
  return undefined;
177
- return new this.WebSocket(serialized.url);
186
+ return new this.WebSocket(serialized);
178
187
  }
179
188
  }
@@ -336,6 +336,9 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
336
336
  pluginReader = undefined;
337
337
  const script = main.toString();
338
338
 
339
+ scrypted.connect = (socket, options) => {
340
+ process.send(options, socket);
341
+ }
339
342
 
340
343
  const forks = new Set<PluginRemote>();
341
344