@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.
- package/dist/http-interfaces.js +2 -2
- package/dist/http-interfaces.js.map +1 -1
- package/dist/plugin/listen-zero.js +13 -1
- package/dist/plugin/listen-zero.js.map +1 -1
- package/dist/plugin/plugin-device.js +11 -2
- package/dist/plugin/plugin-device.js.map +1 -1
- package/dist/plugin/plugin-host-api.js +8 -7
- package/dist/plugin/plugin-host-api.js.map +1 -1
- package/dist/plugin/plugin-host.js +36 -87
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-http.js +100 -0
- package/dist/plugin/plugin-http.js.map +1 -0
- package/dist/plugin/plugin-lazy-remote.js +73 -0
- package/dist/plugin/plugin-lazy-remote.js.map +1 -0
- package/dist/plugin/plugin-npm-dependencies.js +16 -16
- package/dist/plugin/plugin-npm-dependencies.js.map +1 -1
- package/dist/plugin/plugin-remote-websocket.js +40 -34
- package/dist/plugin/plugin-remote-websocket.js.map +1 -1
- package/dist/plugin/plugin-remote.js +35 -26
- package/dist/plugin/plugin-remote.js.map +1 -1
- package/dist/rpc.js +53 -26
- package/dist/rpc.js.map +1 -1
- package/dist/runtime.js +45 -111
- package/dist/runtime.js.map +1 -1
- package/dist/scrypted-main.js +3 -0
- package/dist/scrypted-main.js.map +1 -1
- package/dist/services/plugin.js +2 -2
- package/dist/services/plugin.js.map +1 -1
- package/dist/state.js +2 -8
- package/dist/state.js.map +1 -1
- package/package.json +2 -2
- package/python/plugin-remote.py +4 -4
- package/python/rpc.py +68 -26
- package/src/http-interfaces.ts +3 -3
- package/src/plugin/listen-zero.ts +13 -0
- package/src/plugin/plugin-api.ts +1 -1
- package/src/plugin/plugin-device.ts +11 -2
- package/src/plugin/plugin-host-api.ts +9 -8
- package/src/plugin/plugin-host.ts +40 -95
- package/src/plugin/plugin-http.ts +117 -0
- package/src/plugin/plugin-lazy-remote.ts +70 -0
- package/src/plugin/plugin-npm-dependencies.ts +19 -18
- package/src/plugin/plugin-remote-websocket.ts +55 -60
- package/src/plugin/plugin-remote.ts +45 -38
- package/src/rpc.ts +62 -25
- package/src/runtime.ts +55 -128
- package/src/scrypted-main.ts +4 -0
- package/src/services/plugin.ts +2 -2
- 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
|
|
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(
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
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
|
|
61
|
-
(
|
|
63
|
+
export interface WebSocketConnect {
|
|
64
|
+
(url: string, protocols: string[], callbacks: WebSocketConnectCallbacks): void;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
interface
|
|
65
|
-
(
|
|
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
|
-
|
|
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,
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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,
|
|
436
|
-
if (url.startsWith('io://')) {
|
|
437
|
-
const id = url.substring('
|
|
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
|
-
|
|
466
|
+
send: (message: string) => api.ioSend(id, message),
|
|
467
|
+
});
|
|
461
468
|
}
|
|
462
469
|
else {
|
|
463
470
|
throw new Error('unsupported websocket');
|