@scrypted/server 0.0.116 → 0.0.118
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/.vscode/launch.json +1 -0
- package/dist/http-interfaces.js +11 -4
- package/dist/http-interfaces.js.map +1 -1
- package/dist/level.js.map +1 -1
- package/dist/plugin/media.js +3 -3
- package/dist/plugin/media.js.map +1 -1
- package/dist/plugin/plugin-console.js.map +1 -1
- package/dist/plugin/plugin-device.js +13 -3
- package/dist/plugin/plugin-device.js.map +1 -1
- package/dist/plugin/plugin-host-api.js +2 -2
- package/dist/plugin/plugin-host-api.js.map +1 -1
- package/dist/plugin/plugin-host.js +110 -235
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-lazy-remote.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +253 -0
- package/dist/plugin/plugin-remote-worker.js.map +1 -0
- package/dist/plugin/plugin-remote.js +38 -14
- package/dist/plugin/plugin-remote.js.map +1 -1
- package/dist/plugin/plugin-repl.js.map +1 -1
- package/dist/plugin/system.js +7 -5
- package/dist/plugin/system.js.map +1 -1
- package/dist/rpc.js +2 -1
- package/dist/rpc.js.map +1 -1
- package/dist/runtime.js +5 -4
- package/dist/runtime.js.map +1 -1
- package/dist/scrypted-main.js +3 -437
- package/dist/scrypted-main.js.map +1 -1
- package/dist/scrypted-plugin-main.js +8 -0
- package/dist/scrypted-plugin-main.js.map +1 -0
- package/dist/scrypted-server-main.js +448 -0
- package/dist/scrypted-server-main.js.map +1 -0
- package/package.json +5 -4
- package/python/media.py +1 -12
- package/python/plugin-remote.py +15 -7
- package/python/rpc.py +1 -1
- package/src/http-interfaces.ts +12 -5
- package/src/level.ts +0 -2
- package/src/plugin/media.ts +3 -3
- package/src/plugin/plugin-api.ts +9 -1
- package/src/plugin/plugin-console.ts +0 -1
- package/src/plugin/plugin-device.ts +10 -3
- package/src/plugin/plugin-host-api.ts +2 -2
- package/src/plugin/plugin-host.ts +121 -264
- package/src/plugin/plugin-http.ts +2 -2
- package/src/plugin/plugin-lazy-remote.ts +1 -1
- package/src/plugin/plugin-remote-worker.ts +272 -0
- package/src/plugin/plugin-remote.ts +45 -16
- package/src/plugin/plugin-repl.ts +0 -1
- package/src/plugin/system.ts +8 -6
- package/src/rpc.ts +3 -1
- package/src/runtime.ts +5 -4
- package/src/scrypted-main.ts +3 -508
- package/src/scrypted-plugin-main.ts +6 -0
- package/src/scrypted-server-main.ts +516 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { RpcMessage, RpcPeer } from '../rpc';
|
|
2
|
+
import { SystemManager, DeviceManager, ScryptedNativeId } from '@scrypted/sdk/types'
|
|
3
|
+
import { attachPluginRemote, PluginReader } from './plugin-remote';
|
|
4
|
+
import { PluginAPI } from './plugin-api';
|
|
5
|
+
import { MediaManagerImpl } from './media';
|
|
6
|
+
import { PassThrough } from 'stream';
|
|
7
|
+
import { Console } from 'console'
|
|
8
|
+
import { install as installSourceMapSupport } from 'source-map-support';
|
|
9
|
+
import net from 'net'
|
|
10
|
+
import { installOptionalDependencies } from './plugin-npm-dependencies';
|
|
11
|
+
import { createREPLServer } from './plugin-repl';
|
|
12
|
+
|
|
13
|
+
export function startSharedPluginRemote() {
|
|
14
|
+
process.setMaxListeners(100);
|
|
15
|
+
process.on('message', (message: any) => {
|
|
16
|
+
if (message.type === 'start')
|
|
17
|
+
startPluginRemote(message.pluginId)
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function startPluginRemote(pluginId: string) {
|
|
22
|
+
let peerSend: (message: RpcMessage, reject?: (e: Error) => void) => void;
|
|
23
|
+
let peerListener: NodeJS.MessageListener;
|
|
24
|
+
|
|
25
|
+
if (process.argv[3] === '@scrypted/shared') {
|
|
26
|
+
peerSend = (message, reject) => process.send({
|
|
27
|
+
pluginId,
|
|
28
|
+
message,
|
|
29
|
+
}, undefined, {
|
|
30
|
+
swallowErrors: !reject,
|
|
31
|
+
}, e => {
|
|
32
|
+
if (e)
|
|
33
|
+
reject?.(e);
|
|
34
|
+
});
|
|
35
|
+
peerListener = (message: any) => {
|
|
36
|
+
if (message.type === 'message' && message.pluginId === pluginId)
|
|
37
|
+
peer.handleMessage(message.message);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
peerSend = (message, reject) => process.send(message, undefined, {
|
|
42
|
+
swallowErrors: !reject,
|
|
43
|
+
}, e => {
|
|
44
|
+
if (e)
|
|
45
|
+
reject?.(e);
|
|
46
|
+
});
|
|
47
|
+
peerListener = message => peer.handleMessage(message as RpcMessage);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const peer = new RpcPeer('unknown', 'host', peerSend);
|
|
51
|
+
peer.transportSafeArgumentTypes.add(Buffer.name);
|
|
52
|
+
process.on('message', peerListener);
|
|
53
|
+
process.on('disconnect', () => {
|
|
54
|
+
console.error('peer host disconnected, exiting.');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
let systemManager: SystemManager;
|
|
59
|
+
let deviceManager: DeviceManager;
|
|
60
|
+
let api: PluginAPI;
|
|
61
|
+
|
|
62
|
+
const getConsole = (hook: (stdout: PassThrough, stderr: PassThrough) => Promise<void>,
|
|
63
|
+
also?: Console, alsoPrefix?: string) => {
|
|
64
|
+
|
|
65
|
+
const stdout = new PassThrough();
|
|
66
|
+
const stderr = new PassThrough();
|
|
67
|
+
|
|
68
|
+
hook(stdout, stderr);
|
|
69
|
+
|
|
70
|
+
const ret = new Console(stdout, stderr);
|
|
71
|
+
|
|
72
|
+
const methods = [
|
|
73
|
+
'log', 'warn',
|
|
74
|
+
'dir', 'time',
|
|
75
|
+
'timeEnd', 'timeLog',
|
|
76
|
+
'trace', 'assert',
|
|
77
|
+
'clear', 'count',
|
|
78
|
+
'countReset', 'group',
|
|
79
|
+
'groupEnd', 'table',
|
|
80
|
+
'debug', 'info',
|
|
81
|
+
'dirxml', 'error',
|
|
82
|
+
'groupCollapsed',
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const printers = ['log', 'info', 'debug', 'trace', 'warn', 'error'];
|
|
86
|
+
for (const m of methods) {
|
|
87
|
+
const old = (ret as any)[m].bind(ret);
|
|
88
|
+
(ret as any)[m] = (...args: any[]) => {
|
|
89
|
+
// prefer the mixin version for local/remote console dump.
|
|
90
|
+
if (also && alsoPrefix && printers.includes(m)) {
|
|
91
|
+
(also as any)[m](alsoPrefix, ...args);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
(console as any)[m](...args);
|
|
95
|
+
}
|
|
96
|
+
// call through to old method to ensure it gets written
|
|
97
|
+
// to log buffer.
|
|
98
|
+
old(...args);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return ret;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let pluginsPromise: Promise<any>;
|
|
106
|
+
function getPlugins() {
|
|
107
|
+
if (!pluginsPromise)
|
|
108
|
+
pluginsPromise = api.getComponent('plugins');
|
|
109
|
+
return pluginsPromise;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const getDeviceConsole = (nativeId?: ScryptedNativeId) => {
|
|
113
|
+
// the the plugin console is simply the default console
|
|
114
|
+
// and gets read from stderr/stdout.
|
|
115
|
+
if (!nativeId)
|
|
116
|
+
return console;
|
|
117
|
+
|
|
118
|
+
return getConsole(async (stdout, stderr) => {
|
|
119
|
+
const connect = async () => {
|
|
120
|
+
const plugins = await getPlugins();
|
|
121
|
+
const port = await plugins.getRemoteServicePort(peer.selfName, 'console-writer');
|
|
122
|
+
const socket = net.connect(port);
|
|
123
|
+
socket.write(nativeId + '\n');
|
|
124
|
+
const writer = (data: Buffer) => {
|
|
125
|
+
socket.write(data);
|
|
126
|
+
};
|
|
127
|
+
stdout.on('data', writer);
|
|
128
|
+
stderr.on('data', writer);
|
|
129
|
+
socket.on('error', () => {
|
|
130
|
+
stdout.removeAllListeners();
|
|
131
|
+
stderr.removeAllListeners();
|
|
132
|
+
stdout.pause();
|
|
133
|
+
stderr.pause();
|
|
134
|
+
setTimeout(connect, 10000);
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
connect();
|
|
138
|
+
}, undefined, undefined);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const getMixinConsole = (mixinId: string, nativeId: ScryptedNativeId) => {
|
|
142
|
+
return getConsole(async (stdout, stderr) => {
|
|
143
|
+
if (!mixinId) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// todo: fix this. a mixin provider can mixin another device to make it a mixin provider itself.
|
|
147
|
+
// so the mixin id in the mixin table will be incorrect.
|
|
148
|
+
// there's no easy way to fix this from the remote.
|
|
149
|
+
// if (!systemManager.getDeviceById(mixinId).mixins.includes(idForNativeId(nativeId))) {
|
|
150
|
+
// return;
|
|
151
|
+
// }
|
|
152
|
+
const reconnect = () => {
|
|
153
|
+
stdout.removeAllListeners();
|
|
154
|
+
stderr.removeAllListeners();
|
|
155
|
+
stdout.pause();
|
|
156
|
+
stderr.pause();
|
|
157
|
+
setTimeout(tryConnect, 10000);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const connect = async () => {
|
|
161
|
+
const ds = deviceManager.getDeviceState(nativeId);
|
|
162
|
+
if (!ds) {
|
|
163
|
+
// deleted?
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const plugins = await getPlugins();
|
|
168
|
+
const { pluginId, nativeId: mixinNativeId } = await plugins.getDeviceInfo(mixinId);
|
|
169
|
+
const port = await plugins.getRemoteServicePort(pluginId, 'console-writer');
|
|
170
|
+
const socket = net.connect(port);
|
|
171
|
+
socket.write(mixinNativeId + '\n');
|
|
172
|
+
const writer = (data: Buffer) => {
|
|
173
|
+
let str = data.toString().trim();
|
|
174
|
+
str = str.replaceAll('\n', `\n[${ds.name}]: `);
|
|
175
|
+
str = `[${ds.name}]: ` + str + '\n';
|
|
176
|
+
socket.write(str);
|
|
177
|
+
};
|
|
178
|
+
stdout.on('data', writer);
|
|
179
|
+
stderr.on('data', writer);
|
|
180
|
+
socket.on('close', reconnect);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const tryConnect = async () => {
|
|
184
|
+
try {
|
|
185
|
+
await connect();
|
|
186
|
+
}
|
|
187
|
+
catch (e) {
|
|
188
|
+
reconnect();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
tryConnect();
|
|
192
|
+
}, getDeviceConsole(nativeId), `[${systemManager.getDeviceById(mixinId)?.name}]`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let lastCpuUsage: NodeJS.CpuUsage;
|
|
196
|
+
setInterval(() => {
|
|
197
|
+
const cpuUsage = process.cpuUsage(lastCpuUsage);
|
|
198
|
+
lastCpuUsage = cpuUsage;
|
|
199
|
+
peer.sendOob({
|
|
200
|
+
type: 'stats',
|
|
201
|
+
cpu: cpuUsage,
|
|
202
|
+
memoryUsage: process.memoryUsage(),
|
|
203
|
+
});
|
|
204
|
+
global?.gc();
|
|
205
|
+
}, 10000);
|
|
206
|
+
|
|
207
|
+
let replPort: Promise<number>;
|
|
208
|
+
|
|
209
|
+
let _pluginConsole: Console;
|
|
210
|
+
const getPluginConsole = () => {
|
|
211
|
+
if (!_pluginConsole)
|
|
212
|
+
_pluginConsole = getDeviceConsole(undefined);
|
|
213
|
+
return _pluginConsole;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
attachPluginRemote(peer, {
|
|
217
|
+
createMediaManager: async (sm) => {
|
|
218
|
+
systemManager = sm;
|
|
219
|
+
return new MediaManagerImpl(systemManager, getPluginConsole());
|
|
220
|
+
},
|
|
221
|
+
onGetRemote: async (_api, _pluginId) => {
|
|
222
|
+
api = _api;
|
|
223
|
+
peer.selfName = pluginId;
|
|
224
|
+
},
|
|
225
|
+
onPluginReady: async (scrypted, params, plugin) => {
|
|
226
|
+
replPort = createREPLServer(scrypted, params, plugin);
|
|
227
|
+
},
|
|
228
|
+
getPluginConsole,
|
|
229
|
+
getDeviceConsole,
|
|
230
|
+
getMixinConsole,
|
|
231
|
+
async getServicePort(name, ...args: any[]) {
|
|
232
|
+
if (name === 'repl') {
|
|
233
|
+
if (!replPort)
|
|
234
|
+
throw new Error('REPL unavailable: Plugin not loaded.')
|
|
235
|
+
return replPort;
|
|
236
|
+
}
|
|
237
|
+
throw new Error(`unknown service ${name}`);
|
|
238
|
+
},
|
|
239
|
+
async onLoadZip(pluginReader: PluginReader, packageJson: any) {
|
|
240
|
+
const entry = pluginReader('main.nodejs.js.map')
|
|
241
|
+
const map = entry?.toString();
|
|
242
|
+
|
|
243
|
+
installSourceMapSupport({
|
|
244
|
+
environment: 'node',
|
|
245
|
+
retrieveSourceMap(source) {
|
|
246
|
+
if (source === '/plugin/main.nodejs.js' || source === `/${pluginId}/main.nodejs.js`) {
|
|
247
|
+
if (!map)
|
|
248
|
+
return null;
|
|
249
|
+
return {
|
|
250
|
+
url: '/plugin/main.nodejs.js',
|
|
251
|
+
map,
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
await installOptionalDependencies(getPluginConsole(), packageJson);
|
|
258
|
+
}
|
|
259
|
+
}).then(scrypted => {
|
|
260
|
+
systemManager = scrypted.systemManager;
|
|
261
|
+
deviceManager = scrypted.deviceManager;
|
|
262
|
+
|
|
263
|
+
process.on('uncaughtException', e => {
|
|
264
|
+
getPluginConsole().error('uncaughtException', e);
|
|
265
|
+
scrypted.log.e('uncaughtException ' + e?.toString());
|
|
266
|
+
});
|
|
267
|
+
process.on('unhandledRejection', e => {
|
|
268
|
+
getPluginConsole().error('unhandledRejection', e);
|
|
269
|
+
scrypted.log.e('unhandledRejection ' + e?.toString());
|
|
270
|
+
});
|
|
271
|
+
})
|
|
272
|
+
}
|
|
@@ -7,6 +7,8 @@ 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
9
|
import { createWebSocketClass, WebSocketConnectCallbacks, WebSocketMethods } from './plugin-remote-websocket';
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
const {link} = require('linkfs');
|
|
10
12
|
|
|
11
13
|
class DeviceLogger implements Logger {
|
|
12
14
|
nativeId: ScryptedNativeId;
|
|
@@ -306,13 +308,15 @@ export interface WebSocketCustomHandler {
|
|
|
306
308
|
methods: WebSocketMethods;
|
|
307
309
|
}
|
|
308
310
|
|
|
311
|
+
export type PluginReader = (name: string) => Buffer;
|
|
312
|
+
|
|
309
313
|
export interface PluginRemoteAttachOptions {
|
|
310
314
|
createMediaManager?: (systemManager: SystemManager) => Promise<MediaManager>;
|
|
311
315
|
getServicePort?: (name: string, ...args: any[]) => Promise<number>;
|
|
312
316
|
getDeviceConsole?: (nativeId?: ScryptedNativeId) => Console;
|
|
313
317
|
getPluginConsole?: () => Console;
|
|
314
318
|
getMixinConsole?: (id: string, nativeId?: ScryptedNativeId) => Console;
|
|
315
|
-
onLoadZip?: (
|
|
319
|
+
onLoadZip?: (pluginReader: PluginReader, packageJson: any) => Promise<void>;
|
|
316
320
|
onGetRemote?: (api: PluginAPI, pluginId: string) => Promise<void>;
|
|
317
321
|
onPluginReady?: (scrypted: ScryptedStatic, params: any, plugin: any) => Promise<void>;
|
|
318
322
|
}
|
|
@@ -434,26 +438,51 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
|
|
|
434
438
|
done(ret);
|
|
435
439
|
},
|
|
436
440
|
|
|
437
|
-
async loadZip(packageJson: any, zipData: Buffer, zipOptions?: PluginRemoteLoadZipOptions) {
|
|
441
|
+
async loadZip(packageJson: any, zipData: Buffer | string, zipOptions?: PluginRemoteLoadZipOptions) {
|
|
438
442
|
const pluginConsole = getPluginConsole?.();
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
+
|
|
444
|
+
let volume: any;
|
|
445
|
+
let pluginReader: PluginReader;
|
|
446
|
+
if (zipOptions?.unzippedPath && fs.existsSync(zipOptions?.unzippedPath)) {
|
|
447
|
+
volume = link(fs, ['', path.join(zipOptions.unzippedPath, 'fs')]);
|
|
448
|
+
pluginReader = name => {
|
|
449
|
+
const filename = path.join(zipOptions.unzippedPath, name);
|
|
450
|
+
if (!fs.existsSync(filename))
|
|
451
|
+
return;
|
|
452
|
+
return fs.readFileSync(filename);
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
const admZip = new AdmZip(zipData);
|
|
457
|
+
volume = new Volume();
|
|
458
|
+
for (const entry of admZip.getEntries()) {
|
|
459
|
+
if (entry.isDirectory)
|
|
460
|
+
continue;
|
|
461
|
+
if (!entry.entryName.startsWith('fs/'))
|
|
462
|
+
continue;
|
|
463
|
+
const name = entry.entryName.substring('fs/'.length);
|
|
464
|
+
volume.mkdirpSync(path.dirname(name));
|
|
465
|
+
const data = entry.getData();
|
|
466
|
+
volume.writeFileSync(name, data);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
pluginReader = name => {
|
|
470
|
+
const entry = admZip.getEntry(name);
|
|
471
|
+
if (!entry)
|
|
472
|
+
return;
|
|
473
|
+
return entry.getData();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
zipData = undefined;
|
|
477
|
+
|
|
478
|
+
await options?.onLoadZip?.(pluginReader, packageJson);
|
|
479
|
+
const main = pluginReader('main.nodejs.js');
|
|
480
|
+
pluginReader = undefined;
|
|
481
|
+
const script = main.toString();
|
|
443
482
|
const window: any = {};
|
|
444
483
|
const exports: any = window;
|
|
445
484
|
window.exports = exports;
|
|
446
485
|
|
|
447
|
-
const volume = new Volume();
|
|
448
|
-
for (const entry of zip.getEntries()) {
|
|
449
|
-
if (entry.isDirectory)
|
|
450
|
-
continue;
|
|
451
|
-
if (!entry.entryName.startsWith('fs/'))
|
|
452
|
-
continue;
|
|
453
|
-
const name = entry.entryName.substr('fs/'.length);
|
|
454
|
-
volume.mkdirpSync(path.dirname(name));
|
|
455
|
-
volume.writeFileSync(name, entry.getData());
|
|
456
|
-
}
|
|
457
486
|
|
|
458
487
|
function websocketConnect(url: string, protocols: any, callbacks: WebSocketConnectCallbacks) {
|
|
459
488
|
if (url.startsWith('io://') || url.startsWith('ws://')) {
|
package/src/plugin/system.ts
CHANGED
|
@@ -12,7 +12,7 @@ function newDeviceProxy(id: string, systemManager: SystemManagerImpl) {
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class DeviceProxyHandler implements PrimitiveProxyHandler<any>, ScryptedDevice {
|
|
15
|
-
device: ScryptedDevice
|
|
15
|
+
device: Promise<ScryptedDevice>;
|
|
16
16
|
constructor(public id: string, public systemManager: SystemManagerImpl) {
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -41,19 +41,20 @@ class DeviceProxyHandler implements PrimitiveProxyHandler<any>, ScryptedDevice {
|
|
|
41
41
|
return new Proxy(() => p, this);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
ensureDevice() {
|
|
45
45
|
if (!this.device)
|
|
46
|
-
this.device =
|
|
46
|
+
this.device = this.systemManager.api.getDeviceById(this.id);
|
|
47
|
+
return this.device;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
async apply(target: any, thisArg: any, argArray?: any) {
|
|
50
51
|
const method = target();
|
|
51
|
-
await this.ensureDevice();
|
|
52
|
+
const device = await this.ensureDevice();
|
|
52
53
|
if (false && method === 'refresh') {
|
|
53
54
|
const name = this.systemManager.state[this.id]?.[ScryptedInterfaceProperty.name].value;
|
|
54
55
|
this.systemManager.log.i(`requested refresh ${name}`);
|
|
55
56
|
}
|
|
56
|
-
return (
|
|
57
|
+
return (device as any)[method](...argArray);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
listen(event: string | EventListenerOptions, callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any) => void): EventListenerRegister {
|
|
@@ -84,7 +85,8 @@ class EventListenerRegisterImpl implements EventListenerRegister {
|
|
|
84
85
|
async removeListener(): Promise<void> {
|
|
85
86
|
try {
|
|
86
87
|
const register = await this.promise;
|
|
87
|
-
|
|
88
|
+
this.promise = undefined;
|
|
89
|
+
register?.removeListener();
|
|
88
90
|
}
|
|
89
91
|
catch (e) {
|
|
90
92
|
console.error('removeListener', e);
|
package/src/rpc.ts
CHANGED
|
@@ -135,6 +135,9 @@ class RpcProxy implements PrimitiveProxyHandler<any> {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
apply(target: any, thisArg: any, argArray?: any): any {
|
|
138
|
+
if (Object.isFrozen(this.peer.pendingResults))
|
|
139
|
+
return Promise.reject(new RPCResultError(this.peer, 'RpcPeer has been killed'));
|
|
140
|
+
|
|
138
141
|
// rpc objects can be functions. if the function is a oneway method,
|
|
139
142
|
// it will have a null in the oneway method list. this is because
|
|
140
143
|
// undefined is not JSON serializable.
|
|
@@ -509,7 +512,6 @@ export class RpcPeer {
|
|
|
509
512
|
const localProxiedEntry = this.localProxied.get(local);
|
|
510
513
|
// if a finalizer id is specified, it must match.
|
|
511
514
|
if (rpcFinalize.__local_proxy_finalizer_id && rpcFinalize.__local_proxy_finalizer_id !== localProxiedEntry?.finalizerId) {
|
|
512
|
-
console.error(this.selfName, this.peerName, 'finalizer mismatch')
|
|
513
515
|
break;
|
|
514
516
|
}
|
|
515
517
|
delete this.localProxyMap[rpcFinalize.__local_proxy_id];
|
package/src/runtime.ts
CHANGED
|
@@ -313,7 +313,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
|
|
316
|
+
handleEngineIOEndpoint(req: Request, res: ServerResponse, endpointRequest: HttpRequest, pluginData: HttpPluginData) {
|
|
317
317
|
const { pluginHost, pluginDevice } = pluginData;
|
|
318
318
|
|
|
319
319
|
(req as any).scrypted = {
|
|
@@ -326,7 +326,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
|
326
326
|
pluginHost.io.handleRequest(req, res);
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
|
|
329
|
+
handleRequestEndpoint(req: Request, res: Response, endpointRequest: HttpRequest, pluginData: HttpPluginData) {
|
|
330
330
|
const { pluginHost, pluginDevice } = pluginData;
|
|
331
331
|
const handler = this.getDevice<HttpRequestHandler>(pluginDevice._id);
|
|
332
332
|
if (handler.interfaces.includes(ScryptedInterface.EngineIOHandler) && req.headers.connection === 'upgrade' && req.headers.upgrade?.toLowerCase() === 'websocket') {
|
|
@@ -334,7 +334,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
|
334
334
|
console.log(ws);
|
|
335
335
|
});
|
|
336
336
|
}
|
|
337
|
-
handler.onRequest(endpointRequest, createResponseInterface(res, pluginHost.
|
|
337
|
+
handler.onRequest(endpointRequest, createResponseInterface(res, pluginHost.unzippedPath));
|
|
338
338
|
}
|
|
339
339
|
|
|
340
340
|
killPlugin(plugin: Plugin) {
|
|
@@ -465,7 +465,8 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
|
465
465
|
if (!device.interfaces.includes(ScryptedInterface.Readme)) {
|
|
466
466
|
const zipData = Buffer.from(plugin.zip, 'base64');
|
|
467
467
|
const adm = new AdmZip(zipData);
|
|
468
|
-
|
|
468
|
+
const entry = adm.getEntry('README.md');
|
|
469
|
+
if (entry) {
|
|
469
470
|
device.interfaces = device.interfaces.slice();
|
|
470
471
|
device.interfaces.push(ScryptedInterface.Readme);
|
|
471
472
|
}
|