@scrypted/server 0.0.132 → 0.0.137

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.

Files changed (53) hide show
  1. package/.vscode/settings.json +1 -1
  2. package/dist/event-registry.js +1 -1
  3. package/dist/event-registry.js.map +1 -1
  4. package/dist/infer-defaults.js +1 -1
  5. package/dist/infer-defaults.js.map +1 -1
  6. package/dist/media-helpers.js +20 -15
  7. package/dist/media-helpers.js.map +1 -1
  8. package/dist/plugin/descriptor.js +1 -1
  9. package/dist/plugin/descriptor.js.map +1 -1
  10. package/dist/plugin/media.js +207 -101
  11. package/dist/plugin/media.js.map +1 -1
  12. package/dist/plugin/plugin-device.js +5 -1
  13. package/dist/plugin/plugin-device.js.map +1 -1
  14. package/dist/plugin/plugin-host-api.js +1 -1
  15. package/dist/plugin/plugin-host-api.js.map +1 -1
  16. package/dist/plugin/plugin-host.js +15 -6
  17. package/dist/plugin/plugin-host.js.map +1 -1
  18. package/dist/plugin/plugin-remote.js +10 -3
  19. package/dist/plugin/plugin-remote.js.map +1 -1
  20. package/dist/plugin/system.js +1 -1
  21. package/dist/plugin/system.js.map +1 -1
  22. package/dist/runtime.js +36 -8
  23. package/dist/runtime.js.map +1 -1
  24. package/dist/services/plugin.js +2 -10
  25. package/dist/services/plugin.js.map +1 -1
  26. package/dist/state.js +1 -1
  27. package/dist/state.js.map +1 -1
  28. package/package.json +7 -5
  29. package/python/plugin-remote.py +5 -5
  30. package/src/db-types.ts +1 -1
  31. package/src/event-registry.ts +1 -1
  32. package/src/http-interfaces.ts +1 -1
  33. package/src/infer-defaults.ts +1 -1
  34. package/src/media-helpers.ts +16 -15
  35. package/src/plugin/descriptor.ts +1 -1
  36. package/src/plugin/media.ts +232 -116
  37. package/src/plugin/plugin-api.ts +1 -1
  38. package/src/plugin/plugin-console.ts +1 -1
  39. package/src/plugin/plugin-device.ts +7 -2
  40. package/src/plugin/plugin-host-api.ts +1 -1
  41. package/src/plugin/plugin-host.ts +17 -7
  42. package/src/plugin/plugin-http.ts +1 -1
  43. package/src/plugin/plugin-lazy-remote.ts +1 -1
  44. package/src/plugin/plugin-remote-worker.ts +1 -1
  45. package/src/plugin/plugin-remote.ts +12 -4
  46. package/src/plugin/plugin-repl.ts +1 -1
  47. package/src/plugin/system.ts +1 -1
  48. package/src/runtime.ts +42 -10
  49. package/src/services/plugin.ts +2 -10
  50. package/src/state.ts +1 -1
  51. package/dist/convert.js +0 -117
  52. package/dist/convert.js.map +0 -1
  53. package/src/convert.ts +0 -122
@@ -1,5 +1,4 @@
1
- import { ScryptedInterfaceProperty, SystemDeviceState, MediaStreamUrl, VideoCamera, Camera, BufferConverter, FFMpegInput, MediaManager, MediaObject, ScryptedDevice, ScryptedInterface, ScryptedMimeTypes, SystemManager, SCRYPTED_MEDIA_SCHEME } from "@scrypted/sdk/types";
2
- import { convert, ensureBuffer } from "../convert";
1
+ import { ScryptedInterfaceProperty, SystemDeviceState, MediaStreamUrl, VideoCamera, Camera, BufferConverter, FFMpegInput, MediaManager, MediaObject, ScryptedDevice, ScryptedInterface, ScryptedMimeTypes, SystemManager } from "@scrypted/types";
3
2
  import { MediaObjectRemote } from "./plugin-api";
4
3
  import mimeType from 'mime'
5
4
  import child_process from 'child_process';
@@ -9,106 +8,132 @@ import tmp from 'tmp';
9
8
  import os from 'os';
10
9
  import { getInstalledFfmpeg } from '@scrypted/ffmpeg'
