From 8e3c5c05eafd6be25ceffe9c924d0e96f150f705 Mon Sep 17 00:00:00 2001 From: GermanBluefox Date: Wed, 6 Nov 2024 00:02:41 +0100 Subject: [PATCH] Working on typescript --- package.json | 229 ++++++++++---------- src/lib/sandbox.ts | 498 ++++++++++++++++++++++++------------------- src/lib/scheduler.ts | 77 +++---- src/types.d.ts | 141 +++++++++--- 4 files changed, 540 insertions(+), 405 deletions(-) diff --git a/package.json b/package.json index 25bf211e..93675e44 100644 --- a/package.json +++ b/package.json @@ -1,120 +1,121 @@ { - "name": "iobroker.javascript", - "version": "8.8.3", - "description": "Rules Engine for ioBroker", - "author": "bluefox ", - "contributors": [ - { - "name": "hobbyquaker", - "email": "hq@ccu.io" + "name": "iobroker.javascript", + "version": "8.8.3", + "description": "Rules Engine for ioBroker", + "author": "bluefox ", + "contributors": [ + { + "name": "hobbyquaker", + "email": "hq@ccu.io" + }, + { + "name": "Apollon77", + "email": "ingo@fischer-ka.de" + }, + { + "name": "AlCalzone", + "email": "d.griesel@gmx.net" + }, + { + "name": "Matthias Kleine", + "email": "info@haus-automatisierung.com" + } + ], + "homepage": "https://github.com/ioBroker/ioBroker.javascript", + "license": "MIT", + "keywords": [ + "ioBroker", + "javascript", + "script", + "engine", + "home automation" + ], + "repository": { + "type": "git", + "url": "https://github.com/ioBroker/ioBroker.javascript" }, - { - "name": "Apollon77", - "email": "ingo@fischer-ka.de" + "engines": { + "node": ">=18" }, - { - "name": "AlCalzone", - "email": "d.griesel@gmx.net" + "depsComment": { + "@types/node": "should match the lowest MAJOR version of Node.js we support." }, - { - "name": "Matthias Kleine", - "email": "info@haus-automatisierung.com" + "dependencies": { + "@iobroker/adapter-core": "^3.2.2", + "@types/node": "^22.7.4", + "@types/request": "^2.48.12", + "axios": "^1.7.7", + "jsonata": "^2.0.5", + "jszip": "^3.10.1", + "node-inspect": "^2.0.0", + "node-schedule": "2.1.1", + "request": "^2.88.2", + "semver": "^7.6.3", + "suncalc2": "^1.8.1", + "typescript": "~5.6.2", + "virtual-tsc": "^0.6.2", + "wake_on_lan": "^1.0.0" + }, + "devDependencies": { + "@alcalzone/release-script": "^3.8.0", + "@alcalzone/release-script-plugin-iobroker": "^3.7.2", + "@alcalzone/release-script-plugin-license": "^3.7.0", + "@iobroker/adapter-dev": "^1.3.0", + "@iobroker/build-tools": "^2.0.6", + "@iobroker/eslint-config": "^0.1.6", + "@iobroker/testing": "^5.0.0", + "@iobroker/types": "^6.0.11", + "@types/nodemailer": "^6.4.16", + "@types/node-schedule": "^2.1.7", + "@types/request": "^2.48.12", + "@types/suncalc": "^1.9.2", + "@iobroker/vis-2-widgets-react-dev": "^4.0.3", + "alcalzone-shared": "^4.0.8", + "chai": "^5.1.1", + "mocha": "^10.8.2", + "timekeeper": "^2.3.1" + }, + "bugs": { + "url": "https://github.com/ioBroker/ioBroker.javascript/issues" + }, + "main": "main.js", + "files": [ + "admin/", + "lib/", + "docs/", + "install/", + "lib/", + "io-package.json", + "LICENSE", + "main.js", + "admin/vsFont/codicon.json" + ], + "scripts": { + "test:declarations": "tsc -p test/lib/TS/tsconfig.json && tsc -p test/lib/JS/tsconfig.json", + "test:javascript": "node node_modules/mocha/bin/mocha --exit", + "test": "npm run test:declarations && npm run test:javascript", + "translate": "translate-adapter", + "//postinstall": "node ./install/installTypings.js", + "build": "node tasks", + "release": "release-script --noPush -y --all", + "update-packages": "ncu --upgrade && cd src && ncu --upgrade && cd ../src-admin && ncu --upgrade", + "npm": "npm i && cd src && npm i -f && cd ../src-admin && npm i -f", + "lint": "eslint -c eslint.config.mjs", + "lint-all": "eslint -c eslint.config.mjs && cd src && eslint -c eslint.config.mjs && cd ../src-admin && eslint -c eslint.config.mjs", + "admin-0-clean": "node tasks.js --admin-0-clean", + "admin-1-npm": "node tasks.js --admin-1-npm", + "admin-2-compile": "node tasks.js --admin-2-compile", + "admin-3-copy": "node tasks.js --admin-3-copy", + "admin-build": "node tasks.js --admin-build", + "0-clean": "node tasks.js --0-clean", + "1-npm": "node tasks.js --1-npm", + "2-build": "node tasks.js --2-build", + "3-copy": "node tasks.js --3-copy", + "4-patch": "node tasks.js --4-patch", + "build-editor": "node tasks.js --build", + "blocklyJson2words": "node tasks.js --blocklyJson2words", + "blocklyWords2json": "node tasks.js --blocklyWords2json", + "monaco-update": "node tasks.js --monaco-update", + "monaco-typescript": "node tasks.js --monaco-typescript" } - ], - "homepage": "https://github.com/ioBroker/ioBroker.javascript", - "license": "MIT", - "keywords": [ - "ioBroker", - "javascript", - "script", - "engine", - "home automation" - ], - "repository": { - "type": "git", - "url": "https://github.com/ioBroker/ioBroker.javascript" - }, - "engines": { - "node": ">=18" - }, - "depsComment": { - "@types/node": "should match the lowest MAJOR version of Node.js we support." - }, - "dependencies": { - "@iobroker/adapter-core": "^3.2.2", - "@types/node": "^22.7.4", - "@types/request": "^2.48.12", - "axios": "^1.7.7", - "jsonata": "^2.0.5", - "jszip": "^3.10.1", - "node-inspect": "^2.0.0", - "node-schedule": "2.1.1", - "request": "^2.88.2", - "semver": "^7.6.3", - "suncalc2": "^1.8.1", - "typescript": "~5.6.2", - "virtual-tsc": "^0.6.2", - "wake_on_lan": "^1.0.0" - }, - "devDependencies": { - "@alcalzone/release-script": "^3.8.0", - "@alcalzone/release-script-plugin-iobroker": "^3.7.2", - "@alcalzone/release-script-plugin-license": "^3.7.0", - "@iobroker/adapter-dev": "^1.3.0", - "@iobroker/build-tools": "^2.0.5", - "@iobroker/eslint-config": "^0.1.6", - "@iobroker/testing": "^5.0.0", - "@iobroker/types": "^6.0.11", - "@types/nodemailer": "^6.4.16", - "@types/request": "^2.48.12", - "@types/suncalc": "^1.9.2", - "@iobroker/vis-2-widgets-react-dev": "^4.0.3", - "alcalzone-shared": "^4.0.8", - "chai": "^5.1.1", - "mocha": "^10.7.3", - "timekeeper": "^2.3.1" - }, - "bugs": { - "url": "https://github.com/ioBroker/ioBroker.javascript/issues" - }, - "main": "main.js", - "files": [ - "admin/", - "lib/", - "docs/", - "install/", - "lib/", - "io-package.json", - "LICENSE", - "main.js", - "admin/vsFont/codicon.json" - ], - "scripts": { - "test:declarations": "tsc -p test/lib/TS/tsconfig.json && tsc -p test/lib/JS/tsconfig.json", - "test:javascript": "node node_modules/mocha/bin/mocha --exit", - "test": "npm run test:declarations && npm run test:javascript", - "translate": "translate-adapter", - "//postinstall": "node ./install/installTypings.js", - "build": "node tasks", - "release": "release-script --noPush -y --all", - "update-packages": "ncu --upgrade && cd src && ncu --upgrade && cd ../src-admin && ncu --upgrade", - "npm": "npm i && cd src && npm i -f && cd ../src-admin && npm i -f", - "lint": "eslint -c eslint.config.mjs", - "lint-all": "eslint -c eslint.config.mjs && cd src && eslint -c eslint.config.mjs && cd ../src-admin && eslint -c eslint.config.mjs", - "admin-0-clean": "node tasks.js --admin-0-clean", - "admin-1-npm": "node tasks.js --admin-1-npm", - "admin-2-compile": "node tasks.js --admin-2-compile", - "admin-3-copy": "node tasks.js --admin-3-copy", - "admin-build": "node tasks.js --admin-build", - "0-clean": "node tasks.js --0-clean", - "1-npm": "node tasks.js --1-npm", - "2-build": "node tasks.js --2-build", - "3-copy": "node tasks.js --3-copy", - "4-patch": "node tasks.js --4-patch", - "build-editor": "node tasks.js --build", - "blocklyJson2words": "node tasks.js --blocklyJson2words", - "blocklyWords2json": "node tasks.js --blocklyWords2json", - "monaco-update": "node tasks.js --monaco-update", - "monaco-typescript": "node tasks.js --monaco-typescript" - } } diff --git a/src/lib/sandbox.ts b/src/lib/sandbox.ts index 5aba4721..d226cc5f 100644 --- a/src/lib/sandbox.ts +++ b/src/lib/sandbox.ts @@ -1,17 +1,30 @@ import { isObject, isArray, promisify, getHttpRequestConfig } from './tools'; import { commonTools } from '@iobroker/adapter-core'; -import {AdapterConfig, JavascriptContext, JsScript, Pattern, PushoverOptions, SandboxType, Selector} from '../types'; +import { type ChildProcess } from 'node:child_process'; +import { + AdapterConfig, + AstroRule, ChangeType, FileSubscriptionResult, IobSchedule, + JavascriptContext, + JsScript, LogMessage, + Pattern, + PushoverOptions, + SandboxType, + Selector, SubscriptionResult, TimeRule +} from '../types'; import * as constsMod from './consts'; import * as wordsMod from './words'; import * as eventObjMod from './eventObj'; import { patternCompareFunctions as patternCompareFunctionsMod } from './patternCompareFunctions'; import { type PatternEventCompareFunction } from './patternCompareFunctions'; import * as jsonataMod from 'jsonata'; +import { type Job } from 'node-schedule'; import { iobJS } from "./javascript"; import {ExecOptions} from "node:child_process"; import { type SendMailOptions } from 'nodemailer'; -import { AxiosHeaders, AxiosHeaderValue, AxiosResponse, ResponseType } from "axios"; -import {SchedulerRule} from "./scheduler"; +import { AxiosHeaders, AxiosHeaderValue, AxiosResponse, ResponseType } from 'axios'; +import { SchedulerRule } from './scheduler'; +import {EventObj} from "./eventObj"; +import {AstroEvent} from "./consts"; const pattern2RegEx = commonTools.pattern2RegEx; export default function sandBox( @@ -559,15 +572,15 @@ export default function sandBox( return mods[md]; } - let error; + let error: Error | undefined; try { mods[md] = require( adapter.getAdapterScopedPackageIdentifier ? adapter.getAdapterScopedPackageIdentifier(md) : md, ); return mods[md]; - } catch (e) { - error = e; + } catch (e: any) { + error = e as Error; } try { @@ -577,7 +590,7 @@ export default function sandBox( mods[md] = require(md); return mods[md]; - } catch (e) { + } catch (e: any) { context.logError(name, error || e, 6); adapter.setState(`scriptProblem.${name.substring('script.js.'.length)}`, { val: true, @@ -596,11 +609,7 @@ export default function sandBox( __subscriptionsLog: 0, __schedules: 0, }, - /** - * @param {string} selector - * @returns {iobJS.QueryResult} - */ - $: function (selector: string) { + $: function (selector: string): iobJS.QueryResult { // following is supported // 'type[commonAttr=something]', 'id[commonAttr=something]', id(enumName="something")', id{nativeName="something"} // Type can be state, channel or device @@ -615,7 +624,6 @@ export default function sandBox( // Todo CACHE!!! - /** @type {iobJS.QueryResult} */ const result: iobJS.QueryResult = {}; let name: string = ''; @@ -1062,15 +1070,15 @@ export default function sandBox( await sandbox.setStateChangedAsync(this[i], state, isAck); } }; - result.setStateDelayed = function (state, isAck, delay, clearRunning, callback) { + result.setStateDelayed = function (state: ioBroker.SettableState | ioBroker.StateValue, isAck: boolean | number | undefined, delay: number | boolean, clearRunning: boolean | (() => void), callback?: () => void) { if (typeof isAck !== 'boolean') { - callback = clearRunning; - clearRunning = delay; - delay = isAck; + callback = clearRunning as () => void; + clearRunning = delay as boolean; + delay = isAck as number; isAck = undefined; } if (typeof delay !== 'number') { - callback = clearRunning; + callback = clearRunning as () => void; clearRunning = delay; delay = 0; } @@ -1080,7 +1088,7 @@ export default function sandBox( } let count = this.length; for (let i = 0; i < this.length; i++) { - sandbox.setStateDelayed(this[i], state, isAck, delay, clearRunning, () => { + sandbox.setStateDelayed(this[i], state, isAck as boolean, delay, clearRunning, () => { if (!--count && typeof callback === 'function') { callback(); } @@ -1088,7 +1096,7 @@ export default function sandBox( } return this; }; - result.on = function (callbackOrId, value) { + result.on = function (callbackOrId: string | ((data: any) => void), value?: any) { for (let i = 0; i < this.length; i++) { sandbox.subscribe(this[i], callbackOrId, value); } @@ -1096,7 +1104,7 @@ export default function sandBox( }; return result; }, - log: function (msg, severity) { + log: function (msg: string, severity?: ioBroker.LogLevel): void { severity = severity || 'info'; // disable log in log handler (prevent endless loops) @@ -1119,7 +1127,7 @@ export default function sandBox( adapter.log[severity](`${name}: ${msg}`); } }, - onLog: function (severity: ioBroker.LogLevel, callback: (info: any) => void): number { + onLog: function (severity: ioBroker.LogLevel, callback: (info: LogMessage) => void): number { if (!['info', 'error', 'debug', 'silly', 'warn', '*'].includes(severity)) { sandbox.log(`Unknown severity "${severity}"`, 'warn'); return 0; @@ -1151,7 +1159,7 @@ export default function sandBox( return handler.id; }, - onLogUnregister: function (idOrCallbackOrSeverity) { + onLogUnregister: function (idOrCallbackOrSeverity: string | ioBroker.LogLevel | ((info: LogMessage) => void)): boolean { let found = false; if (context.logSubscriptions?.[sandbox.scriptName]) { @@ -1201,7 +1209,7 @@ export default function sandBox( cmd: string, options?: ExecOptions | ((error: Error | null | string, stdout?: string, stderr?: string) => void), callback?: (error: Error | null | string, stdout?: string, stderr?: string) => void, - ) { + ): undefined | ChildProcess { if (typeof options === 'function') { callback = options as (error: Error | null | string, stdout?: string, stderr?: string) => void; options = {}; @@ -1307,7 +1315,7 @@ export default function sandBox( } } }) - .catch(error => { + .catch((error: any) => { const responseTime = Date.now() - startTime; sandbox.log(`httpGet(url=${url}, error=${error.message})`, 'error'); @@ -1359,7 +1367,7 @@ export default function sandBox( result: { statusCode: number | null; data: any; - headers: Record; + headers: Record; responseTime: number; }, ) => void), @@ -1368,7 +1376,7 @@ export default function sandBox( result: { statusCode: number | null; data: any; - headers: Record; + headers: Record; responseTime: number; }, ) => void, @@ -1379,7 +1387,14 @@ export default function sandBox( } const config = { - ...getHttpRequestConfig(url, options), + ...getHttpRequestConfig(url, options as { + timeout?: number; + responseType?: ResponseType; + headers?: Record; + basicAuth?: { user: string; password: string } | null; + bearerAuth?: string; + validateCertificate?: boolean; + }), method: 'post', data, }; @@ -1390,7 +1405,7 @@ export default function sandBox( mods.axios .default(config) - .then(response => { + .then((response: AxiosResponse) => { const responseTime = Date.now() - startTime; sandbox.verbose && sandbox.log(`httpPost(url=${url}, responseTime=${responseTime}ms)`, 'info'); @@ -1482,62 +1497,71 @@ export default function sandBox( return filePath; }, - subscribe: function (pattern: SchedulerRule | string | (SchedulerRule | string)[], callbackOrId: (id: string) => void, value) { + subscribe: function ( + pattern: TimeRule | AstroRule | Pattern | SchedulerRule | string | (TimeRule | AstroRule | Pattern | SchedulerRule | string)[], + callbackOrChangeTypeOrId: string | ChangeType | ((event?: EventObj) => void), + value?: any, + ): SubscriptionResult | IobSchedule | string | null | undefined | (SubscriptionResult | IobSchedule | string | null | undefined)[] { + // If a schedule object is given if ( (typeof pattern === 'string' && pattern[0] === '{') || (typeof pattern === 'object' && (pattern as SchedulerRule).period) ) { - return sandbox.schedule(pattern as SchedulerRule, callbackOrId); + return sandbox.schedule(pattern as SchedulerRule, callbackOrChangeTypeOrId as () => void); } + // If an array of schedules is given if (pattern && Array.isArray(pattern)) { - const result = []; + const result: (IobSchedule | string | null | undefined)[] = []; for (const p of pattern) { - result.push(sandbox.subscribe(p as SchedulerRule | string, callbackOrId, value)); + result.push(sandbox.subscribe(p as SchedulerRule | string, callbackOrChangeTypeOrId, value) as IobSchedule | string | null | undefined); } return result; } // detect subscribe('id', 'any', (obj) => {}) + let oPattern: Pattern; if ( (typeof pattern === 'string' || pattern instanceof RegExp) && - typeof callbackOrId === 'string' && + typeof callbackOrChangeTypeOrId === 'string' && typeof value === 'function' ) { - pattern = { id: pattern, change: callbackOrId }; - callbackOrId = value; + oPattern = { id: pattern, change: callbackOrChangeTypeOrId as ChangeType }; + callbackOrChangeTypeOrId = value; value = undefined; + } else { + oPattern = pattern as Pattern; } - if (pattern?.id && Array.isArray(pattern.id)) { - const result = []; - for (let t = 0; t < pattern.id.length; t++) { - const pa = JSON.parse(JSON.stringify(pattern)); - pa.id = pattern.id[t]; - result.push(sandbox.subscribe(pa, callbackOrId, value)); + if (oPattern?.id && Array.isArray(oPattern.id)) { + const result: (IobSchedule | string | null | undefined)[] = []; + for (let t = 0; t < oPattern.id.length; t++) { + const pa: Pattern = JSON.parse(JSON.stringify(oPattern)); + pa.id = oPattern.id[t]; + result.push(sandbox.subscribe(pa, callbackOrChangeTypeOrId, value) as IobSchedule | string | null | undefined); } return result; } // try to detect astro or cron (by spaces) if (isObject(pattern) || (typeof pattern === 'string' && pattern.match(/[,/\d*]+\s[,/\d*]+\s[,/\d*]+/))) { - if (pattern.astro) { - return sandbox.schedule(pattern, callbackOrId); - } else if (pattern.time) { - return sandbox.schedule(pattern.time, callbackOrId); + if ((pattern as AstroRule).astro) { + return sandbox.schedule(pattern as AstroRule, callbackOrChangeTypeOrId as () => void); + } else if ((pattern as TimeRule).time) { + return sandbox.schedule((pattern as TimeRule).time as string, callbackOrChangeTypeOrId as () => void); } } - let callback; + let callback: undefined | ((obj: EventObj) => void); // source is set by regexp if defined as /regexp/ - if (!isObject(pattern) || pattern instanceof RegExp || pattern.source) { - pattern = { id: pattern, change: 'ne' }; + if (!isObject(pattern) || pattern instanceof RegExp || (pattern as RegExp).source) { + oPattern = { id: pattern as string | RegExp, change: 'ne' }; } - if (pattern.id !== undefined && !pattern.id) { + if (oPattern.id !== undefined && !oPattern.id) { sandbox.log(`Error by subscription (trigger): empty ID defined. All states matched.`, 'error'); return; - } else if (typeof pattern.id === 'boolean' || typeof pattern.id === 'number') { + } else if (typeof oPattern.id === 'boolean' || typeof oPattern.id === 'number') { sandbox.log(`Error by subscription (trigger): Wrong ID of type boolean or number.`, 'error'); return; } @@ -1551,32 +1575,32 @@ export default function sandBox( ); } - if (pattern.q === undefined) { - pattern.q = 0; + if (oPattern.q === undefined) { + oPattern.q = 0; } // add adapter namespace if nothing given - if (pattern.id && typeof pattern.id === 'string' && !pattern.id.includes('.')) { - pattern.id = `${adapter.namespace}.${pattern.id}`; + if (oPattern.id && typeof oPattern.id === 'string' && !oPattern.id.includes('.')) { + oPattern.id = `${adapter.namespace}.${oPattern.id}`; } - if (typeof callbackOrId === 'function') { - callback = callbackOrId; + if (typeof callbackOrChangeTypeOrId === 'function') { + callback = callbackOrChangeTypeOrId; } else { if (typeof value === 'undefined') { - callback = function (obj) { - sandbox.setState(callbackOrId, obj.newState.val); + callback = function (obj: EventObj) { + sandbox.setState(callbackOrChangeTypeOrId as string, obj.newState.val); }; } else { callback = function (/* obj */) { - sandbox.setState(callbackOrId, value); + sandbox.setState(callbackOrChangeTypeOrId as string, value); }; } } - const subs = { - pattern, - callback: obj => { + const subs: SubscriptionResult = { + pattern: oPattern, + callback: (obj: EventObj) => { if (typeof callback === 'function') { try { callback.call(sandbox, obj); @@ -1589,8 +1613,8 @@ export default function sandBox( }; // try to extract adapter - if (pattern.id && typeof pattern.id === 'string') { - const parts = pattern.id.split('.'); + if (oPattern.id && typeof oPattern.id === 'string') { + const parts = oPattern.id.split('.'); const a = `${parts[0]}.${parts[1]}`; const _adapter = `system.adapter.${a}`; @@ -1598,31 +1622,31 @@ export default function sandBox( const alive = `system.adapter.${a}.alive`; context.adapterSubs[alive] = context.adapterSubs[alive] || []; - const subExists = context.adapterSubs[alive].filter(sub => sub === pattern.id).length > 0; + const subExists = context.adapterSubs[alive].filter(sub => sub === oPattern.id).length > 0; if (!subExists) { - context.adapterSubs[alive].push(pattern.id); - adapter.sendTo(a, 'subscribe', pattern.id); + context.adapterSubs[alive].push(oPattern.id); + adapter.sendTo(a, 'subscribe', oPattern.id); } } } sandbox.verbose && sandbox.log(`subscribe: ${JSON.stringify(subs)}`, 'info'); - subscribePattern(script, pattern.id); + subscribePattern(script, oPattern.id as string); - subs.patternCompareFunctions = getPatternCompareFunctions(pattern); + subs.patternCompareFunctions = getPatternCompareFunctions(oPattern); context.subscriptions.push(subs); - if (pattern.enumName || pattern.enumId) { + if (oPattern.enumName || oPattern.enumId) { context.isEnums = true; } return subs; }, - getSubscriptions: function () { - const result = {}; + getSubscriptions: function (): Record { + const result: Record = {}; for (let s = 0; s < context.subscriptions.length; s++) { - result[context.subscriptions[s].pattern.id] = result[context.subscriptions[s].pattern.id] || []; - result[context.subscriptions[s].pattern.id].push({ + result[context.subscriptions[s].pattern.id as string] = result[context.subscriptions[s].pattern.id as string] || []; + result[context.subscriptions[s].pattern.id as string].push({ name: context.subscriptions[s].name, pattern: context.subscriptions[s].pattern, }); @@ -1630,8 +1654,8 @@ export default function sandBox( sandbox.verbose && sandbox.log(`getSubscriptions() => ${JSON.stringify(result)}`, 'info'); return result; }, - getFileSubscriptions: function () { - const result = {}; + getFileSubscriptions: function (): Record { + const result: Record = {}; for (let s = 0; s < context.subscriptionsFile.length; s++) { const key = `${context.subscriptionsFile[s].id}$%$${context.subscriptionsFile[s].fileNamePattern}`; result[key] = result[key] || []; @@ -1644,14 +1668,14 @@ export default function sandBox( sandbox.verbose && sandbox.log(`getFileSubscriptions() => ${JSON.stringify(result)}`, 'info'); return result; }, - adapterSubscribe: function (id) { + adapterSubscribe: function (id: string): void { if (typeof id !== 'string') { sandbox.log(`adapterSubscribe: invalid type of id ${typeof id}`, 'error'); return; } const parts = id.split('.'); const _adapter = `system.adapter.${parts[0]}.${parts[1]}`; - if (objects[_adapter] && objects[_adapter].common && objects[_adapter].common.subscribable) { + if (objects[_adapter]?.common?.subscribable) { const a = `${parts[0]}.${parts[1]}`; const alive = `system.adapter.${a}.alive`; context.adapterSubs[alive] = context.adapterSubs[alive] || []; @@ -1660,49 +1684,62 @@ export default function sandBox( adapter.sendTo(a, 'subscribe', id); } }, - adapterUnsubscribe: function (id) { + adapterUnsubscribe: function (idOrObject: string | SubscriptionResult | (string | SubscriptionResult)[]): boolean | boolean[] { + // BF: it could be an error return sandbox.unsubscribe(id); }, - unsubscribe: function (idOrObject) { + unsubscribe: function (idOrObject: string | SubscriptionResult | (string | SubscriptionResult)[]): boolean | boolean[] { if (idOrObject && Array.isArray(idOrObject)) { - const result = []; + const result: boolean[] = []; for (let t = 0; t < idOrObject.length; t++) { - result.push(sandbox.unsubscribe(idOrObject[t])); + result.push(sandbox.unsubscribe(idOrObject[t]) as boolean); } return result; } + sandbox.verbose && sandbox.log(`adapterUnsubscribe(id=${JSON.stringify(idOrObject)})`, 'info'); + if (isObject(idOrObject)) { for (let i = context.subscriptions.length - 1; i >= 0; i--) { if (context.subscriptions[i] === idOrObject) { - unsubscribePattern(script, context.subscriptions[i].pattern.id); + unsubscribePattern(script, context.subscriptions[i].pattern.id as string); context.subscriptions.splice(i, 1); sandbox.__engine.__subscriptions--; return true; } } - } else { - let deleted = 0; - for (let i = context.subscriptions.length - 1; i >= 0; i--) { - if (context.subscriptions[i].name === name && context.subscriptions[i].pattern.id === idOrObject) { - deleted++; - unsubscribePattern(script, context.subscriptions[i].pattern.id); - context.subscriptions.splice(i, 1); - sandbox.__engine.__subscriptions--; - } + return false; + } + let deleted = 0; + for (let i = context.subscriptions.length - 1; i >= 0; i--) { + if (context.subscriptions[i].name === name && context.subscriptions[i].pattern.id === idOrObject) { + deleted++; + unsubscribePattern(script, context.subscriptions[i].pattern.id as string); + context.subscriptions.splice(i, 1); + sandbox.__engine.__subscriptions--; } - return !!deleted; } + return !!deleted; }, - on: function (pattern, callbackOrId, value) { - return sandbox.subscribe(pattern, callbackOrId, value); + on: function ( + pattern: + | TimeRule + | AstroRule + | Pattern + | SchedulerRule + | string + | (TimeRule | AstroRule | Pattern | SchedulerRule | string)[], + callbackOrChangeTypeOrId: string | ChangeType | ((event?: EventObj) => void), + value?: any, + ): SubscriptionResult | IobSchedule | string | null | undefined | (SubscriptionResult | IobSchedule | string | null | undefined)[] { + return sandbox.subscribe(pattern, callbackOrChangeTypeOrId, value); }, - onEnumMembers: function (id, callback) { - if (enums.includes(id)) { - const subscriptions = {}; + onEnumMembers: function (enumId: string, callback: (event?: EventObj) => void): void { + if (enums.includes(enumId)) { + const subscriptions: Record = {}; const init = () => { - const obj = objects[id]; + const obj = objects[enumId]; const common = obj?.common ?? {}; const members = common?.members ?? []; @@ -1719,7 +1756,7 @@ export default function sandBox( if (!Object.keys(subscriptions).includes(objId)) { if (objects?.[objId]?.type === 'state') { // Just subscribe to states - subscriptions[objId] = sandbox.subscribe(objId, callback); // TODO: more features + subscriptions[objId] = sandbox.subscribe(objId, callback) as string | SubscriptionResult; // TODO: more features } } } @@ -1733,18 +1770,20 @@ export default function sandBox( init(); - sandbox.subscribeObject(id, obj => { - obj && init(); - }); + sandbox.subscribeObject(enumId, obj => obj && init()); } else { sandbox.log(`onEnumMembers: enum with id "${id}" doesn't exists`, 'error'); - return; } }, - onFile: function (id: string, fileNamePattern: string | string[], withFile, callback) { - if (typeof withFile === 'function') { - callback = withFile; - withFile = false; + onFile: function ( + id: string, + fileNamePattern: string | string[], + withFileOrCallback: boolean | ((id: string, fileName: string, size: number, file?: string | Buffer, mimeType?: string) => void), + callback?: (id: string, fileName: string, size: number, file?: string | Buffer, mimeType?: string) => void, + ): undefined | FileSubscriptionResult | (undefined | FileSubscriptionResult)[] { + if (typeof withFileOrCallback === 'function') { + callback = withFileOrCallback as (id: string, fileName: string, size: number, file?: string | Buffer, mimeType?: string) => void; + withFileOrCallback = false; } if (!adapter.subscribeForeignFiles) { @@ -1767,7 +1806,7 @@ export default function sandBox( } if (Array.isArray(fileNamePattern)) { - return fileNamePattern.map(filePattern => sandbox.onFile(id, filePattern, withFile, callback)); + return fileNamePattern.map(filePattern => sandbox.onFile(id, filePattern, withFileOrCallback, callback) as undefined | FileSubscriptionResult); } sandbox.__engine.__subscriptionsFile += 1; @@ -1785,8 +1824,8 @@ export default function sandBox( ); } - let idRegEx; - let fileRegEx; + let idRegEx: RegExp | undefined; + let fileRegEx: RegExp | undefined; if (id.includes('*')) { idRegEx = new RegExp(pattern2RegEx(id)); } @@ -1794,13 +1833,13 @@ export default function sandBox( fileRegEx = new RegExp(pattern2RegEx(fileNamePattern)); } - const subs = { + const subs: FileSubscriptionResult = { id, fileNamePattern, - withFile, + withFile: withFileOrCallback as boolean, idRegEx, fileRegEx, - callback: (id, fileName, size, withFile) => { + callback: (id: string, fileName: string, size: number, withFile: boolean): void => { try { sandbox.verbose && sandbox.log(`onFile changed(id=${id}, fileName=${fileName}, size=${size})`, 'info'); @@ -1830,13 +1869,13 @@ export default function sandBox( subscribeFile(script, id, fileNamePattern); return subs; }, - offFile: function (idOrObject, fileNamePattern) { + offFile: function (idOrObject: FileSubscriptionResult | string | (FileSubscriptionResult | string)[], fileNamePattern?: string | string[]): boolean | boolean[] { if (!adapter.unsubscribeForeignFiles) { sandbox.log( 'offFile: your js-controller does not support yet file unsubscribes. Please update to js-controller@4.1.x or newer', 'warn', ); - return; + return false; } sandbox.verbose && @@ -1847,40 +1886,14 @@ export default function sandBox( if (idOrObject && typeof idOrObject === 'object') { if (Array.isArray(idOrObject)) { - const result = []; + const result: boolean[] = []; for (let t = 0; t < idOrObject.length; t++) { - result.push(sandbox.offFile(idOrObject[t])); + result.push(sandbox.offFile(idOrObject[t] as FileSubscriptionResult | string) as boolean); } return result; - } else { - for (let i = context.subscriptionsFile.length - 1; i >= 0; i--) { - if (context.subscriptionsFile[i] === idOrObject) { - unsubscribeFile( - script, - context.subscriptionsFile[i].id, - context.subscriptionsFile[i].fileNamePattern, - ); - - sandbox.verbose && - sandbox.log( - `offFile(type=object, fileNamePattern=${fileNamePattern}, removing id=${context.subscriptionsFile[i].id})`, - 'info', - ); - - context.subscriptionsFile.splice(i, 1); - sandbox.__engine.__subscriptionsFile--; - return true; - } - } } - } else { - let deleted = 0; for (let i = context.subscriptionsFile.length - 1; i >= 0; i--) { - if ( - context.subscriptionsFile[i].id === idOrObject && - context.subscriptionsFile[i].fileNamePattern === fileNamePattern - ) { - deleted++; + if (context.subscriptionsFile[i] === idOrObject) { unsubscribeFile( script, context.subscriptionsFile[i].id, @@ -1889,40 +1902,81 @@ export default function sandBox( sandbox.verbose && sandbox.log( - `offFile(type=string, fileNamePattern=${fileNamePattern}, removing id=${context.subscriptionsFile[i].id})`, + `offFile(type=object, fileNamePattern=${fileNamePattern}, removing id=${context.subscriptionsFile[i].id})`, 'info', ); context.subscriptionsFile.splice(i, 1); sandbox.__engine.__subscriptionsFile--; + return true; } } - return !!deleted; + return false; + } + + if (isArray(fileNamePattern)) { + const result: boolean[] = []; + for (let t = 0; t < fileNamePattern.length; t++) { + result.push(sandbox.offFile(idOrObject, fileNamePattern[t]) as boolean); + } + return result; + } + + let deleted = 0; + for (let i = context.subscriptionsFile.length - 1; i >= 0; i--) { + if ( + context.subscriptionsFile[i].id === idOrObject && + context.subscriptionsFile[i].fileNamePattern === fileNamePattern + ) { + deleted++; + unsubscribeFile( + script, + context.subscriptionsFile[i].id, + context.subscriptionsFile[i].fileNamePattern, + ); + + sandbox.verbose && + sandbox.log( + `offFile(type=string, fileNamePattern=${fileNamePattern}, removing id=${context.subscriptionsFile[i].id})`, + 'info', + ); + + context.subscriptionsFile.splice(i, 1); + sandbox.__engine.__subscriptionsFile--; + } } + return !!deleted; }, /** Registers a one-time subscription which automatically unsubscribes after the first invocation */ - once: function (pattern, callback) { - function _once(cb) { - /** @type {iobJS.StateChangeHandler} */ - const handler = obj => { - sandbox.unsubscribe(subscription); + once: function (pattern: + | TimeRule + | AstroRule + | Pattern + | SchedulerRule + | string + | (TimeRule | AstroRule | Pattern | SchedulerRule | string)[], callback?: (event?: EventObj) => void): string | SubscriptionResult | Promise + { + function _once(cb: (obj?: EventObj) => void): string | SubscriptionResult { + let subscription: string | SubscriptionResult; + const handler = (obj?: EventObj): void => { + subscription && sandbox.unsubscribe(subscription); typeof cb === 'function' && cb(obj); }; - const subscription = sandbox.subscribe(pattern, handler); + subscription = sandbox.subscribe(pattern, handler) as string | SubscriptionResult; return subscription; } if (typeof callback === 'function') { // Callback-style: once("id", (obj) => { ... }) return _once(callback); - } else { - // Promise-style: once("id").then(obj => { ... }) - return new Promise(resolve => _once(resolve)); } + + // Promise-style: once("id").then(obj => { ... }) + return new Promise(resolve => _once(resolve)); }, - schedule: function (pattern: SchedulerRule | string, callback) { + schedule: function (pattern: SchedulerRule | AstroRule | Date | string, callback: () => void): IobSchedule | string | null | undefined { if (typeof callback !== 'function') { sandbox.log(`schedule callback missing`, 'error'); - return; + return null; } if ( @@ -1935,7 +1989,7 @@ export default function sandBox( 'info', ); - const schedule = context.scheduler.add(pattern, sandbox.scriptName, callback); + const schedule: string | null = context.scheduler.add(pattern as SchedulerRule | string, sandbox.scriptName, callback); if (schedule) { script.wizards.push(schedule); sandbox.__engine.__schedules += 1; @@ -1951,7 +2005,8 @@ export default function sandBox( return schedule; } - if (typeof pattern === 'object' && pattern.astro) { + if (typeof pattern === 'object' && (pattern as AstroRule).astro) { + const astroPattern = pattern as AstroRule; const nowdate = new Date(); if ( @@ -1963,7 +2018,7 @@ export default function sandBox( (adapter.config as AdapterConfig).longitude === null ) { sandbox.log('Longitude or latitude does not set. Cannot use astro.', 'error'); - return; + return null; } // ensure events are calculated independent of current time @@ -1974,31 +2029,31 @@ export default function sandBox( todayNoon, (adapter.config as AdapterConfig).latitude, (adapter.config as AdapterConfig).longitude, - )[pattern.astro]; + )[astroPattern.astro]; - // event on next day, correct or force recalculation at midnight + // event on the next day, correct or force recalculation at midnight if (todayNoon.getDate() !== ts.getDate()) { todayNoon.setDate(todayNoon.getDate() - 1); ts = mods.suncalc.getTimes( todayNoon, (adapter.config as AdapterConfig).latitude, (adapter.config as AdapterConfig).longitude, - )[pattern.astro]; + )[astroPattern.astro]; } if (ts.getTime().toString() === 'NaN') { sandbox.log( - `Cannot calculate "${pattern.astro}" for ${(adapter.config as AdapterConfig).latitude}, ${(adapter.config as AdapterConfig).longitude}`, + `Cannot calculate "${astroPattern.astro}" for ${(adapter.config as AdapterConfig).latitude}, ${(adapter.config as AdapterConfig).longitude}`, 'warn', ); ts = new Date(nowdate.getTime()); if ( - pattern.astro === 'sunriseEnd' || - pattern.astro === 'goldenHourEnd' || - pattern.astro === 'sunset' || - pattern.astro === 'nightEnd' || - pattern.astro === 'nauticalDusk' + astroPattern.astro === 'sunriseEnd' || + astroPattern.astro === 'goldenHourEnd' || + astroPattern.astro === 'sunset' || + astroPattern.astro === 'nightEnd' || + astroPattern.astro === 'nauticalDusk' ) { ts.setHours(23); ts.setMinutes(59); @@ -2010,8 +2065,8 @@ export default function sandBox( } } - if (ts && pattern.shift) { - ts = new Date(ts.getTime() + pattern.shift * 60000); + if (ts && astroPattern.shift) { + ts = new Date(ts.getTime() + astroPattern.shift * 60000); } if (!ts || ts < nowdate) { @@ -2035,7 +2090,7 @@ export default function sandBox( sandbox.verbose && sandbox.log( - `schedule(astro=${pattern.astro}, offset=${pattern.shift}) is tomorrow, waiting until ${date.toISOString()}`, + `schedule(astro=${astroPattern.astro}, offset=${astroPattern.shift}) is tomorrow, waiting until ${date.toISOString()}`, 'info', ); @@ -2044,7 +2099,7 @@ export default function sandBox( if (sandbox.__engine.__schedules > 0) { sandbox.__engine.__schedules--; } - sandbox.schedule(pattern, callback); + sandbox.schedule(astroPattern, callback); }, date.getTime() - Date.now()); return; @@ -2070,18 +2125,19 @@ export default function sandBox( if (sandbox.__engine.__schedules > 0) { sandbox.__engine.__schedules--; } - sandbox.schedule(pattern, callback); + sandbox.schedule(astroPattern, callback); }, 2000); }, ts.getTime() - Date.now()); sandbox.verbose && sandbox.log( - `schedule(astro=${pattern.astro}, offset=${pattern.shift}) is today, waiting until ${ts.toISOString()}`, + `schedule(astro=${astroPattern.astro}, offset=${astroPattern.shift}) is today, waiting until ${ts.toISOString()}`, 'info', ); } else { // fix a problem with sunday and 7 if (typeof pattern === 'string') { + // this could be a CRON const parts = pattern.replace(/\s+/g, ' ').split(' '); if (parts.length >= 5 && parseInt(parts[5], 10) >= 7) { parts[5] = '0'; @@ -2090,11 +2146,11 @@ export default function sandBox( } // created in VM the date object: pattern instanceof Date => false // so fix it - if (typeof pattern === 'object' && pattern.getDate) { - pattern = new Date(pattern); + if (typeof pattern === 'object' && (pattern as Date).getDate) { + pattern = new Date(pattern as Date); } - const schedule = mods.nodeSchedule.scheduleJob(pattern, () => { + const schedule: IobSchedule = mods.nodeSchedule.scheduleJob(pattern, (): void => { try { callback.call(sandbox); } catch (e) { @@ -2113,7 +2169,7 @@ export default function sandBox( schedule._ioBroker = { type: 'cron', - pattern, + pattern: pattern as string | Date, scriptName: sandbox.scriptName, id: `cron_${Date.now()}_${Math.round(Math.random() * 100000)}`, }; @@ -2128,9 +2184,9 @@ export default function sandBox( return schedule; } }, - scheduleById: function (id, ack, callback) { - let schedulId = null; - let currentExp = null; // current cron expression + scheduleById: function (id: string, ack: boolean | (() => void) | undefined, callback?: () => void): void { + let scheduleId: IobSchedule | string | null | undefined = null; + let currentExp: string | null = null; // current cron expression if (typeof ack === 'function') { callback = ack; @@ -2140,23 +2196,20 @@ export default function sandBox( const rhms = /^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$/; // hh:mm:ss const rhm = /^(2[0-3]|[01]?[0-9]):([0-5]?[0-9])$/; // hh:mm - const init = time => { + const init = (time: string): void => { if (typeof time === 'string') { - let h = undefined; - let m = undefined; - let s = undefined; + let h: number | undefined = undefined; + let m: number | undefined = undefined; + let s: number | undefined = undefined; + let isValid = false; if (rhms.test(time)) { - [h, m, s] = time - .match(rhms) - .slice(1) + [h, m, s] = time.match(rhms)?.slice(1) .map(v => parseInt(v)); isValid = true; } else if (rhm.test(time)) { - [h, m] = time - .match(rhm) - .slice(1) + [h, m] = time.match(rhm)?.slice(1) .map(v => parseInt(v)); isValid = true; } @@ -2172,12 +2225,12 @@ export default function sandBox( ); currentExp = cronExp; - if (schedulId) { - sandbox.clearSchedule(schedulId); - schedulId = null; + if (scheduleId) { + sandbox.clearSchedule(scheduleId); + scheduleId = null; } - schedulId = sandbox.schedule(cronExp, () => { + scheduleId = sandbox.schedule(cronExp, () => { if (typeof callback === 'function') { try { callback.call(sandbox); @@ -2202,26 +2255,26 @@ export default function sandBox( }; sandbox.getState(id, (err, state) => { - if (!err && state && state.val) { + if (!err && state?.val) { sandbox.verbose && sandbox.log(`scheduleById(id=${id}): Init with value ${state.val}`, 'info'); - init(state.val); + init(state.val.toString()); } }); - const triggerDef = { id, change: 'any' }; + const triggerDef: Pattern = { id, change: 'any' }; if (ack !== undefined) { - triggerDef.ack = ack; + triggerDef.ack = ack as boolean; } sandbox.on(triggerDef, obj => { - if (obj && obj.state.val) { + if (obj?.state?.val) { sandbox.verbose && sandbox.log(`scheduleById(id=${id}): Update with value ${obj.state.val}`, 'info'); - init(obj.state.val); + init(obj.state.val.toString()); } }); }, - getAstroDate: function (pattern, date, offsetMinutes) { + getAstroDate: function (pattern: AstroEvent, date?: Date | number, offsetMinutes?: number): Date | undefined { if (date === undefined) { date = new Date(); } @@ -2268,7 +2321,7 @@ export default function sandBox( } return ts; }, - isAstroDay: function () { + isAstroDay: function (): boolean | undefined { const nowDate = new Date(); const dayBegin = sandbox.getAstroDate('sunrise'); const dayEnd = sandbox.getAstroDate('sunset'); @@ -2293,23 +2346,10 @@ export default function sandBox( } context.scheduler.remove(schedule); return true; - } else { - for (let i = 0; i < script.schedules.length; i++) { - if (schedule && typeof schedule === 'object' && schedule.type === 'cron') { - if (script.schedules[i]._ioBroker && script.schedules[i]._ioBroker.id === schedule.id) { - if (!mods.nodeSchedule.cancelJob(script.schedules[i])) { - sandbox.log('Error by canceling scheduled job', 'error'); - } - delete script.schedules[i]; - script.schedules.splice(i, 1); - if (sandbox.__engine.__schedules > 0) { - sandbox.__engine.__schedules--; - } - - sandbox.verbose && sandbox.log('clearSchedule() => cleared', 'info'); - return true; - } - } else if (script.schedules[i] === schedule) { + } + for (let i = 0; i < script.schedules.length; i++) { + if (schedule && typeof schedule === 'object' && schedule.type === 'cron') { + if (script.schedules[i]._ioBroker && script.schedules[i]._ioBroker.id === schedule.id) { if (!mods.nodeSchedule.cancelJob(script.schedules[i])) { sandbox.log('Error by canceling scheduled job', 'error'); } @@ -2322,6 +2362,18 @@ export default function sandBox( sandbox.verbose && sandbox.log('clearSchedule() => cleared', 'info'); return true; } + } else if (script.schedules[i] === schedule) { + if (!mods.nodeSchedule.cancelJob(script.schedules[i])) { + sandbox.log('Error by canceling scheduled job', 'error'); + } + delete script.schedules[i]; + script.schedules.splice(i, 1); + if (sandbox.__engine.__schedules > 0) { + sandbox.__engine.__schedules--; + } + + sandbox.verbose && sandbox.log('clearSchedule() => cleared', 'info'); + return true; } } diff --git a/src/lib/scheduler.ts b/src/lib/scheduler.ts index f21a04ba..16d75c5f 100644 --- a/src/lib/scheduler.ts +++ b/src/lib/scheduler.ts @@ -510,76 +510,79 @@ export default class Scheduler { return date && date.y === context.y && date.M === context.M && date.d === context.d; } - add(schedule: SchedulerRule, scriptName: string, cb: (id: string) => void): string | null { + add(schedule: SchedulerRule | string, scriptName: string, cb: (id: string) => void): string | null { + let oSchedule: SchedulerRule; if (typeof schedule === 'string') { try { - schedule = JSON.parse(schedule); + oSchedule = JSON.parse(schedule); } catch (e) { this.log.error(`Cannot parse schedule: ${schedule}`); return null; } + } else { + oSchedule = schedule; } const id = this._getId(); - if (typeof schedule !== 'object' || !schedule.period) { - this.log.error(`Invalid schedule structure: ${JSON.stringify(schedule)}`); + if (typeof oSchedule !== 'object' || !oSchedule.period) { + this.log.error(`Invalid schedule structure: ${JSON.stringify(oSchedule)}`); return null; } const context = this.getContext(); - const sch: SchedulerRuleParsed = JSON.parse(JSON.stringify(schedule)); + const sch: SchedulerRuleParsed = JSON.parse(JSON.stringify(oSchedule)); sch.scriptName = scriptName; - sch.original = JSON.stringify(schedule); + sch.original = JSON.stringify(oSchedule); sch.id = id; sch.cb = cb; - if (schedule.time?.start) { - const astroNameStart = this._getAstroName(schedule.time.start); + if (oSchedule.time?.start) { + const astroNameStart = this._getAstroName(oSchedule.time.start); if (astroNameStart && sch.time) { sch.time.start = astroNameStart; - } else if (schedule.time.start.includes(':')) { - const parts = schedule.time.start.split(':'); + } else if (oSchedule.time.start.includes(':')) { + const parts = oSchedule.time.start.split(':'); if (sch.time) { sch.time.start = parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10); } } else { - this.log.error(`unknown astro event "${schedule.time.start}"`); + this.log.error(`unknown astro event "${oSchedule.time.start}"`); return null; } } - if (schedule.time?.end) { - const astroNameEnd = this._getAstroName(schedule.time.end); + if (oSchedule.time?.end) { + const astroNameEnd = this._getAstroName(oSchedule.time.end); if (astroNameEnd && sch.time) { sch.time.end = astroNameEnd; - } else if (schedule.time.end.includes(':')) { - const parts = schedule.time.end.split(':'); + } else if (oSchedule.time.end.includes(':')) { + const parts = oSchedule.time.end.split(':'); if (sch.time) { sch.time.end = parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10); } } else { - this.log.error(`unknown astro event "${schedule.time.end}"`); + this.log.error(`unknown astro event "${oSchedule.time.end}"`); return null; } } if (sch.time) { - if (schedule.time.mode === 'minutes') { + if (oSchedule.time.mode === 'minutes') { sch.time.mode = 60; - } else if (schedule.time.mode === 'hours') { + } else if (oSchedule.time.mode === 'hours') { sch.time.mode = 3600; } } - if (schedule.period.once) { - sch.period.once = this.string2date(schedule.period.once); + if (oSchedule.period.once) { + sch.period.once = this.string2date(oSchedule.period.once); } - if (schedule.valid && sch.valid) { - if (schedule.valid.from) { - sch.valid.from = this.string2date(schedule.valid.from); + if (oSchedule.valid && sch.valid) { + if (oSchedule.valid.from) { + sch.valid.from = this.string2date(oSchedule.valid.from); } - if (schedule.valid.to) { - sch.valid.to = this.string2date(schedule.valid.to); + if (oSchedule.valid.to) { + sch.valid.to = this.string2date(oSchedule.valid.to); } if (this.isPast(context, sch.valid.to)) { @@ -587,20 +590,20 @@ export default class Scheduler { return null; } - if (typeof schedule.period.days === 'number' && schedule.period.days > 1 && sch.valid.from) { + if (typeof oSchedule.period.days === 'number' && oSchedule.period.days > 1 && sch.valid.from) { // fromDate must be unix time sch.valid.fromDate = new this.Date(sch.valid.from.y, sch.valid.from.M, sch.valid.from.d).getTime(); - } else if (typeof schedule.period.weeks === 'number' && schedule.period.weeks > 1) { + } else if (typeof oSchedule.period.weeks === 'number' && oSchedule.period.weeks > 1) { const fromDate: Date = sch.valid.from ? new this.Date(sch.valid.from.y, sch.valid.from.M, sch.valid.from.d) : new this.Date(); //sch.valid.fromDate.setDate(-sch.valid.fromDate.getDate() - sch.valid.fromDate.getDay()); // fromDate must be unix time sch.valid.fromDate = fromDate.getTime(); - } else if (sch.valid.from && typeof schedule.period.months === 'number' && schedule.period.months > 1) { + } else if (sch.valid.from && typeof oSchedule.period.months === 'number' && oSchedule.period.months > 1) { // fromDate must be object sch.valid.fromDate = new this.Date(sch.valid.from.y, sch.valid.from.M, sch.valid.from.d); - } else if (sch.valid.from && typeof schedule.period.years === 'number' && schedule.period.years > 1) { + } else if (sch.valid.from && typeof oSchedule.period.years === 'number' && oSchedule.period.years > 1) { // fromDate must be object sch.valid.fromDate = new this.Date(sch.valid.from.y, sch.valid.from.M, sch.valid.from.d); } @@ -619,9 +622,9 @@ export default class Scheduler { return null; } - if (schedule.period.dows) { + if (oSchedule.period.dows) { try { - sch.period.dows = JSON.parse(schedule.period.dows); + sch.period.dows = JSON.parse(oSchedule.period.dows); } catch (e) { this.log.error(`Cannot parse day of weeks: ${sch.period.dows}`); return null; @@ -632,10 +635,10 @@ export default class Scheduler { } } - if (schedule.period.months && typeof schedule.period.months !== 'number') { + if (oSchedule.period.months && typeof oSchedule.period.months !== 'number') { // can be number or array-string try { - sch.period.months = JSON.parse(schedule.period.months) as number[]; + sch.period.months = JSON.parse(oSchedule.period.months) as number[]; } catch (e) { this.log.error(`Cannot parse day of months: ${sch.period.months}`); return null; @@ -643,16 +646,16 @@ export default class Scheduler { sch.period.months = sch.period.months.map(m => m - 1); } - if (schedule.period.dates) { + if (oSchedule.period.dates) { try { - sch.period.dates = JSON.parse(schedule.period.dates) as number[]; + sch.period.dates = JSON.parse(oSchedule.period.dates) as number[]; } catch (e) { this.log.error(`Cannot parse day of dates: ${sch.period.dates}`); return null; } } - if (schedule.period.yearMonth) { - sch.period.yearMonth = schedule.period.yearMonth - 1; + if (oSchedule.period.yearMonth) { + sch.period.yearMonth = oSchedule.period.yearMonth - 1; } this.list[id] = sch; this.recalculate(); diff --git a/src/types.d.ts b/src/types.d.ts index e70b13d2..6a183e57 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,6 +1,10 @@ import Scheduler, { SchedulerRule } from './lib/scheduler'; import { ExecOptions } from 'node:child_process'; -import { ResponseType } from 'axios'; +import { AxiosHeaderValue, ResponseType } from 'axios'; +import type { Job } from 'node-schedule'; +import { EventObj } from './lib/eventObj'; +import type { PatternEventCompareFunction } from './lib/patternCompareFunctions'; +import { AstroEvent } from './lib/consts'; export interface AdapterConfig { latitude: string; @@ -40,6 +44,12 @@ export type JavascriptTimer = { export type ScriptType = 'TypeScript/ts' | 'Blockly' | 'Rules' | 'JavaScript/js'; +export type TimeRule = { + time: string | { hour: number; minute: number }; +}; + +export type IobSchedule = Job & { _ioBroker: { type: 'cron'; pattern: string | Date; scriptName: string; id: string } }; + export type PushoverOptions = { message: string; // mandatory - your text message title?: string; // optional - your message's title, otherwise your app's name is used @@ -81,15 +91,16 @@ export type PushoverOptions = { timestamp?: number; // optional - a Unix timestamp of your message's date and time to display to the user, rather than the time your message is received by our API html?: string; // optional - 1 to enable parsing of HTML formatting for bold, italic, underlined and font color monospace?: 1; // optional - 1 to display the message in monospace font - // either html or monospace is allowed + // either HTML or monospace is allowed file?: string | { name: string; data: Buffer }; // optional - attachment }; + export interface JsScript { onStopTimeout: number; onStopCb: () => void; intervals: NodeJS.Timeout[]; timeouts: NodeJS.Timeout[]; - schedules: string[]; + schedules: IobSchedule[]; wizards: string[]; name: string; engineType: ScriptType; @@ -100,6 +111,16 @@ export interface JsScript { setStatePerMinuteProblemCounter: number; } +export type LogMessage = any; + +export type AstroRule = { + astro: AstroEvent; + shift: number; + limitStart: string; + limitEnd: string; + event: string; +}; + export type SandboxType = { mods: Record; _id: string; @@ -121,7 +142,7 @@ export type SandboxType = { __schedules: number; }; $: (selector: string) => any; - log: (msg: string, severity?: string) => void; + log: (msg: string, severity?: ioBroker.LogLevel) => void; onLog: (severity: ioBroker.LogLevel, callback: (info: any) => void) => number; onLogUnregister: (idOrCallbackOrSeverity: string | ((msg: string) => void)) => void; exec: ( @@ -178,7 +199,7 @@ export type SandboxType = { result: { statusCode: number | null; data: any; - headers: Record; + headers: Record; responseTime: number; }, ) => void), @@ -187,49 +208,87 @@ export type SandboxType = { result: { statusCode: number | null; data: any; - headers: Record; + headers: Record; responseTime: number; }, ) => void, ) => void; createTempFile: (fileName: string, data: Buffer | string) => string | undefined; - subscribe: (pattern: string, callbackOrId: string | ((data: any) => void), value?: any) => void; - getSubscriptions: () => any[]; - getFileSubscriptions: () => any[]; + subscribe: ( + pattern: + | TimeRule + | AstroRule + | Pattern + | SchedulerRule + | string + | (TimeRule | AstroRule | Pattern | SchedulerRule | string)[], + callbackOrChangeTypeOrId: string | ChangeType | ((event?: EventObj) => void), + value?: any, + ) => + | SubscriptionResult + | IobSchedule + | string + | null + | undefined + | (SubscriptionResult | IobSchedule | string | null | undefined)[]; + getSubscriptions: () => Record; + getFileSubscriptions: () => Record; adapterSubscribe: (id: string) => void; - adapterUnsubscribe: (id: string) => void; - unsubscribe: (idOrObject: string | Record) => void; + adapterUnsubscribe: ( + idOrObject: string | SubscriptionResult | (string | SubscriptionResult)[], + ) => boolean | boolean[]; + unsubscribe: (idOrObject: string | SubscriptionResult | (string | SubscriptionResult)[]) => boolean | boolean[]; on: ( - pattern: SchedulerRule | string | (SchedulerRule | string)[], - callbackOrId: string | ((id: string) => void), + pattern: + | TimeRule + | AstroRule + | Pattern + | SchedulerRule + | string + | (TimeRule | AstroRule | Pattern | SchedulerRule | string)[], + callbackOrChangeTypeOrId: string | ChangeType | ((event?: EventObj) => void), value?: any, - ) => void; - onEnumMembers: (id: string, callback: (err: Error | null, result: any) => void) => void; + ) => + | SubscriptionResult + | IobSchedule + | string + | null + | undefined + | (SubscriptionResult | IobSchedule | string | null | undefined)[]; + onEnumMembers: (enumId: string, callback: (event?: EventObj) => void) => void; onFile: ( id: string, fileNamePattern: string | string[], - withFile: boolean, - callback: (err: Error | null, result: any) => void, - ) => void; - offFile: (idOrObject: string | Record, fileNamePattern: string) => void; + withFileOrCallback: + | boolean + | ((id: string, fileName: string, size: number, file?: string | Buffer, mimeType?: string) => void), + callback?: (id: string, fileName: string, size: number, file?: string | Buffer, mimeType?: string) => void, + ) => undefined | FileSubscriptionResult | (undefined | FileSubscriptionResult)[]; + offFile: ( + idOrObject: FileSubscriptionResult | string | (FileSubscriptionResult | string)[], + fileNamePattern?: string | string[], + ) => boolean | boolean[]; once: (pattern: string, callback: (data: any) => void) => void; - schedule: (pattern: SchedulerRule | string | (SchedulerRule | string)[], callback: (id: string) => void) => void; - scheduleById: (id: string, ack: boolean, callback: (data: any) => void) => void; - getAstroDate: (pattern: string, date: Date, offsetMinutes: number) => Date; - isAstroDay: () => boolean; + schedule: ( + pattern: SchedulerRule | AstroRule | Date | string, + callback: () => void, + ) => IobSchedule | string | null | undefined; + scheduleById: (id: string, ack: boolean | (() => void) | undefined, callback?: () => void) => void; + getAstroDate: (pattern: AstroEvent, date?: Date | number, offsetMinutes?: number) => Date | undefined; + isAstroDay: () => boolean | undefined; clearSchedule: (schedule: any) => void; getSchedules: (allScripts: boolean) => any[]; setState: ( id: string, state: ioBroker.SettableState | ioBroker.StateValue, - isAck: boolean, - callback: (err: Error | null) => void, + isAck?: boolean, + callback?: (err: Error | null) => void, ) => void; setStateChanged: ( id: string, state: ioBroker.SettableState | ioBroker.StateValue, - isAck: boolean, - callback: (err: Error | null) => void, + isAck?: boolean, + callback?: (err: Error | null) => void, ) => void; setStateDelayed: ( id: string, @@ -351,13 +410,15 @@ export type SandboxType = { logHandler?: ioBroker.LogLevel | '*'; }; +export type ChangeType = 'eq' | 'ne' | 'gt' | 'ge' | 'lt' | 'le' | 'any' | '*'; + export type Pattern = { logic?: 'and' | 'or'; id?: RegExp | string | string[]; name?: RegExp | string | string[]; ack?: boolean | string; oldAck?: boolean | string; - change?: 'eq' | 'ne' | 'gt' | 'ge' | 'lt' | 'le' | 'any' | '*'; + change?: ChangeType; q?: '*' | number; oldQ?: '*' | number; @@ -410,6 +471,24 @@ export type Selector = { idRegExp?: RegExp; }; +export type SubscriptionResult = { + name: string; + pattern: Pattern; + options?: Record; + patternCompareFunctions?: PatternEventCompareFunction[] & { logic?: 'and' | 'or' }; + callback: (obj: EventObj) => void; +}; + +export type FileSubscriptionResult = { + id: string; + name: string; + fileNamePattern: string; + idRegEx: RegExp | undefined; + fileRegEx: RegExp | undefined; + withFile: boolean; + callback: (id: string, fileName: string, size: number, withFile: boolean) => void; +}; + export interface JavascriptContext { adapter: ioBroker.Adapter; mods: Record; @@ -418,8 +497,8 @@ export interface JavascriptContext { interimStateValues: Record; stateIds: string[]; errorLogFunction: (text: string, ...args: any[]) => void; - subscriptions: { name: string; pattern: string; options: Record }[]; - subscriptionsFile: { name: string; pattern: string; options: Record }[]; + subscriptions: SubscriptionResult[]; + subscriptionsFile: FileSubscriptionResult[]; subscriptionsObject: { name: string; pattern: string; options: Record }[]; subscribedPatterns: Record; subscribedPatternsFile: Record; @@ -437,7 +516,7 @@ export interface JavascriptContext { messageBusHandlers: Record void }>>; logSubscriptions: { sandbox: SandboxType; - cb: (info: any) => void; + cb: (info: LogMessage) => void; id: string; severity: ioBroker.LogLevel | '*'; }[];