@scrypted/server 0.1.16 → 0.2.1
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 +3 -4
- package/dist/event-registry.js.map +1 -1
- package/dist/plugin/media.js +53 -65
- package/dist/plugin/media.js.map +1 -1
- package/dist/plugin/plugin-api.js +1 -1
- package/dist/plugin/plugin-api.js.map +1 -1
- package/dist/plugin/plugin-device.js +25 -11
- package/dist/plugin/plugin-device.js.map +1 -1
- package/dist/plugin/plugin-host-api.js.map +1 -1
- package/dist/plugin/plugin-host.js +5 -2
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +66 -24
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/plugin/plugin-remote.js +14 -4
- package/dist/plugin/plugin-remote.js.map +1 -1
- package/dist/rpc.js +2 -2
- package/dist/rpc.js.map +1 -1
- package/dist/runtime.js +11 -10
- package/dist/runtime.js.map +1 -1
- package/dist/scrypted-server-main.js +8 -4
- package/dist/scrypted-server-main.js.map +1 -1
- package/dist/server-settings.js +5 -1
- package/dist/server-settings.js.map +1 -1
- package/dist/state.js +2 -1
- package/dist/state.js.map +1 -1
- package/package.json +4 -11
- package/src/event-registry.ts +3 -4
- package/src/plugin/media.ts +71 -84
- package/src/plugin/plugin-api.ts +4 -4
- package/src/plugin/plugin-device.ts +25 -11
- package/src/plugin/plugin-host-api.ts +1 -1
- package/src/plugin/plugin-host.ts +0 -1
- package/src/plugin/plugin-remote-worker.ts +90 -30
- package/src/plugin/plugin-remote.ts +17 -6
- package/src/rpc.ts +2 -1
- package/src/runtime.ts +6 -9
- package/src/scrypted-server-main.ts +8 -4
- package/src/state.ts +2 -1
@@ -216,15 +216,24 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
|
|
216
216
|
let { allInterfaces } = await previousEntry;
|
217
217
|
try {
|
218
218
|
const mixinProvider = this.scrypted.getDevice(mixinId) as ScryptedDevice & MixinProvider;
|
219
|
-
const
|
219
|
+
const isMixinProvider = mixinProvider?.interfaces?.includes(ScryptedInterface.MixinProvider);
|
220
|
+
const interfaces = isMixinProvider && await mixinProvider?.canMixin(type, allInterfaces) as any as ScryptedInterface[];
|
220
221
|
if (!interfaces) {
|
221
222
|
// this is not an error
|
222
223
|
// do not advertise interfaces so it is skipped during
|
223
224
|
// vtable lookup.
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
225
|
+
if (!mixinProvider || (isMixinProvider && !interfaces)) {
|
226
|
+
console.log(`Mixin provider ${mixinId} can no longer mixin ${this.id}. Removing.`, {
|
227
|
+
mixinProvider: !!mixinProvider,
|
228
|
+
interfaces,
|
229
|
+
});
|
230
|
+
const mixins: string[] = getState(pluginDevice, ScryptedInterfaceProperty.mixins) || [];
|
231
|
+
this.scrypted.stateManager.setPluginDeviceState(pluginDevice, ScryptedInterfaceProperty.mixins, mixins.filter(mid => mid !== mixinId))
|
232
|
+
this.scrypted.datastore.upsert(pluginDevice);
|
233
|
+
}
|
234
|
+
else {
|
235
|
+
console.log(`Mixin provider ${mixinId} can not mixin ${this.id}. It is no longer a MixinProvider. This may be temporary. Passing through.`);
|
236
|
+
}
|
228
237
|
return {
|
229
238
|
passthrough: true,
|
230
239
|
allInterfaces,
|
@@ -353,12 +362,17 @@ export class PluginDeviceProxyHandler implements PrimitiveProxyHandler<any>, Scr
|
|
353
362
|
for (const mixin of this.mixinTable) {
|
354
363
|
const entry = await mixin.entry;
|
355
364
|
if (!entry.methods) {
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
365
|
+
if (entry.interfaces.size) {
|
366
|
+
const pluginDevice = this.scrypted.findPluginDeviceById(mixin.mixinProviderId || this.id);
|
367
|
+
const plugin = this.scrypted.plugins[pluginDevice.pluginId];
|
368
|
+
let methods = new Set<string>(getInterfaceMethods(ScryptedInterfaceDescriptors, entry.interfaces))
|
369
|
+
if (plugin.api.descriptors)
|
370
|
+
methods = new Set<string>([...methods, ...getInterfaceMethods(plugin.api.descriptors, entry.interfaces)]);
|
371
|
+
entry.methods = methods;
|
372
|
+
}
|
373
|
+
else {
|
374
|
+
entry.methods = new Set();
|
375
|
+
}
|
362
376
|
}
|
363
377
|
if (entry.methods.has(method)) {
|
364
378
|
return { mixin, entry };
|
@@ -47,7 +47,7 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
|
|
47
47
|
|
48
48
|
// do we care about mixin validation here?
|
49
49
|
// maybe to prevent/notify errant dangling events?
|
50
|
-
async onMixinEvent(id: string, nativeIdOrMixinDevice: ScryptedNativeId | any, eventInterface:
|
50
|
+
async onMixinEvent(id: string, nativeIdOrMixinDevice: ScryptedNativeId | any, eventInterface: string, eventData?: any) {
|
51
51
|
// nativeId code path has been deprecated in favor of mixin object 12/10/2021
|
52
52
|
const device = this.scrypted.findPluginDeviceById(id);
|
53
53
|
|
@@ -214,7 +214,6 @@ export class PluginHost {
|
|
214
214
|
await remote.setNativeId(pluginDevice.nativeId, pluginDevice._id, pluginDevice.storage || {});
|
215
215
|
}
|
216
216
|
|
217
|
-
await remote.setSystemState(scrypted.stateManager.getSystemState());
|
218
217
|
const waitDebug = pluginDebug?.waitDebug;
|
219
218
|
if (waitDebug) {
|
220
219
|
console.info('waiting for debugger...');
|
@@ -9,18 +9,24 @@ import { install as installSourceMapSupport } from 'source-map-support';
|
|
9
9
|
import { PassThrough } from 'stream';
|
10
10
|
import { RpcMessage, RpcPeer } from '../rpc';
|
11
11
|
import { MediaManagerImpl } from './media';
|
12
|
-
import { PluginAPI, PluginRemoteLoadZipOptions } from './plugin-api';
|
12
|
+
import { PluginAPI, PluginAPIProxy, PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
|
13
13
|
import { installOptionalDependencies } from './plugin-npm-dependencies';
|
14
|
-
import { attachPluginRemote, PluginReader, setupPluginRemote } from './plugin-remote';
|
14
|
+
import { attachPluginRemote, DeviceManagerImpl, PluginReader, setupPluginRemote } from './plugin-remote';
|
15
15
|
import { createREPLServer } from './plugin-repl';
|
16
16
|
import { NodeThreadWorker } from './runtime/node-thread-worker';
|
17
17
|
const { link } = require('linkfs');
|
18
18
|
|
19
|
+
interface PluginStats {
|
20
|
+
type: 'stats',
|
21
|
+
cpu: NodeJS.CpuUsage;
|
22
|
+
memoryUsage: NodeJS.MemoryUsage;
|
23
|
+
}
|
24
|
+
|
19
25
|
export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any) => void) {
|
20
26
|
const peer = new RpcPeer('unknown', 'host', peerSend);
|
21
27
|
|
22
28
|
let systemManager: SystemManager;
|
23
|
-
let deviceManager:
|
29
|
+
let deviceManager: DeviceManagerImpl;
|
24
30
|
let api: PluginAPI;
|
25
31
|
|
26
32
|
const getConsole = (hook: (stdout: PassThrough, stderr: PassThrough) => Promise<void>,
|
@@ -126,12 +132,6 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
|
|
126
132
|
if (!mixinId) {
|
127
133
|
return;
|
128
134
|
}
|
129
|
-
// todo: fix this. a mixin provider can mixin another device to make it a mixin provider itself.
|
130
|
-
// so the mixin id in the mixin table will be incorrect.
|
131
|
-
// there's no easy way to fix this from the remote.
|
132
|
-
// if (!systemManager.getDeviceById(mixinId).mixins.includes(idForNativeId(nativeId))) {
|
133
|
-
// return;
|
134
|
-
// }
|
135
135
|
const reconnect = () => {
|
136
136
|
stdout.removeAllListeners();
|
137
137
|
stderr.removeAllListeners();
|
@@ -178,15 +178,35 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
|
|
178
178
|
return ret;
|
179
179
|
}
|
180
180
|
|
181
|
-
|
182
|
-
|
181
|
+
// process.cpuUsage is for the entire process.
|
182
|
+
// process.memoryUsage is per thread.
|
183
|
+
const allMemoryStats = new Map<NodeThreadWorker, NodeJS.MemoryUsage>();
|
184
|
+
|
185
|
+
peer.getParam('updateStats').then((updateStats: (stats: PluginStats) => void) => {
|
183
186
|
setInterval(() => {
|
184
|
-
const cpuUsage = process.cpuUsage(
|
185
|
-
|
187
|
+
const cpuUsage = process.cpuUsage();
|
188
|
+
allMemoryStats.set(undefined, process.memoryUsage());
|
189
|
+
|
190
|
+
const memoryUsage: NodeJS.MemoryUsage = {
|
191
|
+
rss: 0,
|
192
|
+
heapTotal: 0,
|
193
|
+
heapUsed: 0,
|
194
|
+
external: 0,
|
195
|
+
arrayBuffers: 0,
|
196
|
+
}
|
197
|
+
|
198
|
+
for (const mu of allMemoryStats.values()) {
|
199
|
+
memoryUsage.rss += mu.rss;
|
200
|
+
memoryUsage.heapTotal += mu.heapTotal;
|
201
|
+
memoryUsage.heapUsed += mu.heapUsed;
|
202
|
+
memoryUsage.external += mu.external;
|
203
|
+
memoryUsage.arrayBuffers += mu.arrayBuffers;
|
204
|
+
}
|
205
|
+
|
186
206
|
updateStats({
|
187
207
|
type: 'stats',
|
188
208
|
cpu: cpuUsage,
|
189
|
-
memoryUsage
|
209
|
+
memoryUsage,
|
190
210
|
});
|
191
211
|
}, 10000);
|
192
212
|
});
|
@@ -316,35 +336,75 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
|
|
316
336
|
pluginReader = undefined;
|
317
337
|
const script = main.toString();
|
318
338
|
|
319
|
-
|
339
|
+
|
340
|
+
const forks = new Set<PluginRemote>();
|
341
|
+
|
342
|
+
scrypted.fork = () => {
|
320
343
|
const ntw = new NodeThreadWorker(pluginId, {
|
321
344
|
env: process.env,
|
322
345
|
pluginDebug: undefined,
|
323
346
|
});
|
324
|
-
const threadPeer = new RpcPeer('main', 'thread', (message, reject) => ntw.send(message, reject));
|
325
|
-
threadPeer.params.updateStats = (stats: any) => {
|
326
|
-
// todo: merge.
|
327
|
-
// this.stats = stats;
|
328
|
-
}
|
329
|
-
ntw.setupRpcPeer(threadPeer);
|
330
347
|
|
331
|
-
const
|
332
|
-
|
333
|
-
|
334
|
-
|
348
|
+
const result = (async () => {
|
349
|
+
const threadPeer = new RpcPeer('main', 'thread', (message, reject) => ntw.send(message, reject));
|
350
|
+
threadPeer.params.updateStats = (stats: PluginStats) => {
|
351
|
+
allMemoryStats.set(ntw, stats.memoryUsage);
|
352
|
+
}
|
353
|
+
ntw.setupRpcPeer(threadPeer);
|
354
|
+
|
355
|
+
class PluginForkAPI extends PluginAPIProxy {
|
356
|
+
[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS] = (api as any)[RpcPeer.PROPERTY_PROXY_ONEWAY_METHODS];
|
357
|
+
|
358
|
+
setStorage(nativeId: string, storage: { [key: string]: any; }): Promise<void> {
|
359
|
+
const id = deviceManager.nativeIds.get(nativeId).id;
|
360
|
+
(scrypted.pluginRemoteAPI as PluginRemote).setNativeId(nativeId, id, storage);
|
361
|
+
for (const r of forks) {
|
362
|
+
if (r === remote)
|
363
|
+
continue;
|
364
|
+
r.setNativeId(nativeId, id, storage);
|
365
|
+
}
|
366
|
+
return super.setStorage(nativeId, storage);
|
367
|
+
}
|
368
|
+
}
|
369
|
+
const forkApi = new PluginForkAPI(api);
|
370
|
+
|
371
|
+
const remote = await setupPluginRemote(threadPeer, forkApi, pluginId, () => systemManager.getSystemState());
|
372
|
+
forks.add(remote);
|
373
|
+
ntw.worker.on('exit', () => {
|
374
|
+
forkApi.removeListeners();
|
375
|
+
forks.delete(remote);
|
376
|
+
allMemoryStats.delete(ntw);
|
377
|
+
});
|
378
|
+
|
379
|
+
for (const [nativeId, dmd] of deviceManager.nativeIds.entries()) {
|
380
|
+
await remote.setNativeId(nativeId, dmd.id, dmd.storage);
|
381
|
+
}
|
382
|
+
|
383
|
+
const forkOptions = Object.assign({}, zipOptions);
|
384
|
+
forkOptions.fork = true;
|
385
|
+
return remote.loadZip(packageJson, zipData, forkOptions)
|
386
|
+
})();
|
387
|
+
|
388
|
+
result.catch(() => ntw.kill());
|
389
|
+
|
390
|
+
return {
|
391
|
+
worker: ntw.worker,
|
392
|
+
result,
|
393
|
+
}
|
335
394
|
}
|
336
395
|
|
337
396
|
try {
|
338
397
|
peer.evalLocal(script, zipOptions?.filename || '/plugin/main.nodejs.js', params);
|
339
|
-
pluginConsole?.log('plugin successfully loaded');
|
340
398
|
|
341
399
|
if (zipOptions?.fork) {
|
400
|
+
pluginConsole?.log('plugin forked');
|
342
401
|
const fork = exports.fork;
|
343
|
-
const
|
344
|
-
|
345
|
-
return
|
402
|
+
const forked = await fork();
|
403
|
+
forked[RpcPeer.PROPERTY_JSON_DISABLE_SERIALIZATION] = true;
|
404
|
+
return forked;
|
346
405
|
}
|
347
406
|
|
407
|
+
pluginConsole?.log('plugin loaded');
|
348
408
|
let pluginInstance = exports.default;
|
349
409
|
// support exporting a plugin class, plugin main function,
|
350
410
|
// or a plugin instance
|
@@ -365,7 +425,7 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
|
|
365
425
|
}
|
366
426
|
}).then(scrypted => {
|
367
427
|
systemManager = scrypted.systemManager;
|
368
|
-
deviceManager = scrypted.deviceManager;
|
428
|
+
deviceManager = scrypted.deviceManager as DeviceManagerImpl;
|
369
429
|
});
|
370
430
|
|
371
431
|
return peer;
|
@@ -107,6 +107,10 @@ class DeviceStateProxyHandler implements ProxyHandler<any> {
|
|
107
107
|
get?(target: any, p: PropertyKey, receiver: any) {
|
108
108
|
if (p === 'id')
|
109
109
|
return this.id;
|
110
|
+
if (p === RpcPeer.PROPERTY_PROXY_PROPERTIES)
|
111
|
+
return { id: this.id }
|
112
|
+
if (p === 'setState')
|
113
|
+
return this.setState;
|
110
114
|
return this.deviceManager.systemManager.state[this.id][p as string]?.value;
|
111
115
|
}
|
112
116
|
|
@@ -128,7 +132,7 @@ interface DeviceManagerDevice {
|
|
128
132
|
storage: { [key: string]: any };
|
129
133
|
}
|
130
134
|
|
131
|
-
class DeviceManagerImpl implements DeviceManager {
|
135
|
+
export class DeviceManagerImpl implements DeviceManager {
|
132
136
|
api: PluginAPI;
|
133
137
|
nativeIds = new Map<string, DeviceManagerDevice>();
|
134
138
|
deviceStorage = new Map<string, StorageImpl>();
|
@@ -153,6 +157,11 @@ class DeviceManagerImpl implements DeviceManager {
|
|
153
157
|
return new Proxy(handler, handler);
|
154
158
|
}
|
155
159
|
|
160
|
+
createDeviceState(id: string, setState: (property: string, value: any) => Promise<void>): DeviceState {
|
161
|
+
const handler = new DeviceStateProxyHandler(this, id, setState);
|
162
|
+
return new Proxy(handler, handler);
|
163
|
+
}
|
164
|
+
|
156
165
|
getDeviceStorage(nativeId?: any): StorageImpl {
|
157
166
|
let ret = this.deviceStorage.get(nativeId);
|
158
167
|
if (!ret) {
|
@@ -297,7 +306,7 @@ export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId:
|
|
297
306
|
if (!peer.constructorSerializerMap.get(Buffer))
|
298
307
|
peer.addSerializer(Buffer, 'Buffer', new BufferSerializer());
|
299
308
|
const getRemote = await peer.getParam('getRemote');
|
300
|
-
const remote = await getRemote(api, pluginId);
|
309
|
+
const remote = await getRemote(api, pluginId) as PluginRemote;
|
301
310
|
|
302
311
|
await remote.setSystemState(getSystemState());
|
303
312
|
api.listen((id, eventDetails, eventData) => {
|
@@ -337,7 +346,7 @@ export interface WebSocketCustomHandler {
|
|
337
346
|
export type PluginReader = (name: string) => Buffer;
|
338
347
|
|
339
348
|
export interface PluginRemoteAttachOptions {
|
340
|
-
createMediaManager?: (systemManager: SystemManager, deviceManager:
|
349
|
+
createMediaManager?: (systemManager: SystemManager, deviceManager: DeviceManagerImpl) => Promise<MediaManager>;
|
341
350
|
getServicePort?: (name: string, ...args: any[]) => Promise<number>;
|
342
351
|
getDeviceConsole?: (nativeId?: ScryptedNativeId) => Console;
|
343
352
|
getPluginConsole?: () => Console;
|
@@ -381,6 +390,7 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
|
|
381
390
|
mediaManager,
|
382
391
|
log,
|
383
392
|
pluginHostAPI: api,
|
393
|
+
pluginRemoteAPI: undefined,
|
384
394
|
}
|
385
395
|
|
386
396
|
delete peer.params.getRemote;
|
@@ -402,9 +412,8 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
|
|
402
412
|
'setNativeId',
|
403
413
|
],
|
404
414
|
getServicePort,
|
405
|
-
createDeviceState(id: string, setState: (property: string, value: any) => Promise<void>) {
|
406
|
-
|
407
|
-
return new Proxy(handler, handler);
|
415
|
+
async createDeviceState(id: string, setState: (property: string, value: any) => Promise<void>) {
|
416
|
+
return deviceManager.createDeviceState(id, setState);
|
408
417
|
},
|
409
418
|
|
410
419
|
async ioEvent(id: string, event: string, message?: any) {
|
@@ -507,6 +516,8 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
|
|
507
516
|
},
|
508
517
|
}
|
509
518
|
|
519
|
+
ret.pluginRemoteAPI = remote;
|
520
|
+
|
510
521
|
return remote;
|
511
522
|
}
|
512
523
|
|
package/src/rpc.ts
CHANGED
@@ -321,7 +321,8 @@ export class RpcPeer {
|
|
321
321
|
const params = Object.assign({}, this.params, coercedParams);
|
322
322
|
let compile: CompileFunction;
|
323
323
|
try {
|
324
|
-
|
324
|
+
// prevent bundlers from trying to include non-existent vm module.
|
325
|
+
compile = module[`require`]('vm').compileFunction;
|
325
326
|
}
|
326
327
|
catch (e) {
|
327
328
|
compile = compileFunction;
|
package/src/runtime.ts
CHANGED
@@ -8,7 +8,6 @@ import http, { ServerResponse } from 'http';
|
|
8
8
|
import https from 'https';
|
9
9
|
import { spawn as ptySpawn } from 'node-pty-prebuilt-multiarch';
|
10
10
|
import path from 'path';
|
11
|
-
import qs from "query-string";
|
12
11
|
import rimraf from 'rimraf';
|
13
12
|
import semver from 'semver';
|
14
13
|
import { PassThrough } from 'stream';
|
@@ -184,12 +183,10 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
184
183
|
|
185
184
|
const url = new URL(callback_url as string);
|
186
185
|
if (url.search) {
|
187
|
-
const
|
188
|
-
const state = search.state as string;
|
186
|
+
const state = url.searchParams.get('state');
|
189
187
|
if (state) {
|
190
188
|
const { s, d, r } = JSON.parse(state);
|
191
|
-
|
192
|
-
url.search = '?' + qs.stringify(search);
|
189
|
+
url.searchParams.set('state', s);
|
193
190
|
const oauthClient: ScryptedDevice & OauthClient = this.getDevice(d);
|
194
191
|
await oauthClient.onOauthCallback(url.toString()).catch();
|
195
192
|
res.redirect(r);
|
@@ -197,12 +194,12 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
197
194
|
}
|
198
195
|
}
|
199
196
|
if (url.hash) {
|
200
|
-
const hash =
|
201
|
-
const state = hash.state
|
197
|
+
const hash = new URLSearchParams(url.hash.substring(1));
|
198
|
+
const state = hash.get('state');
|
202
199
|
if (state) {
|
203
200
|
const { s, d, r } = JSON.parse(state);
|
204
|
-
hash.state
|
205
|
-
url.hash = '#' +
|
201
|
+
hash.set('state', s);
|
202
|
+
url.hash = '#' + hash.toString();
|
206
203
|
const oauthClient: ScryptedDevice & OauthClient = this.getDevice(d);
|
207
204
|
await oauthClient.onOauthCallback(url.toString());
|
208
205
|
res.redirect(r);
|
@@ -12,7 +12,6 @@ import { getHostAddresses, SCRYPTED_DEBUG_PORT, SCRYPTED_INSECURE_PORT, SCRYPTED
|
|
12
12
|
import crypto from 'crypto';
|
13
13
|
import cookieParser from 'cookie-parser';
|
14
14
|
import axios from 'axios';
|
15
|
-
import qs from 'query-string';
|
16
15
|
import { RPCResultError } from './rpc';
|
17
16
|
import fs from 'fs';
|
18
17
|
import mkdirp from 'mkdirp';
|
@@ -318,8 +317,8 @@ async function start() {
|
|
318
317
|
|
319
318
|
app.get('/web/component/script/search', async (req, res) => {
|
320
319
|
try {
|
321
|
-
const query =
|
322
|
-
text: req.query.text,
|
320
|
+
const query = new URLSearchParams({
|
321
|
+
text: req.query.text.toString(),
|
323
322
|
})
|
324
323
|
const response = await axios(`https://registry.npmjs.org/-/v1/search?${query}`);
|
325
324
|
res.send(response.data);
|
@@ -405,7 +404,12 @@ async function start() {
|
|
405
404
|
|
406
405
|
app.get('/logout', (req, res) => {
|
407
406
|
res.clearCookie(getLoginUserToken(req.secure));
|
408
|
-
|
407
|
+
if (req.headers['accept']?.startsWith('application/json')) {
|
408
|
+
res.send({});
|
409
|
+
}
|
410
|
+
else {
|
411
|
+
res.redirect('/endpoint/@scrypted/core/public/');
|
412
|
+
}
|
409
413
|
});
|
410
414
|
|
411
415
|
let hasLogin = await db.getCount(ScryptedUser) > 0;
|
package/src/state.ts
CHANGED
@@ -116,7 +116,7 @@ export class ScryptedStateManager extends EventRegistry {
|
|
116
116
|
let cb = (eventDetails: EventDetails, eventData: any) => {
|
117
117
|
if (denoise && lastData === eventData)
|
118
118
|
return;
|
119
|
-
callback(eventDetails, eventData);
|
119
|
+
callback?.(eventDetails, eventData);
|
120
120
|
};
|
121
121
|
|
122
122
|
const wrappedRegister = super.listenDevice(id, options, cb);
|
@@ -124,6 +124,7 @@ export class ScryptedStateManager extends EventRegistry {
|
|
124
124
|
return new EventListenerRegisterImpl(() => {
|
125
125
|
wrappedRegister.removeListener();
|
126
126
|
cb = undefined;
|
127
|
+
callback = undefined;
|
127
128
|
polling = false;
|
128
129
|
});
|
129
130
|
}
|