@scrypted/server 0.4.8 → 0.4.10
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/mixin/mixin-cycle.js +2 -1
- package/dist/mixin/mixin-cycle.js.map +1 -1
- package/dist/plugin/plugin-device.js +6 -6
- 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 +14 -0
- package/dist/runtime.js.map +1 -1
- package/dist/scrypted-server-main.js +0 -6
- package/dist/scrypted-server-main.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/mixin/mixin-cycle.ts +1 -1
- package/src/plugin/plugin-api.ts +4 -0
- package/src/plugin/plugin-device.ts +6 -6
- 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 +16 -1
- package/src/scrypted-server-main.ts +0 -7
- package/src/services/plugin.ts +2 -0
- package/src/state.ts +68 -20
package/src/event-registry.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { EventDetails, EventListenerOptions, EventListenerRegister, ScryptedInterface
|
1
|
+
import { EventDetails, EventListenerOptions, EventListenerRegister, ScryptedInterface } from "@scrypted/types";
|
2
2
|
|
3
3
|
export class EventListenerRegisterImpl implements EventListenerRegister {
|
4
4
|
removeListener: () => void;
|
@@ -8,6 +8,18 @@ export class EventListenerRegisterImpl implements EventListenerRegister {
|
|
8
8
|
}
|
9
9
|
}
|
10
10
|
|
11
|
+
export function getMixinEventName(options: string | EventListenerOptions) {
|
12
|
+
let { event, mixinId } = (options || {}) as EventListenerOptions;
|
13
|
+
if (!event && typeof options === 'string')
|
14
|
+
event = options as string;
|
15
|
+
if (!event)
|
16
|
+
event = undefined;
|
17
|
+
if (!mixinId)
|
18
|
+
return event;
|
19
|
+
let ret = `${event}-mixin-${mixinId}`;
|
20
|
+
return ret;
|
21
|
+
}
|
22
|
+
|
11
23
|
// todo: storage should only go to host plugin
|
12
24
|
const allowedEventInterfaces = new Set<string>([ScryptedInterface.ScryptedDevice, 'Logger', 'Storage'])
|
13
25
|
|
@@ -25,11 +37,7 @@ export class EventRegistry {
|
|
25
37
|
}
|
26
38
|
|
27
39
|
listenDevice(id: string, options: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: any) => void): EventListenerRegister {
|
28
|
-
let
|
29
|
-
if (!event && typeof options === 'string')
|
30
|
-
event = options as string;
|
31
|
-
if (!event)
|
32
|
-
event = undefined;
|
40
|
+
let event = getMixinEventName(options)
|
33
41
|
const token = `${id}#${event}`;
|
34
42
|
let events = this.listeners[token];
|
35
43
|
if (!events) {
|
@@ -44,23 +52,34 @@ export class EventRegistry {
|
|
44
52
|
});
|
45
53
|
}
|
46
54
|
|
47
|
-
notify(id: string|undefined, eventTime: number, eventInterface: string, property: string|undefined, value: any,
|
55
|
+
notify(id: string | undefined, eventTime: number, eventInterface: string, property: string | undefined, value: any, options?: {
|
56
|
+
changed?: boolean;
|
57
|
+
mixinId?: string;
|
58
|
+
}): boolean {
|
59
|
+
const { changed, mixinId } = options || {};
|
48
60
|
// prevent property event noise
|
49
61
|
if (property && !changed)
|
50
62
|
return false;
|
51
63
|
|
52
64
|
const eventDetails: EventDetails = {
|
53
|
-
eventId:
|
54
|
-
changed,
|
65
|
+
eventId: undefined,
|
55
66
|
eventInterface,
|
56
67
|
eventTime,
|
57
68
|
property,
|
69
|
+
mixinId,
|
58
70
|
};
|
59
71
|
|
72
|
+
return this.notifyEventDetails(id, eventDetails, value);
|
73
|
+
}
|
74
|
+
|
75
|
+
notifyEventDetails(id: string | undefined, eventDetails: EventDetails, value: any, eventInterface?: string) {
|
76
|
+
eventDetails.eventId ||= Math.random().toString(36).substring(2);
|
77
|
+
eventInterface ||= eventDetails.eventInterface;
|
78
|
+
|
60
79
|
// system listeners only get state changes.
|
61
80
|
// there are many potentially noisy stateless events, like
|
62
81
|
// object detection and settings changes.
|
63
|
-
if (property || allowedEventInterfaces.has(eventInterface)) {
|
82
|
+
if ((eventDetails.property && !eventDetails.mixinId) || allowedEventInterfaces.has(eventInterface)) {
|
64
83
|
for (const event of this.systemListeners) {
|
65
84
|
event(id, eventDetails, value);
|
66
85
|
}
|
package/src/media-helpers.ts
CHANGED
@@ -25,6 +25,9 @@ export function safeKillFFmpeg(cp: ChildProcess) {
|
|
25
25
|
}
|
26
26
|
|
27
27
|
export function ffmpegLogInitialOutput(console: Console, cp: ChildProcess, forever?: boolean, storage?: Storage) {
|
28
|
+
if (!console)
|
29
|
+
return;
|
30
|
+
|
28
31
|
const SCRYPTED_FFMPEG_NOISY = !!process.env.SCRYPTED_FFMPEG_NOISY || !!storage?.getItem('SCRYPTED_FFMPEG_NOISY');
|
29
32
|
|
30
33
|
function logger(log: (str: string) => void): (buffer: Buffer) => void {
|
@@ -55,6 +58,8 @@ export function ffmpegLogInitialOutput(console: Console, cp: ChildProcess, forev
|
|
55
58
|
}
|
56
59
|
|
57
60
|
export function safePrintFFmpegArguments(console: Console, args: string[]) {
|
61
|
+
if (!console)
|
62
|
+
return;
|
58
63
|
const ret = [];
|
59
64
|
let redactNext = false;
|
60
65
|
for (const arg of args) {
|
package/src/mixin/mixin-cycle.ts
CHANGED
@@ -2,7 +2,7 @@ import { ScryptedInterfaceProperty } from "@scrypted/types";
|
|
2
2
|
import { ScryptedRuntime } from "../runtime";
|
3
3
|
import { getState } from "../state";
|
4
4
|
|
5
|
-
function getMixins(scrypted: ScryptedRuntime, id: string) {
|
5
|
+
export function getMixins(scrypted: ScryptedRuntime, id: string) {
|
6
6
|
const pluginDevice = scrypted.findPluginDeviceById(id);
|
7
7
|
if (!pluginDevice)
|
8
8
|
return [];
|
package/src/plugin/plugin-api.ts
CHANGED
@@ -171,7 +171,11 @@ export interface PluginRemote {
|
|
171
171
|
setSystemState(state: { [id: string]: { [property: string]: SystemDeviceState } }): Promise<void>;
|
172
172
|
setNativeId(nativeId: ScryptedNativeId, id: string, storage: { [key: string]: any }): Promise<void>;
|
173
173
|
updateDeviceState(id: string, state: { [property: string]: SystemDeviceState }): Promise<void>;
|
174
|
+
/**
|
175
|
+
* @deprecated
|
176
|
+
*/
|
174
177
|
notify(id: string, eventTime: number, eventInterface: string, property: string | undefined, value: SystemDeviceState | any, changed?: boolean): Promise<void>;
|
178
|
+
notify(id: string, eventDetails: EventDetails, eventData: SystemDeviceState | any): Promise<void>;
|
175
179
|
|
176
180
|
ioEvent(id: string, event: string, message?: any): Promise<void>;
|
177
181
|
|
@@ -56,23 +56,23 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
|
|
56
56
|
// allow mixins in the process of being released to manage final
|
57
57
|
// events, etc, before teardown.
|
58
58
|
this.releasing.add(proxy);
|
59
|
-
mixinProvider?.releaseMixin(this.id, proxy);
|
59
|
+
mixinProvider?.releaseMixin(this.id, proxy).catch(() => { });
|
60
60
|
await sleep(1000);
|
61
61
|
this.releasing.delete(proxy);
|
62
62
|
})().catch(() => { });
|
63
63
|
}
|
64
64
|
|
65
|
-
async
|
65
|
+
async getMixinProviderId(id: string, mixinDevice: any) {
|
66
66
|
if (this.releasing.has(mixinDevice))
|
67
67
|
return true;
|
68
68
|
await this.scrypted.devices[id].handler.ensureProxy();
|
69
69
|
for (const mixin of this.scrypted.devices[id].handler.mixinTable) {
|
70
70
|
const { proxy } = await mixin.entry;
|
71
71
|
if (proxy === mixinDevice) {
|
72
|
-
return
|
72
|
+
return mixin.mixinProviderId || id;
|
73
73
|
}
|
74
74
|
}
|
75
|
-
return
|
75
|
+
return undefined;
|
76
76
|
}
|
77
77
|
|
78
78
|
// should this be async?
|
@@ -260,7 +260,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
|
|
260
260
|
const propertyInterfaces = getPropertyInterfaces(host.api.descriptors || ScryptedInterfaceDescriptors);
|
261
261
|
// todo: remove this and pass the setter directly.
|
262
262
|
const deviceState = await host.remote.createDeviceState(this.id,
|
263
|
-
async (property, value) => this.scrypted.stateManager.
|
263
|
+
async (property, value) => this.scrypted.stateManager.setPluginDeviceStateFromMixin(pluginDevice, property, value, propertyInterfaces[property], mixinId));
|
264
264
|
const mixinProxy = await mixinProvider.getMixin(wrappedProxy, previousInterfaces as ScryptedInterface[], deviceState);
|
265
265
|
if (!mixinProxy)
|
266
266
|
throw new PluginError(`mixin provider ${mixinId} did not return mixin for ${this.id}`);
|
@@ -370,7 +370,7 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
|
|
370
370
|
const pluginDevice = this.scrypted.findPluginDeviceById(mixin.mixinProviderId || this.id);
|
371
371
|
const plugin = this.scrypted.plugins[pluginDevice.pluginId];
|
372
372
|
let methods = new Set<string>(getInterfaceMethods(ScryptedInterfaceDescriptors, entry.interfaces))
|
373
|
-
if (plugin
|
373
|
+
if (plugin?.api.descriptors)
|
374
374
|
methods = new Set<string>([...methods, ...getInterfaceMethods(plugin.api.descriptors, entry.interfaces)]);
|
375
375
|
entry.methods = methods;
|
376
376
|
}
|
@@ -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
@@ -20,7 +20,7 @@ import { getDisplayName, getDisplayRoom, getDisplayType, getProvidedNameOrDefaul
|
|
20
20
|
import { IOServer } from './io';
|
21
21
|
import { Level } from './level';
|
22
22
|
import { LogEntry, Logger, makeAlertId } from './logger';
|
23
|
-
import { hasMixinCycle } from './mixin/mixin-cycle';
|
23
|
+
import { getMixins, hasMixinCycle } from './mixin/mixin-cycle';
|
24
24
|
import { AccessControls } from './plugin/acl';
|
25
25
|
import { PluginDebug } from './plugin/plugin-debug';
|
26
26
|
import { PluginDeviceProxyHandler } from './plugin/plugin-device';
|
@@ -652,10 +652,25 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
652
652
|
this.setupPluginHostAutoRestart(pluginHost);
|
653
653
|
this.plugins[pluginId] = pluginHost;
|
654
654
|
|
655
|
+
const pluginDeviceSet = new Set<string>();
|
655
656
|
for (const pluginDevice of pluginDevices) {
|
657
|
+
if (pluginDeviceSet.has(pluginDevice._id))
|
658
|
+
continue;
|
659
|
+
pluginDeviceSet.add(pluginDevice._id);
|
656
660
|
this.getDevice(pluginDevice._id)?.probe().catch(() => { });
|
657
661
|
}
|
658
662
|
|
663
|
+
for (const pluginDevice of Object.values(this.pluginDevices)) {
|
664
|
+
const { _id } = pluginDevice;
|
665
|
+
if (pluginDeviceSet.has(_id))
|
666
|
+
continue;
|
667
|
+
for (const mixinId of getMixins(this, _id)) {
|
668
|
+
if (pluginDeviceSet.has(mixinId)) {
|
669
|
+
this.getDevice(_id)?.probe().catch(() => { });
|
670
|
+
}
|
671
|
+
}
|
672
|
+
}
|
673
|
+
|
659
674
|
return pluginHost;
|
660
675
|
}
|
661
676
|
|
@@ -161,13 +161,6 @@ async function start() {
|
|
161
161
|
next();
|
162
162
|
})
|
163
163
|
|
164
|
-
app.options('*', (req, res) => {
|
165
|
-
// add more?
|
166
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
167
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With');
|
168
|
-
res.send(200);
|
169
|
-
});
|
170
|
-
|
171
164
|
const authSalt = crypto.randomBytes(16);
|
172
165
|
const createAuthorizationToken = (login_user_token: string) => {
|
173
166
|
const salted = login_user_token + authSalt;
|
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
|
|