@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.
- package/dist/db-types.js +1 -2
- package/dist/db-types.js.map +1 -1
- package/dist/plugin/acl.js +83 -0
- package/dist/plugin/acl.js.map +1 -0
- package/dist/plugin/plugin-api.js +28 -11
- package/dist/plugin/plugin-api.js.map +1 -1
- package/dist/plugin/plugin-device.js +4 -0
- package/dist/plugin/plugin-device.js.map +1 -1
- package/dist/plugin/plugin-host-api.js +0 -16
- package/dist/plugin/plugin-host-api.js.map +1 -1
- package/dist/plugin/plugin-host.js +13 -7
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-http.js +1 -0
- package/dist/plugin/plugin-http.js.map +1 -1
- package/dist/plugin/plugin-remote-websocket.js +25 -14
- package/dist/plugin/plugin-remote-websocket.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +3 -0
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/plugin/plugin-remote.js +41 -8
- package/dist/plugin/plugin-remote.js.map +1 -1
- package/dist/rpc.js +37 -8
- package/dist/rpc.js.map +1 -1
- package/dist/runtime.js +44 -14
- package/dist/runtime.js.map +1 -1
- package/dist/scrypted-server-main.js +11 -11
- package/dist/scrypted-server-main.js.map +1 -1
- package/dist/services/plugin.js +1 -12
- package/dist/services/plugin.js.map +1 -1
- package/dist/services/users.js +46 -0
- package/dist/services/users.js.map +1 -0
- package/dist/usertoken.js +9 -5
- package/dist/usertoken.js.map +1 -1
- package/package.json +2 -2
- package/src/db-types.ts +2 -2
- package/src/plugin/acl.ts +104 -0
- package/src/plugin/plugin-api.ts +41 -25
- package/src/plugin/plugin-device.ts +6 -0
- package/src/plugin/plugin-host-api.ts +1 -20
- package/src/plugin/plugin-host.ts +21 -12
- package/src/plugin/plugin-http.ts +1 -0
- package/src/plugin/plugin-remote-websocket.ts +26 -17
- package/src/plugin/plugin-remote-worker.ts +3 -0
- package/src/plugin/plugin-remote.ts +49 -11
- package/src/rpc.ts +43 -9
- package/src/runtime.ts +48 -20
- package/src/scrypted-server-main.ts +11 -11
- package/src/services/plugin.ts +2 -12
- package/src/services/users.ts +43 -0
- 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
|
-
|
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
|
+
}
|
package/src/plugin/plugin-api.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
import type {
|
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
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
117
|
-
return this.api.
|
118
|
-
|
119
|
-
|
120
|
-
|
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,
|
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
|
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');
|
@@ -62,7 +62,7 @@ export interface WebSocketConnectCallbacks {
|
|
62
62
|
}
|
63
63
|
|
64
64
|
export interface WebSocketConnect {
|
65
|
-
(
|
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(
|
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(
|
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.
|
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.
|
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
|
-
|
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
|
-
|
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:
|
183
|
+
deserialize(serialized: WebSocketConnection, serializationContext?: any) {
|
175
184
|
if (!this.WebSocket)
|
176
185
|
return undefined;
|
177
|
-
return new this.WebSocket(serialized
|
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
|
|