@scrypted/server 0.4.9 → 0.4.11

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.

@@ -61,14 +61,20 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
61
61
  const { interfaces } = await tableEntry.entry;
62
62
  if (!interfaces.has(eventInterface))
63
63
  throw new Error(`${mixinProvider._id} does not mixin ${eventInterface} for ${id}`);
64
+
65
+ this.scrypted.stateManager.notifyInterfaceEvent(device, eventInterface, eventData);
64
66
  }
65
67
  else {
66
68
  const mixin: object = nativeIdOrMixinDevice;
67
- if (!await this.scrypted.devices[device._id]?.handler?.isMixin(id, mixin)) {
69
+ let mixinProviderId = await this.scrypted.devices[device._id]?.handler?.getMixinProviderId(id, mixin);
70
+ if (!mixinProviderId)
68
71
  throw new Error(`${mixin} does not mixin ${eventInterface} for ${id}`);
69
- }
72
+
73
+ if (mixinProviderId === true)
74
+ mixinProviderId = undefined;
75
+ // this.scrypted.stateManager.notifyInterfaceEvent(device, eventInterface, eventData);
76
+ this.scrypted.stateManager.notifyInterfaceEventFromMixin(device, eventInterface, eventData, mixinProviderId as string);
70
77
  }
71
- this.scrypted.stateManager.notifyInterfaceEvent(device, eventInterface, eventData);
72
78
  }
73
79
 
