core/mqtt: scripts refactor

This commit is contained in:
Koushik Dutta
2022-02-22 12:30:22 -08:00
parent b96f7de484
commit 8371d51104
29 changed files with 382 additions and 141 deletions

1
common/fs/@types/node Symbolic link
View File

@@ -0,0 +1 @@
../../node_modules/@types/node

1
common/fs/@types/sdk/index.d.ts vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../sdk/index.d.ts

1
common/fs/@types/sdk/types.d.ts vendored Symbolic link
View File

@@ -0,0 +1 @@
../../../../sdk/types/index.d.ts

View File

@@ -1 +0,0 @@
../../../sdk/index.d.ts

View File

@@ -1 +0,0 @@
../../../sdk/types/index.d.ts

112
common/package-lock.json generated
View File

@@ -11,6 +11,7 @@
"dependencies": {
"@koush/werift": "file:../external/werift/packages/webrtc",
"@scrypted/sdk": "file:../sdk",
"node-fetch-commonjs": "^3.1.1",
"typescript": "^4.4.3"
},
"devDependencies": {
@@ -118,6 +119,73 @@
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag==",
"dev": true
},
"node_modules/fetch-blob": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz",
"integrity": "sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch-commonjs": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-fetch-commonjs/-/node-fetch-commonjs-3.1.1.tgz",
"integrity": "sha512-TgkdVJdiEaauzWwB9NoD4TvHZFtG6KKEffvotWf9WNIyoRZHsCFjGfb3bhkIXrMt3YFgFi8ZApbwWoe1h3XTpA==",
"dependencies": {
"formdata-polyfill": "^4.0.10",
"web-streams-polyfill": "^3.1.1"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/typescript": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
@@ -129,6 +197,14 @@
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz",
"integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==",
"engines": {
"node": ">= 8"
}
}
},
"dependencies": {
@@ -206,10 +282,46 @@
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag==",
"dev": true
},
"fetch-blob": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz",
"integrity": "sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==",
"requires": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
}
},
"formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"requires": {
"fetch-blob": "^3.1.2"
}
},
"node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
},
"node-fetch-commonjs": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-fetch-commonjs/-/node-fetch-commonjs-3.1.1.tgz",
"integrity": "sha512-TgkdVJdiEaauzWwB9NoD4TvHZFtG6KKEffvotWf9WNIyoRZHsCFjGfb3bhkIXrMt3YFgFi8ZApbwWoe1h3XTpA==",
"requires": {
"formdata-polyfill": "^4.0.10",
"web-streams-polyfill": "^3.1.1"
}
},
"typescript": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA=="
},
"web-streams-polyfill": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz",
"integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA=="
}
}
}

View File

