@iobroker/js-controller-cli 7.0.8-alpha.8-20251127-036be0224 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/lib/cli/cliLogs.js.map +1 -1
- package/build/cjs/lib/setup/dbConnection.js +4 -4
- package/build/cjs/lib/setup/dbConnection.js.map +2 -2
- package/build/cjs/lib/setup/multihostClient.d.ts +58 -22
- package/build/cjs/lib/setup/multihostClient.js +31 -24
- package/build/cjs/lib/setup/multihostClient.js.map +3 -3
- package/build/cjs/lib/setup/setupMultihost.d.ts +16 -4
- package/build/cjs/lib/setup/setupMultihost.js +32 -16
- package/build/cjs/lib/setup/setupMultihost.js.map +3 -3
- package/build/cjs/lib/setup/setupSetup.d.ts +3 -11
- package/build/cjs/lib/setup/setupSetup.js +65 -76
- package/build/cjs/lib/setup/setupSetup.js.map +2 -2
- package/build/cjs/lib/setup/setupVendor.d.ts +3 -1
- package/build/cjs/lib/setup/setupVendor.js +72 -35
- package/build/cjs/lib/setup/setupVendor.js.map +2 -2
- package/build/cjs/lib/setup.js +5 -2
- package/build/cjs/lib/setup.js.map +2 -2
- package/build/esm/lib/cli/cliLogs.js +1 -1
- package/build/esm/lib/cli/cliLogs.js.map +1 -1
- package/build/esm/lib/setup/dbConnection.d.ts.map +1 -1
- package/build/esm/lib/setup/dbConnection.js +14 -14
- package/build/esm/lib/setup/dbConnection.js.map +1 -1
- package/build/esm/lib/setup/multihostClient.d.ts +58 -22
- package/build/esm/lib/setup/multihostClient.d.ts.map +1 -1
- package/build/esm/lib/setup/multihostClient.js +47 -23
- package/build/esm/lib/setup/multihostClient.js.map +1 -1
- package/build/esm/lib/setup/setupMultihost.d.ts +16 -4
- package/build/esm/lib/setup/setupMultihost.d.ts.map +1 -1
- package/build/esm/lib/setup/setupMultihost.js +41 -18
- package/build/esm/lib/setup/setupMultihost.js.map +1 -1
- package/build/esm/lib/setup/setupSetup.d.ts +3 -11
- package/build/esm/lib/setup/setupSetup.d.ts.map +1 -1
- package/build/esm/lib/setup/setupSetup.js +66 -80
- package/build/esm/lib/setup/setupSetup.js.map +1 -1
- package/build/esm/lib/setup/setupVendor.d.ts +3 -1
- package/build/esm/lib/setup/setupVendor.d.ts.map +1 -1
- package/build/esm/lib/setup/setupVendor.js +72 -35
- package/build/esm/lib/setup/setupVendor.js.map +1 -1
- package/build/esm/lib/setup.d.ts.map +1 -1
- package/build/esm/lib/setup.js +6 -3
- package/build/esm/lib/setup.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +7 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/cli/cliLogs.ts", "../../../../../../node_modules/@alcalzone/esm2cjs/shims/import.meta.url/shim.js"],
|
|
4
|
-
"sourcesContent": ["import { CLICommand, type CLICommandOptions } from './cliCommand.js';\nimport { tools, logger as toolsLogger } from '@iobroker/js-controller-common';\nimport chokidar from 'chokidar';\nimport fs from 'fs-extra';\nimport os from 'node:os';\nimport es from 'event-stream';\nimport { createRequire } from 'node:module';\n// eslint-disable-next-line unicorn/prefer-module\nconst require = createRequire(import.meta.url || `file://${__filename}`);\n\nconst { getConfigFileName } = tools;\n\ninterface CLILogsOptions {\n /** Whether to show today's full log */\n complete?: boolean;\n /** An optional RegExp to filter by */\n regex?: RegExp;\n}\n\n/** Command ioBroker state ... */\nexport class CLILogs extends CLICommand {\n private readonly fileSizes = new Map<string, number>();\n private isReady = false;\n\n constructor(options: CLICommandOptions) {\n super(options);\n }\n\n /**\n * Executes a command\n *\n * @param args\n * @param params additional parsed CLI parameters\n */\n execute(args: any[], params: Record<string, any>): void {\n const adapterName = args[0];\n const watch = params.watch || params.w;\n const count = params.lines || 1_000;\n\n const options: CLILogsOptions = {\n complete: this.options.all,\n };\n\n const config = fs.readJSONSync(require.resolve(getConfigFileName()));\n const logger = toolsLogger(config.log);\n
|
|
4
|
+
"sourcesContent": ["import { CLICommand, type CLICommandOptions } from './cliCommand.js';\nimport { tools, logger as toolsLogger } from '@iobroker/js-controller-common';\nimport chokidar from 'chokidar';\nimport fs from 'fs-extra';\nimport os from 'node:os';\nimport es from 'event-stream';\nimport { createRequire } from 'node:module';\n// eslint-disable-next-line unicorn/prefer-module\nconst require = createRequire(import.meta.url || `file://${__filename}`);\n\nconst { getConfigFileName } = tools;\n\ninterface CLILogsOptions {\n /** Whether to show today's full log */\n complete?: boolean;\n /** An optional RegExp to filter by */\n regex?: RegExp;\n}\n\n/** Command ioBroker state ... */\nexport class CLILogs extends CLICommand {\n private readonly fileSizes = new Map<string, number>();\n private isReady = false;\n\n constructor(options: CLICommandOptions) {\n super(options);\n }\n\n /**\n * Executes a command\n *\n * @param args\n * @param params additional parsed CLI parameters\n */\n execute(args: any[], params: Record<string, any>): void {\n const adapterName = args[0];\n const watch = params.watch || params.w;\n const count = params.lines || 1_000;\n\n const options: CLILogsOptions = {\n complete: this.options.all,\n };\n\n const config = fs.readJSONSync(require.resolve(getConfigFileName()));\n const logger = toolsLogger(config.log);\n // @ts-expect-error todo adjust logger type\n let fileName = logger.getFileName();\n if (fileName) {\n let lines = fs.readFileSync(fileName).toString('utf-8').split('\\n');\n lines = lines.filter(line => line);\n if (lines.length > count) {\n lines.splice(0, lines.length - count);\n }\n let regex: RegExp;\n if (adapterName) {\n //2019-03-02 13:26:54.698 - debug: iot.0 [ALEXA] Created ALEXA device: Bad.Hauptlicht.Aktor.STATE [\"turnOn\",\"turnOff\"]\n regex = new RegExp(`: ${adapterName}\\\\.`);\n options.regex = regex;\n }\n lines.forEach(line => {\n if (regex && !regex.test(line)) {\n return;\n }\n console.log(line);\n });\n\n if (watch) {\n fileName = fileName.replace(/\\\\/g, '/');\n const parts = fileName.split('/');\n parts.pop();\n chokidar\n .watch(`${parts.join('/')}/iobroker*`, {\n awaitWriteFinish: { stabilityThreshold: 500 },\n followSymlinks: false,\n })\n .on('all', this.watchHandler.bind(this, options))\n .on('ready', () => (this.isReady = true));\n }\n } else {\n console.log('No log file found');\n }\n }\n\n /**\n * Called by chokidar when watched files change\n *\n * @param options some options\n * @param event The type of change\n * @param path Which path has changed\n * @param stats Information about the file\n */\n watchHandler(options: CLILogsOptions, event: string, path: string, stats: Record<string, any>): void {\n if (event === 'add' || !this.fileSizes.has(path)) {\n this.fileSizes.set(path, stats.size);\n if (stats.size > 0 && (this.isReady || (options.complete && this.isTodaysLogfile(path)))) {\n this.streamChange(path, 0, options);\n }\n } else if (event === 'change') {\n const oldFileSize = this.fileSizes.get(path)!;\n this.fileSizes.set(path, stats.size);\n if (this.isReady && stats.size > oldFileSize) {\n this.streamChange(path, oldFileSize, options);\n }\n } else if (event === 'unlink') {\n this.fileSizes.delete(path);\n }\n }\n\n /**\n * If the log file belongs to today\n *\n * @param path The log file path\n */\n isTodaysLogfile(path: string): boolean {\n const YYYYMMDDDate = new Date().toJSON().slice(0, 10);\n return path.includes(YYYYMMDDDate);\n }\n\n /**\n * Streams a portion of a file to the console\n *\n * @param path The file to stream\n * @param start The offset in bytes where to start\n * @param options some options\n */\n streamChange(path: string, start: number, options: CLILogsOptions): void {\n const input = fs.createReadStream(path, {\n encoding: 'utf8',\n start,\n autoClose: true,\n });\n if (options.regex) {\n // Read the input line by line and only include the lines matching the filter\n input\n .pipe(es.split())\n .pipe(es.filterSync(line => options.regex!.test(line)))\n .pipe(es.mapSync((line: string) => line + os.EOL))\n .pipe(process.stdout);\n } else {\n // just pipe the input through\n tools.pipeLinewise(input, process.stdout);\n }\n }\n}\n", "export const __import_meta_url =\n typeof document === 'undefined' ? new (require('url'.replace('', '')).URL)('file:' + __filename).href :\n (document.currentScript && document.currentScript.src || new URL('main.js', document.baseURI).href)\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["require", "fs", "toolsLogger", "chokidar", "es", "os"]
|
|
7
7
|
}
|
|
@@ -118,9 +118,9 @@ async function dbConnect(onlyCheck, params, callback) {
|
|
|
118
118
|
}
|
|
119
119
|
});
|
|
120
120
|
} else {
|
|
121
|
-
console.log(`No connection to objects ${config.objects.host}:${config.objects.port}[${config.objects.type}]`);
|
|
121
|
+
console.log(`No connection to objects ${Array.isArray(config.objects.host) ? config.objects.host.join(", ") : config.objects.host}:${Array.isArray(config.objects.port) ? config.objects.port.join(", ") : config.objects.port}[${config.objects.type}]`);
|
|
122
122
|
if (onlyCheck) {
|
|
123
|
-
callback
|
|
123
|
+
callback?.({
|
|
124
124
|
objects,
|
|
125
125
|
states,
|
|
126
126
|
isOffline: true,
|
|
@@ -183,7 +183,7 @@ async function dbConnect(onlyCheck, params, callback) {
|
|
|
183
183
|
await objects.destroy();
|
|
184
184
|
objects = null;
|
|
185
185
|
}
|
|
186
|
-
console.log(`No connection to states ${config.states.host}:${config.states.port}[${config.states.type}]`);
|
|
186
|
+
console.log(`No connection to states ${Array.isArray(config.states.host) ? config.states.host.join(", ") : config.states.host}:${Array.isArray(config.states.port) ? config.states.port.join(", ") : config.states.port}[${config.states.type}]`);
|
|
187
187
|
if (onlyCheck) {
|
|
188
188
|
callback && callback({
|
|
189
189
|
objects,
|
|
@@ -293,7 +293,7 @@ async function resetDbConnect() {
|
|
|
293
293
|
States = null;
|
|
294
294
|
}
|
|
295
295
|
if (pluginHandler) {
|
|
296
|
-
pluginHandler.destroyAll();
|
|
296
|
+
await pluginHandler.destroyAll();
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
async function exitApplicationSave(exitCode) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/setup/dbConnection.ts"],
|
|
4
|
-
"sourcesContent": ["import type { DbConnectAsyncReturn, DbConnectCallback } from '../_Types.js';\nimport fs from 'fs-extra';\nimport {\n getObjectsConstructor,\n getStatesConstructor,\n objectsDbHasServer,\n statesDbHasServer,\n} from '@iobroker/js-controller-common';\nimport { EXIT_CODES } from '@iobroker/js-controller-common';\nimport { tools } from '@iobroker/js-controller-common';\nimport { setTimeout as wait } from 'node:timers/promises';\nimport type { Client as StatesRedisClient } from '@iobroker/db-states-redis';\nimport type { Client as ObjectsInRedisClient } from '@iobroker/db-objects-redis';\nimport path from 'node:path';\nimport type { PluginHandlerSettings } from '@iobroker/plugin-base/types';\nimport { PluginHandler } from '@iobroker/plugin-base';\n\nlet pluginHandler: InstanceType<typeof PluginHandler>;\nlet Objects: typeof ObjectsInRedisClient | null; // constructor\nlet objects: ObjectsInRedisClient | null; // instance\nlet States: typeof StatesRedisClient | null; // constructor\nlet states: StatesRedisClient | null; // instance\n\ninterface DbConnectParams {\n /** DB connect timeout, default is 10_000 */\n timeout?: number;\n}\n\nexport function dbConnect(callback: DbConnectCallback): void;\nexport function dbConnect(params: Record<string, any>, callback: DbConnectCallback): void;\nexport function dbConnect(onlyCheck: boolean, params: Record<string, any>, callback: DbConnectCallback): void;\n/**\n * Connects to the DB or tests the connection.\n *\n * @param onlyCheck\n * @param params\n * @param callback\n */\nexport async function dbConnect(\n onlyCheck: boolean | Record<string, any> | DbConnectCallback,\n params?: DbConnectParams | DbConnectCallback,\n callback?: DbConnectCallback,\n): Promise<void> {\n if (typeof onlyCheck === 'object') {\n callback = params as DbConnectCallback;\n params = onlyCheck;\n onlyCheck = false;\n }\n if (typeof onlyCheck === 'function') {\n callback = onlyCheck;\n onlyCheck = false;\n }\n if (typeof params === 'function') {\n callback = params;\n params = {};\n }\n\n if (!callback) {\n throw new Error('No callback for dbConnect');\n }\n\n params = params || {};\n\n const config: ioBroker.IoBrokerJson = fs.readJSONSync(tools.getConfigFileName());\n\n if (objects && states) {\n return void callback({ objects, states, isOffline: false, objectsDBType: config.objects.type, config });\n }\n\n config.states = config.states || { type: 'jsonl' };\n config.objects = config.objects || { type: 'jsonl' };\n // Make sure the DB has enough time (5s). JsonL can take a bit longer if the process just crashed before\n // because the lockfile might not have been freed.\n config.states.connectTimeout = Math.max(config.states.connectTimeout || 0, 5_000);\n config.objects.connectTimeout = Math.max(config.objects.connectTimeout || 0, 5_000);\n\n Objects = await getObjectsConstructor(); // Objects DB Client object\n States = await getStatesConstructor(); // States DB Client object\n\n let isObjectConnected = false;\n let isStatesConnected = false;\n\n // Detect timeout or try to open file itself\n setTimeout(\n async () => {\n if (isObjectConnected && isStatesConnected) {\n return;\n }\n\n if (!isObjectConnected) {\n if (objects) {\n // Destroy Client we tried to connect with\n await objects.destroy();\n objects = null;\n }\n\n const hasObjectsServer = await objectsDbHasServer(config.objects.type);\n\n if (hasObjectsServer) {\n // Just open in memory DB itself\n Objects = (await import(`@iobroker/db-objects-${config.objects.type}`)).Server;\n objects = new Objects!({\n connection: config.objects,\n logger: {\n silly: (_msg: string) => {\n /** do not log on this level */\n },\n debug: (_msg: string) => {\n /** do not log on this level */\n },\n info: (_msg: string) => {\n /** do not log on this level */\n },\n warn: (msg: string) => console.log(msg),\n error: (msg: string) => console.log(msg),\n },\n connected: async () => {\n isObjectConnected = true;\n if (isStatesConnected && typeof callback === 'function') {\n try {\n await initializePlugins(config);\n } catch {\n // ignore in silence\n }\n return void callback({\n objects: objects!,\n states: states!,\n isOffline: true,\n objectsDBType: config.objects.type,\n config,\n });\n }\n },\n });\n } else {\n console.log(\n `No connection to objects ${config.objects.host}:${config.objects.port}[${config.objects.type}]`,\n );\n if (onlyCheck) {\n callback &&\n callback({\n objects: objects!,\n states: states!,\n isOffline: true,\n objectsDBType: config.objects.type,\n config,\n });\n callback = undefined;\n } else {\n return void exitApplicationSave(EXIT_CODES.NO_CONNECTION_TO_OBJ_DB);\n }\n }\n }\n\n if (!isStatesConnected) {\n if (states) {\n // Destroy Client we tried to connect with\n await states.destroy();\n states = null;\n }\n\n const hasStatesServer = await statesDbHasServer(config.states.type);\n\n if (hasStatesServer) {\n // Just open in memory DB itself\n States = (await import(`@iobroker/db-states-${config.states.type}`)).Server;\n\n states = new States!({\n connection: config.states,\n logger: {\n silly: (_msg: string) => {\n /** do not log on this level */\n },\n debug: (_msg: string) => {\n /** do not log on this level */\n },\n info: (_msg: string) => {\n /** do not log on this level */\n },\n warn: (msg: string) => console.log(msg),\n error: (msg: string) => console.log(msg),\n },\n connected: async () => {\n isStatesConnected = true;\n if (isObjectConnected && typeof callback === 'function') {\n try {\n await initializePlugins(config);\n } catch {\n // ignore in silence\n }\n return void callback({\n objects: objects!,\n states: states!,\n isOffline: true,\n objectsDBType: config.objects.type,\n config,\n });\n }\n },\n // react on change\n // @ts-expect-error todo according to types and first look states.onchange does not exist\n change: (id, msg) => states?.onChange(id, msg),\n });\n // @ts-expect-error todo according to types and first look states.onchange does not exist\n states.onChange = null; // here the custom onChange handler could be installed\n } else {\n if (states) {\n // Destroy Client we tried to connect with\n await (states as StatesRedisClient).destroy();\n states = null;\n }\n if (objects) {\n // Destroy Client we tried to connect with\n await objects.destroy();\n objects = null;\n }\n console.log(\n `No connection to states ${config.states.host}:${config.states.port}[${config.states.type}]`,\n );\n if (onlyCheck) {\n callback &&\n callback({\n objects: objects!,\n states: states!,\n isOffline: true,\n objectsDBType: config.objects.type,\n config,\n });\n callback = undefined;\n } else {\n return void exitApplicationSave(EXIT_CODES.NO_CONNECTION_TO_OBJ_DB);\n }\n }\n }\n\n await wait((params.timeout || 10_000) + config.objects.connectTimeout);\n\n // Failsafe\n if (isObjectConnected && isStatesConnected) {\n return;\n }\n\n console.log('No connection to databases possible ...');\n if (onlyCheck) {\n callback &&\n callback({\n // TODO types: allow null if onlyCheck is true\n objects: null as any,\n states: null as any,\n isOffline: true,\n objectsDBType: config.objects.type,\n config,\n });\n callback = undefined;\n } else {\n return void exitApplicationSave(EXIT_CODES.NO_CONNECTION_TO_OBJ_DB);\n }\n },\n params.timeout || config.objects.connectTimeout * 2,\n );\n\n // try to connect as client\n objects = new Objects({\n connection: config.objects,\n logger: {\n silly: (_msg: string) => {\n /** do not log on this level */\n },\n debug: (_msg: string) => {\n /** do not log on this level */\n },\n info: (_msg: string) => {\n /** do not log on this level */\n },\n warn: (msg: string) => console.log(msg),\n error: (msg: string) => console.log(msg),\n },\n connected: async () => {\n if (isObjectConnected) {\n return;\n }\n isObjectConnected = true;\n\n if (isStatesConnected && typeof callback === 'function') {\n const isOffline = await checkSystemOffline(onlyCheck);\n try {\n await initializePlugins(config);\n } catch {\n // ignore in silence\n }\n callback({ objects: objects!, states: states!, isOffline, objectsDBType: config.objects.type, config });\n }\n },\n });\n\n states = new States({\n connection: config.states,\n logger: {\n silly: (_msg: string) => {\n /** do not log on this level */\n },\n debug: (_msg: string) => {\n /** do not log on this level */\n },\n info: (_msg: string) => {\n /** do not log on this level */\n },\n warn: (msg: string) => console.log(msg),\n error: (msg: string) => console.log(msg),\n },\n connected: async () => {\n if (isStatesConnected) {\n return;\n }\n isStatesConnected = true;\n\n if (isObjectConnected && typeof callback === 'function') {\n const isOffline = await checkSystemOffline(onlyCheck);\n try {\n await initializePlugins(config);\n } catch {\n // ignore in silence\n }\n callback({ objects: objects!, states: states!, isOffline, objectsDBType: config.objects.type, config });\n }\n },\n // @ts-expect-error todo according to types and first look states.onchange does not exist\n change: (id, state) => states?.onChange(id, state),\n });\n}\n\n/**\n * Connects to the DB or tests the connection.\n *\n * @param onlyCheck if only connection check should be performed\n * @param params options used by dbConnect\n */\nexport function dbConnectAsync(onlyCheck: boolean, params?: DbConnectParams): Promise<DbConnectAsyncReturn> {\n return new Promise(resolve => dbConnect(onlyCheck, params || {}, params => resolve(params)));\n}\n\n/**\n * Disconnect the database\n */\nexport async function resetDbConnect(): Promise<void> {\n if (objects) {\n await objects.destroy();\n objects = null;\n }\n if (states) {\n await states.destroy();\n states = null;\n }\n if (Objects) {\n Objects = null;\n }\n if (States) {\n States = null;\n }\n\n if (pluginHandler) {\n pluginHandler.destroyAll();\n }\n}\n\n/**\n * Exits the process and saves objects before exit - never resolves\n *\n * @param exitCode code passed to process.exit\n */\nexport async function exitApplicationSave(exitCode?: number): Promise<never> {\n await resetDbConnect();\n await wait(1_000);\n process.exit(exitCode);\n}\n\n/**\n * Initialize plugins from io-pack and config json\n *\n * @param config - parsed content of iobroker.json\n */\nfunction initializePlugins(config: Record<string, any>): Promise<void> {\n const ioPackage = fs.readJsonSync(path.join(tools.getControllerDir(), 'io-package.json'));\n const packageJson = fs.readJsonSync(path.join(tools.getControllerDir(), 'package.json'));\n const hostname = tools.getHostName();\n\n const pluginSettings: PluginHandlerSettings = {\n namespace: `system.host.${hostname}`,\n logNamespace: `host.${hostname}`,\n scope: 'controller',\n log: {\n // cli should be clean, only log warn/error\n silly: (_msg: string) => {\n /** do not log on this level */\n },\n debug: (_msg: string) => {\n /** do not log on this level */\n },\n info: (_msg: string) => {\n /** do not log on this level */\n },\n warn: (msg: string) => console.log(msg),\n error: (msg: string) => console.log(msg),\n level: 'warn',\n },\n iobrokerConfig: config,\n parentPackage: packageJson,\n controllerVersion: ioPackage.common.version,\n };\n\n pluginHandler = new PluginHandler(pluginSettings);\n pluginHandler.addPlugins(ioPackage.common.plugins, tools.getControllerDir()); // Plugins from io-package have priority over ...\n pluginHandler.addPlugins(config.plugins, tools.getControllerDir()); // ... plugins from iobroker.json\n pluginHandler.setDatabaseForPlugins(objects, states);\n\n return pluginHandler.initPlugins(ioPackage);\n}\n\n/**\n * Checks if system is offline\n *\n * @param onlyCheck - returns true then\n */\nasync function checkSystemOffline(onlyCheck: boolean): Promise<boolean> {\n if (!objects || !states) {\n // should never happen\n return true;\n }\n if (onlyCheck) {\n return true;\n }\n\n const offlineStatus = !(await tools.isHostRunning(objects, states));\n\n return offlineStatus;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA;;;;;;;;AAAA,sBAAe;AACf,kCAKO;AACP,IAAAA,+BAA2B;AAC3B,IAAAA,+BAAsB;AACtB,sBAAmC;AAGnC,uBAAiB;AAEjB,yBAA8B;AAE9B,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,IAAI;AAiBJ,eAAsB,UAClB,WACA,QACA,UAA4B;AAE5B,MAAI,OAAO,cAAc,UAAU;AAC/B,eAAW;AACX,aAAS;AACT,gBAAY;EAChB;AACA,MAAI,OAAO,cAAc,YAAY;AACjC,eAAW;AACX,gBAAY;EAChB;AACA,MAAI,OAAO,WAAW,YAAY;AAC9B,eAAW;AACX,aAAS,CAAA;EACb;AAEA,MAAI,CAAC,UAAU;AACX,UAAM,IAAI,MAAM,2BAA2B;EAC/C;AAEA,WAAS,UAAU,CAAA;AAEnB,QAAM,SAAgC,gBAAAC,QAAG,aAAa,mCAAM,kBAAiB,CAAE;AAE/E,MAAI,WAAW,QAAQ;AACnB,WAAO,KAAK,SAAS,EAAE,SAAS,QAAQ,WAAW,OAAO,eAAe,OAAO,QAAQ,MAAM,OAAM,CAAE;EAC1G;AAEA,SAAO,SAAS,OAAO,UAAU,EAAE,MAAM,QAAO;AAChD,SAAO,UAAU,OAAO,WAAW,EAAE,MAAM,QAAO;AAGlD,SAAO,OAAO,iBAAiB,KAAK,IAAI,OAAO,OAAO,kBAAkB,GAAG,GAAK;AAChF,SAAO,QAAQ,iBAAiB,KAAK,IAAI,OAAO,QAAQ,kBAAkB,GAAG,GAAK;AAElF,YAAU,UAAM,mDAAqB;AACrC,WAAS,UAAM,kDAAoB;AAEnC,MAAI,oBAAoB;AACxB,MAAI,oBAAoB;AAGxB,aACI,YAAW;AACP,QAAI,qBAAqB,mBAAmB;AACxC;IACJ;AAEA,QAAI,CAAC,mBAAmB;AACpB,UAAI,SAAS;AAET,cAAM,QAAQ,QAAO;AACrB,kBAAU;MACd;AAEA,YAAM,mBAAmB,UAAM,gDAAmB,OAAO,QAAQ,IAAI;AAErE,UAAI,kBAAkB;AAElB,mBAAW,MAAM,OAAO,wBAAwB,OAAO,QAAQ,IAAI,KAAK;AACxE,kBAAU,IAAI,QAAS;UACnB,YAAY,OAAO;UACnB,QAAQ;YACJ,OAAO,CAAC,SAAgB;YAExB;YACA,OAAO,CAAC,SAAgB;YAExB;YACA,MAAM,CAAC,SAAgB;YAEvB;YACA,MAAM,CAAC,QAAgB,QAAQ,IAAI,GAAG;YACtC,OAAO,CAAC,QAAgB,QAAQ,IAAI,GAAG;;UAE3C,WAAW,YAAW;AAClB,gCAAoB;AACpB,gBAAI,qBAAqB,OAAO,aAAa,YAAY;AACrD,kBAAI;AACA,sBAAM,kBAAkB,MAAM;cAClC,QAAQ;cAER;AACA,qBAAO,KAAK,SAAS;gBACjB;gBACA;gBACA,WAAW;gBACX,eAAe,OAAO,QAAQ;gBAC9B;eACH;YACL;UACJ;SACH;MACL,OAAO;AACH,gBAAQ,IACJ,4BAA4B,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ,IAAI,GAAG;
|
|
4
|
+
"sourcesContent": ["import type { DbConnectAsyncReturn, DbConnectCallback } from '../_Types.js';\nimport fs from 'fs-extra';\nimport {\n getObjectsConstructor,\n getStatesConstructor,\n objectsDbHasServer,\n statesDbHasServer,\n} from '@iobroker/js-controller-common';\nimport { EXIT_CODES } from '@iobroker/js-controller-common';\nimport { tools } from '@iobroker/js-controller-common';\nimport { setTimeout as wait } from 'node:timers/promises';\nimport type { Client as StatesRedisClient } from '@iobroker/db-states-redis';\nimport type { Client as ObjectsInRedisClient } from '@iobroker/db-objects-redis';\nimport path from 'node:path';\nimport type { InternalAdapterJsonConfig, PluginHandlerSettings } from '@iobroker/plugin-base';\nimport { PluginHandler } from '@iobroker/plugin-base';\n\nlet pluginHandler: InstanceType<typeof PluginHandler>;\nlet Objects: typeof ObjectsInRedisClient | null; // constructor\nlet objects: ObjectsInRedisClient | null; // instance\nlet States: typeof StatesRedisClient | null; // constructor\nlet states: StatesRedisClient | null; // instance\n\ninterface DbConnectParams {\n /** DB connect timeout, default is 10_000 */\n timeout?: number;\n}\n\nexport function dbConnect(callback: DbConnectCallback): void;\nexport function dbConnect(params: Record<string, any>, callback: DbConnectCallback): void;\nexport function dbConnect(onlyCheck: boolean, params: Record<string, any>, callback: DbConnectCallback): void;\n/**\n * Connects to the DB or tests the connection.\n *\n * @param onlyCheck if only connection check should be performed\n * @param params options used by dbConnect\n * @param callback called when connection is established or check is done\n */\nexport async function dbConnect(\n onlyCheck: boolean | Record<string, any> | DbConnectCallback,\n params?: DbConnectParams | DbConnectCallback,\n callback?: DbConnectCallback,\n): Promise<void> {\n if (typeof onlyCheck === 'object') {\n callback = params as DbConnectCallback;\n params = onlyCheck;\n onlyCheck = false;\n }\n if (typeof onlyCheck === 'function') {\n callback = onlyCheck;\n onlyCheck = false;\n }\n if (typeof params === 'function') {\n callback = params;\n params = {};\n }\n\n if (!callback) {\n throw new Error('No callback for dbConnect');\n }\n\n params = params || {};\n\n const config: ioBroker.IoBrokerJson = fs.readJSONSync(tools.getConfigFileName());\n\n if (objects && states) {\n return void callback({ objects, states, isOffline: false, objectsDBType: config.objects.type, config });\n }\n\n config.states = config.states || { type: 'jsonl' };\n config.objects = config.objects || { type: 'jsonl' };\n // Make sure the DB has enough time (5s). JsonL can take a bit longer if the process just crashed before\n // because the lockfile might not have been freed.\n config.states.connectTimeout = Math.max(config.states.connectTimeout || 0, 5_000);\n config.objects.connectTimeout = Math.max(config.objects.connectTimeout || 0, 5_000);\n\n Objects = await getObjectsConstructor(); // Objects DB Client object\n States = await getStatesConstructor(); // States DB Client object\n\n let isObjectConnected = false;\n let isStatesConnected = false;\n\n // Detect timeout or try to open file itself\n setTimeout(\n async () => {\n if (isObjectConnected && isStatesConnected) {\n return;\n }\n\n if (!isObjectConnected) {\n if (objects) {\n // Destroy Client we tried to connect with\n await objects.destroy();\n objects = null;\n }\n\n const hasObjectsServer = await objectsDbHasServer(config.objects.type);\n\n if (hasObjectsServer) {\n // Just open in memory DB itself\n Objects = (await import(`@iobroker/db-objects-${config.objects.type}`)).Server;\n objects = new Objects!({\n connection: config.objects,\n logger: {\n silly: (_msg: string) => {\n /** do not log on this level */\n },\n debug: (_msg: string) => {\n /** do not log on this level */\n },\n info: (_msg: string) => {\n /** do not log on this level */\n },\n warn: (msg: string) => console.log(msg),\n error: (msg: string) => console.log(msg),\n },\n connected: async () => {\n isObjectConnected = true;\n if (isStatesConnected && typeof callback === 'function') {\n try {\n await initializePlugins(config);\n } catch {\n // ignore in silence\n }\n return void callback({\n objects: objects!,\n states: states!,\n isOffline: true,\n objectsDBType: config.objects.type,\n config,\n });\n }\n },\n });\n } else {\n console.log(\n `No connection to objects ${Array.isArray(config.objects.host) ? config.objects.host.join(', ') : config.objects.host}:${Array.isArray(config.objects.port) ? config.objects.port.join(', ') : config.objects.port}[${config.objects.type}]`,\n );\n if (onlyCheck) {\n callback?.({\n objects: objects!,\n states: states!,\n isOffline: true,\n objectsDBType: config.objects.type,\n config,\n });\n callback = undefined;\n } else {\n return void exitApplicationSave(EXIT_CODES.NO_CONNECTION_TO_OBJ_DB);\n }\n }\n }\n\n if (!isStatesConnected) {\n if (states) {\n // Destroy Client we tried to connect with\n await states.destroy();\n states = null;\n }\n\n const hasStatesServer = await statesDbHasServer(config.states.type);\n\n if (hasStatesServer) {\n // Just open in memory DB itself\n States = (await import(`@iobroker/db-states-${config.states.type}`)).Server;\n\n states = new States!({\n connection: config.states,\n logger: {\n silly: (_msg: string) => {\n /** do not log on this level */\n },\n debug: (_msg: string) => {\n /** do not log on this level */\n },\n info: (_msg: string) => {\n /** do not log on this level */\n },\n warn: (msg: string) => console.log(msg),\n error: (msg: string) => console.log(msg),\n },\n connected: async () => {\n isStatesConnected = true;\n if (isObjectConnected && typeof callback === 'function') {\n try {\n await initializePlugins(config);\n } catch {\n // ignore in silence\n }\n return void callback({\n objects: objects!,\n states: states!,\n isOffline: true,\n objectsDBType: config.objects.type,\n config,\n });\n }\n },\n // react on change\n // @ts-expect-error todo according to types and first look states.onchange does not exist\n change: (id, msg) => states?.onChange(id, msg),\n });\n // @ts-expect-error todo according to types and first look states.onchange does not exist\n states.onChange = null; // here the custom onChange handler could be installed\n } else {\n if (states) {\n // Destroy Client we tried to connect with\n await (states as StatesRedisClient).destroy();\n states = null;\n }\n if (objects) {\n // Destroy Client we tried to connect with\n await objects.destroy();\n objects = null;\n }\n console.log(\n `No connection to states ${Array.isArray(config.states.host) ? config.states.host.join(', ') : config.states.host}:${Array.isArray(config.states.port) ? config.states.port.join(', ') : config.states.port}[${config.states.type}]`,\n );\n if (onlyCheck) {\n callback &&\n callback({\n objects: objects!,\n states: states!,\n isOffline: true,\n objectsDBType: config.objects.type,\n config,\n });\n callback = undefined;\n } else {\n return void exitApplicationSave(EXIT_CODES.NO_CONNECTION_TO_OBJ_DB);\n }\n }\n }\n\n await wait((params.timeout || 10_000) + config.objects.connectTimeout);\n\n // Failsafe\n if (isObjectConnected && isStatesConnected) {\n return;\n }\n\n console.log('No connection to databases possible ...');\n if (onlyCheck) {\n callback &&\n callback({\n // TODO types: allow null if onlyCheck is true\n objects: null as any,\n states: null as any,\n isOffline: true,\n objectsDBType: config.objects.type,\n config,\n });\n callback = undefined;\n } else {\n return void exitApplicationSave(EXIT_CODES.NO_CONNECTION_TO_OBJ_DB);\n }\n },\n params.timeout || config.objects.connectTimeout * 2,\n );\n\n // try to connect as client\n objects = new Objects({\n connection: config.objects,\n logger: {\n silly: (_msg: string) => {\n /** do not log on this level */\n },\n debug: (_msg: string) => {\n /** do not log on this level */\n },\n info: (_msg: string) => {\n /** do not log on this level */\n },\n warn: (msg: string) => console.log(msg),\n error: (msg: string) => console.log(msg),\n },\n connected: async () => {\n if (isObjectConnected) {\n return;\n }\n isObjectConnected = true;\n\n if (isStatesConnected && typeof callback === 'function') {\n const isOffline = await checkSystemOffline(onlyCheck);\n try {\n await initializePlugins(config);\n } catch {\n // ignore in silence\n }\n callback({ objects: objects!, states: states!, isOffline, objectsDBType: config.objects.type, config });\n }\n },\n });\n\n states = new States({\n connection: config.states,\n logger: {\n silly: (_msg: string) => {\n /** do not log on this level */\n },\n debug: (_msg: string) => {\n /** do not log on this level */\n },\n info: (_msg: string) => {\n /** do not log on this level */\n },\n warn: (msg: string) => console.log(msg),\n error: (msg: string) => console.log(msg),\n },\n connected: async () => {\n if (isStatesConnected) {\n return;\n }\n isStatesConnected = true;\n\n if (isObjectConnected && typeof callback === 'function') {\n const isOffline = await checkSystemOffline(onlyCheck);\n try {\n await initializePlugins(config);\n } catch {\n // ignore in silence\n }\n callback({ objects: objects!, states: states!, isOffline, objectsDBType: config.objects.type, config });\n }\n },\n // @ts-expect-error todo according to types and first look states.onchange does not exist\n change: (id, state) => states?.onChange(id, state),\n });\n}\n\n/**\n * Connects to the DB or tests the connection.\n *\n * @param onlyCheck if only connection check should be performed\n * @param params options used by dbConnect\n */\nexport function dbConnectAsync(onlyCheck: boolean, params?: DbConnectParams): Promise<DbConnectAsyncReturn> {\n return new Promise(resolve => dbConnect(onlyCheck, params || {}, params => resolve(params)));\n}\n\n/**\n * Disconnect the database\n */\nexport async function resetDbConnect(): Promise<void> {\n if (objects) {\n await objects.destroy();\n objects = null;\n }\n if (states) {\n await states.destroy();\n states = null;\n }\n if (Objects) {\n Objects = null;\n }\n if (States) {\n States = null;\n }\n\n if (pluginHandler) {\n await pluginHandler.destroyAll();\n }\n}\n\n/**\n * Exits the process and saves objects before exit - never resolves\n *\n * @param exitCode code passed to process.exit\n */\nexport async function exitApplicationSave(exitCode?: number): Promise<never> {\n await resetDbConnect();\n await wait(1_000);\n process.exit(exitCode);\n}\n\n/**\n * Initialize plugins from io-pack and config json\n *\n * @param config - parsed content of iobroker.json\n */\nfunction initializePlugins(config: Record<string, any>): Promise<void> {\n const ioPackage = fs.readJsonSync(path.join(tools.getControllerDir(), 'io-package.json'));\n const packageJson = fs.readJsonSync(path.join(tools.getControllerDir(), 'package.json'));\n const hostname = tools.getHostName();\n\n const pluginSettings: PluginHandlerSettings = {\n namespace: `system.host.${hostname}`,\n logNamespace: `host.${hostname}`,\n scope: 'controller',\n log: {\n // cli should be clean, only log warn/error\n silly: (_msg: string) => {\n /** do not log on this level */\n },\n debug: (_msg: string) => {\n /** do not log on this level */\n },\n info: (_msg: string) => {\n /** do not log on this level */\n },\n warn: (msg: string) => console.log(msg),\n error: (msg: string) => console.log(msg),\n level: 'warn',\n },\n iobrokerConfig: config as InternalAdapterJsonConfig,\n parentPackage: packageJson,\n controllerVersion: ioPackage.common.version,\n };\n\n pluginHandler = new PluginHandler(pluginSettings);\n pluginHandler.addPlugins(ioPackage.common.plugins, tools.getControllerDir()); // Plugins from io-package have priority over ...\n pluginHandler.addPlugins(config.plugins, tools.getControllerDir()); // ... plugins from iobroker.json\n // @ts-expect-error objects and state object version conflicts that are none\n pluginHandler.setDatabaseForPlugins(objects, states);\n\n return pluginHandler.initPlugins(ioPackage);\n}\n\n/**\n * Checks if system is offline\n *\n * @param onlyCheck - returns true then\n */\nasync function checkSystemOffline(onlyCheck: boolean): Promise<boolean> {\n if (!objects || !states) {\n // should never happen\n return true;\n }\n if (onlyCheck) {\n return true;\n }\n\n const offlineStatus = !(await tools.isHostRunning(objects, states));\n\n return offlineStatus;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA;;;;;;;;AAAA,sBAAe;AACf,kCAKO;AACP,IAAAA,+BAA2B;AAC3B,IAAAA,+BAAsB;AACtB,sBAAmC;AAGnC,uBAAiB;AAEjB,yBAA8B;AAE9B,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,IAAI;AAiBJ,eAAsB,UAClB,WACA,QACA,UAA4B;AAE5B,MAAI,OAAO,cAAc,UAAU;AAC/B,eAAW;AACX,aAAS;AACT,gBAAY;EAChB;AACA,MAAI,OAAO,cAAc,YAAY;AACjC,eAAW;AACX,gBAAY;EAChB;AACA,MAAI,OAAO,WAAW,YAAY;AAC9B,eAAW;AACX,aAAS,CAAA;EACb;AAEA,MAAI,CAAC,UAAU;AACX,UAAM,IAAI,MAAM,2BAA2B;EAC/C;AAEA,WAAS,UAAU,CAAA;AAEnB,QAAM,SAAgC,gBAAAC,QAAG,aAAa,mCAAM,kBAAiB,CAAE;AAE/E,MAAI,WAAW,QAAQ;AACnB,WAAO,KAAK,SAAS,EAAE,SAAS,QAAQ,WAAW,OAAO,eAAe,OAAO,QAAQ,MAAM,OAAM,CAAE;EAC1G;AAEA,SAAO,SAAS,OAAO,UAAU,EAAE,MAAM,QAAO;AAChD,SAAO,UAAU,OAAO,WAAW,EAAE,MAAM,QAAO;AAGlD,SAAO,OAAO,iBAAiB,KAAK,IAAI,OAAO,OAAO,kBAAkB,GAAG,GAAK;AAChF,SAAO,QAAQ,iBAAiB,KAAK,IAAI,OAAO,QAAQ,kBAAkB,GAAG,GAAK;AAElF,YAAU,UAAM,mDAAqB;AACrC,WAAS,UAAM,kDAAoB;AAEnC,MAAI,oBAAoB;AACxB,MAAI,oBAAoB;AAGxB,aACI,YAAW;AACP,QAAI,qBAAqB,mBAAmB;AACxC;IACJ;AAEA,QAAI,CAAC,mBAAmB;AACpB,UAAI,SAAS;AAET,cAAM,QAAQ,QAAO;AACrB,kBAAU;MACd;AAEA,YAAM,mBAAmB,UAAM,gDAAmB,OAAO,QAAQ,IAAI;AAErE,UAAI,kBAAkB;AAElB,mBAAW,MAAM,OAAO,wBAAwB,OAAO,QAAQ,IAAI,KAAK;AACxE,kBAAU,IAAI,QAAS;UACnB,YAAY,OAAO;UACnB,QAAQ;YACJ,OAAO,CAAC,SAAgB;YAExB;YACA,OAAO,CAAC,SAAgB;YAExB;YACA,MAAM,CAAC,SAAgB;YAEvB;YACA,MAAM,CAAC,QAAgB,QAAQ,IAAI,GAAG;YACtC,OAAO,CAAC,QAAgB,QAAQ,IAAI,GAAG;;UAE3C,WAAW,YAAW;AAClB,gCAAoB;AACpB,gBAAI,qBAAqB,OAAO,aAAa,YAAY;AACrD,kBAAI;AACA,sBAAM,kBAAkB,MAAM;cAClC,QAAQ;cAER;AACA,qBAAO,KAAK,SAAS;gBACjB;gBACA;gBACA,WAAW;gBACX,eAAe,OAAO,QAAQ;gBAC9B;eACH;YACL;UACJ;SACH;MACL,OAAO;AACH,gBAAQ,IACJ,4BAA4B,MAAM,QAAQ,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ,KAAK,KAAK,IAAI,IAAI,OAAO,QAAQ,IAAI,IAAI,MAAM,QAAQ,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ,KAAK,KAAK,IAAI,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ,IAAI,GAAG;AAEhP,YAAI,WAAW;AACX,qBAAW;YACP;YACA;YACA,WAAW;YACX,eAAe,OAAO,QAAQ;YAC9B;WACH;AACD,qBAAW;QACf,OAAO;AACH,iBAAO,KAAK,oBAAoB,wCAAW,uBAAuB;QACtE;MACJ;IACJ;AAEA,QAAI,CAAC,mBAAmB;AACpB,UAAI,QAAQ;AAER,cAAM,OAAO,QAAO;AACpB,iBAAS;MACb;AAEA,YAAM,kBAAkB,UAAM,+CAAkB,OAAO,OAAO,IAAI;AAElE,UAAI,iBAAiB;AAEjB,kBAAU,MAAM,OAAO,uBAAuB,OAAO,OAAO,IAAI,KAAK;AAErE,iBAAS,IAAI,OAAQ;UACjB,YAAY,OAAO;UACnB,QAAQ;YACJ,OAAO,CAAC,SAAgB;YAExB;YACA,OAAO,CAAC,SAAgB;YAExB;YACA,MAAM,CAAC,SAAgB;YAEvB;YACA,MAAM,CAAC,QAAgB,QAAQ,IAAI,GAAG;YACtC,OAAO,CAAC,QAAgB,QAAQ,IAAI,GAAG;;UAE3C,WAAW,YAAW;AAClB,gCAAoB;AACpB,gBAAI,qBAAqB,OAAO,aAAa,YAAY;AACrD,kBAAI;AACA,sBAAM,kBAAkB,MAAM;cAClC,QAAQ;cAER;AACA,qBAAO,KAAK,SAAS;gBACjB;gBACA;gBACA,WAAW;gBACX,eAAe,OAAO,QAAQ;gBAC9B;eACH;YACL;UACJ;;;UAGA,QAAQ,CAAC,IAAI,QAAQ,QAAQ,SAAS,IAAI,GAAG;SAChD;AAED,eAAO,WAAW;MACtB,OAAO;AACH,YAAI,QAAQ;AAER,gBAAO,OAA6B,QAAO;AAC3C,mBAAS;QACb;AACA,YAAI,SAAS;AAET,gBAAM,QAAQ,QAAO;AACrB,oBAAU;QACd;AACA,gBAAQ,IACJ,2BAA2B,MAAM,QAAQ,OAAO,OAAO,IAAI,IAAI,OAAO,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,OAAO,IAAI,IAAI,MAAM,QAAQ,OAAO,OAAO,IAAI,IAAI,OAAO,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,OAAO,IAAI,IAAI,OAAO,OAAO,IAAI,GAAG;AAExO,YAAI,WAAW;AACX,sBACI,SAAS;YACL;YACA;YACA,WAAW;YACX,eAAe,OAAO,QAAQ;YAC9B;WACH;AACL,qBAAW;QACf,OAAO;AACH,iBAAO,KAAK,oBAAoB,wCAAW,uBAAuB;QACtE;MACJ;IACJ;AAEA,cAAM,gBAAAC,aAAM,OAAO,WAAW,OAAU,OAAO,QAAQ,cAAc;AAGrE,QAAI,qBAAqB,mBAAmB;AACxC;IACJ;AAEA,YAAQ,IAAI,yCAAyC;AACrD,QAAI,WAAW;AACX,kBACI,SAAS;;QAEL,SAAS;QACT,QAAQ;QACR,WAAW;QACX,eAAe,OAAO,QAAQ;QAC9B;OACH;AACL,iBAAW;IACf,OAAO;AACH,aAAO,KAAK,oBAAoB,wCAAW,uBAAuB;IACtE;EACJ,GACA,OAAO,WAAW,OAAO,QAAQ,iBAAiB,CAAC;AAIvD,YAAU,IAAI,QAAQ;IAClB,YAAY,OAAO;IACnB,QAAQ;MACJ,OAAO,CAAC,SAAgB;MAExB;MACA,OAAO,CAAC,SAAgB;MAExB;MACA,MAAM,CAAC,SAAgB;MAEvB;MACA,MAAM,CAAC,QAAgB,QAAQ,IAAI,GAAG;MACtC,OAAO,CAAC,QAAgB,QAAQ,IAAI,GAAG;;IAE3C,WAAW,YAAW;AAClB,UAAI,mBAAmB;AACnB;MACJ;AACA,0BAAoB;AAEpB,UAAI,qBAAqB,OAAO,aAAa,YAAY;AACrD,cAAM,YAAY,MAAM,mBAAmB,SAAS;AACpD,YAAI;AACA,gBAAM,kBAAkB,MAAM;QAClC,QAAQ;QAER;AACA,iBAAS,EAAE,SAAmB,QAAiB,WAAW,eAAe,OAAO,QAAQ,MAAM,OAAM,CAAE;MAC1G;IACJ;GACH;AAED,WAAS,IAAI,OAAO;IAChB,YAAY,OAAO;IACnB,QAAQ;MACJ,OAAO,CAAC,SAAgB;MAExB;MACA,OAAO,CAAC,SAAgB;MAExB;MACA,MAAM,CAAC,SAAgB;MAEvB;MACA,MAAM,CAAC,QAAgB,QAAQ,IAAI,GAAG;MACtC,OAAO,CAAC,QAAgB,QAAQ,IAAI,GAAG;;IAE3C,WAAW,YAAW;AAClB,UAAI,mBAAmB;AACnB;MACJ;AACA,0BAAoB;AAEpB,UAAI,qBAAqB,OAAO,aAAa,YAAY;AACrD,cAAM,YAAY,MAAM,mBAAmB,SAAS;AACpD,YAAI;AACA,gBAAM,kBAAkB,MAAM;QAClC,QAAQ;QAER;AACA,iBAAS,EAAE,SAAmB,QAAiB,WAAW,eAAe,OAAO,QAAQ,MAAM,OAAM,CAAE;MAC1G;IACJ;;IAEA,QAAQ,CAAC,IAAI,UAAU,QAAQ,SAAS,IAAI,KAAK;GACpD;AACL;AAQM,SAAU,eAAe,WAAoB,QAAwB;AACvE,SAAO,IAAI,QAAQ,aAAW,UAAU,WAAW,UAAU,CAAA,GAAI,CAAAC,YAAU,QAAQA,OAAM,CAAC,CAAC;AAC/F;AAKA,eAAsB,iBAAc;AAChC,MAAI,SAAS;AACT,UAAM,QAAQ,QAAO;AACrB,cAAU;EACd;AACA,MAAI,QAAQ;AACR,UAAM,OAAO,QAAO;AACpB,aAAS;EACb;AACA,MAAI,SAAS;AACT,cAAU;EACd;AACA,MAAI,QAAQ;AACR,aAAS;EACb;AAEA,MAAI,eAAe;AACf,UAAM,cAAc,WAAU;EAClC;AACJ;AAOA,eAAsB,oBAAoB,UAAiB;AACvD,QAAM,eAAc;AACpB,YAAM,gBAAAD,YAAK,GAAK;AAChB,UAAQ,KAAK,QAAQ;AACzB;AAOA,SAAS,kBAAkB,QAA2B;AAClD,QAAM,YAAY,gBAAAD,QAAG,aAAa,iBAAAG,QAAK,KAAK,mCAAM,iBAAgB,GAAI,iBAAiB,CAAC;AACxF,QAAM,cAAc,gBAAAH,QAAG,aAAa,iBAAAG,QAAK,KAAK,mCAAM,iBAAgB,GAAI,cAAc,CAAC;AACvF,QAAM,WAAW,mCAAM,YAAW;AAElC,QAAM,iBAAwC;IAC1C,WAAW,eAAe,QAAQ;IAClC,cAAc,QAAQ,QAAQ;IAC9B,OAAO;IACP,KAAK;;MAED,OAAO,CAAC,SAAgB;MAExB;MACA,OAAO,CAAC,SAAgB;MAExB;MACA,MAAM,CAAC,SAAgB;MAEvB;MACA,MAAM,CAAC,QAAgB,QAAQ,IAAI,GAAG;MACtC,OAAO,CAAC,QAAgB,QAAQ,IAAI,GAAG;MACvC,OAAO;;IAEX,gBAAgB;IAChB,eAAe;IACf,mBAAmB,UAAU,OAAO;;AAGxC,kBAAgB,IAAI,iCAAc,cAAc;AAChD,gBAAc,WAAW,UAAU,OAAO,SAAS,mCAAM,iBAAgB,CAAE;AAC3E,gBAAc,WAAW,OAAO,SAAS,mCAAM,iBAAgB,CAAE;AAEjE,gBAAc,sBAAsB,SAAS,MAAM;AAEnD,SAAO,cAAc,YAAY,SAAS;AAC9C;AAOA,eAAe,mBAAmB,WAAkB;AAChD,MAAI,CAAC,WAAW,CAAC,QAAQ;AAErB,WAAO;EACX;AACA,MAAI,WAAW;AACX,WAAO;EACX;AAEA,QAAM,gBAAgB,CAAE,MAAM,mCAAM,cAAc,SAAS,MAAM;AAEjE,SAAO;AACX;",
|
|
6
6
|
"names": ["import_js_controller_common", "fs", "wait", "params", "path"]
|
|
7
7
|
}
|
|
@@ -1,12 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Multihost discovery client used by the CLI setup utilities.
|
|
3
|
+
*
|
|
4
|
+
* This module implements a lightweight UDP-based discovery protocol (multicast/broadcast)
|
|
5
|
+
* to find other ioBroker hosts on the local network. It supports an optional
|
|
6
|
+
* password-based handshake and returns the objects/states database configuration
|
|
7
|
+
* necessary for remote setup and connection.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Message structure received from a multihost server during discovery or connect.
|
|
11
|
+
*/
|
|
12
|
+
export interface ReceivedMessage {
|
|
13
|
+
/** Command name, e.g. 'browse' */
|
|
14
|
+
cmd: 'browse' | 'auth';
|
|
15
|
+
/** Unique message identifier */
|
|
3
16
|
id: number;
|
|
4
|
-
|
|
17
|
+
/** Result string returned by server: 'ok', 'not authenticated', etc. */
|
|
18
|
+
result: 'ok' | 'not authenticated' | string;
|
|
19
|
+
/** Optional IP address of responder */
|
|
5
20
|
ip?: string;
|
|
21
|
+
/** Optional hostname of responder */
|
|
6
22
|
hostname?: string;
|
|
23
|
+
/** Informational text */
|
|
7
24
|
info?: string;
|
|
25
|
+
/** Whether responder is a slave */
|
|
8
26
|
slave?: boolean;
|
|
27
|
+
/** Authentication token (when required) */
|
|
9
28
|
auth?: string;
|
|
29
|
+
/** Salt used for password hashing during authentication */
|
|
10
30
|
salt?: string;
|
|
11
31
|
/** The states config of ioBroker.json */
|
|
12
32
|
states?: ioBroker.StatesDatabaseOptions;
|
|
@@ -14,47 +34,63 @@ interface ReceivedMessage {
|
|
|
14
34
|
objects?: ioBroker.ObjectsDatabaseOptions;
|
|
15
35
|
}
|
|
16
36
|
export type BrowseResultEntry = Partial<ReceivedMessage>;
|
|
37
|
+
/**
|
|
38
|
+
* MHClient implements browsing and connecting to multihost-enabled ioBroker hosts.
|
|
39
|
+
*
|
|
40
|
+
* Usage:
|
|
41
|
+
* - Create an instance of MHClient
|
|
42
|
+
* - Call `browse(timeout, isDebug)` to discover hosts
|
|
43
|
+
* - Call `connect(ip, password, callback)` to retrieve configs from a host
|
|
44
|
+
*/
|
|
17
45
|
export declare class MHClient {
|
|
46
|
+
/** Incremental message id used for request/response correlation */
|
|
18
47
|
private id;
|
|
19
48
|
private timer;
|
|
20
49
|
private server;
|
|
21
50
|
/**
|
|
22
|
-
* Stops the MH server
|
|
51
|
+
* Stops the MH server and clears any pending timers.
|
|
52
|
+
*
|
|
53
|
+
* Cleans up the UDP socket and associated timeout to ensure no resources
|
|
54
|
+
* are leaked after browsing or connect operations finish.
|
|
23
55
|
*/
|
|
24
56
|
private stopServer;
|
|
25
57
|
/**
|
|
26
|
-
* Calculate
|
|
58
|
+
* Calculate SHA-256 hash from secret and salt.
|
|
27
59
|
*
|
|
28
|
-
* @param secret
|
|
29
|
-
* @param salt
|
|
30
|
-
* @param callback
|
|
60
|
+
* @param secret - Multihost secret/password
|
|
61
|
+
* @param salt - Salt provided by server
|
|
62
|
+
* @param callback - Called with hex-encoded SHA-256 result
|
|
31
63
|
*/
|
|
32
64
|
private sha;
|
|
33
65
|
/**
|
|
34
|
-
* Starts
|
|
66
|
+
* Starts a UDP server socket used for discovery and authentication.
|
|
35
67
|
*
|
|
36
|
-
* @param isBroadcast
|
|
37
|
-
* @param timeout
|
|
38
|
-
* @param onReady ready
|
|
39
|
-
* @param onMessage
|
|
40
|
-
* @param onFinished
|
|
68
|
+
* @param isBroadcast - If true, enables broadcast mode on the socket.
|
|
69
|
+
* @param timeout - Time in ms after which the server will be automatically closed.
|
|
70
|
+
* @param onReady - Called once the socket is bound and ready to send.
|
|
71
|
+
* @param onMessage - Handler invoked for each parsed message. Return true to stop the server.
|
|
72
|
+
* @param onFinished - Called when the server stops or an error occurs.
|
|
41
73
|
*/
|
|
42
74
|
private startServer;
|
|
43
75
|
/**
|
|
44
|
-
*
|
|
76
|
+
* Browse for multihost servers.
|
|
77
|
+
*
|
|
78
|
+
* Sends a multicast/broadcast "browse" request and collects responses until timeout.
|
|
45
79
|
*
|
|
46
|
-
* @param timeout
|
|
47
|
-
* @param isDebug
|
|
80
|
+
* @param timeout - Milliseconds to wait for responses.
|
|
81
|
+
* @param isDebug - If true, include local addresses and log received messages.
|
|
82
|
+
* @returns Promise resolving to an array of discovered hosts (partial ReceivedMessage entries).
|
|
48
83
|
*/
|
|
49
84
|
browse(timeout: number, isDebug: boolean): Promise<BrowseResultEntry[]>;
|
|
50
85
|
/**
|
|
51
|
-
* Connect to server
|
|
86
|
+
* Connect to a single multihost server and retrieve its objects/states configuration.
|
|
87
|
+
*
|
|
88
|
+
* Performs an optional password-based authentication handshake if the server requires it.
|
|
52
89
|
*
|
|
53
|
-
* @param ip
|
|
54
|
-
* @param password
|
|
55
|
-
* @param callback
|
|
90
|
+
* @param ip - IP address of the server to connect to.
|
|
91
|
+
* @param password - Password to use for authentication (if required). Pass empty string to skip.
|
|
92
|
+
* @param callback - Callback called with (err, objectsConfig, statesConfig, address).
|
|
56
93
|
*/
|
|
57
94
|
connect(ip: string, password: string, callback: (err: Error | undefined, objectsConfig?: ioBroker.ObjectsDatabaseOptions, statesConfig?: ioBroker.StatesDatabaseOptions, address?: string) => void): void;
|
|
58
95
|
}
|
|
59
|
-
export {};
|
|
60
96
|
//# sourceMappingURL=multihostClient.d.ts.map
|
|
@@ -32,16 +32,20 @@ __export(multihostClient_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(multihostClient_exports);
|
|
34
34
|
var import_node_dgram = __toESM(require("node:dgram"), 1);
|
|
35
|
+
var crypto = __toESM(require("node:crypto"), 1);
|
|
35
36
|
var import_js_controller_common = require("@iobroker/js-controller-common");
|
|
36
|
-
var import_node_crypto = __toESM(require("node:crypto"), 1);
|
|
37
37
|
const PORT = 50005;
|
|
38
38
|
const MULTICAST_ADDR = "239.255.255.250";
|
|
39
39
|
class MHClient {
|
|
40
|
+
/** Incremental message id used for request/response correlation */
|
|
40
41
|
id = 1;
|
|
41
42
|
timer = null;
|
|
42
43
|
server;
|
|
43
44
|
/**
|
|
44
|
-
* Stops the MH server
|
|
45
|
+
* Stops the MH server and clears any pending timers.
|
|
46
|
+
*
|
|
47
|
+
* Cleans up the UDP socket and associated timeout to ensure no resources
|
|
48
|
+
* are leaked after browsing or connect operations finish.
|
|
45
49
|
*/
|
|
46
50
|
stopServer() {
|
|
47
51
|
if (this.server) {
|
|
@@ -57,14 +61,14 @@ class MHClient {
|
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
/**
|
|
60
|
-
* Calculate
|
|
64
|
+
* Calculate SHA-256 hash from secret and salt.
|
|
61
65
|
*
|
|
62
|
-
* @param secret
|
|
63
|
-
* @param salt
|
|
64
|
-
* @param callback
|
|
66
|
+
* @param secret - Multihost secret/password
|
|
67
|
+
* @param salt - Salt provided by server
|
|
68
|
+
* @param callback - Called with hex-encoded SHA-256 result
|
|
65
69
|
*/
|
|
66
70
|
sha(secret, salt, callback) {
|
|
67
|
-
const hash =
|
|
71
|
+
const hash = crypto.createHash("sha256");
|
|
68
72
|
hash.on("readable", function() {
|
|
69
73
|
const data = hash.read();
|
|
70
74
|
if (data) {
|
|
@@ -75,13 +79,13 @@ class MHClient {
|
|
|
75
79
|
hash.end();
|
|
76
80
|
}
|
|
77
81
|
/**
|
|
78
|
-
* Starts
|
|
82
|
+
* Starts a UDP server socket used for discovery and authentication.
|
|
79
83
|
*
|
|
80
|
-
* @param isBroadcast
|
|
81
|
-
* @param timeout
|
|
82
|
-
* @param onReady ready
|
|
83
|
-
* @param onMessage
|
|
84
|
-
* @param onFinished
|
|
84
|
+
* @param isBroadcast - If true, enables broadcast mode on the socket.
|
|
85
|
+
* @param timeout - Time in ms after which the server will be automatically closed.
|
|
86
|
+
* @param onReady - Called once the socket is bound and ready to send.
|
|
87
|
+
* @param onMessage - Handler invoked for each parsed message. Return true to stop the server.
|
|
88
|
+
* @param onFinished - Called when the server stops or an error occurs.
|
|
85
89
|
*/
|
|
86
90
|
startServer(isBroadcast, timeout, onReady, onMessage, onFinished) {
|
|
87
91
|
if (this.server) {
|
|
@@ -123,10 +127,13 @@ class MHClient {
|
|
|
123
127
|
this.server.bind();
|
|
124
128
|
}
|
|
125
129
|
/**
|
|
126
|
-
*
|
|
130
|
+
* Browse for multihost servers.
|
|
131
|
+
*
|
|
132
|
+
* Sends a multicast/broadcast "browse" request and collects responses until timeout.
|
|
127
133
|
*
|
|
128
|
-
* @param timeout
|
|
129
|
-
* @param isDebug
|
|
134
|
+
* @param timeout - Milliseconds to wait for responses.
|
|
135
|
+
* @param isDebug - If true, include local addresses and log received messages.
|
|
136
|
+
* @returns Promise resolving to an array of discovered hosts (partial ReceivedMessage entries).
|
|
130
137
|
*/
|
|
131
138
|
browse(timeout, isDebug) {
|
|
132
139
|
const result = [];
|
|
@@ -167,11 +174,13 @@ class MHClient {
|
|
|
167
174
|
});
|
|
168
175
|
}
|
|
169
176
|
/**
|
|
170
|
-
* Connect to server
|
|
177
|
+
* Connect to a single multihost server and retrieve its objects/states configuration.
|
|
178
|
+
*
|
|
179
|
+
* Performs an optional password-based authentication handshake if the server requires it.
|
|
171
180
|
*
|
|
172
|
-
* @param ip
|
|
173
|
-
* @param password
|
|
174
|
-
* @param callback
|
|
181
|
+
* @param ip - IP address of the server to connect to.
|
|
182
|
+
* @param password - Password to use for authentication (if required). Pass empty string to skip.
|
|
183
|
+
* @param callback - Callback called with (err, objectsConfig, statesConfig, address).
|
|
175
184
|
*/
|
|
176
185
|
connect(ip, password, callback) {
|
|
177
186
|
let callCb = true;
|
|
@@ -191,10 +200,8 @@ class MHClient {
|
|
|
191
200
|
} else if (!msg.states) {
|
|
192
201
|
callback(new Error(`Invalid configuration received: ${JSON.stringify(msg)}`));
|
|
193
202
|
callCb = false;
|
|
194
|
-
} else {
|
|
195
|
-
|
|
196
|
-
callback(void 0, msg.objects, msg.states, rinfo.address);
|
|
197
|
-
}
|
|
203
|
+
} else if (typeof callback === "function") {
|
|
204
|
+
callback(void 0, msg.objects, msg.states, rinfo.address);
|
|
198
205
|
}
|
|
199
206
|
}
|
|
200
207
|
} else if (msg.result === "not authenticated") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/setup/multihostClient.ts"],
|
|
4
|
-
"sourcesContent": ["import dgram from 'node:dgram';\nimport { tools } from '@iobroker/js-controller-common';\nimport crypto from 'node:crypto';\n\nconst PORT = 50005;\nconst MULTICAST_ADDR = '239.255.255.250';\n\ninterface ReceivedMessage {\n cmd: string;\n id: number;\n result: string;\n ip?: string;\n hostname?: string;\n info?: string;\n slave?: boolean;\n auth?: string;\n salt?: string;\n /** The states config of ioBroker.json */\n states?: ioBroker.StatesDatabaseOptions;\n /** The objects config of ioBroker.json */\n objects?: ioBroker.ObjectsDatabaseOptions;\n}\n\nexport type BrowseResultEntry = Partial<ReceivedMessage>;\n\nexport class MHClient {\n private id: number = 1;\n private timer: NodeJS.Timeout | null = null;\n private server: dgram.Socket | undefined;\n\n /**\n * Stops the MH server\n */\n private stopServer(): void {\n if (this.server) {\n try {\n this.server.close();\n } catch {\n // OK\n }\n this.server = undefined;\n }\n\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n }\n\n /**\n * Calculate the SHA\n *\n * @param secret the MH secret\n * @param salt the MH salt\n * @param callback\n */\n private sha(secret: string, salt: string, callback: (sha: string) => void): void {\n // calculate sha256\n const hash = crypto.createHash('sha256');\n\n hash.on('readable', function () {\n const data = hash.read();\n if (data) {\n callback(data.toString('hex'));\n }\n });\n\n hash.write(secret + salt);\n hash.end();\n }\n\n /**\n * Starts the MH server\n *\n * @param isBroadcast if server should receive broadcast\n * @param timeout timeout after which MH server will be closed\n * @param onReady ready handler\n * @param onMessage message handler, if return true here, server will be stopped\n * @param onFinished finished handler\n */\n private startServer(\n isBroadcast: boolean,\n timeout: number,\n onReady: () => void,\n onMessage: (msg: ReceivedMessage, rinfo: dgram.RemoteInfo) => boolean,\n onFinished: (err?: Error) => void,\n ): void {\n if (this.server) {\n onFinished(new Error('Some operation still active'));\n return;\n }\n this.server = dgram.createSocket('udp4');\n timeout = timeout || 2_000;\n\n this.timer = setTimeout(() => {\n if (this.server) {\n this.stopServer();\n onFinished();\n }\n }, timeout);\n\n this.server.on('error', err => {\n if (this.server) {\n this.stopServer();\n onFinished(err);\n }\n });\n\n this.server.on('message', (msg, rinfo) => {\n try {\n const message: ReceivedMessage = JSON.parse(msg.toString());\n if (this.server) {\n if (onMessage(message, rinfo)) {\n this.stopServer();\n }\n }\n } catch {\n console.error(`Multihost discovery client: Invalid answer: ${msg.toString()}`);\n }\n });\n\n this.server.on('listening', () => {\n if (isBroadcast) {\n this.server!.setBroadcast(true);\n }\n onReady();\n });\n\n this.server.bind();\n }\n\n /**\n * Start MH browsing for server\n *\n * @param timeout timeout to stop browsing\n * @param isDebug debug will also show local addresses\n */\n browse(timeout: number, isDebug: boolean): Promise<BrowseResultEntry[]> {\n const result: BrowseResultEntry[] = [];\n const ownIps = tools.findIPs();\n\n return new Promise((resolve, reject) => {\n this.startServer(\n true,\n timeout,\n () => {\n const text = JSON.stringify({\n cmd: 'browse',\n id: ++this.id,\n });\n this.server!.send(text, 0, text.length, PORT, MULTICAST_ADDR);\n },\n (msg, rinfo) => {\n // ignore own answers\n if (isDebug || (!tools.isLocalAddress(rinfo.address) && !ownIps.includes(rinfo.address))) {\n if (msg.result === 'not authenticated') {\n result.push({\n ip: rinfo.address,\n hostname: rinfo.address,\n info: 'authentication required',\n auth: msg.auth,\n });\n } else if (msg.result === 'ok') {\n result.push(msg);\n } else {\n console.log(`Multihost discovery client: Unknown answer: ${JSON.stringify(msg)}`);\n }\n }\n if (isDebug) {\n console.log(JSON.stringify(msg));\n }\n\n return false;\n },\n err => {\n if (err) {\n reject(err);\n return;\n }\n\n resolve(result);\n },\n );\n });\n }\n\n /**\n * Connect to server\n *\n * @param ip ip address of server\n * @param password password for authentication\n * @param callback\n */\n connect(\n ip: string,\n password: string,\n callback: (\n err: Error | undefined,\n objectsConfig?: ioBroker.ObjectsDatabaseOptions,\n statesConfig?: ioBroker.StatesDatabaseOptions,\n address?: string,\n ) => void,\n ): void {\n let callCb = true;\n\n this.startServer(\n false,\n 2_000,\n () => {\n const text = JSON.stringify({\n cmd: 'browse',\n id: ++this.id,\n });\n this.server!.send(text, 0, text.length, PORT, ip);\n },\n (msg, rinfo) => {\n if (msg.cmd === 'browse' && msg.id === this.id) {\n if (msg.result === 'ok') {\n if (callCb) {\n if (!msg.objects) {\n callback(new Error(`Invalid configuration received: ${JSON.stringify(msg)}`));\n callCb = false;\n } else if (!msg.states) {\n callback(new Error(`Invalid configuration received: ${JSON.stringify(msg)}`));\n callCb = false;\n } else {\n if (typeof callback === 'function') {\n callback(undefined, msg.objects, msg.states, rinfo.address);\n }\n }\n }\n } else if (msg.result === 'not authenticated') {\n if (!password) {\n if (callCb) {\n callback(new Error(`not authenticated: ${JSON.stringify(msg)}`));\n callCb = false;\n }\n } else {\n this.sha(password, msg.salt!, shaText => {\n // send password\n const text = JSON.stringify({\n cmd: 'browse',\n id: ++this.id,\n password: shaText,\n });\n this.server!.send(text, 0, text.length, PORT, ip);\n });\n return false;\n }\n } else if (msg.result === 'invalid password') {\n if (callCb) {\n callback(new Error('invalid password'));\n callCb = false;\n }\n } else {\n console.log(msg.result);\n }\n return true;\n }\n console.warn(`Multihost discovery client: Unexpected message: ${JSON.stringify(msg)}`);\n\n return false;\n },\n function onFinished(err) {\n if (callCb) {\n callback(err);\n callCb = false;\n }\n },\n );\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;
|
|
6
|
-
"names": ["
|
|
4
|
+
"sourcesContent": ["/**\n * Multihost discovery client used by the CLI setup utilities.\n *\n * This module implements a lightweight UDP-based discovery protocol (multicast/broadcast)\n * to find other ioBroker hosts on the local network. It supports an optional\n * password-based handshake and returns the objects/states database configuration\n * necessary for remote setup and connection.\n */\n\nimport dgram from 'node:dgram';\nimport * as crypto from 'node:crypto';\n\nimport { tools } from '@iobroker/js-controller-common';\n\nconst PORT = 50005;\nconst MULTICAST_ADDR = '239.255.255.250';\n\n/**\n * Message structure received from a multihost server during discovery or connect.\n */\nexport interface ReceivedMessage {\n /** Command name, e.g. 'browse' */\n cmd: 'browse' | 'auth';\n /** Unique message identifier */\n id: number;\n /** Result string returned by server: 'ok', 'not authenticated', etc. */\n // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents\n result: 'ok' | 'not authenticated' | string;\n /** Optional IP address of responder */\n ip?: string;\n /** Optional hostname of responder */\n hostname?: string;\n /** Informational text */\n info?: string;\n /** Whether responder is a slave */\n slave?: boolean;\n /** Authentication token (when required) */\n auth?: string;\n /** Salt used for password hashing during authentication */\n salt?: string;\n /** The states config of ioBroker.json */\n states?: ioBroker.StatesDatabaseOptions;\n /** The objects config of ioBroker.json */\n objects?: ioBroker.ObjectsDatabaseOptions;\n}\n\nexport type BrowseResultEntry = Partial<ReceivedMessage>;\n\n/**\n * MHClient implements browsing and connecting to multihost-enabled ioBroker hosts.\n *\n * Usage:\n * - Create an instance of MHClient\n * - Call `browse(timeout, isDebug)` to discover hosts\n * - Call `connect(ip, password, callback)` to retrieve configs from a host\n */\nexport class MHClient {\n /** Incremental message id used for request/response correlation */\n private id: number = 1;\n private timer: NodeJS.Timeout | null = null;\n private server: dgram.Socket | undefined;\n\n /**\n * Stops the MH server and clears any pending timers.\n *\n * Cleans up the UDP socket and associated timeout to ensure no resources\n * are leaked after browsing or connect operations finish.\n */\n private stopServer(): void {\n if (this.server) {\n try {\n this.server.close();\n } catch {\n // OK\n }\n this.server = undefined;\n }\n\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n }\n\n /**\n * Calculate SHA-256 hash from secret and salt.\n *\n * @param secret - Multihost secret/password\n * @param salt - Salt provided by server\n * @param callback - Called with hex-encoded SHA-256 result\n */\n private sha(secret: string, salt: string, callback: (sha: string) => void): void {\n // calculate sha256\n const hash = crypto.createHash('sha256');\n\n hash.on('readable', function () {\n const data = hash.read();\n if (data) {\n callback(data.toString('hex'));\n }\n });\n\n hash.write(secret + salt);\n hash.end();\n }\n\n /**\n * Starts a UDP server socket used for discovery and authentication.\n *\n * @param isBroadcast - If true, enables broadcast mode on the socket.\n * @param timeout - Time in ms after which the server will be automatically closed.\n * @param onReady - Called once the socket is bound and ready to send.\n * @param onMessage - Handler invoked for each parsed message. Return true to stop the server.\n * @param onFinished - Called when the server stops or an error occurs.\n */\n private startServer(\n isBroadcast: boolean,\n timeout: number,\n onReady: () => void,\n onMessage: (msg: ReceivedMessage, rinfo: dgram.RemoteInfo) => boolean,\n onFinished: (err?: Error) => void,\n ): void {\n if (this.server) {\n onFinished(new Error('Some operation still active'));\n return;\n }\n this.server = dgram.createSocket('udp4');\n timeout = timeout || 2_000;\n\n this.timer = setTimeout(() => {\n if (this.server) {\n this.stopServer();\n onFinished();\n }\n }, timeout);\n\n this.server.on('error', err => {\n if (this.server) {\n this.stopServer();\n onFinished(err);\n }\n });\n\n this.server.on('message', (msg, rinfo) => {\n try {\n const message: ReceivedMessage = JSON.parse(msg.toString());\n if (this.server) {\n if (onMessage(message, rinfo)) {\n this.stopServer();\n }\n }\n } catch {\n console.error(`Multihost discovery client: Invalid answer: ${msg.toString()}`);\n }\n });\n\n this.server.on('listening', () => {\n if (isBroadcast) {\n this.server!.setBroadcast(true);\n }\n onReady();\n });\n\n this.server.bind();\n }\n\n /**\n * Browse for multihost servers.\n *\n * Sends a multicast/broadcast \"browse\" request and collects responses until timeout.\n *\n * @param timeout - Milliseconds to wait for responses.\n * @param isDebug - If true, include local addresses and log received messages.\n * @returns Promise resolving to an array of discovered hosts (partial ReceivedMessage entries).\n */\n browse(timeout: number, isDebug: boolean): Promise<BrowseResultEntry[]> {\n const result: BrowseResultEntry[] = [];\n const ownIps = tools.findIPs();\n\n return new Promise((resolve, reject) => {\n this.startServer(\n true,\n timeout,\n () => {\n const text = JSON.stringify({\n cmd: 'browse',\n id: ++this.id,\n });\n this.server!.send(text, 0, text.length, PORT, MULTICAST_ADDR);\n },\n (msg, rinfo) => {\n // ignore own answers\n if (isDebug || (!tools.isLocalAddress(rinfo.address) && !ownIps.includes(rinfo.address))) {\n if (msg.result === 'not authenticated') {\n result.push({\n ip: rinfo.address,\n hostname: rinfo.address,\n info: 'authentication required',\n auth: msg.auth,\n });\n } else if (msg.result === 'ok') {\n result.push(msg);\n } else {\n console.log(`Multihost discovery client: Unknown answer: ${JSON.stringify(msg)}`);\n }\n }\n if (isDebug) {\n console.log(JSON.stringify(msg));\n }\n\n return false;\n },\n err => {\n if (err) {\n reject(err);\n return;\n }\n\n resolve(result);\n },\n );\n });\n }\n\n /**\n * Connect to a single multihost server and retrieve its objects/states configuration.\n *\n * Performs an optional password-based authentication handshake if the server requires it.\n *\n * @param ip - IP address of the server to connect to.\n * @param password - Password to use for authentication (if required). Pass empty string to skip.\n * @param callback - Callback called with (err, objectsConfig, statesConfig, address).\n */\n connect(\n ip: string,\n password: string,\n callback: (\n err: Error | undefined,\n objectsConfig?: ioBroker.ObjectsDatabaseOptions,\n statesConfig?: ioBroker.StatesDatabaseOptions,\n address?: string,\n ) => void,\n ): void {\n let callCb = true;\n\n this.startServer(\n false,\n 2_000,\n () => {\n const text = JSON.stringify({\n cmd: 'browse',\n id: ++this.id,\n });\n this.server!.send(text, 0, text.length, PORT, ip);\n },\n (msg, rinfo) => {\n // we expect only one answer\n if (msg.cmd === 'browse' && msg.id === this.id) {\n if (msg.result === 'ok') {\n if (callCb) {\n if (!msg.objects) {\n callback(new Error(`Invalid configuration received: ${JSON.stringify(msg)}`));\n callCb = false;\n } else if (!msg.states) {\n callback(new Error(`Invalid configuration received: ${JSON.stringify(msg)}`));\n callCb = false;\n } else if (typeof callback === 'function') {\n callback(undefined, msg.objects, msg.states, rinfo.address);\n }\n }\n } else if (msg.result === 'not authenticated') {\n if (!password) {\n if (callCb) {\n callback(new Error(`not authenticated: ${JSON.stringify(msg)}`));\n callCb = false;\n }\n } else {\n this.sha(password, msg.salt!, shaText => {\n // send password\n const text = JSON.stringify({\n cmd: 'browse',\n id: ++this.id,\n password: shaText,\n });\n this.server!.send(text, 0, text.length, PORT, ip);\n });\n return false;\n }\n } else if (msg.result === 'invalid password') {\n if (callCb) {\n callback(new Error('invalid password'));\n callCb = false;\n }\n } else {\n console.log(msg.result);\n }\n return true;\n }\n console.warn(`Multihost discovery client: Unexpected message: ${JSON.stringify(msg)}`);\n\n return false;\n },\n function onFinished(err) {\n if (callCb) {\n callback(err);\n callCb = false;\n }\n },\n );\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;AASA,wBAAkB;AAClB,aAAwB;AAExB,kCAAsB;AAEtB,MAAM,OAAO;AACb,MAAM,iBAAiB;AAyCjB,MAAO,SAAQ;;EAET,KAAa;EACb,QAA+B;EAC/B;;;;;;;EAQA,aAAU;AACd,QAAI,KAAK,QAAQ;AACb,UAAI;AACA,aAAK,OAAO,MAAK;MACrB,QAAQ;MAER;AACA,WAAK,SAAS;IAClB;AAEA,QAAI,KAAK,OAAO;AACZ,mBAAa,KAAK,KAAK;AACvB,WAAK,QAAQ;IACjB;EACJ;;;;;;;;EASQ,IAAI,QAAgB,MAAc,UAA+B;AAErE,UAAM,OAAO,OAAO,WAAW,QAAQ;AAEvC,SAAK,GAAG,YAAY,WAAA;AAChB,YAAM,OAAO,KAAK,KAAI;AACtB,UAAI,MAAM;AACN,iBAAS,KAAK,SAAS,KAAK,CAAC;MACjC;IACJ,CAAC;AAED,SAAK,MAAM,SAAS,IAAI;AACxB,SAAK,IAAG;EACZ;;;;;;;;;;EAWQ,YACJ,aACA,SACA,SACA,WACA,YAAiC;AAEjC,QAAI,KAAK,QAAQ;AACb,iBAAW,IAAI,MAAM,6BAA6B,CAAC;AACnD;IACJ;AACA,SAAK,SAAS,kBAAAA,QAAM,aAAa,MAAM;AACvC,cAAU,WAAW;AAErB,SAAK,QAAQ,WAAW,MAAK;AACzB,UAAI,KAAK,QAAQ;AACb,aAAK,WAAU;AACf,mBAAU;MACd;IACJ,GAAG,OAAO;AAEV,SAAK,OAAO,GAAG,SAAS,SAAM;AAC1B,UAAI,KAAK,QAAQ;AACb,aAAK,WAAU;AACf,mBAAW,GAAG;MAClB;IACJ,CAAC;AAED,SAAK,OAAO,GAAG,WAAW,CAAC,KAAK,UAAS;AACrC,UAAI;AACA,cAAM,UAA2B,KAAK,MAAM,IAAI,SAAQ,CAAE;AAC1D,YAAI,KAAK,QAAQ;AACb,cAAI,UAAU,SAAS,KAAK,GAAG;AAC3B,iBAAK,WAAU;UACnB;QACJ;MACJ,QAAQ;AACJ,gBAAQ,MAAM,+CAA+C,IAAI,SAAQ,CAAE,EAAE;MACjF;IACJ,CAAC;AAED,SAAK,OAAO,GAAG,aAAa,MAAK;AAC7B,UAAI,aAAa;AACb,aAAK,OAAQ,aAAa,IAAI;MAClC;AACA,cAAO;IACX,CAAC;AAED,SAAK,OAAO,KAAI;EACpB;;;;;;;;;;EAWA,OAAO,SAAiB,SAAgB;AACpC,UAAM,SAA8B,CAAA;AACpC,UAAM,SAAS,kCAAM,QAAO;AAE5B,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAU;AACnC,WAAK,YACD,MACA,SACA,MAAK;AACD,cAAM,OAAO,KAAK,UAAU;UACxB,KAAK;UACL,IAAI,EAAE,KAAK;SACd;AACD,aAAK,OAAQ,KAAK,MAAM,GAAG,KAAK,QAAQ,MAAM,cAAc;MAChE,GACA,CAAC,KAAK,UAAS;AAEX,YAAI,WAAY,CAAC,kCAAM,eAAe,MAAM,OAAO,KAAK,CAAC,OAAO,SAAS,MAAM,OAAO,GAAI;AACtF,cAAI,IAAI,WAAW,qBAAqB;AACpC,mBAAO,KAAK;cACR,IAAI,MAAM;cACV,UAAU,MAAM;cAChB,MAAM;cACN,MAAM,IAAI;aACb;UACL,WAAW,IAAI,WAAW,MAAM;AAC5B,mBAAO,KAAK,GAAG;UACnB,OAAO;AACH,oBAAQ,IAAI,+CAA+C,KAAK,UAAU,GAAG,CAAC,EAAE;UACpF;QACJ;AACA,YAAI,SAAS;AACT,kBAAQ,IAAI,KAAK,UAAU,GAAG,CAAC;QACnC;AAEA,eAAO;MACX,GACA,SAAM;AACF,YAAI,KAAK;AACL,iBAAO,GAAG;AACV;QACJ;AAEA,gBAAQ,MAAM;MAClB,CAAC;IAET,CAAC;EACL;;;;;;;;;;EAWA,QACI,IACA,UACA,UAKS;AAET,QAAI,SAAS;AAEb,SAAK,YACD,OACA,KACA,MAAK;AACD,YAAM,OAAO,KAAK,UAAU;QACxB,KAAK;QACL,IAAI,EAAE,KAAK;OACd;AACD,WAAK,OAAQ,KAAK,MAAM,GAAG,KAAK,QAAQ,MAAM,EAAE;IACpD,GACA,CAAC,KAAK,UAAS;AAEX,UAAI,IAAI,QAAQ,YAAY,IAAI,OAAO,KAAK,IAAI;AAC5C,YAAI,IAAI,WAAW,MAAM;AACrB,cAAI,QAAQ;AACR,gBAAI,CAAC,IAAI,SAAS;AACd,uBAAS,IAAI,MAAM,mCAAmC,KAAK,UAAU,GAAG,CAAC,EAAE,CAAC;AAC5E,uBAAS;YACb,WAAW,CAAC,IAAI,QAAQ;AACpB,uBAAS,IAAI,MAAM,mCAAmC,KAAK,UAAU,GAAG,CAAC,EAAE,CAAC;AAC5E,uBAAS;YACb,WAAW,OAAO,aAAa,YAAY;AACvC,uBAAS,QAAW,IAAI,SAAS,IAAI,QAAQ,MAAM,OAAO;YAC9D;UACJ;QACJ,WAAW,IAAI,WAAW,qBAAqB;AAC3C,cAAI,CAAC,UAAU;AACX,gBAAI,QAAQ;AACR,uBAAS,IAAI,MAAM,sBAAsB,KAAK,UAAU,GAAG,CAAC,EAAE,CAAC;AAC/D,uBAAS;YACb;UACJ,OAAO;AACH,iBAAK,IAAI,UAAU,IAAI,MAAO,aAAU;AAEpC,oBAAM,OAAO,KAAK,UAAU;gBACxB,KAAK;gBACL,IAAI,EAAE,KAAK;gBACX,UAAU;eACb;AACD,mBAAK,OAAQ,KAAK,MAAM,GAAG,KAAK,QAAQ,MAAM,EAAE;YACpD,CAAC;AACD,mBAAO;UACX;QACJ,WAAW,IAAI,WAAW,oBAAoB;AAC1C,cAAI,QAAQ;AACR,qBAAS,IAAI,MAAM,kBAAkB,CAAC;AACtC,qBAAS;UACb;QACJ,OAAO;AACH,kBAAQ,IAAI,IAAI,MAAM;QAC1B;AACA,eAAO;MACX;AACA,cAAQ,KAAK,mDAAmD,KAAK,UAAU,GAAG,CAAC,EAAE;AAErF,aAAO;IACX,GACA,SAAS,WAAW,KAAG;AACnB,UAAI,QAAQ;AACR,iBAAS,GAAG;AACZ,iBAAS;MACb;IACJ,CAAC;EAET;;",
|
|
6
|
+
"names": ["dgram"]
|
|
7
7
|
}
|
|
@@ -5,14 +5,26 @@ interface MHParams {
|
|
|
5
5
|
persist?: boolean;
|
|
6
6
|
debug?: boolean;
|
|
7
7
|
}
|
|
8
|
+
/** Options for Multihost CLI */
|
|
8
9
|
export interface CLIMultihostOptions {
|
|
10
|
+
/** Redis client for objects DB */
|
|
9
11
|
objects: ObjectsRedisClient;
|
|
12
|
+
/** Additional parameters */
|
|
10
13
|
params?: MHParams;
|
|
11
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Multihost CLI class
|
|
17
|
+
* Handles multihost related commands
|
|
18
|
+
*/
|
|
12
19
|
export declare class Multihost {
|
|
13
20
|
private readonly configName;
|
|
14
21
|
private params;
|
|
15
22
|
private objects;
|
|
23
|
+
/**
|
|
24
|
+
* Constructor
|
|
25
|
+
*
|
|
26
|
+
* @param options options for the Multihost CLI
|
|
27
|
+
*/
|
|
16
28
|
constructor(options: CLIMultihostOptions);
|
|
17
29
|
/**
|
|
18
30
|
* Retrieve config (iobroker.json content)
|
|
@@ -53,10 +65,10 @@ export declare class Multihost {
|
|
|
53
65
|
/**
|
|
54
66
|
* Connect to given MH server
|
|
55
67
|
*
|
|
56
|
-
* @param mhClient
|
|
57
|
-
* @param ip
|
|
68
|
+
* @param mhClient MultiHost Client used for connection
|
|
69
|
+
* @param ip IP address of server
|
|
58
70
|
* @param pass password
|
|
59
|
-
* @param callback
|
|
71
|
+
* @param callback callback
|
|
60
72
|
*/
|
|
61
73
|
connectHelper(mhClient: MHClient, ip: string, pass: string, callback: (err?: Error) => void): void;
|
|
62
74
|
/**
|
|
@@ -64,7 +76,7 @@ export declare class Multihost {
|
|
|
64
76
|
*
|
|
65
77
|
* @param index index of host to connect to
|
|
66
78
|
* @param pass password
|
|
67
|
-
* @param callback
|
|
79
|
+
* @param callback callback
|
|
68
80
|
*/
|
|
69
81
|
connect(index: number | null, pass: string | null, callback: (err?: Error, list?: BrowseResultEntry[]) => void): Promise<void>;
|
|
70
82
|
}
|