74
80
  async getMediaManager(): Promise<MediaManager> {
@@ -100,7 +106,11 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
100
106
 
101
107
  async setState(nativeId: ScryptedNativeId, key: string, value: any) {
102
108
  checkProperty(key, value);
103
- this.scrypted.stateManager.setPluginState(this.pluginId, nativeId, this.propertyInterfaces?.[key], key, value);
109
+ const { pluginId } = this;
110
+ const device = this.scrypted.findPluginDevice(pluginId, nativeId);
111
+ if (!device)
112
+ throw new Error(`device not found for plugin id ${pluginId} native id ${nativeId}`);
113
+ await this.scrypted.stateManager.setPluginDeviceStateFromMixin(device, key, value, this.propertyInterfaces?.[key], device._id);
104
114
  }
105
115
 
106
116
  async setStorage(nativeId: ScryptedNativeId, storage: { [key: string]: string }) {
@@ -136,7 +146,7 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
136
146
 
137
147
  async onDeviceEvent(nativeId: any, eventInterface: any, eventData?: any) {
138
148
  const plugin = this.scrypted.findPluginDevice(this.pluginId, nativeId);
139
- this.scrypted.stateManager.notifyInterfaceEvent(plugin, eventInterface, eventData);
149
+ this.scrypted.stateManager.notifyInterfaceEventFromMixin(plugin, eventInterface, eventData, plugin._id);
140
150
  }
141
151
 
142
152
  async getDeviceById<T>(id: string): Promise<T & ScryptedDevice> {
@@ -146,11 +156,11 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
146
156
  return this.manageListener(this.scrypted.stateManager.listen(callback));
147
157
  }
148
158
  async listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister> {
149
- const device = this.scrypted.findPluginDeviceById(id);
150
- if (device) {
151
- const self = this.scrypted.findPluginDevice(this.pluginId);
152
- this.scrypted.getDeviceLogger(self).log('i', `requested listen ${getState(device, ScryptedInterfaceProperty.name)} ${JSON.stringify(event)}`);
153
- }
159
+ // const device = this.scrypted.findPluginDeviceById(id);
160
+ // if (device) {
161
+ // const self = this.scrypted.findPluginDevice(this.pluginId);
162
+ // this.scrypted.getDeviceLogger(self).log('i', `requested listen ${getState(device, ScryptedInterfaceProperty.name)} ${JSON.stringify(event)}`);
163
+ // }
154
164
  return this.manageListener(this.scrypted.stateManager.listenDevice(id, event, callback));
155
165
  }
156
166
 
@@ -1,4 +1,4 @@
1
- import { ScryptedNativeId, SystemDeviceState } from '@scrypted/types'
1
+ import { EventDetails, ScryptedNativeId, SystemDeviceState } from '@scrypted/types'
2
2
  import { PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
3
3
 
4
4
  /**
@@ -42,7 +42,9 @@ import { PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
42
42
  }
43
43
  return this.remote.updateDeviceState(id, state);
44
44
  }
45
- async notify(id: string, eventTime: number, eventInterface: string, property: string, propertyState: SystemDeviceState, changed?: boolean): Promise<void> {
45
+ // TODO: deprecate/clean up this signature
46
+ // 12/30/2022
47
+ async notify(id: string, eventTimeOrDetails: number| EventDetails, eventInterfaceOrData: string | SystemDeviceState | any, property?: string, value?: SystemDeviceState | any, changed?: boolean) {
46
48
  try {
47
49
  if (!this.remote)
48
50
  await this.remoteReadyPromise;
@@ -50,7 +52,7 @@ import { PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
50
52
  catch (e) {
51
53
  return;
52
54
  }
53
- return this.remote.notify(id, eventTime, eventInterface, property, propertyState, changed);
55
+ return this.remote.notify(id, eventTimeOrDetails as any, eventInterfaceOrData, property, value, changed);
54
56
  }
55
57
  async ioEvent(id: string, event: string, message?: any): Promise<void> {
56
58
  if (!this.remote)
@@ -1,4 +1,4 @@
1
- import { Device, DeviceManager, DeviceManifest, DeviceState, EndpointManager, Logger, MediaManager, ScryptedInterface, ScryptedInterfaceProperty, ScryptedMimeTypes, ScryptedNativeId, ScryptedStatic, SystemDeviceState, SystemManager } from '@scrypted/types';
1
+ import { Device, DeviceManager, DeviceManifest, DeviceState, EndpointManager, EventDetails, Logger, MediaManager, ScryptedInterface, ScryptedInterfaceProperty, ScryptedMimeTypes, ScryptedNativeId, ScryptedStatic, SystemDeviceState, SystemManager } from '@scrypted/types';
2
2
  import { RpcPeer, RPCResultError } from '../rpc';
3
3
  import { AccessControls } from './acl';
4
4
  import { BufferSerializer } from './buffer-serializer';
@@ -175,8 +175,6 @@ class DeviceStateProxyHandler implements ProxyHandler<any> {
175
175
  checkProperty(p.toString(), value);
176
176
  const now = Date.now();
177
177
  this.deviceManager.systemManager.state[this.id][p as string] = {
178
- lastEventTime: now,
179
- stateTime: now,
180
178
  value,
181
179
  };
182
180
  this.setState(p.toString(), value);
@@ -367,7 +365,7 @@ export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId:
367
365
 
368
366
  const accessControls: AccessControls = peer.tags.acl;
369
367
 
370
- const getAccessControlDeviceState = (id: string, state?: { [property: string]: SystemDeviceState } ) => {
368
+ const getAccessControlDeviceState = (id: string, state?: { [property: string]: SystemDeviceState }) => {
371
369
  state = state || getSystemState()[id];
372
370
  if (accessControls && state) {
373
371
  state = Object.assign({}, state);
@@ -420,11 +418,11 @@ export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId:
420
418
  return;
421
419
  }
422
420
 
423
- if (eventDetails.property) {
424
- remote.notify(id, eventDetails.eventTime, eventDetails.eventInterface, eventDetails.property, getSystemState()[id]?.[eventDetails.property], eventDetails.changed).catch(() => { });
421
+ if (eventDetails.property && !eventDetails.mixinId) {
422
+ remote.notify(id, eventDetails, getSystemState()[id]?.[eventDetails.property]).catch(() => { });
425
423
  }
426
424
  else {
427
- remote.notify(id, eventDetails.eventTime, eventDetails.eventInterface, eventDetails.property, eventData, eventDetails.changed).catch(() => { });
425
+ remote.notify(id, eventDetails, eventData).catch(() => { });
428
426
  }
429
427
  });
430
428
 
@@ -567,26 +565,48 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
567
565
  async updateDeviceState(id: string, state: { [property: string]: SystemDeviceState }) {
568
566
  if (!state) {
569
567
  delete systemManager.state[id];
570
- systemManager.events.notify(undefined, undefined, ScryptedInterface.ScryptedDevice, ScryptedInterfaceProperty.id, id, true);
568
+ systemManager.events.notify(undefined, undefined, ScryptedInterface.ScryptedDevice, ScryptedInterfaceProperty.id, id, { changed: true });
571
569
  }
572
570
  else {
573
571
  systemManager.state[id] = state;
574
- systemManager.events.notify(id, undefined, ScryptedInterface.ScryptedDevice, undefined, state, true);
572
+ systemManager.events.notify(id, undefined, ScryptedInterface.ScryptedDevice, undefined, state, { changed: true });
575
573
  }
576
574
  },
577
575
 
578
- async notify(id: string, eventTime: number, eventInterface: string, property: string, value: SystemDeviceState | any, changed?: boolean) {
579
- if (property) {
580
- const state = systemManager.state?.[id];
581
- if (!state) {
582
- log.w(`state not found for ${id}`);
583
- return;
576
+ async notify(id: string, eventTimeOrDetails: number| EventDetails, eventInterfaceOrData: string | SystemDeviceState | any, property?: string, value?: SystemDeviceState | any, changed?: boolean) {
577
+ if (typeof eventTimeOrDetails === 'number') {
578
+ // TODO: remove legacy code path
579
+ // 12/30/2022
580
+ const eventTime = eventTimeOrDetails as number;
581
+ const eventInterface = eventInterfaceOrData as string;
582
+ if (property) {
583
+ const state = systemManager.state?.[id];
584
+ if (!state) {
585
+ log.w(`state not found for ${id}`);
586
+ return;
587
+ }
588
+ state[property] = value;
589
+ systemManager.events.notify(id, eventTime, eventInterface, property, value.value, { changed });
590
+ }
591
+ else {
592
+ systemManager.events.notify(id, eventTime, eventInterface, property, value, { changed });
584
593
  }
585
- state[property] = value;
586
- systemManager.events.notify(id, eventTime, eventInterface, property, value.value, changed);
587
594
  }
588
595
  else {
589
- systemManager.events.notify(id, eventTime, eventInterface, property, value, changed);
596
+ const eventDetails = eventTimeOrDetails as EventDetails;
597
+ const eventData = eventInterfaceOrData as any;
598
+ if (eventDetails.property && !eventDetails.mixinId) {
599
+ const state = systemManager.state?.[id];
600
+ if (!state) {
601
+ log.w(`state not found for ${id}`);
602
+ return;
603
+ }
604
+ state[eventDetails.property] = eventData;
605
+ systemManager.events.notifyEventDetails(id, eventDetails, eventData.value);
606
+ }
607
+ else {
608
+ systemManager.events.notifyEventDetails(id, eventDetails, eventData);
609
+ }
590
610
  }
591
611
  },
592
612
 
@@ -166,15 +166,11 @@ export class SystemManagerImpl implements SystemManager {
166
166
  return this.events.listen(makeOneWayCallback((id, eventDetails, eventData) => callback(this.getDeviceById(id), eventDetails, eventData)));
167
167
  }
168
168
  listenDevice(id: string, options: string | EventListenerOptions, callback: EventListener): EventListenerRegister {
169
- let { event, watch } = (options || {}) as EventListenerOptions;
170
- if (!event && typeof options === 'string')
171
- event = options as string;
172
- if (!event)
173
- event = undefined;
169
+ let { watch } = (options || {}) as EventListenerOptions;
174
170
 
175
171
  // passive watching can be fast pathed to observe local state
176
172
  if (watch)
177
- return this.events.listenDevice(id, event, (eventDetails, eventData) => callback(this.getDeviceById(id), eventDetails, eventData));
173
+ return this.events.listenDevice(id, options, (eventDetails, eventData) => callback(this.getDeviceById(id), eventDetails, eventData));
178
174
 
179
175
  return new EventListenerRegisterImpl(this.api.listenDevice(id, options, makeOneWayCallback((eventDetails, eventData) => callback(this.getDeviceById(id), eventDetails, eventData))));
180
176
  }
package/src/rpc.ts CHANGED
@@ -154,6 +154,9 @@ class RpcProxy implements PrimitiveProxyHandler<any> {
154
154
 
155
155
  if (this.proxyOneWayMethods?.includes?.(method)) {
156
156
  rpcApply.oneway = true;
157
+ // a oneway callable object doesn't need to be in the JSON payload.
158
+ if (method === null)
159
+ delete rpcApply.method;
157
160
  this.peer.send(rpcApply, undefined, serializationContext);
158
161
  return Promise.resolve();
159
162
  }
package/src/runtime.ts CHANGED
@@ -394,7 +394,7 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
394
394
  let accessControls: AccessControls;
395
395
  if (username) {
396
396
  const user = await this.datastore.tryGet(ScryptedUser, username);
397
- if (user.aclId) {
397
+ if (user?.aclId) {
398
398
  const accessControl = this.getDevice<SU>(user.aclId);
399
399
  try {
400
400
  const acls = await accessControl.getScryptedUserAccessControl();
@@ -70,6 +70,8 @@ export class PluginComponent {
70
70
  async kill(pluginId: string) {
71
71
  return this.scrypted.plugins[pluginId]?.kill();
72
72
  }
73
+ // TODO: Remove this, ScryptedPlugin exists now.
74
+ // 12/29/2022
73
75
  async getPackageJson(pluginId: string) {
74
76
  return this.scrypted.getPackageJson(pluginId);
75
77
  }
package/src/state.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { EventDetails, EventListenerOptions, EventListenerRegister, Refresh, ScryptedInterface, ScryptedInterfaceProperty, ScryptedNativeId, SystemDeviceState } from "@scrypted/types";
2
2
  import throttle from 'lodash/throttle';
3
3
  import { PluginDevice } from "./db-types";
4
- import { EventListenerRegisterImpl, EventRegistry } from "./event-registry";
5
- import { allInterfaceProperties, propertyInterfaces } from "./plugin/descriptor";
6
- import { RefreshSymbol } from "./plugin/plugin-device";
4
+ import { EventListenerRegisterImpl, EventRegistry, getMixinEventName } from "./event-registry";
5
+ import { propertyInterfaces } from "./plugin/descriptor";
6
+ import { QueryInterfaceSymbol, RefreshSymbol } from "./plugin/plugin-device";
7
7
  import { ScryptedRuntime } from "./runtime";
8
8
  import { sleep } from "./sleep";
9
9
 
@@ -34,25 +34,74 @@ export class ScryptedStateManager extends EventRegistry {
34
34
  this.scrypted = scrypted;
35
35
  }
36
36
 
37
- setPluginState(pluginId: string, nativeId: ScryptedNativeId, eventInterface: ScryptedInterface, property: string, value: any) {
38
- const device = this.scrypted.findPluginDevice(pluginId, nativeId);
37
+ async getImplementerId(pluginDevice: PluginDevice, eventInterface: ScryptedInterface | string) {
38
+ if (!eventInterface)
39
+ throw new Error(`ScryptedInterface is required`);
40
+
41
+ const device = this.scrypted.getDevice(pluginDevice._id);
39
42
  if (!device)
40
- throw new Error(`device not found for plugin id ${pluginId} native id ${nativeId}`);
41
- this.setPluginDeviceState(device, property, value, eventInterface);
43
+ throw new Error(`device ${pluginDevice._id} not found?`);
44
+
45
+ const implementerId: string = await (device as any)[QueryInterfaceSymbol](eventInterface);
46
+ return implementerId;
47
+ }
48
+
49
+ async notifyInterfaceEventFromMixin(pluginDevice: PluginDevice, eventInterface: ScryptedInterface | string, value: any, mixinId: string) {
50
+ // TODO: figure out how to clean this up this hack. For now,
51
+ // Settings interface is allowed to bubble from mixin devices..
52
+ if (eventInterface !== ScryptedInterface.Settings) {
53
+ const implementerId = await this.getImplementerId(pluginDevice, eventInterface);
54
+ if (implementerId !== mixinId) {
55
+ const event = getMixinEventName({
56
+ event: eventInterface,
57
+ mixinId,
58
+ });
59
+
60
+ this.notifyEventDetails(pluginDevice._id, {
61
+ eventId: undefined,
62
+ eventInterface,
63
+ eventTime: Date.now(),
64
+ mixinId,
65
+ }, value, event);
66
+
67
+ return;
68
+ }
69
+ }
70
+
71
+ this.notify(pluginDevice?._id, Date.now(), eventInterface, undefined, value);
72
+ }
73
+
74
+ async setPluginDeviceStateFromMixin(pluginDevice: PluginDevice, property: string, value: any, eventInterface: ScryptedInterface, mixinId: string) {
75
+ const implementerId = await this.getImplementerId(pluginDevice, eventInterface);
76
+ if (implementerId !== mixinId) {
77
+ const event = getMixinEventName({
78
+ event: eventInterface,
79
+ mixinId,
80
+ });
81
+ this.scrypted.getDeviceLogger(pluginDevice).log('i', `${property}: ${value} (mixin)`);
82
+ this.notifyEventDetails(pluginDevice._id, {
83
+ eventId: undefined,
84
+ eventInterface,
85
+ eventTime: Date.now(),
86
+ mixinId,
87
+ property,
88
+ }, value, event);
89
+ return false;
90
+ }
91
+
92
+ return this.setPluginDeviceState(pluginDevice, property, value, eventInterface);
42
93
  }
43
94
 
44
95
  setPluginDeviceState(device: PluginDevice, property: string, value: any, eventInterface?: ScryptedInterface) {
45
96
  eventInterface = eventInterface || propertyInterfaces[property];
46
97
  if (!eventInterface)
47
- throw new Error(`${property} is not a valid property`);
98
+ throw new Error(`eventInterface must be provided`);
48
99
 
49
100
  const changed = setState(device, property, value);
50
101
 
51
- const eventTime = device?.state?.[property]?.lastEventTime;
52
-
53
102
  if (eventInterface !== ScryptedInterface.ScryptedDevice) {
54
- if (this.notify(device?._id, eventTime, eventInterface, property, value, changed) && device) {
55
- this.scrypted.getDeviceLogger(device).log('i', `state change: ${property} ${value}`);
103
+ if (this.notify(device?._id, Date.now(), eventInterface, property, value, { changed }) && device) {
104
+ this.scrypted.getDeviceLogger(device).log('i', `${property}: ${value}`);
56
105
  }
57
106
  }
58
107
 
@@ -63,15 +112,18 @@ export class ScryptedStateManager extends EventRegistry {
63
112
  }
64
113
 
65
114
  updateDescriptor(device: PluginDevice) {
66
- this.notify(device._id, undefined, ScryptedInterface.ScryptedDevice, undefined, device.state, true);
115
+ this.notify(device._id, undefined, ScryptedInterface.ScryptedDevice, undefined, device.state, { changed: true });
67
116
  }
68
117
 
69
118
  removeDevice(id: string) {
70
- this.notify(undefined, undefined, ScryptedInterface.ScryptedDevice, ScryptedInterfaceProperty.id, id, true);
119
+ this.notify(undefined, undefined, ScryptedInterface.ScryptedDevice, ScryptedInterfaceProperty.id, id, { changed: true });
71
120
  }
72
121
 
73
- notifyInterfaceEvent(device: PluginDevice, eventInterface: ScryptedInterface | string, value: any) {
74
- this.notify(device?._id, Date.now(), eventInterface, undefined, value, true);
122
+ notifyInterfaceEvent(device: PluginDevice, eventInterface: ScryptedInterface | string, value: any, mixinId?: string) {
123
+ this.notify(device?._id, Date.now(), eventInterface, undefined, value, {
124
+ changed: true,
125
+ mixinId,
126
+ });
75
127
  }
76
128
 
77
129
  setState(id: string, property: string, value: any) {
@@ -214,12 +266,8 @@ export function setState(pluginDevice: PluginDevice, property: string, value: an
214
266
  if (!pluginDevice.state[property])
215
267
  pluginDevice.state[property] = {};
216
268
  const state = pluginDevice.state[property];
217
- const now = Date.now();
218
269
  const changed = !isSameValue(value, state.value);
219
- if (changed)
220
- state.stateTime = now;
221
270
  state.value = value;
222
- state.lastEventTime = now;
223
271
  return changed;
224
272
  }
225
273