@scrypted/server 0.1.10 → 0.1.13

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.

@@ -1,15 +1,14 @@
1
- import { DeviceProvider, EventDetails, EventListener, EventListenerOptions, EventListenerRegister, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedInterfaceProperty } from "@scrypted/types";
2
- import { ScryptedRuntime } from "../runtime";
1
+ import { DeviceProvider, EventListener, EventListenerOptions, EventListenerRegister, MixinProvider, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedInterfaceProperty } from "@scrypted/types";
2
+ import fs from 'fs';
3
+ import path from 'path';
3
4
  import { PluginDevice } from "../db-types";
4
- import { MixinProvider } from "@scrypted/types";
5
- import { RpcPeer, PrimitiveProxyHandler } from "../rpc";
6
- import { getState } from "../state";
7
5
  import { getDisplayType } from "../infer-defaults";
8
- import { allInterfaceProperties, isValidInterfaceMethod, methodInterfaces } from "./descriptor";
9
- import { PluginError } from "./plugin-error";
6
+ import { PrimitiveProxyHandler, RpcPeer } from "../rpc";
7
+ import { ScryptedRuntime } from "../runtime";
10
8
  import { sleep } from "../sleep";
11
- import path from 'path';
12
- import fs from 'fs';
9
+ import { getState } from "../state";
10
+ import { allInterfaceProperties, getInterfaceMethods, getPropertyInterfaces } from "./descriptor";
11
+ import { PluginError } from "./plugin-error";
13
12
 
14
13
  interface MixinTable {
15
14
  mixinProviderId: string;
@@ -17,7 +16,8 @@ interface MixinTable {
17
16
  }
18
17
 
19
18
  interface MixinTableEntry {
20
- interfaces: Set<string>
19
+ interfaces: Set<string>;
20
+ methods?: Set<string>;
21
21
  allInterfaces: string[];
22
22
  proxy: any;
23
23
  error?: Error;
@@ -244,9 +244,10 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
244
244
 
245
245
  const implementer = await (mixinProvider as any)[QueryInterfaceSymbol](ScryptedInterface.MixinProvider);
246
246
  const host = this.scrypted.getPluginHostForDeviceId(implementer);
247
+ const propertyInterfaces = getPropertyInterfaces(host.api.descriptors || ScryptedInterfaceDescriptors);
247
248
  // todo: remove this and pass the setter directly.
248
249
  const deviceState = await host.remote.createDeviceState(this.id,
249
- async (property, value) => this.scrypted.stateManager.setPluginDeviceState(pluginDevice, property, value));
250
+ async (property, value) => this.scrypted.stateManager.setPluginDeviceState(pluginDevice, property, value, propertyInterfaces[property]));
250
251
  const mixinProxy = await mixinProvider.getMixin(wrappedProxy, previousInterfaces as ScryptedInterface[], deviceState);
251
252
  if (!mixinProxy)
252
253
  throw new PluginError(`mixin provider ${mixinId} did not return mixin for ${this.id}`);
@@ -283,7 +284,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
283
284
  }
284
285
 
285
286
  get(target: any, p: PropertyKey, receiver: any): any {
286
- if (p === 'constructor')
287
+ if (RpcPeer.PROBED_PROPERTIES.has(p))
287
288
  return;
288
289
  const handled = RpcPeer.handleFunctionInvocations(this, target, p, receiver);
289
290
  if (handled)
@@ -300,9 +301,6 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
300
301
  if (p === RefreshSymbol || p === QueryInterfaceSymbol)
301
302
  return new Proxy(() => p, this);
302
303
 
303
- if (!isValidInterfaceMethod(pluginDevice.state.interfaces.value, prop))
304
- return;
305
-
306
304
  if (ScryptedInterfaceDescriptors[ScryptedInterface.ScryptedDevice].methods.includes(prop))
307
305
  return (this as any)[p].bind(this);
308
306
 
@@ -339,11 +337,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
339
337
  }
340
338
 
