@scrypted/server 0.0.108 → 0.0.112
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 +2 -2
- package/dist/event-registry.js.map +1 -1
- package/dist/plugin/plugin-api.js +2 -2
- package/dist/plugin/plugin-api.js.map +1 -1
- package/dist/plugin/plugin-device.js +1 -0
- package/dist/plugin/plugin-device.js.map +1 -1
- package/dist/plugin/plugin-host-api.js +10 -8
- package/dist/plugin/plugin-host-api.js.map +1 -1
- package/dist/plugin/plugin-npm-dependencies.js +16 -16
- package/dist/plugin/plugin-npm-dependencies.js.map +1 -1
- package/dist/plugin/system.js +11 -3
- package/dist/plugin/system.js.map +1 -1
- package/dist/rpc.js +57 -27
- package/dist/rpc.js.map +1 -1
- package/dist/state.js.map +1 -1
- package/package.json +1 -1
- package/python/plugin-remote.py +4 -4
- package/python/rpc.py +68 -26
- package/src/event-registry.ts +5 -5
- package/src/plugin/plugin-api.ts +4 -4
- package/src/plugin/plugin-device.ts +2 -1
- package/src/plugin/plugin-host-api.ts +11 -9
- package/src/plugin/plugin-npm-dependencies.ts +19 -18
- package/src/plugin/system.ts +15 -6
- package/src/rpc.ts +66 -26
- package/src/state.ts +2 -2
package/package.json
CHANGED
package/python/plugin-remote.py
CHANGED
|
@@ -21,7 +21,7 @@ from asyncio.futures import Future
|
|
|
21
21
|
from asyncio.streams import StreamReader, StreamWriter
|
|
22
22
|
import os
|
|
23
23
|
from os import sys
|
|
24
|
-
|
|
24
|
+
import platform
|
|
25
25
|
|
|
26
26
|
class SystemDeviceState(TypedDict):
|
|
27
27
|
lastEventTime: int
|
|
@@ -164,8 +164,8 @@ class PluginRemote:
|
|
|
164
164
|
|
|
165
165
|
zip = zipfile.ZipFile(zipPath)
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
plugin_volume = os.environ.get('SCRYPTED_PLUGIN_VOLUME')
|
|
168
|
+
python_prefix = os.path.join(plugin_volume, 'python-%s-%s' % (platform.system(), platform.machine()))
|
|
169
169
|
if not os.path.exists(python_prefix):
|
|
170
170
|
os.makedirs(python_prefix)
|
|
171
171
|
|
|
@@ -179,7 +179,7 @@ class PluginRemote:
|
|
|
179
179
|
|
|
180
180
|
requirementstxt = os.path.join(python_prefix, 'requirements.txt')
|
|
181
181
|
installed_requirementstxt = os.path.join(
|
|
182
|
-
python_prefix, '
|
|
182
|
+
python_prefix, 'requirements.installed.txt')
|
|
183
183
|
|
|
184
184
|
need_pip = True
|
|
185
185
|
try:
|
package/python/rpc.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from asyncio.futures import Future
|
|
2
|
-
from typing import Callable, Mapping, List
|
|
2
|
+
from typing import Any, Callable, Mapping, List
|
|
3
3
|
import traceback
|
|
4
4
|
import inspect
|
|
5
|
+
from typing_extensions import TypedDict
|
|
5
6
|
import weakref
|
|
7
|
+
import sys
|
|
6
8
|
|
|
7
9
|
jsonSerializable = set()
|
|
8
10
|
jsonSerializable.add(float)
|
|
@@ -45,21 +47,35 @@ class RpcProxyMethod:
|
|
|
45
47
|
return self.__proxy.__apply__(self.__proxy_method_name, args)
|
|
46
48
|
|
|
47
49
|
|
|
50
|
+
class LocalProxiedEntry(TypedDict):
|
|
51
|
+
id: str
|
|
52
|
+
finalizerId: str
|
|
53
|
+
|
|
54
|
+
|
|
48
55
|
class RpcProxy(object):
|
|
49
|
-
def __init__(self, peer,
|
|
50
|
-
self.__dict__['__proxy_id'] =
|
|
56
|
+
def __init__(self, peer, entry: LocalProxiedEntry, proxyConstructorName: str, proxyProps: any, proxyOneWayMethods: List[str]):
|
|
57
|
+
self.__dict__['__proxy_id'] = entry['id']
|
|
58
|
+
self.__dict__['__proxy_entry'] = entry
|
|
51
59
|
self.__dict__['__proxy_constructor'] = proxyConstructorName
|
|
52
60
|
self.__dict__['__proxy_peer'] = peer
|
|
53
61
|
self.__dict__['__proxy_props'] = proxyProps
|
|
54
62
|
self.__dict__['__proxy_oneway_methods'] = proxyOneWayMethods
|
|
55
63
|
|
|
56
64
|
def __getattr__(self, name):
|
|
65
|
+
if name == '__proxy_finalizer_id':
|
|
66
|
+
return self.dict['__proxy_entry']['finalizerId']
|
|
57
67
|
if name in self.__dict__:
|
|
58
68
|
return self.__dict__[name]
|
|
59
69
|
if self.__dict__['__proxy_props'] and name in self.__dict__['__proxy_props']:
|
|
60
70
|
return self.__dict__['__proxy_props'][name]
|
|
61
71
|
return RpcProxyMethod(self, name)
|
|
62
72
|
|
|
73
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
74
|
+
if name == '__proxy_finalizer_id':
|
|
75
|
+
self.dict['__proxy_entry']['finalizerId'] = value
|
|
76
|
+
|
|
77
|
+
return super().__setattr__(name, value)
|
|
78
|
+
|
|
63
79
|
def __call__(self, *args, **kwargs):
|
|
64
80
|
print('call')
|
|
65
81
|
pass
|
|
@@ -69,20 +85,18 @@ class RpcProxy(object):
|
|
|
69
85
|
|
|
70
86
|
|
|
71
87
|
class RpcPeer:
|
|
72
|
-
# todo: these are all class statics lol, fix this.
|
|
73
|
-
idCounter = 1
|
|
74
|
-
peerName = 'Unnamed Peer'
|
|
75
|
-
params: Mapping[str, any] = {}
|
|
76
|
-
localProxied: Mapping[any, str] = {}
|
|
77
|
-
localProxyMap: Mapping[str, any] = {}
|
|
78
|
-
constructorSerializerMap = {}
|
|
79
|
-
proxyCounter = 1
|
|
80
|
-
pendingResults: Mapping[str, Future] = {}
|
|
81
|
-
remoteWeakProxies: Mapping[str, any] = {}
|
|
82
|
-
nameDeserializerMap: Mapping[str, RpcSerializer] = {}
|
|
83
|
-
|
|
84
88
|
def __init__(self, send: Callable[[object, Callable[[Exception], None]], None]) -> None:
|
|
85
89
|
self.send = send
|
|
90
|
+
self.idCounter = 1
|
|
91
|
+
self.peerName = 'Unnamed Peer'
|
|
92
|
+
self.params: Mapping[str, any] = {}
|
|
93
|
+
self.localProxied: Mapping[any, LocalProxiedEntry] = {}
|
|
94
|
+
self.localProxyMap: Mapping[str, any] = {}
|
|
95
|
+
self.constructorSerializerMap = {}
|
|
96
|
+
self.proxyCounter = 1
|
|
97
|
+
self.pendingResults: Mapping[str, Future] = {}
|
|
98
|
+
self.remoteWeakProxies: Mapping[str, any] = {}
|
|
99
|
+
self.nameDeserializerMap: Mapping[str, RpcSerializer] = {}
|
|
86
100
|
|
|
87
101
|
def __apply__(self, proxyId: str, oneWayMethods: List[str], method: str, args: list):
|
|
88
102
|
serializedArgs = []
|
|
@@ -120,12 +134,17 @@ class RpcPeer:
|
|
|
120
134
|
def serialize(self, value, requireProxy):
|
|
121
135
|
if (not value or (not requireProxy and type(value) in jsonSerializable)):
|
|
122
136
|
return value
|
|
137
|
+
|
|
123
138
|
__remote_constructor_name = 'Function' if callable(value) else value.__proxy_constructor if hasattr(
|
|
124
139
|
value, '__proxy_constructor') else type(value).__name__
|
|
125
|
-
|
|
126
|
-
|
|
140
|
+
|
|
141
|
+
proxiedEntry = self.localProxied.get(value, None)
|
|
142
|
+
if proxiedEntry:
|
|
143
|
+
proxiedEntry['finalizerId'] = str(self.proxyCounter)
|
|
144
|
+
self.proxyCounter = self.proxyCounter + 1
|
|
127
145
|
ret = {
|
|
128
|
-
'__remote_proxy_id':
|
|
146
|
+
'__remote_proxy_id': proxiedEntry['id'],
|
|
147
|
+
'__remote_proxy_finalizer_id': proxiedEntry['finalizerId'],
|
|
129
148
|
'__remote_constructor_name': __remote_constructor_name,
|
|
130
149
|
'__remote_proxy_props': getattr(value, '__proxy_props', None),
|
|
131
150
|
'__remote_proxy_oneway_methods': getattr(value, '__proxy_oneway_methods', None),
|
|
@@ -140,13 +159,15 @@ class RpcPeer:
|
|
|
140
159
|
}
|
|
141
160
|
return ret
|
|
142
161
|
|
|
143
|
-
serializerMapName = self.constructorSerializerMap.get(
|
|
162
|
+
serializerMapName = self.constructorSerializerMap.get(
|
|
163
|
+
type(value), None)
|
|
144
164
|
if serializerMapName:
|
|
145
165
|
__remote_constructor_name = serializerMapName
|
|
146
166
|
serializer = self.nameDeserializerMap.get(serializerMapName, None)
|
|
147
167
|
serialized = serializer.serialize(value)
|
|
148
168
|
ret = {
|
|
149
169
|
'__remote_proxy_id': None,
|
|
170
|
+
'__remote_proxy_finalizer_id': None,
|
|
150
171
|
'__remote_constructor_name': __remote_constructor_name,
|
|
151
172
|
'__remote_proxy_props': getattr(value, '__proxy_props', None),
|
|
152
173
|
'__remote_proxy_oneway_methods': getattr(value, '__proxy_oneway_methods', None),
|
|
@@ -156,11 +177,16 @@ class RpcPeer:
|
|
|
156
177
|
|
|
157
178
|
proxyId = str(self.proxyCounter)
|
|
158
179
|
self.proxyCounter = self.proxyCounter + 1
|
|
159
|
-
|
|
180
|
+
proxiedEntry = {
|
|
181
|
+
'id': proxyId,
|
|
182
|
+
'finalizerId': proxyId,
|
|
183
|
+
}
|
|
184
|
+
self.localProxied[value] = proxiedEntry
|
|
160
185
|
self.localProxyMap[proxyId] = value
|
|
161
186
|
|
|
162
187
|
ret = {
|
|
163
188
|
'__remote_proxy_id': proxyId,
|
|
189
|
+
'__remote_proxy_finalizer_id': proxyId,
|
|
164
190
|
'__remote_constructor_name': __remote_constructor_name,
|
|
165
191
|
'__remote_proxy_props': getattr(value, '__proxy_props', None),
|
|
166
192
|
'__remote_proxy_oneway_methods': getattr(value, '__proxy_oneway_methods', None),
|
|
@@ -168,20 +194,26 @@ class RpcPeer:
|
|
|
168
194
|
|
|
169
195
|
return ret
|
|
170
196
|
|
|
171
|
-
def finalize(self,
|
|
197
|
+
def finalize(self, localProxiedEntry: LocalProxiedEntry):
|
|
198
|
+
id = localProxiedEntry['id']
|
|
172
199
|
self.remoteWeakProxies.pop(id, None)
|
|
173
200
|
rpcFinalize = {
|
|
174
201
|
'__local_proxy_id': id,
|
|
202
|
+
'__local_proxy_finalizer_id': localProxiedEntry['finalizerId'],
|
|
175
203
|
'type': 'finalize',
|
|
176
204
|
}
|
|
177
205
|
self.send(rpcFinalize)
|
|
178
206
|
|
|
179
207
|
def newProxy(self, proxyId: str, proxyConstructorName: str, proxyProps: any, proxyOneWayMethods: List[str]):
|
|
180
|
-
|
|
208
|
+
localProxiedEntry: LocalProxiedEntry = {
|
|
209
|
+
'id': proxyId,
|
|
210
|
+
'finalizerId': None,
|
|
211
|
+
}
|
|
212
|
+
proxy = RpcProxy(self, localProxiedEntry, proxyConstructorName,
|
|
181
213
|
proxyProps, proxyOneWayMethods)
|
|
182
214
|
wr = weakref.ref(proxy)
|
|
183
215
|
self.remoteWeakProxies[proxyId] = wr
|
|
184
|
-
weakref.finalize(proxy, lambda: self.finalize(
|
|
216
|
+
weakref.finalize(proxy, lambda: self.finalize(localProxiedEntry))
|
|
185
217
|
return proxy
|
|
186
218
|
|
|
187
219
|
def deserialize(self, value):
|
|
@@ -192,6 +224,8 @@ class RpcPeer:
|
|
|
192
224
|
return value
|
|
193
225
|
|
|
194
226
|
__remote_proxy_id = value.get('__remote_proxy_id', None)
|
|
227
|
+
__remote_proxy_finalizer_id = value.get(
|
|
228
|
+
'__remote_proxy_finalizer_id', None)
|
|
195
229
|
__local_proxy_id = value.get('__local_proxy_id', None)
|
|
196
230
|
__remote_constructor_name = value.get(
|
|
197
231
|
'__remote_constructor_name', None)
|
|
@@ -206,6 +240,7 @@ class RpcPeer:
|
|
|
206
240
|
if not proxy:
|
|
207
241
|
proxy = self.newProxy(__remote_proxy_id, __remote_constructor_name,
|
|
208
242
|
__remote_proxy_props, __remote_proxy_oneway_methods)
|
|
243
|
+
proxy.__proxy_finalizer_id = __remote_proxy_finalizer_id
|
|
209
244
|
return proxy
|
|
210
245
|
|
|
211
246
|
if __local_proxy_id:
|
|
@@ -297,9 +332,16 @@ class RpcPeer:
|
|
|
297
332
|
future.set_result(self.deserialize(
|
|
298
333
|
message.get('result', None)))
|
|
299
334
|
elif messageType == 'finalize':
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
self.
|
|
335
|
+
finalizerId = message.get('__local_proxy_finalizer_id', None)
|
|
336
|
+
proxyId = message['__local_proxy_id']
|
|
337
|
+
local = self.localProxyMap.get(proxyId, None)
|
|
338
|
+
if local:
|
|
339
|
+
localProxiedEntry = self.localProxied.get(local)
|
|
340
|
+
if localProxiedEntry and finalizerId and localProxiedEntry['finalizerId'] != finalizerId:
|
|
341
|
+
print('mismatch finalizer id', file=sys.stderr)
|
|
342
|
+
return
|
|
343
|
+
self.localProxied.pop(local, None)
|
|
344
|
+
local = self.localProxyMap.pop(proxyId, None)
|
|
303
345
|
else:
|
|
304
346
|
raise RpcResultException(
|
|
305
347
|
None, 'unknown rpc message type %s' % type)
|
package/src/event-registry.ts
CHANGED
|
@@ -12,12 +12,12 @@ export class EventListenerRegisterImpl implements EventListenerRegister {
|
|
|
12
12
|
const allowedEventInterfaces = new Set<string>([ScryptedInterface.ScryptedDevice, 'Logger', 'Storage'])
|
|
13
13
|
|
|
14
14
|
export class EventRegistry {
|
|
15
|
-
systemListeners = new Set<(id: string, eventDetails: EventDetails, eventData:
|
|
16
|
-
listeners: { [token: string]: Set<(eventDetails: EventDetails, eventData:
|
|
15
|
+
systemListeners = new Set<(id: string, eventDetails: EventDetails, eventData: any) => void>();
|
|
16
|
+
listeners: { [token: string]: Set<(eventDetails: EventDetails, eventData: any) => void> } = {};
|
|
17
17
|
|
|
18
|
-
listen(
|
|
18
|
+
listen(callback: (id: string, eventDetails: EventDetails, eventData: any) => void): EventListenerRegister {
|
|
19
19
|
const events = this.systemListeners;
|
|
20
|
-
let cb =
|
|
20
|
+
let cb = callback;
|
|
21
21
|
events.add(cb);
|
|
22
22
|
return new EventListenerRegisterImpl(() => {
|
|
23
23
|
events.delete(cb);
|
|
@@ -25,7 +25,7 @@ export class EventRegistry {
|
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
listenDevice(id: string, options: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData:
|
|
28
|
+
listenDevice(id: string, options: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: any) => void): EventListenerRegister {
|
|
29
29
|
let { event } = (options || {}) as EventListenerOptions;
|
|
30
30
|
if (!event && typeof options === 'string')
|
|
31
31
|
event = options as string;
|
package/src/plugin/plugin-api.ts
CHANGED
|
@@ -20,7 +20,7 @@ export interface PluginAPI {
|
|
|
20
20
|
setDeviceProperty(id: string, property: ScryptedInterfaceProperty, value: any): Promise<void>;
|
|
21
21
|
removeDevice(id: string): Promise<void>;
|
|
22
22
|
listen(EventListener: (id: string, eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister>;
|
|
23
|
-
listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData:
|
|
23
|
+
listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister>;
|
|
24
24
|
|
|
25
25
|
ioClose(id: string): Promise<void>;
|
|
26
26
|
ioSend(id: string, message: string): Promise<void>;
|
|
@@ -101,10 +101,10 @@ export class PluginAPIProxy extends PluginAPIManagedListeners implements PluginA
|
|
|
101
101
|
removeDevice(id: string): Promise<void> {
|
|
102
102
|
return this.api.removeDevice(id);
|
|
103
103
|
}
|
|
104
|
-
async listen(
|
|
105
|
-
return this.manageListener(await this.api.listen(
|
|
104
|
+
async listen(callback: (id: string, eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister> {
|
|
105
|
+
return this.manageListener(await this.api.listen(callback));
|
|
106
106
|
}
|
|
107
|
-
async listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData:
|
|
107
|
+
async listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister> {
|
|
108
108
|
return this.manageListener(await this.api.listenDevice(id, event, callback));
|
|
109
109
|
}
|
|
110
110
|
ioClose(id: string): Promise<void> {
|
|
@@ -54,6 +54,7 @@ export class PluginDeviceProxyHandler implements ProxyHandler<any>, ScryptedDevi
|
|
|
54
54
|
async isMixin(id: string, mixinDevice: any) {
|
|
55
55
|
if (this.releasing.has(mixinDevice))
|
|
56
56
|
return true;
|
|
57
|
+
await this.scrypted.devices[id].handler.ensureProxy();
|
|
57
58
|
for (const mixin of this.scrypted.devices[id].handler.mixinTable) {
|
|
58
59
|
const { proxy } = await mixin.entry;
|
|
59
60
|
if (proxy === mixinDevice) {
|
|
@@ -298,7 +299,7 @@ export class PluginDeviceProxyHandler implements ProxyHandler<any>, ScryptedDevi
|
|
|
298
299
|
return new Proxy(() => prop, this);
|
|
299
300
|
}
|
|
300
301
|
|
|
301
|
-
listen(event: string | EventListenerOptions, callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData:
|
|
302
|
+
listen(event: string | EventListenerOptions, callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any) => void): EventListenerRegister {
|
|
302
303
|
return this.scrypted.stateManager.listenDevice(this.id, event, (eventDetails, eventData) => callback(this.scrypted.getDevice(this.id), eventDetails, eventData));
|
|
303
304
|
}
|
|
304
305
|
async setName(name: string): Promise<void> {
|
|
@@ -47,22 +47,23 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
|
|
|
47
47
|
const device = this.scrypted.findPluginDeviceById(id);
|
|
48
48
|
|
|
49
49
|
if (!nativeIdOrMixinDevice || typeof nativeIdOrMixinDevice === 'string') {
|
|
50
|
+
const nativeId: string = nativeIdOrMixinDevice;
|
|
50
51
|
// todo: deprecate this code path
|
|
51
|
-
const mixinProvider = this.scrypted.findPluginDevice(this.pluginId,
|
|
52
|
+
const mixinProvider = this.scrypted.findPluginDevice(this.pluginId, nativeId);
|
|
52
53
|
const mixins: string[] = getState(device, ScryptedInterfaceProperty.mixins) || [];
|
|
53
54
|
if (!mixins.includes(mixinProvider._id))
|
|
54
55
|
throw new Error(`${mixinProvider._id} is not a mixin provider for ${id}`);
|
|
55
56
|
|
|
56
|
-
this.scrypted.findPluginDevice(this.pluginId,
|
|
57
|
+
this.scrypted.findPluginDevice(this.pluginId, nativeId);
|
|
57
58
|
const tableEntry = this.scrypted.devices[device._id].handler.mixinTable.find(entry => entry.mixinProviderId === mixinProvider._id);
|
|
58
59
|
const { interfaces } = await tableEntry.entry;
|
|
59
60
|
if (!interfaces.has(eventInterface))
|
|
60
61
|
throw new Error(`${mixinProvider._id} does not mixin ${eventInterface} for ${id}`);
|
|
61
62
|
}
|
|
62
63
|
else {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
throw new Error(`${
|
|
64
|
+
const mixin: object = nativeIdOrMixinDevice;
|
|
65
|
+
if (!await this.scrypted.devices[device._id]?.handler?.isMixin(id, mixin)) {
|
|
66
|
+
throw new Error(`${mixin} does not mixin ${eventInterface} for ${id}`);
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
this.scrypted.stateManager.notifyInterfaceEvent(device, eventInterface, eventData);
|
|
@@ -121,7 +122,8 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
async onDevicesChanged(deviceManifest: DeviceManifest) {
|
|
124
|
-
const
|
|
125
|
+
const provider = this.scrypted.findPluginDevice(this.pluginId, deviceManifest.providerNativeId);
|
|
126
|
+
const existing = this.scrypted.findPluginDevices(this.pluginId).filter(p => p.state[ScryptedInterfaceProperty.providerId].value === provider._id);
|
|
125
127
|
const newIds = deviceManifest.devices.map(device => device.nativeId);
|
|
126
128
|
const toRemove = existing.filter(e => e.nativeId && !newIds.includes(e.nativeId));
|
|
127
129
|
|
|
@@ -150,10 +152,10 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
|
|
|
150
152
|
async getDeviceById<T>(id: string): Promise<T & ScryptedDevice> {
|
|
151
153
|
return this.scrypted.getDevice(id);
|
|
152
154
|
}
|
|
153
|
-
async listen(
|
|
154
|
-
return this.manageListener(this.scrypted.stateManager.listen(
|
|
155
|
+
async listen(callback: (id: string, eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister> {
|
|
156
|
+
return this.manageListener(this.scrypted.stateManager.listen(callback));
|
|
155
157
|
}
|
|
156
|
-
async listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData:
|
|
158
|
+
async listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister> {
|
|
157
159
|
const device = this.scrypted.findPluginDeviceById(id);
|
|
158
160
|
if (device) {
|
|
159
161
|
const self = this.scrypted.findPluginDevice(this.pluginId);
|
|
@@ -3,14 +3,18 @@ import fs from 'fs';
|
|
|
3
3
|
import child_process from 'child_process';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { once } from 'events';
|
|
6
|
+
import process from 'process';
|
|
7
|
+
import mkdirp from "mkdirp";
|
|
6
8
|
|
|
7
9
|
export async function installOptionalDependencies(console: Console, packageJson: any) {
|
|
8
10
|
const pluginVolume = ensurePluginVolume(packageJson.name);
|
|
9
|
-
const
|
|
11
|
+
const nodePrefix = path.join(pluginVolume, `${process.platform}-${process.arch}`);
|
|
12
|
+
const packageJsonPath = path.join(nodePrefix, 'package.json');
|
|
13
|
+
const currentInstalledPackageJsonPath = path.join(nodePrefix, 'package.installed.json');
|
|
10
14
|
|
|
11
15
|
let currentPackageJson: any;
|
|
12
16
|
try {
|
|
13
|
-
currentPackageJson = JSON.parse(fs.readFileSync(
|
|
17
|
+
currentPackageJson = JSON.parse(fs.readFileSync(currentInstalledPackageJsonPath).toString());
|
|
14
18
|
}
|
|
15
19
|
catch (e) {
|
|
16
20
|
}
|
|
@@ -34,21 +38,18 @@ export async function installOptionalDependencies(console: Console, packageJson:
|
|
|
34
38
|
delete reduced.optionalDependencies;
|
|
35
39
|
delete reduced.devDependencies;
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
fs.rmSync(optPj);
|
|
51
|
-
throw e;
|
|
52
|
-
}
|
|
41
|
+
mkdirp.sync(nodePrefix);
|
|
42
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(reduced));
|
|
43
|
+
|
|
44
|
+
const cp = child_process.spawn('npm', ['--prefix', nodePrefix, 'install'], {
|
|
45
|
+
cwd: nodePrefix,
|
|
46
|
+
stdio: 'inherit',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await once(cp, 'exit');
|
|
50
|
+
if (cp.exitCode !== 0)
|
|
51
|
+
throw new Error('npm installation failed with exit code ' + cp.exitCode);
|
|
52
|
+
|
|
53
|
+
fs.writeFileSync(currentInstalledPackageJsonPath, JSON.stringify(reduced));
|
|
53
54
|
console.log('native dependencies installed.');
|
|
54
55
|
}
|
package/src/plugin/system.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventListenerOptions, EventDetails, EventListenerRegister, ScryptedDevice, ScryptedInterface, ScryptedInterfaceDescriptors, SystemDeviceState, SystemManager, ScryptedInterfaceProperty, ScryptedDeviceType, Logger } from "@scrypted/sdk/types";
|
|
2
2
|
import { PluginAPI } from "./plugin-api";
|
|
3
|
-
import { handleFunctionInvocations } from '../rpc';
|
|
3
|
+
import { handleFunctionInvocations, PROPERTY_PROXY_ONEWAY_METHODS } from '../rpc';
|
|
4
4
|
import { EventRegistry } from "../event-registry";
|
|
5
5
|
import { allInterfaceProperties, isValidInterfaceMethod } from "./descriptor";
|
|
6
6
|
|
|
@@ -56,7 +56,7 @@ class DeviceProxyHandler implements ProxyHandler<any>, ScryptedDevice {
|
|
|
56
56
|
return (this.device as any)[method](...argArray);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
listen(event: string | EventListenerOptions, callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData:
|
|
59
|
+
listen(event: string | EventListenerOptions, callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any) => void): EventListenerRegister {
|
|
60
60
|
return this.systemManager.listenDevice(this.id, event, callback);
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -92,6 +92,15 @@ class EventListenerRegisterImpl implements EventListenerRegister {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
function makeOneWayCallback<T>(input: T): T {
|
|
96
|
+
const f: any = input;
|
|
97
|
+
const oneways: string[] = f[PROPERTY_PROXY_ONEWAY_METHODS] || [];
|
|
98
|
+
if (!oneways.includes(null))
|
|
99
|
+
oneways.push(null);
|
|
100
|
+
f[PROPERTY_PROXY_ONEWAY_METHODS] = oneways;
|
|
101
|
+
return input;
|
|
102
|
+
}
|
|
103
|
+
|
|
95
104
|
export class SystemManagerImpl implements SystemManager {
|
|
96
105
|
api: PluginAPI;
|
|
97
106
|
state: {[id: string]: {[property: string]: SystemDeviceState}};
|
|
@@ -122,10 +131,10 @@ export class SystemManagerImpl implements SystemManager {
|
|
|
122
131
|
return this.getDeviceById(id);
|
|
123
132
|
}
|
|
124
133
|
}
|
|
125
|
-
listen(
|
|
126
|
-
return this.events.listen((id, eventDetails, eventData) =>
|
|
134
|
+
listen(callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any) => void): EventListenerRegister {
|
|
135
|
+
return this.events.listen(makeOneWayCallback((id, eventDetails, eventData) => callback(this.getDeviceById(id), eventDetails, eventData)));
|
|
127
136
|
}
|
|
128
|
-
listenDevice(id: string, options: string | EventListenerOptions, callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData:
|
|
137
|
+
listenDevice(id: string, options: string | EventListenerOptions, callback: (eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any) => void): EventListenerRegister {
|
|
129
138
|
let { event, watch } = (options || {}) as EventListenerOptions;
|
|
130
139
|
if (!event && typeof options === 'string')
|
|
131
140
|
event = options as string;
|
|
@@ -136,7 +145,7 @@ export class SystemManagerImpl implements SystemManager {
|
|
|
136
145
|
if (watch)
|
|
137
146
|
return this.events.listenDevice(id, event, (eventDetails, eventData) => callback(this.getDeviceById(id), eventDetails, eventData));
|
|
138
147
|
|
|
139
|
-
return new EventListenerRegisterImpl(this.api.listenDevice(id, options, (eventDetails, eventData) => callback(this.getDeviceById(id), eventDetails, eventData)))
|
|
148
|
+
return new EventListenerRegisterImpl(this.api.listenDevice(id, options, makeOneWayCallback((eventDetails, eventData) => callback(this.getDeviceById(id), eventDetails, eventData))));
|
|
140
149
|
}
|
|
141
150
|
|
|
142
151
|
async removeDevice(id: string) {
|