add plugin restart

This commit is contained in:
Koushik Dutta
2021-09-08 21:47:39 -07:00
parent a693bd8d38
commit 396068dfc3
15 changed files with 227 additions and 196 deletions

View File

@@ -12,12 +12,12 @@
"@scrypted/sdk": "file:../sdk"
},
"devDependencies": {
"@types/node": "^16.7.1"
"@types/node": "^16.9.0"
}
},
"../sdk": {
"name": "@scrypted/sdk",
"version": "0.0.65",
"version": "0.0.69",
"license": "ISC",
"dependencies": {
"@babel/core": "^7.2.2",
@@ -71,9 +71,9 @@
"link": true
},
"node_modules/@types/node": {
"version": "16.7.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.1.tgz",
"integrity": "sha512-ncRdc45SoYJ2H4eWU9ReDfp3vtFqDYhjOsKlFFUDEn8V1Bgr2RjYal8YT5byfadWIRluhPFU6JiDOl0H6Sl87A==",
"version": "16.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.0.tgz",
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag==",
"dev": true
}
},
@@ -118,9 +118,9 @@
}
},
"@types/node": {
"version": "16.7.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.1.tgz",
"integrity": "sha512-ncRdc45SoYJ2H4eWU9ReDfp3vtFqDYhjOsKlFFUDEn8V1Bgr2RjYal8YT5byfadWIRluhPFU6JiDOl0H6Sl87A==",
"version": "16.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.0.tgz",
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag==",
"dev": true
}
}

View File

@@ -12,6 +12,6 @@
"@scrypted/sdk": "file:../sdk"
},
"devDependencies": {
"@types/node": "^16.7.1"
"@types/node": "^16.9.0"
}
}

View File

@@ -17,6 +17,7 @@
"url-parse": "^1.4.7"
},
"devDependencies": {
"@types/node": "^16.9.0",
"@types/url-parse": "^1.4.4"
}
},
@@ -125,6 +126,12 @@
"resolved": "../../sdk",
"link": true
},
"node_modules/@types/node": {
"version": "16.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.0.tgz",
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag==",
"dev": true
},
"node_modules/@types/url-parse": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.4.tgz",
@@ -323,6 +330,12 @@
"webpack-inject-plugin": "^1.0.2"
}
},
"@types/node": {
"version": "16.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.0.tgz",
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag==",
"dev": true
},
"@types/url-parse": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.4.tgz",

View File

@@ -33,6 +33,7 @@
"url-parse": "^1.4.7"
},
"devDependencies": {
"@types/node": "^16.9.0",
"@types/url-parse": "^1.4.4"
}
}

View File

@@ -238,7 +238,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
})
ws.onclose = () => {
api.kill();
api.removeListeners();
}
}

6
sdk/index.d.ts vendored
View File