11
10
  import { ffmpegLogInitialOutput } from "../media-helpers";
11
+ import Graph from 'node-dijkstra';
12
+ import MimeType from 'whatwg-mimetype';
13
+ import axios from 'axios';
14
+ import https from 'https';
15
+
16
+ function typeMatches(target: string, candidate: string): boolean {
17
+ // candidate will accept anything
18
+ if (candidate === '*')
19
+ return true;
20
+ return target === candidate;
21
+ }
12
22
 
13
- function addBuiltins(console: Console, mediaManager: MediaManager) {
14
- mediaManager.builtinConverters.push({
15
- fromMimeType: `${ScryptedMimeTypes.Url};${ScryptedMimeTypes.AcceptUrlParameter}=true`,
16
- toMimeType: ScryptedMimeTypes.FFmpegInput,
17
- async convert(data: string | Buffer, fromMimeType: string): Promise<Buffer | string> {
18
- const url = data.toString();
19
- const args: FFMpegInput = {
20
- url,
21
- inputArguments: [
22
- '-i',
23
- url,
24
- ],
25
- }
23
+ function mimeMatches(target: MimeType, candidate: MimeType) {
24
+ return typeMatches(target.type, candidate.type) && typeMatches(target.subtype, candidate.subtype);
25
+ }
26
26
 
27
- return Buffer.from(JSON.stringify(args));
28
- }
29
- });
27
+ const httpsAgent = new https.Agent({
28
+ rejectUnauthorized: false
29
+ })
30
30
 
31
- mediaManager.builtinConverters.push({
32
- fromMimeType: ScryptedMimeTypes.FFmpegInput,
33
- toMimeType: ScryptedMimeTypes.MediaStreamUrl,
34
- async convert(data: string | Buffer, fromMimeType: string): Promise<Buffer | string> {
35
- return data;
31
+ export abstract class MediaManagerBase implements MediaManager {
32
+ builtinConverters: BufferConverter[] = [];
33
+
34
+ constructor(public console: Console) {
35
+ for (const h of ['http', 'https']) {
36
+ this.builtinConverters.push({
37
+ fromMimeType: ScryptedMimeTypes.SchemePrefix + h,
38
+ toMimeType: ScryptedMimeTypes.MediaObject,
39
+ convert: async (data, fromMimeType, toMimeType) => {
40
+ const ab = await axios.get(data.toString(), {
41
+ responseType: 'arraybuffer',
42
+ httpsAgent,
43
+ });
44
+ const mimeType = ab.headers['content-type'] || toMimeType;
45
+ const mo = this.createMediaObject(Buffer.from(ab.data), mimeType);
46
+ return mo;
47
+ }
48
+ });
36
49
  }
37
- });
38
-
39
- mediaManager.builtinConverters.push({
40
- fromMimeType: ScryptedMimeTypes.MediaStreamUrl,
41
- toMimeType: ScryptedMimeTypes.FFmpegInput,
42
- async convert(data: string | Buffer, fromMimeType: string): Promise<Buffer | string> {
43
- const mediaUrl: MediaStreamUrl = JSON.parse(data.toString());
44
-
45
- const inputArguments: string[] = [
46
- '-i',
47
- mediaUrl.url,
48
- ];
49
-
50
- if (mediaUrl.url.startsWith('rtsp://')) {
51
- inputArguments.unshift(
52
- // should this be set here? configurable?
53
- // do we ever want udp?
54
- "-rtsp_transport",
55
- "tcp",
56
- // 10 seconds
57
- '-analyzeduration', '10000000',
58
- // 20mb
59
- '-probesize', '20000000',
60
- "-reorder_queue_size",
61
- "1024",
62
- "-max_delay",
63
- // 10 second delay
64
- "10000000",
65
- )
66
- }
67
50
 
68
- const ret: FFMpegInput = Object.assign({
69
- inputArguments,
70
- }, mediaUrl);
51
+ this.builtinConverters.push({
52
+ fromMimeType: `${ScryptedMimeTypes.Url};${ScryptedMimeTypes.AcceptUrlParameter}=true`,
53
+ toMimeType: ScryptedMimeTypes.FFmpegInput,
54
+ async convert(data, fromMimeType): Promise<Buffer> {
55
+ const url = data.toString();
56
+ const args: FFMpegInput = {
57
+ url,
58
+ inputArguments: [
59
+ '-i', url,
60
+ ],
61
+ }
71
62
 
72
- return Buffer.from(JSON.stringify(ret));
73
- }
74
- })
75
-
76
- mediaManager.builtinConverters.push({
77
- fromMimeType: ScryptedMimeTypes.FFmpegInput,
78
- toMimeType: 'image/jpeg',
79
- async convert(data: string | Buffer, fromMimeType: string): Promise<Buffer | string> {
80
- const ffInput: FFMpegInput = JSON.parse(data.toString());
81
-
82
- const args = [
83
- '-hide_banner',
84
- ];
85
- args.push(...ffInput.inputArguments);
86
-
87
- const tmpfile = tmp.fileSync();
88
- args.push('-y', "-vframes", "1", '-f', 'image2', tmpfile.name);
89
-
90
- const cp = child_process.spawn(await mediaManager.getFFmpegPath(), args);
91
- ffmpegLogInitialOutput(console, cp);
92
- cp.on('error', (code) => {
93
- console.error('ffmpeg error code', code);
94
- })
95
- const to = setTimeout(() => {
96
- console.log('ffmpeg stream to image convesion timed out.');
97
- cp.kill('SIGKILL');
98
- }, 10000);
99
- await once(cp, 'exit');
100
- clearTimeout(to);
101
- const ret = fs.readFileSync(tmpfile.name);
102
- return ret;
103
- }
104
- });
105
- }
63
+ return Buffer.from(JSON.stringify(args));
64
+ }
65
+ });
106
66
 
107
- export abstract class MediaManagerBase implements MediaManager {
108
- builtinConverters: BufferConverter[] = [];
67
+ this.builtinConverters.push({
68
+ fromMimeType: ScryptedMimeTypes.FFmpegInput,
69
+ toMimeType: ScryptedMimeTypes.MediaStreamUrl,
70
+ async convert(data: Buffer, fromMimeType: string): Promise<Buffer> {
71
+ return data;
72
+ }
73
+ });
74
+
75
+ this.builtinConverters.push({
76
+ fromMimeType: ScryptedMimeTypes.MediaStreamUrl,
77
+ toMimeType: ScryptedMimeTypes.FFmpegInput,
78
+ async convert(data, fromMimeType: string): Promise<Buffer> {
79
+ const mediaUrl: MediaStreamUrl = JSON.parse(data.toString());
80
+
81
+ const inputArguments: string[] = [
82
+ '-i', mediaUrl.url,
83
+ ];
84
+
85
+ if (mediaUrl.url.startsWith('rtsp://')) {
86
+ inputArguments.unshift(
87
+ "-rtsp_transport", "tcp",
88
+ "-max_delay", "1000000",
89
+ );
90
+ }
91
+
92
+ const ret: FFMpegInput = Object.assign({
93
+ inputArguments,
94
+ }, mediaUrl);
95
+
96
+ return Buffer.from(JSON.stringify(ret));
97
+ }
98
+ });
109
99
 
110
- constructor(public console: Console) {
111
- addBuiltins(this.console, this);
100
+ this.builtinConverters.push({
101
+ fromMimeType: 'image/*',
102
+ toMimeType: 'image/*',
103
+ convert: async (data, fromMimeType: string): Promise<Buffer> => {
104
+ return data as Buffer;
105
+ }
106
+ });
107
+
108
+ this.builtinConverters.push({
109
+ fromMimeType: ScryptedMimeTypes.FFmpegInput,
110
+ toMimeType: 'image/jpeg',
111
+ convert: async (data, fromMimeType: string): Promise<Buffer> => {
112
+ const ffInput: FFMpegInput = JSON.parse(data.toString());
113
+
114
+ const args = [
115
+ '-hide_banner',
116
+ ];
117
+ args.push(...ffInput.inputArguments);
118
+
119
+ const tmpfile = tmp.fileSync();
120
+ args.push('-y', "-vframes", "1", '-f', 'image2', tmpfile.name);
121
+
122
+ const cp = child_process.spawn(await this.getFFmpegPath(), args);
123
+ ffmpegLogInitialOutput(console, cp);
124
+ cp.on('error', (code) => {
125
+ console.error('ffmpeg error code', code);
126
+ })
127
+ const to = setTimeout(() => {
128
+ console.log('ffmpeg stream to image convesion timed out.');
129
+ cp.kill('SIGKILL');
130
+ }, 10000);
131
+ await once(cp, 'exit');
132
+ clearTimeout(to);
133
+ const ret = fs.readFileSync(tmpfile.name);
134
+ return ret;
135
+ }
136
+ });
112
137
  }
113
138
 
114
139
  abstract getSystemState(): { [id: string]: { [property: string]: SystemDeviceState } };
@@ -153,26 +178,26 @@ export abstract class MediaManagerBase implements MediaManager {
153
178
  }
154
179
 
155
180
  async convertMediaObjectToInsecureLocalUrl(mediaObject: string | MediaObject, toMimeType: string): Promise<string> {
156
- const intermediate = await convert(this.getConverters(), this.ensureMediaObjectRemote(mediaObject), toMimeType);
181
+ const intermediate = await this.convert(this.getConverters(), this.ensureMediaObjectRemote(mediaObject), toMimeType);
157
182
  const converted = this.createMediaObject(intermediate.data, intermediate.mimeType);
158
- const url = await convert(this.getConverters(), converted, ScryptedMimeTypes.InsecureLocalUrl);
183
+ const url = await this.convert(this.getConverters(), converted, ScryptedMimeTypes.InsecureLocalUrl);
159
184
  return url.data.toString();
160
185
  }
161
186
 
162
187
  async convertMediaObjectToBuffer(mediaObject: MediaObject, toMimeType: string): Promise<Buffer> {
163
- const intermediate = await convert(this.getConverters(), this.ensureMediaObjectRemote(mediaObject), toMimeType);
164
- return ensureBuffer(intermediate.data);
188
+ const intermediate = await this.convert(this.getConverters(), this.ensureMediaObjectRemote(mediaObject), toMimeType);
189
+ return intermediate.data as Buffer;
165
190
  }
166
191
  async convertMediaObjectToLocalUrl(mediaObject: string | MediaObject, toMimeType: string): Promise<string> {
167
- const intermediate = await convert(this.getConverters(), this.ensureMediaObjectRemote(mediaObject), toMimeType);
192
+ const intermediate = await this.convert(this.getConverters(), this.ensureMediaObjectRemote(mediaObject), toMimeType);
168
193
  const converted = this.createMediaObject(intermediate.data, intermediate.mimeType);
169
- const url = await convert(this.getConverters(), converted, ScryptedMimeTypes.LocalUrl);
194
+ const url = await this.convert(this.getConverters(), converted, ScryptedMimeTypes.LocalUrl);
170
195
  return url.data.toString();
171
196
  }
172
197
  async convertMediaObjectToUrl(mediaObject: string | MediaObject, toMimeType: string): Promise<string> {
173
- const intermediate = await convert(this.getConverters(), this.ensureMediaObjectRemote(mediaObject), toMimeType);
198
+ const intermediate = await this.convert(this.getConverters(), this.ensureMediaObjectRemote(mediaObject), toMimeType);
174
199
  const converted = this.createMediaObject(intermediate.data, intermediate.mimeType);
175
- const url = await convert(this.getConverters(), converted, ScryptedMimeTypes.Url);
200
+ const url = await this.convert(this.getConverters(), converted, ScryptedMimeTypes.Url);
176
201
  return url.data.toString();
177
202
  }
178
203
 
@@ -194,25 +219,116 @@ export abstract class MediaManagerBase implements MediaManager {
194
219
  return new MediaObjectImpl();
195
220
  }
196
221
 
197
- async createMediaObjectFromUrl(data: string, mimeType?: string): Promise<MediaObject> {
198
- if (!data.startsWith(SCRYPTED_MEDIA_SCHEME))
199
- return this.createMediaObject(data, mimeType || ScryptedMimeTypes.Url);
222
+ async createMediaObjectFromUrl(data: string): Promise<MediaObject> {
223
+ const url = new URL(data);
224
+ const scheme = url.protocol.slice(0, -1);
225
+ const fromMimeType = ScryptedMimeTypes.SchemePrefix + scheme;
226
+ return this.createMediaObject(data, fromMimeType);
227
+ }
228
+
229
+ async convert(converters: BufferConverter[], mediaObject: MediaObjectRemote, toMimeType: string): Promise<{ data: Buffer | string, mimeType: string }> {
230
+ // console.log('converting', mediaObject.mimeType, toMimeType);
231
+ const mediaMime = new MimeType(mediaObject.mimeType);
232
+ const outputMime = new MimeType(toMimeType);
200
233
 
201
- const url = new URL(data.toString());
202
- const id = url.hostname;
203
- const path = url.pathname.split('/')[1];
204
- let mo: MediaObject;
205
- if (path === ScryptedInterface.VideoCamera) {
206
- mo = await this.getDeviceById<VideoCamera>(id).getVideoStream();
234
+ if (mimeMatches(mediaMime, outputMime)) {
235
+ return {
236
+ mimeType: outputMime.essence,
237
+ data: await mediaObject.getData(),
238
+ }
207
239
  }
208
- else if (path === ScryptedInterface.Camera) {
209
- mo = await this.getDeviceById<Camera>(id).takePicture() as any;
240
+
241
+ const converterIds = new Map<BufferConverter, string>();
242
+ const converterReverseids = new Map<string, BufferConverter>();
243
+ let id = 0;
244
+ for (const converter of converters) {
245
+ const cid = (id++).toString();
246
+ converterIds.set(converter, cid);
247
+ converterReverseids.set(cid, converter);
210
248
  }
211
- else {
212
- throw new Error('Unrecognized Scrypted Media interface.')
249
+
250
+ const nodes: any = {};
251
+ const mediaNode: any = {};
252
+ nodes['mediaObject'] = mediaNode;
253
+ nodes['output'] = {};
254
+ for (const converter of converters) {
255
+ try {
256
+ const inputMime = new MimeType(converter.fromMimeType);
257
+ const convertedMime = new MimeType(converter.toMimeType);
258
+ const targetId = converterIds.get(converter);
259
+ const node: any = nodes[targetId] = {};
260
+ for (const candidate of converters) {
261
+ try {
262
+ const candidateMime = new MimeType(candidate.fromMimeType);
263
+ if (!mimeMatches(convertedMime, candidateMime))
264
+ continue;
265
+ const candidateId = converterIds.get(candidate);
266
+ node[candidateId] = 1;
267
+ }
268
+ catch (e) {
269
+ console.warn('skipping converter due to error', e)
270
+ }
271
+ }
272
+
273
+ if (mimeMatches(mediaMime, inputMime)) {
274
+ mediaNode[targetId] = 1;
275
+ }
276
+ if (mimeMatches(outputMime, convertedMime) || converter.toMimeType === ScryptedMimeTypes.MediaObject) {
277
+ node['output'] = 1;
278
+ }
279
+ }
280
+ catch (e) {
281
+ console.warn('skipping converter due to error', e)
282
+ }
283
+ }
284
+
285
+ const graph = new Graph();
286
+ for (const id of Object.keys(nodes)) {
287
+ graph.addNode(id, nodes[id]);
288
+ }
289
+
290
+ const route = graph.path('mediaObject', 'output') as Array<string>;
291
+ if (!route || !route.length)
292
+ throw new Error('no converter found');
293
+ // pop off the mediaObject start node, no conversion necessary.
294
+ route.shift();
295
+ // also remove the output node.
296
+ route.splice(route.length - 1);
297
+ let value = await mediaObject.getData();
298
+ let valueMime = new MimeType(mediaObject.mimeType);
299
+ for (const node of route) {
300
+ const converter = converterReverseids.get(node);
301
+ const converterToMimeType = new MimeType(converter.toMimeType);
302
+ const converterFromMimeType = new MimeType(converter.fromMimeType);
303
+ const type = converterToMimeType.type === '*' ? valueMime.type : converterToMimeType.type;
304
+ const subtype = converterToMimeType.subtype === '*' ? valueMime.subtype : converterToMimeType.subtype;
305
+ const targetMimeType = `${type}/${subtype}`;
306
+
307
+ if (typeof value === 'string' && !converterFromMimeType.parameters.has(ScryptedMimeTypes.AcceptUrlParameter)) {
308
+ const url = new URL(value);
309
+ const scheme = url.protocol.slice(0, -1);
310
+ const fromMimeType = ScryptedMimeTypes.SchemePrefix + scheme;
311
+ for (const converter of this.getConverters()) {
312
+ if (converter.fromMimeType !== fromMimeType || converter.toMimeType !== ScryptedMimeTypes.MediaObject)
313
+ continue;
314
+
315
+ const mo = await converter.convert(value, fromMimeType, toMimeType) as MediaObject;
316
+ const found = await this.convertMediaObjectToBuffer(mo, toMimeType);
317
+ return {
318
+ data: found,
319
+ mimeType: mo.mimeType,
320
+ };
321
+ }
322
+ throw new Error(`no ${ScryptedInterface.BufferConverter} exists for scheme: ${scheme}`);
323
+ }
324
+ value = await converter.convert(value, valueMime.essence, targetMimeType) as string | Buffer;
325
+ valueMime = new MimeType(targetMimeType);
213
326
  }
214
327
 
215
- return mo;
328
+ return {
329
+ data: value,
330
+ mimeType: valueMime.essence,
331
+ };
216
332
  }
217
333
  }
218
334
 
@@ -1,4 +1,4 @@
1
- import { ScryptedNativeId, ScryptedDevice, Device, DeviceManifest, EventDetails, EventListenerOptions, EventListenerRegister, ScryptedInterfaceProperty, MediaObject, SystemDeviceState, MediaManager, HttpRequest } from '@scrypted/sdk/types'
1
+ import { ScryptedNativeId, ScryptedDevice, Device, DeviceManifest, EventDetails, EventListenerOptions, EventListenerRegister, ScryptedInterfaceProperty, MediaObject, SystemDeviceState, MediaManager, HttpRequest } from '@scrypted/types'
2
2
 
3
3
  export interface PluginLogger {
4
4
  log(level: string, message: string): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { ScryptedNativeId } from '@scrypted/sdk/types'
1
+ import { ScryptedNativeId } from '@scrypted/types'
2
2
  import { listenZero } from './listen-zero';
3
3
  import { Server } from 'net';
4
4
  import { once } from 'events';
@@ -1,7 +1,7 @@
1
- import { DeviceProvider, EventDetails, EventListenerOptions, EventListenerRegister, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedInterfaceProperty } from "@scrypted/sdk/types";
1
+ import { DeviceProvider, EventDetails, EventListenerOptions, EventListenerRegister, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedInterfaceProperty } from "@scrypted/types";
2
2
  import { ScryptedRuntime } from "../runtime";
3
3
  import { PluginDevice } from "../db-types";
4
- import { MixinProvider } from "@scrypted/sdk/types";
4
+ import { MixinProvider } from "@scrypted/types";
5
5
  import { handleFunctionInvocations, PrimitiveProxyHandler } from "../rpc";
6
6
  import { getState } from "../state";
7
7
  import { getDisplayType } from "../infer-defaults";
@@ -402,6 +402,11 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
402
402
  }
403
403
  }
404
404
 
405
+ if (method === 'getPluginJson'
406
+ && getState(pluginDevice, ScryptedInterfaceProperty.providedInterfaces)?.includes(ScryptedInterface.ScryptedPlugin)) {
407
+ return this.scrypted.getPackageJson(pluginDevice.pluginId);
408
+ }
409
+
405
410
  if (!isValidInterfaceMethod(pluginDevice.state.interfaces.value, method))
406
411
  throw new PluginError(`device ${this.id} does not support method ${method}`);
407
412
 
@@ -1,4 +1,4 @@
1
- import { ScryptedNativeId, ScryptedDevice, Device, DeviceManifest, EventDetails, EventListenerOptions, EventListenerRegister, ScryptedInterfaceProperty, MediaManager, HttpRequest } from '@scrypted/sdk/types'
1
+ import { ScryptedNativeId, ScryptedDevice, Device, DeviceManifest, EventDetails, EventListenerOptions, EventListenerRegister, ScryptedInterfaceProperty, MediaManager, HttpRequest } from '@scrypted/types'
2
2
  import { ScryptedRuntime } from '../runtime';
3
3
  import { Plugin } from '../db-types';
4
4
  import { PluginAPI, PluginAPIManagedListeners } from './plugin-api';
@@ -1,6 +1,6 @@
1
1
  import { RpcPeer } from '../rpc';
2
2
  import AdmZip from 'adm-zip';
3
- import { Device, EngineIOHandler } from '@scrypted/sdk/types'
3
+ import { Device, EngineIOHandler } from '@scrypted/types'
4
4
  import { ScryptedRuntime } from '../runtime';
5
5
  import { Plugin } from '../db-types';
6
6
  import io, { Socket } from 'engine.io';
@@ -27,6 +27,8 @@ import rimraf from 'rimraf';
27
27
 
28
28
  export class PluginHost {
29
29
  static sharedWorker: child_process.ChildProcess;
30
+ static sharedWorkerImmediateRestart = false;
31
+
30
32
  worker: child_process.ChildProcess;
31
33
  peer: RpcPeer;
32
34
  pluginId: string;
@@ -51,6 +53,9 @@ export class PluginHost {
51
53
  kill() {
52
54
  this.killed = true;
53
55
  this.api.removeListeners();
56
+ // things might get a bit race prone, so clear out the shared worker before killing.
57
+ if (this.worker === PluginHost.sharedWorker)
58
+ PluginHost.sharedWorker = undefined;
54
59
  this.worker.kill('SIGKILL');
55
60
  this.io.close();
56
61
  for (const s of Object.values(this.ws)) {
@@ -254,7 +259,7 @@ export class PluginHost {
254
259
  // stdin, stdout, stderr, peer in, peer out
255
260
  stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe'],
256
261
  env: Object.assign({
257
- PYTHONPATH: path.join(process.cwd(), 'node_modules/@scrypted/sdk'),
262
+ PYTHONPATH: path.join(process.cwd(), 'node_modules/@scrypted/types'),
258
263
  }, process.env, env),
259
264
  });
260
265
 
@@ -293,17 +298,22 @@ export class PluginHost {
293
298
  Object.keys(this.packageJson.optionalDependencies || {}).length === 0;
294
299
  if (useSharedWorker) {
295
300
  if (!PluginHost.sharedWorker) {
296
- PluginHost.sharedWorker = child_process.fork(require.main.filename, ['child', '@scrypted/shared'], {
301
+ const worker = child_process.fork(require.main.filename, ['child', '@scrypted/shared'], {
297
302
  stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
298
303
  env: Object.assign({}, process.env, env),
299
304
  serialization: 'advanced',
300
305
  execArgv,
301
306
  });
307
+ PluginHost.sharedWorker = worker;
302
308
  PluginHost.sharedWorker.setMaxListeners(100);
303
- PluginHost.sharedWorker.on('close', () => PluginHost.sharedWorker = undefined);
304
- PluginHost.sharedWorker.on('error', () => PluginHost.sharedWorker = undefined);
305
- PluginHost.sharedWorker.on('exit', () => PluginHost.sharedWorker = undefined);
306
- PluginHost.sharedWorker.on('disconnect', () => PluginHost.sharedWorker = undefined);
309
+ const clearSharedWorker = () => {
310
+ if (worker === PluginHost.sharedWorker)
311
+ PluginHost.sharedWorker = undefined;
312
+ };
313
+ PluginHost.sharedWorker.on('close', () => clearSharedWorker);
314
+ PluginHost.sharedWorker.on('error', () => clearSharedWorker);
315
+ PluginHost.sharedWorker.on('exit', () => clearSharedWorker);
316
+ PluginHost.sharedWorker.on('disconnect', () => clearSharedWorker);
307
317
  }
308
318
  PluginHost.sharedWorker.send({
309
319
  type: 'start',
@@ -1,6 +1,6 @@
1
1
  import { Request, Response, Router } from 'express';
2
2
  import bodyParser from 'body-parser';
3
- import { HttpRequest } from '@scrypted/sdk/types';
3
+ import { HttpRequest } from '@scrypted/types';
4
4
  import WebSocket, { Server as WebSocketServer } from "ws";
5
5
  import { ServerResponse } from 'http';
6
6
 
@@ -1,4 +1,4 @@
1
- import { ScryptedNativeId, SystemDeviceState } from '@scrypted/sdk/types'
1
+ import { ScryptedNativeId, SystemDeviceState } from '@scrypted/types'
2
2
  import { PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
3
3
 
4
4
  /**
@@ -1,5 +1,5 @@
1
1
  import { RpcMessage, RpcPeer } from '../rpc';
2
- import { SystemManager, DeviceManager, ScryptedNativeId } from '@scrypted/sdk/types'
2
+ import { SystemManager, DeviceManager, ScryptedNativeId } from '@scrypted/types'
3
3
  import { attachPluginRemote, PluginReader } from './plugin-remote';
4
4
  import { PluginAPI } from './plugin-api';
5
5
  import { MediaManagerImpl } from './media';
@@ -1,14 +1,14 @@
1
1
  import AdmZip from 'adm-zip';
2
2
  import { Volume } from 'memfs';
3
3
  import path from 'path';
4
- import { ScryptedNativeId, DeviceManager, Logger, Device, DeviceManifest, DeviceState, EndpointManager, SystemDeviceState, ScryptedStatic, SystemManager, MediaManager, ScryptedMimeTypes, ScryptedInterface, ScryptedInterfaceProperty, HttpRequest } from '@scrypted/sdk/types'
4
+ import { ScryptedNativeId, DeviceManager, Logger, Device, DeviceManifest, DeviceState, EndpointManager, SystemDeviceState, ScryptedStatic, SystemManager, MediaManager, ScryptedMimeTypes, ScryptedInterface, ScryptedInterfaceProperty, HttpRequest } from '@scrypted/types'
5
5
  import { PluginAPI, PluginLogger, PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
6
6
  import { SystemManagerImpl } from './system';
7
7
  import { RpcPeer, RPCResultError, PROPERTY_PROXY_ONEWAY_METHODS, PROPERTY_JSON_DISABLE_SERIALIZATION } from '../rpc';
8
8
  import { BufferSerializer } from './buffer-serializer';
9
9
  import { createWebSocketClass, WebSocketConnectCallbacks, WebSocketMethods } from './plugin-remote-websocket';
10
10
  import fs from 'fs';
11
- const {link} = require('linkfs');
11
+ const { link } = require('linkfs');
12
12
 
13
13
  class DeviceLogger implements Logger {
14
14
  nativeId: ScryptedNativeId;
@@ -530,10 +530,18 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
530
530
  peer.evalLocal(script, zipOptions?.filename || '/plugin/main.nodejs.js', params);
531
531
  pluginConsole?.log('plugin successfully loaded');
532
532
  await options?.onPluginReady?.(ret, params, exports.default);
533
- return exports.default;
533
+
534
+ const defaultExport = exports.default;
535
+ // support exporting a plugin class, plugin main function,
536
+ // or a plugin instance
537
+ if (defaultExport.toString().startsWith('class '))
538
+ return new defaultExport();
539
+ if (typeof defaultExport === 'function')
540
+ return await defaultExport();
541
+ return defaultExport;
534
542
  }
535
543
  catch (e) {
536
- pluginConsole?.error('plugin failed to load', e);
544
+ pluginConsole?.error('plugin failed to start', e);
537
545
  throw e;
538
546
  }
539
547
  },
@@ -2,7 +2,7 @@ import { listenZero } from './listen-zero';
2
2
  import { Server } from 'net';
3
3
  import { once } from 'events';
4
4
  import repl from 'repl';
5
- import { ScryptedStatic } from '@scrypted/sdk';
5
+ import { ScryptedStatic } from '@scrypted/types';
6
6
 
7
7
  export async function createREPLServer(scrypted: ScryptedStatic, params: any, plugin: any): Promise<number> {
8
8
  const { deviceManager, systemManager } = scrypted;
@@ -1,4 +1,4 @@
1
- import { EventListenerOptions, EventDetails, EventListenerRegister, ScryptedDevice, ScryptedInterface, ScryptedInterfaceDescriptors, SystemDeviceState, SystemManager, ScryptedInterfaceProperty, ScryptedDeviceType, Logger } from "@scrypted/sdk/types";
1
+ import { EventListenerOptions, EventDetails, EventListenerRegister, ScryptedDevice, ScryptedInterface, ScryptedInterfaceDescriptors, SystemDeviceState, SystemManager, ScryptedInterfaceProperty, ScryptedDeviceType, Logger } from "@scrypted/types";
2
2
  import { PluginAPI } from "./plugin-api";
3
3
  import { handleFunctionInvocations, PrimitiveProxyHandler, PROPERTY_PROXY_ONEWAY_METHODS } from '../rpc';
4
4
  import { EventRegistry } from "../event-registry";