From 7e2525cad559a857ddb4d6d0f9fc9fb4c501422c Mon Sep 17 00:00:00 2001 From: GermanBluefox Date: Tue, 12 Nov 2024 16:31:04 +0000 Subject: [PATCH] Working on typescript --- package.json | 4 +- src/lib/javascript.d.ts | 138 ++-- src/lib/mirror.ts | 2 +- src/lib/protectFs.ts | 194 +++-- src/lib/sandbox.ts | 1493 +++++++++++++++++++++++++-------------- src/lib/scheduler.ts | 4 +- main.js => src/main.ts | 943 +++++++++++++------------ src/types.d.ts | 98 ++- tsconfig.json | 5 +- 9 files changed, 1684 insertions(+), 1197 deletions(-) rename main.js => src/main.ts (80%) diff --git a/package.json b/package.json index 93675e44..835f5c61 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "bugs": { "url": "https://github.com/ioBroker/ioBroker.javascript/issues" }, - "main": "main.js", + "main": "src/main.ts", "files": [ "admin/", "lib/", @@ -87,7 +87,7 @@ "lib/", "io-package.json", "LICENSE", - "main.js", + "src/main.ts", "admin/vsFont/codicon.json" ], "scripts": { diff --git a/src/lib/javascript.d.ts b/src/lib/javascript.d.ts index 5af3a1c5..93437455 100644 --- a/src/lib/javascript.d.ts +++ b/src/lib/javascript.d.ts @@ -1,11 +1,10 @@ // import all modules that are available in the sandbox // this has a nice side effect that we may augment the global scope -import * as child_process from 'child_process'; import * as os from 'os'; type EmptyCallback = () => void | Promise; -type ErrorCallback = (err?: string) => void | Promise; -type GenericCallback = (err?: string | null, result?: T) => void | Promise; +type ErrorCallback = (err?: Error) => void | Promise; +type GenericCallback = (err?: Error | null, result?: T) => void | Promise; type SimpleCallback = (result?: T) => void | Promise; type MessageCallback = (data: T, callback: iobJS.MessageCallback) => void | Promise; type LogCallback = (msg: any) => void | Promise; @@ -78,41 +77,13 @@ declare global { sensor_reports_error = 0x84, } - type PrimitiveTypeStateValue = string | number | boolean; - type StateValue = null | PrimitiveTypeStateValue | PrimitiveTypeStateValue[] | Record; + type SettableState = AtLeastOne; - interface State { - /** The value of the state. */ + interface TypedState extends ioBroker.State { val: T; - - /** Direction flag: false for desired value and true for actual value. Default: false. */ - ack: boolean; - - /** Unix timestamp. Default: current time */ - ts: number; - - /** Unix timestamp of the last time the value changed */ - lc: number; - - /** Name of the adapter instance which set the value, e.g. "system.adapter.web.0" */ - from: string; - - /** Optional time in seconds after which the state is reset to null */ - expire?: number; - - /** Optional quality of the state value */ - q?: StateQuality; - - /** Optional comment */ - c?: string; - - /** Discriminant property to switch between AbsentState and State */ - notExist: undefined; } - type SettableState = AtLeastOne; - - interface AbsentState { + interface AbsentState extends ioBroker.State { val: null; notExist: true; @@ -862,8 +833,7 @@ declare global { type SettableOtherObject = SettableObject; /** Represents the change of a state */ - interface ChangedStateObject - extends StateObject { + interface ChangedStateObject extends StateObject { common: StateCommon; native: Record; id?: string; @@ -877,11 +847,11 @@ declare global { /** The names of enums this state is assigned to. For example ["Licht","Garten"] */ enumNames?: Array; /** new state */ - state: State; + state: TypedState; /** @deprecated Use state instead **/ - newState: State; + newState: TypedState; /** previous state */ - oldState: State; + oldState: TypedState; /** Name of the adapter instance which set the value, e.g. "system.adapter.web.0" */ from?: string; /** Unix timestamp. Default: current time */ @@ -892,16 +862,16 @@ declare global { ack?: boolean; } - type GetStateCallback = ( - err?: string | null, - state?: State | AbsentState, + type GetStateCallback = ( + err?: Error | null, + state?: TypedState | AbsentState, ) => void | Promise; - type ExistsStateCallback = (err?: string | null, exists?: Boolean) => void | Promise; + type ExistsStateCallback = (err?: Error | null, exists?: Boolean) => void | Promise; - type SetStateCallback = (err?: string | null, id?: string) => void | Promise; + type SetStateCallback = (err?: Error | null, id?: string) => void | Promise; type SetStatePromise = Promise>; - type StateChangeHandler = ( + type StateChangeHandler = ( obj: ChangedStateObject, ) => void | Promise; type ObjectChangeHandler = (id: string, obj: iobJS.Object) => void | Promise; @@ -934,11 +904,11 @@ declare global { mimeType?: string, ) => void | Promise; - type SetObjectCallback = (err?: string | null, obj?: { id: string }) => void | Promise; + type SetObjectCallback = (err?: Error | null, obj?: { id: string }) => void | Promise; type SetObjectPromise = Promise>; type GetObjectCallback = ( - err?: string | null, + err?: Error | null, obj?: ObjectIdToObjectType | null, ) => void; type GetObjectPromise = Promise>>; @@ -946,7 +916,7 @@ declare global { type LogLevel = 'silly' | 'debug' | 'info' | 'warn' | 'error' | 'force'; type ReadFileCallback = ( - err?: string | null, + err?: Error | null, file?: Buffer | string, mimeType?: string, ) => void | Promise; @@ -984,9 +954,9 @@ declare global { name?: string | string[] | RegExp; /** type of change */ change?: 'eq' | 'ne' | 'gt' | 'ge' | 'lt' | 'le' | 'any'; - val?: StateValue; + val?: ioBroker.StateValue; /** New value must not be equal to given one */ - valNe?: StateValue; + valNe?: ioBroker.StateValue; /** New value must be greater than given one */ valGt?: number; /** New value must be greater or equal to given one */ @@ -998,9 +968,9 @@ declare global { /** Acknowledged state of new value is equal to given one */ ack?: boolean; /** Previous value must be equal to given one */ - oldVal?: StateValue; + oldVal?: ioBroker.StateValue; /** Previous value must be not equal to given one */ - oldValNe?: StateValue; + oldValNe?: ioBroker.StateValue; /** Previous value must be greater than given one */ oldValGt?: number; /** Previous value must be greater or equal given one */ @@ -1098,15 +1068,15 @@ declare global { * this can be called synchronously and immediately returns the state. * Otherwise, you need to provide a callback. */ - getState(callback: GetStateCallback): void; - getState(): State | null | undefined; - getStateAsync(): Promise | null | undefined>; + getState(callback: GetStateCallback): void; + getState(): TypedState | null | undefined; + getStateAsync(): Promise | null | undefined>; /** * Sets all queried states to the given value. */ - setState(state: State | StateValue | SettableState, ack?: boolean, callback?: SetStateCallback): this; - setStateAsync(state: State | StateValue | SettableState, ack?: boolean): Promise; + setState(state: ioBroker.StateValue | ioBroker.SettableState, ack?: boolean | 'true' | 'false' | SetStateCallback, callback?: SetStateCallback): this; + setStateAsync(state: ioBroker.StateValue | ioBroker.SettableState, ack?: boolean | 'true' | 'false'): Promise; setStateDelayed( state: any, isAck?: boolean, @@ -1119,11 +1089,11 @@ declare global { * Sets all queried states to the given value only if the value really changed. */ setStateChanged( - state: State | StateValue | SettableState, + state: ioBroker.StateValue | ioBroker.SettableState, ack?: boolean, callback?: SetStateCallback, ): this; - setStateChangedAsync(state: State | StateValue | SettableState, ack?: boolean): Promise; + setStateChangedAsync(state: ioBroker.StateValue | ioBroker.SettableState, ack?: boolean): Promise; /** * Subscribes the given callback to changes of the matched states. @@ -1302,7 +1272,7 @@ declare global { validateCertificate?: boolean; } - type HttpResponseCallback = (err?: string | null, response?: iobJS.httpResponse) => void | Promise; + type HttpResponseCallback = (err?: Error | null, response?: iobJS.httpResponse) => void | Promise; interface httpResponse { statusCode: number; data: string; @@ -1562,19 +1532,19 @@ declare global { */ function setState( id: string, - state: iobJS.State | iobJS.StateValue | iobJS.SettableState, + state:ioBroker.StateValue | ioBroker.SettableState, callback?: iobJS.SetStateCallback, ): void; function setState( id: string, - state: iobJS.State | iobJS.StateValue | iobJS.SettableState, + state: ioBroker.StateValue | ioBroker.SettableState, ack: boolean, callback?: iobJS.SetStateCallback, ): void; function setStateAsync( id: string, - state: iobJS.State | iobJS.StateValue | iobJS.SettableState, + state: ioBroker.StateValue | ioBroker.SettableState, ack?: boolean, ): iobJS.SetStatePromise; @@ -1584,19 +1554,19 @@ declare global { */ function setStateChanged( id: string, - state: iobJS.State | iobJS.StateValue | iobJS.SettableState, + state: ioBroker.StateValue | ioBroker.SettableState, callback?: iobJS.SetStateCallback, ): void; function setStateChanged( id: string, - state: iobJS.State | iobJS.StateValue | iobJS.SettableState, + state: ioBroker.StateValue | ioBroker.SettableState, ack: boolean, callback?: iobJS.SetStateCallback, ): void; function setStateChangedAsync( id: string, - state: iobJS.State | iobJS.StateValue | iobJS.SettableState, + state: ioBroker.StateValue | ioBroker.SettableState, ack?: boolean, ): iobJS.SetStatePromise; @@ -1610,39 +1580,39 @@ declare global { */ function setStateDelayed( id: string, - state: iobJS.State | iobJS.StateValue | iobJS.SettableState, + state: ioBroker.StateValue | ioBroker.SettableState, delay: number, clearRunning: boolean, callback?: iobJS.SetStateCallback, ): number | null; function setStateDelayed( id: string, - state: iobJS.State | iobJS.StateValue | iobJS.SettableState, + state: ioBroker.StateValue | ioBroker.SettableState, ack: boolean, clearRunning: boolean, callback?: iobJS.SetStateCallback, ): number | null; function setStateDelayed( id: string, - state: iobJS.State | iobJS.StateValue | iobJS.SettableState, + state: ioBroker.StateValue | ioBroker.SettableState, ack: boolean, delay: number, callback?: iobJS.SetStateCallback, ): number | null; function setStateDelayed( id: string, - state: iobJS.State | iobJS.StateValue | iobJS.SettableState, + state: ioBroker.StateValue | ioBroker.SettableState, delay: number, callback?: iobJS.SetStateCallback, ): number | null; function setStateDelayed( id: string, - state: iobJS.State | iobJS.StateValue | iobJS.SettableState, + state: ioBroker.StateValue | ioBroker.SettableState, callback?: iobJS.SetStateCallback, ): number | null; function setStateDelayed( id: string, - state: iobJS.State | iobJS.StateValue | iobJS.SettableState, + state: ioBroker.StateValue | ioBroker.SettableState, ack: boolean, delay: number, clearRunning: boolean, @@ -1673,9 +1643,9 @@ declare global { * this can be called synchronously and immediately returns the state. * Otherwise, you need to provide a callback. */ - function getState(id: string, callback: iobJS.GetStateCallback): void; - function getState(id: string): iobJS.State | iobJS.AbsentState; - function getStateAsync(id: string): Promise>; + function getState(id: string, callback: iobJS.GetStateCallback): void; + function getState(id: string): iobJS.TypedState | iobJS.AbsentState; + function getStateAsync(id: string): Promise>; /** * Checks if the state with the given ID exists @@ -1729,23 +1699,23 @@ declare global { * @param callback (optional) Called after the state was created */ function createState(name: string, callback?: iobJS.SetStateCallback): void; - function createState(name: string, initValue: iobJS.StateValue, callback?: iobJS.SetStateCallback): void; + function createState(name: string, initValue: ioBroker.StateValue, callback?: iobJS.SetStateCallback): void; function createState( name: string, - initValue: iobJS.StateValue, + initValue: ioBroker.StateValue, forceCreation: boolean, callback?: iobJS.SetStateCallback, ): void; function createState( name: string, - initValue: iobJS.StateValue, + initValue: ioBroker.StateValue, forceCreation: boolean, common: Partial, callback?: iobJS.SetStateCallback, ): void; function createState( name: string, - initValue: iobJS.StateValue, + initValue: ioBroker.StateValue, forceCreation: boolean, common: Partial, native: any, @@ -1754,7 +1724,7 @@ declare global { function createState(name: string, common: Partial, callback?: iobJS.SetStateCallback): void; function createState( name: string, - initValue: iobJS.StateValue, + initValue: ioBroker.StateValue, common: Partial, callback?: iobJS.SetStateCallback, ): void; @@ -1766,7 +1736,7 @@ declare global { ): void; function createState( name: string, - initValue: iobJS.StateValue, + initValue: ioBroker.StateValue, common: Partial, native: any, callback?: iobJS.SetStateCallback, @@ -1774,7 +1744,7 @@ declare global { function createStateAsync( name: string, - initValue?: iobJS.StateValue, + initValue?: ioBroker.StateValue, forceCreation?: boolean, common?: Partial, native?: any, @@ -1783,12 +1753,12 @@ declare global { function createStateAsync(name: string, common: Partial, native?: any): iobJS.SetStatePromise; function createStateAsync( name: string, - initValue: iobJS.StateValue, + initValue: ioBroker.StateValue, common: Partial, ): iobJS.SetStatePromise; function createStateAsync( name: string, - initValue: iobJS.StateValue, + initValue: ioBroker.StateValue, common: Partial, native?: any, ): iobJS.SetStatePromise; diff --git a/src/lib/mirror.ts b/src/lib/mirror.ts index 0740e8ed..d1af8908 100644 --- a/src/lib/mirror.ts +++ b/src/lib/mirror.ts @@ -17,7 +17,7 @@ import { ScriptType } from '../types'; const MODE_0777 = 511; -export default class Mirror { +export class Mirror { private adapter: ioBroker.Adapter; private readonly diskRoot: string; private readonly from: string; diff --git a/src/lib/protectFs.ts b/src/lib/protectFs.ts index 823dc095..881e64e1 100644 --- a/src/lib/protectFs.ts +++ b/src/lib/protectFs.ts @@ -1,9 +1,22 @@ -import {BufferEncodingOption, CopyOptions, Mode, ObjectEncodingOptions, OpenMode, PathLike} from 'node:fs'; +import { + BufferEncodingOption, + CopyOptions, + MakeDirectoryOptions, + Mode, + ObjectEncodingOptions, + OpenMode, + PathLike, + RmDirOptions, + RmOptions, + StatOptions, + Stats, + TimeLike, +} from 'node:fs'; import { Abortable } from 'node:events'; -import {FileHandle, FlagAndOpenMode} from 'fs/promises'; -import {NoParamCallback} from "fs"; -import {URL} from "node:url"; -import {Stream} from "node:stream"; +import { FileHandle, FlagAndOpenMode } from 'fs/promises'; +import { CopySyncOptions, NoParamCallback, PathOrFileDescriptor } from 'fs'; +import { URL } from 'node:url'; +import { Stream } from 'node:stream'; const nodeFS = require('node:fs'); const { sep, normalize, join } = require('node:path'); @@ -16,7 +29,14 @@ export default class ProtectFs { constructor(log: ioBroker.Logger, ioBrokerDataDir: string) { this.ioBrokerDataDir = ioBrokerDataDir; - this.log = log || console; + this.log = log || { + silly: (message: string): void => console.log(message), + debug: (message: string): void => console.debug(message), + info: (message: string): void => console.info(message), + warn: (message: string): void => console.warn(message), + error: (message: string): void => console.error(message), + level: 'info', + }; this.promises = { access: async (path: PathLike, mode?: number): Promise => { @@ -59,103 +79,115 @@ export default class ProtectFs { | Stream, options?: | (ObjectEncodingOptions & { - mode?: Mode | undefined; - flag?: OpenMode | undefined; - /** - * If all data is successfully written to the file, and `flush` - * is `true`, `filehandle.sync()` is used to flush the data. - * @default false - */ - flush?: boolean | undefined; - } & Abortable) + mode?: Mode | undefined; + flag?: OpenMode | undefined; + /** + * If all data is successfully written to the file, and `flush` + * is `true`, `filehandle.sync()` is used to flush the data. + * @default false + */ + flush?: boolean | undefined; + } & Abortable) | BufferEncoding | null, ): Promise => { - this.#checkProtected(arguments[0], true); - return nodeFS.promises.writeFile.apply(this, arguments); // async function writeFile(path, data, options) { + this.#checkProtected(file, true); + return nodeFS.promises.writeFile.call(this, file, data, options); // async function writeFile(path, data, options) { }, unlink: async (path: PathLike): Promise => { this.#checkProtected(path, false); - return nodeFS.promises.unlink.apply(this, arguments); // async function unlink(path) { + return nodeFS.promises.unlink.call(this, path); // async function unlink(path) { }, appendFile: async ( path: PathLike | FileHandle, data: string | Uint8Array, - options?: (ObjectEncodingOptions & FlagAndOpenMode & { flush?: boolean | undefined }) | BufferEncoding | null, + options?: + | (ObjectEncodingOptions & FlagAndOpenMode & { flush?: boolean | undefined }) + | BufferEncoding + | null, ): Promise => { this.#checkProtected(path, false); - return nodeFS.promises.appendFile.apply(this, arguments); // async function appendFile(path, data, options) { + return nodeFS.promises.appendFile.call(this, path, data, options); // async function appendFile(path, data, options) { }, chmod: async (path: PathLike, mode: Mode): Promise => { this.#checkProtected(path, false); - return nodeFS.promises.chmod.apply(this, arguments); // async function chmod(path, mode) { + return nodeFS.promises.chmod.call(this, path, mode); // async function chmod(path, mode) { }, copyFile: async (src: PathLike, dest: PathLike, mode?: number): Promise => { this.#checkProtected(src, false); this.#checkProtected(dest, false); - return nodeFS.promises.copyFile.apply(this, arguments); // async function copyFile(src, dest, mode) { + return nodeFS.promises.copyFile.call(this, src, dest, mode); // async function copyFile(src, dest, mode) { }, rename: async (oldPath: PathLike, newPath: PathLike): Promise => { this.#checkProtected(oldPath, false); this.#checkProtected(newPath, false); - return nodeFS.promises.rename.apply(this, arguments); // async function rename(oldPath, newPath) { + return nodeFS.promises.rename.call(this, oldPath, newPath); // async function rename(oldPath, newPath) { }, open: async (path: PathLike, flags?: string | number, mode?: Mode): Promise => { this.#checkProtected(path, true); - return nodeFS.promises.open.apply(this, arguments); // async function open(path, flags, mode) { + return nodeFS.promises.open.call(this, path, flags, mode); // async function open(path, flags, mode) { }, truncate: async (path: PathLike, len?: number): Promise => { - this.#checkProtected(arguments[0], false); - return nodeFS.promises.truncate.apply(this, arguments); // async function truncate(path, len = 0) { + this.#checkProtected(path, false); + return nodeFS.promises.truncate.call(this, path, len); // async function truncate(path, len = 0) { }, - stat: async () => { - this.#checkProtected(arguments[0], true); - return nodeFS.promises.stat.apply(this, arguments); // async function stat(path, options = { bigint: false }) { + stat: async (path: PathLike, opts?: StatOptions): Promise => { + this.#checkProtected(path, true); + return nodeFS.promises.stat.call(this, path, opts); // async function stat(path, options = { bigint: false }) { }, - utimes: async () => { - this.#checkProtected(arguments[0], false); - return nodeFS.promises.utimes.apply(this, arguments); // async function utimes(path, atime, mtime) { + utimes: async (path: PathLike, atime: TimeLike, mtime: TimeLike): Promise => { + this.#checkProtected(path, false); + return nodeFS.promises.utimes.call(this, path, atime, mtime); // async function utimes(path, atime, mtime) { }, - readdir: async () => { - this.#checkProtected(arguments[0], true); - return nodeFS.promises.readdir.apply(this, arguments); // async function readdir(path, options) { + readdir: async ( + path: PathLike, + options?: ObjectEncodingOptions & { + withFileTypes: true; + recursive?: boolean | undefined; + }, + ) => { + this.#checkProtected(path, true); + return nodeFS.promises.readdir.call(this, path, options); // async function readdir(path, options) { }, - lchmod: async () => { - this.#checkProtected(arguments[0], false); - return nodeFS.promises.lchmod.apply(this, arguments); // async function lchmod(path, mode) { + lchmod: async (path: PathLike, mode: Mode): Promise => { + this.#checkProtected(path, false); + return nodeFS.promises.lchmod.call(this, path, mode); // async function lchmod(path, mode) { }, - lchown: async () => { - this.#checkProtected(arguments[0], false); - return nodeFS.promises.lchown.apply(this, arguments); // async function lchown(path, uid, gid) { + lchown: async (path: PathLike, uid: number, gid: number): Promise => { + this.#checkProtected(path, false); + return nodeFS.promises.lchown.call(this, path, uid, gid); // async function lchown(path, uid, gid) { }, - link: async () => { - this.#checkProtected(arguments[0], false); - this.#checkProtected(arguments[1], false); - return nodeFS.promises.link.apply(this, arguments); // async function link(existingPath, newPath) { + link: async (existingPath: PathLike, newPath: PathLike): Promise => { + this.#checkProtected(existingPath, false); + this.#checkProtected(newPath, false); + return nodeFS.promises.link.call(this, existingPath, newPath); // async function link(existingPath, newPath) { }, - lstat: async () => { - this.#checkProtected(arguments[0], true); - return nodeFS.promises.lstat.apply(this, arguments); // async function lstat(path, options = { bigint: false }) { + lstat: async (path: PathLike, opts?: StatOptions): Promise => { + this.#checkProtected(path, true); + return nodeFS.promises.lstat.call(this, path, opts); // async function lstat(path, options = { bigint: false }) { }, - lutimes: async () => { - this.#checkProtected(arguments[0], false); - return nodeFS.promises.lutimes.apply(this, arguments); // async function lutimes(path, atime, mtime) { + lutimes: async (path: PathLike, atime: TimeLike, mtime: TimeLike): Promise => { + this.#checkProtected(path, false); + return nodeFS.promises.lutimes.call(this, path, atime, mtime); // async function lutimes(path, atime, mtime) { }, - mkdir: async () => { - this.#checkProtected(arguments[0], false); - return nodeFS.promises.mkdir.apply(this, arguments); // async function mkdir(path, options) { + mkdir: async ( + path: PathLike, + options?: Mode | MakeDirectoryOptions | null, + ): Promise => { + this.#checkProtected(path, false); + return nodeFS.promises.mkdir.call(this, path, options); // async function mkdir(path, options) { }, - mkdtemp: async () => { - this.#checkProtected(arguments[0], false); - return nodeFS.promises.mkdtemp.apply(this, arguments); // async function mkdtemp(prefix, options) { + mkdtemp: async (prefix: string, options: BufferEncodingOption): Promise => { + this.#checkProtected(prefix, false); + return nodeFS.promises.mkdtemp.call(this, prefix, options); // async function mkdtemp(prefix, options) { }, - rm: async () => { - this.#checkProtected(arguments[0], false); - return nodeFS.promises.rm.apply(this, arguments); // async function rm(path, options) { + rm: async (path: PathLike, options?: RmOptions): Promise => { + this.#checkProtected(path, false); + return nodeFS.promises.rm.call(this, path, options); // async function rm(path, options) { }, - rmdir: async () => { - this.#checkProtected(arguments[0], false); - return nodeFS.promises.rmdir.apply(this, arguments); // async function rmdir(path, options) { + rmdir: async (path: PathLike, options?: RmDirOptions): Promise => { + this.#checkProtected(path, false); + return nodeFS.promises.rmdir.call(this, path, options); // async function rmdir(path, options) { }, }; @@ -220,20 +252,32 @@ export default class ProtectFs { return nodeFS.cp(source, destination, opts); } - cpSync() { - this.#checkProtected(arguments[0], false); - this.#checkProtected(arguments[1], false); - return nodeFS.cpSync.apply(this, arguments); // function cpSync(src, dest, options) { - } - - readFile() { - this.#checkProtected(arguments[0], true); - return nodeFS.readFile.apply(this, arguments); // function readFile(path, options, callback) { + cpSync(source: string | URL, destination: string | URL, opts?: CopySyncOptions): void { + this.#checkProtected(source, false); + this.#checkProtected(destination, false); + return nodeFS.cpSync.call(this, source, destination, opts); // function cpSync(src, dest, options) { } - readFileSync() { - this.#checkProtected(arguments[0], true); - return nodeFS.readFileSync.apply(this, arguments); // function readFileSync(path, options) { + readFile(path: PathOrFileDescriptor, callback: (err: NodeJS.ErrnoException | null, data: Buffer) => void): void { + if (typeof path !== 'number') { + this.#checkProtected(path, true); + } + return nodeFS.readFile.call(this, path, callback); // function readFile(path, options, callback) { + } + + readFileSync( + path: PathOrFileDescriptor, + options: + | { + encoding: BufferEncoding; + flag?: string | undefined; + } + | BufferEncoding, + ): string | Buffer { + if (typeof path !== 'number') { + this.#checkProtected(path, true); + } + return nodeFS.readFileSync.call(this, path, options); // function readFileSync(path, options) { } readlink() { diff --git a/src/lib/sandbox.ts b/src/lib/sandbox.ts index 39a6f7d3..2e90d650 100644 --- a/src/lib/sandbox.ts +++ b/src/lib/sandbox.ts @@ -1,33 +1,40 @@ -import { isObject, isArray, promisify, getHttpRequestConfig } from './tools'; +import { type ChildProcess, ExecOptions } from 'node:child_process'; +import * as jsonataMod from 'jsonata'; +import { type SendMailOptions } from 'nodemailer'; +import { AxiosHeaderValue, AxiosResponse, ResponseType } from 'axios'; + import { commonTools } from '@iobroker/adapter-core'; -import { type ChildProcess } from 'node:child_process'; + +import { isObject, isArray, promisify, getHttpRequestConfig } from './tools'; import { AdapterConfig, - AstroRule, ChangeType, CommonAlias, FileSubscriptionResult, IobSchedule, + AstroRule, + ChangeType, + CommonAlias, + FileSubscriptionResult, + IobSchedule, JavascriptContext, - JsScript, LogMessage, + JsScript, + LogMessage, Pattern, PushoverOptions, SandboxType, - Selector, SubscriptionResult, TimeRule + Selector, + SubscribeObject, + 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 {ScheduleName, SchedulerRule, SchedulerRuleParsed} from './scheduler'; -import {EventObj} from "./eventObj"; -import {AstroEvent} from "./consts"; +import { ScheduleName, SchedulerRule } from './scheduler'; +import { EventObj } from './eventObj'; +import { AstroEvent } from './consts'; const pattern2RegEx = commonTools.pattern2RegEx; -export default function sandBox( +export function sandBox( script: JsScript, name: string, verbose: boolean | undefined, @@ -132,7 +139,7 @@ export default function sandBox( } } - function unsubscribeFile(script: JsScript, id: string, fileNamePattern: string): void { { + function unsubscribeFile(script: JsScript, id: string, fileNamePattern: string): void { const key = `${id}$%$${fileNamePattern}`; if (script.subscribesFile[key]) { script.subscribesFile[key]--; @@ -250,7 +257,7 @@ export default function sandBox( * @param value The value to compare with the reference * @param reference The reference to compare the value to */ - function looselyEqualsString(value: string | number |boolean | undefined, reference: string): boolean { + function looselyEqualsString(value: string | number | boolean | undefined, reference: string): boolean { // For booleans, compare the string representation // For other types do a loose comparison return typeof value === 'boolean' @@ -263,7 +270,7 @@ export default function sandBox( * @param {any} value */ function getCommonTypeOf(value: any): ioBroker.CommonType { - return isArray(value) ? 'array' : isObject(value) ? 'object' : typeof value as ioBroker.CommonType; + return isArray(value) ? 'array' : isObject(value) ? 'object' : (typeof value as ioBroker.CommonType); } /** @@ -412,8 +419,8 @@ export default function sandBox( } } catch (err: any) { context.logWithLineInfo?.warn( - `Could not stringify value for type ${actualCommonType} and id ${id}: ${err.message}`, - ); + `Could not stringify value for type ${actualCommonType} and id ${id}: ${err.message}`, + ); if (typeof callback === 'function') { try { callback.call( @@ -483,7 +490,7 @@ export default function sandBox( } else { if (!(adapter.config as AdapterConfig).subscribe) { // store actual state to make possible to process value in callback - // risk that there will be an error on setState is very low + // risk that there will be an error on setState is very low, // but we will not store new state if the setStateChanged is called if (!isChanged) { context.interimStateValues[id] = stateAsObject; @@ -510,7 +517,9 @@ export default function sandBox( if (!(adapter.config as AdapterConfig).subscribe && context.interimStateValues[id]) { // if the state is changed, we will compare it with interimStateValues const oldState = context.interimStateValues[id], - attrs = Object.keys(stateAsObject).filter(attr => attr !== 'ts' && stateAsObject[attr] !== undefined); + attrs = Object.keys(stateAsObject).filter( + attr => attr !== 'ts' && stateAsObject[attr] !== undefined, + ); if (attrs.every(attr => stateAsObject[attr] === oldState[attr]) === false) { // state is changed for sure, and we will call setForeignState // and store new state to interimStateValues @@ -544,10 +553,11 @@ export default function sandBox( } } - const sandbox: SandboxType = { + sandbox = { mods, _id: script._id, - name, // deprecated + // @deprecated use scriptName + name, scriptName: name, instance: adapter.instance || 0, defaultDataDir: context.getAbsoluteDefaultDataDir(), @@ -609,6 +619,7 @@ export default function sandBox( __subscriptionsLog: 0, __schedules: 0, }, + $: function (selector: string): iobJS.QueryResult { // following is supported // 'type[commonAttr=something]', 'id[commonAttr=something]', id(enumName="something")', id{nativeName="something"} @@ -624,7 +635,7 @@ export default function sandBox( // Todo CACHE!!! - const result: iobJS.QueryResult = {}; + const result: iobJS.QueryResult = {} as iobJS.QueryResult; let name: string = ''; const commonStrings: string[] = []; @@ -856,7 +867,7 @@ export default function sandBox( return enumSelectors.every(_enum => enumIds.includes(_enum)); } - let res: string[] = []; + let res: string[]; if (name === 'schedule') { res = context.schedules || []; @@ -984,12 +995,18 @@ export default function sandBox( } } - const resUnique = [...new Set(res)]; + const resUnique = []; + for (let i = 0; i < res.length; i++) { + if (!resUnique.includes(res[i])) { + resUnique.push(res[i]); + } + } for (let i = 0; i < resUnique.length; i++) { result[i] = resUnique[i]; } result.length = resUnique.length; + // Implementing the Symbol.iterator contract makes the query result iterable result[Symbol.iterator] = function* () { for (let i = 0; i < result.length; i++) { @@ -1011,13 +1028,21 @@ export default function sandBox( } return this; }; - result.getState = function (callback?: (err: Error | null | undefined, state?: ioBroker.State | null) => void): void | null | undefined | ioBroker.State { + // @ts-expect-error fix later + result.getState = function ( + callback?: iobJS.GetStateCallback, + ): void | null | undefined | iobJS.TypedState | iobJS.AbsentState { if ((adapter.config as AdapterConfig).subscribe) { if (typeof callback !== 'function') { sandbox.log('You cannot use this function synchronous', 'error'); } else { - adapter.getForeignState(this[0], (err, state) => - callback(err, context.convertBackStringifiedValues(this[0], state)), + adapter.getForeignState(this[0], (err: Error | null, state?: ioBroker.State | null) => + callback( + err, + context.convertBackStringifiedValues(this[0], state) as + | iobJS.TypedState + | iobJS.AbsentState, + ), ); } } else { @@ -1025,39 +1050,66 @@ export default function sandBox( return null; } if (context.interimStateValues[this[0]] !== undefined) { - return context.convertBackStringifiedValues(this[0], context.interimStateValues[this[0]]); + return context.convertBackStringifiedValues(this[0], context.interimStateValues[this[0]]) as + | iobJS.TypedState + | iobJS.AbsentState; } - return context.convertBackStringifiedValues(this[0], states[this[0]]); + return context.convertBackStringifiedValues(this[0], states[this[0]]) as + | iobJS.TypedState + | iobJS.AbsentState; } }; - result.getStateAsync = async function (): Promise { + result.getStateAsync = async function (): Promise< + iobJS.TypedState | iobJS.AbsentState | null + > { if ((adapter.config as AdapterConfig).subscribe) { const state = await adapter.getForeignStateAsync(this[0]); - return context.convertBackStringifiedValues(this[0], state); - } else { - if (!this[0]) { - return null; - } - if (context.interimStateValues[this[0]] !== undefined) { - return context.convertBackStringifiedValues(this[0], context.interimStateValues[this[0]]); - } - return context.convertBackStringifiedValues(this[0], states[this[0]]); + return context.convertBackStringifiedValues(this[0], state) as + | iobJS.TypedState + | iobJS.AbsentState + | null; + } + if (!this[0]) { + return null; + } + if (context.interimStateValues[this[0]] !== undefined) { + return context.convertBackStringifiedValues(this[0], context.interimStateValues[this[0]]) as + | iobJS.TypedState + | iobJS.AbsentState + | null; } + return context.convertBackStringifiedValues(this[0], states[this[0]]) as + | iobJS.TypedState + | iobJS.AbsentState + | null; }; - result.setState = function (state: ioBroker.SettableState | ioBroker.StateValue, isAck?: boolean | 'false' | 'true' | null, callback) { + result.setState = function ( + state: ioBroker.SettableState | ioBroker.StateValue, + isAck?: boolean | 'false' | 'true' | null | iobJS.SetStateCallback, + callback?: iobJS.SetStateCallback, + ) { if (typeof isAck === 'function') { callback = isAck; isAck = undefined; } - result.setStateAsync(state, isAck).then(() => typeof callback === 'function' && callback()); + result + .setStateAsync(state, isAck as boolean | 'false' | 'true') + .then(() => typeof callback === 'function' && callback()); return this; }; - result.setStateAsync = async function (state: ioBroker.SettableState | ioBroker.StateValue, isAck?: boolean): Promise { + result.setStateAsync = async function ( + state: ioBroker.SettableState | ioBroker.StateValue, + isAck?: boolean, + ): Promise { for (let i = 0; i < this.length; i++) { await sandbox.setStateAsync(this[i], state, isAck); } }; - result.setStateChanged = function (state: ioBroker.SettableState | ioBroker.StateValue, isAck?: boolean, callback?: () => void) { + result.setStateChanged = function ( + state: ioBroker.SettableState | ioBroker.StateValue, + isAck?: boolean, + callback?: () => void, + ) { if (typeof isAck === 'function') { callback = isAck; isAck = undefined; @@ -1065,12 +1117,21 @@ export default function sandBox( result.setStateChangedAsync(state, isAck).then(() => typeof callback === 'function' && callback()); return this; }; - result.setStateChangedAsync = async function (state: ioBroker.SettableState | ioBroker.StateValue, isAck?: boolean): Promise { + result.setStateChangedAsync = async function ( + state: ioBroker.SettableState | ioBroker.StateValue, + isAck?: boolean, + ): Promise { for (let i = 0; i < this.length; i++) { await sandbox.setStateChangedAsync(this[i], state, isAck); } }; - result.setStateDelayed = function (state: ioBroker.SettableState | ioBroker.StateValue, isAck: boolean | number | undefined, delay: number | boolean, clearRunning: boolean | (() => void), callback?: () => void) { + 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 as () => void; clearRunning = delay as boolean; @@ -1159,7 +1220,9 @@ export default function sandBox( return handler.id; }, - onLogUnregister: function (idOrCallbackOrSeverity: string | ioBroker.LogLevel | ((info: LogMessage) => void)): boolean { + onLogUnregister: function ( + idOrCallbackOrSeverity: string | ioBroker.LogLevel | ((info: LogMessage) => void), + ): boolean { let found = false; if (context.logSubscriptions?.[sandbox.scriptName]) { @@ -1209,7 +1272,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 { + ): undefined | ChildProcess { if (typeof options === 'function') { callback = options as (error: Error | null | string, stdout?: string, stderr?: string) => void; options = {}; @@ -1232,15 +1295,19 @@ export default function sandBox( }); } } else { - return mods.child_process.exec(cmd, options, (error: Error | null, stdout: string, stderr: string): void => { - if (typeof callback === 'function') { - try { - callback.call(sandbox, error, stdout, stderr); - } catch (e) { - errorInCallback(e); + return mods.child_process.exec( + cmd, + options, + (error: Error | null, stdout: string, stderr: string): void => { + if (typeof callback === 'function') { + try { + callback.call(sandbox, error, stdout, stderr); + } catch (e) { + errorInCallback(e); + } } - } - }); + }, + ); } } }, @@ -1249,40 +1316,51 @@ export default function sandBox( sandbox.log(`email(msg=${JSON.stringify(msg)}) is deprecated. Please use sendTo instead!`, 'warn'); adapter.sendTo('email', msg); }, - pushover: function (msg: string| PushoverOptions): void { + pushover: function (msg: string | PushoverOptions): void { sandbox.verbose && sandbox.log(`pushover(msg=${JSON.stringify(msg)})`, 'info'); sandbox.log(`pushover(msg=${JSON.stringify(msg)}) is deprecated. Please use sendTo instead!`, 'warn'); adapter.sendTo('pushover', msg); }, httpGet: function ( url: string, - options: { - timeout?: number; - responseType?: ResponseType; - headers?: Record; - basicAuth?: { user: string; password: string } | null; - bearerAuth?: string; - validateCertificate?: boolean; - } | ((error: Error | null, result: { - statusCode: number | null, - data: any, - headers: Record, - responseTime: number, - }) => void), - callback?: (error: Error | null, result: { - statusCode: number | null, - data: any, - headers: Record, - responseTime: number, - }) => void + options: + | { + timeout?: number; + responseType?: ResponseType; + headers?: Record; + basicAuth?: { user: string; password: string } | null; + bearerAuth?: string; + validateCertificate?: boolean; + } + | (( + error: Error | null, + result: { + statusCode: number | null; + data: any; + headers: Record; + responseTime: number; + }, + ) => void), + callback?: ( + error: Error | null, + result: { + statusCode: number | null; + data: any; + headers: Record; + responseTime: number; + }, + ) => void, ): void { if (typeof options === 'function') { - callback = options as (error: Error | null, result: { - statusCode: number | null, - data: any, - headers: Record, - responseTime: number, - }) => void; + callback = options as ( + error: Error | null, + result: { + statusCode: number | null; + data: any; + headers: Record; + responseTime: number; + }, + ) => void; options = {}; } @@ -1322,10 +1400,10 @@ export default function sandBox( if (typeof callback === 'function') { let result: { - statusCode: number | null, - data: any, - headers: Record, - responseTime: number, + statusCode: number | null; + data: any; + headers: Record; + responseTime: number; } = { statusCode: null, data: null, @@ -1355,22 +1433,22 @@ export default function sandBox( data: any, options: | { - timeout?: number; - responseType?: ResponseType; - headers?: Record; - basicAuth?: { user: string; password: string } | null; - bearerAuth?: string; - validateCertificate?: boolean; - } + timeout?: number; + responseType?: ResponseType; + headers?: Record; + basicAuth?: { user: string; password: string } | null; + bearerAuth?: string; + validateCertificate?: boolean; + } | (( - error: Error | null, - result: { - statusCode: number | null; - data: any; - headers: Record; - responseTime: number; - }, - ) => void), + error: Error | null, + result: { + statusCode: number | null; + data: any; + headers: Record; + responseTime: number; + }, + ) => void), callback?: ( error: Error | null, result: { @@ -1387,14 +1465,17 @@ export default function sandBox( } const config = { - ...getHttpRequestConfig(url, options as { - timeout?: number; - responseType?: ResponseType; - headers?: Record; - basicAuth?: { user: string; password: string } | null; - bearerAuth?: string; - validateCertificate?: boolean; - }), + ...getHttpRequestConfig( + url, + options as { + timeout?: number; + responseType?: ResponseType; + headers?: Record; + basicAuth?: { user: string; password: string } | null; + bearerAuth?: string; + validateCertificate?: boolean; + }, + ), method: 'post', data, }; @@ -1498,10 +1579,22 @@ export default function sandBox( return filePath; }, subscribe: function ( - pattern: TimeRule | AstroRule | Pattern | SchedulerRule | string | (TimeRule | AstroRule | Pattern | SchedulerRule | string)[], + 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)[] { + ): + | SubscriptionResult + | IobSchedule + | string + | null + | undefined + | (SubscriptionResult | IobSchedule | string | null | undefined)[] { // If a schedule object is given if ( (typeof pattern === 'string' && pattern[0] === '{') || @@ -1513,7 +1606,13 @@ export default function sandBox( if (pattern && Array.isArray(pattern)) { const result: (IobSchedule | string | null | undefined)[] = []; for (const p of pattern) { - result.push(sandbox.subscribe(p as SchedulerRule | string, callbackOrChangeTypeOrId, value) as IobSchedule | string | null | undefined); + result.push( + sandbox.subscribe(p as SchedulerRule | string, callbackOrChangeTypeOrId, value) as + | IobSchedule + | string + | null + | undefined, + ); } return result; } @@ -1537,7 +1636,13 @@ export default function sandBox( 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); + result.push( + sandbox.subscribe(pa, callbackOrChangeTypeOrId, value) as + | IobSchedule + | string + | null + | undefined, + ); } return result; } @@ -1547,7 +1652,10 @@ export default function sandBox( 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); + return sandbox.schedule( + (pattern as TimeRule).time as string, + callbackOrChangeTypeOrId as () => void, + ); } } @@ -1645,7 +1753,8 @@ export default function sandBox( getSubscriptions: function (): Record { const result: Record = {}; for (let s = 0; s < context.subscriptions.length; s++) { - result[context.subscriptions[s].pattern.id as string] = result[context.subscriptions[s].pattern.id as string] || []; + 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, @@ -1684,11 +1793,15 @@ export default function sandBox( adapter.sendTo(a, 'subscribe', id); } }, - adapterUnsubscribe: function (idOrObject: string | SubscriptionResult | (string | SubscriptionResult)[]): boolean | boolean[] { - // BF: it could be an error - return sandbox.unsubscribe(id); + adapterUnsubscribe: function ( + idOrObject: string | SubscriptionResult | (string | SubscriptionResult)[], + ): boolean | boolean[] { + // todo: BF - it could be an error + return sandbox.unsubscribe(idOrObject); }, - unsubscribe: function (idOrObject: string | SubscriptionResult | (string | SubscriptionResult)[]): boolean | boolean[] { + unsubscribe: function ( + idOrObject: string | SubscriptionResult | (string | SubscriptionResult)[], + ): boolean | boolean[] { if (idOrObject && Array.isArray(idOrObject)) { const result: boolean[] = []; for (let t = 0; t < idOrObject.length; t++) { @@ -1731,7 +1844,13 @@ export default function sandBox( | (TimeRule | AstroRule | Pattern | SchedulerRule | string)[], callbackOrChangeTypeOrId: string | ChangeType | ((event?: EventObj) => void), value?: any, - ): SubscriptionResult | IobSchedule | string | null | undefined | (SubscriptionResult | IobSchedule | string | null | undefined)[] { + ): + | SubscriptionResult + | IobSchedule + | string + | null + | undefined + | (SubscriptionResult | IobSchedule | string | null | undefined)[] { return sandbox.subscribe(pattern, callbackOrChangeTypeOrId, value); }, onEnumMembers: function (enumId: string, callback: (event?: EventObj) => void): void { @@ -1756,14 +1875,16 @@ 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) as string | SubscriptionResult; // TODO: more features + subscriptions[objId] = sandbox.subscribe(objId, callback) as + | string + | SubscriptionResult; // TODO: more features } } } sandbox.verbose && sandbox.log( - `onEnumMembers(id=${id}, members=${JSON.stringify(Object.keys(subscriptions))})`, + `onEnumMembers(id=${enumId}, members=${JSON.stringify(Object.keys(subscriptions))})`, 'info', ); }; @@ -1772,17 +1893,25 @@ export default function sandBox( sandbox.subscribeObject(enumId, obj => obj && init()); } else { - sandbox.log(`onEnumMembers: enum with id "${id}" doesn't exists`, 'error'); + sandbox.log(`onEnumMembers: enum with id "${enumId}" doesn't exists`, 'error'); } }, onFile: function ( id: string, fileNamePattern: string | string[], - withFileOrCallback: boolean | ((id: string, fileName: string, size: number, file?: string | Buffer, mimeType?: 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)[] { if (typeof withFileOrCallback === 'function') { - callback = withFileOrCallback as (id: string, fileName: string, size: number, file?: string | Buffer, mimeType?: string) => void; + callback = withFileOrCallback as ( + id: string, + fileName: string, + size: number, + file?: string | Buffer, + mimeType?: string, + ) => void; withFileOrCallback = false; } @@ -1806,7 +1935,12 @@ export default function sandBox( } if (Array.isArray(fileNamePattern)) { - return fileNamePattern.map(filePattern => sandbox.onFile(id, filePattern, withFileOrCallback, callback) as undefined | FileSubscriptionResult); + return fileNamePattern.map( + filePattern => + sandbox.onFile(id, filePattern, withFileOrCallback, callback) as + | undefined + | FileSubscriptionResult, + ); } sandbox.__engine.__subscriptionsFile += 1; @@ -1869,7 +2003,10 @@ export default function sandBox( subscribeFile(script, id, fileNamePattern); return subs; }, - offFile: function (idOrObject: FileSubscriptionResult | string | (FileSubscriptionResult | string)[], fileNamePattern?: string | string[]): boolean | boolean[] { + 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', @@ -1948,14 +2085,16 @@ export default function sandBox( return !!deleted; }, /** Registers a one-time subscription which automatically unsubscribes after the first invocation */ - once: function (pattern: - | TimeRule - | AstroRule - | Pattern - | SchedulerRule - | string - | (TimeRule | AstroRule | Pattern | SchedulerRule | string)[], callback?: (event?: EventObj) => void): string | SubscriptionResult | Promise - { + 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 => { @@ -1973,7 +2112,10 @@ export default function sandBox( // Promise-style: once("id").then(obj => { ... }) return new Promise(resolve => _once(resolve)); }, - schedule: function (pattern: SchedulerRule | AstroRule | Date | string, callback: () => void): IobSchedule | string | null | undefined { + schedule: function ( + pattern: SchedulerRule | AstroRule | Date | string, + callback: () => void, + ): IobSchedule | string | null | undefined { if (typeof callback !== 'function') { sandbox.log(`schedule callback missing`, 'error'); return null; @@ -1989,7 +2131,11 @@ export default function sandBox( 'info', ); - const schedule: string | null = context.scheduler.add(pattern as SchedulerRule | string, 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; @@ -2205,11 +2351,15 @@ export default function sandBox( 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; } @@ -2292,8 +2442,10 @@ export default function sandBox( } if ( - (!(adapter.config as AdapterConfig).latitude && (adapter.config as AdapterConfig).latitude as unknown as number !== 0) || - (!(adapter.config as AdapterConfig).longitude && (adapter.config as AdapterConfig).longitude as unknown as number !== 0) + (!(adapter.config as AdapterConfig).latitude && + ((adapter.config as AdapterConfig).latitude as unknown as number) !== 0) || + (!(adapter.config as AdapterConfig).longitude && + ((adapter.config as AdapterConfig).longitude as unknown as number) !== 0) ) { sandbox.log('Longitude or latitude does not set. Cannot use astro.', 'error'); return; @@ -2396,10 +2548,20 @@ export default function sandBox( } return schedules; }, - setState: function (id: string, state: ioBroker.SettableState | ioBroker.StateValue, isAck?: boolean | ((err?: Error | null) => void), callback?: (err?: Error | null) => void): void { + setState: function ( + id: string, + state: ioBroker.SettableState | ioBroker.StateValue, + isAck?: boolean | 'true' | 'false' | ((err?: Error | null) => void), + callback?: (err?: Error | null) => void, + ): void { return setStateHelper(sandbox, false, false, id, state, isAck, callback); }, - setStateChanged: function (id: string, state: ioBroker.SettableState | ioBroker.StateValue, isAck?: boolean | ((err?: Error | null) => void), callback?: (err?: Error | null) => void): void { + setStateChanged: function ( + id: string, + state: ioBroker.SettableState | ioBroker.StateValue, + isAck?: boolean | ((err?: Error | null) => void), + callback?: (err?: Error | null) => void, + ): void { return setStateHelper(sandbox, false, true, id, state, isAck, callback); }, setStateDelayed: function ( @@ -2500,8 +2662,16 @@ export default function sandBox( id: context.timerId, ts: Date.now(), delay: delay, - val: isObject(state) && (state as ioBroker.SettableState).val !== undefined ? (state as ioBroker.SettableState).val as ioBroker.StateValue : state as ioBroker.StateValue, - ack: isObject(state) && (state as ioBroker.SettableState).val !== undefined && (state as ioBroker.SettableState).ack !== undefined ? (state as ioBroker.SettableState).ack : isAck as boolean | undefined, + val: + isObject(state) && (state as ioBroker.SettableState).val !== undefined + ? ((state as ioBroker.SettableState).val as ioBroker.StateValue) + : (state as ioBroker.StateValue), + ack: + isObject(state) && + (state as ioBroker.SettableState).val !== undefined && + (state as ioBroker.SettableState).ack !== undefined + ? (state as ioBroker.SettableState).ack + : (isAck as boolean | undefined), }); return context.timerId; @@ -2535,12 +2705,16 @@ export default function sandBox( } return false; }, - getStateDelayed: function (id: string | number): - null | - { timerId: number; left: number; delay: number; val: ioBroker.StateValue; ack?: boolean } | - { timerId: number; left: number; delay: number; val: ioBroker.StateValue; ack?: boolean }[] | - Record - { + getStateDelayed: function ( + id: string | number, + ): + | null + | { timerId: number; left: number; delay: number; val: ioBroker.StateValue; ack?: boolean } + | { timerId: number; left: number; delay: number; val: ioBroker.StateValue; ack?: boolean }[] + | Record< + string, + { timerId: number; left: number; delay: number; val: ioBroker.StateValue; ack?: boolean }[] + > { const now = Date.now(); if (id) { // Check a type of state @@ -2567,7 +2741,13 @@ export default function sandBox( return null; } - let result: { timerId: number; left: number; delay: number; val: ioBroker.StateValue; ack?: boolean }[] = []; + let result: { + timerId: number; + left: number; + delay: number; + val: ioBroker.StateValue; + ack?: boolean; + }[] = []; if (Object.prototype.hasOwnProperty.call(timers, id) && timers[id] && timers[id].length) { for (let tt = 0; tt < timers[id].length; tt++) { result.push({ @@ -2581,7 +2761,10 @@ export default function sandBox( } return result; } - let result: Record = {}; + let result: Record< + string, + { timerId: number; left: number; delay: number; val: ioBroker.StateValue; ack?: boolean }[] + > = {}; for (const _id in timers) { if (Object.prototype.hasOwnProperty.call(timers, _id) && timers[_id] && timers[_id].length) { result[_id] = []; @@ -2607,17 +2790,28 @@ export default function sandBox( } return context.convertBackStringifiedValues(id, state); }, - setStateAsync: function (id: string, state: ioBroker.SettableState | ioBroker.StateValue, isAck?: boolean): Promise { + setStateAsync: function ( + id: string, + state: ioBroker.SettableState | ioBroker.StateValue, + isAck?: boolean, + ): Promise { return new Promise((resolve, reject) => setStateHelper(sandbox, false, false, id, state, isAck, err => (err ? reject(err) : resolve())), ); }, - setStateChangedAsync: function (id: string, state: ioBroker.SettableState | ioBroker.StateValue, isAck?: boolean): Promise { + setStateChangedAsync: function ( + id: string, + state: ioBroker.SettableState | ioBroker.StateValue, + isAck?: boolean, + ): Promise { return new Promise((resolve, reject) => setStateHelper(sandbox, false, true, id, state, isAck, err => (err ? reject(err) : resolve())), ); }, - getState: function (id: string, callback?: (err: Error | null | undefined, state?: ioBroker.State | null | undefined) => void): undefined | void | (ioBroker.State & { notExist?: true }) { + getState: function ( + id: string, + callback?: (err: Error | null | undefined, state?: ioBroker.State | null | undefined) => void, + ): undefined | void | (ioBroker.State & { notExist?: true }) { if (typeof id !== 'string') { sandbox.log(`getState has been called with id of type "${typeof id}" but expects a string`, 'error'); return undefined; @@ -2679,7 +2873,10 @@ export default function sandBox( } } }, - existsState: function (id: string, callback?: (err: Error | null | undefined, stateExists?: boolean) => void): void | boolean { + existsState: function ( + id: string, + callback?: (err: Error | null | undefined, stateExists?: boolean) => void, + ): void | boolean { if (typeof id !== 'string') { sandbox.log(`existsState has been called with id of type "${typeof id}" but expects a string`, 'error'); return false; @@ -2715,7 +2912,10 @@ export default function sandBox( } } }, - existsObject: function (id: string, callback?: (err: Error | null | undefined, objectExists?: boolean) => void): void | boolean { + existsObject: function ( + id: string, + callback?: (err: Error | null | undefined, objectExists?: boolean) => void, + ): void | boolean { if (typeof id !== 'string') { sandbox.log( `existsObject has been called with id of type "${typeof id}" but expects a string`, @@ -2725,8 +2925,7 @@ export default function sandBox( } if (typeof callback === 'function') { - adapter.getForeignObject(id, (err, obj) => - callback(err, !!obj)); + adapter.getForeignObject(id, (err, obj) => callback(err, !!obj)); } else { return !!objects[id]; } @@ -2750,7 +2949,10 @@ export default function sandBox( }, getObject: function ( id: string, - enumName: null | string | ((err: Error | null | undefined, obj?: ioBroker.Object | null | undefined) => void), + enumName: + | null + | string + | ((err: Error | null | undefined, obj?: ioBroker.Object | null | undefined) => void), cb?: (err: Error | null | undefined, obj?: ioBroker.Object | null | undefined) => void, ): void | ioBroker.Object | null { if (typeof id !== 'string') { @@ -2830,36 +3032,57 @@ export default function sandBox( } }, // This function will be overloaded later if the modification of objects is allowed - setObject: function (_id: string, _obj: ioBroker.Object, callback?: (err?: Error | null | undefined, res?: { id: string }) => void): void { + setObject: function ( + _id: string, + _obj: ioBroker.Object, + callback?: (err?: Error | null | undefined, res?: { id: string }) => void, + ): void { sandbox.log('Function "setObject" is not allowed. Use adapter settings to allow it.', 'error'); if (typeof callback === 'function') { try { - callback.call(sandbox, new Error('Function "setObject" is not allowed. Use adapter settings to allow it.')); + callback.call( + sandbox, + new Error('Function "setObject" is not allowed. Use adapter settings to allow it.'), + ); } catch (e) { errorInCallback(e); } } }, // This function will be overloaded later if the modification of objects is allowed - extendObject: function (_id: string, _obj: Partial, callback?: (err?: Error | null | undefined, res?: { id: string }) => void): void { + extendObject: function ( + _id: string, + _obj: Partial, + callback?: (err?: Error | null | undefined, res?: { id: string }) => void, + ): void { sandbox.log('Function "extendObject" is not allowed. Use adapter settings to allow it.', 'error'); if (typeof callback === 'function') { try { - callback.call(sandbox, new Error('Function "extendObject" is not allowed. Use adapter settings to allow it.')); + callback.call( + sandbox, + new Error('Function "extendObject" is not allowed. Use adapter settings to allow it.'), + ); } catch (e) { errorInCallback(e); } } }, // This function will be overloaded later if the modification of objects is allowed - deleteObject: function (_id: string, _isRecursive?: boolean | ioBroker.ErrorCallback, callback?: ioBroker.ErrorCallback): void { + deleteObject: function ( + _id: string, + _isRecursive?: boolean | ioBroker.ErrorCallback, + callback?: ioBroker.ErrorCallback, + ): void { if (typeof _isRecursive === 'function') { callback = _isRecursive; } sandbox.log('Function "deleteObject" is not allowed. Use adapter settings to allow it.', 'error'); if (typeof callback === 'function') { try { - callback.call(sandbox, new Error('Function "deleteObject" is not allowed. Use adapter settings to allow it.')); + callback.call( + sandbox, + new Error('Function "deleteObject" is not allowed. Use adapter settings to allow it.'), + ); } catch (e) { errorInCallback(e); } @@ -2870,7 +3093,8 @@ export default function sandBox( const r = enumName ? new RegExp(`^enum\\.${enumName}\\.`) : false; for (let i = 0; i < enums.length; i++) { if (!r || r.test(enums[i])) { - const common: ioBroker.EnumCommon = (objects[enums[i]] as ioBroker.EnumObject).common || {} as ioBroker.EnumCommon; + const common: ioBroker.EnumCommon = + (objects[enums[i]] as ioBroker.EnumObject).common || ({} as ioBroker.EnumCommon); result.push({ id: enums[i], members: common.members || [], @@ -2937,10 +3161,13 @@ export default function sandBox( name = `alias.0.${name}`; } - const _common: Partial = common as Partial || {}; + const _common: Partial = (common as Partial) || {}; if (isObject(_common.alias)) { // alias already in common, use this - } else if (isObject(alias) && (typeof (alias as CommonAlias).id === 'string' || isObject((alias as CommonAlias).id))) { + } else if ( + isObject(alias) && + (typeof (alias as CommonAlias).id === 'string' || isObject((alias as CommonAlias).id)) + ) { _common.alias = alias as CommonAlias; } else if (typeof alias === 'string') { _common.alias = { id: alias }; @@ -2957,22 +3184,25 @@ export default function sandBox( return; } - let aliasSourceId: string = isObject(_common.alias.id) ? (_common.alias.id as {read: string; write: string}).read : _common.alias.id as string; + let aliasSourceId: string = isObject(_common.alias.id) + ? (_common.alias.id as { read: string; write: string }).read + : (_common.alias.id as string); if (!objects[aliasSourceId] && objects[`${adapter.namespace}.${aliasSourceId}`]) { aliasSourceId = `${adapter.namespace}.${aliasSourceId}`; if (isObject(_common.alias.id)) { - (_common.alias.id as {read: string; write: string}).read = aliasSourceId; + (_common.alias.id as { read: string; write: string }).read = aliasSourceId; } else { _common.alias.id = aliasSourceId; } } if ( isObject(_common.alias.id) && - (_common.alias.id as {read: string; write: string}).write && - !objects[(_common.alias.id as {read: string; write: string}).write] && - objects[`${adapter.namespace}.${(_common.alias.id as {read: string; write: string}).write}`] + (_common.alias.id as { read: string; write: string }).write && + !objects[(_common.alias.id as { read: string; write: string }).write] && + objects[`${adapter.namespace}.${(_common.alias.id as { read: string; write: string }).write}`] ) { - (_common.alias.id as {read: string; write: string}).write = `${adapter.namespace}.${(_common.alias.id as {read: string; write: string}).write}`; + (_common.alias.id as { read: string; write: string }).write = + `${adapter.namespace}.${(_common.alias.id as { read: string; write: string }).write}`; } const obj = objects[aliasSourceId]; if (!obj) { @@ -3024,12 +3254,24 @@ export default function sandBox( _common.desc = obj.common.desc; } - return sandbox.createState(name, undefined, forceCreation as boolean, _common, native, callback as (err: Error | null) => void); + return sandbox.createState( + name, + undefined, + forceCreation as boolean, + _common, + native, + callback as (err: Error | null) => void, + ); }, createState: async function ( name: string, initValue: undefined | ioBroker.StateValue | ioBroker.State, - forceCreation: boolean | undefined | Record | Partial | ((err: Error | null) => void), + forceCreation: + | boolean + | undefined + | Record + | Partial + | ((err: Error | null) => void), common?: Partial | ((err: Error | null) => void), native?: Record | ((err: Error | null) => void), callback?: (error?: Error | null) => void, @@ -3043,11 +3285,11 @@ export default function sandBox( common = undefined; } if (typeof initValue === 'function') { - callback = initValue as (err: Error | null) => void;; + callback = initValue as (err: Error | null) => void; initValue = undefined; } if (typeof forceCreation === 'function') { - callback = forceCreation as (err: Error | null) => void;; + callback = forceCreation as (err: Error | null) => void; forceCreation = undefined; } if (isObject(initValue)) { @@ -3235,7 +3477,10 @@ export default function sandBox( let aObj: ioBroker.StateObject | null | undefined; try { - aObj = (await adapter.getForeignObjectAsync(alias.id as string)) as ioBroker.StateObject | null | undefined; + aObj = (await adapter.getForeignObjectAsync(alias.id as string)) as + | ioBroker.StateObject + | null + | undefined; } catch (e) { // ignore } @@ -3279,7 +3524,7 @@ export default function sandBox( // try to create the linked states let aObj: ioBroker.StateObject | null | undefined; try { - aObj = await adapter.getForeignObjectAsync(readId) as ioBroker.StateObject | null | undefined; + aObj = (await adapter.getForeignObjectAsync(readId)) as ioBroker.StateObject | null | undefined; } catch (e) { // ignore } @@ -3303,7 +3548,10 @@ export default function sandBox( } if (writeId && _common.write !== false) { try { - aObj = await adapter.getForeignObjectAsync(writeId) as ioBroker.StateObject | null | undefined; + aObj = (await adapter.getForeignObjectAsync(writeId)) as + | ioBroker.StateObject + | null + | undefined; } catch (e) { // ignore } @@ -3407,7 +3655,14 @@ export default function sandBox( !states[id] && states[`${adapter.namespace}.${id}`] === undefined ) { - states[id] = { val: null, ack: true, lc: Date.now(), ts: Date.now(), q: 0, from: `system.adapter.${adapter.namespace}` }; + states[id] = { + val: null, + ack: true, + lc: Date.now(), + ts: Date.now(), + q: 0, + from: `system.adapter.${adapter.namespace}`, + }; } if (typeof callback === 'function') { try { @@ -3485,13 +3740,13 @@ export default function sandBox( _adapter: string, cmd: string, msg?: any, - options?: Record | ((result: any) => void), - callback: (result: any, options: Record, _adapter: string) => void, + options?: Record | ((result: any, options: Record, _adapter: string) => void), + callback?: (result: any, options: Record, _adapter: string) => void, ): void { const defaultTimeout = 20000; if (typeof options === 'function') { - callback = options as (result: any) => void; + callback = options as (result: any, options: Record, _adapter: string) => void; options = { timeout: defaultTimeout }; } @@ -3592,7 +3847,12 @@ export default function sandBox( ); } }, - sendto: function (_adapter: string, cmd: string, msg: any, callback?: (result: any, options: Record, _adapter: string) => void): void { + sendto: function ( + _adapter: string, + cmd: string, + msg: any, + callback?: (result: any, options: Record, _adapter: string) => void, + ): void { return sandbox.sendTo(_adapter, cmd, msg, callback); }, sendToAsync: function (_adapter: string, cmd: string, msg?: any, options?: Record): Promise { @@ -3718,12 +3978,12 @@ export default function sandBox( sandbox.log(`Invalid callback for setImmediate! - ${typeof callback}`, 'error'); } }, - cb: function (callback) { - return function () { - if (context.scripts[name] && context.scripts[name]._id === sandbox._id) { + cb: function (callback: (args: any[]) => void): (args: any[]) => void { + return function (args: any[]) { + if (context.scripts[name]?._id === sandbox._id) { if (typeof callback === 'function') { try { - callback.apply(this, arguments); + callback.apply(this, args); } catch (e) { errorInCallback(e); } @@ -3733,74 +3993,110 @@ export default function sandBox( } }; }, - compareTime: function (startTime, endTime, operation, time) { - let pos; + compareTime: function ( + startTime: iobJS.AstroDate | string | Date | number, + endTime: iobJS.AstroDate | string | Date | number | null, + operation: 'between' | 'not between' | '<' | '<=' | '>' | '>=' | '==' | '<>' | '!=', + time?: iobJS.AstroDate | string | Date | number, + ): boolean { if (startTime && typeof startTime === 'string') { - if ((pos = consts.astroListLow.indexOf(startTime.toLowerCase())) !== -1) { - startTime = sandbox.getAstroDate(consts.astroList[pos]); - startTime = startTime.toLocaleTimeString([], { + const pos = consts.astroListLow.indexOf(startTime.toLowerCase()); + if (pos !== -1) { + const aTime = sandbox.getAstroDate(consts.astroList[pos]); + startTime = aTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false, }); } - } else if (startTime && isObject(startTime) && startTime.astro) { - startTime = sandbox.getAstroDate(startTime.astro, startTime.date || new Date(), startTime.offset || 0); - startTime = startTime.toLocaleTimeString([], { + } else if (startTime && isObject(startTime) && (startTime as iobJS.AstroDate).astro) { + const aTime = sandbox.getAstroDate( + (startTime as iobJS.AstroDate).astro, + (startTime as iobJS.AstroDate).date || new Date(), + (startTime as iobJS.AstroDate).offset || 0, + ); + startTime = aTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false, }); } + if (endTime && typeof endTime === 'string') { - if ((pos = consts.astroListLow.indexOf(endTime.toLowerCase())) !== -1) { - endTime = sandbox.getAstroDate(consts.astroList[pos]); - endTime = endTime.toLocaleTimeString([], { + const pos = consts.astroListLow.indexOf(endTime.toLowerCase()); + if (pos !== -1) { + const aTime = sandbox.getAstroDate(consts.astroList[pos]); + endTime = aTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false, }); } - } else if (endTime && isObject(endTime) && endTime.astro) { - endTime = sandbox.getAstroDate(endTime.astro, endTime.date || new Date(), endTime.offset || 0); - endTime = endTime.toLocaleTimeString([], { + } else if (endTime && isObject(endTime) && (endTime as iobJS.AstroDate).astro) { + const aTime = sandbox.getAstroDate( + (endTime as iobJS.AstroDate).astro, + (endTime as iobJS.AstroDate).date || new Date(), + (endTime as iobJS.AstroDate).offset || 0, + ); + endTime = aTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false, }); } + + // --- Convert "time" to number + let nTime: number | undefined; + // maybe it is astro date like 'sunrise' or 'sunset' if (time && typeof time === 'string') { - if ((pos = consts.astroListLow.indexOf(time.toLowerCase())) !== -1) { - time = sandbox.getAstroDate(consts.astroList[pos]); + const pos = consts.astroListLow.indexOf(time.toLowerCase()); + if (pos !== -1) { + nTime = sandbox.getAstroDate(consts.astroList[pos]).getTime(); } - } else if (time && isObject(time) && time.astro) { - time = sandbox.getAstroDate(time.astro, time.date || new Date(), time.offset || 0); + } else if (time && isObject(time) && (time as iobJS.AstroDate).astro) { + nTime = sandbox + .getAstroDate( + (time as iobJS.AstroDate).astro, + (time as iobJS.AstroDate).date || new Date(), + (time as iobJS.AstroDate).offset || 0, + ) + .getTime(); } let daily = true; if (time) { daily = false; } - if (time && !isObject(time)) { - if (typeof time === 'string' && !time.includes(' ') && !time.includes('T')) { - const parts = time.split(':'); - time = new Date(); - time.setHours(parseInt(parts[0], 10)); - time.setMinutes(parseInt(parts[1], 10)); - time.setMilliseconds(0); - - if (parts.length === 3) { - time.setSeconds(parseInt(parts[2], 10)); + // if not astro date + if (!nTime) { + if (time && !isObject(time)) { + if (typeof time === 'string' && !time.includes(' ') && !time.includes('T')) { + const parts = time.split(':'); + const oTime = new Date(); + oTime.setHours(parseInt(parts[0], 10)); + oTime.setMinutes(parseInt(parts[1], 10)); + oTime.setMilliseconds(0); + + if (parts.length === 3) { + oTime.setSeconds(parseInt(parts[2], 10)); + } else { + oTime.setSeconds(0); + } + nTime = oTime.getTime(); } else { - time.setSeconds(0); + nTime = new Date(time as string | number).getTime(); } + } else if (!time) { + const oTime = new Date(); + oTime.setMilliseconds(0); + nTime = oTime.getTime(); } else { - time = new Date(time); + // If Date + nTime = (time as Date).getTime(); } - } else if (!time) { - time = new Date(); - time.setMilliseconds(0); } + // --- End of conversion "time" to number + let nStartTime: number; if (typeof startTime === 'string') { if (!startTime.includes(' ') && !startTime.includes('T')) { @@ -3821,10 +4117,11 @@ export default function sandBox( } } else { daily = false; - startTime = new Date(startTime); + startTime = new Date(startTime as number | Date); } - startTime = startTime.getTime(); + nStartTime = startTime.getTime(); + let nEndTime: number | null; if (endTime && typeof endTime === 'string') { if (!endTime.includes(' ') && !endTime.includes('T')) { const parts = endTime.split(':'); @@ -3844,89 +4141,71 @@ export default function sandBox( } } else if (endTime) { daily = false; - endTime = new Date(endTime); + endTime = new Date(endTime as number | Date); } else { endTime = null; } if (endTime) { - endTime = endTime.getTime(); + nEndTime = (endTime as Date).getTime(); + } else { + nEndTime = null; } if (operation === 'between') { - if (endTime) { - if (typeof time === 'object') { - time = time.getTime(); - } - if (typeof startTime === 'object') { - startTime = startTime.getTime(); + if (nEndTime) { + if (nStartTime > nEndTime && daily) { + return !(nTime >= nEndTime && nTime < nStartTime); } - if (typeof endTime === 'object') { - endTime = endTime.getTime(); - } - - if (startTime > endTime && daily) { - return !(time >= endTime && time < startTime); - } else { - return time >= startTime && time < endTime; - } - } else { - sandbox.log(`missing or unrecognized endTime expression: ${endTime}`, 'warn'); - return false; + return nTime >= nStartTime && nTime < nEndTime; } - } else if (operation === 'not between') { - if (endTime) { - if (typeof time === 'object') { - time = time.getTime(); - } - if (typeof startTime === 'object') { - startTime = startTime.getTime(); - } - if (typeof endTime === 'object') { - endTime = endTime.getTime(); - } - if (startTime > endTime && daily) { - return time >= endTime && time < startTime; - } else { - return !(time >= startTime && time < endTime); + sandbox.log(`missing or unrecognized endTime expression: ${endTime}`, 'warn'); + return false; + } + + if (operation === 'not between') { + if (nEndTime) { + if (nStartTime > nEndTime && daily) { + return nTime >= nEndTime && nTime < nStartTime; } - } else { - sandbox.log(`missing or unrecognized endTime expression: ${endTime}`, 'warn'); - return false; - } - } else { - if (typeof time === 'object') { - time = time.getTime(); - } - if (typeof startTime === 'object') { - startTime = startTime.getTime(); - } - - if (operation === '>') { - return time > startTime; - } else if (operation === '>=') { - return time >= startTime; - } else if (operation === '<') { - return time < startTime; - } else if (operation === '<=') { - return time <= startTime; - } else if (operation === '==') { - return time === startTime; - } else if (operation === '<>') { - return time !== startTime; - } else { - sandbox.log(`Invalid operator: ${operation}`, 'warn'); - return false; + return !(nTime >= nStartTime && nTime < nEndTime); } + sandbox.log(`missing or unrecognized endTime expression: ${endTime}`, 'warn'); + return false; + } + + if (operation === '>') { + return nTime > nStartTime; + } + if (operation === '>=') { + return nTime >= nStartTime; + } + if (operation === '<') { + return nTime < nStartTime; + } + if (operation === '<=') { + return nTime <= nStartTime; + } + if (operation === '==') { + return nTime === nStartTime; + } + if (operation === '<>' || operation === '!=') { + return nTime !== nStartTime; } + sandbox.log(`Invalid operator: ${operation}`, 'warn'); + return false; }, - onStop: function (cb, timeout) { + onStop: function (cb: () => void, timeout?: number): void { sandbox.verbose && sandbox.log(`onStop(timeout=${timeout})`, 'info'); script.onStopCb = cb; script.onStopTimeout = timeout || 1000; }, - formatValue: function (value, decimals, format) { + formatValue: function (value: number | string, decimals: number | string, format?: string): string { + if (typeof decimals === 'string') { + format = decimals; + decimals = 0; + } if (!format) { if (adapter.isFloatComma !== undefined) { format = adapter.isFloatComma ? '.,' : ',.'; @@ -3934,9 +4213,13 @@ export default function sandBox( format = objects['system.config'].common.isFloatComma ? '.,' : ',.'; } } - return adapter.formatValue(value, decimals, format); + return adapter.formatValue(value, decimals as number, format); }, - formatDate: function (date, format, language) { + formatDate: function ( + date: Date | string | number | iobJS.AstroDate, + format?: string, + language?: ioBroker.Languages, + ): string { if (!format) { if (adapter.dateFormat) { format = adapter.dateFormat; @@ -3948,8 +4231,24 @@ export default function sandBox( } format = format || 'DD.MM.YYYY'; } + // maybe it is astro date like 'sunrise' or 'sunset' + if (date && typeof date === 'string') { + const pos = consts.astroListLow.indexOf(date.toLowerCase()); + if (pos !== -1) { + date = sandbox.getAstroDate(consts.astroList[pos]).getTime(); + } + } else if (date && isObject(date) && (date as iobJS.AstroDate).astro) { + date = sandbox + .getAstroDate( + (date as iobJS.AstroDate).astro, + (date as iobJS.AstroDate).date || new Date(), + (date as iobJS.AstroDate).offset || 0, + ) + .getTime(); + } + if (format.match(/[WНOО]+/)) { - let text = adapter.formatDate(date, format); + let text: string = adapter.formatDate(date as Date | string | number, format); if (!language || !consts.dayOfWeeksFull[language]) { language = adapter.language || @@ -3963,11 +4262,11 @@ export default function sandBox( } if (typeof date === 'number' || typeof date === 'string') { date = new Date(date); - } else if (typeof date.getMonth !== 'function') { + } else if (typeof (date as Date).getMonth !== 'function') { sandbox.log(`Invalid date object provided: ${JSON.stringify(date)}`, 'error'); return 'Invalid date'; } - const d = date.getDay(); + const d: number = (date as Date).getDay(); text = text.replace('НН', consts.dayOfWeeksFull[language][d]); let initialText = text; text = text.replace('WW', consts.dayOfWeeksFull[language][d]); @@ -3978,7 +4277,7 @@ export default function sandBox( text = text.replace('Н', consts.dayOfWeeksShort[language][d]); text = text.replace('Н', consts.dayOfWeeksShort[language][d]); - const m = date.getMonth(); + const m: number = (date as Date).getMonth(); initialText = text; text = text.replace('OOO', consts.monthFullGen[language][m]); text = text.replace('ООО', consts.monthFullGen[language][m]); @@ -3989,11 +4288,10 @@ export default function sandBox( text = text.replace('O', consts.monthShort[language][m]); } return text; - } else { - return adapter.formatDate(date, format); } + return adapter.formatDate(date as string | number | Date, format); }, - formatTimeDiff: function (diff, format) { + formatTimeDiff: function (diff: number, format?: string): string { if (!format) { format = 'hh:mm:ss'; } @@ -4012,8 +4310,8 @@ export default function sandBox( if (/DD|TT|ДД|D|T|Д/.test(text)) { const days = Math.floor(diff / day); - text = text.replace(/DD|TT|ДД/, days < 10 ? `0${days}` : days); - text = text.replace(/D|T|Д/, days); + text = text.replace(/DD|TT|ДД/, days < 10 ? `0${days}` : days.toString()); + text = text.replace(/[DTД]/, days.toString()); sandbox.verbose && sandbox.log(`formatTimeDiff(format=${format}, text=${text}, days=${days})`, 'debug'); @@ -4023,8 +4321,8 @@ export default function sandBox( if (/hh|SS|чч|h|S|ч/.test(text)) { const hours = Math.floor(diff / hour); - text = text.replace(/hh|SS|чч/, hours < 10 ? `0${hours}` : hours); - text = text.replace(/h|S|ч/, hours); + text = text.replace(/hh|SS|чч/, hours < 10 ? `0${hours}` : hours.toString()); + text = text.replace(/[hSч]/, hours.toString()); sandbox.verbose && sandbox.log(`formatTimeDiff(format=${format}, text=${text}, hours=${hours})`, 'debug'); @@ -4035,8 +4333,8 @@ export default function sandBox( if (/mm|мм|m|м/.test(text)) { const minutes = Math.floor(diff / minute); - text = text.replace(/mm|мм/, minutes < 10 ? `0${minutes}` : minutes); - text = text.replace(/m|м/, minutes); + text = text.replace(/mm|мм/, minutes < 10 ? `0${minutes}` : minutes.toString()); + text = text.replace(/[mм]/, minutes.toString()); sandbox.verbose && sandbox.log(`formatTimeDiff(format=${format}, text=${text}, minutes=${minutes})`, 'debug'); @@ -4047,29 +4345,30 @@ export default function sandBox( if (/ss|сс|мм|s|с/.test(text)) { const seconds = Math.floor(diff / second); - text = text.replace(/ss|сс/, seconds < 10 ? `0${seconds}` : seconds); - text = text.replace(/s|с/, seconds); + text = text.replace(/ss|сс/, seconds < 10 ? `0${seconds}` : seconds.toString()); + text = text.replace(/[sс]/, seconds.toString()); sandbox.verbose && sandbox.log(`formatTimeDiff(format=${format}, text=${text}, seconds=${seconds})`, 'debug'); - - diff -= seconds * second; + // diff -= seconds * second; // no milliseconds } sandbox.verbose && sandbox.log(`formatTimeDiff(format=${format}, text=${text})`, 'debug'); return neg ? `-${text}` : text; }, - getDateObject: function (date) { + getDateObject: function (date: Date | number | string): Date { if (isObject(date)) { - return date; + return date as Date; } if (typeof date === 'undefined') { return new Date(); - } else if (typeof date !== 'string') { + } + if (typeof date !== 'string') { return new Date(date); } + // If only hours: 20, 2 if (date.match(/^\d?\d$/)) { const _now = new Date(); date = `${_now.getFullYear()}-${_now.getMonth() + 1}-${_now.getDate()} ${date}:00`; @@ -4081,9 +4380,14 @@ export default function sandBox( return new Date(date); }, - writeFile: function (_adapter, fileName, data, callback) { + writeFile: function ( + _adapter: string, + fileName: string, + data: string | Buffer | ((err: Error) => void), + callback?: (err: Error) => void, + ): void { if (typeof data === 'function' || !data) { - callback = data; + callback = data as (err: Error) => void; data = fileName; fileName = _adapter; _adapter = null; @@ -4096,7 +4400,7 @@ export default function sandBox( 'warn', ); if (typeof callback === 'function') { - setTimeout(function () { + setTimeout(function (): void { try { callback.call(sandbox); } catch (e) { @@ -4109,26 +4413,34 @@ export default function sandBox( adapter.writeFile(_adapter, fileName, data, callback); } }, - readFile: function (_adapter, fileName, callback) { + readFile: function ( + _adapter: string, + fileName: string | ((err: Error, data?: Buffer | string, mimeType?: string) => void), + callback?: (err: Error, data?: Buffer | string, mimeType?: string) => void, + ): void { if (typeof fileName === 'function') { - callback = fileName; + callback = fileName as (err: Error, data: Buffer | string) => void; fileName = _adapter; _adapter = null; } _adapter = _adapter || '0_userdata.0'; sandbox.verbose && sandbox.log(`readFile(adapter=${_adapter}, fileName=${fileName})`, 'info'); - adapter.fileExists(_adapter, fileName, (error, result) => { + adapter.fileExists(_adapter, fileName, (error: Error | null, result: boolean): void => { if (error) { callback(error); } else if (!result) { - callback('Not exists'); + callback(new Error('Not exists')); } else { adapter.readFile(_adapter, fileName, callback); } }); }, - unlink: function (_adapter, fileName, callback) { + unlink: function ( + _adapter: string, + fileName: string | ((err: Error) => void), + callback?: (err: Error) => void, + ): void { if (typeof fileName === 'function') { callback = fileName; fileName = _adapter; @@ -4142,7 +4454,7 @@ export default function sandBox( 'warn', ); if (typeof callback === 'function') { - setTimeout(function () { + setTimeout(function (): void { try { callback.call(sandbox); } catch (e) { @@ -4155,10 +4467,14 @@ export default function sandBox( adapter.unlink(_adapter, fileName, callback); } }, - delFile: function (_adapter, fileName, callback) { - return sandbox.unlink(_adapter, fileName, callback); + delFile: function ( + _adapter: string, + fileName: string | ((err: Error) => void), + callback?: (err: Error) => void, + ): void { + return sandbox.unlink(_adapter, fileName as string, callback); }, - rename: function (_adapter, oldName, newName, callback) { + rename: function (_adapter: string, oldName: string, newName: string, callback?: (err: Error) => void) { _adapter = _adapter || '0_userdata.0'; if (debug) { @@ -4181,13 +4497,34 @@ export default function sandBox( adapter.rename(_adapter, oldName, newName, callback); } }, - renameFile: function (_adapter, oldName, newName, callback) { + renameFile: function ( + _adapter: string, + oldName: string, + newName: string, + callback?: (err: Error) => void, + ): void { return sandbox.rename(_adapter, oldName, newName, callback); }, - getHistory: function (instance, options, callback) { + getHistory: function ( + instance: string | (ioBroker.GetHistoryOptions & { id: string; timeout?: number | string }), + options: + | (ioBroker.GetHistoryOptions & { id?: string; timeout?: number | string }) + | ioBroker.GetHistoryCallback, + callback?: ( + error: Error | null, + result?: ioBroker.GetHistoryResult | null, + options?: ioBroker.GetHistoryOptions & { id: string; timeout?: number | string }, + instance?: string, + ) => void, + ): void { if (isObject(instance)) { - callback = options; - options = instance; + callback = options as ( + error: Error | null, + result?: ioBroker.GetHistoryResult | null, + options?: ioBroker.GetHistoryOptions & { id: string; timeout?: number | string }, + instance?: string, + ) => void; + options = instance as ioBroker.GetHistoryOptions & { id?: string; timeout?: number | string }; instance = null; } @@ -4197,19 +4534,23 @@ export default function sandBox( if (!isObject(options)) { return sandbox.log('No options found!', 'error'); } - if (!options.id) { + if (!(options as ioBroker.GetHistoryOptions & { id?: string; timeout?: number | string }).id) { return sandbox.log('No ID found!', 'error'); } - const timeoutMs = parseInt(options?.timeout, 10) || 20000; + const timeoutMs = + parseInt( + (options as ioBroker.GetHistoryOptions & { id?: string; timeout?: number }) + ?.timeout as unknown as string, + 10, + ) || 20000; if (!instance) { + // @ts-expect-error defaultHistory is private attribute of adapter. Fix later if (adapter.defaultHistory) { + // @ts-expect-error defaultHistory is private attribute of adapter. Fix later instance = adapter.defaultHistory; } else { - instance = - objects['system.config'] && objects['system.config'].common - ? objects['system.config'].common.defaultHistory - : null; + instance = objects['system.config']?.common?.defaultHistory || null; } } @@ -4225,8 +4566,8 @@ export default function sandBox( } return; } - if (instance.startsWith('system.adapter.')) { - instance = instance.substring('system.adapter.'.length); + if ((instance as string).startsWith('system.adapter.')) { + instance = (instance as string).substring('system.adapter.'.length); } if (!objects[`system.adapter.${instance}`]) { @@ -4252,62 +4593,76 @@ export default function sandBox( } }, timeoutMs); - adapter.sendTo(instance, 'getHistory', { id: options.id, options: options }, result => { - timeout && clearTimeout(timeout); - - sandbox.verbose && result && result.error && sandbox.log(`getHistory => ${result.error}`, 'error'); - sandbox.verbose && - result && - result.result && - sandbox.log(`getHistory => ${result.result.length} items`, 'debug'); + adapter.sendTo( + instance as string, + 'getHistory', + { + id: (options as ioBroker.GetHistoryOptions & { id: string; timeout?: number | string }).id, + options, + }, + (res: any): void => { + timeout && clearTimeout(timeout); + const result: { + error?: Error; + result?: ioBroker.GetHistoryResult; + step?: number; + sessionId?: string; + } = res; + + sandbox.verbose && result?.error && sandbox.log(`getHistory => ${result.error}`, 'error'); + sandbox.verbose && + result?.result && + sandbox.log(`getHistory => ${result.result.length} items`, 'debug'); - if (typeof callback === 'function') { - try { - callback.call(sandbox, result.error, result.result, options, instance); - } catch (e) { - errorInCallback(e); + if (typeof callback === 'function') { + try { + callback.call(sandbox, result.error, result.result, options, instance); + } catch (e) { + errorInCallback(e); + } + callback = null; } - callback = null; - } - }); + }, + ); }, - runScript: function (scriptName, callback) { + runScript: function (scriptName: string, callback?: (err?: Error | null) => void): boolean { scriptName = scriptName || name; - if (!scriptName.match(/^script\.js\./)) scriptName = `script.js.${scriptName}`; + if (!scriptName.match(/^script\.js\./)) { + scriptName = `script.js.${scriptName}`; + } // start another script if (!objects[scriptName] || !objects[scriptName].common) { sandbox.log(`Cannot start "${scriptName}", because not found`, 'error'); return false; - } else { - if (debug) { - sandbox.log( - `runScript(scriptName=${scriptName}) - ${words._('was not executed, while debug mode is active')}`, - 'warn', + } + if (debug) { + sandbox.log( + `runScript(scriptName=${scriptName}) - ${words._('was not executed, while debug mode is active')}`, + 'warn', + ); + typeof callback === 'function' && callback(); + return true; + } + if (objects[scriptName].common.enabled) { + objects[scriptName].common.enabled = false; + adapter.extendForeignObject(scriptName, { common: { enabled: false } }, (/* err, obj */) => { + adapter.extendForeignObject( + scriptName, + { common: { enabled: true } }, + err => typeof callback === 'function' && callback(err), ); - typeof callback === 'function' && callback(); - } else { - if (objects[scriptName].common.enabled) { - objects[scriptName].common.enabled = false; - adapter.extendForeignObject(scriptName, { common: { enabled: false } }, (/* err, obj */) => { - adapter.extendForeignObject( - scriptName, - { common: { enabled: true } }, - err => typeof callback === 'function' && callback(err), - ); - scriptName = null; - }); - } else { - adapter.extendForeignObject( - scriptName, - { common: { enabled: true } }, - err => typeof callback === 'function' && callback(err), - ); - } - } + scriptName = null; + }); return true; } + adapter.extendForeignObject( + scriptName, + { common: { enabled: true } }, + err => typeof callback === 'function' && callback(err), + ); + return true; }, - runScriptAsync: function (scriptName) { + runScriptAsync: function (scriptName: string): Promise { return new Promise((resolve, reject) => { const result = sandbox.runScript(scriptName, err => { if (err) { @@ -4322,95 +4677,105 @@ export default function sandBox( } }); }, - startScript: function (scriptName, ignoreIfStarted, callback) { + startScript: function ( + scriptName: string, + ignoreIfStarted?: boolean | ((err: Error | null, started: boolean) => void), + callback?: (err: Error | null, started: boolean) => void, + ): boolean { if (typeof ignoreIfStarted === 'function') { - callback = ignoreIfStarted; + callback = ignoreIfStarted as (err: Error | null, started: boolean) => void; ignoreIfStarted = false; } scriptName = scriptName || name; - if (!scriptName.match(/^script\.js\./)) scriptName = `script.js.${scriptName}`; + if (!scriptName.match(/^script\.js\./)) { + scriptName = `script.js.${scriptName}`; + } // start another script if (!objects[scriptName] || !objects[scriptName].common) { sandbox.log(`Cannot start "${scriptName}", because not found`, 'error'); return false; - } else { - if (debug) { - sandbox.log( - `startScript(scriptName=${scriptName}) - ${words._('was not executed, while debug mode is active')}`, - 'warn', - ); - typeof callback === 'function' && callback(null, false); - } else { - if (objects[scriptName].common.enabled) { - if (!ignoreIfStarted) { - objects[scriptName].common.enabled = false; - adapter.extendForeignObject(scriptName, { common: { enabled: false } }, () => { - adapter.extendForeignObject( - scriptName, - { common: { enabled: true } }, - err => typeof callback === 'function' && callback(err, true), - ); - scriptName = null; - }); - } else if (typeof callback === 'function') { - callback(null, false); - } - } else { - adapter.extendForeignObject(scriptName, { common: { enabled: true } }, err => { - typeof callback === 'function' && callback(err, true); - }); - } + } + if (debug) { + sandbox.log( + `startScript(scriptName=${scriptName}) - ${words._('was not executed, while debug mode is active')}`, + 'warn', + ); + typeof callback === 'function' && callback(null, false); + return true; + } + if (objects[scriptName].common.enabled) { + if (!ignoreIfStarted) { + objects[scriptName].common.enabled = false; + adapter.extendForeignObject(scriptName, { common: { enabled: false } }, () => { + adapter.extendForeignObject( + scriptName, + { common: { enabled: true } }, + err => typeof callback === 'function' && callback(err, true), + ); + scriptName = null; + }); + } else if (typeof callback === 'function') { + callback(null, false); } return true; } + adapter.extendForeignObject(scriptName, { common: { enabled: true } }, err => { + typeof callback === 'function' && callback(err, true); + }); + return true; }, - startScriptAsync: function (scriptName, ...args) { + startScriptAsync: function (scriptName: string, ignoreIfStarted?: boolean): Promise { return new Promise((resolve, reject) => { - const result = sandbox.startScript(scriptName, ...args, (err, started) => { - if (err) { - reject(err); - } else { - resolve(started); - } - }); + const result = sandbox.startScript( + scriptName, + ignoreIfStarted, + (err: Error | null, started: boolean): void => { + if (err) { + reject(err); + } else { + resolve(started); + } + }, + ); if (result === false) { reject(`Script ${scriptName} was not found!`); } }); }, - stopScript: function (scriptName, callback) { + stopScript: function (scriptName: string, callback?: (err: Error | null, stopped: boolean) => void): boolean { scriptName = scriptName || name; - if (!scriptName.match(/^script\.js\./)) scriptName = `script.js.${scriptName}`; + if (!scriptName.match(/^script\.js\./)) { + scriptName = `script.js.${scriptName}`; + } // stop another script if (!objects[scriptName] || !objects[scriptName].common) { sandbox.log(`Cannot stop "${scriptName}", because not found`, 'error'); return false; - } else { - if (debug) { - sandbox.log( - `stopScript(scriptName=${scriptName}) - ${words._('was not executed, while debug mode is active')}`, - 'warn', - ); - typeof callback === 'function' && callback(null, false); - } else { - if (objects[scriptName].common.enabled) { - objects[scriptName].common.enabled = false; - adapter.extendForeignObject(scriptName, { common: { enabled: false } }, err => { - typeof callback === 'function' && callback(err, true); - scriptName = null; - }); - } else if (typeof callback === 'function') { - callback(null, false); - } - } + } + if (debug) { + sandbox.log( + `stopScript(scriptName=${scriptName}) - ${words._('was not executed, while debug mode is active')}`, + 'warn', + ); + typeof callback === 'function' && callback(null, false); return true; } + if (objects[scriptName].common.enabled) { + objects[scriptName].common.enabled = false; + adapter.extendForeignObject(scriptName, { common: { enabled: false } }, err => { + typeof callback === 'function' && callback(err, true); + scriptName = null; + }); + } else if (typeof callback === 'function') { + callback(null, false); + } + return true; }, - stopScriptAsync: function (scriptName) { + stopScriptAsync: function (scriptName: string): Promise { return new Promise((resolve, reject) => { - const result = sandbox.stopScript(scriptName, (err, stopped) => { + const result = sandbox.stopScript(scriptName, (err: Error | null, stopped: boolean): void => { if (err) { reject(err); } else { @@ -4422,18 +4787,17 @@ export default function sandBox( } }); }, - isScriptActive: function (scriptName) { + isScriptActive: function (scriptName: string): boolean { if (!scriptName.match(/^script\.js\./)) { scriptName = `script.js.${scriptName}`; } if (!objects[scriptName] || !objects[scriptName].common) { sandbox.log('Script does not exist', 'error'); return false; - } else { - return objects[scriptName].common.enabled; } + return objects[scriptName].common.enabled; }, - startInstanceAsync: async function (instanceName) { + startInstanceAsync: async function (instanceName: string): Promise { const objInstanceId = `system.adapter.${instanceName}`; const exists = await adapter.foreignObjectExists(objInstanceId); @@ -4446,16 +4810,15 @@ export default function sandBox( sandbox.verbose && sandbox.log(`startInstanceAsync (instanceName=${instanceName})`, 'info'); return true; - } else { - sandbox.log(`Cannot start instance "${instanceName}", because already running`, 'warn'); } + sandbox.log(`Cannot start instance "${instanceName}", because already running`, 'warn'); } else { sandbox.log(`Cannot start instance "${instanceName}", because not found`, 'error'); } return false; }, - restartInstanceAsync: async function (instanceName) { + restartInstanceAsync: async function (instanceName: string): Promise { const objInstanceId = `system.adapter.${instanceName}`; const exists = await adapter.foreignObjectExists(objInstanceId); @@ -4468,16 +4831,15 @@ export default function sandBox( sandbox.verbose && sandbox.log(`restartInstanceAsync (instanceName=${instanceName})`, 'info'); return true; - } else { - sandbox.log(`Cannot restart instance "${instanceName}", because not running`, 'warn'); } + sandbox.log(`Cannot restart instance "${instanceName}", because not running`, 'warn'); } else { sandbox.log(`Cannot restart instance "${instanceName}", because not found`, 'error'); } return false; }, - stopInstanceAsync: async function (instanceName) { + stopInstanceAsync: async function (instanceName: string): Promise { const objInstanceId = `system.adapter.${instanceName}`; const exists = await adapter.foreignObjectExists(objInstanceId); @@ -4490,36 +4852,35 @@ export default function sandBox( sandbox.verbose && sandbox.log(`stopInstanceAsync (instanceName=${instanceName})`, 'info'); return true; - } else { - sandbox.log(`Cannot stop instance "${instanceName}", because not running`, 'warn'); } + sandbox.log(`Cannot stop instance "${instanceName}", because not running`, 'warn'); } else { sandbox.log(`Cannot stop instance "${instanceName}", because not found`, 'error'); } return false; }, - toInt: function (val) { + toInt: function (val: boolean | string | number | 'true' | 'false'): number { if (val === true || val === 'true') { val = 1; } if (val === false || val === 'false') { val = 0; } - val = parseInt(val) || 0; + val = parseInt(val as unknown as string) || 0; return val; }, - toFloat: function (val) { + toFloat: function (val: boolean | string | number | 'true' | 'false'): number { if (val === true || val === 'true') { val = 1; } if (val === false || val === 'false') { val = 0; } - val = parseFloat(val) || 0; + val = parseFloat(val as unknown as string) || 0; return val; }, - toBoolean: function (val) { + toBoolean: function (val: boolean | string | number | 'true' | 'false'): boolean { if (val === '1' || val === 'true') { val = true; } @@ -4528,7 +4889,7 @@ export default function sandBox( } return !!val; }, - getAttr: function (obj, path) { + getAttr: function (obj: string | Record, path: string | string[]): any { if (typeof path === 'string') { path = path.split('.'); } @@ -4563,16 +4924,19 @@ export default function sandBox( if (!path.length) { return obj; - } else { - const type = typeof obj; - if (obj === null || obj === undefined || type === 'boolean' || type === 'number') { - return null; - } else { - return sandbox.getAttr(obj, path); - } } + const type = typeof obj; + if (obj === null || obj === undefined || type === 'boolean' || type === 'number') { + return null; + } + return sandbox.getAttr(obj, path); }, - messageTo: function (target, data, options, callback) { + messageTo: function ( + target: string | { instance: string | null | number; script: string | null; message: string }, + data: any, + options: { timeout?: number | string } | ((result: any, options: { timeout?: number | string }) => void), + callback?: (result: any, options: { timeout?: number | string }, instance: string | number | null) => void, + ) { const defaultTimeout = 5000; if (typeof target !== 'object') { @@ -4583,9 +4947,9 @@ export default function sandBox( options = { timeout: defaultTimeout }; } - let timeout; + let timeout: NodeJS.Timeout | null = null; if (typeof callback === 'function') { - const timeoutDuration = parseInt(options?.timeout, 10) || defaultTimeout; + const timeoutDuration = parseInt(options?.timeout as unknown as string, 10) || defaultTimeout; timeout = setTimeout(() => { timeout = null; @@ -4619,8 +4983,9 @@ export default function sandBox( 'jsMessageBus', { message: target.message, script: target.script, data }, timeout && - function (result) { + function (res: any) { timeout && clearTimeout(timeout); + const result: { result?: any; error?: Error | null } = res; sandbox.verbose && result?.result && @@ -4637,13 +5002,13 @@ export default function sandBox( }, ); } else { - // Send to all instances + // Send it to all instances context.adapter.getObjectView( 'system', 'instance', { startkey: 'system.adapter.javascript.', endkey: 'system.adapter.javascript.\u9999' }, options, - (err, res) => { + (err: Error | null, res): void => { if (err || !res) { sandbox.log(`messageTo failed: ${err.message}`, 'error'); return; @@ -4657,7 +5022,7 @@ export default function sandBox( 'jsMessageBus', { message: target.message, script: target.script, data }, timeout && - function (result) { + function (result: any): void { timeout && clearTimeout(timeout); if (typeof callback === 'function') { @@ -4679,47 +5044,54 @@ export default function sandBox( ); } }, - messageToAsync: function (target, data, options) { + messageToAsync: function ( + target: string | { instance: string | null | number; script: string | null; message: string }, + data: any, + options?: { timeout?: number | string }, + ): Promise { return new Promise((resolve, reject) => { - sandbox.messageTo(target, data, options, res => { + sandbox.messageTo(target, data, options, (res: any): void => { + const result: { error?: Error } = res; sandbox.verbose && sandbox.log(`messageTo result => ${JSON.stringify(res)}`, 'debug'); - if (!res || res.error) { - reject(res ? res.error : new Error('Unknown error')); + if (!res || result.error) { + reject(result ? result.error : new Error('Unknown error')); } else { - resolve(res); + resolve(result); } }); }); }, - onMessage: function (messageName, callback) { + onMessage: function ( + messageName: string, + callback: (data: any, cb: (result: any) => void) => void, + ): null | number { if (typeof callback !== 'function') { sandbox.log('onMessage callback is not a function', 'error'); return null; - } else { - context.messageBusHandlers[sandbox.scriptName] = context.messageBusHandlers[sandbox.scriptName] || {}; - context.messageBusHandlers[sandbox.scriptName][messageName] = - context.messageBusHandlers[sandbox.scriptName][messageName] || []; + } + context.messageBusHandlers[sandbox.scriptName] = context.messageBusHandlers[sandbox.scriptName] || {}; + context.messageBusHandlers[sandbox.scriptName][messageName] = + context.messageBusHandlers[sandbox.scriptName][messageName] || []; - const handler = { id: Date.now() + Math.floor(Math.random() * 10000), cb: callback, sandbox }; - context.messageBusHandlers[sandbox.scriptName][messageName].push(handler); + const handler = { id: Date.now() + Math.floor(Math.random() * 10000), cb: callback, sandbox }; + context.messageBusHandlers[sandbox.scriptName][messageName].push(handler); - sandbox.__engine.__subscriptionsMessage += 1; + sandbox.__engine.__subscriptionsMessage += 1; - if ( - sandbox.__engine.__subscriptionsMessage % (adapter.config as AdapterConfig).maxTriggersPerScript === - 0 - ) { - sandbox.log( - `More than ${sandbox.__engine.__subscriptionsMessage} message subscriptions registered. Check your script!`, - 'warn', - ); - } - - return handler.id; + if ( + sandbox.__engine.__subscriptionsMessage % (adapter.config as AdapterConfig).maxTriggersPerScript === + 0 + ) { + sandbox.log( + `More than ${sandbox.__engine.__subscriptionsMessage} message subscriptions registered. Check your script!`, + 'warn', + ); } + + return handler.id; }, - onMessageUnregister: function (idOrName) { + onMessageUnregister: function (idOrName: number | string): boolean { const ctx = context.messageBusHandlers[sandbox.scriptName]; let found = false; if (ctx) { @@ -4751,39 +5123,57 @@ export default function sandBox( return found; }, console: { - log: function (msg) { + log: function (msg: string): void { sandbox.log(msg, 'info'); }, - error: function (msg) { + error: function (msg: string): void { sandbox.log(msg, 'error'); }, - warn: function (msg) { + warn: function (msg: string): void { sandbox.log(msg, 'warn'); }, - info: function (msg) { + info: function (msg: string): void { sandbox.log(msg, 'info'); }, - debug: function (msg) { + debug: function (msg: string): void { sandbox.log(msg, 'debug'); }, }, - jsonataExpression: function (data, expression) { + jsonataExpression: function (data: any, expression: string): Promise { return jsonata(expression).evaluate(data); }, - wait: function (ms) { - return new Promise(resolve => sandbox.setTimeout(resolve, ms)); + wait: function (ms: number): Promise { + return new Promise((resolve: () => void): void => { + sandbox.setTimeout(resolve, ms); + }); }, - sleep: function (ms) { + sleep: function (ms: number): Promise { return sandbox.wait(ms); }, - onObject: function (pattern, callback) { + onObject: function ( + pattern: string | string[], + callback: (id: string, obj?: ioBroker.Object | null) => void, + ): SubscribeObject | SubscribeObject[] { return sandbox.subscribeObject(pattern, callback); }, - subscribeObject: function (pattern, callback) { + subscribeObject: function ( + pattern: string | string[], + callback: (id: string, obj?: ioBroker.Object | null) => void, + ): SubscribeObject | SubscribeObject[] { if (Array.isArray(pattern)) { - const result = []; + const result: { + name: string; + pattern: string; + callback: (id: string, obj?: ioBroker.Object | null) => void; + }[] = []; for (let p = 0; p < pattern.length; p++) { - result.push(sandbox.subscribeObject(pattern[p], callback)); + result.push( + sandbox.subscribeObject(pattern[p], callback) as { + name: string; + pattern: string; + callback: (id: string, obj?: ioBroker.Object | null) => void; + }, + ); } return result; } @@ -4799,17 +5189,16 @@ export default function sandBox( // source is set by regexp if defined as /regexp/ if (!pattern || typeof pattern !== 'string') { - return sandbox.log( - 'Error by subscribeObject: pattern can be only string or array of strings.', - 'error', - ); + sandbox.log('Error by subscribeObject: pattern can be only string or array of strings.', 'error'); + return; } if (typeof callback !== 'function') { - return sandbox.log('Error by subscribeObject: callback is not a function', 'error'); + sandbox.log('Error by subscribeObject: callback is not a function', 'error'); + return; } - const subs = { pattern, callback, name }; + const subs: SubscribeObject = { pattern: pattern as string, callback, name }; sandbox.verbose && sandbox.log(`subscribeObject: ${JSON.stringify(subs)}`, 'info'); adapter.subscribeForeignObjects(pattern); @@ -4818,44 +5207,41 @@ export default function sandBox( return subs; }, - unsubscribeObject: function (idOrObject) { - if (idOrObject && Array.isArray(idOrObject)) { - const result = []; - for (let t = 0; t < idOrObject.length; t++) { - result.push(sandbox.unsubscribeObject(idOrObject[t])); + unsubscribeObject: function (subObject: SubscribeObject | SubscribeObject[]): boolean | boolean[] { + if (subObject && Array.isArray(subObject)) { + const result: boolean[] = []; + for (let t = 0; t < subObject.length; t++) { + result.push(sandbox.unsubscribeObject(subObject[t]) as boolean); } return result; } - sandbox.verbose && sandbox.log(`adapterUnsubscribeObject(id=${JSON.stringify(idOrObject)})`, 'info'); + sandbox.verbose && sandbox.log(`adapterUnsubscribeObject(id=${JSON.stringify(subObject)})`, 'info'); - if (isObject(idOrObject)) { - for (let i = context.subscriptionsObject.length - 1; i >= 0; i--) { - if (context.subscriptionsObject[i] === idOrObject) { - adapter.unsubscribeForeignObjects(idOrObject.pattern); - context.subscriptionsObject.splice(i, 1); - sandbox.__engine.__subscriptionsObject--; - return true; - } + for (let i = context.subscriptionsObject.length - 1; i >= 0; i--) { + if (context.subscriptionsObject[i] === subObject) { + adapter.unsubscribeForeignObjects(subObject.pattern); + context.subscriptionsObject.splice(i, 1); + sandbox.__engine.__subscriptionsObject--; + return true; } - } else { - let deleted = 0; - for (let i = context.subscriptionsObject.length - 1; i >= 0; i--) { - if ( - context.subscriptionsObject[i].name && - context.subscriptionsObject[i].pattern === idOrObject.pattern - ) { - deleted++; - adapter.unsubscribeForeignObjects(idOrObject.pattern); - context.subscriptionsObject.splice(i, 1); - sandbox.__engine.__subscriptionsObject--; - } + } + let deleted = 0; + for (let i = context.subscriptionsObject.length - 1; i >= 0; i--) { + if ( + context.subscriptionsObject[i].name && + context.subscriptionsObject[i].pattern === (subObject as SubscribeObject).pattern + ) { + deleted++; + adapter.unsubscribeForeignObjects((subObject as SubscribeObject).pattern); + context.subscriptionsObject.splice(i, 1); + sandbox.__engine.__subscriptionsObject--; } - return !!deleted; } + return !!deleted; }, // internal function to send the block debugging info to the front-end - _sendToFrontEnd: function (blockId, data) { + _sendToFrontEnd: function (blockId: string, data: any): void { if (context.rulesOpened === sandbox.scriptName) { adapter.setState( 'debug.rules', @@ -4867,7 +5253,11 @@ export default function sandBox( }; if ((adapter.config as AdapterConfig).enableSetObject) { - sandbox.setObject = function (id: string, obj: ioBroker.Object, callback?: (err?: Error | null | undefined, res?: { id: string }) => void): void { + sandbox.setObject = function ( + id: string, + obj: ioBroker.Object, + callback?: (err?: Error | null | undefined, res?: { id: string }) => void, + ): void { if (id && typeof id === 'string' && id.startsWith('system.adapter.')) { sandbox.log( `Using setObject on system object ${id} can be dangerous (protected instance attributes may be lost)`, @@ -4905,7 +5295,11 @@ export default function sandBox( }); } }; - sandbox.extendObject = function (id: string, obj: Partial, callback?: (err: Error | undefined | null, res?: { id: string }) => void): void { + sandbox.extendObject = function ( + id: string, + obj: Partial, + callback?: (err: Error | undefined | null, res?: { id: string }) => void, + ): void { if (debug) { sandbox.log( `extendObject(id=${id}, obj=${JSON.stringify(obj)}) - ${words._('was not executed, while debug mode is active')}`, @@ -4952,7 +5346,6 @@ export default function sandBox( } // promisify methods on the sandbox - /** @type {(keyof typeof sandbox)[]} */ const promisifiedMethods = [ 'existsState', 'existsObject', diff --git a/src/lib/scheduler.ts b/src/lib/scheduler.ts index facb3e0d..d621ce3e 100644 --- a/src/lib/scheduler.ts +++ b/src/lib/scheduler.ts @@ -43,7 +43,7 @@ interface SunCalc { getMoonTimes: (date: Date, latitude: number, longitude: number, inUTC?: boolean) => GetMoonTimes; } -type AstroEventName = +export type AstroEventName = | 'dawn' | 'dusk' | 'goldenHour' @@ -151,7 +151,7 @@ export type ScheduleName = { scriptName: string; }; -export default class Scheduler { +export class Scheduler { private readonly list: Record; private readonly Date: typeof Date; private readonly suncalc: SunCalc; diff --git a/main.js b/src/main.ts similarity index 80% rename from main.js rename to src/main.ts index 91abecbe..5d2ddcb5 100644 --- a/main.js +++ b/src/main.ts @@ -8,76 +8,121 @@ * Copyright (c) 2014 hobbyquaker */ -/* jshint -W097 */ -/* jshint -W083 */ -/* jshint strict: false */ -/* jslint node: true */ -/* jshint shadow: true */ -'use strict'; - -const vm = require('node:vm'); -const nodeFS = require('node:fs'); -const nodePath = require('node:path'); -const tsc = require('virtual-tsc'); -const Mirror = require('./lib/mirror'); -const fork = require('child_process').fork; - -const mods = { - fs: {}, - dgram: require('node:dgram'), - crypto: require('node:crypto'), - dns: require('node:dns'), - events: require('node:events'), - http: require('node:http'), - https: require('node:https'), - http2: require('node:http2'), - net: require('node:net'), - os: require('node:os'), - path: require('node:path'), - util: require('node:util'), - child_process: require('node:child_process'), - stream: require('node:stream'), - zlib: require('node:zlib'), - suncalc: require('suncalc2'), - axios: require('axios'), - wake_on_lan: require('wake_on_lan'), - nodeSchedule: require('node-schedule'), +import { Script } from 'node:vm'; +import { readFileSync, existsSync, statSync, writeFileSync, type Stats } from 'node:fs'; +import { join, sep, normalize } from 'node:path'; +import { fork } from 'node:child_process'; +import { setTypeScriptResolveOptions, Server } from 'virtual-tsc'; +import { isDeepStrictEqual } from 'node:util'; + +import * as dgram from 'node:dgram'; +import * as crypto from 'node:crypto'; +import * as dns from 'node:dns'; +import * as events from 'node:events'; +import * as http from 'node:http'; +import * as https from 'node:https'; +import * as http2 from 'node:http2'; +import * as net from 'node:net'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import * as util from 'node:util'; +import * as child_process from 'node:child_process'; +import * as stream from 'node:stream'; +import * as zlib from 'node:zlib'; + +import * as suncalc from 'suncalc2'; +import * as axios from 'axios'; +import * as wake_on_lan from 'wake_on_lan'; +import * as nodeSchedule from 'node-schedule'; + +import { Mirror } from './lib/mirror'; +import ProtectFs from './lib/protectFs'; + +import { getAbsoluteDefaultDataDir, Adapter, EXIT_CODES, type AdapterOptions } from '@iobroker/adapter-core'; +import { setLanguage, getLanguage } from './lib/words'; +import { sandBox } from './lib/sandbox'; +import { requestModuleNameByUrl } from './lib/nodeModulesManagement'; +import { createEventObject } from './lib/eventObj'; +import {AstroEventName, Scheduler} from './lib/scheduler'; +import { targetTsLib, tsCompilerOptions, jsDeclarationCompilerOptions } from './lib/typescriptSettings'; +import { hashSource } from './lib/tools'; +import { + resolveTypescriptLibs, + resolveTypings, + scriptIdToTSFilename, + transformScriptBeforeCompilation, + transformGlobalDeclarations, +} from '../lib/typescriptTools'; +import type {FileSubscriptionResult, JavascriptContext} from './types'; +import * as ts from 'typescript'; +import { type GetTimesResult } from 'suncalc'; + +const mods: { + fs: typeof ProtectFs; + dgram: typeof dgram; + crypto: typeof crypto; + dns: typeof dns; + events: typeof events; + http: typeof http; + https: typeof https; + http2: typeof http2; + net: typeof net; + os: typeof os; + path: typeof path; + util: typeof util; + child_process: typeof child_process; + stream: typeof stream; + zlib: typeof zlib; + suncalc: typeof suncalc; + axios: typeof axios; + wake_on_lan: typeof wake_on_lan; + nodeSchedule: typeof nodeSchedule; +} = { + fs: {} as typeof ProtectFs, + dgram, + crypto, + dns, + events, + http, + https, + http2, + net, + os, + path, + util, + child_process, + stream, + zlib, + + suncalc, + axios, + wake_on_lan, + nodeSchedule, }; /** * List of forbidden Locations for a mirror directory * relative to the default data directory * ATTENTION: the same list is also located in index_m.html!! - * - * @type {*[]} */ -const forbiddenMirrorLocations = ['backup-objects', 'files', 'backitup', '../backups', '../node_modules', '../log']; - -const utils = require('@iobroker/adapter-core'); -const words = require('./lib/words'); -const sandBox = require('./lib/sandbox'); -const { requestModuleNameByUrl } = require('./lib/nodeModulesManagement.js'); -const eventObj = require('./lib/eventObj'); -const Scheduler = require('./lib/scheduler'); -const { targetTsLib, tsCompilerOptions, jsDeclarationCompilerOptions } = require('./lib/typescriptSettings'); -const { hashSource, isObject } = require('./lib/tools'); -const { isDeepStrictEqual } = require('node:util'); -const { - resolveTypescriptLibs, - resolveTypings, - scriptIdToTSFilename, - transformScriptBeforeCompilation, - transformGlobalDeclarations, -} = require('./lib/typescriptTools'); - -const packageJson = require('./package.json'); +const forbiddenMirrorLocations: string[] = [ + 'backup-objects', + 'files', + 'backitup', + '../backups', + '../node_modules', + '../log', +]; + +const packageJson: Record = JSON.parse(readFileSync(`${__dirname}/../package.json`).toString()); const SCRIPT_CODE_MARKER = 'script.js.'; const stopCounters = {}; -let setStateCountCheckInterval = null; +let setStateCountCheckInterval: NodeJS.Timeout | null = null; + +let webstormDebug: string | undefined; +let debugMode: boolean = false; -let webstormDebug; -let debugMode; if (process.argv) { for (let a = 1; a < process.argv.length; a++) { if (process.argv[a].startsWith('--webstorm')) { @@ -88,7 +133,7 @@ if (process.argv) { console.log('No script name provided'); process.exit(300); } else { - debugMode = process.argv[a + 1]; + debugMode = !!process.argv[a + 1]; } } } @@ -97,8 +142,7 @@ if (process.argv) { const isCI = !!process.env.CI; // ambient declarations for typescript -/** @type {Record} */ -let tsAmbient; +let tsAmbient: Record; // TypeScript's scripts are only recompiled if their source hash changes. // If an adapter update fixes the compilation bugs, a user won't notice until the changes and re-saves the script. @@ -106,16 +150,16 @@ let tsAmbient; // adapter version and TypeScript version in the hash const tsSourceHashBase = `versions:adapter=${packageJson.version},typescript=${packageJson.dependencies.typescript}`; -let mirror; +let mirror: typeof Mirror | undefined; -/** @type {boolean} if logs are subscribed or not */ -let logSubscribed; +/** if logs are subscribed or not */ +let logSubscribed: boolean = false; /** - * @param {string} scriptID - The current script the declarations were generated from - * @param {string} declarations + * @param scriptID - The current script the declarations were generated from + * @param declarations */ -function provideDeclarationsForGlobalScript(scriptID, declarations) { +function provideDeclarationsForGlobalScript(scriptID: string, declarations: string): void { // Remember which declarations this global script had access to, // we need this so the editor doesn't show a duplicate identifier error if (globalDeclarations != null && globalDeclarations !== '') { @@ -137,16 +181,16 @@ function provideDeclarationsForGlobalScript(scriptID, declarations) { } // taken from here: https://stackoverflow.com/questions/11887934/how-to-check-if-dst-daylight-saving-time-is-in-effect-and-if-so-the-offset -function dstOffsetAtDate(dateInput) { - const fullYear = dateInput.getFullYear() | 0; +function dstOffsetAtDate(dateInput: Date): number { + const fullYear: number = dateInput.getFullYear() | 0; // "Leap Years are any year that can be exactly divided by 4 (2012, 2016, etc) // except if it can be exactly divided by 100, then it isn't (2100, 2200, etc) // except if it can be exactly divided by 400, then it is (2000, 2400)" // (https://www.mathsisfun.com/leap-years.html). - const isLeapYear = ((fullYear & 3) | ((fullYear / 100) & 3)) === 0 ? 1 : 0; + const isLeapYear: 1 | 0 = ((fullYear & 3) | ((fullYear / 100) & 3)) === 0 ? 1 : 0; // (fullYear & 3) = (fullYear % 4), but faster //Alternative:var isLeapYear=(new Date(currentYear,1,29,12)).getDate()===29?1:0 - const fullMonth = dateInput.getMonth() | 0; + const fullMonth: number = dateInput.getMonth() | 0; return ( // 1. We know what the time since the Epoch really is +dateInput - // same as the dateInput.getTime() method @@ -154,12 +198,12 @@ function dstOffsetAtDate(dateInput) { +new Date(fullYear, 0) - // day defaults to 1 if not explicitly zeroed // 3. Now, subtract what we would expect the time to be if daylight savings // did not exist. This yields the time-offset due to daylight savings. - ((// Calculate the day of the year in the Gregorian calendar + // Calculate the day of the year in the Gregorian calendar // The code below works based upon the facts of signed right shifts // • (x) >> n: shifts n and fills in the n highest bits with 0s // • (-x) >> n: shifts n and fills in the n highest bits with 1s // (This assumes that x is a positive integer) - ((-1 + // first day in the year is day 1 + ((((-1 + // the first day in the year is day 1 (31 & (-fullMonth >> 4)) + // January // (-11)>>4 = -1 ((28 + isLeapYear) & ((1 - fullMonth) >> 4)) + // February (31 & ((2 - fullMonth) >> 4)) + // March @@ -172,7 +216,7 @@ function dstOffsetAtDate(dateInput) { (31 & ((9 - fullMonth) >> 4)) + // October (30 & ((10 - fullMonth) >> 4)) + // November // There are no months past December: the year rolls into the next. - // Thus, fullMonth is 0-based, so it will never be 12 in Javascript + // Thus, "fullMonth" is 0-based, so it will never be 12 in JavaScript (dateInput.getDate() | 0)) & // get day of the month 0xffff) * @@ -188,7 +232,7 @@ function dstOffsetAtDate(dateInput) { ); } -function loadTypeScriptDeclarations() { +function loadTypeScriptDeclarations(): void { // try to load the typings on disk for all 3rd party modules const packages = [ 'node', // this provides auto-completion for most builtins @@ -252,7 +296,7 @@ function loadTypeScriptDeclarations() { } } -const context = { +const context: JavascriptContext = { mods, objects: {}, states: {}, @@ -290,17 +334,41 @@ const context = { leadingZeros: true, }, rulesOpened: null, //opened rules - getAbsoluteDefaultDataDir: utils.getAbsoluteDefaultDataDir, + getAbsoluteDefaultDataDir, + adapter: null, + language: 'en', + logError: function (msg: string, e: Error, offs?: number): void { + const stack = e.stack ? e.stack.toString().split('\n') : e ? e.toString() : ''; + if (!msg.includes('\n')) { + msg = msg.replace(/[: ]*$/, ': '); + } + + // errorLogFunction.error(msg + stack[0]); + context.errorLogFunction.error(msg + fixLineNo(stack[0])); + for (let i = offs || 1; i < stack.length; i++) { + if (!stack[i]) { + continue; + } + if (stack[i].match(/runInNewContext|javascript\.js:/)) { + break; + } + // adapter.log.error(fixLineNo(stack[i])); + context.errorLogFunction.error(fixLineNo(stack[i])); + } + }, }; -const regExGlobalOld = /_global$/; -const regExGlobalNew = /script\.js\.global\./; +const regExGlobalOld: RegExp = /_global$/; +const regExGlobalNew: RegExp = /script\.js\.global\./; -function checkIsGlobal(obj) { - return obj && obj.common && (regExGlobalOld.test(obj.common.name) || regExGlobalNew.test(obj._id)); +function checkIsGlobal(obj: ioBroker.ScriptObject): boolean { + return obj?.common && (regExGlobalOld.test(obj.common.name) || regExGlobalNew.test(obj._id)); } -function convertBackStringifiedValues(id: string, state: ioBroker.State | null | undefined): ioBroker.State | null | undefined { +function convertBackStringifiedValues( + id: string, + state: ioBroker.State | null | undefined, +): ioBroker.State | null | undefined { if ( state && typeof state.val === 'string' && @@ -356,10 +424,10 @@ function prepareStateObject(id: string, state: ioBroker.SettableState): ioBroker } } - return state; + return state as ioBroker.State; } -function fileMatching(sub, id, fileName) { +function fileMatching(sub: FileSubscriptionResult, id: string, fileName: string): boolean { if (sub.idRegEx) { if (!sub.idRegEx.test(id)) { return false; @@ -383,29 +451,23 @@ function fileMatching(sub, id, fileName) { } /** - * @type {Set} * Stores the IDs of script objects whose change should be ignored because * the compiled source was just updated */ -const ignoreObjectChange = new Set(); +const ignoreObjectChange: Set = new Set(); let objectsInitDone = false; let statesInitDone = false; -/** @type {ioBroker.Adapter} */ -let adapter; +let adapter: ioBroker.Adapter; -function startAdapter(options) { +function startAdapter(options: Partial = {}) { options = options || {}; Object.assign(options, { name: 'javascript', useFormatDate: true, // load float formatting - /** - * @param id { string } - * @param obj { ioBroker.Object } - */ - objectChange: (id, obj) => { + objectChange: (id: string, obj?: ioBroker.Object | null): void => { // Check if we should ignore this change (once!) because we just updated the compiled sources if (ignoreObjectChange.has(id)) { // Update the cached script object and do nothing more @@ -445,29 +507,29 @@ function startAdapter(options) { if (obj && id === 'system.config') { // set language for debug messages - if (obj.common && obj.common.language) { - words.setLanguage(obj.common.language); + if (obj.common?.language) { + setLanguage(obj.common.language); } } // update stored time format for variables.dayTime - if (id === adapter.namespace + '.variables.dayTime' && obj && obj.native) { + if (id === `${adapter.namespace}.variables.dayTime` && obj?.native) { context.timeSettings.format12 = obj.native.format12 || false; context.timeSettings.leadingZeros = obj.native.leadingZeros === undefined ? true : obj.native.leadingZeros; } // send changes to disk mirror - mirror && mirror.onObjectChange(id, obj); + mirror?.onObjectChange(id, obj); const formerObj = context.objects[id]; updateObjectContext(id, obj); // Update all Meta object data // for alias object changes on state objects we need to manually update the - // state cache value because new value is only published on next change + // state cache value because new value is only published on the next change if (obj && obj.type === 'state' && id.startsWith('alias.0.')) { - adapter.getForeignState(id, (err, state) => { + adapter.getForeignState(id, (err: Error | null, state: ioBroker.State | null | undefined): void => { if (err) { return; } @@ -568,7 +630,7 @@ function startAdapter(options) { load(id); } } else { - //if (obj.common.source !== formerObj.common.source) { + // if (obj.common.source !== formerObj.common.source) { // Source changed => restart it stopCounters[id] = stopCounters[id] ? stopCounters[id] + 1 : 1; stop( @@ -583,7 +645,7 @@ function startAdapter(options) { } }, - stateChange: (id, state) => { + stateChange: (id: string, state?: ioBroker.State | null): void => { if (context.interimStateValues[id] !== undefined) { // any update invalidates the remembered interim value delete context.interimStateValues[id]; @@ -605,7 +667,7 @@ function startAdapter(options) { return; } - const oldState = context.states[id]; + const oldState: ioBroker.State | null | undefined = context.states[id]; if (state) { if (oldState) { // enable or disable script @@ -649,7 +711,7 @@ function startAdapter(options) { context.stateIds.splice(pos, 1); } } - const _eventObj = eventObj.createEventObject( + const _eventObj = createEventObject( context, id, context.convertBackStringifiedValues(id, state), @@ -669,7 +731,7 @@ function startAdapter(options) { } }, - fileChange: (id, fileName, size) => { + fileChange: (id: string, fileName: string, size?: number): void => { // if this file matches any subscriptions for (let i = 0, l = context.subscriptionsFile.length; i < l; i++) { const sub = context.subscriptionsFile[i]; @@ -683,7 +745,7 @@ function startAdapter(options) { } }, - unload: callback => { + unload: (callback: () => void) => { debugStop().then(() => { stopTimeSchedules(); if (setStateCountCheckInterval) { @@ -693,7 +755,7 @@ function startAdapter(options) { }); }, - ready: () => { + ready: (): void => { adapter.config.maxSetStatePerMinute = parseInt(adapter.config.maxSetStatePerMinute, 10) || 1000; adapter.config.maxTriggersPerScript = parseInt(adapter.config.maxTriggersPerScript, 10) || 100; @@ -721,7 +783,7 @@ function startAdapter(options) { return null; } //Exclude event if own directory is included but not inside own node_modules - const ownNodeModulesDir = nodePath.join(__dirname, 'node_modules'); + const ownNodeModulesDir = join(__dirname, 'node_modules'); if ( !eventData.stacktrace.frames.find( frame => @@ -753,310 +815,292 @@ function startAdapter(options) { } }, - message: obj => { - if (obj) { - switch (obj.command) { - // process messageTo commands - case 'toScript': - case 'jsMessageBus': - if ( - obj.message && - (obj.message.instance === null || - obj.message.instance === undefined || - `javascript.${obj.message.instance}` === adapter.namespace || - obj.message.instance === adapter.namespace) - ) { - Object.keys(context.messageBusHandlers).forEach(name => { - // script name could be script.js.xxx or only xxx - if ( - (!obj.message.script || obj.message.script === name) && - context.messageBusHandlers[name][obj.message.message] - ) { - context.messageBusHandlers[name][obj.message.message].forEach(handler => { - const sandbox = handler.sandbox; - - sandbox.verbose && - sandbox.log(`onMessage: ${JSON.stringify(obj.message)}`, 'info'); - - try { - if (obj.callback) { - handler.cb.call(sandbox, obj.message.data, result => { - sandbox.verbose && - sandbox.log( - `onMessage result: ${JSON.stringify(result)}`, - 'info', - ); - - adapter.sendTo(obj.from, obj.command, result, obj.callback); - }); - } else { - handler.cb.call(sandbox, obj.message.data, result => { - sandbox.verbose && - sandbox.log( - `onMessage result: ${JSON.stringify(result)}`, - 'info', - ); - }); - } - } catch (e) { - adapter.setState( - `scriptProblem.${name.substring(SCRIPT_CODE_MARKER.length)}`, - true, - true, - ); - context.logError('Error in callback', e); + message: (obj: ioBroker.Message) => { + switch (obj?.command) { + // process messageTo commands + case 'toScript': + case 'jsMessageBus': + if ( + obj.message && + (obj.message.instance === null || + obj.message.instance === undefined || + `javascript.${obj.message.instance}` === adapter.namespace || + obj.message.instance === adapter.namespace) + ) { + Object.keys(context.messageBusHandlers).forEach(name => { + // script name could be script.js.xxx or only xxx + if ( + (!obj.message.script || obj.message.script === name) && + context.messageBusHandlers[name][obj.message.message] + ) { + context.messageBusHandlers[name][obj.message.message].forEach(handler => { + const sandbox = handler.sandbox; + + sandbox.verbose && sandbox.log(`onMessage: ${JSON.stringify(obj.message)}`, 'info'); + + try { + if (obj.callback) { + handler.cb.call(sandbox, obj.message.data, result => { + sandbox.verbose && + sandbox.log(`onMessage result: ${JSON.stringify(result)}`, 'info'); + + adapter.sendTo(obj.from, obj.command, result, obj.callback); + }); + } else { + handler.cb.call(sandbox, obj.message.data, result => { + sandbox.verbose && + sandbox.log(`onMessage result: ${JSON.stringify(result)}`, 'info'); + }); } - }); - } - }); - } - break; + } catch (e) { + adapter.setState( + `scriptProblem.${name.substring(SCRIPT_CODE_MARKER.length)}`, + true, + true, + ); + context.logError('Error in callback', e); + } + }); + } + }); + } + break; - case 'loadTypings': { - // Load typings for the editor - const typings = {}; + case 'loadTypings': { + // Load typings for the editor + const typings = {}; - // try to load TypeScript lib files from disk - try { - const typescriptLibs = resolveTypescriptLibs(targetTsLib); - Object.assign(typings, typescriptLibs); - } catch (e) { - /* ok, no lib then */ - } + // try to load TypeScript lib files from disk + try { + const typescriptLibs = resolveTypescriptLibs(targetTsLib); + Object.assign(typings, typescriptLibs); + } catch (e) { + /* ok, no lib then */ + } - // provide the already-loaded ioBroker typings and global script declarations - Object.assign(typings, tsAmbient); + // provide the already-loaded ioBroker typings and global script declarations + Object.assign(typings, tsAmbient); - // also provide the known global declarations for each global script - for (const globalScriptPaths of Object.keys(knownGlobalDeclarationsByScript)) { - typings[`${globalScriptPaths}.d.ts`] = knownGlobalDeclarationsByScript[globalScriptPaths]; - } + // also provide the known global declarations for each global script + for (const globalScriptPaths of Object.keys(knownGlobalDeclarationsByScript)) { + typings[`${globalScriptPaths}.d.ts`] = knownGlobalDeclarationsByScript[globalScriptPaths]; + } - if (obj.callback) { - adapter.sendTo(obj.from, obj.command, { typings }, obj.callback); - } - break; + if (obj.callback) { + adapter.sendTo(obj.from, obj.command, { typings }, obj.callback); } + break; + } - case 'calcAstroAll': { - if (obj.message) { - const sunriseOffset = - parseInt( - obj.message.sunriseOffset === undefined - ? adapter.config.sunriseOffset - : obj.message.sunriseOffset, - 10, - ) || 0; - const sunsetOffset = - parseInt( - obj.message.sunsetOffset === undefined - ? adapter.config.sunsetOffset - : obj.message.sunsetOffset, - 10, - ) || 0; - const longitude = - parseFloat( - obj.message.longitude === undefined - ? adapter.config.longitude - : obj.message.longitude, - ) || 0; - const latitude = - parseFloat( - obj.message.latitude === undefined ? adapter.config.latitude : obj.message.latitude, - ) || 0; - const today = getAstroStartOfDay(); - let astroEvents = {}; + case 'calcAstroAll': { + if (obj.message) { + const sunriseOffset = + parseInt( + obj.message.sunriseOffset === undefined + ? adapter.config.sunriseOffset + : obj.message.sunriseOffset, + 10, + ) || 0; + const sunsetOffset = + parseInt( + obj.message.sunsetOffset === undefined + ? adapter.config.sunsetOffset + : obj.message.sunsetOffset, + 10, + ) || 0; + const longitude = + parseFloat( + obj.message.longitude === undefined ? adapter.config.longitude : obj.message.longitude, + ) || 0; + const latitude = + parseFloat( + obj.message.latitude === undefined ? adapter.config.latitude : obj.message.latitude, + ) || 0; + const today = getAstroStartOfDay(); + let astroEvents: GetTimesResult & { nextSunrise: Date; nextSunset: Date } = {} as GetTimesResult & { nextSunrise: Date; nextSunset: Date }; + try { + astroEvents = mods.suncalc.getTimes(today, latitude, longitude); + } catch (e) { + adapter.log.error(`Cannot calculate astro data: ${e}`); + } + if (astroEvents) { try { - astroEvents = mods.suncalc.getTimes(today, latitude, longitude); + astroEvents.nextSunrise = getAstroEvent( + today, + obj.message.sunriseEvent || adapter.config.sunriseEvent, + obj.message.sunriseLimitStart || adapter.config.sunriseLimitStart, + obj.message.sunriseLimitEnd || adapter.config.sunriseLimitEnd, + sunriseOffset, + false, + latitude, + longitude, + true, + ); + astroEvents.nextSunset = getAstroEvent( + today, + obj.message.sunsetEvent || adapter.config.sunsetEvent, + obj.message.sunsetLimitStart || adapter.config.sunsetLimitStart, + obj.message.sunsetLimitEnd || adapter.config.sunsetLimitEnd, + sunsetOffset, + true, + latitude, + longitude, + true, + ); } catch (e) { adapter.log.error(`Cannot calculate astro data: ${e}`); } - if (astroEvents) { - try { - astroEvents.nextSunrise = getAstroEvent( - today, - obj.message.sunriseEvent || adapter.config.sunriseEvent, - obj.message.sunriseLimitStart || adapter.config.sunriseLimitStart, - obj.message.sunriseLimitEnd || adapter.config.sunriseLimitEnd, - sunriseOffset, - false, - latitude, - longitude, - true, - ); - astroEvents.nextSunset = getAstroEvent( - today, - obj.message.sunsetEvent || adapter.config.sunsetEvent, - obj.message.sunsetLimitStart || adapter.config.sunsetLimitStart, - obj.message.sunsetLimitEnd || adapter.config.sunsetLimitEnd, - sunsetOffset, - true, - latitude, - longitude, - true, - ); - } catch (e) { - adapter.log.error(`Cannot calculate astro data: ${e}`); - } - } - - const result = {}; - const keys = Object.keys(astroEvents).sort((a, b) => astroEvents[a] - astroEvents[b]); - keys.forEach(key => { - const validDate = astroEvents[key] !== null && !isNaN(astroEvents[key].getTime()); - - result[key] = { - isValidDate: validDate, - serverTime: validDate ? formatHoursMinutesSeconds(astroEvents[key]) : 'n/a', - date: validDate ? astroEvents[key].toISOString() : 'n/a', - }; - }); - - obj.callback && adapter.sendTo(obj.from, obj.command, result, obj.callback); } - break; - } - - case 'calcAstro': { - if (obj.message) { - const longitude = - parseFloat( - obj.message.longitude === undefined - ? adapter.config.longitude - : obj.message.longitude, - ) || 0; - const latitude = - parseFloat( - obj.message.latitude === undefined ? adapter.config.latitude : obj.message.latitude, - ) || 0; - const today = getAstroStartOfDay(); - - const sunriseEvent = obj.message?.sunriseEvent || adapter.config.sunriseEvent; - const sunriseLimitStart = - obj.message?.sunriseLimitStart || adapter.config.sunriseLimitStart; - const sunriseLimitEnd = obj.message?.sunriseLimitEnd || adapter.config.sunriseLimitEnd; - const sunriseOffset = - parseInt( - obj.message.sunriseOffset === undefined - ? adapter.config.sunriseOffset - : obj.message.sunriseOffset, - 10, - ) || 0; - const nextSunrise = getAstroEvent( - today, - sunriseEvent, - sunriseLimitStart, - sunriseLimitEnd, - sunriseOffset, - false, - latitude, - longitude, - true, - ); - - const sunsetEvent = obj.message?.sunsetEvent || adapter.config.sunsetEvent; - const sunsetLimitStart = obj.message?.sunsetLimitStart || adapter.config.sunsetLimitStart; - const sunsetLimitEnd = obj.message?.sunsetLimitEnd || adapter.config.sunsetLimitEnd; - const sunsetOffset = - parseInt( - obj.message.sunsetOffset === undefined - ? adapter.config.sunsetOffset - : obj.message.sunsetOffset, - 10, - ) || 0; - const nextSunset = getAstroEvent( - today, - sunsetEvent, - sunsetLimitStart, - sunsetLimitEnd, - sunsetOffset, - true, - latitude, - longitude, - true, - ); - const validDateSunrise = nextSunrise !== null && !isNaN(nextSunrise.getTime()); - const validDateSunset = nextSunset !== null && !isNaN(nextSunset.getTime()); + const result = {}; + const keys = Object.keys(astroEvents).sort((a, b) => astroEvents[a] - astroEvents[b]); + keys.forEach(key => { + const validDate = astroEvents[key] !== null && !isNaN(astroEvents[key].getTime()); - adapter.log.debug( - `calcAstro sunrise: ${sunriseEvent} -> start ${sunriseLimitStart}, end: ${sunriseLimitEnd}, offset: ${sunriseOffset} - ${validDateSunrise ? nextSunrise.toISOString() : 'n/a'}`, - ); - adapter.log.debug( - `calcAstro sunset: ${sunsetEvent} -> start ${sunsetLimitStart}, end: ${sunsetLimitEnd}, offset: ${sunsetOffset} - ${validDateSunset ? nextSunset.toISOString() : 'n/a'}`, - ); + result[key] = { + isValidDate: validDate, + serverTime: validDate ? formatHoursMinutesSeconds(astroEvents[key]) : 'n/a', + date: validDate ? astroEvents[key].toISOString() : 'n/a', + }; + }); - obj.callback && - adapter.sendTo( - obj.from, - obj.command, - { - nextSunrise: { - isValidDate: validDateSunrise, - serverTime: validDateSunrise - ? formatHoursMinutesSeconds(nextSunrise) - : 'n/a', - date: nextSunrise.toISOString(), - }, - nextSunset: { - isValidDate: validDateSunset, - serverTime: validDateSunset ? formatHoursMinutesSeconds(nextSunset) : 'n/a', - date: nextSunset.toISOString(), - }, - }, - obj.callback, - ); - } - break; + obj.callback && adapter.sendTo(obj.from, obj.command, result, obj.callback); } + break; + } - case 'debug': { - !debugMode && debugStart(obj.message); - break; - } + case 'calcAstro': { + if (obj.message) { + const longitude = + parseFloat( + obj.message.longitude === undefined ? adapter.config.longitude : obj.message.longitude, + ) || 0; + const latitude = + parseFloat( + obj.message.latitude === undefined ? adapter.config.latitude : obj.message.latitude, + ) || 0; + const today = getAstroStartOfDay(); + + const sunriseEvent = obj.message?.sunriseEvent || adapter.config.sunriseEvent; + const sunriseLimitStart = obj.message?.sunriseLimitStart || adapter.config.sunriseLimitStart; + const sunriseLimitEnd = obj.message?.sunriseLimitEnd || adapter.config.sunriseLimitEnd; + const sunriseOffset = + parseInt( + obj.message.sunriseOffset === undefined + ? adapter.config.sunriseOffset + : obj.message.sunriseOffset, + 10, + ) || 0; + const nextSunrise = getAstroEvent( + today, + sunriseEvent, + sunriseLimitStart, + sunriseLimitEnd, + sunriseOffset, + false, + latitude, + longitude, + true, + ); - case 'debugStop': { - !debugMode && debugStop().then(() => console.log('stopped')); - break; - } + const sunsetEvent = obj.message?.sunsetEvent || adapter.config.sunsetEvent; + const sunsetLimitStart = obj.message?.sunsetLimitStart || adapter.config.sunsetLimitStart; + const sunsetLimitEnd = obj.message?.sunsetLimitEnd || adapter.config.sunsetLimitEnd; + const sunsetOffset = + parseInt( + obj.message.sunsetOffset === undefined + ? adapter.config.sunsetOffset + : obj.message.sunsetOffset, + 10, + ) || 0; + const nextSunset = getAstroEvent( + today, + sunsetEvent, + sunsetLimitStart, + sunsetLimitEnd, + sunsetOffset, + true, + latitude, + longitude, + true, + ); - case 'rulesOn': { - context.rulesOpened = obj.message; - console.log(`Enable messaging for ${context.rulesOpened}`); - break; - } + const validDateSunrise = nextSunrise !== null && !isNaN(nextSunrise.getTime()); + const validDateSunset = nextSunset !== null && !isNaN(nextSunset.getTime()); - case 'rulesOff': { - // maybe if (context.rulesOpened === obj.message) - console.log(`Disable messaging for ${context.rulesOpened}`); - context.rulesOpened = null; - break; - } + adapter.log.debug( + `calcAstro sunrise: ${sunriseEvent} -> start ${sunriseLimitStart}, end: ${sunriseLimitEnd}, offset: ${sunriseOffset} - ${validDateSunrise ? nextSunrise.toISOString() : 'n/a'}`, + ); + adapter.log.debug( + `calcAstro sunset: ${sunsetEvent} -> start ${sunsetLimitStart}, end: ${sunsetLimitEnd}, offset: ${sunsetOffset} - ${validDateSunset ? nextSunset.toISOString() : 'n/a'}`, + ); - case 'getIoBrokerDataDir': { obj.callback && adapter.sendTo( obj.from, obj.command, { - dataDir: utils.getAbsoluteDefaultDataDir(), - sep: nodePath.sep, + nextSunrise: { + isValidDate: validDateSunrise, + serverTime: validDateSunrise ? formatHoursMinutesSeconds(nextSunrise) : 'n/a', + date: nextSunrise.toISOString(), + }, + nextSunset: { + isValidDate: validDateSunset, + serverTime: validDateSunset ? formatHoursMinutesSeconds(nextSunset) : 'n/a', + date: nextSunset.toISOString(), + }, }, obj.callback, ); - break; } + break; + } + + case 'debug': { + !debugMode && debugStart(obj.message); + break; + } + + case 'debugStop': { + !debugMode && debugStop().then(() => console.log('stopped')); + break; + } + + case 'rulesOn': { + context.rulesOpened = obj.message; + console.log(`Enable messaging for ${context.rulesOpened}`); + break; + } + + case 'rulesOff': { + // maybe if (context.rulesOpened === obj.message) + console.log(`Disable messaging for ${context.rulesOpened}`); + context.rulesOpened = null; + break; + } + + case 'getIoBrokerDataDir': { + obj.callback && + adapter.sendTo( + obj.from, + obj.command, + { + dataDir: getAbsoluteDefaultDataDir(), + sep, + }, + obj.callback, + ); + break; } } }, /** * If the JS-Controller catches an unhandled error, this will be called - * so we have a chance to handle it ourself. - * - * @param {Error} err + * so we have a chance to handle it ourselves. */ - error: err => { + error: (err: Error): boolean | undefined => { // Identify unhandled errors originating from callbacks in scripts // These are not caught by wrapping the execution code in try-catch if (err && typeof err.stack === 'string') { @@ -1089,11 +1133,11 @@ function startAdapter(options) { }, }); - adapter = new utils.Adapter(options); + adapter = new Adapter(options); // handler for logs adapter.on('log', (msg: any) => - Object.keys(context.logSubscriptions).forEach(name => + Object.keys(context.logSubscriptions).forEach((name: string) => context.logSubscriptions[name].forEach(handler => { if ( typeof handler.cb === 'function' && @@ -1112,7 +1156,7 @@ function startAdapter(options) { return adapter; } -function updateObjectContext(id, obj) { +function updateObjectContext(id: string, obj: ioBroker.Object | null): void { if (obj) { // add state to state ID's list if (obj.type === 'state') { @@ -1136,7 +1180,9 @@ function updateObjectContext(id, obj) { } else { // delete object from state ID's list const pos = context.stateIds.indexOf(id); - pos !== -1 && context.stateIds.splice(pos, 1); + if (pos !== -1) { + context.stateIds.splice(pos, 1); + } if (context.devices && context.channels) { const parts = id.split('.'); parts.pop(); @@ -1173,7 +1219,7 @@ function updateObjectContext(id, obj) { let nn = context.objects[id].common ? context.objects[id].common.name : ''; if (nn && typeof nn === 'object') { - nn = nn[words.getLanguage()] || nn.en; + nn = nn[getLanguage()] || nn.en; } if (n !== nn) { @@ -1187,7 +1233,7 @@ function updateObjectContext(id, obj) { } } -function main() { +function main(): void { !debugMode && patchFont().then(patched => patched && adapter.log.debug('Font patched')); adapter.log.debug(`config.subscribe (Do not subscribe all states on start): ${adapter.config.subscribe}`); @@ -1207,19 +1253,19 @@ function main() { context.errorLogFunction = webstormDebug ? console : adapter.log; activeStr = `${adapter.namespace}.scriptEnabled.`; - mods.fs = new require('./lib/protectFs')(adapter.log, utils.getAbsoluteDefaultDataDir()); + mods.fs = new ProtectFs(adapter.log, getAbsoluteDefaultDataDir()); mods['fs/promises'] = mods.fs.promises; // to avoid require('fs/promises'); // try to read TS declarations try { tsAmbient = { - 'javascript.d.ts': nodeFS.readFileSync(mods.path.join(__dirname, 'lib/javascript.d.ts'), 'utf8'), + 'javascript.d.ts': readFileSync(mods.path.join(__dirname, 'lib/javascript.d.ts'), 'utf8'), }; tsServer.provideAmbientDeclarations(tsAmbient); jsDeclarationServer.provideAmbientDeclarations(tsAmbient); } catch (e) { adapter.log.warn(`Could not read TypeScript ambient declarations: ${e.message}`); - // This should not happen, so send a error report to Sentry + // This should not happen, so send an error report to Sentry if (adapter.supportsFeature && adapter.supportsFeature('PLUGINS')) { const sentryInstance = adapter.getPluginInstance('sentry'); if (sentryInstance) { @@ -1423,11 +1469,11 @@ function main() { if (adapter.config.mirrorPath) { adapter.config.mirrorInstance = parseInt(adapter.config.mirrorInstance, 10) || 0; if (adapter.instance === adapter.config.mirrorInstance) { - const ioBDataDir = utils.getAbsoluteDefaultDataDir() + nodePath.sep; - adapter.config.mirrorPath = nodePath.normalize(adapter.config.mirrorPath); + const ioBDataDir = getAbsoluteDefaultDataDir() + sep; + adapter.config.mirrorPath = normalize(adapter.config.mirrorPath); let mirrorForbidden = false; for (let dir of forbiddenMirrorLocations) { - dir = nodePath.join(ioBDataDir, dir) + nodePath.sep; + dir = join(ioBDataDir, dir) + sep; if (dir.includes(adapter.config.mirrorPath) || adapter.config.mirrorPath.startsWith(dir)) { adapter.log.error( `The Mirror directory is not allowed to be a central ioBroker directory!`, @@ -1495,52 +1541,61 @@ let globalDeclarations = ''; // have access to, because it depends on the compile order let knownGlobalDeclarationsByScript = {}; let globalScriptLines = 0; -// let activeRegEx = null; let activeStr = ''; // enabled state prefix let dayScheduleTimer = null; // schedule for astrological day let sunScheduleTimer = null; // schedule for sun moment times let timeScheduleTimer = null; // schedule for astrological day -function getNextTimeEvent(time, useNextDay) { - const now = getAstroStartOfDay(); +function getNextTimeEvent(time: string, useNextDay?: boolean): Date { + const now: Date = getAstroStartOfDay(); let [timeHours, timeMinutes] = time.split(':'); - timeHours = parseInt(timeHours, 10); - timeMinutes = parseInt(timeMinutes, 10); + const nTimeHours = parseInt(timeHours, 10); + const nTimeMinutes = parseInt(timeMinutes, 10); if ( useNextDay && - (now.getHours() > timeHours || (now.getHours() === timeHours && now.getMinutes() > timeMinutes)) + (now.getHours() > nTimeHours || (now.getHours() === nTimeHours && now.getMinutes() > nTimeMinutes)) ) { now.setDate(now.getDate() + 1); } - now.setHours(timeHours); - now.setMinutes(timeMinutes); + now.setHours(nTimeHours); + now.setMinutes(nTimeMinutes); return now; } -function getAstroEvent(date, astroEvent, start, end, offsetMinutes, isDayEnd, latitude, longitude, useNextDay) { - let ts = mods.suncalc.getTimes(date, latitude, longitude)[astroEvent]; +function getAstroEvent( + date: Date, + astroEvent: AstroEventName, + start: string, + end: string, + offsetMinutes: number | string, + isDayEnd: boolean, + latitude: number, + longitude: number, + useNextDay?: boolean, +): Date { + let ts: Date = mods.suncalc.getTimes(date, latitude, longitude)[astroEvent]; if (!ts || ts.getTime().toString() === 'NaN') { ts = isDayEnd ? getNextTimeEvent(end, useNextDay) : getNextTimeEvent(start, useNextDay); } ts.setMilliseconds(0); - ts.setMinutes(ts.getMinutes() + (parseInt(offsetMinutes, 10) || 0)); + ts.setMinutes(ts.getMinutes() + (parseInt(offsetMinutes as unknown as string, 10) || 0)); let [timeHoursStart, timeMinutesStart] = start.split(':'); - timeHoursStart = parseInt(timeHoursStart, 10); - timeMinutesStart = parseInt(timeMinutesStart, 10) || 0; + const nTimeHoursStart = parseInt(timeHoursStart, 10); + const nTimeMinutesStart = parseInt(timeMinutesStart, 10) || 0; - if (ts.getHours() < timeHoursStart || (ts.getHours() === timeHoursStart && ts.getMinutes() < timeMinutesStart)) { + if (ts.getHours() < nTimeHoursStart || (ts.getHours() === nTimeHoursStart && ts.getMinutes() < nTimeMinutesStart)) { ts = getNextTimeEvent(start, useNextDay); ts.setSeconds(0); } let [timeHoursEnd, timeMinutesEnd] = end.split(':'); - timeHoursEnd = parseInt(timeHoursEnd, 10); - timeMinutesEnd = parseInt(timeMinutesEnd, 10) || 0; + const nTimeHoursEnd = parseInt(timeHoursEnd, 10); + const nTimeMinutesEnd = parseInt(timeMinutesEnd, 10) || 0; - if (ts.getHours() > timeHoursEnd || (ts.getHours() === timeHoursEnd && ts.getMinutes() > timeMinutesEnd)) { + if (ts.getHours() > nTimeHoursEnd || (ts.getHours() === nTimeHoursEnd && ts.getMinutes() > nTimeMinutesEnd)) { ts = getNextTimeEvent(end, useNextDay); ts.setSeconds(0); } @@ -1664,7 +1719,7 @@ function dayTimeSchedules(adapter, context) { dayScheduleTimer = setTimeout(dayTimeSchedules, nextTimeout, adapter, context); } -function getAstroStartOfDay() { +function getAstroStartOfDay(): Date { const d = new Date(); d.setMinutes(0); d.setSeconds(0); @@ -1771,22 +1826,20 @@ function tsLog(msg, sev) { // Due to a npm bug, virtual-tsc may be hoisted to the top level node_modules but // typescript may still be in the adapter level (https://npm.community/t/packages-with-peerdependencies-are-incorrectly-hoisted/4794), // so we need to tell virtual-tsc where typescript is -tsc.setTypeScriptResolveOptions({ +setTypeScriptResolveOptions({ paths: [require.resolve('typescript')], }); // compiler instance for typescript -/** @type {tsc.Server} */ -const tsServer = new tsc.Server(tsCompilerOptions, tsLog); +const tsServer = new Server(tsCompilerOptions, tsLog); // compiler instance for global JS declarations -/** @type {tsc.Server} */ -const jsDeclarationServer = new tsc.Server(jsDeclarationCompilerOptions, isCI ? false : undefined); +const jsDeclarationServer = new Server(jsDeclarationCompilerOptions, isCI ? false : undefined); function addGetProperty(object) { try { Object.defineProperty(object, 'get', { value: function (id) { - return this[id] || this[adapter.namespace + '.' + id]; + return this[id] || this[`${adapter.namespace}.${id}`]; }, enumerable: false, }); @@ -1816,26 +1869,6 @@ function fixLineNo(line) { return line; } -context.logError = function (msg, e, offs) { - const stack = e.stack ? e.stack.toString().split('\n') : e ? e.toString() : ''; - if (!msg.includes('\n')) { - msg = msg.replace(/[: ]*$/, ': '); - } - - //errorLogFunction.error(msg + stack[0]); - context.errorLogFunction.error(msg + fixLineNo(stack[0])); - for (let i = offs || 1; i < stack.length; i++) { - if (!stack[i]) { - continue; - } - if (stack[i].match(/runInNewContext|javascript\.js:/)) { - break; - } - //adapter.log.error(fixLineNo(stack[i])); - context.errorLogFunction.error(fixLineNo(stack[i])); - } -}; - function createActiveObject(id, enabled, cb) { const idActive = `${adapter.namespace}.scriptEnabled.${id.substring(SCRIPT_CODE_MARKER.length)}`; @@ -1892,7 +1925,7 @@ function createProblemObject(id, cb) { context.objects[idProblem] = { _id: idProblem, common: { - name: 'scriptProblem.' + id.substring(SCRIPT_CODE_MARKER.length), + name: `scriptProblem.${id.substring(SCRIPT_CODE_MARKER.length)}`, desc: 'Script has a problem', type: 'boolean', expert: true, @@ -1929,7 +1962,7 @@ function addToNames(obj) { if (obj.common && obj.common.name) { let name = obj.common.name; if (name && typeof name === 'object') { - name = name[words.getLanguage()] || name.en; + name = name[getLanguage()] || name.en; } if (!name || typeof name !== 'string') { // TODO, take name in current language @@ -2079,7 +2112,7 @@ async function installLibraries() { } catch (e) { adapter.log.warn(`Cannot install custom npm package "${moduleName}@${version}": ${e.message}`); } - } else if (!nodeFS.existsSync(`${__dirname}/node_modules/${depName}/package.json`)) { + } else if (!existsSync(`${__dirname}/node_modules/${depName}/package.json`)) { // js-controller < 6.x adapter.log.info(`Installing custom library (legacy mode): "${lib}"`); @@ -2141,7 +2174,7 @@ function createVM(source, name, wrapAsync) { // lineOffset: globalScriptLines }; return { - script: new vm.Script(source, options), + script: new Script(source, options), }; } catch (e) { context.logError(`${name} compile failed:\r\nat `, e); @@ -2577,7 +2610,7 @@ async function getData(callback) { if (!adapter.config.subscribe) { if (err || !res) { adapter.log.error(`Could not initialize states: ${err ? err.message : 'no result'}`); - adapter.terminate(utils.EXIT_CODES.START_IMMEDIATELY_AFTER_STOP); + adapter.terminate(EXIT_CODES.START_IMMEDIATELY_AFTER_STOP); return; } context.states = Object.assign(res, context.states); @@ -2625,11 +2658,11 @@ async function getData(callback) { // set language for debug messages if (systemConfig?.common?.language) { - words.setLanguage(systemConfig.common.language); + setLanguage(systemConfig.common.language); } else if (adapter.language) { - words.setLanguage(adapter.language); + setLanguage(adapter.language); } - context.language = words.getLanguage(); + context.language = getLanguage(); // try to use system coordinates if (adapter.config.useSystemGPS) { @@ -2848,34 +2881,35 @@ function debugStart(data) { } async function patchFont() { - let stat; - let dbFile; + let stat: Stats | undefined; + let dbFile: Buffer | undefined; try { - stat = nodeFS.statSync(`${__dirname}/admin/vs/base/browser/ui/codicons/codicon/codicon.ttf`); - dbFile = await adapter.readFileAsync('javascript.admin', `vs/base/browser/ui/codicons/codicon/codicon.ttf`); + stat = statSync(`${__dirname}/admin/vs/base/browser/ui/codicons/codicon/codicon.ttf`); + const _dbFile = await adapter.readFileAsync( + 'javascript.admin', + `vs/base/browser/ui/codicons/codicon/codicon.ttf`, + ); + if (_dbFile?.file) { + dbFile = _dbFile.file; + } } catch (error) { // ignore } - if (dbFile && dbFile.file) { - dbFile = dbFile.file; - } - if (!stat || stat.size !== 73452 || !dbFile || dbFile.byteLength !== 73452) { + if (stat?.size !== 73452 || dbFile?.byteLength !== 73452) { try { const buffer = Buffer.from( - JSON.parse(nodeFS.readFileSync(`${__dirname}/admin/vsFont/codicon.json`)), + JSON.parse(readFileSync(`${__dirname}/admin/vsFont/codicon.json`).toString()), 'base64', ); - const zip = await require('jszip').loadAsync(buffer); + const jszip = await import('jszip'); + const zip = await jszip.loadAsync(buffer); const data = await zip.file('codicon.ttf').async('arraybuffer'); if (data.byteLength !== 73452) { throw new Error('invalid font file!'); } - nodeFS.writeFileSync( - `${__dirname}/admin/vs/base/browser/ui/codicons/codicon/codicon.ttf`, - Buffer.from(data), - ); + writeFileSync(`${__dirname}/admin/vs/base/browser/ui/codicons/codicon/codicon.ttf`, Buffer.from(data)); // upload this file await adapter.writeFileAsync( 'javascript.admin', @@ -2887,9 +2921,8 @@ async function patchFont() { adapter.log.error(`Cannot patch font: ${error}`); return false; } - } else { - return false; } + return false; } // If started as allInOne mode => return function to create instance diff --git a/src/types.d.ts b/src/types.d.ts index 41c4e2fc..fccadec9 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -5,6 +5,7 @@ import type { Job } from 'node-schedule'; import { EventObj } from './lib/eventObj'; import type { PatternEventCompareFunction } from './lib/patternCompareFunctions'; import { AstroEvent } from './lib/consts'; +import * as JS from './lib/javascript'; export interface AdapterConfig { latitude: string; @@ -62,6 +63,12 @@ export type TimeRule = { time: string | { hour: number; minute: number }; }; +export type SubscribeObject = { + name: string; + pattern: string; + callback: (id: string, obj?: ioBroker.Object | null) => void; +}; + export type IobSchedule = Job & { _ioBroker: { type: 'cron'; pattern: string | Date; scriptName: string; id: string } }; export type PushoverOptions = { @@ -94,9 +101,9 @@ export type PushoverOptions = { // gamelan, incoming, intermission, magic, mechanical, pianobar, siren, // spacealarm, tugboat, alien, climb, persistent, echo, updown, none priority?: -1 | 0 | 1 | 2; // optional - // -1 to always send as a quiet notification, - // 1 to display as high-priority and bypass the user's quiet hours, or - // 2 to also require confirmation from the user + // -1: always send it as a quiet notification, + // 1: to display as high-priority and bypass the user's quiet hours, or + // 2: to also require confirmation from the user token?: string; // optional // add other than configured token to the call url?: string; // optional - a supplementary URL to show with your message @@ -295,7 +302,7 @@ export type SandboxType = { setState: ( id: string, state: ioBroker.SettableState | ioBroker.StateValue, - isAck?: boolean | ((err?: Error | null) => void), + isAck?: boolean | 'true' | 'false' | ((err?: Error | null) => void), callback?: (err?: Error | null) => void, ) => void; setStateChanged: ( @@ -381,7 +388,12 @@ export type SandboxType = { options?: Record | ((result: any, options: Record, _adapter: string) => void), callback?: (result: any, options: Record, _adapter: string) => void, ) => void; - sendto: (adapter: string, cmd: string, msg?: any, callback?: (result: any, options: Record, _adapter: string) => void) => void; + sendto: ( + adapter: string, + cmd: string, + msg?: any, + callback?: (result: any, options: Record, _adapter: string) => void, + ) => void; sendToAsync: (adapter: string, cmd: string, msg: any, options: any) => Promise; sendToHost: (host: string, cmd: string, msg?: any, callback?: (result: any) => void) => void; sendToHostAsync: (host: string, cmd: string, msg?: any) => Promise; @@ -390,27 +402,40 @@ export type SandboxType = { clearInterval: (id: NodeJS.Timeout) => void; setTimeout: (callback: (args?: any[]) => void, ms: number, ...args: any[]) => NodeJS.Timeout | null; clearTimeout: (id: NodeJS.Timeout) => void; - setImmediate: (callback: (args: any[]) => void, ...args: any[]) => number; + setImmediate: (callback: (args: any[]) => void, ...args: any[]) => void; cb: (callback: () => void) => void; - compareTime: (startTime: string, endTime: string, operation: string, time: string) => boolean; - onStop: (cb: () => void, timeout: number) => void; - formatValue: (value: any, decimals: number, format: string) => string; + compareTime: ( + startTime: iobJS.AstroDate | string | Date | number, + endTime: iobJS.AstroDate | string | Date | number | null, + operation: 'between' | 'not between' | '<' | '<=' | '>' | '>=' | '==' | '<>' | '!=', + time?: iobJS.AstroDate | string | Date | number, + ) => boolean; + onStop: (cb: () => void, timeout?: number) => void; + formatValue: (value: number | string, decimals: number | string, format?: string) => string; formatDate: (date: Date, format: string, language: string) => string; - formatTimeDiff: (diff: number, format: string) => string; + formatTimeDiff: (diff: number, format?: string) => string; getDateObject: (date: any) => Date; writeFile: (adapter: string, fileName: string, data: any, callback: (err: Error | null) => void) => void; - readFile: (adapter: string, fileName: string, callback: (err: Error | null, data: any) => void) => void; + readFile: ( + adapter: string, + fileName: string | ((err: Error | null, data?: Buffer | string, mimeType?: string) => void), + callback: (err: Error | null, data?: Buffer | string, mimeType?: string) => void, + ) => void; unlink: (adapter: string, fileName: string, callback: (err: Error | null) => void) => void; delFile: (adapter: string, fileName: string, callback: (err: Error | null) => void) => void; rename: (adapter: string, oldName: string, newName: string, callback: (err: Error | null) => void) => void; renameFile: (adapter: string, oldName: string, newName: string, callback: (err: Error | null) => void) => void; getHistory: (instance: string, options: any, callback: (err: Error | null, result: any) => void) => void; - runScript: (scriptName: string, callback: (err: Error | null) => void) => void; + runScript: (scriptName: string, callback?: (err: Error | null) => void) => boolean; runScriptAsync: (scriptName: string) => Promise; - startScript: (scriptName: string, ignoreIfStarted: boolean, callback: (err: Error | null) => void) => void; - startScriptAsync: (scriptName: string, ...args: any[]) => Promise; - stopScript: (scriptName: string, callback: (err: Error | null) => void) => void; - stopScriptAsync: (scriptName: string) => Promise; + startScript: ( + scriptName: string, + ignoreIfStarted: boolean | ((err: Error | null, started: boolean) => void), + callback?: (err: Error | null, started: boolean) => void, + ) => boolean; + startScriptAsync: (scriptName: string, ignoreIfStarted?: boolean) => Promise; + stopScript: (scriptName: string, callback: (err: Error | null, stopped: boolean) => void) => boolean; + stopScriptAsync: (scriptName: string) => Promise; isScriptActive: (scriptName: string) => boolean; startInstanceAsync: (instanceName: string) => Promise; restartInstanceAsync: (instanceName: string) => Promise; @@ -419,9 +444,18 @@ export type SandboxType = { toFloat: (val: any) => number; toBoolean: (val: any) => boolean; getAttr: (obj: any, path: string | string[]) => any; - messageTo: (target: any, data: any, options: any, callback: (err: Error | null, result: any) => void) => void; - messageToAsync: (target: any, data: any, options: any) => Promise; - onMessage: (messageName: string, callback: (data: any) => void) => void; + messageTo: ( + target: string | { instance: string | null | number; script: string | null; message: string }, + data: any, + options: any, + callback: (result: any, options: { timeout?: number | string }, instance: string | number | null) => void, + ) => void; + messageToAsync: ( + target: string | { instance: string | null | number; script: string | null; message: string }, + data: any, + options?: { timeout?: number | string }, + ) => Promise; + onMessage: (messageName: string, callback: (data: any, cb: (result: any) => void) => void) => null | number; onMessageUnregister: (idOrName: string) => boolean; console: { log: (msg: string) => void; @@ -434,8 +468,11 @@ export type SandboxType = { wait: (ms: number) => Promise; sleep: (ms: number) => Promise; onObject: (pattern: string, callback: (data: any) => void) => void; - subscribeObject: (pattern: string, callback: (data: any) => void) => void; - unsubscribeObject: (idOrObject: string | Record) => void; + subscribeObject: ( + pattern: string | string[], + callback: (id: string, obj?: ioBroker.Object | null) => void, + ) => SubscribeObject | SubscribeObject[]; + unsubscribeObject: (idOrObject: SubscribeObject | SubscribeObject[]) => boolean | boolean[]; _sendToFrontEnd: (blockId: string, data: any) => void; logHandler?: ioBroker.LogLevel | '*'; }; @@ -526,10 +563,16 @@ export interface JavascriptContext { states: Record; interimStateValues: Record; stateIds: string[]; - errorLogFunction: (text: string, ...args: any[]) => void; + errorLogFunction: { + info: (text: string, ...args: any[]) => void; + debug: (text: string, ...args: any[]) => void; + silly: (text: string, ...args: any[]) => void; + warn: (text: string, ...args: any[]) => void; + error: (text: string, ...args: any[]) => void; + }; subscriptions: SubscriptionResult[]; subscriptionsFile: FileSubscriptionResult[]; - subscriptionsObject: { name: string; pattern: string; options: Record }[]; + subscriptionsObject: SubscribeObject[]; subscribedPatterns: Record; subscribedPatternsFile: Record; adapterSubs: Record; @@ -543,13 +586,16 @@ export interface JavascriptContext { timerId: number; names: { [name: string]: string }; // name: id scripts: Record; - messageBusHandlers: Record void }>>; - logSubscriptions: { + messageBusHandlers: Record< + string, + Record void }[]> + >; + logSubscriptions: Record void; id: string; severity: ioBroker.LogLevel | '*'; - }[]; + }[]>; tempDirectories: { [scriptName: string]: string }; // name: path folderCreationVerifiedObjects: Record; updateLogSubscriptions: () => void; diff --git a/tsconfig.json b/tsconfig.json index e7a63ac8..5390f927 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,9 +22,10 @@ "noImplicitAny": false, // "noUnusedLocals": true, // "noUnusedParameters": true, - "target": "es2022", "types": ["node", "@iobroker/types"], }, - "include": ["lib/**/*.js", "lib/**/*.d.ts", "main.js"] + "include": ["lib/**/*.js", "lib/**/*.d.ts", + "src/main" + ] }