@@ -11,6 +11,7 @@
"dependencies": {
"@koush/werift": "file:../external/werift/packages/webrtc",
"@scrypted/sdk": "file:../sdk",
"node-fetch-commonjs": "^3.1.1",
"typescript": "^4.4.3"
},
"devDependencies": {

View File

@@ -0,0 +1,4 @@
export interface ScriptDevice {
handle<T>(handler?: T & object): void;
handleTypes(...interfaces: string[]): void;
}

View File

@@ -1,8 +1,11 @@
import type { TranspileOptions } from "typescript";
import sdk, { ScryptedDeviceBase, ScryptedInterface, ScryptedDeviceType } from "@scrypted/sdk";
import sdk, { ScryptedDeviceBase, MixinDeviceBase, ScryptedInterface, ScryptedDeviceType } from "@scrypted/sdk";
import vm from "vm";
import fs from 'fs';
import { newThread } from '../../server/src/threading';
import { newThread } from '../../../server/src/threading';
import { ScriptDevice } from "./monaco/script-device";
import { ScryptedInterfaceDescriptors } from "@scrypted/sdk/types";
import fetch from 'node-fetch-commonjs';
const { systemManager, deviceManager, mediaManager, endpointManager } = sdk;
@@ -44,8 +47,8 @@ async function tsCompileThread(source: string, options: TranspileOptions = null)
}
function getTypeDefs() {
const scryptedTypesDefs = fs.readFileSync('sdk/types.d.ts').toString();
const scryptedIndexDefs = fs.readFileSync('sdk/index.d.ts').toString();
const scryptedTypesDefs = fs.readFileSync('@types/sdk/types.d.ts').toString();
const scryptedIndexDefs = fs.readFileSync('@types/sdk/index.d.ts').toString();
return {
scryptedIndexDefs,
scryptedTypesDefs,
@@ -53,59 +56,71 @@ function getTypeDefs() {
}
export async function scryptedEval(device: ScryptedDeviceBase, script: string, extraLibs: { [lib: string]: string }, params: { [name: string]: any }) {
const libs = Object.assign({
types: getTypeDefs().scryptedTypesDefs,
}, extraLibs);
const allScripts = Object.values(libs).join('\n').toString() + script;
let compiled: string;
try {
const libs = Object.assign({
types: getTypeDefs().scryptedTypesDefs,
}, extraLibs);
const allScripts = Object.values(libs).join('\n').toString() + script;
const compiled = await tsCompileThread(allScripts);
const allParams = Object.assign({}, params, {
systemManager,
deviceManager,
endpointManager,
mediaManager,
log: device.log,
console: device.console,
localStorage: device.storage,
device,
exports: {},
ScryptedInterface,
ScryptedDeviceType,
});
try {
const asyncWrappedCompiled = `(async function() {\n${compiled}\n})()`;
const f = vm.compileFunction(asyncWrappedCompiled, Object.keys(allParams), {
filename: 'script.js',
});
try {
return await f(...Object.values(allParams));
}
catch (e) {
device.log.e('Error running script.');
device.console.error(e);
throw e;
}
}
catch (e) {
device.log.e('Error evaluating script.');
device.console.error(e);
throw e;
}
compiled = await tsCompileThread(allScripts);
}
catch (e) {
device.log.e('Error compiling script.');
device.log.e('Error compiling typescript.');
device.console.error(e);
throw e;
}
const allParams = Object.assign({}, params, {
fetch,
ScryptedDeviceBase,
MixinDeviceBase,
systemManager,
deviceManager,
endpointManager,
mediaManager,
log: device.log,
console: device.console,
localStorage: device.storage,
device,
exports: {},
ScryptedInterface,
ScryptedDeviceType,
});
const asyncWrappedCompiled = `return (async function() {\n${compiled}\n})`;
let asyncFunction: any;
try {
const functionGenerator = vm.compileFunction(asyncWrappedCompiled, Object.keys(allParams), {
filename: 'script.js',
});
asyncFunction = functionGenerator(...Object.values(allParams));
}
catch (e) {
device.log.e('Error evaluating javascript.');
device.console.error(e);
throw e;
}
try {
return await asyncFunction();
}
catch (e) {
device.log.e('Error running script.');
device.console.error(e);
throw e;
}
}
export function createMonacoEvalDefaults(extraLibs: { [lib: string]: string }) {
const bufferTypeDefs = fs.readFileSync('@types/node/buffer.d.ts').toString();
const safeLibs = {
bufferTypeDefs,
};
const libs = Object.assign(getTypeDefs(), extraLibs);
function monacoEvalDefaultsFunction(monaco, libs) {
function monacoEvalDefaultsFunction(monaco, safeLibs, libs) {
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions(
Object.assign(
{},
@@ -156,14 +171,55 @@ export function createMonacoEvalDefaults(extraLibs: { [lib: string]: string }) {
libs['sdk'],
"node_modules/@types/scrypted__sdk/index.d.ts"
);
monaco.languages.typescript.typescriptDefaults.addExtraLib(
safeLibs.bufferTypeDefs,
"node_modules/@types/node/buffer.d.ts"
);
}
return `(function() {
const safeLibs = ${JSON.stringify(safeLibs)};
const libs = ${JSON.stringify(libs)};
return (monaco) => {
(${monacoEvalDefaultsFunction})(monaco, libs);
(${monacoEvalDefaultsFunction})(monaco, safeLibs, libs);
}
})();
`;
}
export interface ScriptDeviceImpl extends ScriptDevice {
mergeHandler(device: ScryptedDeviceBase): string[];
}
const methodInterfaces: { [method: string]: string } = {};
for (const desc of Object.values(ScryptedInterfaceDescriptors)) {
for (const method of desc.methods) {
methodInterfaces[method] = desc.name;
}
}
export function createScriptDevice(baseInterfaces: string[]): ScriptDeviceImpl {
let scriptHandler: any;
const allInterfaces = baseInterfaces.slice();
return {
handle: <T>(handler?: T & object) => {
scriptHandler = handler;
},
handleTypes: (...interfaces: ScryptedInterface[]) => {
allInterfaces.push(...interfaces);
},
mergeHandler: (device: ScryptedDeviceBase) => {
const handler = scriptHandler || {};
for (const method of Object.keys(handler)) {
const iface = methodInterfaces[method];
if (iface)
allInterfaces.push(iface);
}
Object.assign(device, handler);
return allInterfaces;
},
};
}

View File

@@ -3,11 +3,12 @@ import { ChildProcess } from 'child_process';
import sdk from "@scrypted/sdk";
import { ffmpegLogInitialOutput } from './media-helpers';
import { MP4Atom, parseFragmentedMP4 } from './stream-parser';
import { Readable } from 'stream';
import { Duplex, Readable } from 'stream';
const { mediaManager } = sdk;
export interface FFMpegFragmentedMP4Session {
socket: Duplex;
cp: ChildProcess;
generator: AsyncGenerator<MP4Atom>;
}
@@ -32,6 +33,7 @@ export async function startFFMPegFragmetedMP4Session(inputArguments: string[], a
ffmpegLogInitialOutput(console, cp);
return {
socket: undefined,
cp,
generator: parseFragmentedMP4(cp.stdio[3] as Readable),
};

1
plugins/core/fs/@types Symbolic link
View File

@@ -0,0 +1 @@
../../../common/fs/@types

View File

@@ -1 +0,0 @@
../../../common/fs/sdk

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/core",
"version": "0.0.197",
"version": "0.0.200",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/core",
"version": "0.0.197",
"version": "0.0.200",
"license": "Apache-2.0",
"dependencies": {
"@koush/wrtc": "^0.5.2",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/core",
"version": "0.0.197",
"version": "0.0.200",
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -0,0 +1,4 @@
import type { ScriptDevice } from "@scrypted/common/src/eval/monaco/script-device";
import type { ScryptedDeviceBase } from "@scrypted/sdk";
declare const device: ScryptedDeviceBase & ScriptDevice;

View File

@@ -63,7 +63,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
mediaCore: MediaCore;
automations = new Map<string, Automation>();
aggregate = new Map<string, AggregateDevice>();
scripts = new Map<string, Script>();
scripts = new Map<string, Promise<Script>>();
constructor() {
super();
@@ -93,8 +93,15 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
}
else if (nativeId?.startsWith('script:')) {
const script = new Script(nativeId);
this.scripts.set(nativeId, script);
reportScript(nativeId);
this.scripts.set(nativeId, (async () => {
if (script.providedInterfaces.length > 2) {
await script.run();
}
else {
reportScript(nativeId);
}
return script;
})());
}
}
@@ -124,7 +131,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
const nativeId = `script:${Math.random()}`;
await reportScript(nativeId);
const script = new Script(nativeId);
this.scripts.set(nativeId, script);
this.scripts.set(nativeId, Promise.resolve(script));
const { id } = script;
sendJSON(res, {
id,
@@ -159,15 +166,16 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
});
}
getDevice(nativeId: string) {
async getDevice(nativeId: string) {
if (nativeId === 'mediacore')
return this.mediaCore;
if (nativeId?.startsWith('automation:'))
return this.automations.get(nativeId);
if (nativeId?.startsWith('aggregate:'))
return this.aggregate.get(nativeId);
if (nativeId?.startsWith('script:'))
if (nativeId?.startsWith('script:')) {
return this.scripts.get(nativeId);
}
}
checkEngineIoEndpoint(request: HttpRequest, name: string) {
@@ -203,7 +211,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
}
if (this.checkEngineIoEndpoint(request, 'videocamera')) {
const url = new URL(`http://localhost${request.url}`);
const url = new URL(`http://localhost${request.url}`);
const deviceId = url.searchParams.get('deviceId');
const camera = systemManager.getDeviceById<VideoCamera & RTCSignalingChannel>(deviceId);
if (!camera)

View File

@@ -0,0 +1,8 @@
import { createMonacoEvalDefaults } from "../../../common/src/eval/scrypted-eval";
const libs = {
script: require("!!raw-loader!@scrypted/common/src/eval/monaco/script-device.ts").default,
util: require("!!raw-loader!./api/util.ts").default,
};
export const monacoEvalDefaults = createMonacoEvalDefaults(libs);

View File

@@ -1,10 +1,11 @@
import { Scriptable, Program, ScryptedDeviceBase, ScriptSource } from "@scrypted/sdk";
import { createMonacoEvalDefaults } from "@scrypted/common/src/scrypted-eval";
import sdk, { Scriptable, Program, ScryptedDeviceBase, ScriptSource, ScryptedInterface, ScryptedDeviceType } from "@scrypted/sdk";
import { scryptedEval } from "./scrypted-eval";
import { monacoEvalDefaults } from "./monaco";
import { createScriptDevice, ScriptDeviceImpl } from "@scrypted/common/src/eval/scrypted-eval";
const monacoEvalDefaults = createMonacoEvalDefaults({});
const { log, deviceManager, systemManager } = sdk;
export class Script extends ScryptedDeviceBase implements Scriptable, Program {
export class Script extends ScryptedDeviceBase implements Scriptable, Program, ScriptDeviceImpl {
constructor(nativeId: string) {
super(nativeId);
}
@@ -40,18 +41,63 @@ export class Script extends ScryptedDeviceBase implements Scriptable, Program {
}
}
run(variables?: { [name: string]: any; }): Promise<any> {
async postRunScript() {
const allInterfaces = this.mergeHandler(this);
if (allInterfaces.length !== 2) {
await deviceManager.onDeviceDiscovered({
nativeId: this.nativeId,
interfaces: allInterfaces,
type: ScryptedDeviceType.Unknown,
name: this.providedName,
});
}
}
prepareScript() {
Object.assign(this, createScriptDevice([
ScryptedInterface.Scriptable,
ScryptedInterface.Program,
]));
}
async run(variables?: { [name: string]: any; }): Promise<any> {
this.prepareScript();
try {
const data = JSON.parse(this.storage.getItem('data'));
return scryptedEval(this, data['script.ts'], variables);
const ret = await scryptedEval(this, data['script.ts'], Object.assign({
device: this,
}, variables));
await this.postRunScript();
return ret;
}
catch (e) {
this.log.e('error loading script');
this.console.error(e);
this.console.error('error loading script', e);
throw e;
}
}
async eval(source: ScriptSource, variables: { [name: string]: any }) {
return scryptedEval(this, source.script, variables);
this.prepareScript();
const ret = await scryptedEval(this, source.script, Object.assign({
device: this,
}, variables));
await this.postRunScript();
return ret;
}
// will be done at runtime
mergeHandler(device: ScryptedDeviceBase): string[] {
throw new Error("Method not implemented.");
}
handle<T>(handler?: T & object): void {
throw new Error("Method not implemented.");
}
handleTypes(...interfaces: string[]): void {
throw new Error("Method not implemented.");
}
}

View File

@@ -1,5 +1,5 @@
import { ScryptedDeviceBase } from "@scrypted/sdk";
import { scryptedEval as scryptedEvalBase } from "@scrypted/common/src/scrypted-eval";
import { scryptedEval as scryptedEvalBase } from "@scrypted/common/src/eval/scrypted-eval";
export async function scryptedEval(device: ScryptedDeviceBase, script: string, params: { [name: string]: any }) {
return scryptedEvalBase(device, script, {}, params);

View File

@@ -45,6 +45,8 @@ export async function streamCamera(mediaManager: MediaManager, device: ScryptedD
remoteAudio.play();
console.log('received track', ev.track);
};
return pc;
}
export async function createBlobUrl(mediaManager: MediaManager, mediaObject: MediaObject): Promise<string> {

View File

@@ -22,6 +22,7 @@ export async function getDeviceAvailableMixins(systemManager: SystemManager, dev
const results = await Promise.all(getAllDevices<MixinProvider>(systemManager).map(async (check) => {
try {
if (check.interfaces.includes(ScryptedInterface.MixinProvider)) {
console.time('mixin provider' + check.name);
if (await check.canMixin(device.type, device.interfaces))
return check;
}
@@ -29,6 +30,9 @@ export async function getDeviceAvailableMixins(systemManager: SystemManager, dev
catch (e) {
console.error("mixin check error", check.id, e);
}
finally {
console.timeEnd('mixin provider' + check.name);
}
}));
return results.filter(result => !!result);

1
plugins/mqtt/fs Symbolic link
View File

@@ -0,0 +1 @@
../../common/fs

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/mqtt",
"version": "0.0.38",
"version": "0.0.39",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/mqtt",
"version": "0.0.38",
"version": "0.0.39",
"dependencies": {
"@types/node": "^16.6.1",
"aedes": "^0.46.1",
@@ -17,23 +17,32 @@
"websocket-stream": "^5.5.2"
},
"devDependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@types/nunjucks": "^3.2.0"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.0.134",
"../../common": {
"name": "@scrypted/common",
"version": "1.0.1",
"dev": true,
"license": "ISC",
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
"@babel/plugin-proposal-numeric-separator": "^7.14.5",
"@babel/plugin-proposal-optional-chaining": "^7.14.5",
"@babel/plugin-transform-modules-commonjs": "^7.15.4",
"@babel/plugin-transform-typescript": "^7.15.8",
"@babel/preset-typescript": "^7.15.0",
"@koush/werift": "file:../external/werift/packages/webrtc",
"@scrypted/sdk": "file:../sdk",
"typescript": "^4.4.3"
},
"devDependencies": {
"@types/node": "^16.9.0"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.0.173",
"dev": true,
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.16.7",
"adm-zip": "^0.4.13",
"axios": "^0.21.4",
"babel-loader": "^8.2.3",
@@ -43,7 +52,6 @@
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"tmp": "^0.2.1",
"ts-loader": "^9.2.6",
"webpack": "^5.59.0"
},
"bin": {
@@ -60,12 +68,17 @@
"stringify-object": "^3.3.0",
"ts-node": "^10.4.0",
"typedoc": "^0.22.8",
"typescript-json-schema": "^0.50.1"
"typescript-json-schema": "^0.50.1",
"webpack-bundle-analyzer": "^4.5.0"
}
},
"../sdk": {
"extraneous": true
},
"node_modules/@scrypted/common": {
"resolved": "../../common",
"link": true
},
"node_modules/@scrypted/sdk": {
"resolved": "../../sdk",
"link": true
@@ -344,9 +357,9 @@
"integrity": "sha512-XBU9RXeoYc2/VnvMhplAxEmZLfIk7cvTBu+xwoBuTI8pL19E03cmca17QQycKIdxgwCeFA/a4u27gv1h3ya5LQ=="
},
"node_modules/follow-redirects": {
"version": "1.14.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz",
"integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==",
"version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
"funding": [
{
"type": "individual",
@@ -828,16 +841,19 @@
}
},
"dependencies": {
"@scrypted/common": {
"version": "file:../../common",
"requires": {
"@koush/werift": "file:../external/werift/packages/webrtc",
"@scrypted/sdk": "file:../sdk",
"@types/node": "^16.9.0",
"typescript": "^4.4.3"
}
},
"@scrypted/sdk": {
"version": "file:../../sdk",
"requires": {
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
"@babel/plugin-proposal-numeric-separator": "^7.14.5",
"@babel/plugin-proposal-optional-chaining": "^7.14.5",
"@babel/plugin-transform-modules-commonjs": "^7.15.4",
"@babel/plugin-transform-typescript": "^7.15.8",
"@babel/preset-typescript": "^7.15.0",
"@babel/preset-typescript": "^7.16.7",
"@types/node": "^16.11.1",
"@types/stringify-object": "^4.0.0",
"adm-zip": "^0.4.13",
@@ -850,11 +866,11 @@
"rimraf": "^3.0.2",
"stringify-object": "^3.3.0",
"tmp": "^0.2.1",
"ts-loader": "^9.2.6",
"ts-node": "^10.4.0",
"typedoc": "^0.22.8",
"typescript-json-schema": "^0.50.1",
"webpack": "^5.59.0"
"webpack": "^5.59.0",
"webpack-bundle-analyzer": "^4.5.0"
}
},
"@types/node": {
@@ -1079,9 +1095,9 @@
"integrity": "sha512-XBU9RXeoYc2/VnvMhplAxEmZLfIk7cvTBu+xwoBuTI8pL19E03cmca17QQycKIdxgwCeFA/a4u27gv1h3ya5LQ=="
},
"follow-redirects": {
"version": "1.14.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz",
"integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g=="
"version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
},
"from2": {
"version": "2.3.0",

View File

@@ -36,7 +36,8 @@
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk",
"@scrypted/common": "file:../../common",
"@types/nunjucks": "^3.2.0"
},
"version": "0.0.38"
"version": "0.0.39"
}

View File

@@ -1,3 +1,5 @@
import { ScriptDevice } from '@scrypted/common/src/eval/monaco/script-device';
export interface MqttEvent {
buffer?: Buffer;
json?: any;
@@ -8,9 +10,7 @@ export interface MqttSubscriptions {
[topic: string]: (event: MqttEvent) => void;
}
export interface MqttClient {
export interface MqttClient extends ScriptDevice {
subscribe(subscriptions: MqttSubscriptions, options?: any): void;
handle<T>(handler?: T & object): void;
handleTypes(...interfaces: string[]): void;
publish(topic: string, value: any): Promise<void>;
}

View File

@@ -1,4 +1,4 @@
import type { ScryptedDeviceBase, ScryptedInterface } from "@scrypted/sdk";
import type { ScryptedDeviceBase } from "@scrypted/sdk";
import type { MqttClient, MqttEvent, MqttSubscriptions } from "./mqtt-client";
declare const device: ScryptedDeviceBase;

View File

@@ -15,21 +15,13 @@ import { MqttAutoDiscoveryProvider } from './autodiscovery/autodiscovery';
import { SettingsMixinDeviceBase } from "../../../common/src/settings-mixin";
import { connect, Client } from 'mqtt';
import { isPublishable } from './publishable-types';
import { createScriptDevice, ScriptDeviceImpl } from '@scrypted/common/src/eval/scrypted-eval';
const loopbackLight = require("!!raw-loader!./examples/loopback-light.ts").default;
const methodInterfaces: { [method: string]: string } = {};
for (const desc of Object.values(ScryptedInterfaceDescriptors)) {
for (const method of desc.methods) {
methodInterfaces[method] = desc.name;
}
}
const { log, deviceManager, systemManager } = sdk;
class MqttDevice extends MqttDeviceBase implements Scriptable {
handler: any;
constructor(nativeId: string) {
super(nativeId);
@@ -75,8 +67,6 @@ class MqttDevice extends MqttDeviceBase implements Scriptable {
}): Promise<any> {
const { script } = source;
try {
this.handler = undefined;
const client = this.connectClient();
client.on('connect', () => this.console.log('mqtt client connected'));
client.on('disconnect', () => this.console.log('mqtt client disconnected'));
@@ -84,12 +74,7 @@ class MqttDevice extends MqttDeviceBase implements Scriptable {
this.console.log('mqtt client error', e);
});
const allInterfaces: string[] = [
ScryptedInterface.Scriptable,
ScryptedInterface.Settings,
]
const mqtt: MqttClient = {
const mqtt: MqttClient & ScriptDeviceImpl = {
subscribe: (subscriptions: MqttSubscriptions, options?: any) => {
for (const topic of Object.keys(subscriptions)) {
const fullTopic = this.pathname + topic;
@@ -122,32 +107,23 @@ class MqttDevice extends MqttDeviceBase implements Scriptable {
});
}
},
handle: <T>(handler?: T & object) => {
this.handler = handler;
},
handleTypes: (...interfaces: ScryptedInterface[]) => {
allInterfaces.push(...interfaces);
},
publish: async (topic: string, value: any) => {
if (typeof value === 'object')
value = JSON.stringify(value);
if (value.constructor.name !== Buffer.name)
value = value.toString();
client.publish(this.pathname + topic, value);
}
},
...createScriptDevice([
ScryptedInterface.Scriptable,
ScryptedInterface.Settings,
])
}
await scryptedEval(this, script, {
mqtt,
});
const handler = this.handler || {};
for (const method of Object.keys(handler)) {
const iface = methodInterfaces[method];
if (iface)
allInterfaces.push(iface);
}
Object.assign(this, handler);
const allInterfaces = mqtt.mergeHandler(this);
await deviceManager.onDeviceDiscovered({
nativeId: this.nativeId,

View File

@@ -1,8 +1,7 @@
import { createMonacoEvalDefaults } from "../../../common/src/scrypted-eval";
import { createMonacoEvalDefaults } from "@scrypted/common/src/eval/scrypted-eval";
const libs = {
types: require("!!raw-loader!@scrypted/sdk/types/index.d.ts").default,
sdk: require("!!raw-loader!@scrypted/sdk/index.d.ts").default,
script: require("!!raw-loader!@scrypted/common/src/eval/monaco/script-device.ts").default,
client: require("!!raw-loader!./api/mqtt-client.ts").default,
frigate: require("!!raw-loader!./api/frigate.ts").default,
util: require("!!raw-loader!./api/util.ts").default,

View File

@@ -1,5 +1,5 @@
import { ScryptedDeviceBase } from "@scrypted/sdk";
import { scryptedEval as scryptedEvalBase } from "../../../common/src/scrypted-eval";
import { scryptedEval as scryptedEvalBase } from "@scrypted/common/src/eval/scrypted-eval";
const util = require("!!raw-loader!./api/util.ts").default;
const frigate = require("!!raw-loader!./api/frigate.ts").default;