@@ -72,6 +72,7 @@ export class MixinDeviceBase<T> implements DeviceState {
storage: Storage;
id?: string;
interfaces?: string[];
mixins?: string[];
metadata?: any;
name?: string;
providedInterfaces?: string[];
@@ -120,11 +121,6 @@ export class MixinDeviceBase<T> implements DeviceState {
ultraviolet?: number;
luminance?: number;
position?: Position;
/**
* Called when the mixin has been removed or invalidated.
*/
release(): void;
}
declare const Scrypted: ScryptedStatic;

18
sdk/types.d.ts vendored
View File

@@ -727,6 +727,10 @@ export interface DeviceManager {
*/
onDevicesChanged(devices: DeviceManifest): Promise<void>;
/**
* Restart the plugin. May not happen immediately.
*/
requestRestart(): Promise<void>;
}
/**
* Device objects are created by DeviceProviders when new devices are discover and synced to Scrypted via the DeviceManager.
@@ -825,22 +829,22 @@ export interface SystemManager {
/**
* Find a Scrypted device by id.
*/
getDeviceById(id: string): ScryptedDevice;
getDeviceById(id: string): ScryptedDevice;
/**
* Find a Scrypted device by id.
*/
getDeviceById<T>(id: string): ScryptedDevice & T;
getDeviceById<T>(id: string): ScryptedDevice & T;
/**
* Find a Scrypted device by name.
*/
getDeviceByName(name: string): ScryptedDevice;
getDeviceByName(name: string): ScryptedDevice;
/**
* Find a Scrypted device by name.
*/
getDeviceByName<T>(name: string): ScryptedDevice & T;
getDeviceByName<T>(name: string): ScryptedDevice & T;
/**
* Get the current state of a device.
@@ -880,8 +884,12 @@ export interface MixinProvider {
/**
* Create a mixin that can be applied to the supplied device.
*/
getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }): any;
getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }): Promise<any>;
/**
* Release a mixin device that was previously returned from getMixin.
*/
releaseMixin(id: string, mixinDevice: any): Promise<void>;
}
/**
* The HttpRequestHandler allows handling of web requests under the endpoint path: /endpoint/npm-package-name/*.

View File

@@ -268,6 +268,7 @@ module.exports.ScryptedInterfaceDescriptors = {
],
methods: [
"getVideoStream",
"getVideoStreamOptions",
]
},
Lock: {
@@ -511,6 +512,7 @@ module.exports.ScryptedInterfaceDescriptors = {
methods: [
"canMixin",
"getMixin",
"releaseMixin",
]
},
HttpRequestHandler: {

View File

@@ -31,9 +31,7 @@ export class PluginComponent {
await host?.remote?.setNativeId?.(pluginDevice.nativeId, pluginDevice._id, storage);
}
async setMixins(id: string, mixins: string[]) {
const pluginDevice = this.scrypted.findPluginDeviceById(id);
setState(pluginDevice, ScryptedInterfaceProperty.mixins, [...new Set(mixins)] || []);
await this.scrypted.datastore.upsert(pluginDevice);
this.scrypted.stateManager.setState(id, ScryptedInterfaceProperty.mixins, [...new Set(mixins)] || []);
const device = this.scrypted.invalidatePluginDevice(id);
await device.handler.ensureProxy();
}
@@ -54,7 +52,7 @@ export class PluginComponent {
}
async reload(pluginId: string) {
const plugin = await this.scrypted.datastore.tryGet(Plugin, pluginId);
await this.scrypted.installPlugin(plugin);
await this.scrypted.runPlugin(plugin);
}
async getPackageJson(pluginId: string) {
const plugin = await this.scrypted.datastore.tryGet(Plugin, pluginId);

View File

@@ -50,10 +50,10 @@ function addBuiltins(mediaManager: MediaManager, converters: BufferConverter[])
args.push('-y', "-vf", "select=eq(n\\,1)", "-vframes", "1", '-f', 'singlejpeg', tmpfile.name);
const cp = child_process.spawn(await mediaManager.getFFmpegPath(), args, {
// stdio: 'ignore',
stdio: 'ignore',
});
cp.stdout.on('data', data => console.log(data.toString()));
cp.stderr.on('data', data => console.error(data.toString()));
// cp.stdout.on('data', data => console.log(data.toString()));
// cp.stderr.on('data', data => console.error(data.toString()));
cp.on('error', (code) => {
console.error('ffmpeg error code', code);
})
@@ -205,11 +205,11 @@ function addBuiltins(mediaManager: MediaManager, converters: BufferConverter[])
args.push(`tcp://127.0.0.1:${videoPort}`);
const cp = child_process.spawn(await mediaManager.getFFmpegPath(), args, {
// stdio: 'ignore',
stdio: 'ignore',
});
cp.on('error', e => console.error('ffmpeg error', e));
cp.stdout.on('data', data => console.log(data.toString()));
cp.stderr.on('data', data => console.error(data.toString()));
// cp.stdout.on('data', data => console.log(data.toString()));
// cp.stderr.on('data', data => console.error(data.toString()));
const resolution = new Promise<Array<string>>(resolve => {
cp.stdout.on('data', data => {

View File

@@ -30,9 +30,9 @@ export interface PluginAPI {
getComponent(id: string): Promise<any>;
getMediaManager(): Promise<MediaManager>
getMediaManager(): Promise<MediaManager>;
kill(): Promise<void>;
requestRestart(): Promise<void>;
}
class EventListenerRegisterProxy implements EventListenerRegister {
@@ -46,19 +46,30 @@ class EventListenerRegisterProxy implements EventListenerRegister {
}
}
export class PluginAPIProxy implements PluginAPI {
export class PluginAPIManagedListeners {
listeners = new Set<EventListenerRegister>();
constructor(public api: PluginAPI, public mediaManager?: MediaManager) {
}
logListener(listener: EventListenerRegister): EventListenerRegister {
manageListener(listener: EventListenerRegister): EventListenerRegister {
this.listeners.add(listener);
return new EventListenerRegisterProxy(this.listeners, () => {
this.listeners.delete(listener);
listener.removeListener();
});
}
removeListeners() {
for (const l of [...this.listeners]) {
l.removeListener();
}
this.listeners.clear();
}
}
export class PluginAPIProxy extends PluginAPIManagedListeners implements PluginAPI {
constructor(public api: PluginAPI, public mediaManager?: MediaManager) {
super();
}
setState(nativeId: string, key: string, value: any): Promise<void> {
return this.api.setState(nativeId, key, value);
}
@@ -87,10 +98,10 @@ export class PluginAPIProxy implements PluginAPI {
return this.api.removeDevice(id);
}
async listen(EventListener: (id: string, eventDetails: EventDetails, eventData: any) => void): Promise<EventListenerRegister> {
return this.logListener(await this.api.listen(EventListener));
return this.manageListener(await this.api.listen(EventListener));
}
async listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: object) => void): Promise<EventListenerRegister> {
return this.logListener(await this.api.listenDevice(id, event, callback));
return this.manageListener(await this.api.listenDevice(id, event, callback));
}
ioClose(id: string): Promise<void> {
return this.api.ioClose(id);
@@ -110,11 +121,9 @@ export class PluginAPIProxy implements PluginAPI {
async getMediaManager(): Promise<MediaManager> {
return this.mediaManager;
}
async kill(): Promise<void> {
for (const l of [...this.listeners]) {
l.removeListener();
}
this.listeners.clear();
async requestRestart() {
return this.api.requestRestart();
}
}

View File

@@ -9,6 +9,7 @@ import { hasSameElements } from "../collection";
import { allInterfaceProperties, isValidInterfaceMethod, methodInterfaces } from "./descriptor";
interface MixinTable {
mixinProviderId: string;
interfaces: string[];
proxy: Promise<any>;
}
@@ -28,10 +29,10 @@ export class PluginDeviceProxyHandler implements ProxyHandler<any>, ScryptedDevi
const mixinTable = this.mixinTable;
this.mixinTable = undefined;
(async() => {
for (const mixin of await mixinTable) {
for (const mixinEntry of await mixinTable) {
(async() => {
const proxy = await mixin.proxy;
proxy.release();
const mixinProvider = this.scrypted.getDevice(mixinEntry.mixinProviderId) as ScryptedDevice & MixinProvider;
mixinProvider.releaseMixin(this.id, await mixinEntry.proxy);
})().catch(() => {});
}
})().catch(() => {});;
@@ -65,19 +66,20 @@ export class PluginDeviceProxyHandler implements ProxyHandler<any>, ScryptedDevi
const mixinTable: MixinTable[] = [];
mixinTable.unshift({
mixinProviderId: undefined,
interfaces: allInterfaces.slice(),
proxy,
})
for (const mixinId of getState(pluginDevice, ScryptedInterfaceProperty.mixins) || []) {
const mixin = this.scrypted.getDevice(mixinId) as ScryptedDevice & MixinProvider;
const mixinProvider = this.scrypted.getDevice(mixinId) as ScryptedDevice & MixinProvider;
const wrappedHandler = new PluginDeviceProxyHandler(this.scrypted, this.id);
wrappedHandler.mixinTable = Promise.resolve(mixinTable.slice());
const wrappedProxy = new Proxy(wrappedHandler, wrappedHandler);
try {
const interfaces = await (mixin.canMixin(type, allInterfaces) as any) as ScryptedInterface[];
const interfaces = await (mixinProvider.canMixin(type, allInterfaces) as any) as ScryptedInterface[];
if (!interfaces) {
console.warn(`mixin provider ${mixinId} can no longer mixin ${this.id}`);
const mixins: string[] = getState(pluginDevice, ScryptedInterfaceProperty.mixins) || [];
@@ -89,13 +91,14 @@ export class PluginDeviceProxyHandler implements ProxyHandler<any>, ScryptedDevi
const host = this.scrypted.getPluginHostForDeviceId(mixinId);
const deviceState = await host.remote.createDeviceState(this.id,
async (property, value) => this.scrypted.stateManager.setPluginDeviceState(pluginDevice, property, value));
const mixinProxy = await mixin.getMixin(wrappedProxy, allInterfaces, deviceState);
const mixinProxy = await mixinProvider.getMixin(wrappedProxy, allInterfaces, deviceState);
if (!mixinProxy)
throw new Error(`mixin provider ${mixinId} did not return mixin for ${this.id}`);
allInterfaces.push(...interfaces);
proxy = mixinProxy;
mixinTable.unshift({
mixinProviderId: mixinId,
interfaces,
proxy,
})

View File

@@ -0,0 +1,132 @@
import { ScryptedDevice, Device, DeviceManifest, EventDetails, EventListenerOptions, EventListenerRegister, ScryptedInterfaceProperty, MediaManager, HttpRequest } from '@scrypted/sdk/types'
import { ScryptedRuntime } from '../runtime';
import { Plugin } from '../db-types';
import { PluginAPI, PluginAPIManagedListeners } from './plugin-api';
import { Logger } from '../logger';
import { getState } from '../state';
import { PluginHost } from './plugin-host';
import debounce from 'lodash/debounce';
export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAPI {
pluginId: string;
restartDebounced = debounce(async () => {
const host = this.scrypted.plugins[this.pluginId];
const logger = await this.getLogger(undefined);
if (host.api !== this) {
logger.log('w', 'plugin restart was requested, but a different instance was found. restart cancelled.');
return;
}
const plugin = await this.scrypted.datastore.tryGet(Plugin, this.pluginId);
this.scrypted.runPlugin(plugin);
}, 15000);
constructor(public scrypted: ScryptedRuntime, plugin: Plugin, public pluginHost: PluginHost) {
super();
this.pluginId = plugin._id;
}
getMediaManager(): Promise<MediaManager> {
return null;
}
async deliverPush(endpoint: string, httpRequest: HttpRequest) {
return this.scrypted.deliverPush(endpoint, httpRequest);
}
async getLogger(nativeId: string): Promise<Logger> {
const device = this.scrypted.findPluginDevice(this.pluginId, nativeId);
return this.scrypted.getDeviceLogger(device);
}
getComponent(id: string): Promise<any> {
return this.scrypted.getComponent(id);
}
setDeviceProperty(id: string, property: ScryptedInterfaceProperty, value: any): Promise<void> {
switch (property) {
case ScryptedInterfaceProperty.room:
case ScryptedInterfaceProperty.type:
case ScryptedInterfaceProperty.name:
const device = this.scrypted.findPluginDeviceById(id);
this.scrypted.stateManager.setPluginDeviceState(device, property, value);
return;
default:
throw new Error(`Not allowed to set property ${property}`);
}
}
async ioClose(id: string) {
this.pluginHost.io.clients[id]?.close();
this.pluginHost.ws[id]?.close();
}
async ioSend(id: string, message: string) {
this.pluginHost.io.clients[id]?.send(message);
this.pluginHost.ws[id]?.send(message);
}
async setState(nativeId: string, key: string, value: any) {
this.scrypted.stateManager.setPluginState(this.pluginId, nativeId, key, value);
}
async setStorage(nativeId: string, storage: { [key: string]: string }) {
const device = this.scrypted.findPluginDevice(this.pluginId, nativeId)
device.storage = storage;
this.scrypted.datastore.upsert(device);
}
async onDevicesChanged(deviceManifest: DeviceManifest) {
const existing = this.scrypted.findPluginDevices(this.pluginId);
const newIds = deviceManifest.devices.map(device => device.nativeId);
const toRemove = existing.filter(e => e.nativeId && !newIds.includes(e.nativeId));
for (const remove of toRemove) {
await this.scrypted.removeDevice(remove);
}
for (const upsert of deviceManifest.devices) {
await this.pluginHost.upsertDevice(upsert);
}
}
async onDeviceDiscovered(device: Device) {
await this.pluginHost.upsertDevice(device);
}
async onDeviceRemoved(nativeId: string) {
await this.scrypted.removeDevice(this.scrypted.findPluginDevice(this.pluginId, nativeId))
}
async onDeviceEvent(nativeId: any, eventInterface: any, eventData?: any) {
const plugin = this.scrypted.findPluginDevice(this.pluginId, nativeId);
this.scrypted.stateManager.notifyInterfaceEvent(plugin, eventInterface, eventData);
}
async getDeviceById<T>(id: string): Promise<T & ScryptedDevice> {
return this.scrypted.getDevice(id);
}
async listen(EventListener: (id: string, eventDetails: EventDetails, eventData: object) => void): Promise<EventListenerRegister> {
return this.manageListener(this.scrypted.stateManager.listen(EventListener));
}
async listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: object) => void): Promise<EventListenerRegister> {
const device = this.scrypted.findPluginDeviceById(id);
if (device) {
const self = this.scrypted.findPluginDevice(this.pluginId);
this.scrypted.getDeviceLogger(self).log('i', `requested listen ${getState(device, ScryptedInterfaceProperty.name)} ${JSON.stringify(event)}`);
}
return this.manageListener(this.scrypted.stateManager.listenDevice(id, event, callback));
}
async removeDevice(id: string) {
return this.scrypted.removeDevice(this.scrypted.findPluginDeviceById(id));
}
async requestRestart() {
const logger = await this.getLogger(undefined);
logger.log('i', 'plugin restart was requested');
return this.restartDebounced();
}
}

View File

@@ -18,6 +18,7 @@ import { once } from 'events';
import { PassThrough } from 'stream';
import { Console } from 'console'
import { sleep } from '../sleep';
import { PluginHostAPI } from './plugin-host-api';
export class PluginHost {
worker: cluster.Worker;
@@ -32,13 +33,13 @@ export class PluginHost {
pingTimeout: 120000,
});
ws: { [id: string]: WebSocket } = {};
api: PluginAPI;
api: PluginHostAPI;
pluginName: string;
listener: EventListenerRegister;
kill() {
this.listener.removeListener();
this.api.kill();
this.api.removeListeners();
this.worker.process.kill();
this.io.close();
for (const s of Object.values(this.ws)) {
@@ -61,6 +62,13 @@ export class PluginHost {
return this.pluginName || 'no plugin name';
}
async upsertDevice(upsert: Device) {
const pi = await this.scrypted.upsertDevice(this.pluginId, upsert);
await this.remote.setNativeId(pi.nativeId, pi._id, pi.storage || {});
this.scrypted.invalidatePluginDevice(pi._id);
}
constructor(scrypted: ScryptedRuntime, plugin: Plugin, waitDebug?: Promise<void>) {
this.scrypted = scrypted;
this.pluginId = plugin._id;
@@ -122,113 +130,7 @@ export class PluginHost {
const self = this;
async function upsertDevice(upsert: Device) {
const pi = await scrypted.upsertDevice(self.pluginId, upsert);
await self.remote.setNativeId(pi.nativeId, pi._id, pi.storage || {});
scrypted.invalidatePluginDevice(pi._id);
}
class PluginAPIImpl implements PluginAPI {
getMediaManager(): Promise<MediaManager> {
return null;
}
async deliverPush(endpoint: string, httpRequest: HttpRequest) {
return scrypted.deliverPush(endpoint, httpRequest);
}
async getLogger(nativeId: string): Promise<Logger> {
const device = scrypted.findPluginDevice(plugin._id, nativeId);
return self.scrypted.getDeviceLogger(device);
}
getComponent(id: string): Promise<any> {
return self.scrypted.getComponent(id);
}
setDeviceProperty(id: string, property: ScryptedInterfaceProperty, value: any): Promise<void> {
switch (property) {
case ScryptedInterfaceProperty.room:
case ScryptedInterfaceProperty.type:
case ScryptedInterfaceProperty.name:
const device = scrypted.findPluginDeviceById(id);
scrypted.stateManager.setPluginDeviceState(device, property, value);
return;
default:
throw new Error(`Not allowed to set property ${property}`);
}
}
async ioClose(id: string) {
self.io.clients[id]?.close();
self.ws[id]?.close();
}
async ioSend(id: string, message: string) {
self.io.clients[id]?.send(message);
self.ws[id]?.send(message);
}
async setState(nativeId: string, key: string, value: any) {
scrypted.stateManager.setPluginState(self.pluginId, nativeId, key, value);
}
async setStorage(nativeId: string, storage: { [key: string]: string }) {
const device = scrypted.findPluginDevice(plugin._id, nativeId)
device.storage = storage;
scrypted.datastore.upsert(device);
}
async onDevicesChanged(deviceManifest: DeviceManifest) {
const existing = scrypted.findPluginDevices(self.pluginId);
const newIds = deviceManifest.devices.map(device => device.nativeId);
const toRemove = existing.filter(e => e.nativeId && !newIds.includes(e.nativeId));
for (const remove of toRemove) {
await scrypted.removeDevice(remove);
}
for (const upsert of deviceManifest.devices) {
await upsertDevice(upsert);
}
}
async onDeviceDiscovered(device: Device) {
await upsertDevice(device);
}
async onDeviceRemoved(nativeId: string) {
await scrypted.removeDevice(scrypted.findPluginDevice(plugin._id, nativeId))
}
async onDeviceEvent(nativeId: any, eventInterface: any, eventData?: any) {
const plugin = scrypted.findPluginDevice(self.pluginId, nativeId);
scrypted.stateManager.notifyInterfaceEvent(plugin, eventInterface, eventData);
}
async getDeviceById<T>(id: string): Promise<T & ScryptedDevice> {
return scrypted.getDevice(id);
}
async listen(EventListener: (id: string, eventDetails: EventDetails, eventData: object) => void): Promise<EventListenerRegister> {
return scrypted.stateManager.listen(EventListener);
}
async listenDevice(id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: object) => void): Promise<EventListenerRegister> {
const device = scrypted.findPluginDeviceById(id);
if (device) {
const self = scrypted.findPluginDevice(plugin._id);
scrypted.getDeviceLogger(self).log('i', `requested listen ${getState(device, ScryptedInterfaceProperty.name)} ${JSON.stringify(event)}`);
}
return scrypted.stateManager.listenDevice(id, event, callback);
}
async removeDevice(id: string) {
return scrypted.removeDevice(scrypted.findPluginDeviceById(id));
}
async kill() {
}
}
this.api = new PluginAPIImpl();
this.api = new PluginHostAPI(scrypted, plugin, this);
this.console = this.peer.eval('return console', undefined, undefined, true) as Promise<Console>;
const zipBuffer = Buffer.from(plugin.zip, 'base64');

View File

@@ -155,6 +155,10 @@ class DeviceManagerImpl implements DeviceManager {
this.systemManager = systemManager;
}
async requestRestart() {
return this.api.requestRestart();
}
getDeviceLogger(nativeId?: string): Logger {
return new DeviceLogger(this.api, nativeId, this.getDeviceConsole?.(nativeId) || console);
}
@@ -247,43 +251,6 @@ interface WebSocketCallbacks {
export async function setupPluginRemote(peer: RpcPeer, api: PluginAPI, pluginId: string): Promise<PluginRemote> {
peer.addSerializer(Buffer, 'Buffer', new BufferSerializer());
const listen = api.listen.bind(api);
const registers = new Set<EventListenerRegister>();
class EventListenerRegisterObserver implements EventListenerRegister {
register: EventListenerRegister;
constructor(register: EventListenerRegister) {
this.register = register;
}
removeListener() {
registers.delete(this.register);
this.register.removeListener();
}
}
function manage(register: EventListenerRegister): EventListenerRegister {
registers.add(register);
return new EventListenerRegisterObserver(register);
}
api.listen = async (EventListener: (id: string, eventDetails: EventDetails, eventData: object) => void) => {
return manage(await listen(EventListener));
}
const listenDevice = api.listenDevice.bind(api);
api.listenDevice = async (id: string, event: string | EventListenerOptions, callback: (eventDetails: EventDetails, eventData: object) => void): Promise<EventListenerRegister> => {
return manage(await listenDevice(id, event, callback));
}
api.kill = async () => {
for (const register of registers) {
register.removeListener();
}
}
const ret = await peer.eval('return getRemote(api, pluginId)', undefined, {
api,
pluginId,