@iobroker/adapter-react-v5 6.1.3 → 6.1.5

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 (66) hide show
  1. package/README.md +3 -0
  2. package/craco-module-federation.js +1 -1
  3. package/package.json +1 -1
  4. package/src/AdminConnection.tsx +3 -0
  5. package/src/Components/404.tsx +121 -0
  6. package/src/Components/ColorPicker.tsx +315 -0
  7. package/src/Components/ComplexCron.tsx +507 -0
  8. package/src/Components/CopyToClipboard.tsx +165 -0
  9. package/src/Components/CustomModal.tsx +163 -0
  10. package/src/Components/FileBrowser.tsx +2394 -0
  11. package/src/Components/FileViewer.tsx +384 -0
  12. package/src/Components/Icon.tsx +210 -0
  13. package/src/Components/IconPicker.tsx +149 -0
  14. package/src/Components/IconSelector.tsx +2202 -0
  15. package/src/Components/Image.tsx +176 -0
  16. package/src/Components/Loader.tsx +304 -0
  17. package/src/Components/Logo.tsx +166 -0
  18. package/src/Components/MDUtils.tsx +100 -0
  19. package/src/Components/ObjectBrowser.tsx +7915 -0
  20. package/src/Components/Router.tsx +90 -0
  21. package/src/Components/SaveCloseButtons.tsx +113 -0
  22. package/src/Components/Schedule.tsx +1724 -0
  23. package/src/Components/SelectWithIcon.tsx +197 -0
  24. package/src/Components/TabContainer.tsx +55 -0
  25. package/src/Components/TabContent.tsx +37 -0
  26. package/src/Components/TabHeader.tsx +19 -0
  27. package/src/Components/TableResize.tsx +259 -0
  28. package/src/Components/TextWithIcon.tsx +148 -0
  29. package/src/Components/ToggleThemeMenu.tsx +34 -0
  30. package/src/Components/TreeTable.tsx +919 -0
  31. package/src/Components/UploadImage.tsx +599 -0
  32. package/src/Components/Utils.tsx +1794 -0
  33. package/src/Components/loader.css +222 -0
  34. package/src/Components/withWidth.tsx +21 -0
  35. package/src/Connection.tsx +7 -0
  36. package/src/Dialogs/ComplexCron.tsx +129 -0
  37. package/src/Dialogs/Confirm.tsx +162 -0
  38. package/src/Dialogs/Cron.tsx +182 -0
  39. package/src/Dialogs/Error.tsx +72 -0
  40. package/src/Dialogs/Message.tsx +71 -0
  41. package/src/Dialogs/SelectFile.tsx +270 -0
  42. package/src/Dialogs/SelectID.tsx +298 -0
  43. package/src/Dialogs/SimpleCron.tsx +100 -0
  44. package/src/Dialogs/TextInput.tsx +107 -0
  45. package/src/GenericApp.tsx +976 -0
  46. package/src/LegacyConnection.tsx +3589 -0
  47. package/src/Prompt.tsx +20 -0
  48. package/src/Theme.tsx +479 -0
  49. package/src/icons/IconAdapter.tsx +20 -0
  50. package/src/icons/IconAlias.tsx +20 -0
  51. package/src/icons/IconChannel.tsx +21 -0
  52. package/src/icons/IconClearFilter.tsx +22 -0
  53. package/src/icons/IconClosed.tsx +17 -0
  54. package/src/icons/IconCopy.tsx +16 -0
  55. package/src/icons/IconDevice.tsx +27 -0
  56. package/src/icons/IconDocument.tsx +17 -0
  57. package/src/icons/IconDocumentReadOnly.tsx +18 -0
  58. package/src/icons/IconExpert.tsx +18 -0
  59. package/src/icons/IconFx.tsx +36 -0
  60. package/src/icons/IconInstance.tsx +20 -0
  61. package/src/icons/IconLogout.tsx +30 -0
  62. package/src/icons/IconNoIcon.tsx +19 -0
  63. package/src/icons/IconOpen.tsx +17 -0
  64. package/src/icons/IconProps.tsx +15 -0
  65. package/src/icons/IconState.tsx +17 -0
  66. package/src/index.css +55 -0
