@iobroker/adapter-react-v5 6.1.2 → 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.
- package/README.md +4 -1
- package/craco-module-federation.js +1 -1
- package/package.json +1 -1
- package/src/AdminConnection.tsx +3 -0
- package/src/Components/404.tsx +121 -0
- package/src/Components/ColorPicker.tsx +315 -0
- package/src/Components/ComplexCron.tsx +507 -0
- package/src/Components/CopyToClipboard.tsx +165 -0
- package/src/Components/CustomModal.tsx +163 -0
- package/src/Components/FileBrowser.tsx +2394 -0
- package/src/Components/FileViewer.tsx +384 -0
- package/src/Components/Icon.tsx +210 -0
- package/src/Components/IconPicker.tsx +149 -0
- package/src/Components/IconSelector.tsx +2202 -0
- package/src/Components/Image.tsx +176 -0
- package/src/Components/Loader.tsx +304 -0
- package/src/Components/Logo.tsx +166 -0
- package/src/Components/MDUtils.tsx +100 -0
- package/src/Components/ObjectBrowser.tsx +7915 -0
- package/src/Components/Router.tsx +90 -0
- package/src/Components/SaveCloseButtons.tsx +113 -0
- package/src/Components/Schedule.tsx +1724 -0
- package/src/Components/SelectWithIcon.tsx +197 -0
- package/src/Components/TabContainer.tsx +55 -0
- package/src/Components/TabContent.tsx +37 -0
- package/src/Components/TabHeader.tsx +19 -0
- package/src/Components/TableResize.tsx +259 -0
- package/src/Components/TextWithIcon.tsx +148 -0
- package/src/Components/ToggleThemeMenu.tsx +34 -0
- package/src/Components/TreeTable.tsx +919 -0
- package/src/Components/UploadImage.tsx +599 -0
- package/src/Components/Utils.tsx +1794 -0
- package/src/Components/loader.css +222 -0
- package/src/Components/withWidth.tsx +21 -0
- package/src/Connection.tsx +7 -0
- package/src/Dialogs/ComplexCron.tsx +129 -0
- package/src/Dialogs/Confirm.tsx +162 -0
- package/src/Dialogs/Cron.tsx +182 -0
- package/src/Dialogs/Error.tsx +72 -0
- package/src/Dialogs/Message.tsx +71 -0
- package/src/Dialogs/SelectFile.tsx +270 -0
- package/src/Dialogs/SelectID.tsx +298 -0
- package/src/Dialogs/SimpleCron.tsx +100 -0
- package/src/Dialogs/TextInput.tsx +107 -0
- package/src/GenericApp.tsx +976 -0
- package/src/LegacyConnection.tsx +3589 -0
- package/src/Prompt.tsx +20 -0
- package/src/Theme.tsx +479 -0
- package/src/icons/IconAdapter.tsx +20 -0
- package/src/icons/IconAlias.tsx +20 -0
- package/src/icons/IconChannel.tsx +21 -0
- package/src/icons/IconClearFilter.tsx +22 -0
- package/src/icons/IconClosed.tsx +17 -0
- package/src/icons/IconCopy.tsx +16 -0
- package/src/icons/IconDevice.tsx +27 -0
- package/src/icons/IconDocument.tsx +17 -0
- package/src/icons/IconDocumentReadOnly.tsx +18 -0
- package/src/icons/IconExpert.tsx +18 -0
- package/src/icons/IconFx.tsx +36 -0
- package/src/icons/IconInstance.tsx +20 -0
- package/src/icons/IconLogout.tsx +30 -0
- package/src/icons/IconNoIcon.tsx +19 -0
- package/src/icons/IconOpen.tsx +17 -0
- package/src/icons/IconProps.tsx +15 -0
- package/src/icons/IconState.tsx +17 -0
- 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;
|