@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.
Files changed (43) hide show
  1. package/build/cjs/lib/cli/cliLogs.js.map +1 -1
  2. package/build/cjs/lib/setup/dbConnection.js +4 -4
  3. package/build/cjs/lib/setup/dbConnection.js.map +2 -2
  4. package/build/cjs/lib/setup/multihostClient.d.ts +58 -22
  5. package/build/cjs/lib/setup/multihostClient.js +31 -24
  6. package/build/cjs/lib/setup/multihostClient.js.map +3 -3
  7. package/build/cjs/lib/setup/setupMultihost.d.ts +16 -4
  8. package/build/cjs/lib/setup/setupMultihost.js +32 -16
  9. package/build/cjs/lib/setup/setupMultihost.js.map +3 -3
  10. package/build/cjs/lib/setup/setupSetup.d.ts +3 -11
  11. package/build/cjs/lib/setup/setupSetup.js +65 -76
  12. package/build/cjs/lib/setup/setupSetup.js.map +2 -2
  13. package/build/cjs/lib/setup/setupVendor.d.ts +3 -1
  14. package/build/cjs/lib/setup/setupVendor.js +72 -35
  15. package/build/cjs/lib/setup/setupVendor.js.map +2 -2
  16. package/build/cjs/lib/setup.js +5 -2
  17. package/build/cjs/lib/setup.js.map +2 -2
  18. package/build/esm/lib/cli/cliLogs.js +1 -1
  19. package/build/esm/lib/cli/cliLogs.js.map +1 -1
  20. package/build/esm/lib/setup/dbConnection.d.ts.map +1 -1
  21. package/build/esm/lib/setup/dbConnection.js +14 -14
  22. package/build/esm/lib/setup/dbConnection.js.map +1 -1
  23. package/build/esm/lib/setup/multihostClient.d.ts +58 -22
  24. package/build/esm/lib/setup/multihostClient.d.ts.map +1 -1
  25. package/build/esm/lib/setup/multihostClient.js +47 -23
  26. package/build/esm/lib/setup/multihostClient.js.map +1 -1
  27. package/build/esm/lib/setup/setupMultihost.d.ts +16 -4
  28. package/build/esm/lib/setup/setupMultihost.d.ts.map +1 -1
  29. package/build/esm/lib/setup/setupMultihost.js +41 -18
  30. package/build/esm/lib/setup/setupMultihost.js.map +1 -1
  31. package/build/esm/lib/setup/setupSetup.d.ts +3 -11
  32. package/build/esm/lib/setup/setupSetup.d.ts.map +1 -1
  33. package/build/esm/lib/setup/setupSetup.js +66 -80
  34. package/build/esm/lib/setup/setupSetup.js.map +1 -1
  35. package/build/esm/lib/setup/setupVendor.d.ts +3 -1
  36. package/build/esm/lib/setup/setupVendor.d.ts.map +1 -1
  37. package/build/esm/lib/setup/setupVendor.js +72 -35
  38. package/build/esm/lib/setup/setupVendor.js.map +1 -1
  39. package/build/esm/lib/setup.d.ts.map +1 -1
  40. package/build/esm/lib/setup.js +6 -3
  41. package/build/esm/lib/setup.js.map +1 -1
  42. package/build/tsconfig.build.tsbuildinfo +1 -1
  43. 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 /** @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"],
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 && 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": "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",
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": "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",
6
6
  "names": ["import_js_controller_common", "fs", "wait", "params", "path"]
7
7
  }
@@ -1,12 +1,32 @@
1
- interface ReceivedMessage {
2
- cmd: string;
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
- result: string;
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 the SHA
58
+ * Calculate SHA-256 hash from secret and salt.
27
59
  *
28
- * @param secret the MH secret
29
- * @param salt the MH 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 the MH server
66
+ * Starts a UDP server socket used for discovery and authentication.
35
67
  *
36
- * @param isBroadcast if server should receive broadcast
37
- * @param timeout timeout after which MH server will be closed
38
- * @param onReady ready handler
39
- * @param onMessage message handler, if return true here, server will be stopped
40
- * @param onFinished finished handler
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
- * Start MH browsing for server
76
+ * Browse for multihost servers.
77
+ *
78
+ * Sends a multicast/broadcast "browse" request and collects responses until timeout.
45
79
  *
46
- * @param timeout timeout to stop browsing
47
- * @param isDebug debug will also show local addresses
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 ip address of server
54
- * @param password password for authentication
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 the SHA
64
+ * Calculate SHA-256 hash from secret and salt.
61
65
  *
62
- * @param secret the MH secret
63
- * @param salt the MH 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 = import_node_crypto.default.createHash("sha256");
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 the MH server
82
+ * Starts a UDP server socket used for discovery and authentication.
79
83
  *
80
- * @param isBroadcast if server should receive broadcast
81
- * @param timeout timeout after which MH server will be closed
82
- * @param onReady ready handler
83
- * @param onMessage message handler, if return true here, server will be stopped
84
- * @param onFinished finished handler
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
- * Start MH browsing for server
130
+ * Browse for multihost servers.
131
+ *
132
+ * Sends a multicast/broadcast "browse" request and collects responses until timeout.
127
133
  *
128
- * @param timeout timeout to stop browsing
129
- * @param isDebug debug will also show local addresses
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 ip address of server
173
- * @param password password for authentication
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
- if (typeof callback === "function") {
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": "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",
6
- "names": ["crypto", "dgram"]
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": "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",
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 mhclient used for connection
57
- * @param ip ip address of server
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
  }