@@ -0,0 +1,3589 @@
1
+ /**
2
+ * Copyright 2020-2024, Denis Haev (bluefox) <dogafox@gmail.com>
3
+ *
4
+ * MIT License
5
+ *
6
+ * */
7
+
8
+ import { type HostInfo } from '@iobroker/js-controller-common-db/build/esm/lib/common/tools';
9
+ import { type FilteredNotificationInformation } from '@iobroker/js-controller-common/build/esm/lib/common/notificationHandler';
10
+
11
+ declare global {
12
+ interface Window {
13
+ adapterName: undefined | string;
14
+ socketUrl: undefined | string;
15
+ registerSocketOnLoad: (func: () => void) => void;
16
+ vendorPrefix: undefined | string;
17
+ io: any;
18
+ }
19
+ }
20
+
21
+ /** Possible progress states. */
22
+ export const PROGRESS = {
23
+ /** The socket is connecting. */
24
+ CONNECTING: 0,
25
+ /** The socket is successfully connected. */
26
+ CONNECTED: 1,
27
+ /** All objects are loaded. */
28
+ OBJECTS_LOADED: 2,
29
+ /** All states are loaded. */
30
+ STATES_LOADED: 3,
31
+ /** The socket is ready for use. */
32
+ READY: 4,
33
+ };
34
+
35
+ const PERMISSION_ERROR = 'permissionError';
36
+ const NOT_CONNECTED = 'notConnectedError';
37
+ const TIMEOUT_FOR_ADMIN4 = 1300;
38
+
39
+ export const ERRORS = {
40
+ PERMISSION_ERROR,
41
+ NOT_CONNECTED,
42
+ };
43
+
44
+ export type BinaryStateChangeHandler = (
45
+ id: string,
46
+ base64: string | null,
47
+ ) => void;
48
+
49
+ function fixAdminUI(obj: ioBroker.AdapterObject): ioBroker.AdapterObject {
50
+ // @ts-expect-error it is deprecated, but still could appear
51
+ if (obj?.common && !obj.common.adminUI) {
52
+ if (obj.common.noConfig) {
53
+ // @ts-expect-error it is deprecated, but still could appear
54
+ obj.common.adminUI = obj.common.adminUI || {};
55
+ // @ts-expect-error it is deprecated, but still could appear
56
+ obj.common.adminUI.config = 'none';
57
+ // @ts-expect-error it is deprecated, but still could appear
58
+ } else if (obj.common.jsonConfig) {
59
+ // @ts-expect-error it is deprecated, but still could appear
60
+ obj.common.adminUI = obj.common.adminUI || {};
61
+ // @ts-expect-error it is deprecated, but still could appear
62
+ obj.common.adminUI.config = 'json';
63
+ } else if (obj.common.materialize) {
64
+ // @ts-expect-error it is deprecated, but still could appear
65
+ obj.common.adminUI = obj.common.adminUI || {};
66
+ // @ts-expect-error it is deprecated, but still could appear
67
+ obj.common.adminUI.config = 'materialize';
68
+ } else {
69
+ // @ts-expect-error it is deprecated, but still could appear
70
+ obj.common.adminUI = obj.common.adminUI || {};
71
+ // @ts-expect-error it is deprecated, but still could appear
72
+ obj.common.adminUI.config = 'html';
73
+ }
74
+
75
+ // @ts-expect-error it is deprecated, but still could appear
76
+ if (obj.common.jsonCustom) {
77
+ // @ts-expect-error it is deprecated, but still could appear
78
+ obj.common.adminUI = obj.common.adminUI || {};
79
+ // @ts-expect-error it is deprecated, but still could appear
80
+ obj.common.adminUI.custom = 'json';
81
+ } else if (obj.common.supportCustoms) {
82
+ // @ts-expect-error it is deprecated, but still could appear
83
+ obj.common.adminUI = obj.common.adminUI || {};
84
+ // @ts-expect-error it is deprecated, but still could appear
85
+ obj.common.adminUI.custom = 'json';
86
+ }
87
+
88
+ if (obj.common.materializeTab && obj.common.adminTab) {
89
+ // @ts-expect-error it is deprecated, but still could appear
90
+ obj.common.adminUI = obj.common.adminUI || {};
91
+ // @ts-expect-error it is deprecated, but still could appear
92
+ obj.common.adminUI.tab = 'materialize';
93
+ } else if (obj.common.adminTab) {
94
+ // @ts-expect-error it is deprecated, but still could appear
95
+ obj.common.adminUI = obj.common.adminUI || {};
96
+ // @ts-expect-error it is deprecated, but still could appear
97
+ obj.common.adminUI.tab = 'html';
98
+ }
99
+
100
+ // @ts-expect-error it is deprecated, but still could appear
101
+ obj.common.adminUI && console.debug(`Please add to "${obj._id.replace(/\.\d+$/, '')}" common.adminUI=${JSON.stringify(obj.common.adminUI)}`);
102
+ }
103
+ return obj;
104
+ }
105
+
106
+ /** Converts ioB pattern into regex */
107
+ export function pattern2RegEx(pattern: string): string {
108
+ pattern = (pattern || '').toString();
109
+
110
+ const startsWithWildcard = pattern[0] === '*';
111
+ const endsWithWildcard = pattern[pattern.length - 1] === '*';
112
+
113
+ pattern = pattern
114
+ .replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&')
115
+ .replace(/\*/g, '.*');
116
+
117
+ return (startsWithWildcard ? '' : '^') + pattern + (endsWithWildcard ? '' : '$');
118
+ }
119
+
120
+ interface ConnectionProps {
121
+ /** The socket name. */
122
+ name?: string;
123
+ /** State IDs to always automatically subscribe to. */
124
+ autoSubscribes?: string[];
125
+ /** Automatically subscribe to logging. */
126
+ autoSubscribeLog?: boolean;
127
+ /** The protocol to use for the socket.io connection. */
128
+ protocol: string;
129
+ /** The host name to use for the socket.io connection. */
130
+ host: string;
131
+ /** The port to use for the socket.io connection. */
132
+ port?: string | number;
133
+ /** The socket.io connection timeout. */
134
+ ioTimeout?: number;
135
+ /** The socket.io command timeout. */
136
+ cmdTimeout?: number;
137
+ /** Flag to indicate if all objects should be loaded or not. Default true (not loaded) */
138
+ doNotLoadAllObjects?: boolean;
139
+ /** Flag to indicate if AccessControlList for current user will be loaded or not. Default true (not loaded) */
140
+ doNotLoadACL?: boolean;
141
+ /** Progress callback. */
142
+ onProgress?: (progress: number) => void;
143
+ /** Ready callback. */
144
+ onReady?: (objects: Record<string, ioBroker.Object>) => void;
145
+ /** Log callback. */
146
+ onLog?: (text: string) => void;
147
+ /** Error callback. */
148
+ onError?: (error: any) => void;
149
+ /** Object change callback. */
150
+ onObjectChange?: ioBroker.ObjectChangeHandler;
151
+ /** Gets called when the system language is determined */
152
+ onLanguage?: (lang: ioBroker.Languages) => void;
153
+ /** Forces the use of the Compact Methods, wich only exists in admin 5 UI. */
154
+ admin5only?: boolean;
155
+ /** The device UUID with which the communication must be established */
156
+ uuid?: string;
157
+ /** Authentication token (used only in cloud) */
158
+ token?: string;
159
+ }
160
+
161
+ export interface ConnectOptions {
162
+ path?: string;
163
+ query?: string;
164
+ name?: string;
165
+ timeout?: number;
166
+ uuid?: string;
167
+ }
168
+
169
+ export interface SocketClient{
170
+ connect(url?: string, options?: ConnectOptions): void;
171
+ close(): void;
172
+ destroy(): void;
173
+
174
+ readonly connected: boolean;
175
+
176
+ on(event: string, callback: (...args: any) => void): void;
177
+ off(event: string, callback: (...args: any) => void): void;
178
+ emit(event: string, ...args: any): boolean;
179
+ }
180
+
181
+ interface RenameGroupObject extends ioBroker.GroupObject {
182
+ newId?: ioBroker.ObjectIDs.Group;
183
+ }
184
+
185
+ interface Promises {
186
+ installedCompact?: {
187
+ [host: string]: Promise<Record<string, ioBroker.AdapterObject>> | null;
188
+ } | null;
189
+ installed?: {
190
+ [host: string]: Promise<Record<string, ioBroker.AdapterObject>> | null;
191
+ } | null;
192
+ systemConfig?: Promise<ioBroker.SystemConfigObject> | null;
193
+ hosts?: Promise<ioBroker.HostObject[]> | null;
194
+ users?: Promise<ioBroker.UserObject[]> | null;
195
+ compactAdapters?: Promise<Record<string, ioBroker.AdapterObject>> | null;
196
+ repoCompact?: Promise<any> | null;
197
+ version?: Promise<{ version: string; serverName: string }> | null;
198
+ groups?: Promise<ioBroker.GroupObject[]> | null;
199
+ repo?: Promise<Record<string, ioBroker.AdapterObject>> | null;
200
+ cert?: Promise<{ name: string; type: 'public' | 'private' | 'chained' | '' }[]> | null;
201
+ webName?: Promise<string> | null;
202
+ compactInstances?: Promise<Record<string, ioBroker.InstanceObject>> | null;
203
+ getCompactSystemRepositories?: Promise<ioBroker.Object> | null;
204
+ systemConfigPromise?: Promise<ioBroker.SystemConfigObject> | null;
205
+ hostsCompact?: Promise<ioBroker.HostObject[]> | null;
206
+ uuid?: Promise<string | undefined>;
207
+ [feature: `supportedFeatures_${string}`]: Promise<boolean> | null;
208
+ [type: `instances_${string}`]: Promise<any> | null;
209
+ [_enum: `enums_${string}`]: Promise<any> | null;
210
+ [adapter: `adapter_${string}`]: Promise<any> | null;
211
+ [host: `IPs_${string}`]: Promise<string[]> | null;
212
+ [host: `hostInfo_${string}`]: Promise<HostInfo> | null;
213
+ [host: `hostInfoShort_${string}`]: Promise<HostInfo> | null;
214
+ [host: `rIPs_${string}`]: Promise<{ name: string; address: string; family: 'ipv4' | 'ipv6' }[]> | null;
215
+ currentInstance?: Promise<string> | null;
216
+ }
217
+
218
+ class Connection {
219
+ // Do not define it as null, else we must check for null everywhere
220
+ private _socket: SocketClient;
221
+
222
+ private _authTimer: ReturnType<typeof setTimeout> | null | undefined;
223
+
224
+ private systemLang: ioBroker.Languages = 'en';
225
+
226
+ private readonly _waitForFirstConnection: Promise<void>;
227
+
228
+ private _waitForFirstConnectionResolve: (() => void) | null = null;
229
+
230
+ private _promises: Promises = {};
231
+
232
+ private readonly _instanceSubscriptions: Record<string, {messageType: string; callback: (data: Record<string, any>, sourceInstance: string, messageType: string) => void }[]>;
233
+
234
+ private props: ConnectionProps;
235
+
236
+ private doNotLoadAllObjects: boolean;
237
+
238
+ private readonly doNotLoadACL: boolean;
239
+
240
+ private states: Record<string, ioBroker.State> = {};
241
+
242
+ private objects: Record<string, ioBroker.Object> | null = null;
243
+
244
+ private scriptLoadCounter: number | undefined;
245
+
246
+ private acl: Record<string, any> | null = null;
247
+
248
+ private firstConnect: boolean = true;
249
+
250
+ private readonly waitForRestart: boolean = false;
251
+
252
+ private connected: boolean = false;
253
+
254
+ private readonly statesSubscribes: Record<string, { reg: RegExp; cbs: (ioBroker.StateChangeHandler | BinaryStateChangeHandler)[] }> = {};
255
+
256
+ private readonly objectsSubscribes: Record<string, { reg: RegExp; cbs: ((id: string, obj: ioBroker.Object | null | undefined, oldObj?: ioBroker.Object | null) => void)[] }> = {};
257
+
258
+ private readonly filesSubscribes: Record<string, { regId: RegExp; cbs: ioBroker.FileChangeHandler[]; regFilePattern: RegExp }> = {};
259
+
260
+ private onConnectionHandlers: ((connected: boolean) => void)[] = [];
261
+
262
+ private onLogHandlers: ((message: string) => void)[] = [];
263
+
264
+ private readonly onProgress: ((progress: number) => void);
265
+
266
+ private readonly onError: ((err: string | {
267
+ message: string;
268
+ operation: string;
269
+ type: string;
270
+ id: string;
271
+ }) => void);
272
+
273
+ private loaded: boolean = false;
274
+
275
+ private loadTimer: ReturnType<typeof setTimeout> | null = null;
276
+
277
+ private loadCounter: number = 0;
278
+
279
+ private admin5only: boolean;
280
+
281
+ private ignoreState: string = '';
282
+
283
+ private readonly simStates: Record<string, ioBroker.State> = {};
284
+
285
+ private autoSubscribes: string[];
286
+
287
+ private readonly autoSubscribeLog: boolean;
288
+
289
+ private subscribed: boolean | undefined;
290
+
291
+ public isSecure: boolean | undefined;
292
+
293
+ private onCmdStdoutHandler: ((id: string, text: string) => void) | undefined;
294
+
295
+ private onCmdStderrHandler: ((id: string, text: string) => void) | undefined;
296
+
297
+ private onCmdExitHandler: ((id: string, exitCode: number) => void) | undefined;
298
+
299
+ public systemConfig: ioBroker.SystemConfigObject | null = null;
300
+
301
+ constructor(props: ConnectionProps) {
302
+ props = props || { protocol: window.location.protocol, host: window.location.hostname };
303
+ this.props = props;
304
+
305
+ this.autoSubscribes = this.props.autoSubscribes || [];
306
+ this.autoSubscribeLog = this.props.autoSubscribeLog || false;
307
+
308
+ this.props.protocol = this.props.protocol || window.location.protocol;
309
+ this.props.host = this.props.host || window.location.hostname;
310
+ this.props.port = this.props.port || (window.location.port === '3000' ? (Connection.isWeb() ? 8082 : 8081) : window.location.port);
311
+ this.props.ioTimeout = Math.max(this.props.ioTimeout || 20000, 20000);
312
+ this.props.cmdTimeout = Math.max(this.props.cmdTimeout || 5000, 5000);
313
+ this._instanceSubscriptions = {};
314
+
315
+ // breaking change. Do not load all objects by default is true
316
+ this.doNotLoadAllObjects = this.props.doNotLoadAllObjects === undefined ? true : this.props.doNotLoadAllObjects;
317
+ this.doNotLoadACL = this.props.doNotLoadACL === undefined ? true : this.props.doNotLoadACL;
318
+
319
+ this.states = {};
320
+ this._waitForFirstConnection = new Promise(resolve => {
321
+ this._waitForFirstConnectionResolve = resolve;
322
+ });
323
+ this.onProgress = this.props.onProgress || (() => {});
324
+ this.onError = this.props.onError || ((err: string | {
325
+ message: string;
326
+ operation: string;
327
+ type: string;
328
+ id: string;
329
+ }) => console.error(err));
330
+ this.admin5only = this.props.admin5only || false;
331
+
332
+ this.startSocket();
333
+ }
334
+
335
+ /**
336
+ * Checks if this connection is running in a web adapter and not in an admin.
337
+ * @returns {boolean} True if running in a web adapter or in a socketio adapter.
338
+ */
339
+ static isWeb(): boolean {
340
+ const adapterName: string | undefined = window.adapterName;
341
+ return adapterName === 'material' ||
342
+ adapterName === 'vis' ||
343
+ adapterName?.startsWith('vis-') ||
344
+ adapterName === 'echarts-show' ||
345
+ window.socketUrl !== undefined;
346
+ }
347
+
348
+ /**
349
+ * Starts the socket.io connection.
350
+ */
351
+ startSocket(): void {
352
+ // if socket io is not yet loaded
353
+ if (typeof window.io === 'undefined') {
354
+ // if in index.html the onLoad function not defined
355
+ if (typeof window.registerSocketOnLoad !== 'function') {
356
+ // poll if loaded
357
+ this.scriptLoadCounter = this.scriptLoadCounter || 0;
358
+ this.scriptLoadCounter++;
359
+
360
+ if (this.scriptLoadCounter < 30) {
361
+ // wait till the script loaded
362
+ setTimeout(() => this.startSocket(), 100);
363
+ return;
364
+ }
365
+ window.alert('Cannot load socket.io.js!');
366
+ } else {
367
+ // register on load
368
+ window.registerSocketOnLoad(() => this.startSocket());
369
+ }
370
+ return;
371
+ }
372
+ if (this._socket) {
373
+ // socket was initialized, do not repeat
374
+ return;
375
+ }
376
+
377
+ let host = this.props.host;
378
+ let port = this.props.port;
379
+ let protocol = this.props.protocol.replace(':', '');
380
+ let path = window.location.pathname;
381
+
382
+ if (
383
+ window.location.hostname === 'iobroker.net' ||
384
+ window.location.hostname === 'iobroker.pro'
385
+ ) {
386
+ path = '';
387
+ } else {
388
+ // if web adapter, socket io could be on another port or even host
389
+ if (window.socketUrl) {
390
+ const parsed = new URL(window.socketUrl as string);
391
+ host = parsed.hostname;
392
+ port = parsed.port;
393
+ protocol = parsed.protocol.replace(':', '');
394
+ }
395
+ // get a current path
396
+ const pos = path.lastIndexOf('/');
397
+ if (pos !== -1) {
398
+ path = path.substring(0, pos + 1);
399
+ }
400
+
401
+ if (Connection.isWeb()) {
402
+ // remove one level, like echarts, vis, .... We have here: '/echarts/'
403
+ const parts = path.split('/');
404
+ if (parts.length > 2) {
405
+ parts.pop();
406
+ // if it is a version, like in material, so remove it too
407
+ if (parts[parts.length - 1].match(/\d+\.\d+\.\d+/)) {
408
+ parts.pop();
409
+ }
410
+ parts.pop();
411
+ path = parts.join('/');
412
+ if (!path.endsWith('/')) {
413
+ path += '/';
414
+ }
415
+ }
416
+ }
417
+ }
418
+
419
+ const url = port ? `${protocol}://${host}:${port}${path}` : `${protocol}://${host}${path}`;
420
+
421
+ this._socket = window.io.connect(
422
+ url,
423
+ {
424
+ path: path.endsWith('/') ? `${path}socket.io` : `${path}/socket.io`,
425
+ query: 'ws=true',
426
+ name: this.props.name,
427
+ timeout: this.props.ioTimeout,
428
+ uuid: this.props.uuid,
429
+ },
430
+ );
431
+
432
+ this._socket.on('connect', (noTimeout: boolean | undefined) => {
433
+ // If the user is not admin, it takes some time to install the handlers, because all rights must be checked
434
+ if (noTimeout !== true) {
435
+ setTimeout(() =>
436
+ this.getVersion()
437
+ .then(info => {
438
+ const [major, minor, patch] = info.version.split('.');
439
+ const v = parseInt(major, 10) * 10000 + parseInt(minor, 10) * 100 + parseInt(patch, 10);
440
+ if (v < 40102) {
441
+ this._authTimer = null;
442
+ // possible this is an old version of admin
443
+ this.onPreConnect(false, false);
444
+ } else {
445
+ this._socket.emit('authenticate', (isOk: boolean, isSecure: boolean) => this.onPreConnect(isOk, isSecure));
446
+ }
447
+ }), 500);
448
+ } else {
449
+ // iobroker websocket waits, till all handlers are installed
450
+ this._socket.emit('authenticate', (isOk: boolean, isSecure: boolean) => this.onPreConnect(isOk, isSecure));
451
+ }
452
+ });
453
+
454
+ this._socket.on('reconnect', () => {
455
+ this.onProgress(PROGRESS.READY);
456
+ this.connected = true;
457
+
458
+ if (this.waitForRestart) {
459
+ window.location.reload();
460
+ } else {
461
+ this._subscribe(true);
462
+ this.onConnectionHandlers.forEach(cb => cb(true));
463
+ }
464
+ });
465
+
466
+ this._socket.on('disconnect', () => {
467
+ this.connected = false;
468
+ this.subscribed = false;
469
+ this.onProgress(PROGRESS.CONNECTING);
470
+ this.onConnectionHandlers.forEach(cb => cb(false));
471
+ });
472
+
473
+ this._socket.on('reauthenticate', () =>
474
+ Connection.authenticate());
475
+
476
+ this._socket.on('log', message => {
477
+ this.props.onLog && this.props.onLog(message);
478
+ this.onLogHandlers.forEach(cb => cb(message));
479
+ });
480
+
481
+ this._socket.on('error', (err: string | null) => {
482
+ let _err = err || '';
483
+ if (typeof _err.toString !== 'function') {
484
+ _err = JSON.stringify(_err);
485
+ console.error(`Received strange error: ${_err}`);
486
+ }
487
+ _err = _err.toString();
488
+ if (_err.includes('User not authorized')) {
489
+ Connection.authenticate();
490
+ } else {
491
+ window.alert(`Socket Error: ${err}`);
492
+ }
493
+ });
494
+
495
+ this._socket.on('connect_error', (err: string) =>
496
+ console.error(`Connect error: ${err}`));
497
+
498
+ this._socket.on('permissionError', (err: { operation: string; type: string; id?: string }) =>
499
+ this.onError({
500
+ message: 'no permission',
501
+ operation: err.operation,
502
+ type: err.type,
503
+ id: err.id || '',
504
+ }));
505
+
506
+ this._socket.on('objectChange', (id: string, obj: ioBroker.Object | null) =>
507
+ setTimeout(() => this.objectChange(id, obj), 0));
508
+
509
+ this._socket.on('stateChange', (id: string, state) =>
510
+ setTimeout(() => this.stateChange(id, state), 0));
511
+
512
+ this._socket.on('im', (messageType: string, from: string, data) =>
513
+ setTimeout(() => this.instanceMessage(messageType, from, data), 0));
514
+
515
+ this._socket.on('fileChange', (id: string, fileName: string, size: number | null) =>
516
+ setTimeout(() => this.fileChange(id, fileName, size), 0));
517
+
518
+ this._socket.on('cmdStdout', (id: string, text: string) =>
519
+ this.onCmdStdoutHandler && this.onCmdStdoutHandler(id, text));
520
+
521
+ this._socket.on('cmdStderr', (id: string, text: string) =>
522
+ this.onCmdStderrHandler && this.onCmdStderrHandler(id, text));
523
+
524
+ this._socket.on('cmdExit', (id: string, exitCode: number) =>
525
+ this.onCmdExitHandler && this.onCmdExitHandler(id, exitCode));
526
+ }
527
+
528
+ /**
529
+ * Called internally.
530
+ */
531
+ private onPreConnect(isOk: boolean, isSecure: boolean): void {
532
+ if (this._authTimer) {
533
+ clearTimeout(this._authTimer);
534
+ this._authTimer = null;
535
+ }
536
+
537
+ this.connected = true;
538
+ this.isSecure = isSecure;
539
+
540
+ if (this.waitForRestart) {
541
+ window.location.reload();
542
+ } else {
543
+ if (this.firstConnect) {
544
+ // retry strategy
545
+ this.loadTimer = setTimeout(() => {
546
+ this.loadTimer = null;
547
+ this.loadCounter++;
548
+ if (this.loadCounter < 10) {
549
+ this.onConnect();
550
+ }
551
+ }, 1000);
552
+
553
+ if (!this.loaded) {
554
+ this.onConnect();
555
+ }
556
+ } else {
557
+ this.onProgress(PROGRESS.READY);
558
+ }
559
+
560
+ this._subscribe(true);
561
+ this.onConnectionHandlers.forEach(cb => cb(true));
562
+ }
563
+
564
+ if (this._waitForFirstConnectionResolve) {
565
+ this._waitForFirstConnectionResolve();
566
+ this._waitForFirstConnectionResolve = null;
567
+ }
568
+ }
569
+
570
+ /**
571
+ * Checks if running in ioBroker cloud
572
+ */
573
+ static isCloud(): boolean {
574
+ if (
575
+ window.location.hostname.includes('amazonaws.com') ||
576
+ window.location.hostname.includes('iobroker.in')
577
+ ) {
578
+ return true;
579
+ }
580
+ if (typeof window.socketUrl === 'undefined') {
581
+ return false;
582
+ }
583
+ return (
584
+ window.socketUrl.includes('iobroker.in') ||
585
+ window.socketUrl.includes('amazonaws')
586
+ );
587
+ }
588
+
589
+ /**
590
+ * Checks if the socket is connected.
591
+ */
592
+ isConnected(): boolean {
593
+ return this.connected;
594
+ }
595
+
596
+ /**
597
+ * Checks if the socket is connected.
598
+ * Promise resolves if once connected.
599
+ */
600
+ waitForFirstConnection(): Promise<void> {
601
+ return this._waitForFirstConnection;
602
+ }
603
+
604
+ /**
605
+ * Called internally.
606
+ */
607
+ private async _getUserPermissions(): Promise<Record<string, any> | null> {
608
+ if (this.doNotLoadACL) {
609
+ return null;
610
+ }
611
+ return new Promise((resolve, reject) => {
612
+ this._socket.emit('getUserPermissions', (err: string | null, acl: Record<string, any> | null) =>
613
+ (err ? reject(err) : resolve(acl)));
614
+ });
615
+ }
616
+
617
+ /**
618
+ * Called internally.
619
+ */
620
+ private async onConnect() {
621
+ let acl: Record<string, any> | null | undefined;
622
+ try {
623
+ acl = await this._getUserPermissions();
624
+ } catch (err) {
625
+ this.onError(`Cannot read user permissions: ${err}`);
626
+ return;
627
+ }
628
+ if (!this.doNotLoadACL) {
629
+ if (this.loaded) {
630
+ return;
631
+ }
632
+ this.loaded = true;
633
+ this.loadTimer && clearTimeout(this.loadTimer);
634
+ this.loadTimer = null;
635
+
636
+ this.onProgress(PROGRESS.CONNECTED);
637
+ this.firstConnect = false;
638
+
639
+ this.acl = acl;
640
+ }
641
+
642
+ // Read system configuration
643
+ let data: ioBroker.SystemConfigObject | null;
644
+ try {
645
+ if (this.admin5only && !window.vendorPrefix) {
646
+ data = await this.getCompactSystemConfig();
647
+ } else {
648
+ data = await this.getSystemConfig();
649
+ }
650
+ if (this.doNotLoadACL) {
651
+ if (this.loaded) {
652
+ return;
653
+ }
654
+ this.loaded = true;
655
+ this.loadTimer && clearTimeout(this.loadTimer);
656
+ this.loadTimer = null;
657
+
658
+ this.onProgress(PROGRESS.CONNECTED);
659
+ this.firstConnect = false;
660
+ }
661
+
662
+ this.systemConfig = data;
663
+ if (this.systemConfig && this.systemConfig.common) {
664
+ this.systemLang = this.systemConfig.common.language;
665
+ } else {
666
+ // @ts-expect-error userLanguage is not standard
667
+ this.systemLang = window.navigator.userLanguage || window.navigator.language;
668
+
669
+ if (/^(en|de|ru|pt|nl|fr|it|es|pl|uk)-?/.test(this.systemLang)) {
670
+ this.systemLang = this.systemLang.substr(0, 2) as ioBroker.Languages;
671
+ } else if (
672
+ !/^(en|de|ru|pt|nl|fr|it|es|pl|uk|zh-cn)$/.test(this.systemLang)
673
+ ) {
674
+ this.systemLang = 'en';
675
+ }
676
+ }
677
+
678
+ this.props.onLanguage && this.props.onLanguage(this.systemLang);
679
+
680
+ if (!this.doNotLoadAllObjects) {
681
+ await this.getObjects();
682
+ this.onProgress(PROGRESS.READY);
683
+ this.props.onReady && this.objects && this.props.onReady(this.objects);
684
+ } else {
685
+ this.objects = this.admin5only ? {} : { 'system.config': data };
686
+ this.onProgress(PROGRESS.READY);
687
+ this.props.onReady && this.props.onReady(this.objects);
688
+ }
689
+ } catch (e) {
690
+ this.onError(`Cannot read system config: ${e}`);
691
+ }
692
+ }
693
+
694
+ /**
695
+ * Called internally.
696
+ * @private
697
+ */
698
+ static authenticate() {
699
+ if (window.location.search.includes('&href=')) {
700
+ window.location.href = `${window.location.protocol}//${window.location.host}${window.location.pathname}${window.location.search}${window.location.hash}`;
701
+ } else {
702
+ window.location.href = `${window.location.protocol}//${window.location.host}${window.location.pathname}?login&href=${window.location.search}${window.location.hash}`;
703
+ }
704
+ }
705
+
706
+ /**
707
+ * Subscribe to changes of the given state.
708
+ */
709
+ subscribeState(
710
+ /** The ioBroker state ID. */
711
+ id: string | string[],
712
+ /** Set to true if the given state is binary and requires Base64 decoding. */
713
+ binary: boolean | ioBroker.StateChangeHandler | BinaryStateChangeHandler,
714
+ /** The callback. */
715
+ cb?: ioBroker.StateChangeHandler | BinaryStateChangeHandler,
716
+ ): void {
717
+ if (typeof binary === 'function') {
718
+ cb = binary;
719
+ binary = false;
720
+ }
721
+
722
+ let ids: string[];
723
+ if (!Array.isArray(id)) {
724
+ ids = [id];
725
+ } else {
726
+ ids = id;
727
+ }
728
+ if (!cb) {
729
+ console.error('No callback found for subscribeState');
730
+ return;
731
+ }
732
+ const toSubscribe = [];
733
+ for (let i = 0; i < ids.length; i++) {
734
+ const _id = ids[i];
735
+ if (!this.statesSubscribes[_id]) {
736
+ let reg = _id
737
+ .replace(/\./g, '\\.')
738
+ .replace(/\*/g, '.*')
739
+ .replace(/\(/g, '\\(')
740
+ .replace(/\)/g, '\\)')
741
+ .replace(/\+/g, '\\+')
742
+ .replace(/\[/g, '\\[');
743
+
744
+ if (!reg.includes('*')) {
745
+ reg += '$';
746
+ }
747
+ this.statesSubscribes[_id] = { reg: new RegExp(reg), cbs: [cb] };
748
+ if (_id !== this.ignoreState) {
749
+ toSubscribe.push(_id);
750
+ }
751
+ } else {
752
+ !this.statesSubscribes[_id].cbs.includes(cb) &&
753
+ this.statesSubscribes[_id].cbs.push(cb);
754
+ }
755
+ }
756
+
757
+ if (!this.connected) {
758
+ return;
759
+ }
760
+
761
+ if (toSubscribe.length) {
762
+ // no answer from server required
763
+ this._socket.emit('subscribe', toSubscribe);
764
+ }
765
+
766
+ if (binary) {
767
+ for (let i = 0; i < ids.length; i++) {
768
+ this.getBinaryState(ids[i])
769
+ .then((base64: string) => cb && (cb as BinaryStateChangeHandler)(ids[i], base64))
770
+ .catch(e => console.error(`Cannot getBinaryState "${ids[i]}": ${JSON.stringify(e)}`));
771
+ }
772
+ } else {
773
+ this._socket.emit(Connection.isWeb() ? 'getStates' : 'getForeignStates', id, (err: string | null, states: Record<string, ioBroker.State>) => {
774
+ err && console.error(`Cannot getForeignStates "${id}": ${JSON.stringify(err)}`);
775
+ states && Object.keys(states).forEach(_id => (cb as ioBroker.StateChangeHandler)(_id, states[_id]));
776
+ });
777
+ }
778
+ }
779
+
780
+ /**
781
+ * Subscribe to changes of the given state.
782
+ */
783
+ subscribeStateAsync(
784
+ /** The ioBroker state ID or array of states */
785
+ id: string | string[],
786
+ /** The callback. */
787
+ cb: ioBroker.StateChangeHandler,
788
+ ): Promise<void> {
789
+ let ids: string[];
790
+ if (!Array.isArray(id)) {
791
+ ids = [id];
792
+ } else {
793
+ ids = id;
794
+ }
795
+ const toSubscribe = [];
796
+ for (let i = 0; i < ids.length; i++) {
797
+ const _id = ids[i];
798
+ if (!this.statesSubscribes[_id]) {
799
+ let reg = _id
800
+ .replace(/\./g, '\\.')
801
+ .replace(/\*/g, '.*')
802
+ .replace(/\(/g, '\\(')
803
+ .replace(/\)/g, '\\)')
804
+ .replace(/\+/g, '\\+')
805
+ .replace(/\[/g, '\\[');
806
+
807
+ if (!reg.includes('*')) {
808
+ reg += '$';
809
+ }
810
+ this.statesSubscribes[_id] = { reg: new RegExp(reg), cbs: [] };
811
+ this.statesSubscribes[_id].cbs.push(cb);
812
+ if (_id !== this.ignoreState) {
813
+ // no answer from server required
814
+ toSubscribe.push(_id);
815
+ }
816
+ } else {
817
+ !this.statesSubscribes[_id].cbs.includes(cb) && this.statesSubscribes[_id].cbs.push(cb);
818
+ }
819
+ }
820
+
821
+ if (toSubscribe.length && this.connected) {
822
+ // no answer from server required
823
+ this._socket.emit('subscribe', toSubscribe);
824
+ }
825
+
826
+ return new Promise((resolve, reject) => {
827
+ if (typeof cb === 'function' && this.connected) {
828
+ this._socket.emit(Connection.isWeb() ? 'getStates' : 'getForeignStates', id, (err: string | null, states: Record<string, ioBroker.State>) => {
829
+ err && console.error(`Cannot getForeignStates "${id}": ${JSON.stringify(err)}`);
830
+ states && Object.keys(states).forEach(_id => cb(_id, states[_id]));
831
+ states ? resolve() : reject(new Error(`Cannot getForeignStates "${id}": ${JSON.stringify(err)}`));
832
+ });
833
+ } else {
834
+ this.connected ? reject(new Error('callback is not a function')) : reject(new Error('not connected'));
835
+ }
836
+ });
837
+ }
838
+
839
+ /**
840
+ * Unsubscribes all or the given callback from changes of the given state.
841
+ */
842
+ unsubscribeState(
843
+ /** The ioBroker state ID or array of states */
844
+ id: string | string[],
845
+ /** The callback. */
846
+ cb?: ioBroker.StateChangeHandler | BinaryStateChangeHandler,
847
+ ): void {
848
+ let ids: string[];
849
+ if (!Array.isArray(id)) {
850
+ ids = [id];
851
+ } else {
852
+ ids = id;
853
+ }
854
+ const toUnsubscribe = [];
855
+ for (let i = 0; i < ids.length; i++) {
856
+ const _id = ids[i];
857
+
858
+ if (this.statesSubscribes[_id]) {
859
+ if (cb) {
860
+ const pos = this.statesSubscribes[_id].cbs.indexOf(cb);
861
+ pos !== -1 && this.statesSubscribes[_id].cbs.splice(pos, 1);
862
+ } else {
863
+ this.statesSubscribes[_id].cbs = [];
864
+ }
865
+
866
+ if (!this.statesSubscribes[_id].cbs || !this.statesSubscribes[_id].cbs.length) {
867
+ delete this.statesSubscribes[_id];
868
+ if (_id !== this.ignoreState) {
869
+ toUnsubscribe.push(_id);
870
+ }
871
+ }
872
+ }
873
+ }
874
+
875
+ if (toUnsubscribe.length && this.connected) {
876
+ // no answer from server required
877
+ this._socket.emit('unsubscribe', toUnsubscribe);
878
+ }
879
+ }
880
+
881
+ /**
882
+ * Subscribe to changes of the given object.
883
+ */
884
+ subscribeObject(
885
+ /** The ioBroker object ID. */
886
+ id: string | string[],
887
+ /** The callback. */
888
+ cb: ioBroker.ObjectChangeHandler,
889
+ ): Promise<void> {
890
+ let ids: string[];
891
+ if (!Array.isArray(id)) {
892
+ ids = [id];
893
+ } else {
894
+ ids = id;
895
+ }
896
+ const toSubscribe = [];
897
+ for (let i = 0; i < ids.length; i++) {
898
+ const _id = ids[i];
899
+ if (!this.objectsSubscribes[_id]) {
900
+ let reg = _id.replace(/\./g, '\\.').replace(/\*/g, '.*');
901
+ if (!reg.includes('*')) {
902
+ reg += '$';
903
+ }
904
+ this.objectsSubscribes[_id] = { reg: new RegExp(reg), cbs: [cb] };
905
+ toSubscribe.push(_id);
906
+ } else {
907
+ !this.objectsSubscribes[_id].cbs.includes(cb) &&
908
+ this.objectsSubscribes[_id].cbs.push(cb);
909
+ }
910
+ }
911
+ if (this.connected && toSubscribe.length) {
912
+ this._socket.emit('subscribeObjects', toSubscribe);
913
+ }
914
+
915
+ return Promise.resolve();
916
+ }
917
+
918
+ /**
919
+ * Unsubscribes all or the given callback from changes of the given object.
920
+ */
921
+ unsubscribeObject(
922
+ /** The ioBroker object ID. */
923
+ id: string | string[],
924
+ /** The callback. */
925
+ cb?: ioBroker.ObjectChangeHandler,
926
+ ): Promise<void> {
927
+ let ids: string[];
928
+ if (!Array.isArray(id)) {
929
+ ids = [id];
930
+ } else {
931
+ ids = id;
932
+ }
933
+ const toUnsubscribe = [];
934
+ for (let i = 0; i < ids.length; i++) {
935
+ const _id = ids[i];
936
+ if (this.objectsSubscribes[_id]) {
937
+ if (cb) {
938
+ const pos = this.objectsSubscribes[_id].cbs.indexOf(cb);
939
+ pos !== -1 && this.objectsSubscribes[_id].cbs.splice(pos, 1);
940
+ } else {
941
+ this.objectsSubscribes[_id].cbs = [];
942
+ }
943
+
944
+ if (this.connected && (!this.objectsSubscribes[_id].cbs || !this.objectsSubscribes[_id].cbs.length)) {
945
+ delete this.objectsSubscribes[_id];
946
+ toUnsubscribe.push(_id);
947
+ }
948
+ }
949
+ }
950
+
951
+ if (this.connected && toUnsubscribe.length) {
952
+ this._socket.emit('unsubscribeObjects', toUnsubscribe);
953
+ }
954
+
955
+ return Promise.resolve();
956
+ }
957
+
958
+ /**
959
+ * Called internally.
960
+ */
961
+ private fileChange(
962
+ id: string,
963
+ fileName: string,
964
+ size: number | null,
965
+ ) {
966
+ for (const sub of Object.values(this.filesSubscribes)) {
967
+ if (sub.regId.test(id) && sub.regFilePattern.test(fileName)) {
968
+ for (const cb of sub.cbs) {
969
+ try {
970
+ cb(id, fileName, size);
971
+ } catch (e) {
972
+ console.error(
973
+ `Error by callback of fileChange: ${e}`,
974
+ );
975
+ }
976
+ }
977
+ }
978
+ }
979
+ }
980
+
981
+ /**
982
+ * Subscribe to changes of the files.
983
+ * @param {string} id The ioBroker state ID for meta-object. Could be a pattern
984
+ * @param {string} filePattern Pattern or file name, like 'main/*' or 'main/visViews.json`
985
+ * @param {function} cb The callback.
986
+ */
987
+ async subscribeFiles(
988
+ /** The ioBroker state ID for meta-object. Could be a pattern */
989
+ id: string,
990
+ /** Pattern or file name, like 'main/*' or 'main/visViews.json` */
991
+ filePattern: string | string[],
992
+ /** The callback. */
993
+ cb: ioBroker.FileChangeHandler,
994
+ ) {
995
+ if (typeof cb !== 'function') {
996
+ throw new Error('The state change handler must be a function!');
997
+ }
998
+ let filePatterns;
999
+ if (Array.isArray(filePattern)) {
1000
+ filePatterns = filePattern;
1001
+ } else {
1002
+ filePatterns = [filePattern];
1003
+ }
1004
+ const toSubscribe = [];
1005
+ for (let f = 0; f < filePatterns.length; f++) {
1006
+ const pattern = filePatterns[f];
1007
+ const key = `${id}$%$${pattern}`;
1008
+
1009
+ if (!this.filesSubscribes[key]) {
1010
+ this.filesSubscribes[key] = {
1011
+ regId: new RegExp(pattern2RegEx(id)),
1012
+ regFilePattern: new RegExp(pattern2RegEx(pattern)),
1013
+ cbs: [cb],
1014
+ };
1015
+
1016
+ toSubscribe.push(pattern);
1017
+ } else {
1018
+ !this.filesSubscribes[key].cbs.includes(cb) &&
1019
+ this.filesSubscribes[key].cbs.push(cb);
1020
+ }
1021
+ }
1022
+ if (this.connected && toSubscribe.length) {
1023
+ this._socket.emit('subscribeFiles', id, toSubscribe);
1024
+ }
1025
+ }
1026
+
1027
+ /**
1028
+ * Unsubscribes the given callback from changes of files.
1029
+ * @param {string} id The ioBroker state ID.
1030
+ * @param {string} filePattern Pattern or file name, like 'main/*' or 'main/visViews.json`
1031
+ * @param {function} cb The callback.
1032
+ */
1033
+ unsubscribeFiles(
1034
+ id: string,
1035
+ filePattern: string,
1036
+ cb?: ioBroker.FileChangeHandler,
1037
+ ): void {
1038
+ let filePatterns: string[];
1039
+ if (Array.isArray(filePattern)) {
1040
+ filePatterns = filePattern;
1041
+ } else {
1042
+ filePatterns = [filePattern];
1043
+ }
1044
+ const toUnsubscribe = [];
1045
+ for (let f = 0; f < filePatterns.length; f++) {
1046
+ const pattern = filePatterns[f];
1047
+ const key = `${id}$%$${pattern}`;
1048
+ if (this.filesSubscribes[key]) {
1049
+ const sub = this.filesSubscribes[key];
1050
+ if (cb) {
1051
+ const pos = sub.cbs.indexOf(cb);
1052
+ pos !== -1 && sub.cbs.splice(pos, 1);
1053
+ } else {
1054
+ sub.cbs = [];
1055
+ }
1056
+
1057
+ if (!sub.cbs || !sub.cbs.length) {
1058
+ delete this.filesSubscribes[key];
1059
+ this.connected &&
1060
+ toUnsubscribe.push(pattern);
1061
+ }
1062
+ }
1063
+ }
1064
+
1065
+ if (this.connected && toUnsubscribe.length) {
1066
+ this._socket.emit('unsubscribeFiles', id, toUnsubscribe);
1067
+ }
1068
+ }
1069
+
1070
+ /**
1071
+ * Called internally.
1072
+ */
1073
+ private objectChange(
1074
+ id: string,
1075
+ obj: ioBroker.Object | null,
1076
+ ) {
1077
+ // update main.objects cache
1078
+ if (!this.objects) {
1079
+ return;
1080
+ }
1081
+
1082
+ let oldObj: Partial<ioBroker.Object> | null;
1083
+
1084
+ let changed = false;
1085
+ if (obj) {
1086
+ if (this.objects[id]) {
1087
+ // @ts-expect-error fix later
1088
+ oldObj = { _id: id, type: this.objects[id].type };
1089
+ }
1090
+
1091
+ if (!this.objects[id] || JSON.stringify(this.objects[id]) !== JSON.stringify(obj)) {
1092
+ this.objects[id] = obj;
1093
+ changed = true;
1094
+ }
1095
+ } else if (this.objects[id]) {
1096
+ // @ts-expect-error fix later
1097
+ oldObj = { _id: id, type: this.objects[id].type };
1098
+ delete this.objects[id];
1099
+ changed = true;
1100
+ }
1101
+
1102
+ Object.keys(this.objectsSubscribes).forEach(_id => {
1103
+ if (_id === id || this.objectsSubscribes[_id].reg.test(id)) {
1104
+ this.objectsSubscribes[_id].cbs.forEach(cb => {
1105
+ try {
1106
+ cb(id, obj, oldObj as ioBroker.Object);
1107
+ } catch (e) {
1108
+ console.error(`Error by callback of objectChange: ${e}`);
1109
+ }
1110
+ });
1111
+ }
1112
+ });
1113
+
1114
+ if (changed && this.props.onObjectChange) {
1115
+ this.props.onObjectChange(id, obj);
1116
+ }
1117
+ }
1118
+
1119
+ /**
1120
+ * Called internally.
1121
+ */
1122
+ private stateChange(
1123
+ id: string,
1124
+ state: ioBroker.State | null,
1125
+ ): void {
1126
+ for (const task in this.statesSubscribes) {
1127
+ if (Object.prototype.hasOwnProperty.call(this.statesSubscribes, task) && this.statesSubscribes[task].reg.test(id)) {
1128
+ this.statesSubscribes[task].cbs.forEach(cb => {
1129
+ try {
1130
+ (cb as ioBroker.StateChangeHandler)(id, state);
1131
+ } catch (e) {
1132
+ console.error(`Error by callback of stateChange: ${e}`);
1133
+ }
1134
+ });
1135
+ }
1136
+ }
1137
+ }
1138
+
1139
+ /**
1140
+ * Called internally.
1141
+ * @param {string} messageType
1142
+ * @param {string} sourceInstance
1143
+ * @param {object} data
1144
+ */
1145
+ instanceMessage(messageType: string, sourceInstance: string, data: Record<string, any>) {
1146
+ if (this._instanceSubscriptions[sourceInstance]) {
1147
+ this._instanceSubscriptions[sourceInstance].forEach(sub => {
1148
+ if (sub.messageType === messageType) {
1149
+ sub.callback(data, sourceInstance, messageType);
1150
+ }
1151
+ });
1152
+ }
1153
+ }
1154
+
1155
+ /**
1156
+ * Gets all states.
1157
+ */
1158
+ getStates(
1159
+ /** The pattern to filter states. */
1160
+ pattern?: string | boolean,
1161
+ /** don't call onProgress() when done */
1162
+ disableProgressUpdate?: boolean,
1163
+ ): Promise<Record<string, ioBroker.State>> {
1164
+ if (!this.connected) {
1165
+ return Promise.reject(NOT_CONNECTED);
1166
+ }
1167
+
1168
+ if (typeof pattern === 'boolean') {
1169
+ disableProgressUpdate = pattern;
1170
+ pattern = undefined;
1171
+ }
1172
+
1173
+ return new Promise((resolve, reject) => {
1174
+ this._socket.emit('getStates', pattern, (err: string | null, res: Record<string, ioBroker.State>) => {
1175
+ this.states = res;
1176
+ !disableProgressUpdate && this.onProgress(PROGRESS.STATES_LOADED);
1177
+ err ? reject(err) : resolve(this.states);
1178
+ });
1179
+ });
1180
+ }
1181
+
1182
+ /**
1183
+ * Gets the given state.
1184
+ */
1185
+ getState(
1186
+ /** The state ID. */
1187
+ id: string,
1188
+ ): Promise<ioBroker.State | null> {
1189
+ if (!this.connected) {
1190
+ return Promise.reject(NOT_CONNECTED);
1191
+ }
1192
+ if (id && id === this.ignoreState) {
1193
+ return Promise.resolve(this.simStates[id] || { val: null, ack: true } as ioBroker.State);
1194
+ }
1195
+ return new Promise((resolve, reject) => {
1196
+ this._socket.emit('getState', id, (err: string | null, state: ioBroker.State) =>
1197
+ (err ? reject(err) : resolve(state)));
1198
+ });
1199
+ }
1200
+
1201
+ /**
1202
+ * @deprecated since js-controller 5.0. Use files instead.
1203
+ * Get the given binary state.
1204
+ */
1205
+ getBinaryState(
1206
+ /** The state ID. */
1207
+ id: string,
1208
+ ): Promise<string> {
1209
+ if (!this.connected) {
1210
+ return Promise.reject(NOT_CONNECTED);
1211
+ }
1212
+
1213
+ // the data will come in base64
1214
+ return new Promise((resolve, reject) => {
1215
+ this._socket.emit('getBinaryState', id, (err: string | null, base64: string) =>
1216
+ (err ? reject(err) : resolve(base64)));
1217
+ });
1218
+ }
1219
+
1220
+ /**
1221
+ * @deprecated since js-controller 5.0. Use files instead.
1222
+ * Set the given binary state.
1223
+ */
1224
+ setBinaryState(
1225
+ /** The state ID. */
1226
+ id: string,
1227
+ /** The Base64 encoded binary data. */
1228
+ base64: string,
1229
+ ): Promise<void> {
1230
+ if (!this.connected) {
1231
+ return Promise.reject(NOT_CONNECTED);
1232
+ }
1233
+
1234
+ // the data will come in base64
1235
+ return new Promise((resolve, reject) => {
1236
+ this._socket.emit('setBinaryState', id, base64, (err: string | null) =>
1237
+ ((err ? reject(err) : resolve())));
1238
+ });
1239
+ }
1240
+
1241
+ /**
1242
+ * Sets the given state value.
1243
+ */
1244
+ setState(
1245
+ /** The state ID. */
1246
+ id: string,
1247
+ /** The state value. */
1248
+ val: string | number | boolean | ioBroker.SettableState | null,
1249
+ /** The acknowledgment flag. */
1250
+ ack?: boolean,
1251
+ ): Promise<void> {
1252
+ if (!this.connected) {
1253
+ return Promise.reject(NOT_CONNECTED);
1254
+ }
1255
+
1256
+ // extra handling for "nothing_selected" state for vis
1257
+ if (id && id === this.ignoreState) {
1258
+ let state: ioBroker.State;
1259
+ if (typeof ack === 'boolean') {
1260
+ state = val as ioBroker.State;
1261
+ } else if (typeof val === 'object' && (val as ioBroker.State).val !== undefined) {
1262
+ state = val as ioBroker.State;
1263
+ } else {
1264
+ state = {
1265
+ val: val as string | number | boolean | null,
1266
+ ack: false,
1267
+ ts: Date.now(),
1268
+ lc: Date.now(),
1269
+ from: 'system.adapter.vis.0',
1270
+ };
1271
+ }
1272
+
1273
+ this.simStates[id] = state;
1274
+
1275
+ // inform subscribers about changes
1276
+ if (this.statesSubscribes[id]) {
1277
+ for (const cb of this.statesSubscribes[id].cbs) {
1278
+ try {
1279
+ (cb as ioBroker.StateChangeHandler)(id, state as ioBroker.State);
1280
+ } catch (e) {
1281
+ console.error(
1282
+ `Error by callback of stateChanged: ${e}`,
1283
+ );
1284
+ }
1285
+ }
1286
+ }
1287
+
1288
+ return Promise.resolve();
1289
+ }
1290
+
1291
+ return new Promise((resolve, reject) => {
1292
+ this._socket.emit('setState', id, val, (err: string | null) =>
1293
+ (err ? reject(err) : resolve()));
1294
+ });
1295
+ }
1296
+
1297
+ /**
1298
+ * Gets all objects.
1299
+ */
1300
+ getObjects(
1301
+ /** Set to true to retrieve all objects from the server (instead of using the local cache). */
1302
+ update?: boolean,
1303
+ /** don't call onProgress() when done */
1304
+ disableProgressUpdate?: boolean,
1305
+ ): Promise<Record<string, ioBroker.Object>> {
1306
+ if (!this.connected) {
1307
+ return Promise.reject(NOT_CONNECTED);
1308
+ }
1309
+ return new Promise((resolve, reject) => {
1310
+ if (!update && this.objects) {
1311
+ resolve(this.objects);
1312
+ } else {
1313
+ this._socket.emit(Connection.isWeb() ? 'getObjects' : 'getAllObjects', (err: string | null, res: Record<string, ioBroker.Object>) => {
1314
+ this.objects = res;
1315
+ disableProgressUpdate && this.onProgress(PROGRESS.OBJECTS_LOADED);
1316
+ err ? reject(err) : resolve(this.objects);
1317
+ });
1318
+ }
1319
+ });
1320
+ }
1321
+
1322
+ /**
1323
+ * Gets objects by list of IDs.
1324
+ */
1325
+ getObjectsById(
1326
+ /** Array of object IDs to retrieve. */
1327
+ list: string[],
1328
+ ): Promise<Record<string, ioBroker.Object>> {
1329
+ if (!this.connected) {
1330
+ return Promise.reject(NOT_CONNECTED);
1331
+ }
1332
+
1333
+ return new Promise((resolve, reject) => {
1334
+ this._socket.emit('getObjects', list, (err: string | null, res: Record<string, ioBroker.Object>) =>
1335
+ (err ? reject(err) : resolve(res)));
1336
+ });
1337
+ }
1338
+
1339
+ /**
1340
+ * Called internally.
1341
+ */
1342
+ private _subscribe(isEnable: boolean): void {
1343
+ if (isEnable && !this.subscribed) {
1344
+ this.subscribed = true;
1345
+ this.autoSubscribes.forEach(id => this._socket.emit('subscribeObjects', id));
1346
+ // re-subscribe objects
1347
+ Object.keys(this.objectsSubscribes).forEach(id => this._socket.emit('subscribeObjects', id));
1348
+ // re-subscribe logs
1349
+ this.autoSubscribeLog && this._socket.emit('requireLog', true);
1350
+ // re-subscribe states
1351
+ const ids = Object.keys(this.statesSubscribes);
1352
+ ids.forEach(id => this._socket.emit('subscribe', id));
1353
+ ids.length && this._socket.emit(Connection.isWeb() ? 'getStates' : 'getForeignStates', ids, (err: string | null, states: Record<string, ioBroker.State>) => {
1354
+ err && console.error(`Cannot getForeignStates: ${JSON.stringify(err)}`);
1355
+ // inform about states
1356
+ states && Object.keys(states).forEach(id => this.stateChange(id, states[id]));
1357
+ });
1358
+ } else if (!isEnable && this.subscribed) {
1359
+ this.subscribed = false;
1360
+ // un-subscribe objects
1361
+ this.autoSubscribes.forEach(id => this._socket.emit('unsubscribeObjects', id));
1362
+ Object.keys(this.objectsSubscribes).forEach(id => this._socket.emit('unsubscribeObjects', id));
1363
+ // un-subscribe logs
1364
+ this.autoSubscribeLog && this._socket.emit('requireLog', false);
1365
+
1366
+ // un-subscribe states
1367
+ Object.keys(this.statesSubscribes).forEach(id => this._socket.emit('unsubscribe', id));
1368
+ }
1369
+ }
1370
+
1371
+ /**
1372
+ * Requests log updates.
1373
+ */
1374
+ requireLog(
1375
+ /** Set to true to get logs. */
1376
+ isEnabled: boolean,
1377
+ ): Promise<void> {
1378
+ if (!this.connected) {
1379
+ return Promise.reject(NOT_CONNECTED);
1380
+ }
1381
+ return new Promise((resolve, reject) => {
1382
+ this._socket.emit('requireLog', isEnabled, (err: string | null) =>
1383
+ (err ? reject(err) : resolve()));
1384
+ });
1385
+ }
1386
+
1387
+ /**
1388
+ * Deletes the given object.
1389
+ */
1390
+ delObject(
1391
+ /** The object ID. */
1392
+ id: string,
1393
+ /** Force deletion of non-conform IDs. */
1394
+ maintenance?: boolean,
1395
+ ): Promise<void> {
1396
+ if (!this.connected) {
1397
+ return Promise.reject(NOT_CONNECTED);
1398
+ }
1399
+ return new Promise((resolve, reject) => {
1400
+ this._socket.emit('delObject', id, { maintenance: !!maintenance }, (err: string | null) =>
1401
+ (err ? reject(err) : resolve()));
1402
+ });
1403
+ }
1404
+
1405
+ /**
1406
+ * Deletes the given object and all its children.
1407
+ */
1408
+ delObjects(
1409
+ /** The object ID. */
1410
+ id: string,
1411
+ /** Force deletion of non-conform IDs. */
1412
+ maintenance?: boolean,
1413
+ ): Promise<void> {
1414
+ if (!this.connected) {
1415
+ return Promise.reject(NOT_CONNECTED);
1416
+ }
1417
+ return new Promise((resolve, reject) => {
1418
+ this._socket.emit('delObjects', id, { maintenance: !!maintenance }, (err: string | null) =>
1419
+ (err ? reject(err) : resolve()));
1420
+ });
1421
+ }
1422
+
1423
+ /**
1424
+ * Sets the object.
1425
+ */
1426
+ setObject(id: string, obj: ioBroker.SettableObject): Promise<void> {
1427
+ if (!this.connected) {
1428
+ return Promise.reject(NOT_CONNECTED);
1429
+ }
1430
+
1431
+ if (!obj) {
1432
+ return Promise.reject('Null object is not allowed');
1433
+ }
1434
+
1435
+ obj = JSON.parse(JSON.stringify(obj));
1436
+
1437
+ if (Object.prototype.hasOwnProperty.call(obj, 'from')) {
1438
+ delete obj.from;
1439
+ }
1440
+ if (Object.prototype.hasOwnProperty.call(obj, 'user')) {
1441
+ delete obj.user;
1442
+ }
1443
+ if (Object.prototype.hasOwnProperty.call(obj, 'ts')) {
1444
+ delete obj.ts;
1445
+ }
1446
+
1447
+ return new Promise((resolve, reject) => {
1448
+ this._socket.emit('setObject', id, obj, (err: string | null) =>
1449
+ (err ? reject(err) : resolve()));
1450
+ });
1451
+ }
1452
+
1453
+ /**
1454
+ * Gets the object with the given id from the server.
1455
+ */
1456
+ getObject(id: string): Promise<ioBroker.Object> {
1457
+ if (!this.connected) {
1458
+ return Promise.reject(NOT_CONNECTED);
1459
+ }
1460
+ if (id && id === this.ignoreState) {
1461
+ return Promise.resolve({
1462
+ _id: this.ignoreState,
1463
+ type: 'state',
1464
+ common: {
1465
+ name: 'ignored state',
1466
+ type: 'mixed',
1467
+ read: true,
1468
+ write: true,
1469
+ role: 'state',
1470
+ },
1471
+ native: {},
1472
+ });
1473
+ }
1474
+
1475
+ return new Promise((resolve, reject) => {
1476
+ this._socket.emit('getObject', id, (err: string | null, obj: ioBroker.Object) =>
1477
+ (err ? reject(err) : resolve(obj)));
1478
+ });
1479
+ }
1480
+
1481
+ /**
1482
+ * Get all instances of the given adapter or all instances of all adapters.
1483
+ */
1484
+ getAdapterInstances(
1485
+ /** The name of the adapter. */
1486
+ adapter?: string | boolean,
1487
+ /** Force update. */
1488
+ update?: boolean,
1489
+ ): Promise<ioBroker.InstanceObject[]> {
1490
+ if (typeof adapter === 'boolean') {
1491
+ update = adapter;
1492
+ adapter = '';
1493
+ }
1494
+ adapter = adapter || '';
1495
+
1496
+ if (!update && this._promises[`instances_${adapter}`]) {
1497
+ return this._promises[`instances_${adapter}`] as Promise<ioBroker.InstanceObject[]>;
1498
+ }
1499
+
1500
+ if (!this.connected) {
1501
+ return Promise.reject(NOT_CONNECTED);
1502
+ }
1503
+
1504
+ this._promises[`instances_${adapter}`] = new Promise((resolve, reject) => {
1505
+ let timeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
1506
+ timeout = null;
1507
+ this.getObjectView(
1508
+ `system.adapter.${adapter ? `${adapter}.` : ''}`,
1509
+ `system.adapter.${adapter ? `${adapter}.` : ''}\u9999`,
1510
+ 'instance',
1511
+ )
1512
+ .then(items => resolve(Object.keys(items).map(id => fixAdminUI(items[id] as ioBroker.AdapterObject))))
1513
+ .catch(e => reject(e));
1514
+ }, TIMEOUT_FOR_ADMIN4);
1515
+
1516
+ this._socket.emit('getAdapterInstances', adapter, (err: string | null, instances: ioBroker.InstanceObject[]) => {
1517
+ if (timeout) {
1518
+ clearTimeout(timeout);
1519
+ timeout = null;
1520
+ err ? reject(err) : resolve(instances);
1521
+ }
1522
+ });
1523
+ });
1524
+
1525
+ return this._promises[`instances_${adapter}`] as Promise<ioBroker.InstanceObject[]>;
1526
+ }
1527
+
1528
+ /**
1529
+ * Get adapters with the given name or all adapters.
1530
+ */
1531
+ getAdapters(
1532
+ /** The name of the adapter. */
1533
+ adapter?: string | boolean,
1534
+ /** Force update. */
1535
+ update?: boolean,
1536
+ ): Promise<ioBroker.AdapterObject[]> {
1537
+ if (Connection.isWeb()) {
1538
+ return Promise.reject('Allowed only in admin');
1539
+ }
1540
+
1541
+ if (typeof adapter === 'boolean') {
1542
+ update = adapter;
1543
+ adapter = '';
1544
+ }
1545
+
1546
+ adapter = adapter || '';
1547
+
1548
+ if (!update && this._promises[`adapter_${adapter}`]) {
1549
+ return this._promises[`adapter_${adapter}`] as Promise<ioBroker.AdapterObject[]>;
1550
+ }
1551
+
1552
+ if (!this.connected) {
1553
+ return Promise.reject(NOT_CONNECTED);
1554
+ }
1555
+
1556
+ this._promises[`adapter_${adapter}`] = new Promise((resolve, reject) => {
1557
+ let timeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
1558
+ timeout = null;
1559
+ this.getObjectView(
1560
+ `system.adapter.${adapter}.`,
1561
+ `system.adapter.${adapter}.\u9999`,
1562
+ 'adapter',
1563
+ )
1564
+ .then(items => {
1565
+ resolve(Object.keys(items).map(id => fixAdminUI(items[id] as ioBroker.AdapterObject)));
1566
+ })
1567
+ .catch(e => reject(e));
1568
+ }, TIMEOUT_FOR_ADMIN4);
1569
+
1570
+ this._socket.emit('getAdapters', adapter, (err: string | null, adapters: ioBroker.AdapterObject[]) => {
1571
+ if (timeout) {
1572
+ clearTimeout(timeout);
1573
+ timeout = null;
1574
+ err ? reject(err) : resolve(adapters);
1575
+ }
1576
+ });
1577
+ });
1578
+
1579
+ return this._promises[`adapter_${adapter}`] as Promise<ioBroker.AdapterObject[]>;
1580
+ }
1581
+
1582
+ /**
1583
+ * Called internally.
1584
+ */
1585
+ private _renameGroups(
1586
+ objs: RenameGroupObject[],
1587
+ cb: (err: string | null) => void,
1588
+ ) {
1589
+ if (!objs || !objs.length) {
1590
+ cb && cb(null);
1591
+ } else {
1592
+ const obj = objs.pop();
1593
+ if (!obj) {
1594
+ setTimeout(() => this._renameGroups(objs, cb), 0);
1595
+ return;
1596
+ }
1597
+ const oldId = obj._id;
1598
+ obj._id = obj.newId as ioBroker.ObjectIDs.Group;
1599
+ delete obj.newId;
1600
+
1601
+ this.setObject(obj._id, obj)
1602
+ .then(() => this.delObject(oldId))
1603
+ .then(() => setTimeout(() => this._renameGroups(objs, cb), 0))
1604
+ .catch((err: string | null) => cb && cb(err));
1605
+ }
1606
+ }
1607
+
1608
+ /**
1609
+ * Rename a group.
1610
+ * @param id The id.
1611
+ * @param newId The new id.
1612
+ * @param {string | { [lang in ioBroker.Languages]?: string; }} newName The new name.
1613
+ */
1614
+ async renameGroup(id: string, newId: string, newName: ioBroker.StringOrTranslated): Promise<void> {
1615
+ if (Connection.isWeb()) {
1616
+ return Promise.reject('Allowed only in admin');
1617
+ }
1618
+
1619
+ const groups = await this.getGroups(true);
1620
+ if (groups.length) {
1621
+ // find all elements
1622
+ const groupsToRename: RenameGroupObject[] = (groups as RenameGroupObject[])
1623
+ .filter(group => group._id.startsWith(`${id}.`));
1624
+
1625
+ groupsToRename.forEach(group => {
1626
+ group.newId = (newId + group._id.substring(id.length)) as ioBroker.ObjectIDs.Group;
1627
+ });
1628
+
1629
+ await new Promise((resolve, reject) => {
1630
+ this._renameGroups(groupsToRename, (err: string | null) =>
1631
+ (err ? reject(err) : resolve(null)));
1632
+ });
1633
+ const obj = groups.find(group => group._id === id);
1634
+
1635
+ if (obj) {
1636
+ obj._id = newId as ioBroker.ObjectIDs.Group;
1637
+ if (newName !== undefined) {
1638
+ obj.common = obj.common || ({} as ioBroker.GroupCommon);
1639
+ // @ts-expect-error will be corrected in the next js-controller release
1640
+ obj.common.name = newName;
1641
+ }
1642
+
1643
+ return this.setObject(obj._id, obj)
1644
+ .then(() => this.delObject(id));
1645
+ }
1646
+ }
1647
+
1648
+ return Promise.resolve();
1649
+ }
1650
+
1651
+ /**
1652
+ * Sends a message to a specific instance or all instances of some specific adapter.
1653
+ * @returns {Promise<ioBroker.Message | undefined>}
1654
+ */
1655
+ sendTo(
1656
+ /** The instance to send this message to. */
1657
+ instance: string,
1658
+ /** Command name of the target instance. */
1659
+ command: string,
1660
+ /** The message data to send. */
1661
+ data: any,
1662
+ ): Promise<{ result?: any; error?: string }> {
1663
+ if (!this.connected) {
1664
+ return Promise.reject(NOT_CONNECTED);
1665
+ }
1666
+ return new Promise(resolve => {
1667
+ this._socket.emit('sendTo', instance, command, data, (result: { result?: any; error?: string }) =>
1668
+ resolve(result));
1669
+ });
1670
+ }
1671
+
1672
+ /**
1673
+ * Extend an object and create it if it might not exist.
1674
+ */
1675
+ extendObject(
1676
+ /** The id. */
1677
+ id: string,
1678
+ /** The object. */
1679
+ obj: ioBroker.PartialObject,
1680
+ ): Promise<void> {
1681
+ if (!this.connected) {
1682
+ return Promise.reject(NOT_CONNECTED);
1683
+ }
1684
+
1685
+ obj = JSON.parse(JSON.stringify(obj));
1686
+
1687
+ if (Object.prototype.hasOwnProperty.call(obj, 'from')) {
1688
+ delete obj.from;
1689
+ }
1690
+ if (Object.prototype.hasOwnProperty.call(obj, 'user')) {
1691
+ delete obj.user;
1692
+ }
1693
+ if (Object.prototype.hasOwnProperty.call(obj, 'ts')) {
1694
+ delete obj.ts;
1695
+ }
1696
+
1697
+ return new Promise((resolve, reject) => {
1698
+ this._socket.emit('extendObject', id, obj, (err: string | null) =>
1699
+ ((err ? reject(err) : resolve())));
1700
+ });
1701
+ }
1702
+
1703
+ /**
1704
+ * Register a handler for log messages.
1705
+ */
1706
+ registerLogHandler(handler: (message: string) => void): void {
1707
+ !this.onLogHandlers.includes(handler) && this.onLogHandlers.push(handler);
1708
+ }
1709
+
1710
+ /**
1711
+ * Unregister a handler for log messages.
1712
+ */
1713
+ unregisterLogHandler(handler: (message: string) => void): void {
1714
+ const pos = this.onLogHandlers.indexOf(handler);
1715
+ pos !== -1 && this.onLogHandlers.splice(pos, 1);
1716
+ }
1717
+
1718
+ /**
1719
+ * Register a handler for the connection state.
1720
+ */
1721
+ registerConnectionHandler(handler: (connected: boolean) => void): void {
1722
+ !this.onConnectionHandlers.includes(handler) && this.onConnectionHandlers.push(handler);
1723
+ }
1724
+
1725
+ /**
1726
+ * Unregister a handler for the connection state.
1727
+ */
1728
+ unregisterConnectionHandler(handler: (connected: boolean) => void): void {
1729
+ const pos = this.onConnectionHandlers.indexOf(handler);
1730
+ pos !== -1 && this.onConnectionHandlers.splice(pos, 1);
1731
+ }
1732
+
1733
+ /**
1734
+ * Set the handler for standard output of a command.
1735
+ * @param {(id: string, text: string) => void} handler The handler.
1736
+ */
1737
+ registerCmdStdoutHandler(handler: (id: string, text: string) => void): void {
1738
+ this.onCmdStdoutHandler = handler;
1739
+ }
1740
+
1741
+ /**
1742
+ * Unset the handler for standard output of a command.
1743
+ */
1744
+ unregisterCmdStdoutHandler(/* handler */): void {
1745
+ this.onCmdStdoutHandler = undefined;
1746
+ }
1747
+
1748
+ /**
1749
+ * Set the handler for standard error of a command.
1750
+ * @param {(id: string, text: string) => void} handler The handler.
1751
+ */
1752
+ registerCmdStderrHandler(handler: (id: string, text: string) => void): void {
1753
+ this.onCmdStderrHandler = handler;
1754
+ }
1755
+
1756
+ /**
1757
+ * Unset the handler for standard error of a command.
1758
+ */
1759
+ unregisterCmdStderrHandler() {
1760
+ this.onCmdStderrHandler = undefined;
1761
+ }
1762
+
1763
+ /**
1764
+ * Set the handler for exit of a command.
1765
+ */
1766
+ registerCmdExitHandler(handler: (id: string, exitCode: number) => void): void {
1767
+ this.onCmdExitHandler = handler;
1768
+ }
1769
+
1770
+ /**
1771
+ * Unset the handler for exit of a command.
1772
+ */
1773
+ unregisterCmdExitHandler(): void {
1774
+ this.onCmdExitHandler = undefined;
1775
+ }
1776
+
1777
+ /**
1778
+ * Get all enums with the given name.
1779
+ */
1780
+ getEnums(
1781
+ /** The name of the enum. */
1782
+ _enum?: string,
1783
+ /** Force update. */
1784
+ update?: boolean,
1785
+ ): Promise<Record<string, ioBroker.EnumObject>> {
1786
+ if (!update && this._promises[`enums_${_enum || 'all'}`]) {
1787
+ return this._promises[`enums_${_enum || 'all'}`] as Promise<Record<string, ioBroker.EnumObject>>;
1788
+ }
1789
+
1790
+ if (!this.connected) {
1791
+ return Promise.reject(NOT_CONNECTED);
1792
+ }
1793
+
1794
+ this._promises[`enums_${_enum || 'all'}`] = new Promise((resolve, reject) => {
1795
+ this._socket.emit(
1796
+ 'getObjectView',
1797
+ 'system',
1798
+ 'enum',
1799
+ { startkey: `enum.${_enum || ''}`, endkey: `enum.${_enum ? `${_enum}.` : ''}\u9999` },
1800
+ (err: string | null, res: { rows: { value: ioBroker.EnumObject; id: string }[] }) => {
1801
+ if (!err && res) {
1802
+ const _res: Record<string, ioBroker.EnumObject> = {};
1803
+ for (let i = 0; i < res.rows.length; i++) {
1804
+ if (_enum && res.rows[i].id === `enum.${_enum}`) {
1805
+ continue;
1806
+ }
1807
+ _res[res.rows[i].id] = res.rows[i].value;
1808
+ }
1809
+ resolve(_res);
1810
+ } else {
1811
+ reject(err);
1812
+ }
1813
+ },
1814
+ );
1815
+ });
1816
+
1817
+ return this._promises[`enums_${_enum || 'all'}`] as Promise<Record<string, ioBroker.EnumObject>>;
1818
+ }
1819
+
1820
+ /**
1821
+ * Query a predefined object view.
1822
+ * @param design design - 'system' or other designs like `custom`.
1823
+ * @param type The type of object.
1824
+ * @param start The start ID.
1825
+ * @param [end] The end ID.
1826
+ */
1827
+ getObjectViewCustom(
1828
+ /** The design: 'system' or other designs like `custom`. */
1829
+ design: string,
1830
+ /** The type of object. */
1831
+ type: ioBroker.ObjectType,
1832
+ /** The start ID. */
1833
+ start: string,
1834
+ /** The end ID. */
1835
+ end?: string,
1836
+ ): Promise<Record<string, ioBroker.Object>> {
1837
+ return new Promise((resolve, reject) => {
1838
+ this._socket.emit('getObjectView', design, type, { startkey: start, endkey: end }, (err: string | null, res: { rows: { value: ioBroker.Object; id: string }[] }) => {
1839
+ if (!err) {
1840
+ const _res: Record<string, ioBroker.Object> = {};
1841
+ if (res && res.rows) {
1842
+ for (let i = 0; i < res.rows.length; i++) {
1843
+ _res[res.rows[i].id] = res.rows[i].value;
1844
+ }
1845
+ }
1846
+ resolve(_res);
1847
+ } else {
1848
+ reject(err);
1849
+ }
1850
+ });
1851
+ });
1852
+ }
1853
+
1854
+ /**
1855
+ * Query a predefined object view.
1856
+ * @param type The type of object.
1857
+ * @param start The start ID.
1858
+ * @param [end] The end ID.
1859
+ */
1860
+ getObjectViewSystem(
1861
+ /** The type of object. */
1862
+ type: ioBroker.ObjectType,
1863
+ /** The start ID. */
1864
+ start: string,
1865
+ /** The end ID. */
1866
+ end?: string,
1867
+ ): Promise<Record<string, ioBroker.Object>> {
1868
+ return this.getObjectViewCustom('system', type, start, end);
1869
+ }
1870
+
1871
+ /**
1872
+ * @deprecated since version 1.1.15, cause parameter order does not match backend
1873
+ * Query a predefined object view.
1874
+ * @param {string} start The start ID.
1875
+ * @param {string} end The end ID.
1876
+ * @param {string} type The type of object.
1877
+ * @returns {Promise<Record<string, ioBroker.Object>>}
1878
+ */
1879
+ getObjectView(start: string, end: string, type: ioBroker.ObjectType): Promise<Record<string, ioBroker.Object>> {
1880
+ if (!this.connected) {
1881
+ return Promise.reject(NOT_CONNECTED);
1882
+ }
1883
+
1884
+ start = start || '';
1885
+ end = end || '\u9999';
1886
+ return this.getObjectViewCustom('system', type, start, end);
1887
+ }
1888
+
1889
+ /**
1890
+ * Get the stored certificates.
1891
+ */
1892
+ getCertificates(
1893
+ /** Force update. */
1894
+ update?: boolean,
1895
+ ): Promise<{ name: string; type: 'public' | 'private' | 'chained' | '' }[]> {
1896
+ if (Connection.isWeb()) {
1897
+ return Promise.reject('Allowed only in admin');
1898
+ }
1899
+
1900
+ if (this._promises.cert && !update) {
1901
+ return this._promises.cert;
1902
+ }
1903
+
1904
+ if (!this.connected) {
1905
+ return Promise.reject(NOT_CONNECTED);
1906
+ }
1907
+
1908
+ this._promises.cert = this.getObject('system.certificates')
1909
+ .then(res => {
1910
+ const certs: { name: string; type: 'public' | 'private' | 'chained' | '' }[] = [];
1911
+ if (res && res.native && res.native.certificates) {
1912
+ Object.keys(res.native.certificates).forEach(c => {
1913
+ const cert: string = res.native.certificates[c];
1914
+ if (!cert) {
1915
+ return;
1916
+ }
1917
+ const _cert: { name: string; type: 'public' | 'private' | 'chained' | '' } = {
1918
+ name: c,
1919
+ type: '',
1920
+ };
1921
+ // If it is a filename, it could be everything
1922
+ if (cert.length < 700 && (cert.includes('/') || cert.includes('\\'))) {
1923
+ if (c.toLowerCase().includes('private')) {
1924
+ _cert.type = 'private';
1925
+ } else if (cert.toLowerCase().includes('private')) {
1926
+ _cert.type = 'private';
1927
+ } else if (c.toLowerCase().includes('public')) {
1928
+ _cert.type = 'public';
1929
+ } else if (cert.toLowerCase().includes('public')) {
1930
+ _cert.type = 'public';
1931
+ }
1932
+ certs.push(_cert);
1933
+ } else {
1934
+ _cert.type = (cert.substring(0, '-----BEGIN RSA PRIVATE KEY'.length) === '-----BEGIN RSA PRIVATE KEY' || cert.substring(0, '-----BEGIN PRIVATE KEY'.length) === '-----BEGIN PRIVATE KEY') ? 'private' : 'public';
1935
+
1936
+ if (_cert.type === 'public') {
1937
+ const m = cert.split('-----END CERTIFICATE-----');
1938
+ if (m.filter((t: string) => t.replace(/\r\n|\r|\n/, '').trim()).length > 1) {
1939
+ _cert.type = 'chained';
1940
+ }
1941
+ }
1942
+
1943
+ certs.push(_cert);
1944
+ }
1945
+ });
1946
+ }
1947
+ return certs;
1948
+ });
1949
+
1950
+ return this._promises.cert;
1951
+ }
1952
+
1953
+ /**
1954
+ * Get the logs from a host (only for admin connection).
1955
+ */
1956
+ getLogs(
1957
+ host: string,
1958
+ linesNumber?: number,
1959
+ ): Promise<string[]> {
1960
+ if (Connection.isWeb()) {
1961
+ return Promise.reject('Allowed only in admin');
1962
+ }
1963
+
1964
+ if (!this.connected) {
1965
+ return Promise.reject(NOT_CONNECTED);
1966
+ }
1967
+
1968
+ return new Promise(resolve => {
1969
+ this._socket.emit('sendToHost', host, 'getLogs', linesNumber || 200, (lines: string[]) =>
1970
+ resolve(lines));
1971
+ });
1972
+ }
1973
+
1974
+ /**
1975
+ * Get the log files (only for admin connection).
1976
+ */
1977
+ getLogsFiles(host: string): Promise<string[]> {
1978
+ if (Connection.isWeb()) {
1979
+ return Promise.reject('Allowed only in admin');
1980
+ }
1981
+ if (!this.connected) {
1982
+ return Promise.reject(NOT_CONNECTED);
1983
+ }
1984
+ return new Promise((resolve, reject) => {
1985
+ this._socket.emit('readLogs', host, (err: string | null, files: string[]) =>
1986
+ (err ? reject(err) : resolve(files)));
1987
+ });
1988
+ }
1989
+
1990
+ /**
1991
+ * Delete the logs from a host (only for admin connection).
1992
+ */
1993
+ delLogs(host: string): Promise<void> {
1994
+ if (Connection.isWeb()) {
1995
+ return Promise.reject('Allowed only in admin');
1996
+ }
1997
+ if (!this.connected) {
1998
+ return Promise.reject(NOT_CONNECTED);
1999
+ }
2000
+ return new Promise((resolve, reject) => {
2001
+ this._socket.emit('sendToHost', host, 'delLogs', null, (error: string | null) =>
2002
+ (error ? reject(error) : resolve()));
2003
+ });
2004
+ }
2005
+
2006
+ /**
2007
+ * Read the meta items.
2008
+ */
2009
+ readMetaItems(): Promise<ioBroker.MetaObject[]> {
2010
+ if (!this.connected) {
2011
+ return Promise.reject(NOT_CONNECTED);
2012
+ }
2013
+ return new Promise((resolve, reject) => {
2014
+ this._socket.emit('getObjectView', 'system', 'meta', { startkey: '', endkey: '\u9999' }, (err: string | null, objs: { rows: { value: ioBroker.MetaObject; id: string }[] }) =>
2015
+ (err ? reject(err) : resolve(objs.rows && objs.rows.map((obj: { value: ioBroker.MetaObject; id: string }) => obj.value))));
2016
+ });
2017
+ }
2018
+
2019
+ /**
2020
+ * Read the directory of an adapter.
2021
+ */
2022
+ readDir(
2023
+ /** The adapter name. */
2024
+ adapter: string,
2025
+ /** The directory name. */
2026
+ fileName: string,
2027
+ ): Promise<ioBroker.ReadDirResult[]> {
2028
+ if (!this.connected) {
2029
+ return Promise.reject(NOT_CONNECTED);
2030
+ }
2031
+ return new Promise((resolve, reject) => {
2032
+ this._socket.emit('readDir', adapter, fileName, (err: string | null, files: ioBroker.ReadDirResult[]) =>
2033
+ (err ? reject(err) : resolve(files)));
2034
+ });
2035
+ }
2036
+
2037
+ /**
2038
+ * Read a file of an adapter.
2039
+ */
2040
+ readFile(
2041
+ /** The adapter name. */
2042
+ adapter: string,
2043
+ /** The file name. */
2044
+ fileName: string,
2045
+ /** If it must be a base64 format */
2046
+ base64?: boolean,
2047
+ ): Promise<string | { data: string; type: string }> {
2048
+ if (!this.connected) {
2049
+ return Promise.reject(NOT_CONNECTED);
2050
+ }
2051
+ return new Promise((resolve, reject) => {
2052
+ if (!base64) {
2053
+ this._socket.emit('readFile', adapter, fileName, (err: string | null, data: string, type: string) => {
2054
+ err ? reject(err) : resolve({ data, type });
2055
+ });
2056
+ } else {
2057
+ this._socket.emit('readFile64', adapter, fileName, base64, (err: string | null, data: string) =>
2058
+ (err ? reject(err) : resolve(data)));
2059
+ }
2060
+ });
2061
+ }
2062
+
2063
+ /**
2064
+ * Write a file of an adapter.
2065
+ */
2066
+ writeFile64(
2067
+ /** The adapter name. */
2068
+ adapter: string,
2069
+ /** The file name. */
2070
+ fileName: string,
2071
+ /** The data (if it's a Buffer, it will be converted to Base64). */
2072
+ data: Buffer | string,
2073
+ ): Promise<void> {
2074
+ if (!this.connected) {
2075
+ return Promise.reject(NOT_CONNECTED);
2076
+ }
2077
+ return new Promise((resolve, reject) => {
2078
+ if (typeof data === 'string') {
2079
+ this._socket.emit('writeFile', adapter, fileName, data, (err: string | null) =>
2080
+ (err ? reject(err) : resolve()));
2081
+ } else {
2082
+ const base64 = btoa(
2083
+ new Uint8Array(data)
2084
+ .reduce((_data, byte) => _data + String.fromCharCode(byte), ''),
2085
+ );
2086
+
2087
+ this._socket.emit('writeFile64', adapter, fileName, base64, (err: string | null) =>
2088
+ (err ? reject(err) : resolve()));
2089
+ }
2090
+ });
2091
+ }
2092
+
2093
+ /**
2094
+ * Delete a file of an adapter.
2095
+ */
2096
+ deleteFile(
2097
+ /** The adapter name. */
2098
+ adapter: string,
2099
+ /** The file name. */
2100
+ fileName: string,
2101
+ ): Promise<void> {
2102
+ if (!this.connected) {
2103
+ return Promise.reject(NOT_CONNECTED);
2104
+ }
2105
+ return new Promise((resolve, reject) => {
2106
+ this._socket.emit('unlink', adapter, fileName, (err: string | null) =>
2107
+ (err ? reject(err) : resolve()));
2108
+ });
2109
+ }
2110
+
2111
+ /**
2112
+ * Delete a folder of an adapter.
2113
+ * All files in folder will be deleted.
2114
+ */
2115
+ deleteFolder(
2116
+ /** The adapter name. */
2117
+ adapter: string,
2118
+ /** The file name. */
2119
+ folderName: string,
2120
+ ): Promise<void> {
2121
+ if (!this.connected) {
2122
+ return Promise.reject(NOT_CONNECTED);
2123
+ }
2124
+ return new Promise((resolve, reject) => {
2125
+ this._socket.emit('deleteFolder', adapter, folderName, (err: string | null) =>
2126
+ (err ? reject(err) : resolve()));
2127
+ });
2128
+ }
2129
+
2130
+ /**
2131
+ * Get the list of all hosts.
2132
+ * @param {boolean} [update] Force update.
2133
+ */
2134
+ getHosts(update?: boolean): Promise<ioBroker.HostObject[]> {
2135
+ if (Connection.isWeb()) {
2136
+ return Promise.reject('Allowed only in admin');
2137
+ }
2138
+ if (!update && this._promises.hosts) {
2139
+ return this._promises.hosts;
2140
+ }
2141
+
2142
+ if (!this.connected) {
2143
+ return Promise.reject(NOT_CONNECTED);
2144
+ }
2145
+
2146
+ this._promises.hosts = new Promise((resolve, reject) => {
2147
+ this._socket.emit(
2148
+ 'getObjectView',
2149
+ 'system',
2150
+ 'host',
2151
+ { startkey: 'system.host.', endkey: 'system.host.\u9999' },
2152
+ (err: string | null, doc: { rows: { value: ioBroker.HostObject; id: string }[]}) => {
2153
+ if (err) {
2154
+ reject(err);
2155
+ } else {
2156
+ resolve(doc.rows.map(item => item.value));
2157
+ }
2158
+ },
2159
+ );
2160
+ });
2161
+
2162
+ return this._promises.hosts;
2163
+ }
2164
+
2165
+ /**
2166
+ * Get the list of all users.
2167
+ */
2168
+ getUsers(
2169
+ /** Force update. */
2170
+ update?: boolean,
2171
+ ): Promise<ioBroker.UserObject[]> {
2172
+ if (Connection.isWeb()) {
2173
+ return Promise.reject('Allowed only in admin');
2174
+ }
2175
+ if (!update && this._promises.users) {
2176
+ return this._promises.users;
2177
+ }
2178
+ if (!this.connected) {
2179
+ return Promise.reject(NOT_CONNECTED);
2180
+ }
2181
+
2182
+ this._promises.users = new Promise((resolve, reject) => {
2183
+ this._socket.emit(
2184
+ 'getObjectView',
2185
+ 'system',
2186
+ 'user',
2187
+ { startkey: 'system.user.', endkey: 'system.user.\u9999' },
2188
+ (err: string | null, doc: { rows: { value: ioBroker.UserObject; id: string }[] }) => {
2189
+ if (err) {
2190
+ reject(err);
2191
+ } else {
2192
+ resolve(doc.rows.map(item => item.value));
2193
+ }
2194
+ },
2195
+ );
2196
+ });
2197
+
2198
+ return this._promises.users;
2199
+ }
2200
+
2201
+ /**
2202
+ * Get the list of all groups.
2203
+ */
2204
+ getGroups(
2205
+ /** Force update. */
2206
+ update?: boolean,
2207
+ ): Promise<ioBroker.GroupObject[]> {
2208
+ if (!update && this._promises.groups) {
2209
+ return this._promises.groups;
2210
+ }
2211
+ if (!this.connected) {
2212
+ return Promise.reject(NOT_CONNECTED);
2213
+ }
2214
+
2215
+ this._promises.groups = new Promise((resolve, reject) => {
2216
+ this._socket.emit(
2217
+ 'getObjectView',
2218
+ 'system',
2219
+ 'group',
2220
+ { startkey: 'system.group.', endkey: 'system.group.\u9999' },
2221
+ (err: string | null, doc: { rows: { value: ioBroker.GroupObject; id: string }[] }) => {
2222
+ if (err) {
2223
+ reject(err);
2224
+ } else {
2225
+ resolve(doc.rows.map(item => item.value));
2226
+ }
2227
+ },
2228
+ );
2229
+ });
2230
+
2231
+ return this._promises.groups;
2232
+ }
2233
+
2234
+ /**
2235
+ * Get the host information.
2236
+ */
2237
+ getHostInfo(
2238
+ host: string,
2239
+ /** Force update. */
2240
+ update?: boolean,
2241
+ /** optional read timeout. */
2242
+ timeoutMs?: number,
2243
+ ): Promise<HostInfo> {
2244
+ if (Connection.isWeb()) {
2245
+ return Promise.reject('Allowed only in admin');
2246
+ }
2247
+ if (!host.startsWith('system.host.')) {
2248
+ host += `system.host.${host}`;
2249
+ }
2250
+
2251
+ if (!update && this._promises[`hostInfo_${host}`]) {
2252
+ return this._promises[`hostInfo_${host}`] as Promise<HostInfo>;
2253
+ }
2254
+
2255
+ if (!this.connected) {
2256
+ return Promise.reject(NOT_CONNECTED);
2257
+ }
2258
+
2259
+ this._promises[`hostInfo_${host}`] = new Promise((resolve, reject) => {
2260
+ let timeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
2261
+ if (timeout) {
2262
+ timeout = null;
2263
+ reject('getHostInfo timeout');
2264
+ }
2265
+ }, timeoutMs || this.props.cmdTimeout);
2266
+
2267
+ this._socket.emit('sendToHost', host, 'getHostInfo', null, (data: string | HostInfo) => {
2268
+ if (timeout) {
2269
+ clearTimeout(timeout);
2270
+ timeout = null;
2271
+ if (data === PERMISSION_ERROR) {
2272
+ reject('May not read "getHostInfo"');
2273
+ } else if (!data || typeof data !== 'object') {
2274
+ reject('Cannot read "getHostInfo"');
2275
+ } else {
2276
+ resolve(data);
2277
+ }
2278
+ }
2279
+ });
2280
+ });
2281
+
2282
+ return this._promises[`hostInfo_${host}`] as Promise<HostInfo>;
2283
+ }
2284
+
2285
+ /**
2286
+ * Get the host information (short version).
2287
+ */
2288
+ getHostInfoShort(
2289
+ host: string,
2290
+ /** Force update. */
2291
+ update?: boolean,
2292
+ /** optional read timeout. */
2293
+ timeoutMs?: number,
2294
+ ): Promise<HostInfo> {
2295
+ if (Connection.isWeb()) {
2296
+ return Promise.reject('Allowed only in admin');
2297
+ }
2298
+ if (!host.startsWith('system.host.')) {
2299
+ host += `system.host.${host}`;
2300
+ }
2301
+ if (!update && this._promises[`hostInfoShort_${host}`]) {
2302
+ return this._promises[`hostInfoShort_${host}`] as Promise<HostInfo>;
2303
+ }
2304
+
2305
+ if (!this.connected) {
2306
+ return Promise.reject(NOT_CONNECTED);
2307
+ }
2308
+
2309
+ this._promises[`hostInfoShort_${host}`] = new Promise((resolve, reject) => {
2310
+ let timeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
2311
+ if (timeout) {
2312
+ timeout = null;
2313
+ reject('hostInfoShort timeout');
2314
+ }
2315
+ }, timeoutMs || this.props.cmdTimeout);
2316
+
2317
+ this._socket.emit('sendToHost', host, 'getHostInfoShort', null, (data: string | HostInfo) => {
2318
+ if (timeout) {
2319
+ clearTimeout(timeout);
2320
+ timeout = null;
2321
+ if (data === PERMISSION_ERROR) {
2322
+ reject('May not read "getHostInfoShort"');
2323
+ } else if (!data || typeof data !== 'object') {
2324
+ reject('Cannot read "getHostInfoShort"');
2325
+ } else {
2326
+ resolve(data);
2327
+ }
2328
+ }
2329
+ });
2330
+ });
2331
+
2332
+ return this._promises[`hostInfoShort_${host}`] as Promise<HostInfo>;
2333
+ }
2334
+
2335
+ /**
2336
+ * Get the repository.
2337
+ */
2338
+ getRepository(
2339
+ host: string,
2340
+ options?: { update: boolean; repo: string } | string,
2341
+ /** Force update. */
2342
+ update?: boolean,
2343
+ /** timeout in ms. */
2344
+ timeoutMs?: number,
2345
+ ): Promise<Record<string, ioBroker.AdapterObject>> {
2346
+ if (Connection.isWeb()) {
2347
+ return Promise.reject('Allowed only in admin');
2348
+ }
2349
+ if (!update && this._promises.repo) {
2350
+ return this._promises.repo;
2351
+ }
2352
+
2353
+ if (!this.connected) {
2354
+ return Promise.reject(NOT_CONNECTED);
2355
+ }
2356
+
2357
+ if (!host.startsWith('system.host.')) {
2358
+ host += `system.host.${host}`;
2359
+ }
2360
+
2361
+ this._promises.repo = new Promise((resolve, reject) => {
2362
+ let timeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
2363
+ if (timeout) {
2364
+ timeout = null;
2365
+ reject('getRepository timeout');
2366
+ }
2367
+ }, timeoutMs || this.props.cmdTimeout);
2368
+
2369
+ this._socket.emit('sendToHost', host, 'getRepository', options, (data: string | Record<string, ioBroker.AdapterObject>) => {
2370
+ if (timeout) {
2371
+ clearTimeout(timeout);
2372
+ timeout = null;
2373
+ if (data === PERMISSION_ERROR) {
2374
+ reject('May not read "getRepository"');
2375
+ } else if (!data || typeof data !== 'object') {
2376
+ reject('Cannot read "getRepository"');
2377
+ } else {
2378
+ resolve(data);
2379
+ }
2380
+ }
2381
+ });
2382
+ });
2383
+
2384
+ return this._promises.repo;
2385
+ }
2386
+
2387
+ /**
2388
+ * Get the installed.
2389
+ */
2390
+ getInstalled(
2391
+ host: string,
2392
+ /** Force update. */
2393
+ update?: boolean,
2394
+ /** timeout in ms */
2395
+ cmdTimeout?: number,
2396
+ ): Promise<Record<string, ioBroker.AdapterObject>> {
2397
+ if (Connection.isWeb()) {
2398
+ return Promise.reject('Allowed only in admin');
2399
+ }
2400
+
2401
+ this._promises.installed = this._promises.installed || {};
2402
+
2403
+ if (!update && this._promises.installed[host]) {
2404
+ return this._promises.installed[host] as Promise<Record<string, ioBroker.AdapterObject>>;
2405
+ }
2406
+
2407
+ if (!this.connected) {
2408
+ return Promise.reject(NOT_CONNECTED);
2409
+ }
2410
+
2411
+ if (!host.startsWith('system.host.')) {
2412
+ host += `system.host.${host}`;
2413
+ }
2414
+
2415
+ this._promises.installed[host] = new Promise((resolve, reject) => {
2416
+ let timeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
2417
+ if (timeout) {
2418
+ timeout = null;
2419
+ reject('getInstalled timeout');
2420
+ }
2421
+ }, cmdTimeout || this.props.cmdTimeout);
2422
+
2423
+ this._socket.emit('sendToHost', host, 'getInstalled', null, (data: string | Record<string, ioBroker.AdapterObject>) => {
2424
+ if (timeout) {
2425
+ clearTimeout(timeout);
2426
+ timeout = null;
2427
+ if (data === PERMISSION_ERROR) {
2428
+ reject('May not read "getInstalled"');
2429
+ } else if (!data || typeof data !== 'object') {
2430
+ reject('Cannot read "getInstalled"');
2431
+ } else {
2432
+ resolve(data);
2433
+ }
2434
+ }
2435
+ });
2436
+ });
2437
+
2438
+ return this._promises.installed[host] as Promise<Record<string, ioBroker.AdapterObject>>;
2439
+ }
2440
+
2441
+ /**
2442
+ * Rename file or folder in ioBroker DB
2443
+ */
2444
+ rename(
2445
+ /** instance name */
2446
+ adapter: string,
2447
+ /** current file name, e.g., main/vis-views.json */
2448
+ oldName: string,
2449
+ /** new file name, e.g., main/vis-views-new.json */
2450
+ newName: string,
2451
+ ): Promise<void> {
2452
+ if (!this.connected) {
2453
+ return Promise.reject(NOT_CONNECTED);
2454
+ }
2455
+ return new Promise((resolve, reject) => {
2456
+ this._socket.emit('rename', adapter, oldName, newName, (err: string | null) =>
2457
+ (err ? reject(err) : resolve()));
2458
+ });
2459
+ }
2460
+
2461
+ /**
2462
+ * Rename file in ioBroker DB
2463
+ */
2464
+ renameFile(
2465
+ /** instance name */
2466
+ adapter: string,
2467
+ /** current file name, e.g., main/vis-views.json */
2468
+ oldName: string,
2469
+ /** new file name, e.g., main/vis-views-new.json */
2470
+ newName: string,
2471
+ ): Promise<void> {
2472
+ if (!this.connected) {
2473
+ return Promise.reject(NOT_CONNECTED);
2474
+ }
2475
+ return new Promise((resolve, reject) => {
2476
+ this._socket.emit('renameFile', adapter, oldName, newName, (err: string | null) =>
2477
+ (err ? reject(err) : resolve()));
2478
+ });
2479
+ }
2480
+
2481
+ /**
2482
+ * Execute a command on a host.
2483
+ */
2484
+ cmdExec(
2485
+ /** The host name. */
2486
+ host: string,
2487
+ /** The command. */
2488
+ cmd: string,
2489
+ /** The command ID. */
2490
+ cmdId: string,
2491
+ /** Timeout of command in ms */
2492
+ cmdTimeout: number,
2493
+ ): Promise<void> {
2494
+ if (Connection.isWeb()) {
2495
+ return Promise.reject('Allowed only in admin');
2496
+ }
2497
+ if (!this.connected) {
2498
+ return Promise.reject(NOT_CONNECTED);
2499
+ }
2500
+
2501
+ if (!host.startsWith(host)) {
2502
+ host += `system.host.${host}`;
2503
+ }
2504
+
2505
+ return new Promise((resolve, reject) => {
2506
+ let timeout: ReturnType<typeof setTimeout> | null = cmdTimeout ? setTimeout(() => {
2507
+ if (timeout) {
2508
+ timeout = null;
2509
+ reject('cmdExec timeout');
2510
+ }
2511
+ }, cmdTimeout) : null;
2512
+
2513
+ this._socket.emit('cmdExec', host, cmdId, cmd, null, (err: string | null) => {
2514
+ if (!cmdTimeout || timeout) {
2515
+ timeout && clearTimeout(timeout);
2516
+ timeout = null;
2517
+ if (err) {
2518
+ reject(err);
2519
+ } else {
2520
+ resolve();
2521
+ }
2522
+ }
2523
+ });
2524
+ });
2525
+ }
2526
+
2527
+ /**
2528
+ * Checks if a given feature is supported.
2529
+ */
2530
+ checkFeatureSupported(
2531
+ /** The feature to check. */
2532
+ feature: string,
2533
+ /** Force update. */
2534
+ update?: boolean,
2535
+ ): Promise<boolean> {
2536
+ if (!update && this._promises[`supportedFeatures_${feature}`]) {
2537
+ return this._promises[`supportedFeatures_${feature}`] as Promise<boolean>;
2538
+ }
2539
+
2540
+ if (!this.connected) {
2541
+ return Promise.reject(NOT_CONNECTED);
2542
+ }
2543
+
2544
+ this._promises[`supportedFeatures_${feature}`] = new Promise((resolve, reject) => {
2545
+ this._socket.emit('checkFeatureSupported', feature, (err: string | null, supported: boolean) =>
2546
+ (err ? reject(err) : resolve(supported)));
2547
+ });
2548
+
2549
+ return this._promises[`supportedFeatures_${feature}`] as Promise<boolean>;
2550
+ }
2551
+
2552
+ /**
2553
+ * Read the base settings of a given host.
2554
+ */
2555
+ async readBaseSettings(host: string): Promise<Record<string, any>> {
2556
+ if (Connection.isWeb()) {
2557
+ return Promise.reject('Allowed only in admin');
2558
+ }
2559
+ const result = await this.checkFeatureSupported('CONTROLLER_READWRITE_BASE_SETTINGS');
2560
+ if (result) {
2561
+ if (!this.connected) {
2562
+ return Promise.reject(NOT_CONNECTED);
2563
+ }
2564
+ return new Promise((resolve, reject) => {
2565
+ let timeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
2566
+ if (timeout) {
2567
+ timeout = null;
2568
+ reject('readBaseSettings timeout');
2569
+ }
2570
+ }, this.props.cmdTimeout);
2571
+
2572
+ if (host.startsWith('system.host.')) {
2573
+ host = host.replace(/^system\.host\./, '');
2574
+ }
2575
+
2576
+ this._socket.emit('sendToHost', host, 'readBaseSettings', null, (data: Record<string, any> | string) => {
2577
+ if (timeout) {
2578
+ clearTimeout(timeout);
2579
+ timeout = null;
2580
+
2581
+ if (data === PERMISSION_ERROR) {
2582
+ reject('May not read "BaseSettings"');
2583
+ } else if (!data || typeof data !== 'object') {
2584
+ reject('Cannot read "BaseSettings"');
2585
+ } else {
2586
+ resolve(data);
2587
+ }
2588
+ }
2589
+ });
2590
+ });
2591
+ }
2592
+ return Promise.reject('Not supported');
2593
+ }
2594
+
2595
+ /**
2596
+ * Write the base settings of a given host.
2597
+ * @param {string} host
2598
+ * @param {any} config
2599
+ * @returns {Promise<any>}
2600
+ */
2601
+ writeBaseSettings(host: string, config: Record<string, any>): Promise<{ result?: 'ok'; error?: string }> {
2602
+ if (Connection.isWeb()) {
2603
+ return Promise.reject('Allowed only in admin');
2604
+ }
2605
+ return this.checkFeatureSupported('CONTROLLER_READWRITE_BASE_SETTINGS')
2606
+ .then(result => {
2607
+ if (result) {
2608
+ if (!this.connected) {
2609
+ return Promise.reject(NOT_CONNECTED);
2610
+ }
2611
+ return new Promise((resolve, reject) => {
2612
+ let timeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
2613
+ if (timeout) {
2614
+ timeout = null;
2615
+ reject('writeBaseSettings timeout');
2616
+ }
2617
+ }, this.props.cmdTimeout);
2618
+
2619
+ this._socket.emit('sendToHost', host, 'writeBaseSettings', config, (data: { result?: 'ok'; error?: string } | string) => {
2620
+ if (timeout) {
2621
+ clearTimeout(timeout);
2622
+ timeout = null;
2623
+
2624
+ if (data === PERMISSION_ERROR) {
2625
+ reject(new Error('May not write "BaseSettings"'));
2626
+ } else if (!data) {
2627
+ reject(new Error('Cannot write "BaseSettings"'));
2628
+ } else {
2629
+ resolve(data as { result?: 'ok'; error?: string });
2630
+ }
2631
+ }
2632
+ });
2633
+ });
2634
+ }
2635
+
2636
+ return Promise.reject(new Error('Not supported'));
2637
+ });
2638
+ }
2639
+
2640
+ /**
2641
+ * Send command to restart the iobroker on host
2642
+ */
2643
+ restartController(host: string): Promise<boolean> {
2644
+ if (Connection.isWeb()) {
2645
+ return Promise.reject('Allowed only in admin');
2646
+ }
2647
+ return new Promise((resolve, reject) => {
2648
+ this._socket.emit('sendToHost', host, 'restartController', null, (error: string | null) => {
2649
+ error ? reject(error) : resolve(true);
2650
+ });
2651
+ });
2652
+ }
2653
+
2654
+ /**
2655
+ * Read statistics information from host
2656
+ * @param {string} host
2657
+ * @param {string} typeOfDiag one of none, normal, no-city, extended
2658
+ * @returns {Promise<any>}
2659
+ */
2660
+ getDiagData(
2661
+ host: string,
2662
+ typeOfDiag: 'none' | 'normal' | 'no-city' | 'extended',
2663
+ ): Promise<Record<string, any>> {
2664
+ if (Connection.isWeb()) {
2665
+ return Promise.reject('Allowed only in admin');
2666
+ }
2667
+ return new Promise(resolve => {
2668
+ this._socket.emit('sendToHost', host, 'getDiagData', typeOfDiag, (result: Record<string, any>) =>
2669
+ resolve(result));
2670
+ });
2671
+ }
2672
+
2673
+ /**
2674
+ * Read all states (which might not belong to this adapter) which match the given pattern.
2675
+ */
2676
+ getForeignStates(pattern?: string): Promise<Record<string, ioBroker.State>> {
2677
+ if (!this.connected) {
2678
+ return Promise.reject(NOT_CONNECTED);
2679
+ }
2680
+ if (Connection.isWeb()) {
2681
+ return new Promise((resolve, reject) => {
2682
+ this._socket.emit('getStates', pattern || '*', (err: string | null, states: Record<string, ioBroker.State>) =>
2683
+ (err ? reject(err) : resolve(states)));
2684
+ });
2685
+ }
2686
+
2687
+ return new Promise((resolve, reject) => {
2688
+ this._socket.emit('getForeignStates', pattern || '*', (err: string | null, states: Record<string, ioBroker.State>) =>
2689
+ (err ? reject(err) : resolve(states)));
2690
+ });
2691
+ }
2692
+
2693
+ /**
2694
+ * Get foreign objects by pattern, by specific type and resolve their enums. (Only admin)
2695
+ * @returns {ioBroker.GetObjectsPromise}
2696
+ */
2697
+ getForeignObjects(pattern: string, type?: ioBroker.ObjectType): Promise<Record<string, ioBroker.State>> {
2698
+ if (Connection.isWeb()) {
2699
+ return Promise.reject('Allowed only in admin');
2700
+ }
2701
+
2702
+ if (!this.connected) {
2703
+ return Promise.reject(NOT_CONNECTED);
2704
+ }
2705
+ return new Promise((resolve, reject) => {
2706
+ this._socket.emit('getForeignObjects', pattern || '*', type, (err: string | null, states: Record<string, ioBroker.State>) =>
2707
+ (err ? reject(err) : resolve(states)));
2708
+ });
2709
+ }
2710
+
2711
+ /**
2712
+ * Gets the system configuration.
2713
+ * @param {boolean} [update] Force update.
2714
+ * @returns {Promise<ioBroker.OtherObject>}
2715
+ */
2716
+ getSystemConfig(update?: boolean): Promise<ioBroker.SystemConfigObject> {
2717
+ if (!update && this._promises.systemConfig) {
2718
+ return this._promises.systemConfig;
2719
+ }
2720
+
2721
+ if (!this.connected) {
2722
+ return Promise.reject(NOT_CONNECTED);
2723
+ }
2724
+
2725
+ this._promises.systemConfig = this.getObject('system.config')
2726
+ .then(obj => {
2727
+ const systemConfig: ioBroker.SystemConfigObject = (obj || {}) as ioBroker.SystemConfigObject;
2728
+ systemConfig.common = systemConfig.common || {} as ioBroker.SystemConfigCommon;
2729
+ systemConfig.native = systemConfig.native || {};
2730
+ return systemConfig;
2731
+ });
2732
+
2733
+ return this._promises.systemConfig;
2734
+ }
2735
+
2736
+ /**
2737
+ * Sets the system configuration.
2738
+ */
2739
+ setSystemConfig(obj: ioBroker.SettableObjectWorker<ioBroker.SystemConfigObject>): Promise<ioBroker.SystemConfigObject> {
2740
+ return this.setObject('system.config', obj)
2741
+ .then(() => this._promises.systemConfig = Promise.resolve(obj as ioBroker.SystemConfigObject));
2742
+ }
2743
+
2744
+ /**
2745
+ * Get the raw socket.io socket.
2746
+ */
2747
+ getRawSocket(): SocketClient {
2748
+ return this._socket;
2749
+ }
2750
+
2751
+ /**
2752
+ * Get the history of a given state.
2753
+ */
2754
+ getHistory(
2755
+ id: string,
2756
+ options: ioBroker.GetHistoryOptions,
2757
+ ): Promise<ioBroker.GetHistoryResult> {
2758
+ if (!this.connected) {
2759
+ return Promise.reject(NOT_CONNECTED);
2760
+ }
2761
+
2762
+ return new Promise((resolve, reject) => {
2763
+ this._socket.emit('getHistory', id, options, (err: string | null, values: ioBroker.GetHistoryResult) =>
2764
+ (err ? reject(err) : resolve(values)));
2765
+ });
2766
+ }
2767
+
2768
+ /**
2769
+ * Get the history of a given state.
2770
+ */
2771
+ getHistoryEx(
2772
+ id: string,
2773
+ options: ioBroker.GetHistoryOptions,
2774
+ ): Promise<{ values: ioBroker.GetHistoryResult; sessionId: string; stepIgnore: number }> {
2775
+ if (!this.connected) {
2776
+ return Promise.reject(NOT_CONNECTED);
2777
+ }
2778
+
2779
+ return new Promise((resolve, reject) => {
2780
+ this._socket.emit('getHistory', id, options, (err: string | null, values: ioBroker.GetHistoryResult, stepIgnore: number, sessionId: string) =>
2781
+ (err ? reject(err) : resolve({ values, sessionId, stepIgnore })));
2782
+ });
2783
+ }
2784
+
2785
+ /**
2786
+ * Change the password of the given user.
2787
+ */
2788
+ changePassword(
2789
+ user: string,
2790
+ password: string,
2791
+ ): Promise<void> {
2792
+ if (Connection.isWeb()) {
2793
+ return Promise.reject('Allowed only in admin');
2794
+ }
2795
+ return new Promise((resolve, reject) => {
2796
+ this._socket.emit('changePassword', user, password, (err: string | null) =>
2797
+ (err ? reject(err) : resolve()));
2798
+ });
2799
+ }
2800
+
2801
+ /**
2802
+ * Get the IP addresses of the given host.
2803
+ */
2804
+ getIpAddresses(
2805
+ host: string,
2806
+ /** Force update. */
2807
+ update?: boolean,
2808
+ ): Promise<string[]> {
2809
+ if (Connection.isWeb()) {
2810
+ return Promise.reject('Allowed only in admin');
2811
+ }
2812
+ if (!host.startsWith('system.host.')) {
2813
+ host = `system.host.${host}`;
2814
+ }
2815
+
2816
+ if (!update && this._promises[`IPs_${host}`]) {
2817
+ return this._promises[`IPs_${host}`] as Promise<string[]>;
2818
+ }
2819
+ this._promises[`IPs_${host}`] = this.getObject(host)
2820
+ .then(obj => (obj?.common ? obj.common.address || [] : []));
2821
+
2822
+ return this._promises[`IPs_${host}`] as Promise<string[]>;
2823
+ }
2824
+
2825
+ /**
2826
+ * Get the IP addresses with interface names of the given host or find host by IP.
2827
+ */
2828
+ getHostByIp(
2829
+ ipOrHostName: string,
2830
+ /** Force update. */
2831
+ update?: boolean,
2832
+ ): Promise<{ name: string; address: string; family: 'ipv4' | 'ipv6' }[]> {
2833
+ if (Connection.isWeb()) {
2834
+ return Promise.reject('Allowed only in admin');
2835
+ }
2836
+ if (ipOrHostName.startsWith('system.host.')) {
2837
+ ipOrHostName = ipOrHostName.replace(/^system\.host\./, '');
2838
+ }
2839
+
2840
+ if (!update && this._promises[`rIPs_${ipOrHostName}`]) {
2841
+ return this._promises[`rIPs_${ipOrHostName}`] as Promise<{ name: string; address: string; family: 'ipv4' | 'ipv6' }[]>;
2842
+ }
2843
+ this._promises[`rIPs_${ipOrHostName}`] = new Promise(resolve => {
2844
+ this._socket.emit('getHostByIp', ipOrHostName, (ip: string, host: any) => {
2845
+ const IPs4: {
2846
+ name: string;
2847
+ address: string;
2848
+ family: 'ipv4' | 'ipv6';
2849
+ }[] = [{ name: '[IPv4] 0.0.0.0 - Listen on all IPs', address: '0.0.0.0', family: 'ipv4' }];
2850
+ const IPs6: {
2851
+ name: string;
2852
+ address: string;
2853
+ family: 'ipv4' | 'ipv6';
2854
+ }[] = [{ name: '[IPv6] :: - Listen on all IPs', address: '::', family: 'ipv6' }];
2855
+ if (host?.native?.hardware?.networkInterfaces) {
2856
+ for (const eth in host.native.hardware.networkInterfaces) {
2857
+ if (!Object.prototype.hasOwnProperty.call(host.native.hardware.networkInterfaces, eth)) {
2858
+ continue;
2859
+ }
2860
+ for (let num = 0; num < host.native.hardware.networkInterfaces[eth].length; num++) {
2861
+ if (host.native.hardware.networkInterfaces[eth][num].family !== 'IPv6') {
2862
+ IPs4.push({
2863
+ name: `[${host.native.hardware.networkInterfaces[eth][num].family}] ${host.native.hardware.networkInterfaces[eth][num].address} - ${eth}`,
2864
+ address: host.native.hardware.networkInterfaces[eth][num].address,
2865
+ family: 'ipv4',
2866
+ });
2867
+ } else {
2868
+ IPs6.push({
2869
+ name: `[${host.native.hardware.networkInterfaces[eth][num].family}] ${host.native.hardware.networkInterfaces[eth][num].address} - ${eth}`,
2870
+ address: host.native.hardware.networkInterfaces[eth][num].address,
2871
+ family: 'ipv6',
2872
+ });
2873
+ }
2874
+ }
2875
+ }
2876
+ }
2877
+ for (let i = 0; i < IPs6.length; i++) {
2878
+ IPs4.push(IPs6[i]);
2879
+ }
2880
+ resolve(IPs4);
2881
+ });
2882
+ });
2883
+
2884
+ return this._promises[`rIPs_${ipOrHostName}`] as Promise<{ name: string; address: string; family: 'ipv4' | 'ipv6' }[]>;
2885
+ }
2886
+
2887
+ /**
2888
+ * Encrypt a text
2889
+ */
2890
+ encrypt(text: string): Promise<string> {
2891
+ if (Connection.isWeb()) {
2892
+ return Promise.reject('Allowed only in admin');
2893
+ }
2894
+ return new Promise((resolve, reject) => {
2895
+ this._socket.emit('encrypt', text, (err: string | null, _text: string) =>
2896
+ (err ? reject(err) : resolve(_text)));
2897
+ });
2898
+ }
2899
+
2900
+ /**
2901
+ * Decrypt a text
2902
+ */
2903
+ decrypt(encryptedText: string): Promise<string> {
2904
+ if (Connection.isWeb()) {
2905
+ return Promise.reject('Allowed only in admin');
2906
+ }
2907
+ return new Promise((resolve, reject) => {
2908
+ this._socket.emit('decrypt', encryptedText, (err: string | null, text: string) =>
2909
+ (err ? reject(err) : resolve(text)));
2910
+ });
2911
+ }
2912
+
2913
+ /**
2914
+ * Gets the version.
2915
+ * @returns {Promise<{version: string; serverName: string}>}
2916
+ */
2917
+ getVersion(update?: boolean): Promise<{ version: string; serverName: string }> {
2918
+ if (update && this._promises.version) {
2919
+ delete this._promises.version;
2920
+ }
2921
+
2922
+ this._promises.version = this._promises.version || new Promise((resolve, reject) => {
2923
+ this._socket.emit('getVersion', (err: string | null, version: string, serverName: string) => {
2924
+ // support of old socket.io
2925
+ if (err && !version && typeof err === 'string' && err.match(/\d+\.\d+\.\d+/)) {
2926
+ resolve({ version: err, serverName: 'socketio' });
2927
+ } else {
2928
+ err ? reject(err) : resolve({ version, serverName });
2929
+ }
2930
+ });
2931
+ });
2932
+
2933
+ return this._promises.version;
2934
+ }
2935
+
2936
+ /**
2937
+ * Gets the web server name.
2938
+ * @returns {Promise<string>}
2939
+ */
2940
+ getWebServerName(): Promise<string> {
2941
+ this._promises.webName = this._promises.webName || new Promise((resolve, reject) => {
2942
+ this._socket.emit('getAdapterName', (err: string | null, name: string) =>
2943
+ (err ? reject(err) : resolve(name)));
2944
+ });
2945
+
2946
+ return this._promises.webName;
2947
+ }
2948
+
2949
+ /**
2950
+ * Gets the admin version.
2951
+ * @deprecated use getVersion()
2952
+ */
2953
+ getAdminVersion(): Promise<{ version: string; serverName: string }> {
2954
+ console.log('Deprecated: use getVersion');
2955
+ return this.getVersion();
2956
+ }
2957
+
2958
+ /**
2959
+ * Change access rights for file
2960
+ */
2961
+ chmodFile(
2962
+ /** adapter name */
2963
+ adapter: string,
2964
+ /** file name with a full path. It could be like vis.0/* */
2965
+ filename: string,
2966
+ /** like {mode: 0x644} */
2967
+ options?: { mode: number },
2968
+ ): Promise<{ entries: ioBroker.ChownFileResult[]; id: string }> {
2969
+ if (Connection.isWeb()) {
2970
+ return Promise.reject('Allowed only in admin');
2971
+ }
2972
+ if (!this.connected) {
2973
+ return Promise.reject(NOT_CONNECTED);
2974
+ }
2975
+
2976
+ return new Promise((resolve, reject) => {
2977
+ this._socket.emit('chmodFile', adapter, filename, options, (err: string | null, entries: ioBroker.ChownFileResult[], id: string) =>
2978
+ (err ? reject(err) : resolve({ entries, id })));
2979
+ });
2980
+ }
2981
+
2982
+ /**
2983
+ * Change an owner or/and owner group for file
2984
+ */
2985
+ chownFile(
2986
+ /** adapter name */
2987
+ adapter: string,
2988
+ /** file name with a full path. It could be like vis.0/* */
2989
+ fileName: string,
2990
+ options: { owner?: string; ownerGroup?: string },
2991
+ ): Promise<{ entries: ioBroker.ChownFileResult[]; id: string }> {
2992
+ if (Connection.isWeb()) {
2993
+ return Promise.reject('Allowed only in admin');
2994
+ }
2995
+ if (!this.connected) {
2996
+ return Promise.reject(NOT_CONNECTED);
2997
+ }
2998
+
2999
+ return new Promise((resolve, reject) => {
3000
+ this._socket.emit('chownFile', adapter, fileName, options, (err: string | null, entries: ioBroker.ChownFileResult[], id: string) =>
3001
+ (err ? reject(err) : resolve({ entries, id })));
3002
+ });
3003
+ }
3004
+
3005
+ /**
3006
+ * Check if the file exists
3007
+ */
3008
+ fileExists(
3009
+ /** adapter name */
3010
+ adapter: string,
3011
+ /** file name with a full path. It could be like vis.0/* */
3012
+ fileName: string,
3013
+ ): Promise<boolean> {
3014
+ if (!this.connected) {
3015
+ return Promise.reject(NOT_CONNECTED);
3016
+ }
3017
+
3018
+ return new Promise((resolve, reject) => {
3019
+ this._socket.emit('fileExists', adapter, fileName, (err: string | null, exists: boolean) =>
3020
+ (err ? reject(err) : resolve(exists)));
3021
+ });
3022
+ }
3023
+
3024
+ /**
3025
+ * Get the alarm notifications from a host (only for admin connection).
3026
+ * @returns {Promise<any>}
3027
+ */
3028
+ getNotifications(
3029
+ host: string,
3030
+ category?: string,
3031
+ ): Promise<FilteredNotificationInformation> {
3032
+ if (Connection.isWeb()) {
3033
+ return Promise.reject('Allowed only in admin');
3034
+ }
3035
+
3036
+ if (!this.connected) {
3037
+ return Promise.reject(NOT_CONNECTED);
3038
+ }
3039
+ return new Promise(resolve => {
3040
+ this._socket.emit('sendToHost', host, 'getNotifications', { category }, (notifications: FilteredNotificationInformation) =>
3041
+ resolve(notifications));
3042
+ });
3043
+ }
3044
+
3045
+ /**
3046
+ * Clear the alarm notifications on a host (only for admin connection).
3047
+ * @param {string} host
3048
+ * @param {string} [category] - optional
3049
+ * @returns {Promise<any>}
3050
+ */
3051
+ clearNotifications(
3052
+ host: string,
3053
+ category?: string,
3054
+ ): Promise<{ result: 'ok' }> {
3055
+ if (Connection.isWeb()) {
3056
+ return Promise.reject('Allowed only in admin');
3057
+ }
3058
+
3059
+ if (!this.connected) {
3060
+ return Promise.reject(NOT_CONNECTED);
3061
+ }
3062
+ return new Promise(resolve => {
3063
+ this._socket.emit('sendToHost', host, 'clearNotifications', { category }, (result: { result: 'ok' }) =>
3064
+ resolve(result));
3065
+ });
3066
+ }
3067
+
3068
+ /**
3069
+ * Read if only easy mode is allowed (only for admin connection).
3070
+ */
3071
+ getIsEasyModeStrict(): Promise<boolean> {
3072
+ if (Connection.isWeb()) {
3073
+ return Promise.reject('Allowed only in admin');
3074
+ }
3075
+ if (!this.connected) {
3076
+ return Promise.reject(NOT_CONNECTED);
3077
+ }
3078
+ return new Promise((resolve, reject) => {
3079
+ this._socket.emit('getIsEasyModeStrict', (error: null | string, isStrict: boolean) =>
3080
+ (error ? reject(error) : resolve(isStrict)));
3081
+ });
3082
+ }
3083
+
3084
+ /**
3085
+ * Read easy mode configuration (only for admin connection).
3086
+ * @returns {Promise<any>}
3087
+ */
3088
+ getEasyMode(): Promise<any> {
3089
+ if (Connection.isWeb()) {
3090
+ return Promise.reject('Allowed only in admin');
3091
+ }
3092
+ if (!this.connected) {
3093
+ return Promise.reject(NOT_CONNECTED);
3094
+ }
3095
+ return new Promise((resolve, reject) => {
3096
+ this._socket.emit('getEasyMode', (error: string | null, config: any) =>
3097
+ (error ? reject(error) : resolve(config)));
3098
+ });
3099
+ }
3100
+
3101
+ /**
3102
+ * Read current user
3103
+ * @returns {Promise<string>}
3104
+ */
3105
+ getCurrentUser(): Promise<string> {
3106
+ if (!this.connected) {
3107
+ return Promise.reject(NOT_CONNECTED);
3108
+ }
3109
+
3110
+ return new Promise(resolve => {
3111
+ this._socket.emit('authEnabled', (isSecure: boolean, user: string) =>
3112
+ resolve(user));
3113
+ });
3114
+ }
3115
+
3116
+ getCurrentSession(cmdTimeout?: number) {
3117
+ if (!this.connected) {
3118
+ return Promise.reject(NOT_CONNECTED);
3119
+ }
3120
+
3121
+ return new Promise((resolve, reject) => {
3122
+ const controller = new AbortController();
3123
+
3124
+ let timeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
3125
+ if (timeout) {
3126
+ timeout = null;
3127
+ controller.abort();
3128
+ reject('getCurrentSession timeout');
3129
+ }
3130
+ }, cmdTimeout || 5000);
3131
+
3132
+ fetch('./session', { signal: controller.signal })
3133
+ .then(res => res.json())
3134
+ .then(json => {
3135
+ if (timeout) {
3136
+ clearTimeout(timeout);
3137
+ timeout = null;
3138
+ resolve(json);
3139
+ }
3140
+ })
3141
+ .catch(e =>
3142
+ reject(`getCurrentSession: ${e}`));
3143
+ });
3144
+ }
3145
+
3146
+ /**
3147
+ * Read adapter ratings
3148
+ * @returns {Promise<any>}
3149
+ */
3150
+ getRatings(update?: boolean): Promise<any> {
3151
+ if (Connection.isWeb()) {
3152
+ return Promise.reject('Allowed only in admin');
3153
+ }
3154
+ if (!this.connected) {
3155
+ return Promise.reject(NOT_CONNECTED);
3156
+ }
3157
+ return new Promise((resolve, reject) => {
3158
+ this._socket.emit('getRatings', update, (err: string | null, ratings: any) =>
3159
+ (err ? reject(err) : resolve(ratings)));
3160
+ });
3161
+ }
3162
+
3163
+ /**
3164
+ * Read current web, socketio or admin namespace, like admin.0
3165
+ */
3166
+ getCurrentInstance(): Promise<string> {
3167
+ if (!this.connected) {
3168
+ return Promise.reject(NOT_CONNECTED);
3169
+ }
3170
+
3171
+ this._promises.currentInstance = this._promises.currentInstance ||
3172
+ new Promise((resolve, reject) => {
3173
+ this._socket.emit('getCurrentInstance', (err: string | null, namespace: string) =>
3174
+ (err ? reject(err) : resolve(namespace)));
3175
+ });
3176
+
3177
+ return this._promises.currentInstance;
3178
+ }
3179
+
3180
+ // returns very optimized information for adapters to minimize a connection load
3181
+ getCompactAdapters(update?: boolean): Promise<Record<string, ioBroker.AdapterObject>> {
3182
+ if (Connection.isWeb()) {
3183
+ return Promise.reject('Allowed only in admin');
3184
+ }
3185
+ if (!update && this._promises.compactAdapters) {
3186
+ return this._promises.compactAdapters;
3187
+ }
3188
+ if (!this.connected) {
3189
+ return Promise.reject(NOT_CONNECTED);
3190
+ }
3191
+ this._promises.compactAdapters = new Promise((resolve, reject) => {
3192
+ this._socket.emit('getCompactAdapters', (err: string | null, adapters: Record<string, ioBroker.AdapterObject>) =>
3193
+ (err ? reject(err) : resolve(adapters)));
3194
+ });
3195
+
3196
+ return this._promises.compactAdapters;
3197
+ }
3198
+
3199
+ getAdaptersResetCache(adapter?: string): void {
3200
+ adapter = adapter || '';
3201
+ delete this._promises.compactAdapters;
3202
+ delete this._promises[`adapter_${adapter}`];
3203
+ }
3204
+
3205
+ // returns very optimized information for adapters to minimize a connection load
3206
+ getCompactInstances(update?: boolean): Promise<Record<string, ioBroker.InstanceObject>> {
3207
+ if (Connection.isWeb()) {
3208
+ return Promise.reject('Allowed only in admin');
3209
+ }
3210
+ if (!update && this._promises.compactInstances) {
3211
+ return this._promises.compactInstances;
3212
+ }
3213
+ if (!this.connected) {
3214
+ return Promise.reject(NOT_CONNECTED);
3215
+ }
3216
+
3217
+ this._promises.compactInstances = new Promise((resolve, reject) => {
3218
+ this._socket.emit('getCompactInstances', (err: string | null, instances: Record<string, ioBroker.InstanceObject>) =>
3219
+ (err ? reject(err) : resolve(instances)));
3220
+ });
3221
+
3222
+ return this._promises.compactInstances;
3223
+ }
3224
+
3225
+ getAdapternInstancesResetCache(adapter?: string) {
3226
+ adapter = adapter || '';
3227
+ delete this._promises.compactInstances;
3228
+ delete this._promises[`instances_${adapter}`];
3229
+ }
3230
+
3231
+ /**
3232
+ * Returns very optimized information for adapters to minimize a connection load.
3233
+ * Reads only version of installed adapter
3234
+ */
3235
+ getCompactInstalled(
3236
+ host: string,
3237
+ update?: boolean,
3238
+ cmdTimeout?: number,
3239
+ ): Promise<Record<string, ioBroker.AdapterObject>> {
3240
+ if (Connection.isWeb()) {
3241
+ return Promise.reject('Allowed only in admin');
3242
+ }
3243
+
3244
+ this._promises.installedCompact = this._promises.installedCompact || {};
3245
+
3246
+ if (!update && this._promises.installedCompact[host]) {
3247
+ return this._promises.installedCompact[host] as Promise<Record<string, ioBroker.AdapterObject>>;
3248
+ }
3249
+
3250
+ if (!this.connected) {
3251
+ return Promise.reject(NOT_CONNECTED);
3252
+ }
3253
+
3254
+ if (!host.startsWith('system.host.')) {
3255
+ host += `system.host.${host}`;
3256
+ }
3257
+
3258
+ this._promises.installedCompact[host] = new Promise((resolve, reject) => {
3259
+ let timeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
3260
+ if (timeout) {
3261
+ timeout = null;
3262
+ reject('getCompactInstalled timeout');
3263
+ }
3264
+ }, cmdTimeout || this.props.cmdTimeout);
3265
+
3266
+ this._socket.emit('getCompactInstalled', host, (data: Record<string, ioBroker.AdapterObject> | string) => {
3267
+ if (timeout) {
3268
+ clearTimeout(timeout);
3269
+ timeout = null;
3270
+ if (data === PERMISSION_ERROR) {
3271
+ reject('May not read "getCompactInstalled"');
3272
+ } else if (!data || typeof data !== 'object') {
3273
+ reject('Cannot read "getCompactInstalled"');
3274
+ } else {
3275
+ resolve(data);
3276
+ }
3277
+ }
3278
+ });
3279
+ });
3280
+
3281
+ return this._promises.installedCompact[host] as Promise<Record<string, ioBroker.AdapterObject>>;
3282
+ }
3283
+
3284
+ // returns very optimized information for adapters to minimize a connection load.
3285
+ // reads only version of installed adapter
3286
+ getCompactSystemRepositories(
3287
+ update?: boolean,
3288
+ cmdTimeout?: number,
3289
+ ): Promise<ioBroker.Object> {
3290
+ if (Connection.isWeb()) {
3291
+ return Promise.reject('Allowed only in admin');
3292
+ }
3293
+
3294
+ if (!update && this._promises.getCompactSystemRepositories) {
3295
+ return this._promises.getCompactSystemRepositories as Promise<ioBroker.Object>;
3296
+ }
3297
+
3298
+ if (!this.connected) {
3299
+ return Promise.reject(NOT_CONNECTED);
3300
+ }
3301
+
3302
+ this._promises.getCompactSystemRepositories = new Promise((resolve, reject) => {
3303
+ let timeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
3304
+ if (timeout) {
3305
+ timeout = null;
3306
+ reject('getCompactSystemRepositories timeout');
3307
+ }
3308
+ }, cmdTimeout || this.props.cmdTimeout);
3309
+
3310
+ this._socket.emit('getCompactSystemRepositories', (data: ioBroker.Object | string) => {
3311
+ if (timeout) {
3312
+ clearTimeout(timeout);
3313
+ timeout = null;
3314
+ if (data === PERMISSION_ERROR) {
3315
+ reject('May not read "getCompactSystemRepositories"');
3316
+ } else if (!data || typeof data !== 'object') {
3317
+ reject('Cannot read "getCompactSystemRepositories"');
3318
+ } else {
3319
+ resolve(data);
3320
+ }
3321
+ }
3322
+ });
3323
+ });
3324
+
3325
+ return this._promises.getCompactSystemRepositories;
3326
+ }
3327
+
3328
+ // returns very optimized information for adapters to minimize a connection load
3329
+ getCompactSystemConfig(update?: boolean): Promise<ioBroker.SystemConfigObject> {
3330
+ if (!update && this._promises.systemConfigPromise) {
3331
+ return this._promises.systemConfigPromise;
3332
+ }
3333
+
3334
+ if (!this.connected) {
3335
+ return Promise.reject(NOT_CONNECTED);
3336
+ }
3337
+
3338
+ this._promises.systemConfigPromise = new Promise((resolve, reject) => {
3339
+ this._socket.emit('getCompactSystemConfig', (err: string | null, systemConfig: ioBroker.SystemConfigObject) =>
3340
+ (err ? reject(err) : resolve(systemConfig)));
3341
+ });
3342
+
3343
+ return this._promises.systemConfigPromise;
3344
+ }
3345
+
3346
+ /**
3347
+ * Get the repository in compact form (only version and icon).
3348
+ * @param {string} host
3349
+ * @param {boolean} [update] Force update.
3350
+ * @param {number} [timeoutMs] timeout in ms.
3351
+ * @returns {Promise<any>}
3352
+ */
3353
+ getCompactRepository(
3354
+ host: string,
3355
+ update?: boolean,
3356
+ timeoutMs?: number,
3357
+ ): Promise<Record<string, { version: string; icon: string }>> {
3358
+ if (Connection.isWeb()) {
3359
+ return Promise.reject('Allowed only in admin');
3360
+ }
3361
+
3362
+ if (!update && this._promises.repoCompact) {
3363
+ return this._promises.repoCompact;
3364
+ }
3365
+
3366
+ if (!this.connected) {
3367
+ return Promise.reject(NOT_CONNECTED);
3368
+ }
3369
+
3370
+ if (!host.startsWith('system.host.')) {
3371
+ host += `system.host.${host}`;
3372
+ }
3373
+
3374
+ this._promises.repoCompact = new Promise((resolve, reject) => {
3375
+ let timeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
3376
+ if (timeout) {
3377
+ timeout = null;
3378
+ reject('getCompactRepository timeout');
3379
+ }
3380
+ }, timeoutMs || this.props.cmdTimeout);
3381
+
3382
+ this._socket.emit('getCompactRepository', host, (data: Record<string, { version: string; icon: string }> | string) => {
3383
+ if (timeout) {
3384
+ clearTimeout(timeout);
3385
+ timeout = null;
3386
+ if (data === PERMISSION_ERROR) {
3387
+ reject('May not read "getCompactRepository"');
3388
+ } else if (!data) {
3389
+ reject('Cannot read "getCompactRepository"');
3390
+ } else {
3391
+ resolve(data);
3392
+ }
3393
+ }
3394
+ });
3395
+ });
3396
+
3397
+ return this._promises.repoCompact;
3398
+ }
3399
+
3400
+ getInstalledResetCache() {
3401
+ delete this._promises.repoCompact;
3402
+ delete this._promises.repo;
3403
+ }
3404
+
3405
+ /**
3406
+ * Get the list of all hosts in compact form (only _id, common.name, common.icon, common.color, native.hardware.networkInterfaces)
3407
+ */
3408
+ getCompactHosts(update?: boolean): Promise<ioBroker.HostObject[]> {
3409
+ if (Connection.isWeb()) {
3410
+ return Promise.reject('Allowed only in admin');
3411
+ }
3412
+ if (!update && this._promises.hostsCompact) {
3413
+ return this._promises.hostsCompact;
3414
+ }
3415
+
3416
+ if (!this.connected) {
3417
+ return Promise.reject(NOT_CONNECTED);
3418
+ }
3419
+
3420
+ this._promises.hostsCompact = new Promise((resolve, reject) => {
3421
+ this._socket.emit('getCompactHosts', (err: string | null, hosts: ioBroker.HostObject[]) =>
3422
+ (err ? reject(err) : resolve(hosts)));
3423
+ });
3424
+
3425
+ return this._promises.hostsCompact;
3426
+ }
3427
+
3428
+ /**
3429
+ * Get uuid
3430
+ */
3431
+ getUuid(): Promise<string | undefined> {
3432
+ if (this._promises.uuid) {
3433
+ return this._promises.uuid;
3434
+ }
3435
+
3436
+ if (!this.connected) {
3437
+ return Promise.reject(NOT_CONNECTED);
3438
+ }
3439
+
3440
+ this._promises.uuid = this.getObject('system.meta.uuid')
3441
+ .then(obj => obj?.native?.uuid);
3442
+
3443
+ return this._promises.uuid;
3444
+ }
3445
+
3446
+ /**
3447
+ * Subscribe on instance message
3448
+ * @param {string} [targetInstance] instance, like 'cameras.0'
3449
+ * @param {string} [messageType] message type like 'startCamera/cam3'
3450
+ * @param {object} [data] optional data object
3451
+ * @param {function} [callback] message handler
3452
+ * @returns {Promise<null>}
3453
+ */
3454
+ subscribeOnInstance(
3455
+ /** instance, like 'cameras.0' */
3456
+ targetInstance: string,
3457
+ /** message type like 'startCamera/cam3' */
3458
+ messageType: string,
3459
+ data: any,
3460
+ /** message handler. Could be null if all callbacks for this messageType should be unsubscribed */
3461
+ callback: (_data: Record<string, any>, sourceInstance: string, _messageType: string) => void,
3462
+ ) {
3463
+ if (!this.connected) {
3464
+ return Promise.reject(NOT_CONNECTED);
3465
+ }
3466
+ return new Promise((resolve, reject) => {
3467
+ this._socket.emit(
3468
+ 'clientSubscribe',
3469
+ targetInstance,
3470
+ messageType,
3471
+ data,
3472
+ (err: string | null, result: { error?: string; accepted?: boolean; heartbeat?: number }) => {
3473
+ if (err) {
3474
+ reject(err);
3475
+ } else if (result && result.error) {
3476
+ reject(result.error);
3477
+ } else {
3478
+ if (!targetInstance.startsWith('system.adapter.')) {
3479
+ targetInstance = `system.adapter.${targetInstance}`;
3480
+ }
3481
+ // save callback
3482
+ this._instanceSubscriptions[targetInstance] = this._instanceSubscriptions[targetInstance] || [];
3483
+ if (!this._instanceSubscriptions[targetInstance].find(sub =>
3484
+ sub.messageType === messageType &&
3485
+ sub.callback === callback)
3486
+ ) {
3487
+ this._instanceSubscriptions[targetInstance].push({
3488
+ messageType,
3489
+ callback,
3490
+ });
3491
+ }
3492
+ resolve(result);
3493
+ }
3494
+ },
3495
+ );
3496
+ });
3497
+ }
3498
+
3499
+ /**
3500
+ * Unsubscribe from instance message
3501
+ */
3502
+ unsubscribeFromInstance(
3503
+ /** instance, like 'cameras.0' */
3504
+ targetInstance: string,
3505
+ /** message type like 'startCamera/cam3' */
3506
+ messageType?: string,
3507
+ /** message handler. Could be null if all callbacks for this messageType should be unsubscribed */
3508
+ callback?: (data: Record<string, any>, sourceInstance: string, _messageType: string) => void,
3509
+ ): Promise<boolean> {
3510
+ if (!targetInstance.startsWith('system.adapter.')) {
3511
+ targetInstance = `system.adapter.${targetInstance}`;
3512
+ }
3513
+ let deleted;
3514
+ const promiseResults: Promise<boolean>[] = [];
3515
+ do {
3516
+ deleted = false;
3517
+ const index = this._instanceSubscriptions[targetInstance]?.findIndex(sub =>
3518
+ (!messageType || sub.messageType === messageType) && (!callback || sub.callback === callback));
3519
+
3520
+ if (index !== undefined && index !== null && index !== -1) {
3521
+ deleted = true;
3522
+ // remember messageType
3523
+ const _messageType =
3524
+ this._instanceSubscriptions[targetInstance][index].messageType;
3525
+
3526
+ this._instanceSubscriptions[targetInstance].splice(index, 1);
3527
+ if (!this._instanceSubscriptions[targetInstance].length) {
3528
+ delete this._instanceSubscriptions[targetInstance];
3529
+ }
3530
+
3531
+ // try to find another subscription for this instance and messageType
3532
+ const found = this._instanceSubscriptions[targetInstance] &&
3533
+ this._instanceSubscriptions[targetInstance].find(sub => sub.messageType === _messageType);
3534
+
3535
+ if (!found) {
3536
+ promiseResults.push(new Promise((resolve, reject) => {
3537
+ this._socket.emit('clientUnsubscribe', targetInstance, messageType, (err: string | null, wasSubscribed: boolean) => {
3538
+ if (err) {
3539
+ reject(err);
3540
+ } else {
3541
+ resolve(wasSubscribed);
3542
+ }
3543
+ });
3544
+ }));
3545
+ }
3546
+ }
3547
+ } while (deleted && (!callback || !messageType));
3548
+
3549
+ if (promiseResults.length) {
3550
+ return Promise.all(promiseResults)
3551
+ .then((results: boolean[]) => results.find(result => result) || false);
3552
+ }
3553
+
3554
+ return Promise.resolve(false);
3555
+ }
3556
+
3557
+ /**
3558
+ * Send log to ioBroker log
3559
+ */
3560
+ log(text: string, level?: 'info' | 'debug' | 'warn' | 'error' | 'silly'): void {
3561
+ text && this._socket.emit('log', text, level || 'debug');
3562
+ }
3563
+
3564
+ /**
3565
+ * Logout current user
3566
+ * @returns {Promise<null>}
3567
+ */
3568
+ logout(): Promise<void> {
3569
+ if (!this.connected) {
3570
+ return Promise.reject(NOT_CONNECTED);
3571
+ }
3572
+
3573
+ return new Promise((resolve, reject) => {
3574
+ this._socket.emit('logout', (err: string | null) =>
3575
+ (err ? reject(err) : resolve()));
3576
+ });
3577
+ }
3578
+
3579
+ /**
3580
+ * This is a special method for vis.
3581
+ * It is used to not send to server the changes about "nothing_selected" state
3582
+ * @param id The state that has to be ignored by communication
3583
+ */
3584
+ setStateToIgnore(id?: string | null) {
3585
+ this.ignoreState = id || '';
3586
+ }
3587
+ }
3588
+
3589
+ export default Connection;