@scrypted/server 0.0.107 → 0.0.111
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/event-registry.js +2 -2
- package/dist/event-registry.js.map +1 -1
- 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-api.js +2 -2
- package/dist/plugin/plugin-api.js.map +1 -1
- package/dist/plugin/plugin-device.js +1 -0
- package/dist/plugin/plugin-device.js.map +1 -1
- package/dist/plugin/plugin-host-api.js +10 -8
- 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/plugin/system.js +11 -3
- package/dist/plugin/system.js.map +1 -1
- package/dist/rpc.js +57 -27
- 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/event-registry.ts +5 -5
- package/src/http-interfaces.ts +3 -3
- package/src/plugin/listen-zero.ts +13 -0
- package/src/plugin/plugin-api.ts +5 -5
- package/src/plugin/plugin-device.ts +2 -1
- package/src/plugin/plugin-host-api.ts +11 -10
- 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/plugin/system.ts +15 -6
- package/src/rpc.ts +66 -26
- package/src/runtime.ts +55 -128
- package/src/scrypted-main.ts +4 -0
- package/src/services/plugin.ts +2 -2
- package/src/state.ts +4 -12
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { RpcMessage, RpcPeer } from '../rpc';
|
|
2
2
|
import AdmZip from 'adm-zip';
|
|
3
|
-
import { SystemManager, DeviceManager, ScryptedNativeId, Device, EventListenerRegister, EngineIOHandler,
|
|
3
|
+
import { SystemManager, DeviceManager, ScryptedNativeId, Device, EventListenerRegister, EngineIOHandler, ScryptedInterface, ScryptedInterfaceProperty } from '@scrypted/sdk/types'
|
|
4
4
|
import { ScryptedRuntime } from '../runtime';
|
|
5
5
|
import { Plugin } from '../db-types';
|
|
6
|
-
import io from 'engine.io';
|
|
6
|
+
import io, { Socket } from 'engine.io';
|
|
7
7
|
import { attachPluginRemote, setupPluginRemote } from './plugin-remote';
|
|
8
|
-
import { PluginAPI, PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
|
|
8
|
+
import { PluginAPI, PluginAPIProxy, PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
|
|
9
9
|
import { Logger } from '../logger';
|
|
10
10
|
import { MediaManagerHostImpl, MediaManagerImpl } from './media';
|
|
11
|
-
import
|
|
12
|
-
import WebSocket, { EventEmitter } from 'ws';
|
|
11
|
+
import WebSocket from 'ws';
|
|
13
12
|
import { PassThrough } from 'stream';
|
|
14
13
|
import { Console } from 'console'
|
|
15
14
|
import { sleep } from '../sleep';
|
|
@@ -25,6 +24,7 @@ import { ensurePluginVolume } from './plugin-volume';
|
|
|
25
24
|
import { installOptionalDependencies } from './plugin-npm-dependencies';
|
|
26
25
|
import { ConsoleServer, createConsoleServer } from './plugin-console';
|
|
27
26
|
import { createREPLServer } from './plugin-repl';
|
|
27
|
+
import { LazyRemote } from './plugin-lazy-remote';
|
|
28
28
|
|
|
29
29
|
export class PluginHost {
|
|
30
30
|
worker: child_process.ChildProcess;
|
|
@@ -41,7 +41,6 @@ export class PluginHost {
|
|
|
41
41
|
api: PluginHostAPI;
|
|
42
42
|
pluginName: string;
|
|
43
43
|
packageJson: any;
|
|
44
|
-
listener: EventListenerRegister;
|
|
45
44
|
stats: {
|
|
46
45
|
cpuUsage: NodeJS.CpuUsage,
|
|
47
46
|
memoryUsage: NodeJS.MemoryUsage,
|
|
@@ -51,7 +50,6 @@ export class PluginHost {
|
|
|
51
50
|
|
|
52
51
|
kill() {
|
|
53
52
|
this.killed = true;
|
|
54
|
-
this.listener.removeListener();
|
|
55
53
|
this.api.removeListeners();
|
|
56
54
|
this.worker.kill();
|
|
57
55
|
this.io.close();
|
|
@@ -77,7 +75,6 @@ export class PluginHost {
|
|
|
77
75
|
return this.pluginName || 'no plugin name';
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
|
|
81
78
|
async upsertDevice(upsert: Device) {
|
|
82
79
|
const pi = await this.scrypted.upsertDevice(this.pluginId, upsert, true);
|
|
83
80
|
await this.remote.setNativeId(pi.nativeId, pi._id, pi.storage || {});
|
|
@@ -101,6 +98,17 @@ export class PluginHost {
|
|
|
101
98
|
|
|
102
99
|
this.io.on('connection', async (socket) => {
|
|
103
100
|
try {
|
|
101
|
+
try {
|
|
102
|
+
if (socket.request.url.indexOf('/api') !== -1) {
|
|
103
|
+
await this.createRpcIoPeer(socket);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
socket.close();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
104
112
|
const {
|
|
105
113
|
endpointRequest,
|
|
106
114
|
pluginDevice,
|
|
@@ -138,8 +146,7 @@ export class PluginHost {
|
|
|
138
146
|
logger.log('i', `loading ${this.pluginName}`);
|
|
139
147
|
logger.log('i', 'pid ' + this.worker?.pid);
|
|
140
148
|
|
|
141
|
-
|
|
142
|
-
const remotePromise = setupPluginRemote(this.peer, this.api, self.pluginId);
|
|
149
|
+
const remotePromise = setupPluginRemote(this.peer, this.api, self.pluginId, () => this.scrypted.stateManager.getSystemState());
|
|
143
150
|
const init = (async () => {
|
|
144
151
|
const remote = await remotePromise;
|
|
145
152
|
|
|
@@ -190,19 +197,9 @@ export class PluginHost {
|
|
|
190
197
|
this.module = init.then(({ module }) => module);
|
|
191
198
|
this.remote = new LazyRemote(remotePromise, init.then(({ remote }) => remote));
|
|
192
199
|
|
|
193
|
-
this.listener = scrypted.stateManager.listen((id, eventDetails, eventData) => {
|
|
194
|
-
if (eventDetails.property) {
|
|
195
|
-
const device = scrypted.findPluginDeviceById(id);
|
|
196
|
-
this.remote.notify(id, eventDetails.eventTime, eventDetails.eventInterface, eventDetails.property, device?.state[eventDetails.property], eventDetails.changed);
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
this.remote.notify(id, eventDetails.eventTime, eventDetails.eventInterface, eventDetails.property, eventData, eventDetails.changed);
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
|
|
203
200
|
init.catch(e => {
|
|
204
201
|
console.error('plugin failed to load', e);
|
|
205
|
-
this.
|
|
202
|
+
this.api.removeListeners();
|
|
206
203
|
});
|
|
207
204
|
}
|
|
208
205
|
|
|
@@ -314,6 +311,27 @@ export class PluginHost {
|
|
|
314
311
|
}
|
|
315
312
|
};
|
|
316
313
|
}
|
|
314
|
+
|
|
315
|
+
async createRpcIoPeer(socket: Socket) {
|
|
316
|
+
let connected = true;
|
|
317
|
+
const rpcPeer = new RpcPeer(`api/${this.pluginId}`, 'web', (message, reject) => {
|
|
318
|
+
if (!connected)
|
|
319
|
+
reject?.(new Error('peer disconnected'));
|
|
320
|
+
else
|
|
321
|
+
socket.send(JSON.stringify(message))
|
|
322
|
+
});
|
|
323
|
+
socket.on('message', data => rpcPeer.handleMessage(JSON.parse(data as string)));
|
|
324
|
+
// wrap the host api with a connection specific api that can be torn down on disconnect
|
|
325
|
+
const api = new PluginAPIProxy(this.api, await this.peer.getParam('mediaManager'));
|
|
326
|
+
const kill = () => {
|
|
327
|
+
connected = false;
|
|
328
|
+
rpcPeer.kill('engine.io connection closed.')
|
|
329
|
+
api.removeListeners();
|
|
330
|
+
}
|
|
331
|
+
socket.on('close', kill);
|
|
332
|
+
socket.on('error', kill);
|
|
333
|
+
return setupPluginRemote(rpcPeer, api, null, () => this.scrypted.stateManager.getSystemState());
|
|
334
|
+
}
|
|
317
335
|
}
|
|
318
336
|
|
|
319
337
|
export function startPluginRemote() {
|
|
@@ -331,13 +349,6 @@ export function startPluginRemote() {
|
|
|
331
349
|
let api: PluginAPI;
|
|
332
350
|
let pluginId: string;
|
|
333
351
|
|
|
334
|
-
function idForNativeId(nativeId: ScryptedNativeId) {
|
|
335
|
-
if (!deviceManager)
|
|
336
|
-
return;
|
|
337
|
-
const ds = deviceManager.getDeviceState(nativeId);
|
|
338
|
-
return ds?.id;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
352
|
const getConsole = (hook: (stdout: PassThrough, stderr: PassThrough) => Promise<void>,
|
|
342
353
|
also?: Console, alsoPrefix?: string) => {
|
|
343
354
|
|
|
@@ -489,7 +500,7 @@ export function startPluginRemote() {
|
|
|
489
500
|
getPluginConsole,
|
|
490
501
|
getDeviceConsole,
|
|
491
502
|
getMixinConsole,
|
|
492
|
-
async getServicePort(name) {
|
|
503
|
+
async getServicePort(name, ...args: any[]) {
|
|
493
504
|
if (name === 'repl') {
|
|
494
505
|
if (!replPort)
|
|
495
506
|
throw new Error('REPL unavailable: Plugin not loaded.')
|
|
@@ -530,69 +541,3 @@ export function startPluginRemote() {
|
|
|
530
541
|
});
|
|
531
542
|
})
|
|
532
543
|
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* Warning: do not await in any of these methods unless necessary, otherwise
|
|
536
|
-
* execution order of state reporting may fail.
|
|
537
|
-
*/
|
|
538
|
-
class LazyRemote implements PluginRemote {
|
|
539
|
-
remote: PluginRemote;
|
|
540
|
-
|
|
541
|
-
constructor(public remotePromise: Promise<PluginRemote>, public remoteReadyPromise: Promise<PluginRemote>) {
|
|
542
|
-
this.remoteReadyPromise = (async () => {
|
|
543
|
-
this.remote = await remoteReadyPromise;
|
|
544
|
-
return this.remote;
|
|
545
|
-
})();
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
async loadZip(packageJson: any, zipData: Buffer, options?: PluginRemoteLoadZipOptions): Promise<any> {
|
|
549
|
-
if (!this.remote)
|
|
550
|
-
await this.remoteReadyPromise;
|
|
551
|
-
return this.remote.loadZip(packageJson, zipData, options);
|
|
552
|
-
}
|
|
553
|
-
async setSystemState(state: { [id: string]: { [property: string]: SystemDeviceState; }; }): Promise<void> {
|
|
554
|
-
if (!this.remote)
|
|
555
|
-
await this.remoteReadyPromise;
|
|
556
|
-
return this.remote.setSystemState(state);
|
|
557
|
-
}
|
|
558
|
-
async setNativeId(nativeId: ScryptedNativeId, id: string, storage: { [key: string]: any; }): Promise<void> {
|
|
559
|
-
if (!this.remote)
|
|
560
|
-
await this.remoteReadyPromise;
|
|
561
|
-
return this.remote.setNativeId(nativeId, id, storage);
|
|
562
|
-
}
|
|
563
|
-
async updateDeviceState(id: string, state: { [property: string]: SystemDeviceState; }): Promise<void> {
|
|
564
|
-
try {
|
|
565
|
-
if (!this.remote)
|
|
566
|
-
await this.remoteReadyPromise;
|
|
567
|
-
}
|
|
568
|
-
catch (e) {
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
return this.remote.updateDeviceState(id, state);
|
|
572
|
-
}
|
|
573
|
-
async notify(id: string, eventTime: number, eventInterface: string, property: string, propertyState: SystemDeviceState, changed?: boolean): Promise<void> {
|
|
574
|
-
try {
|
|
575
|
-
if (!this.remote)
|
|
576
|
-
await this.remoteReadyPromise;
|
|
577
|
-
}
|
|
578
|
-
catch (e) {
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
return this.remote.notify(id, eventTime, eventInterface, property, propertyState, changed);
|
|
582
|
-
}
|
|
583
|
-
async ioEvent(id: string, event: string, message?: any): Promise<void> {
|
|
584
|
-
if (!this.remote)
|
|
585
|
-
await this.remoteReadyPromise;
|
|
586
|
-
return this.remote.ioEvent(id, event, message);
|
|
587
|
-
}
|
|
588
|
-
async createDeviceState(id: string, setState: (property: string, value: any) => Promise<void>): Promise<any> {
|
|
589
|
-
if (!this.remote)
|
|
590
|
-
await this.remoteReadyPromise;
|
|
591
|
-
return this.remote.createDeviceState(id, setState);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
async getServicePort(name: string): Promise<number> {
|
|
595
|
-
const remote = await this.remotePromise;
|
|
596
|
-
return remote.getServicePort(name);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
@@ -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
|
|