@scrypted/server 0.119.1 → 0.119.3
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.
@@ -17,6 +17,12 @@ class ChildProcessWorker extends ws_1.EventEmitter {
|
|
17
17
|
this.worker.on('disconnect', () => this.emit('error', new Error('disconnect')));
|
18
18
|
this.worker.on('exit', (code, signal) => this.emit('exit', code, signal));
|
19
19
|
this.worker.on('error', e => this.emit('error', e));
|
20
|
+
// aggressively catch errors
|
21
|
+
// ECONNRESET can be raised when the child process is killed
|
22
|
+
for (const stdio of this.worker.stdio || []) {
|
23
|
+
if (stdio)
|
24
|
+
stdio.on('error', e => this.emit('error', e));
|
25
|
+
}
|
20
26
|
}
|
21
27
|
get pid() {
|
22
28
|
return this.worker?.pid;
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"child-process-worker.js","sourceRoot":"","sources":["../../../src/plugin/runtime/child-process-worker.ts"],"names":[],"mappings":";;;AACA,2BAAkC;AAIlC,MAAsB,kBAAmB,SAAQ,iBAAY;IAOtC;IANT,MAAM,CAA6B;IAE7C,IAAI,YAAY;QACZ,OAAO,IAAI,CAAC,MAAM,CAAC;IACvB,CAAC;IAED,YAAmB,QAAgB,EAAE,OAA6B;QAC9D,KAAK,EAAE,CAAC;QADO,aAAQ,GAAR,QAAQ,CAAQ;IAEnC,CAAC;IAED,WAAW;QACP,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,MAA6B,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAClH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1E,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;
|
1
|
+
{"version":3,"file":"child-process-worker.js","sourceRoot":"","sources":["../../../src/plugin/runtime/child-process-worker.ts"],"names":[],"mappings":";;;AACA,2BAAkC;AAIlC,MAAsB,kBAAmB,SAAQ,iBAAY;IAOtC;IANT,MAAM,CAA6B;IAE7C,IAAI,YAAY;QACZ,OAAO,IAAI,CAAC,MAAM,CAAC;IACvB,CAAC;IAED,YAAmB,QAAgB,EAAE,OAA6B;QAC9D,KAAK,EAAE,CAAC;QADO,aAAQ,GAAR,QAAQ,CAAQ;IAEnC,CAAC;IAED,WAAW;QACP,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,MAA6B,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAClH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1E,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QACpD,4BAA4B;QAC5B,4DAA4D;QAC5D,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;YAC1C,IAAI,KAAK;gBACL,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QACtD,CAAC;IACL,CAAC;IAED,IAAI,GAAG;QACH,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC;IAC5B,CAAC;IAED,IAAI,MAAM;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC9B,CAAC;IAED,IAAI,MAAM;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC9B,CAAC;IAED,IAAI;QACA,IAAI,CAAC,IAAI,CAAC,MAAM;YACZ,OAAO;QACX,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;IAC5B,CAAC;CAIJ;AA7CD,gDA6CC"}
|
package/dist/rpc.d.ts
CHANGED
@@ -94,7 +94,7 @@ export declare class RpcPeer {
|
|
94
94
|
killed: Promise<string>;
|
95
95
|
killedDeferred: Deferred;
|
96
96
|
tags: any;
|
97
|
-
yieldedAsyncIterators: Set<AsyncGenerator<unknown, any,
|
97
|
+
yieldedAsyncIterators: Set<AsyncGenerator<unknown, any, any>>;
|
98
98
|
static readonly finalizerIdSymbol: unique symbol;
|
99
99
|
static remotesCollected: number;
|
100
100
|
static remotesCreated: number;
|
package/package.json
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
{
|
2
2
|
"name": "@scrypted/server",
|
3
|
-
"version": "0.119.
|
3
|
+
"version": "0.119.3",
|
4
4
|
"description": "",
|
5
5
|
"dependencies": {
|
6
6
|
"@scrypted/ffmpeg-static": "^6.1.0-build1",
|
7
7
|
"@scrypted/node-pty": "^1.0.18",
|
8
|
-
"@scrypted/types": "^0.3.
|
8
|
+
"@scrypted/types": "^0.3.62",
|
9
9
|
"adm-zip": "^0.5.16",
|
10
|
-
"body-parser": "^1.20.
|
11
|
-
"cookie-parser": "^1.4.
|
10
|
+
"body-parser": "^1.20.3",
|
11
|
+
"cookie-parser": "^1.4.7",
|
12
12
|
"dotenv": "^16.4.5",
|
13
|
-
"engine.io": "^6.6.
|
14
|
-
"express": "^4.
|
13
|
+
"engine.io": "^6.6.2",
|
14
|
+
"express": "^4.21.1",
|
15
15
|
"follow-redirects": "^1.15.9",
|
16
16
|
"http-auth": "^4.2.0",
|
17
17
|
"level": "^8.0.1",
|
@@ -19,24 +19,24 @@
|
|
19
19
|
"node-dijkstra": "^2.5.0",
|
20
20
|
"node-forge": "^1.3.1",
|
21
21
|
"node-gyp": "^10.2.0",
|
22
|
-
"py": "npm:@bjia56/portable-python@^0.1.
|
22
|
+
"py": "npm:@bjia56/portable-python@^0.1.94",
|
23
23
|
"semver": "^7.6.3",
|
24
24
|
"sharp": "^0.33.5",
|
25
25
|
"source-map-support": "^0.5.21",
|
26
26
|
"tar": "^7.4.3",
|
27
|
-
"tslib": "^2.
|
28
|
-
"typescript": "^5.
|
27
|
+
"tslib": "^2.8.0",
|
28
|
+
"typescript": "^5.6.3",
|
29
29
|
"whatwg-mimetype": "^4.0.0",
|
30
30
|
"ws": "^8.18.0"
|
31
31
|
},
|
32
32
|
"devDependencies": {
|
33
33
|
"@types/adm-zip": "^0.5.5",
|
34
34
|
"@types/cookie-parser": "^1.4.7",
|
35
|
-
"@types/express": "^
|
35
|
+
"@types/express": "^5.0.0",
|
36
36
|
"@types/follow-redirects": "^1.14.4",
|
37
37
|
"@types/http-auth": "^4.1.4",
|
38
|
-
"@types/lodash": "^4.17.
|
39
|
-
"@types/node": "^22.
|
38
|
+
"@types/lodash": "^4.17.10",
|
39
|
+
"@types/node": "^22.7.6",
|
40
40
|
"@types/node-dijkstra": "^2.5.6",
|
41
41
|
"@types/node-forge": "^1.3.11",
|
42
42
|
"@types/semver": "^7.5.8",
|
package/python/plugin_remote.py
CHANGED
@@ -4,10 +4,12 @@ import asyncio
|
|
4
4
|
import base64
|
5
5
|
import gc
|
6
6
|
import hashlib
|
7
|
+
import inspect
|
7
8
|
import multiprocessing
|
8
9
|
import multiprocessing.connection
|
9
10
|
import os
|
10
11
|
import platform
|
12
|
+
import random
|
11
13
|
import sys
|
12
14
|
import threading
|
13
15
|
import time
|
@@ -18,7 +20,7 @@ from asyncio.futures import Future
|
|
18
20
|
from asyncio.streams import StreamReader, StreamWriter
|
19
21
|
from collections.abc import Mapping
|
20
22
|
from io import StringIO
|
21
|
-
from typing import Any, Optional, Set, Tuple, TypedDict
|
23
|
+
from typing import Any, Optional, Set, Tuple, TypedDict, Callable, Coroutine
|
22
24
|
|
23
25
|
import plugin_volume as pv
|
24
26
|
import rpc
|
@@ -57,6 +59,14 @@ class SystemDeviceState(TypedDict):
|
|
57
59
|
value: any
|
58
60
|
|
59
61
|
|
62
|
+
def ensure_not_coroutine(fn: Callable | Coroutine) -> Callable:
|
63
|
+
if inspect.iscoroutinefunction(fn):
|
64
|
+
def wrapper(*args, **kwargs):
|
65
|
+
return asyncio.create_task(fn(*args, **kwargs))
|
66
|
+
return wrapper
|
67
|
+
return fn
|
68
|
+
|
69
|
+
|
60
70
|
class DeviceProxy(object):
|
61
71
|
def __init__(self, systemManager: SystemManager, id: str):
|
62
72
|
self.systemManager = systemManager
|
@@ -97,6 +107,116 @@ class DeviceProxy(object):
|
|
97
107
|
return apply()
|
98
108
|
|
99
109
|
|
110
|
+
class EventListenerRegisterImpl(scrypted_python.scrypted_sdk.EventListenerRegister):
|
111
|
+
removeListener: Callable[[], None]
|
112
|
+
|
113
|
+
def __init__(self, removeListener: Callable[[], None] | Coroutine[Any, None, None]) -> None:
|
114
|
+
self.removeListener = ensure_not_coroutine(removeListener)
|
115
|
+
|
116
|
+
|
117
|
+
class EventRegistry(object):
|
118
|
+
systemListeners: Set[scrypted_python.scrypted_sdk.EventListener]
|
119
|
+
listeners: Mapping[str, Set[Callable[[scrypted_python.scrypted_sdk.EventDetails, Any], None]]]
|
120
|
+
|
121
|
+
__allowedEventInterfaces = set([
|
122
|
+
ScryptedInterface.ScryptedDevice.value,
|
123
|
+
'Logger',
|
124
|
+
'Storage'
|
125
|
+
])
|
126
|
+
|
127
|
+
def __init__(self) -> None:
|
128
|
+
self.systemListeners = set()
|
129
|
+
self.listeners = {}
|
130
|
+
|
131
|
+
def __getMixinEventName(self, options: str | scrypted_python.scrypted_sdk.EventListenerOptions) -> str:
|
132
|
+
mixinId = None
|
133
|
+
if type(options) == str:
|
134
|
+
event = options
|
135
|
+
else:
|
136
|
+
options = options or {}
|
137
|
+
event = options.get("event", None)
|
138
|
+
mixinId = options.get("mixinId", None)
|
139
|
+
if not event:
|
140
|
+
event = "undefined"
|
141
|
+
if not mixinId:
|
142
|
+
return event
|
143
|
+
return f"{event}-mixin-{mixinId}"
|
144
|
+
|
145
|
+
def __generateBase36Str(self) -> str:
|
146
|
+
alphabet = "0123456789abcdefghijklmnopqrstuvwxyz"
|
147
|
+
return "".join(random.choices(alphabet, k=10))
|
148
|
+
|
149
|
+
def listen(
|
150
|
+
self, callback: scrypted_python.scrypted_sdk.EventListener
|
151
|
+
) -> scrypted_python.scrypted_sdk.EventListenerRegister:
|
152
|
+
callback = ensure_not_coroutine(callback)
|
153
|
+
self.systemListeners.add(callback)
|
154
|
+
return EventListenerRegisterImpl(lambda: self.systemListeners.remove(callback))
|
155
|
+
|
156
|
+
def listenDevice(
|
157
|
+
self,
|
158
|
+
id: str,
|
159
|
+
options: str | scrypted_python.scrypted_sdk.EventListenerOptions,
|
160
|
+
callback: Callable[[scrypted_python.scrypted_sdk.EventDetails, Any], None],
|
161
|
+
) -> scrypted_python.scrypted_sdk.EventListenerRegister:
|
162
|
+
event = self.__getMixinEventName(options)
|
163
|
+
token = f"{id}#{event}"
|
164
|
+
events = self.listeners.get(token)
|
165
|
+
if not events:
|
166
|
+
events = set()
|
167
|
+
self.listeners[token] = events
|
168
|
+
callback = ensure_not_coroutine(callback)
|
169
|
+
self.listeners[id].add(callback)
|
170
|
+
return EventListenerRegisterImpl(lambda: self.listeners[id].remove(callback))
|
171
|
+
|
172
|
+
def notify(self, id: str, eventTime: int, eventInterface: str, property: str, value: Any, options: dict = None):
|
173
|
+
options = options or {}
|
174
|
+
changed = options.get("changed")
|
175
|
+
mixinId = options.get("mixinId")
|
176
|
+
|
177
|
+
# prevent property event noise
|
178
|
+
if property and not changed:
|
179
|
+
return False
|
180
|
+
|
181
|
+
eventDetails = {
|
182
|
+
"eventId": None,
|
183
|
+
"eventTime": eventTime,
|
184
|
+
"eventInterface": eventInterface,
|
185
|
+
"property": property,
|
186
|
+
"mixinId": mixinId,
|
187
|
+
}
|
188
|
+
|
189
|
+
return self.notifyEventDetails(id, eventDetails, value)
|
190
|
+
|
191
|
+
def notifyEventDetails(self, id: str, eventDetails: scrypted_python.scrypted_sdk.EventDetails, value: Any, eventInterface: str = None):
|
192
|
+
if not eventDetails.get("eventId"):
|
193
|
+
eventDetails["eventId"] = self.__generateBase36Str()
|
194
|
+
if not eventInterface:
|
195
|
+
eventInterface = eventDetails.get("eventInterface")
|
196
|
+
|
197
|
+
# system listeners only get state changes.
|
198
|
+
# there are many potentially noisy stateless events, like
|
199
|
+
# object detection and settings changes
|
200
|
+
if (eventDetails.get("property") and not eventDetails.get("mixinId")) or \
|
201
|
+
(eventInterface in EventRegistry.__allowedEventInterfaces):
|
202
|
+
for listener in self.systemListeners:
|
203
|
+
listener(id, eventDetails, value)
|
204
|
+
|
205
|
+
token = f"{id}#{eventInterface}"
|
206
|
+
listeners = self.listeners.get(token)
|
207
|
+
if listeners:
|
208
|
+
for listener in listeners:
|
209
|
+
listener(eventDetails, value)
|
210
|
+
|
211
|
+
token = f"{id}#undefined"
|
212
|
+
listeners = self.listeners.get(token)
|
213
|
+
if listeners:
|
214
|
+
for listener in listeners:
|
215
|
+
listener(eventDetails, value)
|
216
|
+
|
217
|
+
return True
|
218
|
+
|
219
|
+
|
100
220
|
class SystemManager(scrypted_python.scrypted_sdk.types.SystemManager):
|
101
221
|
def __init__(
|
102
222
|
self, api: Any, systemState: Mapping[str, Mapping[str, SystemDeviceState]]
|
@@ -105,6 +225,7 @@ class SystemManager(scrypted_python.scrypted_sdk.types.SystemManager):
|
|
105
225
|
self.api = api
|
106
226
|
self.systemState = systemState
|
107
227
|
self.deviceProxies: Mapping[str, DeviceProxy] = {}
|
228
|
+
self.events = EventRegistry()
|
108
229
|
|
109
230
|
async def getComponent(self, id: str) -> Any:
|
110
231
|
return await self.api.getComponent(id)
|
@@ -170,20 +291,34 @@ class SystemManager(scrypted_python.scrypted_sdk.types.SystemManager):
|
|
170
291
|
if checkName.get("value", None) == name:
|
171
292
|
return self.getDeviceById(check)
|
172
293
|
|
173
|
-
|
174
|
-
async def listen(
|
294
|
+
def listen(
|
175
295
|
self, callback: scrypted_python.scrypted_sdk.EventListener
|
176
296
|
) -> scrypted_python.scrypted_sdk.EventListenerRegister:
|
177
|
-
return
|
297
|
+
return self.events.listen(callback)
|
178
298
|
|
179
|
-
|
180
|
-
async def listenDevice(
|
299
|
+
def listenDevice(
|
181
300
|
self,
|
182
301
|
id: str,
|
183
|
-
|
302
|
+
options: str | scrypted_python.scrypted_sdk.EventListenerOptions,
|
184
303
|
callback: scrypted_python.scrypted_sdk.EventListener,
|
185
304
|
) -> scrypted_python.scrypted_sdk.EventListenerRegister:
|
186
|
-
|
305
|
+
callback = ensure_not_coroutine(callback)
|
306
|
+
if type(options) != str and options.get("watch"):
|
307
|
+
return self.events.listenDevice(
|
308
|
+
id, options,
|
309
|
+
lambda eventDetails, eventData: callback(self.getDeviceById(id), eventDetails, eventData)
|
310
|
+
)
|
311
|
+
|
312
|
+
register_fut = asyncio.ensure_future(
|
313
|
+
self.api.listenDevice(
|
314
|
+
id, options,
|
315
|
+
lambda eventDetails, eventData: callback(self.getDeviceById(id), eventDetails, eventData)
|
316
|
+
)
|
317
|
+
)
|
318
|
+
async def unregister():
|
319
|
+
register = await register_fut
|
320
|
+
await register.removeListener()
|
321
|
+
return EventListenerRegisterImpl(lambda: asyncio.ensure_future(unregister()))
|
187
322
|
|
188
323
|
async def removeDevice(self, id: str) -> None:
|
189
324
|
return await self.api.removeDevice(id)
|
@@ -19,6 +19,12 @@ export abstract class ChildProcessWorker extends EventEmitter implements Runtime
|
|
19
19
|
this.worker.on('disconnect', () => this.emit('error', new Error('disconnect')));
|
20
20
|
this.worker.on('exit', (code, signal) => this.emit('exit', code, signal));
|
21
21
|
this.worker.on('error', e => this.emit('error', e));
|
22
|
+
// aggressively catch errors
|
23
|
+
// ECONNRESET can be raised when the child process is killed
|
24
|
+
for (const stdio of this.worker.stdio || []) {
|
25
|
+
if (stdio)
|
26
|
+
stdio.on('error', e => this.emit('error', e));
|
27
|
+
}
|
22
28
|
}
|
23
29
|
|
24
30
|
get pid() {
|