@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.
- package/dist/event-registry.js +24 -10
- package/dist/event-registry.js.map +1 -1
- package/dist/media-helpers.js +4 -0
- package/dist/media-helpers.js.map +1 -1
- package/dist/plugin/plugin-device.js +5 -5
- package/dist/plugin/plugin-device.js.map +1 -1
- package/dist/plugin/plugin-host-api.js +18 -10
- package/dist/plugin/plugin-host-api.js.map +1 -1
- package/dist/plugin/plugin-lazy-remote.js +4 -2
- package/dist/plugin/plugin-lazy-remote.js.map +1 -1
- package/dist/plugin/plugin-remote.js +36 -16
- package/dist/plugin/plugin-remote.js.map +1 -1
- package/dist/plugin/system.js +2 -6
- package/dist/plugin/system.js.map +1 -1
- package/dist/rpc.js +3 -0
- package/dist/rpc.js.map +1 -1
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/services/plugin.js +2 -0
- package/dist/services/plugin.js.map +1 -1
- package/dist/state.js +57 -16
- package/dist/state.js.map +1 -1
- package/package.json +2 -2
- package/src/event-registry.ts +29 -10
- package/src/media-helpers.ts +5 -0
- package/src/plugin/plugin-api.ts +4 -0
- package/src/plugin/plugin-device.ts +5 -5
- package/src/plugin/plugin-host-api.ts +20 -10
- package/src/plugin/plugin-lazy-remote.ts +5 -3
- package/src/plugin/plugin-remote.ts +38 -18
- package/src/plugin/system.ts +2 -6
- package/src/rpc.ts +3 -0
- package/src/runtime.ts +1 -1
- package/src/services/plugin.ts +2 -0
- package/src/state.ts +68 -20
@@ -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
|
-
|
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
|
-
|
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.
|
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
|
-
|
152
|
-
|
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
|
-
|
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,
|
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?:
|
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
|
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
|
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,
|
579
|
-
if (
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
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
|
-
|
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
|
|
package/src/plugin/system.ts
CHANGED
@@ -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 {
|
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,
|
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
|
397
|
+
if (user?.aclId) {
|
398
398
|
const accessControl = this.getDevice<SU>(user.aclId);
|
399
399
|
try {
|
400
400
|
const acls = await accessControl.getScryptedUserAccessControl();
|
package/src/services/plugin.ts
CHANGED
@@ -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 {
|
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
|
-
|
38
|
-
|
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
|
41
|
-
|
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(
|
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,
|
55
|
-
this.scrypted.getDeviceLogger(device).log('i',
|
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,
|
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
|
|