@iobroker/socket-classes 1.6.2 → 2.0.2
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/LICENSE +1 -1
- package/README.md +791 -775
- package/dist/index.d.ts +8 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/passportSocket.d.ts +34 -0
- package/dist/lib/passportSocket.js +91 -0
- package/dist/lib/passportSocket.js.map +1 -0
- package/dist/lib/socketAdmin.d.ts +42 -0
- package/dist/lib/socketAdmin.js +246 -0
- package/dist/lib/socketAdmin.js.map +1 -0
- package/dist/lib/socketCommands.d.ts +83 -0
- package/dist/lib/socketCommands.js +2015 -0
- package/dist/lib/socketCommands.js.map +1 -0
- package/dist/lib/socketCommandsAdmin.d.ts +145 -0
- package/dist/lib/socketCommandsAdmin.js +1586 -0
- package/dist/lib/socketCommandsAdmin.js.map +1 -0
- package/dist/lib/socketCommon.d.ts +143 -0
- package/dist/lib/socketCommon.js +521 -0
- package/dist/lib/socketCommon.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/package.json +23 -17
- package/index.js +0 -19
- package/lib/passportSocket.js +0 -105
- package/lib/socketAdmin.js +0 -203
- package/lib/socketCommands.js +0 -1886
- package/lib/socketCommandsAdmin.js +0 -1492
- package/lib/socketCommon.js +0 -480
|
@@ -0,0 +1,2015 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SocketCommands = exports.COMMANDS_PERMISSIONS = void 0;
|
|
4
|
+
const adapter_core_1 = require("@iobroker/adapter-core"); // Get common adapter utils
|
|
5
|
+
exports.COMMANDS_PERMISSIONS = {
|
|
6
|
+
getObject: { type: 'object', operation: 'read' },
|
|
7
|
+
getObjects: { type: 'object', operation: 'list' },
|
|
8
|
+
getObjectView: { type: 'object', operation: 'list' },
|
|
9
|
+
setObject: { type: 'object', operation: 'write' },
|
|
10
|
+
requireLog: { type: 'object', operation: 'write' }, // just mapping to some command
|
|
11
|
+
delObject: { type: 'object', operation: 'delete' },
|
|
12
|
+
extendObject: { type: 'object', operation: 'write' },
|
|
13
|
+
getHostByIp: { type: 'object', operation: 'list' },
|
|
14
|
+
subscribeObjects: { type: 'object', operation: 'read' },
|
|
15
|
+
unsubscribeObjects: { type: 'object', operation: 'read' },
|
|
16
|
+
getStates: { type: 'state', operation: 'list' },
|
|
17
|
+
getState: { type: 'state', operation: 'read' },
|
|
18
|
+
setState: { type: 'state', operation: 'write' },
|
|
19
|
+
delState: { type: 'state', operation: 'delete' },
|
|
20
|
+
createState: { type: 'state', operation: 'create' },
|
|
21
|
+
subscribe: { type: 'state', operation: 'read' },
|
|
22
|
+
unsubscribe: { type: 'state', operation: 'read' },
|
|
23
|
+
getStateHistory: { type: 'state', operation: 'read' },
|
|
24
|
+
getVersion: { type: '', operation: '' },
|
|
25
|
+
getAdapterName: { type: '', operation: '' },
|
|
26
|
+
addUser: { type: 'users', operation: 'create' },
|
|
27
|
+
delUser: { type: 'users', operation: 'delete' },
|
|
28
|
+
addGroup: { type: 'users', operation: 'create' },
|
|
29
|
+
delGroup: { type: 'users', operation: 'delete' },
|
|
30
|
+
changePassword: { type: 'users', operation: 'write' },
|
|
31
|
+
httpGet: { type: 'other', operation: 'http' },
|
|
32
|
+
cmdExec: { type: 'other', operation: 'execute' },
|
|
33
|
+
sendTo: { type: 'other', operation: 'sendto' },
|
|
34
|
+
sendToHost: { type: 'other', operation: 'sendto' },
|
|
35
|
+
readLogs: { type: 'other', operation: 'execute' },
|
|
36
|
+
readDir: { type: 'file', operation: 'list' },
|
|
37
|
+
createFile: { type: 'file', operation: 'create' },
|
|
38
|
+
writeFile: { type: 'file', operation: 'write' },
|
|
39
|
+
readFile: { type: 'file', operation: 'read' },
|
|
40
|
+
fileExists: { type: 'file', operation: 'read' },
|
|
41
|
+
deleteFile: { type: 'file', operation: 'delete' },
|
|
42
|
+
readFile64: { type: 'file', operation: 'read' },
|
|
43
|
+
writeFile64: { type: 'file', operation: 'write' },
|
|
44
|
+
unlink: { type: 'file', operation: 'delete' },
|
|
45
|
+
rename: { type: 'file', operation: 'write' },
|
|
46
|
+
mkdir: { type: 'file', operation: 'write' },
|
|
47
|
+
chmodFile: { type: 'file', operation: 'write' },
|
|
48
|
+
chownFile: { type: 'file', operation: 'write' },
|
|
49
|
+
subscribeFiles: { type: 'file', operation: 'read' },
|
|
50
|
+
unsubscribeFiles: { type: 'file', operation: 'read' },
|
|
51
|
+
authEnabled: { type: '', operation: '' },
|
|
52
|
+
disconnect: { type: '', operation: '' },
|
|
53
|
+
listPermissions: { type: '', operation: '' },
|
|
54
|
+
getUserPermissions: { type: 'object', operation: 'read' },
|
|
55
|
+
};
|
|
56
|
+
const pattern2RegEx = adapter_core_1.commonTools.pattern2RegEx;
|
|
57
|
+
let axiosGet = null;
|
|
58
|
+
let zipFiles = null;
|
|
59
|
+
class SocketCommands {
|
|
60
|
+
static ERROR_PERMISSION = 'permissionError';
|
|
61
|
+
static COMMANDS_PERMISSIONS = exports.COMMANDS_PERMISSIONS;
|
|
62
|
+
adapter;
|
|
63
|
+
context;
|
|
64
|
+
commands = {};
|
|
65
|
+
subscribes = {};
|
|
66
|
+
#logEnabled = false;
|
|
67
|
+
#clientSubscribes = {};
|
|
68
|
+
#updateSession;
|
|
69
|
+
adapterName;
|
|
70
|
+
_sendToHost;
|
|
71
|
+
states;
|
|
72
|
+
constructor(adapter, updateSession, context) {
|
|
73
|
+
this.adapter = adapter;
|
|
74
|
+
this.#updateSession = updateSession;
|
|
75
|
+
this.context = context;
|
|
76
|
+
this.#updateSession ||= () => true;
|
|
77
|
+
this._sendToHost = null;
|
|
78
|
+
this.#initCommands();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Rename file or folder
|
|
82
|
+
*
|
|
83
|
+
* @param adapter Object ID
|
|
84
|
+
* @param oldName Old file name
|
|
85
|
+
* @param newName New file name
|
|
86
|
+
* @param options options { user?: string; }
|
|
87
|
+
*/
|
|
88
|
+
async #rename(adapter, oldName, newName, options) {
|
|
89
|
+
// read if it is a file or folder
|
|
90
|
+
try {
|
|
91
|
+
if (oldName.endsWith('/')) {
|
|
92
|
+
oldName = oldName.substring(0, oldName.length - 1);
|
|
93
|
+
}
|
|
94
|
+
if (newName.endsWith('/')) {
|
|
95
|
+
newName = newName.substring(0, newName.length - 1);
|
|
96
|
+
}
|
|
97
|
+
const files = await this.adapter.readDirAsync(adapter, oldName, options);
|
|
98
|
+
if (files?.length) {
|
|
99
|
+
for (let f = 0; f < files.length; f++) {
|
|
100
|
+
await this.#rename(adapter, `${oldName}/${files[f].file}`, `${newName}/${files[f].file}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error.message !== 'Not exists') {
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
// else ignore, because it is a file and not a folder
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
await this.adapter.renameAsync(adapter, oldName, newName, options);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (error.message !== 'Not exists') {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
// else ignore, because the folder cannot be deleted
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Delete file or folder
|
|
122
|
+
*
|
|
123
|
+
* @param adapter Object ID
|
|
124
|
+
* @param name File name
|
|
125
|
+
* @param options options { user?: string; }
|
|
126
|
+
*/
|
|
127
|
+
async #unlink(adapter, name, options) {
|
|
128
|
+
// read if it is a file or folder
|
|
129
|
+
try {
|
|
130
|
+
// remove trailing '/'
|
|
131
|
+
if (name.endsWith('/')) {
|
|
132
|
+
name = name.substring(0, name.length - 1);
|
|
133
|
+
}
|
|
134
|
+
const files = await this.adapter.readDirAsync(adapter, name, options);
|
|
135
|
+
if (files && files.length) {
|
|
136
|
+
for (let f = 0; f < files.length; f++) {
|
|
137
|
+
await this.#unlink(adapter, `${name}/${files[f].file}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
// ignore, because it is a file and not a folder
|
|
143
|
+
if (error.message !== 'Not exists') {
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
await this.adapter.unlinkAsync(adapter, name, options);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (error.message !== 'Not exists') {
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
// else ignore, because folder cannot be deleted
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Convert errors into strings and then call cb
|
|
159
|
+
*
|
|
160
|
+
* @param callback Callback function
|
|
161
|
+
* @param error Error
|
|
162
|
+
* @param args Arguments passed to callback
|
|
163
|
+
*/
|
|
164
|
+
static _fixCallback(callback, error, ...args) {
|
|
165
|
+
if (typeof callback !== 'function') {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (error instanceof Error) {
|
|
169
|
+
error = error.message;
|
|
170
|
+
}
|
|
171
|
+
callback(error, ...args);
|
|
172
|
+
}
|
|
173
|
+
_checkPermissions(socket, command, callback, ...args) {
|
|
174
|
+
const _command = command;
|
|
175
|
+
if (socket._acl?.user !== 'system.user.admin') {
|
|
176
|
+
// type: file, object, state, other
|
|
177
|
+
// operation: create, read, write, list, delete, sendto, execute, sendToHost, readLogs
|
|
178
|
+
if (SocketCommands.COMMANDS_PERMISSIONS[_command]) {
|
|
179
|
+
// If permission required
|
|
180
|
+
if (SocketCommands.COMMANDS_PERMISSIONS[_command].type) {
|
|
181
|
+
if (SocketCommands.COMMANDS_PERMISSIONS[_command].type === 'object' &&
|
|
182
|
+
socket._acl?.object &&
|
|
183
|
+
(socket._acl?.object)[SocketCommands.COMMANDS_PERMISSIONS[_command].operation]) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
else if (SocketCommands.COMMANDS_PERMISSIONS[_command].type === 'state' &&
|
|
187
|
+
socket._acl?.state &&
|
|
188
|
+
(socket._acl?.state)[SocketCommands.COMMANDS_PERMISSIONS[_command].operation]) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
this.adapter.log.warn(`No permission for "${socket._acl?.user}" to call ${_command}. Need "${SocketCommands.COMMANDS_PERMISSIONS[_command].type}"."${SocketCommands.COMMANDS_PERMISSIONS[_command].operation}"`);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
this.adapter.log.warn(`No rule for command: ${_command}`);
|
|
199
|
+
}
|
|
200
|
+
if (typeof callback === 'function') {
|
|
201
|
+
callback(SocketCommands.ERROR_PERMISSION);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
if (SocketCommands.COMMANDS_PERMISSIONS[_command]) {
|
|
205
|
+
socket.emit(SocketCommands.ERROR_PERMISSION, {
|
|
206
|
+
command,
|
|
207
|
+
type: SocketCommands.COMMANDS_PERMISSIONS[_command].type,
|
|
208
|
+
operation: SocketCommands.COMMANDS_PERMISSIONS[_command].operation,
|
|
209
|
+
args,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
socket.emit(SocketCommands.ERROR_PERMISSION, { command: _command, args });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
publish(socket, type, id, obj) {
|
|
221
|
+
if (socket?.subscribe?.[type] && this.#updateSession(socket)) {
|
|
222
|
+
return !!socket.subscribe[type].find(sub => {
|
|
223
|
+
if (sub.regex.test(id)) {
|
|
224
|
+
// replace language
|
|
225
|
+
if (this.context.language &&
|
|
226
|
+
id === 'system.config' &&
|
|
227
|
+
obj.common) {
|
|
228
|
+
obj.common.language = this.context.language;
|
|
229
|
+
}
|
|
230
|
+
socket.emit(type, id, obj);
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
publishFile(socket, id, fileName, size) {
|
|
238
|
+
if (socket?.subscribe?.fileChange && this.#updateSession(socket)) {
|
|
239
|
+
const key = `${id}####${fileName}`;
|
|
240
|
+
return !!socket.subscribe.fileChange.find(sub => {
|
|
241
|
+
if (sub.regex.test(key)) {
|
|
242
|
+
socket.emit('fileChange', id, fileName, size);
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
publishInstanceMessage(socket, sourceInstance, messageType, data) {
|
|
250
|
+
if (this.#clientSubscribes[socket.id]?.[sourceInstance]?.includes(messageType)) {
|
|
251
|
+
socket.emit('im', messageType, sourceInstance, data);
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
// inform instance about missing subscription
|
|
255
|
+
this.adapter.sendTo(sourceInstance, 'clientSubscribeError', {
|
|
256
|
+
type: messageType,
|
|
257
|
+
sid: socket.id,
|
|
258
|
+
reason: 'no one subscribed',
|
|
259
|
+
});
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
_showSubscribes(socket, type) {
|
|
263
|
+
if (socket?.subscribe) {
|
|
264
|
+
const s = socket.subscribe[type] || [];
|
|
265
|
+
const ids = [];
|
|
266
|
+
for (let i = 0; i < s.length; i++) {
|
|
267
|
+
ids.push(s[i].pattern);
|
|
268
|
+
}
|
|
269
|
+
this.adapter.log.debug(`Subscribes: ${ids.join(', ')}`);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
this.adapter.log.debug('Subscribes: no subscribes');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
isLogEnabled() {
|
|
276
|
+
return this.#logEnabled;
|
|
277
|
+
}
|
|
278
|
+
subscribe(socket, type, pattern, patternFile) {
|
|
279
|
+
if (!pattern) {
|
|
280
|
+
this.adapter.log.warn('Empty pattern on subscribe!');
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
this.subscribes[type] = this.subscribes[type] || {};
|
|
284
|
+
let p;
|
|
285
|
+
let key;
|
|
286
|
+
pattern = pattern.toString();
|
|
287
|
+
if (patternFile && type === 'fileChange') {
|
|
288
|
+
patternFile = patternFile.toString();
|
|
289
|
+
key = `${pattern}####${patternFile}`;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
key = pattern;
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
p = pattern2RegEx(key);
|
|
296
|
+
}
|
|
297
|
+
catch (e) {
|
|
298
|
+
this.adapter.log.error(`Invalid pattern on subscribe: ${e.message}`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (p === null) {
|
|
302
|
+
this.adapter.log.warn('Empty pattern on subscribe!');
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
let s;
|
|
306
|
+
if (socket) {
|
|
307
|
+
socket.subscribe = socket.subscribe || {};
|
|
308
|
+
s = socket.subscribe[type] = socket.subscribe[type] || [];
|
|
309
|
+
if (s.find(item => item.pattern === key)) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
s.push({ pattern: key, regex: new RegExp(p) });
|
|
313
|
+
}
|
|
314
|
+
const options = socket?._acl?.user ? { user: socket._acl.user } : undefined;
|
|
315
|
+
if (this.subscribes[type][key] === undefined) {
|
|
316
|
+
this.subscribes[type][key] = 1;
|
|
317
|
+
if (type === 'stateChange') {
|
|
318
|
+
this.adapter
|
|
319
|
+
.subscribeForeignStatesAsync(pattern, options)
|
|
320
|
+
.catch(e => this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`));
|
|
321
|
+
}
|
|
322
|
+
else if (type === 'objectChange') {
|
|
323
|
+
this.adapter
|
|
324
|
+
.subscribeForeignObjectsAsync(pattern, options)
|
|
325
|
+
.catch(e => this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`));
|
|
326
|
+
}
|
|
327
|
+
else if (type === 'log') {
|
|
328
|
+
if (!this.#logEnabled && this.adapter.requireLog) {
|
|
329
|
+
this.#logEnabled = true;
|
|
330
|
+
void this.adapter.requireLog(true, options);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
else if (type === 'fileChange' && this.adapter.subscribeForeignFiles) {
|
|
334
|
+
void this.adapter
|
|
335
|
+
.subscribeForeignFiles(pattern, patternFile || '*', options)
|
|
336
|
+
.catch(e => this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
this.subscribes[type][key]++;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
unsubscribe(socket, type, pattern, patternFile) {
|
|
344
|
+
if (!pattern) {
|
|
345
|
+
this.adapter.log.warn('Empty pattern on subscribe!');
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (!this.subscribes[type]) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
let key;
|
|
352
|
+
pattern = pattern.toString();
|
|
353
|
+
if (patternFile && type === 'fileChange') {
|
|
354
|
+
patternFile = patternFile.toString();
|
|
355
|
+
key = `${pattern}####${patternFile}`;
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
key = pattern;
|
|
359
|
+
}
|
|
360
|
+
const options = socket?._acl?.user ? { user: socket._acl.user } : undefined;
|
|
361
|
+
if (socket && typeof socket === 'object') {
|
|
362
|
+
if (!socket.subscribe || !socket.subscribe[type]) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
for (let i = socket.subscribe[type].length - 1; i >= 0; i--) {
|
|
366
|
+
if (socket.subscribe[type][i].pattern === key) {
|
|
367
|
+
// Remove a pattern from a global list
|
|
368
|
+
if (this.subscribes[type][key] !== undefined) {
|
|
369
|
+
this.subscribes[type][key]--;
|
|
370
|
+
if (this.subscribes[type][key] <= 0) {
|
|
371
|
+
if (type === 'stateChange') {
|
|
372
|
+
this.adapter
|
|
373
|
+
.unsubscribeForeignStatesAsync(pattern, options)
|
|
374
|
+
.catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
|
|
375
|
+
}
|
|
376
|
+
else if (type === 'objectChange') {
|
|
377
|
+
this.adapter
|
|
378
|
+
.unsubscribeForeignObjectsAsync(pattern, options)
|
|
379
|
+
.catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
|
|
380
|
+
}
|
|
381
|
+
else if (type === 'log') {
|
|
382
|
+
if (this.#logEnabled && this.adapter.requireLog) {
|
|
383
|
+
this.#logEnabled = false;
|
|
384
|
+
void this.adapter.requireLog(false, options);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
else if (type === 'fileChange' && this.adapter.unsubscribeForeignFiles) {
|
|
388
|
+
void this.adapter
|
|
389
|
+
.unsubscribeForeignFiles(pattern, patternFile || '*', options)
|
|
390
|
+
.catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
|
|
391
|
+
}
|
|
392
|
+
delete this.subscribes[type][pattern];
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
socket.subscribe[type].splice(i, 1);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
else if (key) {
|
|
401
|
+
// Remove a pattern from a global list
|
|
402
|
+
if (this.subscribes[type][key] !== undefined) {
|
|
403
|
+
this.subscribes[type][key]--;
|
|
404
|
+
if (this.subscribes[type][key] <= 0) {
|
|
405
|
+
if (type === 'stateChange') {
|
|
406
|
+
this.adapter
|
|
407
|
+
.unsubscribeForeignStatesAsync(pattern, options)
|
|
408
|
+
.catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
|
|
409
|
+
}
|
|
410
|
+
else if (type === 'objectChange') {
|
|
411
|
+
this.adapter
|
|
412
|
+
.unsubscribeForeignObjectsAsync(pattern, options)
|
|
413
|
+
.catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
|
|
414
|
+
}
|
|
415
|
+
else if (type === 'log') {
|
|
416
|
+
if (this.adapter.requireLog && this.#logEnabled) {
|
|
417
|
+
this.#logEnabled = false;
|
|
418
|
+
void this.adapter.requireLog(false, options);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
else if (type === 'fileChange' && this.adapter.unsubscribeForeignFiles) {
|
|
422
|
+
void this.adapter
|
|
423
|
+
.unsubscribeForeignFiles(pattern, patternFile || '*', options)
|
|
424
|
+
.catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
|
|
425
|
+
}
|
|
426
|
+
delete this.subscribes[type][key];
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
for (const pattern of Object.keys(this.subscribes[type])) {
|
|
432
|
+
if (type === 'stateChange') {
|
|
433
|
+
this.adapter
|
|
434
|
+
.unsubscribeForeignStatesAsync(pattern, options)
|
|
435
|
+
.catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
|
|
436
|
+
}
|
|
437
|
+
else if (type === 'objectChange') {
|
|
438
|
+
this.adapter
|
|
439
|
+
.unsubscribeForeignObjectsAsync(pattern, options)
|
|
440
|
+
.catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
|
|
441
|
+
}
|
|
442
|
+
else if (type === 'log') {
|
|
443
|
+
// console.log((socket._name || socket.id) + ' requireLog false');
|
|
444
|
+
if (this.adapter.requireLog && this.#logEnabled) {
|
|
445
|
+
this.#logEnabled = false;
|
|
446
|
+
void this.adapter.requireLog(false, options);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
else if (type === 'fileChange' && this.adapter.unsubscribeForeignFiles) {
|
|
450
|
+
const [id, fileName] = pattern.split('####');
|
|
451
|
+
void this.adapter
|
|
452
|
+
.unsubscribeForeignFiles(id, fileName, options)
|
|
453
|
+
.catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
this.subscribes[type] = {};
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
subscribeSocket(socket, type) {
|
|
460
|
+
if (!socket || !socket.subscribe) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (!type) {
|
|
464
|
+
// all
|
|
465
|
+
Object.keys(socket.subscribe).forEach(type => this.subscribeSocket(socket, type));
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (!socket.subscribe[type]) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const options = socket?._acl?.user ? { user: socket._acl.user } : undefined;
|
|
472
|
+
for (let i = 0; i < socket.subscribe[type].length; i++) {
|
|
473
|
+
const pattern = socket.subscribe[type][i].pattern;
|
|
474
|
+
if (this.subscribes[type][pattern] === undefined) {
|
|
475
|
+
this.subscribes[type][pattern] = 1;
|
|
476
|
+
if (type === 'stateChange') {
|
|
477
|
+
this.adapter
|
|
478
|
+
.subscribeForeignStatesAsync(pattern, options)
|
|
479
|
+
.catch(e => this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`));
|
|
480
|
+
}
|
|
481
|
+
else if (type === 'objectChange') {
|
|
482
|
+
this.adapter
|
|
483
|
+
.subscribeForeignObjectsAsync(pattern, options)
|
|
484
|
+
.catch(e => this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`));
|
|
485
|
+
}
|
|
486
|
+
else if (type === 'log') {
|
|
487
|
+
if (this.adapter.requireLog && !this.#logEnabled) {
|
|
488
|
+
this.#logEnabled = true;
|
|
489
|
+
void this.adapter.requireLog(true, options);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
else if (type === 'fileChange' && this.adapter.subscribeForeignFiles) {
|
|
493
|
+
const [id, fileName] = pattern.split('####');
|
|
494
|
+
void this.adapter
|
|
495
|
+
.subscribeForeignFiles(id, fileName, options)
|
|
496
|
+
.catch(e => this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
this.subscribes[type][pattern]++;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
unsubscribeSocket(socket, type) {
|
|
505
|
+
if (!socket || !socket.subscribe) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
// inform all instances about disconnected socket
|
|
509
|
+
this.#informAboutDisconnect(socket.id);
|
|
510
|
+
if (!type) {
|
|
511
|
+
// all
|
|
512
|
+
Object.keys(socket.subscribe).forEach(type => this.unsubscribeSocket(socket, type));
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (!socket.subscribe[type]) {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const options = socket?._acl?.user ? { user: socket._acl.user } : undefined;
|
|
519
|
+
for (let i = 0; i < socket.subscribe[type].length; i++) {
|
|
520
|
+
const pattern = socket.subscribe[type][i].pattern;
|
|
521
|
+
if (this.subscribes[type][pattern] !== undefined) {
|
|
522
|
+
this.subscribes[type][pattern]--;
|
|
523
|
+
if (this.subscribes[type][pattern] <= 0) {
|
|
524
|
+
if (type === 'stateChange') {
|
|
525
|
+
this.adapter
|
|
526
|
+
.unsubscribeForeignStatesAsync(pattern, options)
|
|
527
|
+
.catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
|
|
528
|
+
}
|
|
529
|
+
else if (type === 'objectChange') {
|
|
530
|
+
this.adapter
|
|
531
|
+
.unsubscribeForeignObjectsAsync(pattern, options)
|
|
532
|
+
.catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
|
|
533
|
+
}
|
|
534
|
+
else if (type === 'log') {
|
|
535
|
+
if (this.adapter.requireLog && !this.#logEnabled) {
|
|
536
|
+
this.#logEnabled = true;
|
|
537
|
+
void this.adapter.requireLog(true, options);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
else if (type === 'fileChange' && this.adapter.unsubscribeForeignFiles) {
|
|
541
|
+
const [id, fileName] = pattern.split('####');
|
|
542
|
+
void this.adapter
|
|
543
|
+
.unsubscribeForeignFiles(id, fileName, options)
|
|
544
|
+
.catch(e => this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`));
|
|
545
|
+
}
|
|
546
|
+
delete this.subscribes[type][pattern];
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
#subscribeStates(socket, pattern, callback) {
|
|
552
|
+
if (this._checkPermissions(socket, 'subscribe', callback, pattern)) {
|
|
553
|
+
if (Array.isArray(pattern)) {
|
|
554
|
+
for (let p = 0; p < pattern.length; p++) {
|
|
555
|
+
this.subscribe(socket, 'stateChange', pattern[p]);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
this.subscribe(socket, 'stateChange', pattern);
|
|
560
|
+
}
|
|
561
|
+
this.adapter.log.level === 'debug' && this._showSubscribes(socket, 'stateChange');
|
|
562
|
+
typeof callback === 'function' && setImmediate(callback, null);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
#unsubscribeStates(socket, pattern, callback) {
|
|
566
|
+
if (this._checkPermissions(socket, 'unsubscribe', callback, pattern)) {
|
|
567
|
+
if (Array.isArray(pattern)) {
|
|
568
|
+
for (let p = 0; p < pattern.length; p++) {
|
|
569
|
+
this.unsubscribe(socket, 'stateChange', pattern[p]);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
this.unsubscribe(socket, 'stateChange', pattern);
|
|
574
|
+
}
|
|
575
|
+
this.adapter.log.level === 'debug' && this._showSubscribes(socket, 'stateChange');
|
|
576
|
+
typeof callback === 'function' && setImmediate(callback, null);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
#subscribeFiles(socket, id, pattern, callback) {
|
|
580
|
+
if (this._checkPermissions(socket, 'subscribeFiles', callback, pattern)) {
|
|
581
|
+
if (Array.isArray(pattern)) {
|
|
582
|
+
for (let p = 0; p < pattern.length; p++) {
|
|
583
|
+
this.subscribe(socket, 'fileChange', id, pattern[p]);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
this.subscribe(socket, 'fileChange', id, pattern);
|
|
588
|
+
}
|
|
589
|
+
this.adapter.log.level === 'debug' && this._showSubscribes(socket, 'fileChange');
|
|
590
|
+
typeof callback === 'function' && setImmediate(callback, null);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
_unsubscribeFiles(socket, id, pattern, callback) {
|
|
594
|
+
if (this._checkPermissions(socket, 'unsubscribeFiles', callback, pattern)) {
|
|
595
|
+
if (Array.isArray(pattern)) {
|
|
596
|
+
for (let p = 0; p < pattern.length; p++) {
|
|
597
|
+
this.unsubscribe(socket, 'fileChange', id, pattern[p]);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
this.unsubscribe(socket, 'fileChange', id, pattern);
|
|
602
|
+
}
|
|
603
|
+
this.adapter.log.level === 'debug' && this._showSubscribes(socket, 'fileChange');
|
|
604
|
+
typeof callback === 'function' && setImmediate(callback, null);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
addCommandHandler(command, handler) {
|
|
608
|
+
if (handler) {
|
|
609
|
+
this.commands[command] = handler;
|
|
610
|
+
}
|
|
611
|
+
else if (command in this.commands) {
|
|
612
|
+
delete this.commands[command];
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
getCommandHandler(command) {
|
|
616
|
+
return this.commands[command];
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Converts old structures of config definitions into new one - `adminUI`
|
|
620
|
+
*
|
|
621
|
+
* @param obj Instance or adapter object to be converted
|
|
622
|
+
*/
|
|
623
|
+
fixAdminUI(obj) {
|
|
624
|
+
if (obj?.common && !obj.common.adminUI) {
|
|
625
|
+
obj.common.adminUI = { config: 'none' };
|
|
626
|
+
if (obj.common.noConfig) {
|
|
627
|
+
obj.common.adminUI.config = 'none';
|
|
628
|
+
// @ts-expect-error this attribute is deprecated, but still used
|
|
629
|
+
}
|
|
630
|
+
else if (obj.common.jsonConfig) {
|
|
631
|
+
obj.common.adminUI.config = 'json';
|
|
632
|
+
}
|
|
633
|
+
else if (obj.common.materialize) {
|
|
634
|
+
obj.common.adminUI.config = 'materialize';
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
obj.common.adminUI.config = 'html';
|
|
638
|
+
}
|
|
639
|
+
// @ts-expect-error this attribute is deprecated, but still used
|
|
640
|
+
if (obj.common.jsonCustom) {
|
|
641
|
+
obj.common.adminUI.custom = 'json';
|
|
642
|
+
}
|
|
643
|
+
else if (obj.common.supportCustoms) {
|
|
644
|
+
obj.common.adminUI.custom = 'json';
|
|
645
|
+
}
|
|
646
|
+
if (obj.common.materializeTab && obj.common.adminTab) {
|
|
647
|
+
obj.common.adminUI.tab = 'materialize';
|
|
648
|
+
}
|
|
649
|
+
else if (obj.common.adminTab) {
|
|
650
|
+
obj.common.adminUI.tab = 'html';
|
|
651
|
+
}
|
|
652
|
+
if (obj.common.adminUI) {
|
|
653
|
+
this.adapter.log.debug(`Please add to "${obj._id.replace(/\.\d+$/, '')}" common.adminUI=${JSON.stringify(obj.common.adminUI)}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
#httpGet(url, callback) {
|
|
658
|
+
this.adapter.log.debug(`httpGet: ${url}`);
|
|
659
|
+
if (axiosGet) {
|
|
660
|
+
try {
|
|
661
|
+
axiosGet(url, {
|
|
662
|
+
responseType: 'arraybuffer',
|
|
663
|
+
timeout: 15000,
|
|
664
|
+
validateStatus: (status) => status < 400,
|
|
665
|
+
})
|
|
666
|
+
.then((result) => callback(null, { status: result.status, statusText: result.statusText }, result.data))
|
|
667
|
+
.catch((error) => callback(error));
|
|
668
|
+
}
|
|
669
|
+
catch (error) {
|
|
670
|
+
callback(error);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
callback(new Error('axios is not initialized'));
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
// Init common commands that not belong to stats, objects or files
|
|
678
|
+
_initCommandsCommon() {
|
|
679
|
+
/**
|
|
680
|
+
* #DOCUMENTATION commands
|
|
681
|
+
* Wait till the user is authenticated.
|
|
682
|
+
* As the user authenticates himself, the callback will be called
|
|
683
|
+
*
|
|
684
|
+
* @param socket Socket instance
|
|
685
|
+
* @param callback Callback `(isUserAuthenticated: boolean, isAuthenticationUsed: boolean) => void`
|
|
686
|
+
*/
|
|
687
|
+
this.commands.authenticate = (socket, callback) => {
|
|
688
|
+
if (socket._acl?.user !== null) {
|
|
689
|
+
this.adapter.log.debug(`${new Date().toISOString()} Request authenticate [${socket._acl?.user}]`);
|
|
690
|
+
if (typeof callback === 'function') {
|
|
691
|
+
callback(true, socket._secure);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
socket._authPending = callback;
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
/**
|
|
699
|
+
* #DOCUMENTATION commands
|
|
700
|
+
* Write error into ioBroker log
|
|
701
|
+
*
|
|
702
|
+
* @param _socket Socket instance (not used)
|
|
703
|
+
* @param error Error object or error text
|
|
704
|
+
*/
|
|
705
|
+
this.commands.error = (_socket, error) => {
|
|
706
|
+
this.adapter.log.error(`Socket error: ${error.toString()}`);
|
|
707
|
+
};
|
|
708
|
+
/**
|
|
709
|
+
* #DOCUMENTATION commands
|
|
710
|
+
* Write log entry into ioBroker log
|
|
711
|
+
*
|
|
712
|
+
* @param _socket Socket instance (not used)
|
|
713
|
+
* @param text log text
|
|
714
|
+
* @param level one of `['silly', 'debug', 'info', 'warn', 'error']`. Default is 'debug'.
|
|
715
|
+
*/
|
|
716
|
+
this.commands.log = (_socket, text, level) => {
|
|
717
|
+
if (level === 'error') {
|
|
718
|
+
this.adapter.log.error(text);
|
|
719
|
+
}
|
|
720
|
+
else if (level === 'warn') {
|
|
721
|
+
this.adapter.log.warn(text);
|
|
722
|
+
}
|
|
723
|
+
else if (level === 'info') {
|
|
724
|
+
this.adapter.log.info(text);
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
this.adapter.log.debug(text);
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
/**
|
|
731
|
+
* #DOCUMENTATION commands
|
|
732
|
+
* Check if the same feature is supported by the current js-controller
|
|
733
|
+
*
|
|
734
|
+
* @param _socket Socket instance (not used)
|
|
735
|
+
* @param feature feature name like `CONTROLLER_LICENSE_MANAGER`
|
|
736
|
+
* @param callback callback `(error: string | Error | null | undefined, isSupported: boolean) => void`
|
|
737
|
+
*/
|
|
738
|
+
this.commands.checkFeatureSupported = (_socket, feature, callback) => {
|
|
739
|
+
if (feature === 'INSTANCE_MESSAGES') {
|
|
740
|
+
SocketCommands._fixCallback(callback, null, true);
|
|
741
|
+
}
|
|
742
|
+
else if (feature === 'PARTIAL_OBJECT_TREE') {
|
|
743
|
+
SocketCommands._fixCallback(callback, null, true);
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
SocketCommands._fixCallback(callback, null, this.adapter.supportsFeature(feature));
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
/**
|
|
750
|
+
* #DOCUMENTATION commands
|
|
751
|
+
* Get history data from specific instance
|
|
752
|
+
*
|
|
753
|
+
* @param socket Socket instance
|
|
754
|
+
* @param id object ID
|
|
755
|
+
* @param options History options
|
|
756
|
+
* @param callback callback `(error: string | Error | null | undefined, result: ioBroker.GetHistoryResult) => void`
|
|
757
|
+
*/
|
|
758
|
+
this.commands.getHistory = (socket, id, options, callback) => {
|
|
759
|
+
if (this._checkPermissions(socket, 'getStateHistory', callback, id)) {
|
|
760
|
+
if (typeof options === 'string') {
|
|
761
|
+
options = {
|
|
762
|
+
instance: options,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
options = options || {};
|
|
766
|
+
// @ts-expect-error fixed in js-controller
|
|
767
|
+
options.user = socket._acl?.user;
|
|
768
|
+
options.aggregate = options.aggregate || 'none';
|
|
769
|
+
try {
|
|
770
|
+
this.adapter.getHistory(id, options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
771
|
+
}
|
|
772
|
+
catch (error) {
|
|
773
|
+
this.adapter.log.error(`[getHistory] ERROR: ${error.toString()}`);
|
|
774
|
+
SocketCommands._fixCallback(callback, error);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
/**
|
|
779
|
+
* #DOCUMENTATION commands
|
|
780
|
+
* Read content of HTTP(s) page server-side (without CORS and stuff)
|
|
781
|
+
*
|
|
782
|
+
* @param socket Socket instance
|
|
783
|
+
* @param url Page URL
|
|
784
|
+
* @param callback callback `(error: Error | null, result?: { status: number; statusText: string }, data?: string) => void`
|
|
785
|
+
*/
|
|
786
|
+
this.commands.httpGet = (socket, url, callback) => {
|
|
787
|
+
if (this._checkPermissions(socket, 'httpGet', callback, url)) {
|
|
788
|
+
if (axiosGet) {
|
|
789
|
+
this.#httpGet(url, callback);
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
void import('axios').then(({ default: axios }) => {
|
|
793
|
+
axiosGet = axiosGet || axios.get;
|
|
794
|
+
this.#httpGet(url, callback);
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
/**
|
|
800
|
+
* #DOCUMENTATION commands
|
|
801
|
+
* Send the message to specific instance
|
|
802
|
+
*
|
|
803
|
+
* @param socket Socket instance
|
|
804
|
+
* @param adapterInstance instance name, e.g. `history.0`
|
|
805
|
+
* @param command command name
|
|
806
|
+
* @param message the message is instance-dependent
|
|
807
|
+
* @param callback callback `(result: any) => void`
|
|
808
|
+
*/
|
|
809
|
+
this.commands.sendTo = (socket, adapterInstance, command, message, callback) => {
|
|
810
|
+
if (this._checkPermissions(socket, 'sendTo', callback, command)) {
|
|
811
|
+
try {
|
|
812
|
+
this.adapter.sendTo(adapterInstance, command, message, res => typeof callback === 'function' && setImmediate(() => callback(res)));
|
|
813
|
+
}
|
|
814
|
+
catch (error) {
|
|
815
|
+
typeof callback === 'function' && setImmediate(() => callback({ error }));
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
// following commands are protected and require the extra permissions
|
|
820
|
+
const protectedCommands = [
|
|
821
|
+
'cmdExec',
|
|
822
|
+
'getLocationOnDisk',
|
|
823
|
+
'getDiagData',
|
|
824
|
+
'getDevList',
|
|
825
|
+
'delLogs',
|
|
826
|
+
'writeDirAsZip',
|
|
827
|
+
'writeObjectsAsZip',
|
|
828
|
+
'readObjectsAsZip',
|
|
829
|
+
'checkLogging',
|
|
830
|
+
'updateMultihost',
|
|
831
|
+
'rebuildAdapter',
|
|
832
|
+
];
|
|
833
|
+
/**
|
|
834
|
+
* #DOCUMENTATION commands
|
|
835
|
+
* Send a message to the specific host.
|
|
836
|
+
* Host can answer to the following commands: `cmdExec, getRepository, getInstalled, getInstalledAdapter, getVersion, getDiagData, getLocationOnDisk, getDevList, getLogs, getHostInfo, delLogs, readDirAsZip, writeDirAsZip, readObjectsAsZip, writeObjectsAsZip, checkLogging, updateMultihost`.
|
|
837
|
+
*
|
|
838
|
+
* @param socket Socket instance
|
|
839
|
+
* @param host Host name. With or without 'system.host.' prefix
|
|
840
|
+
* @param command Host command
|
|
841
|
+
* @param message the message is command-specific
|
|
842
|
+
* @param callback callback `(result: { error?: string; result?: any }) => void`
|
|
843
|
+
*/
|
|
844
|
+
this.commands.sendToHost = (socket, host, command, message, callback) => {
|
|
845
|
+
if (this._checkPermissions(socket, protectedCommands.includes(command) ? 'cmdExec' : 'sendToHost', (error) => callback({ error: error || SocketCommands.ERROR_PERMISSION }), command)) {
|
|
846
|
+
// Try to decode this file locally as redis has a limitation for files bigger than 20MB
|
|
847
|
+
if (command === 'writeDirAsZip' && message && message.data.length > 1024 * 1024) {
|
|
848
|
+
let buffer;
|
|
849
|
+
try {
|
|
850
|
+
buffer = Buffer.from(message.data, 'base64');
|
|
851
|
+
}
|
|
852
|
+
catch (error) {
|
|
853
|
+
this.adapter.log.error(`Cannot convert data: ${error.toString()}`);
|
|
854
|
+
return callback && callback({ error: `Cannot convert data: ${error.toString()}` });
|
|
855
|
+
}
|
|
856
|
+
zipFiles = zipFiles || adapter_core_1.commonTools.zipFiles;
|
|
857
|
+
zipFiles
|
|
858
|
+
.writeDirAsZip(this.adapter, // normally we have to pass here the internal "objects" object, but as
|
|
859
|
+
// only writeFile is used, and it has the same name, we can pass here the
|
|
860
|
+
// adapter, which has the function with the same name and arguments
|
|
861
|
+
message.id, message.name, buffer, message.options, (error) => callback({ error: error?.toString() }))
|
|
862
|
+
.then(() => callback({}))
|
|
863
|
+
.catch((error) => {
|
|
864
|
+
this.adapter.log.error(`Cannot write zip file as folder: ${error.toString()}`);
|
|
865
|
+
if (callback) {
|
|
866
|
+
callback({ error: error?.toString() });
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
else if (this._sendToHost) {
|
|
871
|
+
this._sendToHost(host, command, message, callback);
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
try {
|
|
875
|
+
this.adapter.sendToHost(host, command, message, callback);
|
|
876
|
+
}
|
|
877
|
+
catch (error) {
|
|
878
|
+
if (callback) {
|
|
879
|
+
callback({ error });
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
/**
|
|
886
|
+
* #DOCUMENTATION commands
|
|
887
|
+
* Ask server is authentication enabled, and if the user authenticated
|
|
888
|
+
*
|
|
889
|
+
* @param socket Socket instance
|
|
890
|
+
* @param callback callback `(isUserAuthenticated: boolean | Error | string, isAuthenticationUsed: boolean) => void`
|
|
891
|
+
*/
|
|
892
|
+
this.commands.authEnabled = (socket, callback) => {
|
|
893
|
+
if (this._checkPermissions(socket, 'authEnabled', callback)) {
|
|
894
|
+
if (typeof callback === 'function') {
|
|
895
|
+
// @ts-expect-error auth could exist in adapter settings
|
|
896
|
+
callback(this.adapter.config.auth, (socket._acl?.user || '').replace(/^system\.user\./, ''));
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
this.adapter.log.warn('[authEnabled] Invalid callback');
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
/**
|
|
904
|
+
* #DOCUMENTATION commands
|
|
905
|
+
* Logout user
|
|
906
|
+
*
|
|
907
|
+
* @param socket Socket instance
|
|
908
|
+
* @param callback callback `(error?: Error) => void`
|
|
909
|
+
*/
|
|
910
|
+
this.commands.logout = (socket, callback) => {
|
|
911
|
+
if (socket.id) {
|
|
912
|
+
void this.adapter.destroySession(socket.id, callback);
|
|
913
|
+
}
|
|
914
|
+
else if (callback) {
|
|
915
|
+
callback(new Error('No session'));
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
/**
|
|
919
|
+
* #DOCUMENTATION commands
|
|
920
|
+
* List commands and permissions
|
|
921
|
+
*
|
|
922
|
+
* @param _socket Socket instance (not used)
|
|
923
|
+
* @param callback callback `(permissions: Record<string, { type: 'object' | 'state' | 'users' | 'other' | 'file' | ''; operation: SocketOperation }>) => void`
|
|
924
|
+
*/
|
|
925
|
+
this.commands.listPermissions = (_socket, callback) => {
|
|
926
|
+
if (typeof callback === 'function') {
|
|
927
|
+
callback(SocketCommands.COMMANDS_PERMISSIONS);
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
this.adapter.log.warn('[listPermissions] Invalid callback');
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
/**
|
|
934
|
+
* #DOCUMENTATION commands
|
|
935
|
+
* Get user permissions
|
|
936
|
+
*
|
|
937
|
+
* @param socket Socket instance
|
|
938
|
+
* @param callback callback `(error: string | null | undefined, userPermissions?: SocketACL | null) => void`
|
|
939
|
+
*/
|
|
940
|
+
this.commands.getUserPermissions = (socket, callback) => {
|
|
941
|
+
if (this._checkPermissions(socket, 'getUserPermissions', callback)) {
|
|
942
|
+
if (typeof callback === 'function') {
|
|
943
|
+
callback(null, socket._acl);
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
this.adapter.log.warn('[getUserPermissions] Invalid callback');
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
/**
|
|
951
|
+
* #DOCUMENTATION commands
|
|
952
|
+
* Get the adapter version. Not the socket-classes version!
|
|
953
|
+
*
|
|
954
|
+
* @param socket Socket instance
|
|
955
|
+
* @param callback callback `(error: string | Error | null | undefined, version: string | undefined, adapterName: string) => void`
|
|
956
|
+
*/
|
|
957
|
+
this.commands.getVersion = (socket, callback) => {
|
|
958
|
+
if (this._checkPermissions(socket, 'getVersion', callback)) {
|
|
959
|
+
if (typeof callback === 'function') {
|
|
960
|
+
callback(null, this.adapter.version, this.adapter.name);
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
this.adapter.log.warn('[getVersion] Invalid callback');
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
/**
|
|
968
|
+
* #DOCUMENTATION commands
|
|
969
|
+
* Get adapter name: "iobroker.ws", "iobroker.socketio", "iobroker.web", "iobroker.admin"
|
|
970
|
+
*
|
|
971
|
+
* @param socket Socket instance
|
|
972
|
+
* @param callback callback `(error: string | Error | null | undefined, version: string | undefined, adapterName: string) => void`
|
|
973
|
+
*/
|
|
974
|
+
this.commands.getAdapterName = (socket, callback) => {
|
|
975
|
+
if (this._checkPermissions(socket, 'getAdapterName', callback)) {
|
|
976
|
+
if (typeof callback === 'function') {
|
|
977
|
+
callback(null, this.adapter.name || 'unknown');
|
|
978
|
+
}
|
|
979
|
+
else {
|
|
980
|
+
this.adapter.log.warn('[getAdapterName] Invalid callback');
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
/** Init commands for files */
|
|
986
|
+
_initCommandsFiles() {
|
|
987
|
+
/**
|
|
988
|
+
* #DOCUMENTATION files
|
|
989
|
+
* Read a file from ioBroker DB
|
|
990
|
+
*
|
|
991
|
+
* @param socket Socket instance
|
|
992
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
993
|
+
* @param fileName file name, e.g. `main/vis-views.json`
|
|
994
|
+
* @param callback Callback `(error: null | undefined | Error | string, data: Buffer | string, mimeType: string) => void`
|
|
995
|
+
*/
|
|
996
|
+
this.commands.readFile = (socket, adapter, fileName, callback) => {
|
|
997
|
+
if (this._checkPermissions(socket, 'readFile', callback, fileName)) {
|
|
998
|
+
try {
|
|
999
|
+
this.adapter.readFile(adapter, fileName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1000
|
+
}
|
|
1001
|
+
catch (error) {
|
|
1002
|
+
this.adapter.log.error(`[readFile] ERROR: ${error.toString()}`);
|
|
1003
|
+
SocketCommands._fixCallback(callback, error);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
/**
|
|
1008
|
+
* #DOCUMENTATION files
|
|
1009
|
+
* Read a file from ioBroker DB as base64 string
|
|
1010
|
+
*
|
|
1011
|
+
* @param socket Socket instance
|
|
1012
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
1013
|
+
* @param fileName file name, e.g. `main/vis-views.json`
|
|
1014
|
+
* @param callback Callback `(error: null | undefined | Error | string, base64: string, mimeType: string) => void`
|
|
1015
|
+
*/
|
|
1016
|
+
this.commands.readFile64 = (socket, adapter, fileName, callback) => {
|
|
1017
|
+
if (this._checkPermissions(socket, 'readFile64', callback, fileName)) {
|
|
1018
|
+
try {
|
|
1019
|
+
this.adapter.readFile(adapter, fileName, { user: socket._acl?.user }, (error, buffer, type) => {
|
|
1020
|
+
let data64;
|
|
1021
|
+
if (buffer) {
|
|
1022
|
+
try {
|
|
1023
|
+
if (type === 'application/json' ||
|
|
1024
|
+
type === 'application/json5' ||
|
|
1025
|
+
fileName.toLowerCase().endsWith('.json5')) {
|
|
1026
|
+
data64 = Buffer.from(encodeURIComponent(buffer)).toString('base64');
|
|
1027
|
+
}
|
|
1028
|
+
else {
|
|
1029
|
+
if (typeof buffer === 'string') {
|
|
1030
|
+
data64 = Buffer.from(buffer).toString('base64');
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
data64 = buffer.toString('base64');
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
catch (error) {
|
|
1038
|
+
this.adapter.log.error(`[readFile64] Cannot convert data: ${error.toString()}`);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
// Convert buffer to base 64
|
|
1042
|
+
if (typeof callback === 'function') {
|
|
1043
|
+
callback(error, data64 || '', type);
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
this.adapter.log.warn('[readFile64] Invalid callback');
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
catch (error) {
|
|
1051
|
+
this.adapter.log.error(`[readFile64] ERROR: ${error.toString()}`);
|
|
1052
|
+
SocketCommands._fixCallback(callback, error);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
/**
|
|
1057
|
+
* #DOCUMENTATION files
|
|
1058
|
+
* Write a file into ioBroker DB as base64 string
|
|
1059
|
+
*
|
|
1060
|
+
* @param socket Socket instance
|
|
1061
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
1062
|
+
* @param fileName file name, e.g. `main/vis-views.json`
|
|
1063
|
+
* @param data64 file content as base64 string
|
|
1064
|
+
* @param options optional `{mode: 0x0644}`
|
|
1065
|
+
* @param callback Callback `(error: null | undefined | Error | string) => void`
|
|
1066
|
+
*/
|
|
1067
|
+
this.commands.writeFile64 = (socket, adapter, fileName, data64, options, callback) => {
|
|
1068
|
+
let _options;
|
|
1069
|
+
if (typeof options === 'function') {
|
|
1070
|
+
callback = options;
|
|
1071
|
+
_options = { user: socket._acl?.user };
|
|
1072
|
+
}
|
|
1073
|
+
else if (!options || options.mode === undefined) {
|
|
1074
|
+
_options = { user: socket._acl?.user };
|
|
1075
|
+
}
|
|
1076
|
+
else {
|
|
1077
|
+
_options = { user: socket._acl?.user, mode: options.mode };
|
|
1078
|
+
}
|
|
1079
|
+
if (this._checkPermissions(socket, 'writeFile64', callback, fileName)) {
|
|
1080
|
+
if (!data64) {
|
|
1081
|
+
return SocketCommands._fixCallback(callback, 'No data provided');
|
|
1082
|
+
}
|
|
1083
|
+
// Convert base 64 to buffer
|
|
1084
|
+
try {
|
|
1085
|
+
const buffer = Buffer.from(data64, 'base64');
|
|
1086
|
+
this.adapter.writeFile(adapter, fileName, buffer, _options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1087
|
+
}
|
|
1088
|
+
catch (error) {
|
|
1089
|
+
this.adapter.log.error(`[writeFile64] Cannot convert data: ${error.toString()}`);
|
|
1090
|
+
SocketCommands._fixCallback(callback, `Cannot convert data: ${error.toString()}`);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
/**
|
|
1095
|
+
* #DOCUMENTATION files
|
|
1096
|
+
* Write a file into ioBroker DB as text
|
|
1097
|
+
*
|
|
1098
|
+
* This function is overloaded in admin (because admin accepts only base64)
|
|
1099
|
+
*
|
|
1100
|
+
* @deprecated
|
|
1101
|
+
* @param socket Socket instance
|
|
1102
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
1103
|
+
* @param fileName file name, e.g. `main/vis-views.json`
|
|
1104
|
+
* @param data file content as text
|
|
1105
|
+
* @param options optional `{mode: 0x0644}`
|
|
1106
|
+
* @param callback Callback `(error: null | undefined | Error | string) => void`
|
|
1107
|
+
*/
|
|
1108
|
+
this.commands.writeFile = (socket, adapter, fileName, data, options, callback) => {
|
|
1109
|
+
if (this._checkPermissions(socket, 'writeFile', callback, fileName)) {
|
|
1110
|
+
let _options;
|
|
1111
|
+
if (typeof options === 'function') {
|
|
1112
|
+
callback = options;
|
|
1113
|
+
_options = { user: socket._acl?.user };
|
|
1114
|
+
}
|
|
1115
|
+
else if (!options || options.mode === undefined) {
|
|
1116
|
+
_options = { user: socket._acl?.user };
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
_options = { user: socket._acl?.user, mode: options.mode };
|
|
1120
|
+
}
|
|
1121
|
+
this.adapter.log.debug('writeFile deprecated. Please use writeFile64');
|
|
1122
|
+
try {
|
|
1123
|
+
this.adapter.writeFile(adapter, fileName, data, _options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1124
|
+
}
|
|
1125
|
+
catch (error) {
|
|
1126
|
+
this.adapter.log.error(`[writeFile] ERROR: ${error.toString()}`);
|
|
1127
|
+
SocketCommands._fixCallback(callback, error);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
/**
|
|
1132
|
+
* #DOCUMENTATION files
|
|
1133
|
+
* Delete file in ioBroker DB
|
|
1134
|
+
*
|
|
1135
|
+
* @param socket Socket instance
|
|
1136
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
1137
|
+
* @param name file name, e.g. `main/vis-views.json`
|
|
1138
|
+
* @param callback Callback `(error: null | undefined | Error | string) => void`
|
|
1139
|
+
*/
|
|
1140
|
+
this.commands.unlink = (socket, adapter, name, callback) => {
|
|
1141
|
+
if (this._checkPermissions(socket, 'unlink', callback, name)) {
|
|
1142
|
+
try {
|
|
1143
|
+
this.#unlink(adapter, name, { user: socket._acl?.user })
|
|
1144
|
+
.then(() => SocketCommands._fixCallback(callback, undefined))
|
|
1145
|
+
.catch(error => SocketCommands._fixCallback(callback, error));
|
|
1146
|
+
}
|
|
1147
|
+
catch (error) {
|
|
1148
|
+
this.adapter.log.error(`[unlink] ERROR: ${error.toString()}`);
|
|
1149
|
+
SocketCommands._fixCallback(callback, error);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
/**
|
|
1154
|
+
* #DOCUMENTATION files
|
|
1155
|
+
* Delete a file in ioBroker DB (same as "unlink", but only for files)
|
|
1156
|
+
*
|
|
1157
|
+
* @param socket Socket instance
|
|
1158
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
1159
|
+
* @param name file name, e.g. `main/vis-views.json`
|
|
1160
|
+
* @param callback Callback `(error: null | undefined | Error | string) => void`
|
|
1161
|
+
*/
|
|
1162
|
+
this.commands.deleteFile = (socket, adapter, name, callback) => {
|
|
1163
|
+
if (this._checkPermissions(socket, 'unlink', callback, name)) {
|
|
1164
|
+
try {
|
|
1165
|
+
this.adapter.unlink(adapter, name, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1166
|
+
}
|
|
1167
|
+
catch (error) {
|
|
1168
|
+
this.adapter.log.error(`[deleteFile] ERROR: ${error.toString()}`);
|
|
1169
|
+
SocketCommands._fixCallback(callback, error);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
/**
|
|
1174
|
+
* #DOCUMENTATION files
|
|
1175
|
+
* Delete folder in ioBroker DB (same as `unlink`, but only for folders)
|
|
1176
|
+
*
|
|
1177
|
+
* @param socket Socket instance
|
|
1178
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
1179
|
+
* @param name folder name, e.g. `main`
|
|
1180
|
+
* @param callback Callback `(error: null | undefined | Error | string) => void`
|
|
1181
|
+
*/
|
|
1182
|
+
this.commands.deleteFolder = (socket, adapter, name, callback) => {
|
|
1183
|
+
if (this._checkPermissions(socket, 'unlink', callback, name)) {
|
|
1184
|
+
try {
|
|
1185
|
+
this.#unlink(adapter, name, { user: socket._acl?.user })
|
|
1186
|
+
.then(() => SocketCommands._fixCallback(callback, null))
|
|
1187
|
+
.catch(error => SocketCommands._fixCallback(callback, error));
|
|
1188
|
+
}
|
|
1189
|
+
catch (error) {
|
|
1190
|
+
this.adapter.log.error(`[deleteFolder] ERROR: ${error.toString()}`);
|
|
1191
|
+
SocketCommands._fixCallback(callback, error);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
/**
|
|
1196
|
+
* #DOCUMENTATION files
|
|
1197
|
+
* Rename a file in ioBroker DB
|
|
1198
|
+
*
|
|
1199
|
+
* @param socket Socket instance
|
|
1200
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
1201
|
+
* @param oldName current file name, e.g. `main/vis-views.json`
|
|
1202
|
+
* @param newName new file name, e.g. `main/vis-views-new.json`
|
|
1203
|
+
* @param callback Callback `(error: null | undefined | Error | string) => void`
|
|
1204
|
+
*/
|
|
1205
|
+
this.commands.renameFile = (socket, adapter, oldName, newName, callback) => {
|
|
1206
|
+
if (this._checkPermissions(socket, 'rename', callback, oldName)) {
|
|
1207
|
+
try {
|
|
1208
|
+
this.adapter.rename(adapter, oldName, newName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1209
|
+
}
|
|
1210
|
+
catch (error) {
|
|
1211
|
+
this.adapter.log.error(`[renameFile] ERROR: ${error.toString()}`);
|
|
1212
|
+
SocketCommands._fixCallback(callback, error);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
/**
|
|
1217
|
+
* #DOCUMENTATION files
|
|
1218
|
+
* Rename file or folder in ioBroker DB
|
|
1219
|
+
*
|
|
1220
|
+
* @param socket Socket instance
|
|
1221
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
1222
|
+
* @param oldName current file name, e.g. `main/vis-views.json`
|
|
1223
|
+
* @param newName new file name, e.g. `main/vis-views-new.json`
|
|
1224
|
+
* @param callback Callback `(error: null | undefined | Error | string) => void`
|
|
1225
|
+
*/
|
|
1226
|
+
this.commands.rename = (socket, adapter, oldName, newName, callback) => {
|
|
1227
|
+
if (this._checkPermissions(socket, 'rename', callback, oldName)) {
|
|
1228
|
+
try {
|
|
1229
|
+
this.#rename(adapter, oldName, newName, { user: socket._acl?.user })
|
|
1230
|
+
.then(() => SocketCommands._fixCallback(callback, undefined))
|
|
1231
|
+
.catch(error => SocketCommands._fixCallback(callback, error));
|
|
1232
|
+
}
|
|
1233
|
+
catch (error) {
|
|
1234
|
+
this.adapter.log.error(`[rename] ERROR: ${error.toString()}`);
|
|
1235
|
+
SocketCommands._fixCallback(callback, error);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
/**
|
|
1240
|
+
* #DOCUMENTATION files
|
|
1241
|
+
* Create a folder in ioBroker DB
|
|
1242
|
+
*
|
|
1243
|
+
* @param socket Socket instance
|
|
1244
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
1245
|
+
* @param dirName desired folder name, e.g. `main`
|
|
1246
|
+
* @param callback Callback `(error: null | undefined | Error | string) => void`
|
|
1247
|
+
*/
|
|
1248
|
+
this.commands.mkdir = (socket, adapter, dirName, callback) => {
|
|
1249
|
+
if (this._checkPermissions(socket, 'mkdir', callback, dirName)) {
|
|
1250
|
+
try {
|
|
1251
|
+
this.adapter.mkdir(adapter, dirName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1252
|
+
}
|
|
1253
|
+
catch (error) {
|
|
1254
|
+
this.adapter.log.error(`[mkdir] ERROR: ${error.toString()}`);
|
|
1255
|
+
SocketCommands._fixCallback(callback, error);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
/**
|
|
1260
|
+
* #DOCUMENTATION files
|
|
1261
|
+
* Read content of folder in ioBroker DB
|
|
1262
|
+
*
|
|
1263
|
+
* @param socket Socket instance
|
|
1264
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
1265
|
+
* @param dirName folder name, e.g. `main`
|
|
1266
|
+
* @param options for future use
|
|
1267
|
+
* @param callback Callback `(error: null | undefined | Error | string, files: Array<{file: string, isDir: boolean, stats: {size: number}, modifiedAt: number, acl: {owner: string, ownerGroup: string, permissions: number, read: boolean, write: boolean}}>) => void`
|
|
1268
|
+
*/
|
|
1269
|
+
this.commands.readDir = (socket, adapter, dirName, options, callback) => {
|
|
1270
|
+
if (typeof options === 'function') {
|
|
1271
|
+
callback = options;
|
|
1272
|
+
}
|
|
1273
|
+
if (this._checkPermissions(socket, 'readDir', callback, dirName)) {
|
|
1274
|
+
try {
|
|
1275
|
+
this.adapter.readDir(adapter, dirName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1276
|
+
}
|
|
1277
|
+
catch (error) {
|
|
1278
|
+
this.adapter.log.error(`[readDir] ERROR: ${error.toString()}`);
|
|
1279
|
+
SocketCommands._fixCallback(callback, error);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
/**
|
|
1284
|
+
* #DOCUMENTATION files
|
|
1285
|
+
* Change a file mode in ioBroker DB
|
|
1286
|
+
*
|
|
1287
|
+
* @param socket Socket instance
|
|
1288
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
1289
|
+
* @param fileName file name, e.g. `main/vis-views.json`
|
|
1290
|
+
* @param options options `{mode: 0x644}`
|
|
1291
|
+
* @param options.mode File mode in linux format 0x644. The first digit is user, second group, third others. Bit 1 is `execute`, bit 2 is `write`, bit 3 is `read`
|
|
1292
|
+
* @param callback Callback `(error: string | Error | null | undefined) => void`
|
|
1293
|
+
*/
|
|
1294
|
+
this.commands.chmodFile = (socket, adapter, fileName, options, callback) => {
|
|
1295
|
+
let _options;
|
|
1296
|
+
if (options?.mode !== undefined) {
|
|
1297
|
+
_options = { user: socket._acl?.user, mode: options.mode };
|
|
1298
|
+
}
|
|
1299
|
+
else {
|
|
1300
|
+
this.adapter.log.error(`[chownFile] ERROR: no options`);
|
|
1301
|
+
SocketCommands._fixCallback(callback, 'no options');
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
if (this._checkPermissions(socket, 'chmodFile', callback, fileName)) {
|
|
1305
|
+
try {
|
|
1306
|
+
this.adapter.chmodFile(adapter, fileName, _options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1307
|
+
}
|
|
1308
|
+
catch (error) {
|
|
1309
|
+
this.adapter.log.error(`[chmodFile] ERROR: ${error.toString()}`);
|
|
1310
|
+
SocketCommands._fixCallback(callback, error);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
/**
|
|
1315
|
+
* #DOCUMENTATION files
|
|
1316
|
+
* Change file owner in ioBroker DB
|
|
1317
|
+
*
|
|
1318
|
+
* @param socket Socket instance
|
|
1319
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
1320
|
+
* @param fileName file name, e.g. `main/vis-views.json`
|
|
1321
|
+
* @param options options `{owner: 'system.user.user', ownerGroup: 'system.group.administrator'}` or `system.user.user`. If ownerGroup is not defined, it will be taken from owner.
|
|
1322
|
+
* @param options.owner New owner, like 'system.user.user'
|
|
1323
|
+
* @param options.ownerGroup New owner group, like 'system.group.administrator' If ownerGroup is not defined, it will be taken from owner.
|
|
1324
|
+
* @param callback Callback `(error: null | undefined | Error | string) => void`
|
|
1325
|
+
*/
|
|
1326
|
+
this.commands.chownFile = (socket, adapter, fileName, options, callback) => {
|
|
1327
|
+
let _options;
|
|
1328
|
+
if (options) {
|
|
1329
|
+
_options = { user: socket._acl?.user, owner: options.owner, ownerGroup: options.ownerGroup };
|
|
1330
|
+
}
|
|
1331
|
+
else {
|
|
1332
|
+
this.adapter.log.error(`[chownFile] ERROR: no options`);
|
|
1333
|
+
SocketCommands._fixCallback(callback, 'no options');
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
if (this._checkPermissions(socket, 'chownFile', callback, fileName)) {
|
|
1337
|
+
try {
|
|
1338
|
+
this.adapter.chownFile(adapter, fileName, _options, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1339
|
+
}
|
|
1340
|
+
catch (error) {
|
|
1341
|
+
this.adapter.log.error(`[chownFile] ERROR: ${error.toString()}`);
|
|
1342
|
+
SocketCommands._fixCallback(callback, error);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
};
|
|
1346
|
+
/**
|
|
1347
|
+
* #DOCUMENTATION files
|
|
1348
|
+
* Check if the file or folder exists in ioBroker DB
|
|
1349
|
+
*
|
|
1350
|
+
* @param socket Socket instance
|
|
1351
|
+
* @param adapter instance name, e.g. `vis.0`
|
|
1352
|
+
* @param fileName file name, e.g. `main/vis-views.json`
|
|
1353
|
+
* @param callback Callback `(error: null | undefined | Error | string, exists?: boolean) => void`
|
|
1354
|
+
*/
|
|
1355
|
+
this.commands.fileExists = (socket, adapter, fileName, callback) => {
|
|
1356
|
+
if (this._checkPermissions(socket, 'fileExists', callback, fileName)) {
|
|
1357
|
+
try {
|
|
1358
|
+
this.adapter.fileExists(adapter, fileName, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1359
|
+
}
|
|
1360
|
+
catch (error) {
|
|
1361
|
+
this.adapter.log.error(`[fileExists] ERROR: ${error.toString()}`);
|
|
1362
|
+
SocketCommands._fixCallback(callback, error);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
/**
|
|
1367
|
+
* #DOCUMENTATION files
|
|
1368
|
+
* Subscribe to file changes in ioBroker DB
|
|
1369
|
+
*
|
|
1370
|
+
* @param socket Socket instance
|
|
1371
|
+
* @param id instance name, e.g. `vis.0` or any object ID of type `meta`. `id` could have wildcards `*` too.
|
|
1372
|
+
* @param pattern file name pattern, e.g. `main/*.json` or array of names
|
|
1373
|
+
* @param callback Callback `(error: null | undefined | Error | string) => void`
|
|
1374
|
+
*/
|
|
1375
|
+
this.commands.subscribeFiles = (socket, id, pattern, callback) => {
|
|
1376
|
+
return this.#subscribeFiles(socket, id, pattern, callback);
|
|
1377
|
+
};
|
|
1378
|
+
/**
|
|
1379
|
+
* #DOCUMENTATION files
|
|
1380
|
+
* Unsubscribe from file changes in ioBroker DB
|
|
1381
|
+
*
|
|
1382
|
+
* @param socket Socket instance
|
|
1383
|
+
* @param id instance name, e.g. `vis.0` or any object ID of type `meta`. `id` could have wildcards `*` too.
|
|
1384
|
+
* @param pattern file name pattern, e.g. `main/*.json` or array of names
|
|
1385
|
+
* @param callback Callback `(error: null | undefined | Error | string) => void`
|
|
1386
|
+
*/
|
|
1387
|
+
this.commands.unsubscribeFiles = (socket, id, pattern, callback) => {
|
|
1388
|
+
return this._unsubscribeFiles(socket, id, pattern, callback);
|
|
1389
|
+
};
|
|
1390
|
+
/**
|
|
1391
|
+
* #DOCUMENTATION commands
|
|
1392
|
+
* Read all instances of the given adapter, or all instances of all adapters if adapterName is not defined
|
|
1393
|
+
*
|
|
1394
|
+
* @param socket Socket instance
|
|
1395
|
+
* @param adapterName adapter name, e.g. `history`. To get all instances of all adapters just place here "".
|
|
1396
|
+
* @param callback callback `(error: null | undefined | Error | string, instanceList?: ioBroker.InstanceObject[]) => void`
|
|
1397
|
+
*/
|
|
1398
|
+
this.commands.getAdapterInstances = (socket, adapterName, callback) => {
|
|
1399
|
+
if (typeof callback === 'function') {
|
|
1400
|
+
if (this._checkPermissions(socket, 'getObject', callback)) {
|
|
1401
|
+
let _adapterName = adapterName !== undefined && adapterName !== null ? adapterName : this.adapterName || '';
|
|
1402
|
+
if (_adapterName) {
|
|
1403
|
+
_adapterName += '.';
|
|
1404
|
+
}
|
|
1405
|
+
try {
|
|
1406
|
+
this.adapter.getObjectView('system', 'instance', {
|
|
1407
|
+
startkey: `system.adapter.${_adapterName}`,
|
|
1408
|
+
endkey: `system.adapter.${_adapterName}\u9999`,
|
|
1409
|
+
}, { user: socket._acl?.user }, (error, doc) => {
|
|
1410
|
+
if (error) {
|
|
1411
|
+
callback(error);
|
|
1412
|
+
}
|
|
1413
|
+
else {
|
|
1414
|
+
callback(null, doc?.rows
|
|
1415
|
+
.map(item => {
|
|
1416
|
+
const obj = item.value;
|
|
1417
|
+
if (obj.common) {
|
|
1418
|
+
delete obj.common.news;
|
|
1419
|
+
}
|
|
1420
|
+
this.fixAdminUI(obj);
|
|
1421
|
+
return obj;
|
|
1422
|
+
})
|
|
1423
|
+
.filter(obj => obj && (!adapterName || obj.common?.name === adapterName)));
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
catch (error) {
|
|
1428
|
+
this.adapter.log.error(`[getAdapterInstances] ERROR: ${error.toString()}`);
|
|
1429
|
+
SocketCommands._fixCallback(callback, error);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
/** Init commands for states */
|
|
1436
|
+
_initCommandsStates() {
|
|
1437
|
+
/**
|
|
1438
|
+
* #DOCUMENTATION states
|
|
1439
|
+
* Get states by pattern of current adapter
|
|
1440
|
+
*
|
|
1441
|
+
* @param socket Socket instance
|
|
1442
|
+
* @param pattern optional pattern, like `system.adapter.*` or array of state IDs. If the pattern is omitted, you will get ALL states of current adapter
|
|
1443
|
+
* @param callback callback `(error: null | undefined | Error | string, states?: Record<string, ioBroker.State>) => void`
|
|
1444
|
+
*/
|
|
1445
|
+
this.commands.getStates = (socket, pattern, callback) => {
|
|
1446
|
+
if (this._checkPermissions(socket, 'getStates', callback, pattern)) {
|
|
1447
|
+
if (typeof pattern === 'function') {
|
|
1448
|
+
callback = pattern;
|
|
1449
|
+
pattern = undefined;
|
|
1450
|
+
}
|
|
1451
|
+
if (typeof callback === 'function') {
|
|
1452
|
+
try {
|
|
1453
|
+
this.adapter.getForeignStates(pattern || '*', { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1454
|
+
}
|
|
1455
|
+
catch (error) {
|
|
1456
|
+
this.adapter.log.error(`[getStates] ERROR: ${error.toString()}`);
|
|
1457
|
+
SocketCommands._fixCallback(callback, error);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
else {
|
|
1461
|
+
this.adapter.log.warn('[getStates] Invalid callback');
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
/**
|
|
1466
|
+
* #DOCUMENTATION states
|
|
1467
|
+
* Same as getStates
|
|
1468
|
+
*
|
|
1469
|
+
* @deprecated
|
|
1470
|
+
* @param socket Socket instance
|
|
1471
|
+
* @param pattern pattern like `system.adapter.*` or array of state IDs
|
|
1472
|
+
* @param callback callback `(error: null | undefined | Error | string, states?: Record<string, ioBroker.State>) => void`
|
|
1473
|
+
*/
|
|
1474
|
+
this.commands.getForeignStates = (socket, pattern, callback) => {
|
|
1475
|
+
this.adapter.log.info('Use getStates');
|
|
1476
|
+
this.commands.getStates(socket, pattern, callback);
|
|
1477
|
+
};
|
|
1478
|
+
/**
|
|
1479
|
+
* #DOCUMENTATION states
|
|
1480
|
+
* Get a state by ID
|
|
1481
|
+
*
|
|
1482
|
+
* @param socket Socket instance
|
|
1483
|
+
* @param id State ID, e.g. `system.adapter.admin.0.memRss`
|
|
1484
|
+
* @param callback Callback `(error: null | undefined | Error | string, state?: ioBroker.State) => void`
|
|
1485
|
+
*/
|
|
1486
|
+
this.commands.getState = (socket, id, callback) => {
|
|
1487
|
+
if (this._checkPermissions(socket, 'getState', callback, id)) {
|
|
1488
|
+
if (typeof callback === 'function') {
|
|
1489
|
+
if (this.states && this.states[id]) {
|
|
1490
|
+
callback(null, this.states[id]);
|
|
1491
|
+
}
|
|
1492
|
+
else {
|
|
1493
|
+
try {
|
|
1494
|
+
void this.adapter
|
|
1495
|
+
.getForeignStateAsync(id, { user: socket._acl?.user })
|
|
1496
|
+
.then(state => SocketCommands._fixCallback(callback, null, [state]))
|
|
1497
|
+
.catch(error => {
|
|
1498
|
+
this.adapter.log.error(`[getState] ERROR: ${error.toString()}`);
|
|
1499
|
+
SocketCommands._fixCallback(callback, error);
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
catch (error) {
|
|
1503
|
+
this.adapter.log.error(`[getState] ERROR: ${error.toString()}`);
|
|
1504
|
+
SocketCommands._fixCallback(callback, error);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
else {
|
|
1509
|
+
this.adapter.log.warn('[getState] Invalid callback');
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
/**
|
|
1514
|
+
* #DOCUMENTATION states
|
|
1515
|
+
* Set a state by ID
|
|
1516
|
+
*
|
|
1517
|
+
* @param socket Socket instance
|
|
1518
|
+
* @param id State ID, e.g. `system.adapter.admin.0.memRss`
|
|
1519
|
+
* @param state State value or object, e.g. `{val: 123, ack: true}`
|
|
1520
|
+
* @param callback Callback `(error: null | undefined | Error | string, state?: ioBroker.State) => void`
|
|
1521
|
+
*/
|
|
1522
|
+
this.commands.setState = (socket, id, state, callback) => {
|
|
1523
|
+
if (this._checkPermissions(socket, 'setState', callback, id)) {
|
|
1524
|
+
if (typeof state !== 'object') {
|
|
1525
|
+
state = { val: state };
|
|
1526
|
+
}
|
|
1527
|
+
// clear cache
|
|
1528
|
+
if (this.states?.[id]) {
|
|
1529
|
+
delete this.states[id];
|
|
1530
|
+
}
|
|
1531
|
+
try {
|
|
1532
|
+
this.adapter.setForeignState(id, state, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1533
|
+
}
|
|
1534
|
+
catch (error) {
|
|
1535
|
+
this.adapter.log.error(`[setState] ERROR: ${error.toString()}`);
|
|
1536
|
+
SocketCommands._fixCallback(callback, error);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
};
|
|
1540
|
+
/**
|
|
1541
|
+
* #DOCUMENTATION states
|
|
1542
|
+
* Get a binary state by ID
|
|
1543
|
+
*
|
|
1544
|
+
* @deprecated
|
|
1545
|
+
* @param _socket Socket instance (not used)
|
|
1546
|
+
* @param id State ID, e.g. `javascript.0.binary`
|
|
1547
|
+
* @param callback Callback `(error: null | undefined | Error | string, base64?: string) => void`
|
|
1548
|
+
*/
|
|
1549
|
+
this.commands.getBinaryState = (_socket, id, callback) => {
|
|
1550
|
+
if (typeof callback === 'function') {
|
|
1551
|
+
this.adapter.log.warn(`getBinaryState is deprecated, but called for ${id}`);
|
|
1552
|
+
callback('This function is deprecated');
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
/**
|
|
1556
|
+
* #DOCUMENTATION states
|
|
1557
|
+
* Set a binary state by ID
|
|
1558
|
+
*
|
|
1559
|
+
* @deprecated
|
|
1560
|
+
* @param _socket Socket instance
|
|
1561
|
+
* @param id State ID, e.g. `javascript.0.binary`
|
|
1562
|
+
* @param _base64 State value as base64 string. Binary states have no acknowledged flag.
|
|
1563
|
+
* @param callback Callback `(error: null | undefined | Error | string) => void`
|
|
1564
|
+
*/
|
|
1565
|
+
this.commands.setBinaryState = (_socket, id, _base64, callback) => {
|
|
1566
|
+
if (typeof callback === 'function') {
|
|
1567
|
+
this.adapter.log.warn(`setBinaryState is deprecated, but called for ${id}`);
|
|
1568
|
+
callback('This function is deprecated');
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
/**
|
|
1572
|
+
* #DOCUMENTATION states
|
|
1573
|
+
* Subscribe to state changes by pattern.
|
|
1574
|
+
* The events will come as 'stateChange' events to the socket.
|
|
1575
|
+
*
|
|
1576
|
+
* @param socket Socket instance
|
|
1577
|
+
* @param pattern Pattern like `system.adapter.*` or array of states like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
|
|
1578
|
+
* @param callback Callback `(error: string | null) => void`
|
|
1579
|
+
*/
|
|
1580
|
+
this.commands.subscribe = (socket, pattern, callback) => {
|
|
1581
|
+
this.#subscribeStates(socket, pattern, callback);
|
|
1582
|
+
};
|
|
1583
|
+
/**
|
|
1584
|
+
* #DOCUMENTATION states
|
|
1585
|
+
* Subscribe to state changes by pattern. Same as `subscribe`.
|
|
1586
|
+
* The events will come as 'stateChange' events to the socket.
|
|
1587
|
+
*
|
|
1588
|
+
* @param socket Socket instance
|
|
1589
|
+
* @param pattern Pattern like `system.adapter.*` or array of states like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
|
|
1590
|
+
* @param callback Callback `(error: string | null) => void`
|
|
1591
|
+
*/
|
|
1592
|
+
this.commands.subscribeStates = (socket, pattern, callback) => {
|
|
1593
|
+
this.#subscribeStates(socket, pattern, callback);
|
|
1594
|
+
};
|
|
1595
|
+
/**
|
|
1596
|
+
* #DOCUMENTATION states
|
|
1597
|
+
* Unsubscribe from state changes by pattern.
|
|
1598
|
+
*
|
|
1599
|
+
* @param socket Socket instance
|
|
1600
|
+
* @param pattern Pattern like `system.adapter.*` or array of states like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
|
|
1601
|
+
* @param callback Callback `(error: string | null) => void`
|
|
1602
|
+
*/
|
|
1603
|
+
this.commands.unsubscribe = (socket, pattern, callback) => {
|
|
1604
|
+
this.#unsubscribeStates(socket, pattern, callback);
|
|
1605
|
+
};
|
|
1606
|
+
/**
|
|
1607
|
+
* #DOCUMENTATION states
|
|
1608
|
+
* Unsubscribe from state changes by pattern. Same as `unsubscribe`.
|
|
1609
|
+
* The events will come as 'stateChange' events to the socket.
|
|
1610
|
+
*
|
|
1611
|
+
* @param socket Socket instance
|
|
1612
|
+
* @param pattern Pattern like `system.adapter.*` or array of states like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
|
|
1613
|
+
* @param callback Callback `(error: string | null) => void`
|
|
1614
|
+
*/
|
|
1615
|
+
this.commands.unsubscribeStates = (socket, pattern, callback) => {
|
|
1616
|
+
this.#unsubscribeStates(socket, pattern, callback);
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
/** Init commands for objects */
|
|
1620
|
+
_initCommandsObjects() {
|
|
1621
|
+
/**
|
|
1622
|
+
* #DOCUMENTATION objects
|
|
1623
|
+
* Get one object.
|
|
1624
|
+
*
|
|
1625
|
+
* @param socket Socket instance
|
|
1626
|
+
* @param id Object ID
|
|
1627
|
+
* @param callback Callback `(error: string | null, obj?: ioBroker.Object) => void`
|
|
1628
|
+
*/
|
|
1629
|
+
this.commands.getObject = (socket, id, callback) => {
|
|
1630
|
+
if (this._checkPermissions(socket, 'getObject', callback, id)) {
|
|
1631
|
+
try {
|
|
1632
|
+
void this.adapter.getForeignObject(id, { user: socket._acl?.user }, (error, obj) => {
|
|
1633
|
+
// overload language from current instance
|
|
1634
|
+
if (this.context.language && id === 'system.config' && obj?.common) {
|
|
1635
|
+
obj.common.language = this.context.language;
|
|
1636
|
+
}
|
|
1637
|
+
SocketCommands._fixCallback(callback, error, obj);
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
catch (error) {
|
|
1641
|
+
this.adapter.log.error(`[getObject] ERROR: ${error.toString()}`);
|
|
1642
|
+
SocketCommands._fixCallback(callback, error);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
/**
|
|
1647
|
+
* #DOCUMENTATION objects
|
|
1648
|
+
* Get all objects that are relevant for web: all states and enums with rooms.
|
|
1649
|
+
* This is non-admin version of "all objects" and will be overloaded in admin
|
|
1650
|
+
*
|
|
1651
|
+
* @param socket Socket instance
|
|
1652
|
+
* @param list Optional list of IDs
|
|
1653
|
+
* @param callback Callback `(error: string | null, objs?: Record<string, ioBroker.Object>) => void`
|
|
1654
|
+
*/
|
|
1655
|
+
this.commands.getObjects = (socket, list, callback) => {
|
|
1656
|
+
if (typeof list === 'function') {
|
|
1657
|
+
callback = list;
|
|
1658
|
+
list = null;
|
|
1659
|
+
}
|
|
1660
|
+
if (list?.length) {
|
|
1661
|
+
if (this._checkPermissions(socket, 'getObject', callback)) {
|
|
1662
|
+
if (typeof callback === 'function') {
|
|
1663
|
+
try {
|
|
1664
|
+
this.adapter.getForeignObjects(list, { user: socket._acl?.user }, (error, objs) => SocketCommands._fixCallback(callback, error, objs));
|
|
1665
|
+
}
|
|
1666
|
+
catch (error) {
|
|
1667
|
+
this.adapter.log.error(`[getObjects] ERROR: ${error.toString()}`);
|
|
1668
|
+
SocketCommands._fixCallback(callback, error);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
else {
|
|
1672
|
+
this.adapter.log.warn('[getObjects] Invalid callback');
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
else if (this._checkPermissions(socket, 'getObjects', callback)) {
|
|
1677
|
+
try {
|
|
1678
|
+
if (typeof callback === 'function') {
|
|
1679
|
+
this.adapter.getForeignObjects('*', 'state', 'rooms', { user: socket._acl?.user }, async (error, states) => {
|
|
1680
|
+
const result = {};
|
|
1681
|
+
try {
|
|
1682
|
+
const channels = await this.adapter.getForeignObjectsAsync('*', 'channel', null, {
|
|
1683
|
+
user: socket._acl?.user,
|
|
1684
|
+
});
|
|
1685
|
+
const devices = await this.adapter.getForeignObjectsAsync('*', 'device', null, {
|
|
1686
|
+
user: socket._acl?.user,
|
|
1687
|
+
});
|
|
1688
|
+
const enums = await this.adapter.getForeignObjectsAsync('*', 'enum', null, {
|
|
1689
|
+
user: socket._acl?.user,
|
|
1690
|
+
});
|
|
1691
|
+
const config = await this.adapter.getForeignObjectAsync('system.config', {
|
|
1692
|
+
user: socket._acl?.user,
|
|
1693
|
+
});
|
|
1694
|
+
Object.assign(result, states, channels, devices, enums);
|
|
1695
|
+
if (config) {
|
|
1696
|
+
result[config._id] = config;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
catch (e) {
|
|
1700
|
+
this.adapter.log.error(`[getObjects] ERROR: ${e.toString()}`);
|
|
1701
|
+
}
|
|
1702
|
+
// overload language
|
|
1703
|
+
if (this.context.language && result['system.config']?.common) {
|
|
1704
|
+
result['system.config'].common.language = this.context.language;
|
|
1705
|
+
}
|
|
1706
|
+
SocketCommands._fixCallback(callback, error, result);
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
else {
|
|
1710
|
+
this.adapter.log.warn('[getObjects] Invalid callback');
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
catch (error) {
|
|
1714
|
+
this.adapter.log.error(`[getObjects] ERROR: ${error.toString()}`);
|
|
1715
|
+
SocketCommands._fixCallback(callback, error);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
/**
|
|
1720
|
+
* #DOCUMENTATION objects
|
|
1721
|
+
* Subscribe to object changes by pattern. The events will come as 'objectChange' events to the socket.
|
|
1722
|
+
*
|
|
1723
|
+
* @param socket Socket instance
|
|
1724
|
+
* @param pattern Pattern like `system.adapter.*` or array of IDs like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
|
|
1725
|
+
* @param callback Callback `(error: string | null) => void`
|
|
1726
|
+
*/
|
|
1727
|
+
this.commands.subscribeObjects = (socket, pattern, callback) => {
|
|
1728
|
+
if (this._checkPermissions(socket, 'subscribeObjects', callback, pattern)) {
|
|
1729
|
+
try {
|
|
1730
|
+
if (Array.isArray(pattern)) {
|
|
1731
|
+
for (let p = 0; p < pattern.length; p++) {
|
|
1732
|
+
this.subscribe(socket, 'objectChange', pattern[p]);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
else {
|
|
1736
|
+
this.subscribe(socket, 'objectChange', pattern);
|
|
1737
|
+
}
|
|
1738
|
+
if (typeof callback === 'function') {
|
|
1739
|
+
setImmediate(callback, null);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
catch (error) {
|
|
1743
|
+
if (typeof callback === 'function') {
|
|
1744
|
+
setImmediate(callback, error);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
/**
|
|
1750
|
+
* #DOCUMENTATION objects
|
|
1751
|
+
* Unsubscribe from object changes by pattern.
|
|
1752
|
+
*
|
|
1753
|
+
* @param socket Socket instance
|
|
1754
|
+
* @param pattern Pattern like `system.adapter.*` or array of IDs like `['system.adapter.admin.0.memRss', 'system.adapter.admin.0.memHeapTotal']`
|
|
1755
|
+
* @param callback Callback `(error: string | null) => void`
|
|
1756
|
+
*/
|
|
1757
|
+
this.commands.unsubscribeObjects = (socket, pattern, callback) => {
|
|
1758
|
+
if (this._checkPermissions(socket, 'unsubscribeObjects', callback, pattern)) {
|
|
1759
|
+
try {
|
|
1760
|
+
if (Array.isArray(pattern)) {
|
|
1761
|
+
for (let p = 0; p < pattern.length; p++) {
|
|
1762
|
+
this.unsubscribe(socket, 'objectChange', pattern[p]);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
else {
|
|
1766
|
+
this.unsubscribe(socket, 'objectChange', pattern);
|
|
1767
|
+
}
|
|
1768
|
+
if (typeof callback === 'function') {
|
|
1769
|
+
setImmediate(callback, null);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
catch (error) {
|
|
1773
|
+
if (typeof callback === 'function') {
|
|
1774
|
+
setImmediate(callback, error);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
/**
|
|
1780
|
+
* #DOCUMENTATION objects
|
|
1781
|
+
* Get a view of objects. Make a query to the object database.
|
|
1782
|
+
*
|
|
1783
|
+
* @param socket Socket instance
|
|
1784
|
+
* @param design Design name, e.g., 'system' or other designs like `custom`, but it must exist object `_design/custom`. To 99,9% use `system`.
|
|
1785
|
+
* @param search Search name, object type, like `state`, `instance`, `adapter`, `host`, ...
|
|
1786
|
+
* @param params Parameters for the query, e.g., `{startkey: 'system.adapter.', endkey: 'system.adapter.\u9999', depth?: number}`
|
|
1787
|
+
* @param params.startkey Start key
|
|
1788
|
+
* @param params.endkey End key. If not provided the `startkey + '\u9999'` will be taken
|
|
1789
|
+
* @param params.depth If the depth is provided, only first level of objects will be returned for smaller size
|
|
1790
|
+
* @param callback Callback `(error: string | null, result?: { rows: Array<GetObjectViewItem>) => void`
|
|
1791
|
+
*/
|
|
1792
|
+
this.commands.getObjectView = (socket, design, search, params, callback) => {
|
|
1793
|
+
if (typeof callback === 'function') {
|
|
1794
|
+
if (this._checkPermissions(socket, 'getObjectView', callback, search)) {
|
|
1795
|
+
try {
|
|
1796
|
+
if (params?.depth) {
|
|
1797
|
+
// To save the bandwidth, the request can define root and depth. Default is depth 1.
|
|
1798
|
+
this.adapter.getObjectView(design, search, params, { user: socket._acl?.user }, (err, result) => {
|
|
1799
|
+
if (result?.rows?.length && result.rows[0].value?._id) {
|
|
1800
|
+
const rows = [];
|
|
1801
|
+
// filter rows
|
|
1802
|
+
const depth = params.depth || 1;
|
|
1803
|
+
let root = params.startkey || '';
|
|
1804
|
+
let rootWithoutDot;
|
|
1805
|
+
if (root) {
|
|
1806
|
+
if (!root.endsWith('.')) {
|
|
1807
|
+
root += '.';
|
|
1808
|
+
rootWithoutDot = root;
|
|
1809
|
+
}
|
|
1810
|
+
else {
|
|
1811
|
+
rootWithoutDot = root.substring(0, root.length - 1);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
else {
|
|
1815
|
+
rootWithoutDot = '';
|
|
1816
|
+
}
|
|
1817
|
+
const rootDepth = root.split('.').length;
|
|
1818
|
+
const virtualObjects = {};
|
|
1819
|
+
for (let r = 0; r < result.rows.length; r++) {
|
|
1820
|
+
const _id = result.rows[r].value._id;
|
|
1821
|
+
if (!root || _id.startsWith(root) || _id === rootWithoutDot) {
|
|
1822
|
+
const parts = _id.split('.');
|
|
1823
|
+
if (parts.length - rootDepth <= depth) {
|
|
1824
|
+
rows.push(result.rows[r]);
|
|
1825
|
+
}
|
|
1826
|
+
else {
|
|
1827
|
+
// create virtual objects to show that there are more objects
|
|
1828
|
+
for (let d = depth; d < parts.length - rootDepth; d++) {
|
|
1829
|
+
const id = parts.slice(0, rootDepth + d).join('.');
|
|
1830
|
+
if (!virtualObjects[id]) {
|
|
1831
|
+
virtualObjects[id] = {
|
|
1832
|
+
id,
|
|
1833
|
+
value: {
|
|
1834
|
+
_id: id,
|
|
1835
|
+
common: {},
|
|
1836
|
+
native: {},
|
|
1837
|
+
type: 'folder',
|
|
1838
|
+
virtual: true,
|
|
1839
|
+
hasChildren: 1,
|
|
1840
|
+
},
|
|
1841
|
+
};
|
|
1842
|
+
rows.push(virtualObjects[id]);
|
|
1843
|
+
}
|
|
1844
|
+
else {
|
|
1845
|
+
virtualObjects[id].value.hasChildren++;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
result.rows = rows;
|
|
1852
|
+
}
|
|
1853
|
+
callback(err, result);
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
else {
|
|
1857
|
+
this.adapter.getObjectView(design, search, params, { user: socket._acl?.user }, callback);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
catch (error) {
|
|
1861
|
+
this.adapter.log.error(`[getObjectView] ERROR: ${error.toString()}`);
|
|
1862
|
+
SocketCommands._fixCallback(callback, error);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
else {
|
|
1867
|
+
this.adapter.log.error('Callback is not a function');
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
/**
|
|
1871
|
+
* #DOCUMENTATION objects
|
|
1872
|
+
* Set an object.
|
|
1873
|
+
*
|
|
1874
|
+
* @param socket Socket instance
|
|
1875
|
+
* @param id Object ID
|
|
1876
|
+
* @param obj Object to set
|
|
1877
|
+
* @param callback Callback `(error: string | null) => void`
|
|
1878
|
+
*/
|
|
1879
|
+
this.commands.setObject = (socket, id, obj, callback) => {
|
|
1880
|
+
if (this._checkPermissions(socket, 'setObject', callback, id)) {
|
|
1881
|
+
try {
|
|
1882
|
+
void this.adapter.setForeignObject(id, obj, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1883
|
+
}
|
|
1884
|
+
catch (error) {
|
|
1885
|
+
this.adapter.log.error(`[setObject] ERROR: ${error.toString()}`);
|
|
1886
|
+
SocketCommands._fixCallback(callback, error);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
};
|
|
1890
|
+
// this function is overloaded in admin
|
|
1891
|
+
/**
|
|
1892
|
+
* #DOCUMENTATION objects
|
|
1893
|
+
* Delete an object. Only deletion of flot and fullcalendar objects is allowed
|
|
1894
|
+
*
|
|
1895
|
+
* @param socket Socket instance
|
|
1896
|
+
* @param id Object ID, like 'flot.0.myChart'
|
|
1897
|
+
* @param _options Options for deletion. Ignored
|
|
1898
|
+
* @param callback Callback `(error: string | null) => void`
|
|
1899
|
+
*/
|
|
1900
|
+
this.commands.delObject = (socket, id, _options, callback) => {
|
|
1901
|
+
if (id.startsWith('flot.') || id.startsWith('fullcalendar.')) {
|
|
1902
|
+
if (this._checkPermissions(socket, 'delObject', callback, id)) {
|
|
1903
|
+
try {
|
|
1904
|
+
this.adapter.delForeignObject(id, { user: socket._acl?.user }, (error, ...args) => SocketCommands._fixCallback(callback, error, ...args));
|
|
1905
|
+
}
|
|
1906
|
+
catch (error) {
|
|
1907
|
+
this.adapter.log.error(`[delObject] ERROR: ${error.toString()}`);
|
|
1908
|
+
SocketCommands._fixCallback(callback, error);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
else {
|
|
1913
|
+
SocketCommands._fixCallback(callback, SocketCommands.ERROR_PERMISSION);
|
|
1914
|
+
}
|
|
1915
|
+
};
|
|
1916
|
+
/**
|
|
1917
|
+
* #DOCUMENTATION commands
|
|
1918
|
+
* Client subscribes to specific instance's messages.
|
|
1919
|
+
* Client informs specific instance about subscription on its messages.
|
|
1920
|
+
* After subscription, the socket will receive "im" messages from desired instance
|
|
1921
|
+
* The target instance MUST acknowledge the subscription and return result
|
|
1922
|
+
*
|
|
1923
|
+
* @param socket Socket instance
|
|
1924
|
+
* @param targetInstance Instance name, e.g., 'cameras.0'
|
|
1925
|
+
* @param messageType Message type, e.g., 'startRecording/cam1'
|
|
1926
|
+
* @param data Optional data object, e.g., {width: 640, height: 480}
|
|
1927
|
+
* @param callback Callback `(error: string | null, result?:{ accepted: boolean; heartbeat?: number; error?: string; }) => void`
|
|
1928
|
+
*/
|
|
1929
|
+
this.commands.clientSubscribe = (socket, targetInstance, messageType, data, callback) => {
|
|
1930
|
+
if (typeof data === 'function') {
|
|
1931
|
+
callback = data;
|
|
1932
|
+
data = null;
|
|
1933
|
+
}
|
|
1934
|
+
if (!targetInstance.startsWith('system.adapter.')) {
|
|
1935
|
+
targetInstance = `system.adapter.${targetInstance}`;
|
|
1936
|
+
}
|
|
1937
|
+
const sid = socket.id;
|
|
1938
|
+
// GUI subscribes for messages from targetInstance
|
|
1939
|
+
this.#clientSubscribes[sid] = this.#clientSubscribes[sid] || {};
|
|
1940
|
+
this.#clientSubscribes[sid][targetInstance] = this.#clientSubscribes[sid][targetInstance] || [];
|
|
1941
|
+
if (!this.#clientSubscribes[sid][targetInstance].includes(messageType)) {
|
|
1942
|
+
this.#clientSubscribes[sid][targetInstance].push(messageType);
|
|
1943
|
+
}
|
|
1944
|
+
// inform instance about new subscription
|
|
1945
|
+
this.adapter.sendTo(targetInstance, 'clientSubscribe', { type: messageType, sid, data }, result => SocketCommands._fixCallback(callback, null, result));
|
|
1946
|
+
};
|
|
1947
|
+
/**
|
|
1948
|
+
* #DOCUMENTATION commands
|
|
1949
|
+
* Client unsubscribes from specific instance's messages.
|
|
1950
|
+
* The target instance MUST NOT acknowledge the un-subscription
|
|
1951
|
+
*
|
|
1952
|
+
* @param socket Socket instance
|
|
1953
|
+
* @param targetInstance Instance name, e.g., 'cameras.0'
|
|
1954
|
+
* @param messageType Message type, e.g., 'startRecording/cam1'
|
|
1955
|
+
* @param callback Callback `(error: string | null) => void`
|
|
1956
|
+
*/
|
|
1957
|
+
this.commands.clientUnsubscribe = (socket, targetInstance, messageType, callback) => {
|
|
1958
|
+
const sid = socket.id;
|
|
1959
|
+
if (!targetInstance.startsWith('system.adapter.')) {
|
|
1960
|
+
targetInstance = `system.adapter.${targetInstance}`;
|
|
1961
|
+
}
|
|
1962
|
+
// GUI unsubscribes for messages from targetInstance
|
|
1963
|
+
if (this.#clientSubscribes[sid] && this.#clientSubscribes[sid][targetInstance]) {
|
|
1964
|
+
const pos = this.#clientSubscribes[sid][targetInstance].indexOf(messageType);
|
|
1965
|
+
if (pos !== -1) {
|
|
1966
|
+
this.#clientSubscribes[sid][targetInstance].splice(pos, 1);
|
|
1967
|
+
// inform instance about unsubscription
|
|
1968
|
+
this.adapter.sendTo(targetInstance, 'clientUnsubscribe', {
|
|
1969
|
+
type: [messageType],
|
|
1970
|
+
sid,
|
|
1971
|
+
reason: 'client',
|
|
1972
|
+
});
|
|
1973
|
+
SocketCommands._fixCallback(callback, null, true);
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
SocketCommands._fixCallback(callback, null, false);
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
/** Init all commands: common, objects, states, files */
|
|
1981
|
+
#initCommands() {
|
|
1982
|
+
this._initCommandsCommon();
|
|
1983
|
+
this._initCommandsObjects();
|
|
1984
|
+
this._initCommandsStates();
|
|
1985
|
+
this._initCommandsFiles();
|
|
1986
|
+
}
|
|
1987
|
+
#informAboutDisconnect(socketId) {
|
|
1988
|
+
// say to all instances, that this socket was disconnected
|
|
1989
|
+
if (this.#clientSubscribes[socketId]) {
|
|
1990
|
+
Object.keys(this.#clientSubscribes[socketId]).forEach(targetInstance => {
|
|
1991
|
+
this.adapter.sendTo(targetInstance, 'clientUnsubscribe', {
|
|
1992
|
+
type: this.#clientSubscribes[socketId][targetInstance],
|
|
1993
|
+
sid: socketId,
|
|
1994
|
+
reason: 'disconnect',
|
|
1995
|
+
});
|
|
1996
|
+
});
|
|
1997
|
+
delete this.#clientSubscribes[socketId];
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
applyCommands(socket) {
|
|
2001
|
+
Object.keys(this.commands).forEach(command => socket.on(command, (...args) => {
|
|
2002
|
+
if (this.#updateSession(socket)) {
|
|
2003
|
+
this.commands[command](socket, ...args);
|
|
2004
|
+
}
|
|
2005
|
+
}));
|
|
2006
|
+
}
|
|
2007
|
+
disableEventThreshold() {
|
|
2008
|
+
// could be overloaded
|
|
2009
|
+
}
|
|
2010
|
+
destroy() {
|
|
2011
|
+
// could be overloaded
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
exports.SocketCommands = SocketCommands;
|
|
2015
|
+
//# sourceMappingURL=socketCommands.js.map
|