@scrypted/server 0.113.0 → 0.115.0
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.
- package/dist/fetch/http-fetch.js +6 -27
- package/dist/fetch/http-fetch.js.map +1 -1
- package/dist/fetch/index.d.ts +5 -2
- package/dist/fetch/index.js +6 -26
- package/dist/fetch/index.js.map +1 -1
- package/dist/listen-zero.d.ts +2 -2
- package/dist/listen-zero.js +1 -1
- package/dist/listen-zero.js.map +1 -1
- package/dist/plugin/plugin-console.js +2 -2
- package/dist/plugin/plugin-console.js.map +1 -1
- package/dist/plugin/plugin-npm-dependencies.js +5 -1
- package/dist/plugin/plugin-npm-dependencies.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +4 -3
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/plugin/plugin-remote.js +0 -1
- package/dist/plugin/plugin-remote.js.map +1 -1
- package/dist/plugin/plugin-repl.js +1 -1
- package/dist/plugin/plugin-repl.js.map +1 -1
- package/dist/plugin/runtime/node-worker-common.js +28 -5
- package/dist/plugin/runtime/node-worker-common.js.map +1 -1
- package/dist/rpc-peer-eval.d.ts +4 -0
- package/dist/rpc-peer-eval.js +25 -0
- package/dist/rpc-peer-eval.js.map +1 -0
- package/dist/rpc-serializer.js.map +1 -1
- package/dist/rpc.d.ts +0 -3
- package/dist/rpc.js +0 -21
- package/dist/rpc.js.map +1 -1
- package/dist/runtime.js +7 -5
- package/dist/runtime.js.map +1 -1
- package/dist/scrypted-server-main.js +14 -0
- package/dist/scrypted-server-main.js.map +1 -1
- package/package.json +12 -12
- package/python/plugin_remote.py +15 -1
- package/src/fetch/http-fetch.ts +7 -32
- package/src/fetch/index.ts +10 -33
- package/src/listen-zero.ts +3 -3
- package/src/plugin/plugin-console.ts +2 -2
- package/src/plugin/plugin-npm-dependencies.ts +5 -1
- package/src/plugin/plugin-remote-worker.ts +4 -3
- package/src/plugin/plugin-remote.ts +1 -2
- package/src/plugin/plugin-repl.ts +1 -1
- package/src/plugin/runtime/node-worker-common.ts +30 -5
- package/src/rpc-peer-eval.ts +27 -0
- package/src/rpc-serializer.ts +2 -2
- package/src/rpc.ts +2 -28
- package/src/runtime.ts +9 -5
- package/src/scrypted-server-main.ts +16 -0
package/package.json
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
{
|
2
2
|
"name": "@scrypted/server",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.115.0",
|
4
4
|
"description": "",
|
5
5
|
"dependencies": {
|
6
6
|
"@mapbox/node-pre-gyp": "^1.0.11",
|
7
7
|
"@scrypted/ffmpeg-static": "^6.1.0-build1",
|
8
|
-
"@scrypted/node-pty": "^1.0.
|
9
|
-
"@scrypted/types": "^0.3.
|
8
|
+
"@scrypted/node-pty": "^1.0.18",
|
9
|
+
"@scrypted/types": "^0.3.43",
|
10
10
|
"adm-zip": "^0.5.14",
|
11
11
|
"body-parser": "^1.20.2",
|
12
12
|
"cookie-parser": "^1.4.6",
|
13
13
|
"dotenv": "^16.4.5",
|
14
|
-
"engine.io": "^6.
|
14
|
+
"engine.io": "^6.6.0",
|
15
15
|
"express": "^4.19.2",
|
16
16
|
"follow-redirects": "^1.15.6",
|
17
17
|
"http-auth": "^4.2.0",
|
@@ -21,17 +21,17 @@
|
|
21
21
|
"nan": "^2.20.0",
|
22
22
|
"node-dijkstra": "^2.5.0",
|
23
23
|
"node-forge": "^1.3.1",
|
24
|
-
"node-gyp": "^10.
|
25
|
-
"py": "npm:@bjia56/portable-python@^0.1.
|
24
|
+
"node-gyp": "^10.2.0",
|
25
|
+
"py": "npm:@bjia56/portable-python@^0.1.54",
|
26
26
|
"router": "^1.3.8",
|
27
|
-
"semver": "^7.6.
|
27
|
+
"semver": "^7.6.3",
|
28
28
|
"sharp": "^0.33.4",
|
29
29
|
"source-map-support": "^0.5.21",
|
30
|
-
"tar": "^7.4.
|
30
|
+
"tar": "^7.4.3",
|
31
31
|
"tslib": "^2.6.3",
|
32
|
-
"typescript": "^5.5.
|
32
|
+
"typescript": "^5.5.4",
|
33
33
|
"whatwg-mimetype": "^4.0.0",
|
34
|
-
"ws": "^8.
|
34
|
+
"ws": "^8.18.0"
|
35
35
|
},
|
36
36
|
"devDependencies": {
|
37
37
|
"@types/adm-zip": "^0.5.5",
|
@@ -40,13 +40,13 @@
|
|
40
40
|
"@types/follow-redirects": "^1.14.4",
|
41
41
|
"@types/http-auth": "^4.1.4",
|
42
42
|
"@types/ip": "^1.1.3",
|
43
|
-
"@types/lodash": "^4.17.
|
43
|
+
"@types/lodash": "^4.17.7",
|
44
44
|
"@types/node-dijkstra": "^2.5.6",
|
45
45
|
"@types/node-forge": "^1.3.11",
|
46
46
|
"@types/semver": "^7.5.8",
|
47
47
|
"@types/source-map-support": "^0.5.10",
|
48
48
|
"@types/whatwg-mimetype": "^3.0.2",
|
49
|
-
"@types/ws": "^8.5.
|
49
|
+
"@types/ws": "^8.5.11"
|
50
50
|
},
|
51
51
|
"bin": {
|
52
52
|
"scrypted-serve": "bin/scrypted-serve"
|
package/python/plugin_remote.py
CHANGED
@@ -29,6 +29,7 @@ from plugin_pip import install_with_pip, need_requirements, remove_pip_dirs
|
|
29
29
|
from scrypted_python.scrypted_sdk import PluginFork, ScryptedStatic
|
30
30
|
from scrypted_python.scrypted_sdk.types import (Device, DeviceManifest,
|
31
31
|
EventDetails,
|
32
|
+
ScryptedInterface,
|
32
33
|
ScryptedInterfaceMethods,
|
33
34
|
ScryptedInterfaceProperty,
|
34
35
|
Storage)
|
@@ -139,6 +140,19 @@ class SystemManager(scrypted_python.scrypted_sdk.types.SystemManager):
|
|
139
140
|
state = self.systemState.get(check, None)
|
140
141
|
if not state:
|
141
142
|
continue
|
143
|
+
checkInterfaces = state.get('interfaces', None)
|
144
|
+
if not checkInterfaces:
|
145
|
+
continue
|
146
|
+
interfaces = checkInterfaces.get('value', [])
|
147
|
+
if ScryptedInterface.ScryptedPlugin.value in interfaces:
|
148
|
+
checkPluginId = state.get('pluginId', None)
|
149
|
+
if not checkPluginId:
|
150
|
+
continue
|
151
|
+
pluginId = checkPluginId.get('value', None)
|
152
|
+
if not pluginId:
|
153
|
+
continue
|
154
|
+
if pluginId == name:
|
155
|
+
return self.getDeviceById(check)
|
142
156
|
checkName = state.get('name', None)
|
143
157
|
if not checkName:
|
144
158
|
continue
|
@@ -390,7 +404,7 @@ class PluginRemote:
|
|
390
404
|
|
391
405
|
def computeClusterObjectHash(o: ClusterObject) -> str:
|
392
406
|
m = hashlib.sha256()
|
393
|
-
m.update(bytes(f"{o['id']}{o['port']}{o.get('sourcePort'
|
407
|
+
m.update(bytes(f"{o['id']}{o['port']}{o.get('sourcePort') or ''}{o['proxyId']}{clusterSecret}", 'utf8'))
|
394
408
|
return base64.b64encode(m.digest()).decode('utf-8')
|
395
409
|
|
396
410
|
def onProxySerialization(value: Any, proxyId: str, sourcePeerPort: int = None):
|
package/src/fetch/http-fetch.ts
CHANGED
@@ -89,11 +89,11 @@ export async function httpFetch<T extends HttpFetchOptions<Readable>>(options: T
|
|
89
89
|
controller = new AbortController();
|
90
90
|
timeout = setTimeout(() => controller.abort(), options.timeout);
|
91
91
|
|
92
|
-
options.signal?.addEventListener('abort', () => controller.abort(
|
92
|
+
options.signal?.addEventListener('abort', () => controller.abort(options.signal?.reason));
|
93
93
|
}
|
94
94
|
|
95
95
|
const signal = controller?.signal || options.signal;
|
96
|
-
signal?.addEventListener('abort', () => request.destroy(new Error('abort')));
|
96
|
+
signal?.addEventListener('abort', () => request.destroy(new Error(options.signal?.reason || 'abort')));
|
97
97
|
|
98
98
|
const nodeHeaders: Record<string, string[]> = {};
|
99
99
|
for (const [k, v] of headers) {
|
@@ -122,9 +122,12 @@ export async function httpFetch<T extends HttpFetchOptions<Readable>>(options: T
|
|
122
122
|
try {
|
123
123
|
const [response] = await once(request, 'response') as [IncomingMessage];
|
124
124
|
|
125
|
-
|
125
|
+
|
126
|
+
if (options?.checkStatusCode === undefined || options?.checkStatusCode) {
|
126
127
|
try {
|
127
|
-
checkStatus
|
128
|
+
const checker = typeof options?.checkStatusCode === 'function' ? options.checkStatusCode : checkStatus;
|
129
|
+
if (!checker(response.statusCode))
|
130
|
+
throw new Error(`http response statusCode ${response.statusCode}`);
|
128
131
|
}
|
129
132
|
catch (e) {
|
130
133
|
readMessageBuffer(response).catch(() => { });
|
@@ -150,31 +153,3 @@ export async function httpFetch<T extends HttpFetchOptions<Readable>>(options: T
|
|
150
153
|
}
|
151
154
|
}
|
152
155
|
|
153
|
-
function ensureType<T>(v: T) {
|
154
|
-
}
|
155
|
-
|
156
|
-
async function test() {
|
157
|
-
const a = await httpFetch({
|
158
|
-
url: 'http://example.com',
|
159
|
-
});
|
160
|
-
|
161
|
-
ensureType<Buffer>(a.body);
|
162
|
-
|
163
|
-
const b = await httpFetch({
|
164
|
-
url: 'http://example.com',
|
165
|
-
responseType: 'json',
|
166
|
-
});
|
167
|
-
ensureType<any>(b.body);
|
168
|
-
|
169
|
-
const c = await httpFetch({
|
170
|
-
url: 'http://example.com',
|
171
|
-
responseType: 'readable',
|
172
|
-
});
|
173
|
-
ensureType<IncomingMessage>(c.body);
|
174
|
-
|
175
|
-
const d = await httpFetch({
|
176
|
-
url: 'http://example.com',
|
177
|
-
responseType: 'buffer',
|
178
|
-
});
|
179
|
-
ensureType<Buffer>(d.body);
|
180
|
-
}
|
package/src/fetch/index.ts
CHANGED
@@ -7,7 +7,10 @@ export interface HttpFetchOptionsBase<B> {
|
|
7
7
|
signal?: AbortSignal,
|
8
8
|
timeout?: number;
|
9
9
|
rejectUnauthorized?: boolean;
|
10
|
-
|
10
|
+
/**
|
11
|
+
* Checks the status code. Defaults to true.
|
12
|
+
*/
|
13
|
+
checkStatusCode?: boolean | ((statusCode: number) => boolean);
|
11
14
|
body?: B | string | ArrayBufferView | any;
|
12
15
|
withCredentials?: boolean;
|
13
16
|
}
|
@@ -40,6 +43,7 @@ export function fetchStatusCodeOk(statusCode: number) {
|
|
40
43
|
export function checkStatus(statusCode: number) {
|
41
44
|
if (!fetchStatusCodeOk(statusCode))
|
42
45
|
throw new Error(`http response statusCode ${statusCode}`);
|
46
|
+
return true;
|
43
47
|
}
|
44
48
|
|
45
49
|
export function getFetchMethod(options: HttpFetchOptions<any>) {
|
@@ -177,7 +181,7 @@ export async function domFetch<T extends HttpFetchOptions<BodyInit>>(options: T)
|
|
177
181
|
controller = new AbortController();
|
178
182
|
timeout = setTimeout(() => controller.abort(), options.timeout);
|
179
183
|
|
180
|
-
options.signal?.addEventListener('abort', () => controller.abort(
|
184
|
+
options.signal?.addEventListener('abort', () => controller.abort(options.signal?.reason));
|
181
185
|
}
|
182
186
|
|
183
187
|
try {
|
@@ -190,9 +194,11 @@ export async function domFetch<T extends HttpFetchOptions<BodyInit>>(options: T)
|
|
190
194
|
body,
|
191
195
|
});
|
192
196
|
|
193
|
-
if (
|
197
|
+
if (options?.checkStatusCode === undefined || options?.checkStatusCode) {
|
194
198
|
try {
|
195
|
-
checkStatus
|
199
|
+
const checker = typeof options?.checkStatusCode === 'function' ? options.checkStatusCode : checkStatus;
|
200
|
+
if (!checker(response.status))
|
201
|
+
throw new Error(`http response statusCode ${response.status}`);
|
196
202
|
}
|
197
203
|
catch (e) {
|
198
204
|
response.arrayBuffer().catch(() => { });
|
@@ -210,32 +216,3 @@ export async function domFetch<T extends HttpFetchOptions<BodyInit>>(options: T)
|
|
210
216
|
clearTimeout(timeout);
|
211
217
|
}
|
212
218
|
}
|
213
|
-
|
214
|
-
function ensureType<T>(v: T) {
|
215
|
-
}
|
216
|
-
|
217
|
-
async function test() {
|
218
|
-
const a = await domFetch({
|
219
|
-
url: 'http://example.com',
|
220
|
-
});
|
221
|
-
|
222
|
-
ensureType<Buffer>(a.body);
|
223
|
-
|
224
|
-
const b = await domFetch({
|
225
|
-
url: 'http://example.com',
|
226
|
-
responseType: 'json',
|
227
|
-
});
|
228
|
-
ensureType<any>(b.body);
|
229
|
-
|
230
|
-
const c = await domFetch({
|
231
|
-
url: 'http://example.com',
|
232
|
-
responseType: 'readable',
|
233
|
-
});
|
234
|
-
ensureType<Response>(c.body);
|
235
|
-
|
236
|
-
const d = await domFetch({
|
237
|
-
url: 'http://example.com',
|
238
|
-
responseType: 'buffer',
|
239
|
-
});
|
240
|
-
ensureType<Buffer>(d.body);
|
241
|
-
}
|
package/src/listen-zero.ts
CHANGED
@@ -7,13 +7,13 @@ export class ListenZeroSingleClientTimeoutError extends Error {
|
|
7
7
|
}
|
8
8
|
}
|
9
9
|
|
10
|
-
export async function listenZero(server: net.Server, hostname
|
11
|
-
server.listen(0, hostname);
|
10
|
+
export async function listenZero(server: net.Server, hostname: string) {
|
11
|
+
server.listen(0, hostname || '127.0.0.1');
|
12
12
|
await once(server, 'listening');
|
13
13
|
return (server.address() as net.AddressInfo).port;
|
14
14
|
}
|
15
15
|
|
16
|
-
export async function listenZeroSingleClient(hostname
|
16
|
+
export async function listenZeroSingleClient(hostname: string, options?: net.ServerOpts, listenTimeout = 30000) {
|
17
17
|
const server = new net.Server(options);
|
18
18
|
const port = await listenZero(server, hostname);
|
19
19
|
|
@@ -295,8 +295,8 @@ export async function createConsoleServer(remoteStdout: Readable, remoteStderr:
|
|
295
295
|
socket.once('error', cleanup);
|
296
296
|
socket.once('end', cleanup);
|
297
297
|
});
|
298
|
-
const readPort = await listenZero(readServer);
|
299
|
-
const writePort = await listenZero(writeServer);
|
298
|
+
const readPort = await listenZero(readServer, '127.0.0.1');
|
299
|
+
const writePort = await listenZero(writeServer, '127.0.0.1');
|
300
300
|
|
301
301
|
return {
|
302
302
|
clear(nativeId: ScryptedNativeId) {
|
@@ -9,8 +9,12 @@ import { ensurePluginVolume } from "./plugin-volume";
|
|
9
9
|
|
10
10
|
export function defaultNpmExec(args: string[], options: child_process.SpawnOptions) {
|
11
11
|
let npm = 'npm';
|
12
|
-
if (os.platform() === 'win32')
|
12
|
+
if (os.platform() === 'win32') {
|
13
13
|
npm += '.cmd';
|
14
|
+
// wrap each argument in a quote to handle spaces in paths
|
15
|
+
// https://github.com/nodejs/node/issues/38490#issuecomment-927330248
|
16
|
+
args = args.map(arg => '"' + arg + '"');
|
17
|
+
}
|
14
18
|
const cp = child_process.spawn(npm, args, options);
|
15
19
|
return cp;
|
16
20
|
}
|
@@ -9,6 +9,7 @@ import { computeClusterObjectHash } from '../cluster/cluster-hash';
|
|
9
9
|
import { ClusterObject, ConnectRPCObject } from '../cluster/connect-rpc-object';
|
10
10
|
import { listenZero } from '../listen-zero';
|
11
11
|
import { RpcMessage, RpcPeer } from '../rpc';
|
12
|
+
import { evalLocal } from '../rpc-peer-eval';
|
12
13
|
import { createDuplexRpcPeer } from '../rpc-serializer';
|
13
14
|
import { MediaManagerImpl } from './media';
|
14
15
|
import { PluginAPI, PluginAPIProxy, PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
|
@@ -269,11 +270,11 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
|
269
270
|
|
270
271
|
process.on('uncaughtException', e => {
|
271
272
|
getPluginConsole().error('uncaughtException', e);
|
272
|
-
scrypted.log.e('uncaughtException ' + e?.toString());
|
273
|
+
scrypted.log.e('uncaughtException ' + (e.stack || e?.toString()));
|
273
274
|
});
|
274
275
|
process.on('unhandledRejection', e => {
|
275
276
|
getPluginConsole().error('unhandledRejection', e);
|
276
|
-
scrypted.log.e('unhandledRejection ' + e?.toString());
|
277
|
+
scrypted.log.e('unhandledRejection ' + ((e as Error).stack || e?.toString()));
|
277
278
|
});
|
278
279
|
|
279
280
|
installSourceMapSupport({
|
@@ -376,7 +377,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
|
376
377
|
|
377
378
|
try {
|
378
379
|
const filename = zipOptions?.debug ? '/plugin/main.nodejs.js' : `/${pluginId}/main.nodejs.js`;
|
379
|
-
|
380
|
+
evalLocal(peer, script, filename, params);
|
380
381
|
|
381
382
|
if (zipOptions?.fork) {
|
382
383
|
// pluginConsole?.log('plugin forked');
|
@@ -179,7 +179,6 @@ class DeviceStateProxyHandler implements ProxyHandler<any> {
|
|
179
179
|
|
180
180
|
set?(target: any, p: PropertyKey, value: any, receiver: any) {
|
181
181
|
checkProperty(p.toString(), value);
|
182
|
-
const now = Date.now();
|
183
182
|
this.deviceManager.systemManager.state[this.id][p as string] = {
|
184
183
|
value,
|
185
184
|
};
|
@@ -446,7 +445,7 @@ export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId:
|
|
446
445
|
return remote;
|
447
446
|
}
|
448
447
|
catch (e) {
|
449
|
-
throw new RPCResultError(peer, 'error while retrieving PluginRemote', e);
|
448
|
+
throw new RPCResultError(peer, 'error while retrieving PluginRemote', e as Error);
|
450
449
|
}
|
451
450
|
}
|
452
451
|
|
@@ -15,6 +15,7 @@ function prep(pluginVolume: string, hash: string) {
|
|
15
15
|
const zipFile = path.join(zipDir, zipFilename);
|
16
16
|
const unzippedPath = path.join(zipDir, 'unzipped')
|
17
17
|
const zipDirTmp = zipDir + '.tmp';
|
18
|
+
const zipDirTmp2 = zipDir + '.tmp2';
|
18
19
|
|
19
20
|
return {
|
20
21
|
unzippedPath,
|
@@ -22,6 +23,7 @@ function prep(pluginVolume: string, hash: string) {
|
|
22
23
|
zipDir,
|
23
24
|
zipFile,
|
24
25
|
zipDirTmp,
|
26
|
+
zipDirTmp2,
|
25
27
|
};
|
26
28
|
}
|
27
29
|
|
@@ -52,16 +54,39 @@ export function prepareZipSync(pluginVolume: string, h: string, getZip: () => Bu
|
|
52
54
|
}
|
53
55
|
|
54
56
|
export function extractZip(pluginVolume: string, h: string, zipBuffer: Buffer) {
|
55
|
-
const { zipDir, zipDirTmp, zipFilename, zipFile, unzippedPath } = prep(pluginVolume, h);
|
57
|
+
const { zipDir, zipDirTmp, zipDirTmp2, zipFilename, zipFile, unzippedPath } = prep(pluginVolume, h);
|
56
58
|
|
59
|
+
// stage the plugin zip in a tmp directory, then move it to the final location.
|
60
|
+
// so if the zip extraction is corrupt, it won't be used.
|
57
61
|
fs.rmSync(zipDirTmp, {
|
58
62
|
recursive: true,
|
59
63
|
force: true,
|
60
64
|
});
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
+
|
66
|
+
let zipDirTmp2Exists = false;
|
67
|
+
try {
|
68
|
+
fs.rmSync(zipDirTmp2, {
|
69
|
+
recursive: true,
|
70
|
+
force: true,
|
71
|
+
});
|
72
|
+
}
|
73
|
+
catch (e) {
|
74
|
+
zipDirTmp2Exists = true;
|
75
|
+
}
|
76
|
+
|
77
|
+
try {
|
78
|
+
fs.rmSync(zipDir, {
|
79
|
+
recursive: true,
|
80
|
+
force: true,
|
81
|
+
});
|
82
|
+
}
|
83
|
+
catch (e) {
|
84
|
+
if (zipDirTmp2Exists)
|
85
|
+
throw e;
|
86
|
+
// file lock from dangling plugin process may have prevented the recursive rm from completing.
|
87
|
+
// try renaming it. it will get cleaned up eventually.
|
88
|
+
fs.renameSync(zipDir, zipDirTmp2);
|
89
|
+
}
|
65
90
|
fs.mkdirSync(zipDirTmp, {
|
66
91
|
recursive: true,
|
67
92
|
});
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import type { CompileFunctionOptions } from 'vm';
|
2
|
+
import { RpcPeer } from "./rpc";
|
3
|
+
|
4
|
+
type CompileFunction = (code: string, params?: ReadonlyArray<string>, options?: CompileFunctionOptions) => Function;
|
5
|
+
|
6
|
+
function compileFunction(code: string, params?: ReadonlyArray<string>, options?: CompileFunctionOptions): any {
|
7
|
+
params = params || [];
|
8
|
+
const f = `(function(${params.join(',')}) {;${code};})`;
|
9
|
+
return eval(f);
|
10
|
+
}
|
11
|
+
|
12
|
+
export function evalLocal<T>(peer: RpcPeer, script: string, filename?: string, coercedParams?: { [name: string]: any }): T {
|
13
|
+
const params = Object.assign({}, peer.params, coercedParams);
|
14
|
+
let compile: CompileFunction;
|
15
|
+
try {
|
16
|
+
// prevent bundlers from trying to include non-existent vm module.
|
17
|
+
compile = module[`require`]('vm').compileFunction;
|
18
|
+
}
|
19
|
+
catch (e) {
|
20
|
+
compile = compileFunction;
|
21
|
+
}
|
22
|
+
const f = compile(script, Object.keys(params), {
|
23
|
+
filename,
|
24
|
+
});
|
25
|
+
const value = f(...Object.values(params));
|
26
|
+
return value;
|
27
|
+
}
|
package/src/rpc-serializer.ts
CHANGED
@@ -10,7 +10,7 @@ export function createDuplexRpcPeer(selfName: string, peerName: string, readable
|
|
10
10
|
serializer.sendMessage(message, reject, serializationContext);
|
11
11
|
}
|
12
12
|
catch (e) {
|
13
|
-
reject?.(e);
|
13
|
+
reject?.(e as Error);
|
14
14
|
readable.destroy();
|
15
15
|
}
|
16
16
|
});
|
@@ -165,7 +165,7 @@ export function createRpcDuplexSerializer(writable: {
|
|
165
165
|
serializer.onMessageFinish(message);
|
166
166
|
}
|
167
167
|
catch (e) {
|
168
|
-
serializer.kill('message parse failure ' + e.message);
|
168
|
+
serializer.kill('message parse failure ' + (e as Error).message);
|
169
169
|
}
|
170
170
|
}
|
171
171
|
else {
|
package/src/rpc.ts
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
import type { CompileFunctionOptions } from 'vm';
|
2
|
-
type CompileFunction = (code: string, params?: ReadonlyArray<string>, options?: CompileFunctionOptions) => Function;
|
3
|
-
|
4
1
|
export function startPeriodicGarbageCollection() {
|
5
2
|
if (!global.gc) {
|
6
3
|
console.warn('rpc peer garbage collection not available: global.gc is not exposed.');
|
@@ -253,12 +250,6 @@ export class RPCResultError extends Error {
|
|
253
250
|
}
|
254
251
|
}
|
255
252
|
|
256
|
-
function compileFunction(code: string, params?: ReadonlyArray<string>, options?: CompileFunctionOptions): any {
|
257
|
-
params = params || [];
|
258
|
-
const f = `(function(${params.join(',')}) {;${code};})`;
|
259
|
-
return eval(f);
|
260
|
-
}
|
261
|
-
|
262
253
|
declare class WeakRef<T> {
|
263
254
|
target: T;
|
264
255
|
constructor(target: any);
|
@@ -499,23 +490,6 @@ export class RpcPeer {
|
|
499
490
|
});
|
500
491
|
}
|
501
492
|
|
502
|
-
evalLocal<T>(script: string, filename?: string, coercedParams?: { [name: string]: any }): T {
|
503
|
-
const params = Object.assign({}, this.params, coercedParams);
|
504
|
-
let compile: CompileFunction;
|
505
|
-
try {
|
506
|
-
// prevent bundlers from trying to include non-existent vm module.
|
507
|
-
compile = module[`require`]('vm').compileFunction;
|
508
|
-
}
|
509
|
-
catch (e) {
|
510
|
-
compile = compileFunction;
|
511
|
-
}
|
512
|
-
const f = compile(script, Object.keys(params), {
|
513
|
-
filename,
|
514
|
-
});
|
515
|
-
const value = f(...Object.values(params));
|
516
|
-
return value;
|
517
|
-
}
|
518
|
-
|
519
493
|
/**
|
520
494
|
* @deprecated
|
521
495
|
* @param result
|
@@ -723,7 +697,7 @@ export class RpcPeer {
|
|
723
697
|
}
|
724
698
|
catch (e) {
|
725
699
|
// console.error('failure', rpcApply.method, e);
|
726
|
-
this.createErrorResult(result, e);
|
700
|
+
this.createErrorResult(result, e as Error);
|
727
701
|
}
|
728
702
|
|
729
703
|
this.send(result, undefined, serializationContext);
|
@@ -785,7 +759,7 @@ export class RpcPeer {
|
|
785
759
|
}
|
786
760
|
catch (e) {
|
787
761
|
// console.error('failure', rpcApply.method, e);
|
788
|
-
this.createErrorResult(result, e);
|
762
|
+
this.createErrorResult(result, e as Error);
|
789
763
|
}
|
790
764
|
|
791
765
|
if (!rpcApply.oneway)
|
package/src/runtime.ts
CHANGED
@@ -275,10 +275,11 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
275
275
|
return;
|
276
276
|
}
|
277
277
|
|
278
|
+
const reqany = req as any;
|
278
279
|
if ((req as any).upgradeHead)
|
279
|
-
this.connectRPCObjectIO.handleUpgrade(
|
280
|
+
this.connectRPCObjectIO.handleUpgrade(reqany, res.socket, reqany.upgradeHead)
|
280
281
|
else
|
281
|
-
this.connectRPCObjectIO.handleRequest(
|
282
|
+
this.connectRPCObjectIO.handleRequest(reqany, res);
|
282
283
|
}
|
283
284
|
|
284
285
|
async getEndpointPluginData(req: Request, endpoint: string, isUpgrade: boolean, isEngineIOEndpoint: boolean): Promise<HttpPluginData> {
|
@@ -422,15 +423,18 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
422
423
|
return;
|
423
424
|
}
|
424
425
|
|
425
|
-
|
426
|
+
const reqany = req as any;
|
427
|
+
|
428
|
+
reqany.scrypted = {
|
426
429
|
endpointRequest,
|
427
430
|
pluginDevice,
|
428
431
|
accessControls,
|
429
432
|
};
|
433
|
+
|
430
434
|
if ((req as any).upgradeHead)
|
431
|
-
pluginHost.io.handleUpgrade(
|
435
|
+
pluginHost.io.handleUpgrade(reqany, res.socket, reqany.upgradeHead)
|
432
436
|
else
|
433
|
-
pluginHost.io.handleRequest(
|
437
|
+
pluginHost.io.handleRequest(reqany, res);
|
434
438
|
}
|
435
439
|
|
436
440
|
handleRequestEndpoint(req: Request, res: Response, endpointRequest: HttpRequest, pluginData: HttpPluginData) {
|
@@ -161,6 +161,16 @@ async function start(mainFilename: string, options?: {
|
|
161
161
|
callback(sha === user.passwordHash || password === user.token);
|
162
162
|
});
|
163
163
|
|
164
|
+
// the default http-auth will returns a WWW-Authenticate header if login fails.
|
165
|
+
// this causes the Safari to prompt for login.
|
166
|
+
// https://github.com/gevorg/http-auth/blob/4158fa75f58de70fd44aa68876a8674725e0556e/src/auth/base.js#L81
|
167
|
+
// override the ask function to return a bare 401 instead.
|
168
|
+
// @ts-expect-error
|
169
|
+
basicAuth.ask = (res) => {
|
170
|
+
res.statusCode = 401;
|
171
|
+
res.end();
|
172
|
+
};
|
173
|
+
|
164
174
|
const httpsServerOptions = process.env.SCRYPTED_HTTPS_OPTIONS_FILE
|
165
175
|
? JSON.parse(fs.readFileSync(process.env.SCRYPTED_HTTPS_OPTIONS_FILE).toString())
|
166
176
|
: {};
|
@@ -219,6 +229,12 @@ async function start(mainFilename: string, options?: {
|
|
219
229
|
}
|
220
230
|
|
221
231
|
app.use(async (req, res, next) => {
|
232
|
+
// /web/component requires basic auth admin access.
|
233
|
+
if (req.url.startsWith('/web/component/')) {
|
234
|
+
next();
|
235
|
+
return;
|
236
|
+
}
|
237
|
+
|
222
238
|
// the remote address may be ipv6 prefixed so use a fuzzy match.
|
223
239
|
// eg ::ffff:192.168.2.124
|
224
240
|
if (process.env.SCRYPTED_ADMIN_USERNAME
|