341
339
  async applyMixin(method: string, argArray?: any): Promise<any> {
342
- const iface = methodInterfaces[method];
343
- if (!iface)
344
- throw new PluginError(`unknown method ${method}`);
345
-
346
- const found = await this.findMixin(iface);
340
+ const found = await this.findMethod(method);
347
341
  if (found) {
348
342
  const { mixin, entry } = found;
349
343
  const { proxy } = entry;
@@ -355,6 +349,23 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
355
349
  throw new PluginError(`${method} not implemented`)
356
350
  }
357
351
 
352
+ async findMethod(method: string) {
353
+ for (const mixin of this.mixinTable) {
354
+ const entry = await mixin.entry;
355
+ if (!entry.methods) {
356
+ const pluginDevice = this.scrypted.findPluginDeviceById(mixin.mixinProviderId || this.id);
357
+ const plugin = this.scrypted.plugins[pluginDevice.pluginId];
358
+ let methods = new Set<string>(getInterfaceMethods(ScryptedInterfaceDescriptors, entry.interfaces))
359
+ if (plugin.api.descriptors)
360
+ methods = new Set<string>([...methods, ...getInterfaceMethods(plugin.api.descriptors, entry.interfaces)]);
361
+ entry.methods = methods;
362
+ }
363
+ if (entry.methods.has(method)) {
364
+ return { mixin, entry };
365
+ }
366
+ }
367
+ }
368
+
358
369
  async findMixin(iface: string) {
359
370
  for (const mixin of this.mixinTable) {
360
371
  const entry = await mixin.entry;
@@ -407,9 +418,6 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
407
418
  return this.scrypted.getPackageJson(pluginDevice.pluginId);
408
419
  }
409
420
 
410
- if (!isValidInterfaceMethod(pluginDevice.state.interfaces.value, method))
411
- throw new PluginError(`device ${this.id} does not support method ${method}`);
412
-
413
421
  if (method === 'refresh') {
414
422
  const refreshInterface = argArray[0];
415
423
  const userInitiated = argArray[1];
@@ -1,17 +1,20 @@
1
- import { ScryptedNativeId, ScryptedDevice, Device, DeviceManifest, EventDetails, EventListenerOptions, EventListenerRegister, ScryptedInterfaceProperty, MediaManager, HttpRequest, ScryptedInterface } from '@scrypted/types'
2
- import { ScryptedRuntime } from '../runtime';
1
+ import { Device, DeviceManifest, EventDetails, EventListenerOptions, EventListenerRegister, HttpRequest, MediaManager, ScryptedDevice, ScryptedInterfaceDescriptor, ScryptedInterfaceProperty, ScryptedNativeId } from '@scrypted/types';
2
+ import debounce from 'lodash/debounce';
3
3
  import { Plugin } from '../db-types';
4
- import { PluginAPI, PluginAPIManagedListeners } from './plugin-api';
5
4
  import { Logger } from '../logger';
5
+ import { RpcPeer } from '../rpc';
6
+ import { ScryptedRuntime } from '../runtime';
6
7
  import { getState } from '../state';
8
+ import { getPropertyInterfaces } from './descriptor';
9
+ import { PluginAPI, PluginAPIManagedListeners } from './plugin-api';
7
10
  import { PluginHost } from './plugin-host';
8
- import debounce from 'lodash/debounce';
9
- import { RpcPeer } from '../rpc';
10
- import { propertyInterfaces } from './descriptor';
11
11
  import { checkProperty } from './plugin-state-check';
12
12
 
13
13
  export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAPI {
14
14
  pluginId: string;
15
+ typesVersion: string;
16
+ descriptors: { [scryptedInterface: string]: ScryptedInterfaceDescriptor };
17
+ propertyInterfaces: ReturnType<typeof getPropertyInterfaces>;
15
18
 
16
19
  [RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS] = [
17
20
  'onMixinEvent',
@@ -116,7 +119,7 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
116
119
 
117
120
  async setState(nativeId: ScryptedNativeId, key: string, value: any) {
118
121
  checkProperty(key, value);
119
- this.scrypted.stateManager.setPluginState(this.pluginId, nativeId, key, value);
122
+ this.scrypted.stateManager.setPluginState(this.pluginId, nativeId, this.propertyInterfaces?.[key], key, value);
120
123
  }
121
124
 
122
125
  async setStorage(nativeId: ScryptedNativeId, storage: { [key: string]: string }) {
@@ -179,4 +182,10 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
179
182
  logger.log('i', 'plugin restart was requested');
180
183
  return this.restartDebounced();
181
184
  }
185
+
186
+ async setScryptedInterfaceDescriptors(typesVersion: string, descriptors: { [scryptedInterface: string]: ScryptedInterfaceDescriptor }): Promise<void> {
187
+ this.typesVersion = typesVersion;
188
+ this.descriptors = descriptors;
189
+ this.propertyInterfaces = getPropertyInterfaces(descriptors);
190
+ }
182
191
  }
@@ -333,7 +333,14 @@ export class PluginHost {
333
333
  });
334
334
 
335
335
  this.worker.on('rpc', (message, sendHandle) => {
336
- this.createRpcPeer(sendHandle as net.Socket);
336
+ const socket = sendHandle as net.Socket;
337
+ const { pluginId } = message;
338
+ const host = this.scrypted.plugins[pluginId];
339
+ if (!host) {
340
+ socket.destroy();
341
+ return;
342
+ }
343
+ host.createRpcPeer(socket);
337
344
  });
338
345
 
339
346
  this.peer.params.updateStats = (stats: any) => {
@@ -1,8 +1,9 @@
1
+ import os from 'os';
1
2
  import path from 'path';
2
3
  import mkdirp from 'mkdirp';
3
4
 
4
5
  export function getScryptedVolume() {
5
- const volumeDir = process.env.SCRYPTED_VOLUME || path.join(process.cwd(), 'volume');
6
+ const volumeDir = process.env.SCRYPTED_VOLUME || path.join(os.homedir(), '.scrypted', 'volume');
6
7
  return volumeDir;
7
8
  }
8
9
 
@@ -1,16 +1,14 @@
1
- import { EventListenerOptions, EventDetails, EventListenerRegister, ScryptedDevice, ScryptedInterface, ScryptedInterfaceDescriptors, SystemDeviceState, SystemManager, ScryptedInterfaceProperty, ScryptedDeviceType, Logger, EventListener } from "@scrypted/types";
2
- import { PluginAPI } from "./plugin-api";
3
- import { PrimitiveProxyHandler, RpcPeer } from '../rpc';
1
+ import { EventListener, EventListenerOptions, EventListenerRegister, Logger, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptor, ScryptedInterfaceDescriptors, ScryptedInterfaceProperty, SystemDeviceState, SystemManager } from "@scrypted/types";
4
2
  import { EventRegistry } from "../event-registry";
5
- import { allInterfaceProperties, isValidInterfaceMethod } from "./descriptor";
6
-
3
+ import { PrimitiveProxyHandler, RpcPeer } from '../rpc';
4
+ import { getInterfaceMethods, getInterfaceProperties, getPropertyInterfaces, isValidInterfaceMethod, propertyInterfaces } from "./descriptor";
5
+ import { PluginAPI } from "./plugin-api";
7
6
 
8
7
  function newDeviceProxy(id: string, systemManager: SystemManagerImpl) {
9
8
  const handler = new DeviceProxyHandler(id, systemManager);
10
9
  return new Proxy(handler, handler);
11
10
  }
12
11
 
13
-
14
12
  class DeviceProxyHandler implements PrimitiveProxyHandler<any>, ScryptedDevice {
15
13
  device: Promise<ScryptedDevice>;
16
14
  constructor(public id: string, public systemManager: SystemManagerImpl) {
@@ -20,6 +18,31 @@ class DeviceProxyHandler implements PrimitiveProxyHandler<any>, ScryptedDevice {
20
18
  return `ScryptedDevice-${this.id}`
21
19
  }
22
20
 
21
+ ownKeys(target: any): ArrayLike<string | symbol> {
22
+ const interfaces = new Set<string>(this.systemManager.state[this.id].interfaces.value);
23
+ const methods = getInterfaceMethods(this.systemManager.descriptors || ScryptedInterfaceDescriptors, interfaces);
24
+ const properties = getInterfaceProperties(this.systemManager.descriptors || ScryptedInterfaceDescriptors, interfaces);
25
+ return [...methods, ...properties];
26
+ }
27
+
28
+ getOwnPropertyDescriptor(target: any, p: string | symbol): PropertyDescriptor {
29
+ const interfaces = new Set<string>(this.systemManager.state[this.id].interfaces.value);
30
+ const methods = getInterfaceMethods(this.systemManager.descriptors || ScryptedInterfaceDescriptors, interfaces);
31
+ const prop = p.toString();
32
+ if (methods.includes(prop)) {
33
+ return {
34
+ configurable: true,
35
+ };
36
+ }
37
+ const properties = getInterfaceProperties(this.systemManager.descriptors || ScryptedInterfaceDescriptors, interfaces);
38
+ if (properties.includes(prop)) {
39
+ return {
40
+ configurable: true,
41
+ value: this.systemManager.state[this.id][prop]?.value
42
+ }
43
+ }
44
+ }
45
+
23
46
  get(target: any, p: PropertyKey, receiver: any): any {
24
47
  if (p === 'id')
25
48
  return this.id;
@@ -28,11 +51,16 @@ class DeviceProxyHandler implements PrimitiveProxyHandler<any>, ScryptedDevice {
28
51
  if (handled)
29
52
  return handled;
30
53
 
31
- if (allInterfaceProperties.includes(p.toString()))
54
+ const interfaces = new Set<string>(this.systemManager.state[this.id].interfaces.value);
55
+ const prop = p.toString();
56
+ const isValidProperty = this.systemManager.propertyInterfaces?.[prop] || propertyInterfaces[prop];
57
+
58
+ // this will also return old properties that should not exist on a device. ie, a disabled mixin provider.
59
+ // should this change?
60
+ if (isValidProperty)
32
61
  return (this.systemManager.state[this.id] as any)?.[p]?.value;
33
62
 
34
- const prop = p.toString();
35
- if (!isValidInterfaceMethod(this.systemManager.state[this.id].interfaces.value, prop))
63
+ if (!isValidInterfaceMethod(this.systemManager.descriptors || ScryptedInterfaceDescriptors, interfaces, prop))
36
64
  return;
37
65
 
38
66
  if (ScryptedInterfaceDescriptors[ScryptedInterface.ScryptedDevice].methods.includes(prop))
@@ -41,7 +69,7 @@ class DeviceProxyHandler implements PrimitiveProxyHandler<any>, ScryptedDevice {
41
69
  return new Proxy(() => p, this);
42
70
  }
43
71
 
44
- ensureDevice() {
72
+ ensureDevice() {
45
73
  if (!this.device)
46
74
  this.device = this.systemManager.api.getDeviceById(this.id);
47
75
  return this.device;
@@ -101,15 +129,18 @@ function makeOneWayCallback<T>(input: T): T {
101
129
 
102
130
  export class SystemManagerImpl implements SystemManager {
103
131
  api: PluginAPI;
104
- state: {[id: string]: {[property: string]: SystemDeviceState}};
105
- deviceProxies: {[id: string]: ScryptedDevice} = {};
132
+ state: { [id: string]: { [property: string]: SystemDeviceState } };
133
+ deviceProxies: { [id: string]: ScryptedDevice } = {};
106
134
  log: Logger;
107
135
  events = new EventRegistry();
136
+ typesVersion: string;
137
+ descriptors: { [scryptedInterface: string]: ScryptedInterfaceDescriptor };
138
+ propertyInterfaces: ReturnType<typeof getPropertyInterfaces>;
108
139
 
109
140
  getDeviceState(id: string) {
110
141
  return this.state[id];
111
142
  }
112
- getSystemState(): {[id: string]: {[property: string]: SystemDeviceState}} {
143
+ getSystemState(): { [id: string]: { [property: string]: SystemDeviceState } } {
113
144
  return this.state;
114
145
  }
115
146
 
@@ -155,4 +186,11 @@ export class SystemManagerImpl implements SystemManager {
155
186
  getComponent(id: string): Promise<any> {
156
187
  return this.api.getComponent(id);
157
188
  }
189
+
190
+ setScryptedInterfaceDescriptors(typesVersion: string, descriptors: { [scryptedInterface: string]: ScryptedInterfaceDescriptor }): Promise<void> {
191
+ this.typesVersion = typesVersion;
192
+ this.descriptors = descriptors;
193
+ this.propertyInterfaces = getPropertyInterfaces(descriptors);
194
+ return this.api.setScryptedInterfaceDescriptors(typesVersion, descriptors);
195
+ }
158
196
  }
package/src/rpc.ts CHANGED
@@ -246,6 +246,17 @@ export class RpcPeer {
246
246
  static readonly PROPERTY_JSON_DISABLE_SERIALIZATION = '__json_disable_serialization';
247
247
  static readonly PROPERTY_PROXY_PROPERTIES = '__proxy_props';
248
248
  static readonly PROPERTY_JSON_COPY_SERIALIZE_CHILDREN = '__json_copy_serialize_children';
249
+ static readonly PROBED_PROPERTIES = new Set<any>([
250
+ 'then',
251
+ 'constructor',
252
+ '__proxy_id',
253
+ '__proxy_constructor',
254
+ '__proxy_peer',
255
+ RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS,
256
+ RpcPeer.PROPERTY_JSON_DISABLE_SERIALIZATION,
257
+ RpcPeer.PROPERTY_PROXY_PROPERTIES,
258
+ RpcPeer.PROPERTY_JSON_COPY_SERIALIZE_CHILDREN,
259
+ ]);
249
260
 
250
261
  constructor(public selfName: string, public peerName: string, public send: (message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any) => void) {
251
262
  }
package/src/runtime.ts CHANGED
@@ -40,7 +40,7 @@ interface DeviceProxyPair {
40
40
  proxy: ScryptedDevice;
41
41
  }
42
42
 
43
- const MIN_SCRYPTED_CORE_VERSION = 'v0.1.5';
43
+ const MIN_SCRYPTED_CORE_VERSION = 'v0.1.16';
44
44
  const PLUGIN_DEVICE_STATE_VERSION = 2;
45
45
 
46
46
  interface HttpPluginData {
@@ -225,6 +225,7 @@ async function start() {
225
225
  });
226
226
 
227
227
  console.log('#######################################################');
228
+ console.log(`Scrypted Volume : ${volumeDir}`);
228
229
  console.log(`Scrypted Server (Local) : https://localhost:${SCRYPTED_SECURE_PORT}/`);
229
230
  for (const address of getHostAddresses(true, true)) {
230
231
  console.log(`Scrypted Server (Remote) : https://${address}:${SCRYPTED_SECURE_PORT}/`);
package/src/state.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { ScryptedRuntime } from "./runtime";
2
- import { ScryptedNativeId, EventDetails, EventListenerOptions, EventListenerRegister, Refresh, ScryptedInterface, ScryptedInterfaceProperty, SystemDeviceState } from "@scrypted/types";
3
- import { RefreshSymbol } from "./plugin/plugin-device";
1
+ import { EventDetails, EventListenerOptions, EventListenerRegister, Refresh, ScryptedInterface, ScryptedInterfaceProperty, ScryptedNativeId, SystemDeviceState } from "@scrypted/types";
4
2
  import throttle from 'lodash/throttle';
5
- import { sleep } from "./sleep";
6
- import { EventListenerRegisterImpl, EventRegistry } from "./event-registry";
7
3
  import { PluginDevice } from "./db-types";
4
+ import { EventListenerRegisterImpl, EventRegistry } from "./event-registry";
8
5
  import { allInterfaceProperties, propertyInterfaces } from "./plugin/descriptor";
6
+ import { RefreshSymbol } from "./plugin/plugin-device";
7
+ import { ScryptedRuntime } from "./runtime";
8
+ import { sleep } from "./sleep";
9
9
 
10
10
  export class ScryptedStateManager extends EventRegistry {
11
11
  scrypted: ScryptedRuntime;
@@ -34,41 +34,30 @@ export class ScryptedStateManager extends EventRegistry {
34
34
  this.scrypted = scrypted;
35
35
  }
36
36
 
37
- setPluginState(pluginId: string, nativeId: ScryptedNativeId, property: string, value: any) {
37
+ setPluginState(pluginId: string, nativeId: ScryptedNativeId, eventInterface: ScryptedInterface, property: string, value: any) {
38
38
  const device = this.scrypted.findPluginDevice(pluginId, nativeId);
39
39
  if (!device)
40
40
  throw new Error(`device not found for plugin id ${pluginId} native id ${nativeId}`);
41
- this.setPluginDeviceState(device, property, value);
41
+ this.setPluginDeviceState(device, property, value, eventInterface);
42
42
  }
43
43
 
44
- setPluginDeviceState(device: PluginDevice, property: string, value: any) {
45
- if (!allInterfaceProperties.includes(property))
46
- throw new Error(`invalid property ${property}`);
47
-
48
- // this currently doesn't work because inherited properties are not detected.
49
- // ie, MediaPlayer implements StartStop and Pause
50
- // if (!isValidInterfaceProperty(device.state.interfaces.value, property))
51
- // throw new Error(`interface for property ${property} not implemented`);
52
- if (!allInterfaceProperties.includes(property))
44
+ setPluginDeviceState(device: PluginDevice, property: string, value: any, eventInterface?: ScryptedInterface) {
45
+ eventInterface = eventInterface || propertyInterfaces[property];
46
+ if (!eventInterface)
53
47
  throw new Error(`${property} is not a valid property`);
54
48
 
55
49
  const changed = setState(device, property, value);
56
50
 
57
- this.notifyPropertyEvent(device, property, value, changed);
58
-
59
- this.upserts.add(device._id);
60
- this.upsertThrottle();
61
-
62
- return changed;
63
- }
64
-
65
- notifyPropertyEvent(device: PluginDevice, property: string, value: any, changed?: boolean) {
66
51
  const eventTime = device?.state?.[property]?.lastEventTime;
67
- const eventInterface = propertyInterfaces[property];
68
52
 
69
53
  if (this.notify(device?._id, eventTime, eventInterface, property, value, changed) && device) {
70
54
  this.scrypted.getDeviceLogger(device).log('i', `state change: ${property} ${value}`);
71
55
  }
56
+
57
+ this.upserts.add(device._id);
58
+ this.upsertThrottle();
59
+
60
+ return changed;
72
61
  }
73
62
 
74
63
  updateDescriptor(device: PluginDevice) {