@scrypted/server 0.144.1 → 0.144.3
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.
- package/dist/cert.js +1 -1
- package/dist/cert.js.map +1 -1
- package/dist/cluster/cluster-labels.d.ts +3 -3
- package/dist/cluster/cluster-labels.js +2 -2
- package/dist/cluster/cluster-labels.js.map +1 -1
- package/dist/cluster/cluster-setup.d.ts +3 -3
- package/dist/cluster/cluster-setup.js +3 -3
- package/dist/cluster/cluster-setup.js.map +1 -1
- package/dist/db-types.d.ts +4 -4
- package/dist/db-types.js.map +1 -1
- package/dist/event-registry.d.ts +2 -2
- package/dist/event-registry.js +9 -7
- package/dist/event-registry.js.map +1 -1
- package/dist/fetch/index.d.ts +2 -2
- package/dist/fetch/index.js +3 -6
- package/dist/fetch/index.js.map +1 -1
- package/dist/http-interfaces.js +1 -1
- package/dist/http-interfaces.js.map +1 -1
- package/dist/infer-defaults.d.ts +4 -4
- package/dist/infer-defaults.js +8 -4
- package/dist/infer-defaults.js.map +1 -1
- package/dist/ip.js +2 -1
- package/dist/ip.js.map +1 -1
- package/dist/level.d.ts +3 -3
- package/dist/level.js +1 -0
- package/dist/level.js.map +1 -1
- package/dist/listen-zero.js.map +1 -1
- package/dist/mixin/mixin-cycle.js +6 -6
- package/dist/mixin/mixin-cycle.js.map +1 -1
- package/dist/plugin/acl.js.map +1 -1
- package/dist/plugin/cluster.js.map +1 -1
- package/dist/plugin/descriptor.js +1 -1
- package/dist/plugin/descriptor.js.map +1 -1
- package/dist/plugin/device.d.ts +6 -6
- package/dist/plugin/device.js.map +1 -1
- package/dist/plugin/endpoint.js.map +1 -1
- package/dist/plugin/media.js.map +1 -1
- package/dist/plugin/mediaobject.d.ts +1 -1
- package/dist/plugin/mediaobject.js.map +1 -1
- package/dist/plugin/plugin-api.d.ts +6 -6
- package/dist/plugin/plugin-api.js +2 -2
- package/dist/plugin/plugin-api.js.map +1 -1
- package/dist/plugin/plugin-console.js +11 -10
- package/dist/plugin/plugin-console.js.map +1 -1
- package/dist/plugin/plugin-device.d.ts +3 -3
- package/dist/plugin/plugin-device.js +2 -0
- package/dist/plugin/plugin-device.js.map +1 -1
- package/dist/plugin/plugin-host-api.d.ts +1 -1
- package/dist/plugin/plugin-host-api.js +19 -5
- package/dist/plugin/plugin-host-api.js.map +1 -1
- package/dist/plugin/plugin-host.js +3 -2
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-http.d.ts +1 -1
- package/dist/plugin/plugin-http.js +5 -1
- package/dist/plugin/plugin-http.js.map +1 -1
- package/dist/plugin/plugin-lazy-remote.js.map +1 -1
- package/dist/plugin/plugin-remote-websocket.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +5 -2
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/plugin/plugin-remote.js +1 -1
- package/dist/plugin/plugin-remote.js.map +1 -1
- package/dist/plugin/plugin-repl.js +6 -2
- package/dist/plugin/plugin-repl.js.map +1 -1
- package/dist/plugin/runtime/child-process-worker.d.ts +4 -4
- package/dist/plugin/runtime/child-process-worker.js +1 -2
- package/dist/plugin/runtime/child-process-worker.js.map +1 -1
- package/dist/plugin/runtime/cluster-fork-worker.d.ts +1 -1
- package/dist/plugin/runtime/cluster-fork-worker.js +2 -0
- package/dist/plugin/runtime/cluster-fork-worker.js.map +1 -1
- package/dist/plugin/runtime/custom-worker.js.map +1 -1
- package/dist/plugin/runtime/node-fork-worker.d.ts +1 -1
- package/dist/plugin/runtime/node-fork-worker.js.map +1 -1
- package/dist/plugin/runtime/node-thread-worker.js.map +1 -1
- package/dist/plugin/runtime/python-worker.js +1 -1
- package/dist/plugin/runtime/python-worker.js.map +1 -1
- package/dist/plugin/runtime/runtime-worker.d.ts +1 -1
- package/dist/plugin/system.d.ts +1 -1
- package/dist/plugin/system.js +1 -0
- package/dist/plugin/system.js.map +1 -1
- package/dist/promise-utils.d.ts +1 -1
- package/dist/promise-utils.js.map +1 -1
- package/dist/rpc-buffer-serializer.d.ts +1 -1
- package/dist/rpc-serializer.js.map +1 -1
- package/dist/rpc.d.ts +10 -9
- package/dist/rpc.js +15 -8
- package/dist/rpc.js.map +1 -1
- package/dist/runtime.d.ts +11 -11
- package/dist/runtime.js +17 -18
- package/dist/runtime.js.map +1 -1
- package/dist/scrypted-cluster-main.js +3 -2
- package/dist/scrypted-cluster-main.js.map +1 -1
- package/dist/scrypted-plugin-main.js +1 -1
- package/dist/scrypted-plugin-main.js.map +1 -1
- package/dist/scrypted-server-main.js +8 -5
- package/dist/scrypted-server-main.js.map +1 -1
- package/dist/server-settings.d.ts +1 -1
- package/dist/server-settings.js +4 -4
- package/dist/server-settings.js.map +1 -1
- package/dist/services/addresses.d.ts +1 -1
- package/dist/services/addresses.js +2 -2
- package/dist/services/addresses.js.map +1 -1
- package/dist/services/alerts.js +2 -2
- package/dist/services/alerts.js.map +1 -1
- package/dist/services/cluster-fork.js.map +1 -1
- package/dist/services/plugin.d.ts +5 -5
- package/dist/services/plugin.js.map +1 -1
- package/dist/services/service-control.js.map +1 -1
- package/dist/services/users.d.ts +2 -2
- package/dist/services/users.js.map +1 -1
- package/dist/state.d.ts +7 -7
- package/dist/state.js +4 -4
- package/dist/state.js.map +1 -1
- package/dist/threading.js.map +1 -1
- package/dist/usertoken.d.ts +2 -2
- package/dist/usertoken.js.map +1 -1
- package/package.json +2 -2
- package/python/cluster_setup.py +13 -4
- package/python/plugin_remote.py +22 -18
- package/python/rpc_reader.py +3 -5
- package/src/cert.ts +2 -2
- package/src/cluster/cluster-labels.ts +4 -4
- package/src/cluster/cluster-setup.ts +20 -20
- package/src/db-types.ts +16 -16
- package/src/event-registry.ts +13 -11
- package/src/fetch/index.ts +4 -7
- package/src/http-interfaces.ts +2 -2
- package/src/infer-defaults.ts +12 -8
- package/src/ip.ts +2 -1
- package/src/level.ts +10 -9
- package/src/listen-zero.ts +1 -1
- package/src/mixin/mixin-cycle.ts +6 -6
- package/src/plugin/acl.ts +1 -1
- package/src/plugin/cluster.ts +2 -2
- package/src/plugin/descriptor.ts +2 -2
- package/src/plugin/device.ts +16 -16
- package/src/plugin/endpoint.ts +4 -4
- package/src/plugin/media.ts +15 -15
- package/src/plugin/mediaobject.ts +3 -3
- package/src/plugin/plugin-api.ts +7 -7
- package/src/plugin/plugin-console.ts +13 -12
- package/src/plugin/plugin-device.ts +20 -18
- package/src/plugin/plugin-host-api.ts +27 -13
- package/src/plugin/plugin-host.ts +26 -25
- package/src/plugin/plugin-http.ts +9 -5
- package/src/plugin/plugin-lazy-remote.ts +1 -1
- package/src/plugin/plugin-remote-websocket.ts +1 -1
- package/src/plugin/plugin-remote-worker.ts +22 -20
- package/src/plugin/plugin-remote.ts +14 -14
- package/src/plugin/plugin-repl.ts +6 -2
- package/src/plugin/runtime/child-process-worker.ts +11 -12
- package/src/plugin/runtime/cluster-fork-worker.ts +2 -0
- package/src/plugin/runtime/custom-worker.ts +6 -6
- package/src/plugin/runtime/node-fork-worker.ts +3 -3
- package/src/plugin/runtime/node-thread-worker.ts +3 -3
- package/src/plugin/runtime/python-worker.ts +14 -14
- package/src/plugin/runtime/runtime-worker.ts +1 -1
- package/src/plugin/system.ts +24 -23
- package/src/promise-utils.ts +4 -4
- package/src/rpc-serializer.ts +3 -3
- package/src/rpc.ts +25 -17
- package/src/runtime.ts +59 -60
- package/src/scrypted-cluster-main.ts +19 -18
- package/src/scrypted-plugin-main.ts +2 -2
- package/src/scrypted-server-main.ts +17 -14
- package/src/server-settings.ts +5 -5
- package/src/services/addresses.ts +3 -3
- package/src/services/alerts.ts +2 -2
- package/src/services/cluster-fork.ts +9 -9
- package/src/services/plugin.ts +15 -15
- package/src/services/service-control.ts +1 -1
- package/src/services/users.ts +3 -3
- package/src/state.ts +21 -21
- package/src/threading.ts +5 -5
- package/src/usertoken.ts +1 -1
|
@@ -102,7 +102,7 @@ export function prepareConsoles(getConsoleName: () => string, systemManager: ()
|
|
|
102
102
|
return ret;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
const mixinConsoles = new Map<
|
|
105
|
+
const mixinConsoles = new Map<ScryptedNativeId, Map<string, Console>>();
|
|
106
106
|
|
|
107
107
|
function getMixinConsole(mixinId: string, nativeId: ScryptedNativeId) {
|
|
108
108
|
let nativeIdConsoles = mixinConsoles.get(nativeId);
|
|
@@ -179,7 +179,7 @@ export function prepareConsoles(getConsoleName: () => string, systemManager: ()
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
export async function createConsoleServer(remoteStdout: Readable, remoteStderr: Readable, header: string) {
|
|
182
|
-
const outputs = new Map<
|
|
182
|
+
const outputs = new Map<ScryptedNativeId, StdPassThroughs>();
|
|
183
183
|
|
|
184
184
|
const addHeader = (pts: StdPassThroughs) => {
|
|
185
185
|
pts.buffers.push(Buffer.from(header));
|
|
@@ -199,6 +199,7 @@ export async function createConsoleServer(remoteStdout: Readable, remoteStderr:
|
|
|
199
199
|
buffers: [],
|
|
200
200
|
}
|
|
201
201
|
outputs.set(nativeId, pts);
|
|
202
|
+
const ptsRef = pts;
|
|
202
203
|
|
|
203
204
|
let writeTimestamp = true;
|
|
204
205
|
let timestampTimer: NodeJS.Timeout;
|
|
@@ -206,7 +207,7 @@ export async function createConsoleServer(remoteStdout: Readable, remoteStderr:
|
|
|
206
207
|
stderr.on('close', () => clearTimeout(timestampTimer));
|
|
207
208
|
|
|
208
209
|
const appendOutput = (data: Buffer) => {
|
|
209
|
-
const { buffers } =
|
|
210
|
+
const { buffers } = ptsRef;
|
|
210
211
|
|
|
211
212
|
if (writeTimestamp) {
|
|
212
213
|
writeTimestamp = false;
|
|
@@ -220,7 +221,7 @@ export async function createConsoleServer(remoteStdout: Readable, remoteStderr:
|
|
|
220
221
|
// when we're over 4000 lines or whatever these buffer are,
|
|
221
222
|
// truncate down to 2000.
|
|
222
223
|
if (buffers.length > 4000)
|
|
223
|
-
|
|
224
|
+
ptsRef.buffers = buffers.slice(buffers.length - 2000);
|
|
224
225
|
};
|
|
225
226
|
|
|
226
227
|
stdout.on('data', appendOutput);
|
|
@@ -279,15 +280,14 @@ export async function createConsoleServer(remoteStdout: Readable, remoteStderr:
|
|
|
279
280
|
const { server: writeServer, port: writePort } = await clusterListenZero(async (socket) => {
|
|
280
281
|
sockets.add(socket);
|
|
281
282
|
const [data] = await once(socket, 'data');
|
|
282
|
-
let
|
|
283
|
-
const newline =
|
|
283
|
+
let filterStr: string = data.toString();
|
|
284
|
+
const newline = filterStr.indexOf('\n');
|
|
284
285
|
if (newline !== -1) {
|
|
285
|
-
socket.unshift(Buffer.from(
|
|
286
|
+
socket.unshift(Buffer.from(filterStr.substring(newline + 1)));
|
|
286
287
|
}
|
|
287
|
-
|
|
288
|
+
filterStr = filterStr.substring(0, newline);
|
|
288
289
|
|
|
289
|
-
|
|
290
|
-
filter = undefined;
|
|
290
|
+
const filter = filterStr === 'undefined' ? undefined : filterStr;
|
|
291
291
|
|
|
292
292
|
const { stdout } = getPassthroughs(filter);
|
|
293
293
|
socket.pipe(stdout, { end: false });
|
|
@@ -306,9 +306,10 @@ export async function createConsoleServer(remoteStdout: Readable, remoteStderr:
|
|
|
306
306
|
return {
|
|
307
307
|
clear(nativeId: ScryptedNativeId) {
|
|
308
308
|
const pt = outputs.get(nativeId);
|
|
309
|
-
if (pt)
|
|
309
|
+
if (pt) {
|
|
310
310
|
pt.buffers = [];
|
|
311
|
-
|
|
311
|
+
addHeader(pt);
|
|
312
|
+
}
|
|
312
313
|
},
|
|
313
314
|
destroy() {
|
|
314
315
|
for (const socket of sockets) {
|
|
@@ -31,7 +31,7 @@ export const QueryInterfaceSymbol = Symbol("ScryptedPluginDeviceQueryInterface")
|
|
|
31
31
|
export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
32
32
|
scrypted: ScryptedRuntime;
|
|
33
33
|
id: string;
|
|
34
|
-
mixinTable
|
|
34
|
+
mixinTable!: MixinTable[];
|
|
35
35
|
releasing = new Set<any>();
|
|
36
36
|
|
|
37
37
|
static sortInterfaces(interfaces: string[]): string[] {
|
|
@@ -65,8 +65,8 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
65
65
|
async getMixinProviderId(id: string, mixinDevice: any) {
|
|
66
66
|
if (this.releasing.has(mixinDevice))
|
|
67
67
|
return true;
|
|
68
|
-
await this.scrypted.devices[id]
|
|
69
|
-
for (const mixin of this.scrypted.devices[id]
|
|
68
|
+
await this.scrypted.devices[id]!.handler.ensureProxy();
|
|
69
|
+
for (const mixin of this.scrypted.devices[id]!.handler.mixinTable) {
|
|
70
70
|
const { proxy } = await mixin.entry;
|
|
71
71
|
if (proxy === mixinDevice) {
|
|
72
72
|
return mixin.mixinProviderId || id;
|
|
@@ -78,7 +78,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
78
78
|
// should this be async?
|
|
79
79
|
invalidate() {
|
|
80
80
|
const mixinTable = this.mixinTable;
|
|
81
|
-
this.mixinTable = undefined
|
|
81
|
+
this.mixinTable = undefined!;
|
|
82
82
|
for (const mixinEntry of (mixinTable || [])) {
|
|
83
83
|
this.invalidateEntry(mixinEntry);
|
|
84
84
|
}
|
|
@@ -102,7 +102,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
102
102
|
const mixins = getState(pluginDevice, ScryptedInterfaceProperty.mixins) || [];
|
|
103
103
|
// iterate the new mixin table to find the last good mixin,
|
|
104
104
|
// and resume creation from there.
|
|
105
|
-
let lastValidMixinId: string;
|
|
105
|
+
let lastValidMixinId: string | undefined;
|
|
106
106
|
for (const mixinId of mixins) {
|
|
107
107
|
if (!previousMixinIds.length) {
|
|
108
108
|
// reached of the previous mixin table, meaning
|
|
@@ -121,7 +121,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
121
121
|
|
|
122
122
|
// invalidate and remove everything up to lastValidMixinId
|
|
123
123
|
while (true) {
|
|
124
|
-
const entry = this.mixinTable[0]
|
|
124
|
+
const entry = this.mixinTable[0]!;
|
|
125
125
|
if (entry.mixinProviderId === lastValidMixinId)
|
|
126
126
|
break;
|
|
127
127
|
this.mixinTable.shift();
|
|
@@ -149,7 +149,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
149
149
|
try {
|
|
150
150
|
if (!pluginDevice.nativeId) {
|
|
151
151
|
const plugin = this.scrypted.plugins[pluginDevice.pluginId];
|
|
152
|
-
proxy = await plugin
|
|
152
|
+
proxy = await plugin!.module;
|
|
153
153
|
}
|
|
154
154
|
else {
|
|
155
155
|
const providerId = getState(pluginDevice, ScryptedInterfaceProperty.providerId);
|
|
@@ -174,7 +174,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
174
174
|
})();
|
|
175
175
|
|
|
176
176
|
this.mixinTable.unshift({
|
|
177
|
-
mixinProviderId: undefined
|
|
177
|
+
mixinProviderId: undefined!,
|
|
178
178
|
entry,
|
|
179
179
|
});
|
|
180
180
|
}
|
|
@@ -202,7 +202,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
202
202
|
});
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
return this.mixinTable[0]
|
|
205
|
+
return this.mixinTable[0]!.entry.then(entry => {
|
|
206
206
|
if (entry.error) {
|
|
207
207
|
console.error('Mixin device creation completed with error. Merging with previous interface set to retain device descriptor.');
|
|
208
208
|
const previousInterfaces = getState(pluginDevice, ScryptedInterfaceProperty.interfaces) as string[] || [];
|
|
@@ -219,7 +219,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
219
219
|
|
|
220
220
|
async rebuildEntry(pluginDevice: PluginDevice, mixinId: string, wrappedMixinTablePromise: Promise<MixinTable[]>): Promise<MixinTableEntry> {
|
|
221
221
|
const wrappedMixinTable = await wrappedMixinTablePromise;
|
|
222
|
-
const previousEntry = wrappedMixinTable[0]
|
|
222
|
+
const previousEntry = wrappedMixinTable[0]!.entry;
|
|
223
223
|
|
|
224
224
|
const type = getDisplayType(pluginDevice);
|
|
225
225
|
|
|
@@ -255,7 +255,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
255
255
|
passthrough: true,
|
|
256
256
|
allInterfaces,
|
|
257
257
|
interfaces: new Set<string>(),
|
|
258
|
-
proxy: undefined
|
|
258
|
+
proxy: undefined!,
|
|
259
259
|
};
|
|
260
260
|
}
|
|
261
261
|
|
|
@@ -269,7 +269,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
269
269
|
const wrappedProxy = new Proxy(wrappedHandler, wrappedHandler);
|
|
270
270
|
|
|
271
271
|
const implementer = await (mixinProvider as any)[QueryInterfaceSymbol](ScryptedInterface.MixinProvider);
|
|
272
|
-
const host = this.scrypted.getPluginHostForDeviceId(implementer)
|
|
272
|
+
const host = this.scrypted.getPluginHostForDeviceId(implementer)!;
|
|
273
273
|
const propertyInterfaces = getPropertyInterfaces(host.api.descriptors || ScryptedInterfaceDescriptors);
|
|
274
274
|
// todo: remove this and pass the setter directly.
|
|
275
275
|
const deviceState = await host.remote.createDeviceState(this.id,
|
|
@@ -304,8 +304,8 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
304
304
|
passthrough: false,
|
|
305
305
|
allInterfaces,
|
|
306
306
|
interfaces: new Set<string>(),
|
|
307
|
-
error: e,
|
|
308
|
-
proxy: undefined
|
|
307
|
+
error: e as Error,
|
|
308
|
+
proxy: undefined!,
|
|
309
309
|
};
|
|
310
310
|
}
|
|
311
311
|
}
|
|
@@ -328,7 +328,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
328
328
|
if (p === RefreshSymbol || p === QueryInterfaceSymbol)
|
|
329
329
|
return new Proxy(() => p, this);
|
|
330
330
|
|
|
331
|
-
if (ScryptedInterfaceDescriptors[ScryptedInterface.ScryptedDevice]
|
|
331
|
+
if (ScryptedInterfaceDescriptors[ScryptedInterface.ScryptedDevice]!.methods.includes(prop))
|
|
332
332
|
return (this as any)[p].bind(this);
|
|
333
333
|
|
|
334
334
|
return new Proxy(() => prop, this);
|
|
@@ -382,7 +382,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
382
382
|
throw new PluginError(`${method} not implemented`)
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
-
async findMethod(method: string) {
|
|
385
|
+
async findMethod(method: string): Promise<{ mixin: MixinTable, entry: MixinTableEntry } | undefined> {
|
|
386
386
|
for (const mixin of this.mixinTable) {
|
|
387
387
|
const entry = await mixin.entry;
|
|
388
388
|
if (!entry.methods) {
|
|
@@ -402,9 +402,10 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
402
402
|
return { mixin, entry };
|
|
403
403
|
}
|
|
404
404
|
}
|
|
405
|
+
return undefined;
|
|
405
406
|
}
|
|
406
407
|
|
|
407
|
-
async findMixin(iface: string) {
|
|
408
|
+
async findMixin(iface: string): Promise<{ mixin: MixinTable, entry: MixinTableEntry } | undefined> {
|
|
408
409
|
for (const mixin of this.mixinTable) {
|
|
409
410
|
const entry = await mixin.entry;
|
|
410
411
|
const { interfaces } = entry;
|
|
@@ -412,6 +413,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
412
413
|
return { mixin, entry };
|
|
413
414
|
}
|
|
414
415
|
}
|
|
416
|
+
return undefined;
|
|
415
417
|
}
|
|
416
418
|
|
|
417
419
|
async apply(target: any, thisArg: any, argArray?: any): Promise<any> {
|
|
@@ -443,7 +445,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any> {
|
|
|
443
445
|
if (method === 'getReadmeMarkdown') {
|
|
444
446
|
const pluginDevice = this.scrypted.findPluginDeviceById(this.id);
|
|
445
447
|
if (pluginDevice && !pluginDevice.nativeId) {
|
|
446
|
-
const plugin = this.scrypted.plugins[pluginDevice.pluginId]
|
|
448
|
+
const plugin = this.scrypted.plugins[pluginDevice.pluginId]!;
|
|
447
449
|
if (!plugin.packageJson.scrypted.interfaces.includes(ScryptedInterface.Readme)) {
|
|
448
450
|
const readmePath = path.join(plugin.unzippedPath, 'README.md');
|
|
449
451
|
if (fs.existsSync(readmePath)) {
|
|
@@ -12,9 +12,9 @@ import { checkProperty } from './plugin-state-check';
|
|
|
12
12
|
|
|
13
13
|
export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAPI {
|
|
14
14
|
pluginId: string;
|
|
15
|
-
typesVersion
|
|
16
|
-
descriptors
|
|
17
|
-
propertyInterfaces
|
|
15
|
+
typesVersion!: string;
|
|
16
|
+
descriptors!: { [scryptedInterface: string]: ScryptedInterfaceDescriptor };
|
|
17
|
+
propertyInterfaces!: ReturnType<typeof getPropertyInterfaces>;
|
|
18
18
|
|
|
19
19
|
[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS] = [
|
|
20
20
|
'onMixinEvent',
|
|
@@ -56,12 +56,16 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
|
|
|
56
56
|
const nativeId: string = nativeIdOrMixinDevice;
|
|
57
57
|
// todo: deprecate this code path
|
|
58
58
|
const mixinProvider = this.scrypted.findPluginDevice(this.pluginId, nativeId);
|
|
59
|
+
if (!mixinProvider)
|
|
60
|
+
throw new Error(`mixin provider not found for native id ${nativeId}`);
|
|
59
61
|
const mixins: string[] = getState(device, ScryptedInterfaceProperty.mixins) || [];
|
|
60
62
|
if (!mixins.includes(mixinProvider._id))
|
|
61
63
|
throw new Error(`${mixinProvider._id} is not a mixin provider for ${id}`);
|
|
62
64
|
|
|
63
65
|
this.scrypted.findPluginDevice(this.pluginId, nativeId);
|
|
64
|
-
const tableEntry = this.scrypted.devices[device._id]
|
|
66
|
+
const tableEntry = this.scrypted.devices[device._id]?.handler?.mixinTable?.find((entry: any) => entry.mixinProviderId === mixinProvider._id);
|
|
67
|
+
if (!tableEntry)
|
|
68
|
+
throw new Error(`no mixin table entry found for ${mixinProvider._id} on ${id}`);
|
|
65
69
|
const { interfaces } = await tableEntry.entry;
|
|
66
70
|
if (!interfaces.has(eventInterface))
|
|
67
71
|
throw new Error(`${mixinProvider._id} does not mixin ${eventInterface} for ${id}`);
|
|
@@ -87,7 +91,7 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
|
|
|
87
91
|
|
|
88
92
|
async getLogger(nativeId: ScryptedNativeId): Promise<Logger> {
|
|
89
93
|
const device = this.scrypted.findPluginDevice(this.pluginId, nativeId);
|
|
90
|
-
return this.scrypted.getDeviceLogger(device)
|
|
94
|
+
return this.scrypted.getDeviceLogger(device!)!;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
async getComponent(id: string): Promise<any> {
|
|
@@ -103,7 +107,7 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
|
|
|
103
107
|
return this.scrypted.getComponent(id);
|
|
104
108
|
}
|
|
105
109
|
|
|
106
|
-
setDeviceProperty(id: string, property: ScryptedInterfaceProperty, value: any): Promise<void> {
|
|
110
|
+
async setDeviceProperty(id: string, property: ScryptedInterfaceProperty, value: any): Promise<void> {
|
|
107
111
|
switch (property) {
|
|
108
112
|
case ScryptedInterfaceProperty.room:
|
|
109
113
|
case ScryptedInterfaceProperty.type:
|
|
@@ -127,22 +131,32 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
|
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
async setStorage(nativeId: ScryptedNativeId, storage: { [key: string]: string }) {
|
|
130
|
-
const device = this.scrypted.findPluginDevice(this.pluginId, nativeId)
|
|
134
|
+
const device = this.scrypted.findPluginDevice(this.pluginId, nativeId)!;
|
|
131
135
|
device.storage = storage;
|
|
132
136
|
this.scrypted.datastore.upsert(device);
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
async onDevicesChanged(deviceManifest: DeviceManifest) {
|
|
136
140
|
const provider = this.scrypted.findPluginDevice(this.pluginId, deviceManifest.providerNativeId);
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
if (!provider)
|
|
142
|
+
throw new Error(`provider not found for plugin id ${this.pluginId} native id ${deviceManifest.providerNativeId}`);
|
|
143
|
+
const existing = this.scrypted.findPluginDevices(this.pluginId).filter(p => {
|
|
144
|
+
const state = p.state[ScryptedInterfaceProperty.providerId];
|
|
145
|
+
if (!state)
|
|
146
|
+
throw new Error(`providerId state not found for device ${p._id}`);
|
|
147
|
+
return state.value === provider._id;
|
|
148
|
+
});
|
|
149
|
+
if (!deviceManifest.devices)
|
|
150
|
+
throw new Error(`onDevicesChanged called without devices for provider ${deviceManifest.providerNativeId}`);
|
|
151
|
+
const devices = deviceManifest.devices;
|
|
152
|
+
const newIds = devices.map(device => device.nativeId);
|
|
139
153
|
const toRemove = existing.filter(e => e.nativeId && !newIds.includes(e.nativeId));
|
|
140
154
|
|
|
141
155
|
for (const remove of toRemove) {
|
|
142
156
|
await this.scrypted.removeDevice(remove);
|
|
143
157
|
}
|
|
144
158
|
|
|
145
|
-
for (const upsert of
|
|
159
|
+
for (const upsert of devices) {
|
|
146
160
|
upsert.providerNativeId = deviceManifest.providerNativeId;
|
|
147
161
|
const id = await this.pluginHost.upsertDevice(upsert);
|
|
148
162
|
this.scrypted.getDevice(id)?.probe().catch(() => { });
|
|
@@ -156,16 +170,16 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
|
|
|
156
170
|
}
|
|
157
171
|
|
|
158
172
|
async onDeviceRemoved(nativeId: string) {
|
|
159
|
-
await this.scrypted.removeDevice(this.scrypted.findPluginDevice(this.pluginId, nativeId))
|
|
173
|
+
await this.scrypted.removeDevice(this.scrypted.findPluginDevice(this.pluginId, nativeId)!)
|
|
160
174
|
}
|
|
161
175
|
|
|
162
176
|
async onDeviceEvent(nativeId: any, eventInterface: any, eventData?: any) {
|
|
163
|
-
const plugin = this.scrypted.findPluginDevice(this.pluginId, nativeId)
|
|
177
|
+
const plugin = this.scrypted.findPluginDevice(this.pluginId, nativeId)!;
|
|
164
178
|
this.scrypted.stateManager.notifyInterfaceEventFromMixin(plugin, eventInterface, eventData, plugin._id);
|
|
165
179
|
}
|
|
166
180
|
|
|
167
181
|
async getDeviceById<T>(id: string): Promise<T & ScryptedDevice> {
|
|
168
|
-
return this.scrypted.getDevice(id)
|
|
182
|
+
return this.scrypted.getDevice(id)!;
|
|
169
183
|
}
|
|
170
184
|
async listen(callback: (id: string, eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister> {
|
|
171
185
|
return this.manageListener(this.scrypted.stateManager.listen(callback));
|
|
@@ -36,8 +36,8 @@ export class UnsupportedRuntimeError extends Error {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export class PluginHost {
|
|
39
|
-
worker
|
|
40
|
-
peer
|
|
39
|
+
worker!: RuntimeWorker;
|
|
40
|
+
peer!: RpcPeer;
|
|
41
41
|
pluginId: string;
|
|
42
42
|
module: Promise<any>;
|
|
43
43
|
scrypted: ScryptedRuntime;
|
|
@@ -50,7 +50,7 @@ export class PluginHost {
|
|
|
50
50
|
perMessageDeflate: true,
|
|
51
51
|
cors: (req, callback) => {
|
|
52
52
|
const header = this.scrypted.getAccessControlAllowOrigin(req.headers);
|
|
53
|
-
callback(undefined
|
|
53
|
+
callback(undefined!, {
|
|
54
54
|
origin: header,
|
|
55
55
|
credentials: true,
|
|
56
56
|
})
|
|
@@ -61,11 +61,11 @@ export class PluginHost {
|
|
|
61
61
|
pluginName: string;
|
|
62
62
|
packageJson: any;
|
|
63
63
|
killed = false;
|
|
64
|
-
consoleServer
|
|
64
|
+
consoleServer!: Promise<ConsoleServer>;
|
|
65
65
|
zipHash: string;
|
|
66
66
|
zipFile: string;
|
|
67
67
|
unzippedPath: string;
|
|
68
|
-
clusterWorkerId
|
|
68
|
+
clusterWorkerId!: Promise<string>;
|
|
69
69
|
|
|
70
70
|
kill() {
|
|
71
71
|
this.killed = true;
|
|
@@ -103,10 +103,11 @@ export class PluginHost {
|
|
|
103
103
|
if (!needInvalidate) {
|
|
104
104
|
// may also need to invalidate if the the plugin did not previously return a device
|
|
105
105
|
// because it had not yet completed the discovery process.
|
|
106
|
-
const device = this.scrypted.devices[pi._id]
|
|
106
|
+
const device = this.scrypted.devices[pi._id]!;
|
|
107
107
|
try {
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
const handler = device.handler;
|
|
109
|
+
if (handler?.mixinTable)
|
|
110
|
+
needInvalidate = !(await handler.mixinTable?.[handler.mixinTable.length - 1]!.entry).proxy;
|
|
110
111
|
}
|
|
111
112
|
catch (e) {
|
|
112
113
|
// device retrieval had previously failed, fetch again.
|
|
@@ -125,16 +126,16 @@ export class PluginHost {
|
|
|
125
126
|
this.pluginName = plugin.packageJson?.name;
|
|
126
127
|
this.packageJson = plugin.packageJson;
|
|
127
128
|
|
|
128
|
-
const pluginDeviceId = scrypted.findPluginDevice(this.pluginId)
|
|
129
|
-
const logger = scrypted.getDeviceLogger(scrypted.findPluginDevice(this.pluginId))
|
|
129
|
+
const pluginDeviceId = scrypted.findPluginDevice(this.pluginId)!._id;
|
|
130
|
+
const logger = scrypted.getDeviceLogger(scrypted.findPluginDevice(this.pluginId)!)!;
|
|
130
131
|
|
|
131
132
|
const volume = getScryptedVolume();
|
|
132
133
|
const pluginVolume = ensurePluginVolume(this.pluginId);
|
|
133
134
|
|
|
134
135
|
{
|
|
135
|
-
const zipBuffer = Buffer.from(plugin.zip
|
|
136
|
+
const zipBuffer = Buffer.from(plugin.zip!, 'base64');
|
|
136
137
|
// allow garbage collection of the base 64 contents
|
|
137
|
-
plugin = undefined;
|
|
138
|
+
(plugin as any) = undefined;
|
|
138
139
|
const hash = crypto.createHash('md5').update(zipBuffer).digest().toString('hex');
|
|
139
140
|
this.zipHash = hash;
|
|
140
141
|
|
|
@@ -146,7 +147,7 @@ export class PluginHost {
|
|
|
146
147
|
const peerPromise = this.startPluginHost(logger, {
|
|
147
148
|
SCRYPTED_VOLUME: volume,
|
|
148
149
|
SCRYPTED_PLUGIN_VOLUME: pluginVolume,
|
|
149
|
-
}, pluginDebug);
|
|
150
|
+
}, pluginDebug!);
|
|
150
151
|
|
|
151
152
|
this.io.on('connection', async (socket) => {
|
|
152
153
|
try {
|
|
@@ -157,8 +158,8 @@ export class PluginHost {
|
|
|
157
158
|
} = (socket.request as any).scrypted;
|
|
158
159
|
|
|
159
160
|
try {
|
|
160
|
-
if (socket.request.url
|
|
161
|
-
if (socket.request.url
|
|
161
|
+
if (socket.request.url!.indexOf('/engine.io/api') !== -1) {
|
|
162
|
+
if (socket.request.url!.indexOf('/public') !== -1) {
|
|
162
163
|
socket.close();
|
|
163
164
|
return;
|
|
164
165
|
}
|
|
@@ -205,13 +206,13 @@ export class PluginHost {
|
|
|
205
206
|
? new MediaManagerHostImpl(pluginDeviceId, () => scrypted.stateManager.getSystemState(), console, id => scrypted.getDevice(id))
|
|
206
207
|
: undefined;
|
|
207
208
|
|
|
208
|
-
this.api = new PluginHostAPI(scrypted, this.pluginId, this, mediaManager);
|
|
209
|
+
this.api = new PluginHostAPI(scrypted, this.pluginId, this, mediaManager!);
|
|
209
210
|
|
|
210
211
|
logger.log('i', `loading ${this.pluginName}`);
|
|
211
212
|
logger.log('i', 'pid ' + this.worker?.pid);
|
|
212
213
|
|
|
213
|
-
const remotePromise = this.prepareRemote(peerPromise, logger, pluginDebug);
|
|
214
|
-
const init = this.initializeRemote(remotePromise, logger, pluginDebug);
|
|
214
|
+
const remotePromise = this.prepareRemote(peerPromise, logger, pluginDebug!);
|
|
215
|
+
const init = this.initializeRemote(remotePromise, logger, pluginDebug!);
|
|
215
216
|
|
|
216
217
|
init.catch(e => {
|
|
217
218
|
console.error('plugin failed to load', e);
|
|
@@ -280,7 +281,7 @@ export class PluginHost {
|
|
|
280
281
|
}
|
|
281
282
|
catch (e) {
|
|
282
283
|
logger.log('e', 'plugin failed to start ' + e);
|
|
283
|
-
throw new RPCResultError(this.peer, 'cluster plugin start failed', e);
|
|
284
|
+
throw new RPCResultError(this.peer, 'cluster plugin start failed', e as Error | undefined);
|
|
284
285
|
}
|
|
285
286
|
|
|
286
287
|
const startupTime = Date.now();
|
|
@@ -333,8 +334,8 @@ export class PluginHost {
|
|
|
333
334
|
let { runtime } = this.packageJson.scrypted;
|
|
334
335
|
runtime ||= 'node';
|
|
335
336
|
|
|
336
|
-
const pluginDevice = this.scrypted.findPluginDevice(this.pluginId)
|
|
337
|
-
const customRuntime = pluginDevice.state.interfaces
|
|
337
|
+
const pluginDevice = this.scrypted.findPluginDevice(this.pluginId)!;
|
|
338
|
+
const customRuntime = pluginDevice.state.interfaces!.value.includes(ScryptedInterface.ScryptedPluginRuntime);
|
|
338
339
|
if (customRuntime) {
|
|
339
340
|
runtime = 'custom';
|
|
340
341
|
}
|
|
@@ -375,7 +376,7 @@ export class PluginHost {
|
|
|
375
376
|
|
|
376
377
|
this.worker.stdout.on('data', data => console.log(data.toString()));
|
|
377
378
|
this.worker.stderr.on('data', data => console.error(data.toString()));
|
|
378
|
-
this.clusterWorkerId = Promise.resolve(undefined);
|
|
379
|
+
this.clusterWorkerId = Promise.resolve(undefined!);
|
|
379
380
|
}
|
|
380
381
|
else {
|
|
381
382
|
const scrypted: ClusterForkOptions = JSON.parse(JSON.stringify(this.packageJson.scrypted));
|
|
@@ -414,7 +415,7 @@ export class PluginHost {
|
|
|
414
415
|
peer.killedSafe.finally(() => originalPeer.kill());
|
|
415
416
|
}).catch(() => { });
|
|
416
417
|
|
|
417
|
-
this.clusterWorkerId = clusterWorkerId
|
|
418
|
+
this.clusterWorkerId = clusterWorkerId as Promise<string>;
|
|
418
419
|
clusterWorkerId.then(clusterWorkerId => {
|
|
419
420
|
console.log('cluster worker id', clusterWorkerId);
|
|
420
421
|
}).catch(() => {
|
|
@@ -483,7 +484,7 @@ export class PluginHost {
|
|
|
483
484
|
serializer.sendMessage(message, reject, serializationContext);
|
|
484
485
|
}
|
|
485
486
|
catch (e) {
|
|
486
|
-
reject?.(e);
|
|
487
|
+
reject?.(e as Error);
|
|
487
488
|
}
|
|
488
489
|
});
|
|
489
490
|
rpcPeer.tags.acl = accessControls;
|
|
@@ -500,6 +501,6 @@ export class PluginHost {
|
|
|
500
501
|
socket.on('close', kill);
|
|
501
502
|
socket.on('error', kill);
|
|
502
503
|
|
|
503
|
-
return setupPluginRemote(rpcPeer, api, null
|
|
504
|
+
return setupPluginRemote(rpcPeer, api, null!, { serverVersion }, () => this.scrypted.stateManager.getSystemState());
|
|
504
505
|
}
|
|
505
506
|
}
|
|
@@ -6,7 +6,7 @@ import WebSocket, { Server as WebSocketServer } from "ws";
|
|
|
6
6
|
|
|
7
7
|
export function isConnectionUpgrade(headers: IncomingHttpHeaders) {
|
|
8
8
|
// connection:'keep-alive, Upgrade'
|
|
9
|
-
return headers.connection?.toLowerCase().includes('upgrade');
|
|
9
|
+
return headers.connection?.toLowerCase().includes('upgrade') ?? false;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export abstract class PluginHttp<T> {
|
|
@@ -38,7 +38,7 @@ export abstract class PluginHttp<T> {
|
|
|
38
38
|
|
|
39
39
|
abstract handleEngineIOEndpoint(req: Request, res: ServerResponse, endpointRequest: HttpRequest, pluginData: T): void;
|
|
40
40
|
abstract handleRequestEndpoint(req: Request, res: Response, endpointRequest: HttpRequest, pluginData: T): void;
|
|
41
|
-
abstract getEndpointPluginData(req: Request, endpoint: string, isUpgrade: boolean, isEngineIOEndpoint: boolean): Promise<T>;
|
|
41
|
+
abstract getEndpointPluginData(req: Request, endpoint: string, isUpgrade: boolean, isEngineIOEndpoint: boolean): Promise<T | undefined>;
|
|
42
42
|
abstract handleWebSocket(endpoint: string, httpRequest: HttpRequest, ws: WebSocket, pluginData: T): Promise<void>;
|
|
43
43
|
abstract checkUpgrade(req: Request, res: Response, pluginData: T): void;
|
|
44
44
|
|
|
@@ -49,7 +49,7 @@ export abstract class PluginHttp<T> {
|
|
|
49
49
|
|
|
50
50
|
const end = (code: number, message: string) => {
|
|
51
51
|
if (isUpgrade) {
|
|
52
|
-
const socket = res.socket
|
|
52
|
+
const socket = res.socket!;
|
|
53
53
|
socket.write(`HTTP/1.1 ${code} ${message}\r\n` +
|
|
54
54
|
'\r\n');
|
|
55
55
|
socket.destroy();
|
|
@@ -65,6 +65,10 @@ export abstract class PluginHttp<T> {
|
|
|
65
65
|
let endpoint = pkg;
|
|
66
66
|
if (owner)
|
|
67
67
|
endpoint = `@${owner}/${endpoint}`;
|
|
68
|
+
if (!endpoint) {
|
|
69
|
+
end(400, 'Bad Request');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
68
72
|
const pluginData = await this.getEndpointPluginData(req, endpoint, isUpgrade, isEngineIOEndpoint);
|
|
69
73
|
|
|
70
74
|
if (!pluginData) {
|
|
@@ -125,7 +129,7 @@ export abstract class PluginHttp<T> {
|
|
|
125
129
|
catch (e) {
|
|
126
130
|
res.header('Content-Type', 'text/plain');
|
|
127
131
|
res.status(500);
|
|
128
|
-
res.send(e.toString());
|
|
132
|
+
res.send((e as Error).toString());
|
|
129
133
|
console.error(e);
|
|
130
134
|
}
|
|
131
135
|
}
|
|
@@ -136,7 +140,7 @@ export abstract class PluginHttp<T> {
|
|
|
136
140
|
catch (e) {
|
|
137
141
|
res.header('Content-Type', 'text/plain');
|
|
138
142
|
res.status(500);
|
|
139
|
-
res.send(e.toString());
|
|
143
|
+
res.send((e as Error).toString());
|
|
140
144
|
console.error(e);
|
|
141
145
|
}
|
|
142
146
|
}
|
|
@@ -8,7 +8,7 @@ import { PluginRemote, PluginRemoteLoadZipOptions, PluginZipAPI } from './plugin
|
|
|
8
8
|
* execution order of state reporting may fail.
|
|
9
9
|
*/
|
|
10
10
|
export class LazyRemote implements PluginRemote {
|
|
11
|
-
remote
|
|
11
|
+
remote!: PluginRemote;
|
|
12
12
|
|
|
13
13
|
constructor(public remotePromise: Promise<PluginRemote>, public remoteReadyPromise: Promise<PluginRemote>) {
|
|
14
14
|
this.remoteReadyPromise = (async () => {
|
|
@@ -74,7 +74,7 @@ export function createWebSocketClass(__websocketConnect: WebSocketConnect): any
|
|
|
74
74
|
|
|
75
75
|
class WebSocket extends WebSocketEventTarget {
|
|
76
76
|
_url: string;
|
|
77
|
-
_protocols: string[];
|
|
77
|
+
_protocols: string[] | undefined;
|
|
78
78
|
readyState: number;
|
|
79
79
|
|
|
80
80
|
constructor(public connection: WebSocketConnection, protocols?: string[]) {
|