@scrypted/server 0.1.14 → 0.2.1
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 +3 -4
- package/dist/event-registry.js.map +1 -1
- package/dist/http-interfaces.js +11 -0
- package/dist/http-interfaces.js.map +1 -1
- package/dist/plugin/media.js +78 -67
- package/dist/plugin/media.js.map +1 -1
- package/dist/plugin/plugin-api.js +1 -1
- package/dist/plugin/plugin-api.js.map +1 -1
- package/dist/plugin/plugin-device.js +25 -11
- package/dist/plugin/plugin-device.js.map +1 -1
- package/dist/plugin/plugin-host-api.js.map +1 -1
- package/dist/plugin/plugin-host.js +11 -6
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-http.js +1 -1
- package/dist/plugin/plugin-http.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +170 -17
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/plugin/plugin-remote.js +25 -85
- package/dist/plugin/plugin-remote.js.map +1 -1
- package/dist/plugin/runtime/node-fork-worker.js +11 -3
- package/dist/plugin/runtime/node-fork-worker.js.map +1 -1
- package/dist/plugin/socket-serializer.js +17 -0
- package/dist/plugin/socket-serializer.js.map +1 -0
- package/dist/rpc-serializer.js +23 -10
- package/dist/rpc-serializer.js.map +1 -1
- package/dist/rpc.js +3 -3
- package/dist/rpc.js.map +1 -1
- package/dist/runtime.js +36 -33
- package/dist/runtime.js.map +1 -1
- package/dist/scrypted-plugin-main.js +4 -1
- package/dist/scrypted-plugin-main.js.map +1 -1
- package/dist/scrypted-server-main.js +53 -12
- package/dist/scrypted-server-main.js.map +1 -1
- package/dist/server-settings.js +5 -1
- package/dist/server-settings.js.map +1 -1
- package/dist/state.js +2 -1
- package/dist/state.js.map +1 -1
- package/package.json +5 -12
- package/src/event-registry.ts +3 -4
- package/src/http-interfaces.ts +13 -0
- package/src/plugin/media.ts +94 -75
- package/src/plugin/plugin-api.ts +5 -4
- package/src/plugin/plugin-device.ts +25 -11
- package/src/plugin/plugin-host-api.ts +1 -1
- package/src/plugin/plugin-host.ts +6 -5
- package/src/plugin/plugin-http.ts +2 -2
- package/src/plugin/plugin-remote-worker.ts +211 -23
- package/src/plugin/plugin-remote.ts +31 -95
- package/src/plugin/runtime/node-fork-worker.ts +11 -3
- package/src/plugin/runtime/runtime-worker.ts +1 -1
- package/src/plugin/socket-serializer.ts +15 -0
- package/src/rpc-serializer.ts +30 -13
- package/src/rpc.ts +3 -2
- package/src/runtime.ts +37 -38
- package/src/scrypted-plugin-main.ts +4 -1
- package/src/scrypted-server-main.ts +59 -13
- package/src/state.ts +2 -1
package/src/plugin/media.ts
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
-
import
|
2
|
-
import { BufferConverter,
|
1
|
+
import ffmpegInstaller from '@ffmpeg-installer/ffmpeg';
|
2
|
+
import { BufferConverter, DeviceManager, FFmpegInput, MediaManager, MediaObject, MediaObjectOptions, MediaStreamUrl, ScryptedInterface, ScryptedInterfaceProperty, ScryptedMimeTypes, ScryptedNativeId, SystemDeviceState, SystemManager } from "@scrypted/types";
|
3
3
|
import axios from 'axios';
|
4
|
-
import child_process from 'child_process';
|
5
|
-
import { once } from 'events';
|
6
4
|
import fs from 'fs';
|
7
5
|
import https from 'https';
|
8
6
|
import mimeType from 'mime';
|
@@ -10,8 +8,6 @@ import mkdirp from "mkdirp";
|
|
10
8
|
import Graph from 'node-dijkstra';
|
11
9
|
import os from 'os';
|
12
10
|
import path from 'path';
|
13
|
-
import rimraf from "rimraf";
|
14
|
-
import tmp from 'tmp';
|
15
11
|
import MimeType from 'whatwg-mimetype';
|
16
12
|
import { MediaObjectRemote } from "./plugin-api";
|
17
13
|
|
@@ -28,14 +24,28 @@ function mimeMatches(target: MimeType, candidate: MimeType) {
|
|
28
24
|
|
29
25
|
const httpsAgent = new https.Agent({
|
30
26
|
rejectUnauthorized: false
|
31
|
-
})
|
27
|
+
});
|
28
|
+
|
29
|
+
type IdBufferConverter = BufferConverter & {
|
30
|
+
id: string;
|
31
|
+
};
|
32
|
+
|
33
|
+
function getBuiltinId(n: number) {
|
34
|
+
return 'builtin-' + n;
|
35
|
+
}
|
36
|
+
|
37
|
+
function getExtraId(n: number) {
|
38
|
+
return 'extra-' + n;
|
39
|
+
}
|
32
40
|
|
33
41
|
export abstract class MediaManagerBase implements MediaManager {
|
34
|
-
builtinConverters:
|
42
|
+
builtinConverters: IdBufferConverter[] = [];
|
43
|
+
extraConverters: IdBufferConverter[] = [];
|
35
44
|
|
36
45
|
constructor() {
|
37
46
|
for (const h of ['http', 'https']) {
|
38
47
|
this.builtinConverters.push({
|
48
|
+
id: getBuiltinId(this.builtinConverters.length),
|
39
49
|
fromMimeType: ScryptedMimeTypes.SchemePrefix + h,
|
40
50
|
toMimeType: ScryptedMimeTypes.MediaObject,
|
41
51
|
convert: async (data, fromMimeType, toMimeType) => {
|
@@ -51,18 +61,32 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
51
61
|
}
|
52
62
|
|
53
63
|
this.builtinConverters.push({
|
64
|
+
id: getBuiltinId(this.builtinConverters.length),
|
54
65
|
fromMimeType: ScryptedMimeTypes.SchemePrefix + 'file',
|
55
66
|
toMimeType: ScryptedMimeTypes.MediaObject,
|
56
67
|
convert: async (data, fromMimeType, toMimeType) => {
|
57
|
-
const
|
68
|
+
const url = data.toString();
|
69
|
+
const filename = url.substring('file:'.length);
|
70
|
+
|
71
|
+
if (toMimeType === ScryptedMimeTypes.FFmpegInput) {
|
72
|
+
const ffmpegInput: FFmpegInput = {
|
73
|
+
url,
|
74
|
+
inputArguments: [
|
75
|
+
'-i', filename,
|
76
|
+
]
|
77
|
+
};
|
78
|
+
return this.createFFmpegMediaObject(ffmpegInput);
|
79
|
+
}
|
80
|
+
|
58
81
|
const ab = await fs.promises.readFile(filename);
|
59
|
-
const mt = mimeType.
|
82
|
+
const mt = mimeType.getType(data.toString());
|
60
83
|
const mo = this.createMediaObject(ab, mt);
|
61
84
|
return mo;
|
62
85
|
}
|
63
86
|
});
|
64
87
|
|
65
88
|
this.builtinConverters.push({
|
89
|
+
id: getBuiltinId(this.builtinConverters.length),
|
66
90
|
fromMimeType: ScryptedMimeTypes.Url,
|
67
91
|
toMimeType: ScryptedMimeTypes.FFmpegInput,
|
68
92
|
async convert(data, fromMimeType): Promise<Buffer> {
|
@@ -79,6 +103,7 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
79
103
|
});
|
80
104
|
|
81
105
|
this.builtinConverters.push({
|
106
|
+
id: getBuiltinId(this.builtinConverters.length),
|
82
107
|
fromMimeType: ScryptedMimeTypes.FFmpegInput,
|
83
108
|
toMimeType: ScryptedMimeTypes.MediaStreamUrl,
|
84
109
|
async convert(data: Buffer, fromMimeType: string): Promise<Buffer> {
|
@@ -87,6 +112,7 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
87
112
|
});
|
88
113
|
|
89
114
|
this.builtinConverters.push({
|
115
|
+
id: getBuiltinId(this.builtinConverters.length),
|
90
116
|
fromMimeType: ScryptedMimeTypes.MediaStreamUrl,
|
91
117
|
toMimeType: ScryptedMimeTypes.FFmpegInput,
|
92
118
|
async convert(data, fromMimeType: string): Promise<Buffer> {
|
@@ -111,52 +137,24 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
111
137
|
}
|
112
138
|
});
|
113
139
|
|
140
|
+
// todo: move this to snapshot plugin
|
114
141
|
this.builtinConverters.push({
|
142
|
+
id: getBuiltinId(this.builtinConverters.length),
|
115
143
|
fromMimeType: 'image/*',
|
116
144
|
toMimeType: 'image/*',
|
117
145
|
convert: async (data, fromMimeType: string): Promise<Buffer> => {
|
118
146
|
return data as Buffer;
|
119
147
|
}
|
120
148
|
});
|
149
|
+
}
|
121
150
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
const console = this.getMixinConsole(options?.sourceId, undefined);
|
127
|
-
|
128
|
-
const ffInput: FFmpegInput = JSON.parse(data.toString());
|
151
|
+
async addConverter(converter: IdBufferConverter): Promise<void> {
|
152
|
+
converter.id = getExtraId(this.extraConverters.length);
|
153
|
+
this.extraConverters.push(converter);
|
154
|
+
}
|
129
155
|
|
130
|
-
|
131
|
-
|
132
|
-
];
|
133
|
-
args.push(...ffInput.inputArguments);
|
134
|
-
|
135
|
-
const tmpfile = tmp.fileSync();
|
136
|
-
try {
|
137
|
-
args.push('-y', "-vframes", "1", '-f', 'image2', tmpfile.name);
|
138
|
-
|
139
|
-
const cp = child_process.spawn(await this.getFFmpegPath(), args);
|
140
|
-
console.log('converting ffmpeg input to image.');
|
141
|
-
// ffmpegLogInitialOutput(console, cp);
|
142
|
-
cp.on('error', (code) => {
|
143
|
-
console.error('ffmpeg error code', code);
|
144
|
-
})
|
145
|
-
const to = setTimeout(() => {
|
146
|
-
console.log('ffmpeg stream to image convesion timed out.');
|
147
|
-
cp.kill('SIGKILL');
|
148
|
-
}, 10000);
|
149
|
-
clearTimeout(to);
|
150
|
-
const [exitCode] = await once(cp, 'exit');
|
151
|
-
if (exitCode)
|
152
|
-
throw new Error(`ffmpeg stream to image convesion failed with exit code: ${exitCode}`);
|
153
|
-
return fs.readFileSync(tmpfile.name);
|
154
|
-
}
|
155
|
-
finally {
|
156
|
-
rimraf.sync(tmpfile.name);
|
157
|
-
}
|
158
|
-
}
|
159
|
-
});
|
156
|
+
async clearConverters(): Promise<void> {
|
157
|
+
this.extraConverters = [];
|
160
158
|
}
|
161
159
|
|
162
160
|
async convertMediaObjectToJSON<T>(mediaObject: MediaObject, toMimeType: string): Promise<T> {
|
@@ -188,7 +186,7 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
188
186
|
return f;
|
189
187
|
|
190
188
|
const defaultPath = os.platform() === 'win32' ? 'ffmpeg.exe' : 'ffmpeg';
|
191
|
-
return
|
189
|
+
return ffmpegInstaller.path || defaultPath;
|
192
190
|
}
|
193
191
|
|
194
192
|
async getFilesPath(): Promise<string> {
|
@@ -200,17 +198,24 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
200
198
|
return ret;
|
201
199
|
}
|
202
200
|
|
203
|
-
getConverters():
|
201
|
+
getConverters(): IdBufferConverter[] {
|
204
202
|
const converters = Object.entries(this.getSystemState())
|
205
203
|
.filter(([id, state]) => state[ScryptedInterfaceProperty.interfaces]?.value?.includes(ScryptedInterface.BufferConverter))
|
206
|
-
.map(([id]) => this.getDeviceById<
|
204
|
+
.map(([id]) => this.getDeviceById<IdBufferConverter>(id));
|
205
|
+
|
206
|
+
// builtins should be after system converters. these should not be overriden by system,
|
207
|
+
// as it could cause system instability with misconfiguration.
|
207
208
|
converters.push(...this.builtinConverters);
|
209
|
+
|
210
|
+
// extra converters are added last and do allow overriding builtins, as
|
211
|
+
// the instability would be confined to a single plugin.
|
212
|
+
converters.push(...this.extraConverters);
|
208
213
|
return converters;
|
209
214
|
}
|
210
215
|
|
211
216
|
ensureMediaObjectRemote(mediaObject: string | MediaObject): MediaObjectRemote {
|
212
217
|
if (typeof mediaObject === 'string') {
|
213
|
-
const mime = mimeType.
|
218
|
+
const mime = mimeType.getType(mediaObject);
|
214
219
|
return this.createMediaObjectRemote(mediaObject, mime);
|
215
220
|
}
|
216
221
|
return mediaObject as MediaObjectRemote;
|
@@ -301,7 +306,7 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
301
306
|
return this.createMediaObjectRemote(data, mimeType, options);
|
302
307
|
}
|
303
308
|
|
304
|
-
async convert(converters:
|
309
|
+
async convert(converters: IdBufferConverter[], mediaObject: MediaObjectRemote, toMimeType: string): Promise<{ data: Buffer | string | any, mimeType: string }> {
|
305
310
|
// console.log('converting', mediaObject.mimeType, toMimeType);
|
306
311
|
const mediaMime = new MimeType(mediaObject.mimeType);
|
307
312
|
const outputMime = new MimeType(toMimeType);
|
@@ -318,13 +323,9 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
318
323
|
sourceId = this.getPluginDeviceId();
|
319
324
|
const console = this.getMixinConsole(sourceId, undefined);
|
320
325
|
|
321
|
-
const
|
322
|
-
const
|
323
|
-
|
324
|
-
for (const converter of converters) {
|
325
|
-
const cid = (id++).toString();
|
326
|
-
converterIds.set(converter, cid);
|
327
|
-
converterReverseids.set(cid, converter);
|
326
|
+
const converterMap = new Map<string, IdBufferConverter>();
|
327
|
+
for (const c of converters) {
|
328
|
+
converterMap.set(c.id, c);
|
328
329
|
}
|
329
330
|
|
330
331
|
const nodes: any = {};
|
@@ -335,31 +336,36 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
335
336
|
try {
|
336
337
|
const inputMime = new MimeType(converter.fromMimeType);
|
337
338
|
const convertedMime = new MimeType(converter.toMimeType);
|
338
|
-
|
339
|
+
// catch all converters should be heavily weighted so as not to use them.
|
340
|
+
const inputWeight = parseFloat(inputMime.parameters.get('converter-weight')) || (inputMime.essence === '*/*' ? 1000 : 1);
|
341
|
+
// const convertedWeight = parseFloat(convertedMime.parameters.get('converter-weight')) || (convertedMime.essence === ScryptedMimeTypes.MediaObject ? 1000 : 1);
|
342
|
+
// const conversionWeight = inputWeight + convertedWeight;
|
343
|
+
const targetId = converter.id;
|
339
344
|
const node: any = nodes[targetId] = {};
|
345
|
+
|
346
|
+
// edge matches
|
340
347
|
for (const candidate of converters) {
|
341
348
|
try {
|
342
349
|
const candidateMime = new MimeType(candidate.fromMimeType);
|
343
350
|
if (!mimeMatches(convertedMime, candidateMime))
|
344
351
|
continue;
|
345
|
-
const
|
346
|
-
|
352
|
+
const outputWeight = parseFloat(candidateMime.parameters.get('converter-weight')) || (candidateMime.essence === '*/*' ? 1000 : 1);
|
353
|
+
const candidateId = candidate.id;
|
354
|
+
node[candidateId] = inputWeight + outputWeight;
|
347
355
|
}
|
348
356
|
catch (e) {
|
349
357
|
console.warn('skipping converter due to error', e)
|
350
358
|
}
|
351
359
|
}
|
352
360
|
|
353
|
-
//
|
361
|
+
// source matches
|
354
362
|
if (mimeMatches(mediaMime, inputMime)) {
|
355
|
-
|
356
|
-
mediaNode[targetId] = inputMime.essence === '*/*' ? 1000 : 1;
|
363
|
+
mediaNode[targetId] = inputWeight;
|
357
364
|
}
|
358
365
|
|
359
366
|
// target output matches
|
360
367
|
if (mimeMatches(outputMime, convertedMime) || converter.toMimeType === ScryptedMimeTypes.MediaObject) {
|
361
|
-
|
362
|
-
node['output'] = converter.toMimeType === ScryptedMimeTypes.MediaObject ? 1000 : 1;
|
368
|
+
node['output'] = inputWeight;
|
363
369
|
}
|
364
370
|
}
|
365
371
|
catch (e) {
|
@@ -382,13 +388,21 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
382
388
|
let value = await mediaObject.getData();
|
383
389
|
let valueMime = new MimeType(mediaObject.mimeType);
|
384
390
|
|
385
|
-
|
386
|
-
const
|
391
|
+
while (route.length) {
|
392
|
+
const node = route.shift();
|
393
|
+
const converter = converterMap.get(node);
|
387
394
|
const converterToMimeType = new MimeType(converter.toMimeType);
|
388
395
|
const converterFromMimeType = new MimeType(converter.fromMimeType);
|
389
396
|
const type = converterToMimeType.type === '*' ? valueMime.type : converterToMimeType.type;
|
390
397
|
const subtype = converterToMimeType.subtype === '*' ? valueMime.subtype : converterToMimeType.subtype;
|
391
|
-
|
398
|
+
let targetMimeType = `${type}/${subtype}`;
|
399
|
+
if (!route.length && outputMime.parameters.size) {
|
400
|
+
const withParameters = new MimeType(targetMimeType);
|
401
|
+
for (const k of outputMime.parameters.keys()) {
|
402
|
+
withParameters.parameters.set(k, outputMime.parameters.get(k));
|
403
|
+
}
|
404
|
+
targetMimeType = outputMime.toString();
|
405
|
+
}
|
392
406
|
|
393
407
|
if (converter.toMimeType === ScryptedMimeTypes.MediaObject) {
|
394
408
|
const mo = await converter.convert(value, valueMime.essence, toMimeType, { sourceId }) as MediaObject;
|
@@ -413,6 +427,15 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
413
427
|
export class MediaManagerImpl extends MediaManagerBase {
|
414
428
|
constructor(public systemManager: SystemManager, public deviceManager: DeviceManager) {
|
415
429
|
super();
|
430
|
+
|
431
|
+
this.builtinConverters.push({
|
432
|
+
id: getBuiltinId(this.builtinConverters.length),
|
433
|
+
fromMimeType: ScryptedMimeTypes.ScryptedDeviceId,
|
434
|
+
toMimeType: ScryptedMimeTypes.ScryptedDevice,
|
435
|
+
convert: async (data, fromMimeType, toMimeType) => {
|
436
|
+
return this.getDeviceById(data.toString());
|
437
|
+
}
|
438
|
+
});
|
416
439
|
}
|
417
440
|
|
418
441
|
getSystemState(): { [id: string]: { [property: string]: SystemDeviceState; }; } {
|
@@ -436,16 +459,12 @@ export class MediaManagerImpl extends MediaManagerBase {
|
|
436
459
|
|
437
460
|
export class MediaManagerHostImpl extends MediaManagerBase {
|
438
461
|
constructor(public pluginDeviceId: string,
|
439
|
-
public
|
462
|
+
public getSystemState: () => { [id: string]: { [property: string]: SystemDeviceState } },
|
440
463
|
public console: Console,
|
441
464
|
public getDeviceById: (id: string) => any) {
|
442
465
|
super();
|
443
466
|
}
|
444
467
|
|
445
|
-
getSystemState(): { [id: string]: { [property: string]: SystemDeviceState; }; } {
|
446
|
-
return this.systemState;
|
447
|
-
}
|
448
|
-
|
449
468
|
getPluginDeviceId(): string {
|
450
469
|
return this.pluginDeviceId;
|
451
470
|
}
|
package/src/plugin/plugin-api.ts
CHANGED
@@ -11,8 +11,8 @@ export interface PluginAPI {
|
|
11
11
|
setState(nativeId: ScryptedNativeId, key: string, value: any): Promise<void>;
|
12
12
|
onDevicesChanged(deviceManifest: DeviceManifest): Promise<void>;
|
13
13
|
onDeviceDiscovered(device: Device): Promise<string>;
|
14
|
-
onDeviceEvent(nativeId: ScryptedNativeId, eventInterface:
|
15
|
-
onMixinEvent(id: string, nativeId: ScryptedNativeId, eventInterface:
|
14
|
+
onDeviceEvent(nativeId: ScryptedNativeId, eventInterface: string, eventData?: any): Promise<void>;
|
15
|
+
onMixinEvent(id: string, nativeId: ScryptedNativeId, eventInterface: string, eventData?: any): Promise<void>;
|
16
16
|
onDeviceRemoved(nativeId: string): Promise<void>;
|
17
17
|
setStorage(nativeId: string, storage: {[key: string]: any}): Promise<void>;
|
18
18
|
|
@@ -89,8 +89,8 @@ export class PluginAPIProxy extends PluginAPIManagedListeners implements PluginA
|
|
89
89
|
onDeviceEvent(nativeId: ScryptedNativeId, eventInterface: any, eventData?: any): Promise<void> {
|
90
90
|
return this.api.onDeviceEvent(nativeId, eventInterface, eventData);
|
91
91
|
}
|
92
|
-
onMixinEvent(id: string, nativeId: ScryptedNativeId, eventInterface:
|
93
|
-
return this.api.onMixinEvent(nativeId, eventInterface, eventData);
|
92
|
+
onMixinEvent(id: string, nativeId: ScryptedNativeId, eventInterface: string, eventData?: any): Promise<void> {
|
93
|
+
return this.api.onMixinEvent(id, nativeId, eventInterface, eventData);
|
94
94
|
}
|
95
95
|
onDeviceRemoved(nativeId: string): Promise<void> {
|
96
96
|
return this.api.onDeviceRemoved(nativeId);
|
@@ -147,6 +147,7 @@ export interface PluginRemoteLoadZipOptions {
|
|
147
147
|
* exist on the "remote", if it is not the same machine.
|
148
148
|
*/
|
149
149
|
unzippedPath?: string;
|
150
|
+
fork?: boolean;
|
150
151
|
}
|
151
152
|
|
152
153
|
export interface PluginRemote {
|
@@ -216,15 +216,24 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
|
|
216
216
|
let { allInterfaces } = await previousEntry;
|
217
217
|
try {
|
218
218
|
const mixinProvider = this.scrypted.getDevice(mixinId) as ScryptedDevice & MixinProvider;
|
219
|
-
const
|
219
|
+
const isMixinProvider = mixinProvider?.interfaces?.includes(ScryptedInterface.MixinProvider);
|
220
|
+
const interfaces = isMixinProvider && await mixinProvider?.canMixin(type, allInterfaces) as any as ScryptedInterface[];
|
220
221
|
if (!interfaces) {
|
221
222
|
// this is not an error
|
222
223
|
// do not advertise interfaces so it is skipped during
|
223
224
|
// vtable lookup.
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
225
|
+
if (!mixinProvider || (isMixinProvider && !interfaces)) {
|
226
|
+
console.log(`Mixin provider ${mixinId} can no longer mixin ${this.id}. Removing.`, {
|
227
|
+
mixinProvider: !!mixinProvider,
|
228
|
+
interfaces,
|
229
|
+
});
|
230
|
+
const mixins: string[] = getState(pluginDevice, ScryptedInterfaceProperty.mixins) || [];
|
231
|
+
this.scrypted.stateManager.setPluginDeviceState(pluginDevice, ScryptedInterfaceProperty.mixins, mixins.filter(mid => mid !== mixinId))
|
232
|
+
this.scrypted.datastore.upsert(pluginDevice);
|
233
|
+
}
|
234
|
+
else {
|
235
|
+
console.log(`Mixin provider ${mixinId} can not mixin ${this.id}. It is no longer a MixinProvider. This may be temporary. Passing through.`);
|
236
|
+
}
|
228
237
|
return {
|
229
238
|
passthrough: true,
|
230
239
|
allInterfaces,
|
@@ -353,12 +362,17 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
|
|
353
362
|
for (const mixin of this.mixinTable) {
|
354
363
|
const entry = await mixin.entry;
|
355
364
|
if (!entry.methods) {
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
365
|
+
if (entry.interfaces.size) {
|
366
|
+
const pluginDevice = this.scrypted.findPluginDeviceById(mixin.mixinProviderId || this.id);
|
367
|
+
const plugin = this.scrypted.plugins[pluginDevice.pluginId];
|
368
|
+
let methods = new Set<string>(getInterfaceMethods(ScryptedInterfaceDescriptors, entry.interfaces))
|
369
|
+
if (plugin.api.descriptors)
|
370
|
+
methods = new Set<string>([...methods, ...getInterfaceMethods(plugin.api.descriptors, entry.interfaces)]);
|
371
|
+
entry.methods = methods;
|
372
|
+
}
|
373
|
+
else {
|
374
|
+
entry.methods = new Set();
|
375
|
+
}
|
362
376
|
}
|
363
377
|
if (entry.methods.has(method)) {
|
364
378
|
return { mixin, entry };
|
@@ -47,7 +47,7 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
|
|
47
47
|
|
48
48
|
// do we care about mixin validation here?
|
49
49
|
// maybe to prevent/notify errant dangling events?
|
50
|
-
async onMixinEvent(id: string, nativeIdOrMixinDevice: ScryptedNativeId | any, eventInterface:
|
50
|
+
async onMixinEvent(id: string, nativeIdOrMixinDevice: ScryptedNativeId | any, eventInterface: string, eventData?: any) {
|
51
51
|
// nativeId code path has been deprecated in favor of mixin object 12/10/2021
|
52
52
|
const device = this.scrypted.findPluginDeviceById(id);
|
53
53
|
|
@@ -135,7 +135,7 @@ export class PluginHost {
|
|
135
135
|
this.io.on('connection', async (socket) => {
|
136
136
|
try {
|
137
137
|
try {
|
138
|
-
if (socket.request.url.indexOf('/api') !== -1) {
|
138
|
+
if (socket.request.url.indexOf('/engine.io/api') !== -1) {
|
139
139
|
if (socket.request.url.indexOf('/public') !== -1) {
|
140
140
|
socket.close();
|
141
141
|
return;
|
@@ -179,7 +179,7 @@ export class PluginHost {
|
|
179
179
|
|
180
180
|
const { runtime } = this.packageJson.scrypted;
|
181
181
|
const mediaManager = runtime === 'python'
|
182
|
-
? new MediaManagerHostImpl(pluginDeviceId, scrypted.stateManager.getSystemState(), console, id => scrypted.getDevice(id))
|
182
|
+
? new MediaManagerHostImpl(pluginDeviceId, () => scrypted.stateManager.getSystemState(), console, id => scrypted.getDevice(id))
|
183
183
|
: undefined;
|
184
184
|
|
185
185
|
this.api = new PluginHostAPI(scrypted, this.pluginId, this, mediaManager);
|
@@ -214,7 +214,6 @@ export class PluginHost {
|
|
214
214
|
await remote.setNativeId(pluginDevice.nativeId, pluginDevice._id, pluginDevice.storage || {});
|
215
215
|
}
|
216
216
|
|
217
|
-
await remote.setSystemState(scrypted.stateManager.getSystemState());
|
218
217
|
const waitDebug = pluginDebug?.waitDebug;
|
219
218
|
if (waitDebug) {
|
220
219
|
console.info('waiting for debugger...');
|
@@ -374,7 +373,8 @@ export class PluginHost {
|
|
374
373
|
serializer.setupRpcPeer(rpcPeer);
|
375
374
|
|
376
375
|
// wrap the host api with a connection specific api that can be torn down on disconnect
|
377
|
-
const
|
376
|
+
const createMediaManager = await this.peer.getParam('createMediaManager');
|
377
|
+
const api = new PluginAPIProxy(this.api, await createMediaManager());
|
378
378
|
const kill = () => {
|
379
379
|
serializer.onDisconnected();
|
380
380
|
api.removeListeners();
|
@@ -389,7 +389,8 @@ export class PluginHost {
|
|
389
389
|
const rpcPeer = createDuplexRpcPeer(`api/${this.pluginId}`, 'duplex', duplex, duplex);
|
390
390
|
|
391
391
|
// wrap the host api with a connection specific api that can be torn down on disconnect
|
392
|
-
const
|
392
|
+
const createMediaManager = await this.peer.getParam('createMediaManager');
|
393
|
+
const api = new PluginAPIProxy(this.api, await createMediaManager());
|
393
394
|
const kill = () => {
|
394
395
|
api.removeListeners();
|
395
396
|
};
|
@@ -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(endpoint: string, isUpgrade: boolean, isEngineIOEndpoint: boolean): Promise<T>;
|
41
|
+
abstract getEndpointPluginData(req: Request, endpoint: string, isUpgrade: boolean, isEngineIOEndpoint: boolean): Promise<T>;
|
42
42
|
abstract handleWebSocket(endpoint: string, httpRequest: HttpRequest, ws: WebSocket, pluginData: T): Promise<void>;
|
43
43
|
|
44
44
|
async endpointHandler(req: Request, res: Response, isPublicEndpoint: boolean, isEngineIOEndpoint: boolean,
|
@@ -75,7 +75,7 @@ export abstract class PluginHttp<T> {
|
|
75
75
|
return;
|
76
76
|
}
|
77
77
|
|
78
|
-
const pluginData = await this.getEndpointPluginData(endpoint, isUpgrade, isEngineIOEndpoint);
|
78
|
+
const pluginData = await this.getEndpointPluginData(req, endpoint, isUpgrade, isEngineIOEndpoint);
|
79
79
|
if (!pluginData) {
|
80
80
|
end(404, 'Not Found');
|
81
81
|
return;
|