iobroker.rest-api 2.0.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -8
- package/admin/i18n/{de/translations.json → de.json} +22 -22
- package/admin/i18n/{en/translations.json → en.json} +22 -23
- package/admin/i18n/{es/translations.json → es.json} +22 -23
- package/admin/i18n/{fr/translations.json → fr.json} +22 -23
- package/admin/i18n/{it/translations.json → it.json} +22 -23
- package/admin/i18n/{nl/translations.json → nl.json} +22 -23
- package/admin/i18n/{pl/translations.json → pl.json} +22 -23
- package/admin/i18n/{pt/translations.json → pt.json} +22 -23
- package/admin/i18n/{ru/translations.json → ru.json} +22 -23
- package/admin/i18n/uk.json +32 -0
- package/admin/i18n/{zh-cn/translations.json → zh-cn.json} +22 -23
- package/admin/jsonConfig.json +178 -180
- package/admin/rest-api.svg +8 -0
- package/dist/lib/api/controllers/common.js +129 -0
- package/dist/lib/api/controllers/common.js.map +1 -0
- package/dist/lib/api/controllers/enum.js +58 -0
- package/dist/lib/api/controllers/enum.js.map +1 -0
- package/dist/lib/api/controllers/file.js +104 -0
- package/dist/lib/api/controllers/file.js.map +1 -0
- package/dist/lib/api/controllers/history.js +262 -0
- package/dist/lib/api/controllers/history.js.map +1 -0
- package/dist/lib/api/controllers/object.js +346 -0
- package/dist/lib/api/controllers/object.js.map +1 -0
- package/dist/lib/api/controllers/sendTo.js +118 -0
- package/dist/lib/api/controllers/sendTo.js.map +1 -0
- package/dist/lib/api/controllers/state.js +545 -0
- package/dist/lib/api/controllers/state.js.map +1 -0
- package/dist/lib/api/swagger/swagger.yaml +2551 -0
- package/{lib → dist/lib}/common.js +8 -10
- package/dist/lib/common.js.map +1 -0
- package/dist/lib/rest-api.js +1216 -0
- package/dist/lib/rest-api.js.map +1 -0
- package/dist/main.js +159 -0
- package/dist/main.js.map +1 -0
- package/examples/demoBrowserClient.html +95 -82
- package/examples/demoNodeClient.js +8 -7
- package/examples/longPolling.js +46 -45
- package/io-package.json +43 -34
- package/package.json +36 -24
- package/lib/api/controllers/common.js +0 -150
- package/lib/api/controllers/enum.js +0 -44
- package/lib/api/controllers/file.js +0 -74
- package/lib/api/controllers/history.js +0 -239
- package/lib/api/controllers/object.js +0 -273
- package/lib/api/controllers/sendTo.js +0 -123
- package/lib/api/controllers/state.js +0 -565
- package/lib/api/swagger/swagger.yaml +0 -2624
- package/lib/rest-api.js +0 -1123
- package/main.js +0 -173
- /package/{lib → dist/lib}/config/default.yaml +0 -0
package/lib/rest-api.js
DELETED
|
@@ -1,1123 +0,0 @@
|
|
|
1
|
-
/* jshint -W097 */
|
|
2
|
-
/* jshint strict: false */
|
|
3
|
-
/* jslint node: true */
|
|
4
|
-
/* jshint -W061 */
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
const SwaggerRunner = require('swagger-node-runner-fork');
|
|
8
|
-
const swaggerUi = require('swagger-ui-express');
|
|
9
|
-
const YAML = require('yamljs');
|
|
10
|
-
const bodyParser = require('body-parser');
|
|
11
|
-
const crypto = require('crypto');
|
|
12
|
-
const axios = require('axios');
|
|
13
|
-
const cors = require('cors');
|
|
14
|
-
const fs = require('fs');
|
|
15
|
-
const path = require('path');
|
|
16
|
-
const multer = require('multer');
|
|
17
|
-
const utils = require('@iobroker/adapter-core'); // Get common adapter utils
|
|
18
|
-
const pattern2RegEx = utils.commonTools.pattern2RegEx;
|
|
19
|
-
const CommandsAdmin = require('@iobroker/socket-classes').SocketCommandsAdmin;
|
|
20
|
-
const CommandsCommon = require('@iobroker/socket-classes').SocketCommands;
|
|
21
|
-
const common = require('./common');
|
|
22
|
-
|
|
23
|
-
process.env.SUPPRESS_NO_CONFIG_WARNING = 'true';
|
|
24
|
-
|
|
25
|
-
const WEB_EXTENSION_PREFIX = 'rest-api/';
|
|
26
|
-
|
|
27
|
-
/*const memStore = { };
|
|
28
|
-
|
|
29
|
-
// Writable memory stream
|
|
30
|
-
class WMStrm extends Writable {
|
|
31
|
-
constructor(key, options) {
|
|
32
|
-
super(options); // init super
|
|
33
|
-
this.key = key; // save key
|
|
34
|
-
this.data = Buffer.from(''); // empty
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
_write(chunk, enc, cb) {
|
|
38
|
-
// our memory store stores things in buffers
|
|
39
|
-
const buffer = (Buffer.isBuffer(chunk)) ? chunk : Buffer.from(chunk, enc);
|
|
40
|
-
|
|
41
|
-
// concat to the buffer already there
|
|
42
|
-
this.data = Buffer.concat([this.data, buffer]);
|
|
43
|
-
cb();
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
*/
|
|
47
|
-
function parseQuery(_url) {
|
|
48
|
-
let url = decodeURI(_url);
|
|
49
|
-
const pos = url.indexOf('?');
|
|
50
|
-
const values = {};
|
|
51
|
-
if (pos !== -1) {
|
|
52
|
-
const arr = url.substring(pos + 1).split('&');
|
|
53
|
-
url = url.substring(0, pos);
|
|
54
|
-
|
|
55
|
-
for (let i = 0; i < arr.length; i++) {
|
|
56
|
-
arr[i] = arr[i].split('=');
|
|
57
|
-
values[arr[i][0].trim()] = arr[i][1] === undefined ? null : decodeURIComponent((arr[i][1] + '').replace(/\+/g, '%20'));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Default value for wait
|
|
61
|
-
if (values.timeout === null) {
|
|
62
|
-
values.timeout = 2000;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const parts = url.split('/');
|
|
67
|
-
|
|
68
|
-
// Analyse system.adapter.socketio.0.uptime,system.adapter.history.0.memRss?value=78&timeout=300
|
|
69
|
-
if (parts[2]) {
|
|
70
|
-
const oId = parts[2].split(',');
|
|
71
|
-
for (let j = oId.length - 1; j >= 0; j--) {
|
|
72
|
-
oId[j] = oId[j].trim();
|
|
73
|
-
if (!oId[j]) {
|
|
74
|
-
oId.splice(j, 1);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return values;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// copied from here: https://github.com/component/escape-html/blob/master/index.js
|
|
83
|
-
const matchHtmlRegExp = /["'&<>]/;
|
|
84
|
-
function escapeHtml(string) {
|
|
85
|
-
const str = '' + string;
|
|
86
|
-
const match = matchHtmlRegExp.exec(str);
|
|
87
|
-
|
|
88
|
-
if (!match) {
|
|
89
|
-
return str;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
let escape;
|
|
93
|
-
let html = '';
|
|
94
|
-
let index = 0;
|
|
95
|
-
let lastIndex = 0;
|
|
96
|
-
|
|
97
|
-
for (index = match.index; index < str.length; index++) {
|
|
98
|
-
switch (str.charCodeAt(index)) {
|
|
99
|
-
case 34: // "
|
|
100
|
-
escape = '"';
|
|
101
|
-
break;
|
|
102
|
-
case 38: // &
|
|
103
|
-
escape = '&';
|
|
104
|
-
break;
|
|
105
|
-
case 39: // '
|
|
106
|
-
escape = ''';
|
|
107
|
-
break;
|
|
108
|
-
case 60: // <
|
|
109
|
-
escape = '<';
|
|
110
|
-
break;
|
|
111
|
-
case 62: // >
|
|
112
|
-
escape = '>';
|
|
113
|
-
break;
|
|
114
|
-
default:
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (lastIndex !== index) {
|
|
119
|
-
html += str.substring(lastIndex, index);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
lastIndex = index + 1;
|
|
123
|
-
html += escape;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return lastIndex !== index
|
|
127
|
-
? html + str.substring(lastIndex, index)
|
|
128
|
-
: html;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function decorateLogFile(filename, text) {
|
|
132
|
-
const prefix = '<html><head>' +
|
|
133
|
-
'<style>\n' +
|
|
134
|
-
' table {' +
|
|
135
|
-
' font-family: monospace;\n' +
|
|
136
|
-
' font-size: 14px;\n' +
|
|
137
|
-
' }\n' +
|
|
138
|
-
' .info {\n' +
|
|
139
|
-
' background: white;' +
|
|
140
|
-
' }\n' +
|
|
141
|
-
' .type {\n' +
|
|
142
|
-
' font-weight: bold;' +
|
|
143
|
-
' }\n' +
|
|
144
|
-
' .silly {\n' +
|
|
145
|
-
' background: #b3b3b3;' +
|
|
146
|
-
' }\n' +
|
|
147
|
-
' .debug {\n' +
|
|
148
|
-
' background: lightgray;' +
|
|
149
|
-
' }\n' +
|
|
150
|
-
' .warn {\n' +
|
|
151
|
-
' background: #ffdb75;' +
|
|
152
|
-
' color: white;' +
|
|
153
|
-
' }\n' +
|
|
154
|
-
' .error {\n' +
|
|
155
|
-
' background: #ff6a5b;' +
|
|
156
|
-
' }\n' +
|
|
157
|
-
'</style>\n' +
|
|
158
|
-
'<script>\n' +
|
|
159
|
-
'function decorate (line) {\n' +
|
|
160
|
-
' var className = "info";\n' +
|
|
161
|
-
' line = line.replace(/\\x1B\\[39m/g, "</span>");\n' +
|
|
162
|
-
' if (line.indexOf("[32m") !== -1) {\n' +
|
|
163
|
-
' className = "info";\n'+
|
|
164
|
-
' line = line.replace(/\\x1B\\[32m/g, "<span class=\\"type\\">");\n' +
|
|
165
|
-
' } else \n' +
|
|
166
|
-
' if (line.indexOf("[34m") !== -1) {\n' +
|
|
167
|
-
' className = "debug";\n'+
|
|
168
|
-
' line = line.replace(/\\x1B\\[34m/g, "<span class=\\"type\\">");\n' +
|
|
169
|
-
' } else \n' +
|
|
170
|
-
' if (line.indexOf("[33m") !== -1) {\n' +
|
|
171
|
-
' className = "warn";\n'+
|
|
172
|
-
' line = line.replace(/\\x1B\\[33m/g, "<span class=\\"type\\">");\n' +
|
|
173
|
-
' } else \n' +
|
|
174
|
-
' if (line.indexOf("[31m") !== -1) {\n' +
|
|
175
|
-
' className = "error";\n'+
|
|
176
|
-
' line = line.replace(/\\x1B\\[31m/g, "<span class=\\"type\\">");\n' +
|
|
177
|
-
' } else \n' +
|
|
178
|
-
' if (line.indexOf("[35m") !== -1) {\n' +
|
|
179
|
-
' className = "silly";\n'+
|
|
180
|
-
' line = line.replace(/\\x1B\\[35m/g, "<span class=\\"type\\">");\n' +
|
|
181
|
-
' } else {\n' +
|
|
182
|
-
' }\n' +
|
|
183
|
-
' return "<tr class=\\"" + className + "\\"><td>" + line + "</td></tr>";\n'+
|
|
184
|
-
'}\n' +
|
|
185
|
-
'document.addEventListener("DOMContentLoaded", function () { \n' +
|
|
186
|
-
' var text = document.body.innerHTML;\n' +
|
|
187
|
-
' var lines = text.split("\\n");\n' +
|
|
188
|
-
' text = "<table>";\n' +
|
|
189
|
-
' for (var i = 0; i < lines.length; i++) {\n' +
|
|
190
|
-
' if (lines[i]) text += decorate(lines[i]);\n' +
|
|
191
|
-
' }\n' +
|
|
192
|
-
' text += "</table>";\n' +
|
|
193
|
-
' document.body.innerHTML = text;\n' +
|
|
194
|
-
' window.scrollTo(0,document.body.scrollHeight);\n' +
|
|
195
|
-
'});\n' +
|
|
196
|
-
'</script>\n</head>\n<body>\n';
|
|
197
|
-
const suffix = '</body></html>';
|
|
198
|
-
const log = text || fs.readFileSync(filename).toString();
|
|
199
|
-
return prefix + log + suffix;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function removeTextFromFile(fileName, start, end) {
|
|
203
|
-
let file = fs.readFileSync(fileName).toString('utf8').split('\n');
|
|
204
|
-
// find <!-- START -->
|
|
205
|
-
let newFile = [];
|
|
206
|
-
let foundStart = false;
|
|
207
|
-
let foundEnd = false;
|
|
208
|
-
for (let f = 0; f < file.length; f++) {
|
|
209
|
-
if (!foundStart && file[f].includes(start)) {
|
|
210
|
-
foundStart = true;
|
|
211
|
-
continue;
|
|
212
|
-
} else
|
|
213
|
-
if (file[f].includes(end)) {
|
|
214
|
-
foundEnd = true;
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
if (!foundStart || foundEnd) {
|
|
218
|
-
newFile.push(file[f]);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return newFile.join('\n');
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* SwaggerUI class
|
|
226
|
-
*
|
|
227
|
-
* From settings used only secure, auth and crossDomain
|
|
228
|
-
*
|
|
229
|
-
* @class
|
|
230
|
-
* @param {object} _ignore not used in this web extension
|
|
231
|
-
* @param {object} webSettings settings of the web server, like <pre><code>{secure: settings.secure, port: settings.port}</code></pre>
|
|
232
|
-
* @param {object} adapter web adapter object
|
|
233
|
-
* @param {object} instanceSettings instance object with common and native
|
|
234
|
-
* @param {object} app express application
|
|
235
|
-
* @param {function} callback called when the engine is initialized
|
|
236
|
-
* @return {object} object instance
|
|
237
|
-
*/
|
|
238
|
-
function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callback) {
|
|
239
|
-
if (!(this instanceof SwaggerUI)) {
|
|
240
|
-
return new SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callback);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (typeof instanceSettings === 'function') {
|
|
244
|
-
callback = instanceSettings;
|
|
245
|
-
instanceSettings = null;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
this.app = app || require('express')();
|
|
249
|
-
this.adapter = adapter;
|
|
250
|
-
this.settings = webSettings;
|
|
251
|
-
this.config = instanceSettings ? instanceSettings.native : adapter.config;
|
|
252
|
-
this.namespace = instanceSettings ? instanceSettings._id.substring('system.adapter.'.length) : this.adapter.namespace;
|
|
253
|
-
this.subscribes = {};
|
|
254
|
-
this.checkInterval = null;
|
|
255
|
-
this.extension = !!instanceSettings;
|
|
256
|
-
this.routerPrefix = this.extension ? `/${WEB_EXTENSION_PREFIX}` : '/';
|
|
257
|
-
this.gcInterval = null;
|
|
258
|
-
this.commands = this.adapter.config.noAdminCommands ? new CommandsCommon(adapter) : new CommandsAdmin(adapter);
|
|
259
|
-
|
|
260
|
-
this.config.defaultUser = this.extension ? this.config.defaultUser : this.config.defaultUser || 'system.user.admin';
|
|
261
|
-
|
|
262
|
-
this.config.checkInterval = this.config.checkInterval === undefined ? 20000 : parseInt(this.config.checkInterval, 10);
|
|
263
|
-
if (this.config.checkInterval && this.config.checkInterval < 5000) {
|
|
264
|
-
this.config.checkInterval = 5000;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
this.config.hookTimeout = parseInt(this.config.hookTimeout, 10) || 3000;
|
|
268
|
-
if (this.config.hookTimeout && this.config.hookTimeout < 50) {
|
|
269
|
-
this.config.hookTimeout = 50;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (this.config.onlyAllowWhenUserIsOwner === undefined) {
|
|
273
|
-
this.config.onlyAllowWhenUserIsOwner = false;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (!this.config.defaultUser.match(/^system\.user\./)) {
|
|
277
|
-
this.config.defaultUser = 'system.user.' + this.config.defaultUser;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// enable cors only if standalone
|
|
281
|
-
!instanceSettings && this.app.use(cors());
|
|
282
|
-
const jsonParser = bodyParser.json({
|
|
283
|
-
limit: '100mb',
|
|
284
|
-
});
|
|
285
|
-
const rawParser = bodyParser.raw({
|
|
286
|
-
limit: '100mb',
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
this.app.use((req, res, next) => {
|
|
290
|
-
if (req.method !== 'GET' && req.url.startsWith('/v1/binary/')) {
|
|
291
|
-
rawParser(req, res, next);
|
|
292
|
-
} else {
|
|
293
|
-
jsonParser(req, res, next);
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
/*this.app.use(bodyParser.json({
|
|
297
|
-
limit: '100mb',
|
|
298
|
-
}));
|
|
299
|
-
this.app.use(bodyParser.urlencoded({
|
|
300
|
-
extended: true,
|
|
301
|
-
parameterLimit: 100000,
|
|
302
|
-
limit: '100mb',
|
|
303
|
-
}));*/
|
|
304
|
-
|
|
305
|
-
const _options = {
|
|
306
|
-
appRoot: __dirname,
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
// prepare yaml
|
|
310
|
-
_options.swaggerFile = `${__dirname}/api/swagger/swagger.yaml`;
|
|
311
|
-
if (this.adapter.config.noCommands) {
|
|
312
|
-
const newText = removeTextFromFile(_options.swaggerFile, '# commands start', '# commands stop');
|
|
313
|
-
|
|
314
|
-
_options.swaggerFile = `${__dirname}/api/swagger/swaggerEdited.yaml`;
|
|
315
|
-
if (!fs.existsSync(_options.swaggerFile) || fs.readFileSync(_options.swaggerFile).toString('utf8') !== newText) {
|
|
316
|
-
fs.writeFileSync(_options.swaggerFile, newText);
|
|
317
|
-
}
|
|
318
|
-
} else if (this.adapter.config.noAdminCommands) {
|
|
319
|
-
const newText = removeTextFromFile(_options.swaggerFile, '# admin commands start', '# admin commands end');
|
|
320
|
-
_options.swaggerFile = `${__dirname}/api/swagger/swaggerEdited.yaml`;
|
|
321
|
-
if (!fs.existsSync(_options.swaggerFile) || fs.readFileSync(_options.swaggerFile).toString('utf8') !== newText) {
|
|
322
|
-
fs.writeFileSync(_options.swaggerFile, newText);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// create swagger.yaml copy with changed basePath
|
|
327
|
-
if (this.extension) {
|
|
328
|
-
let file = fs.readFileSync(_options.swaggerFile).toString('utf8')
|
|
329
|
-
file = file.replace('basePath: "/v1"', `basePath: "/${WEB_EXTENSION_PREFIX}v1"`);
|
|
330
|
-
|
|
331
|
-
_options.swaggerFile = `${__dirname}/api/swagger/swagger_extension.yaml`;
|
|
332
|
-
|
|
333
|
-
if (!fs.existsSync(_options.swaggerFile) || fs.readFileSync(_options.swaggerFile).toString('utf8') !== file) {
|
|
334
|
-
fs.writeFileSync(_options.swaggerFile, file);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
let swaggerDocument = YAML.load(_options.swaggerFile);
|
|
339
|
-
if (this.extension) {
|
|
340
|
-
swaggerDocument.basePath = `/${WEB_EXTENSION_PREFIX}v1`;
|
|
341
|
-
}
|
|
342
|
-
const that = this;
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (!this.config.noUI) {
|
|
346
|
-
this.app.get(`${this.routerPrefix}api-docs/swagger.json`, (req, res) =>
|
|
347
|
-
res.json(swaggerDocument));
|
|
348
|
-
|
|
349
|
-
const options = {
|
|
350
|
-
customCss: '.swagger-ui .topbar { background-color: #4dabf5; }',
|
|
351
|
-
};
|
|
352
|
-
// show WEB CSS and so on
|
|
353
|
-
this.app.use(`${this.routerPrefix}api-doc/`, swaggerUi.serve, swaggerUi.setup(swaggerDocument, options));
|
|
354
|
-
this.app.get(this.routerPrefix, (req, res) =>
|
|
355
|
-
res.redirect(`${this.routerPrefix}api-doc/`));
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function isAuthenticated(req, res, callback) {
|
|
359
|
-
if (that.config.auth) {
|
|
360
|
-
let values = parseQuery(req.url);
|
|
361
|
-
if (!values.user || !values.pass) {
|
|
362
|
-
if (req.headers.authorization && req.headers.authorization.startsWith('Basic ')) {
|
|
363
|
-
const auth = Buffer.from(req.headers.authorization.substring(6), 'base64').toString('utf8');
|
|
364
|
-
const pos = auth.indexOf(':');
|
|
365
|
-
if (pos !== -1) {
|
|
366
|
-
values = {
|
|
367
|
-
user: auth.substring(0, pos),
|
|
368
|
-
pass: auth.substring(pos + 1)
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
if (!values.user || !values.pass) {
|
|
373
|
-
res.status(401).send({error: 'User is required'});
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (!values.user.match(/^system\.user\./)) {
|
|
378
|
-
values.user = `system.user.${values.user}`;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
that.adapter.checkPassword(values.user, values.pass, result => {
|
|
382
|
-
if (result) {
|
|
383
|
-
req._user = values.user;
|
|
384
|
-
// that.adapter.log.debug(`Logged in: ${values.user}`);
|
|
385
|
-
callback(true);
|
|
386
|
-
} else {
|
|
387
|
-
callback = null;
|
|
388
|
-
that.adapter.log.warn(`Invalid password or user name: ${values.user}`);
|
|
389
|
-
res.status(401).send({error: `Invalid password or user name: ${values.user}`});
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
} else if (callback) {
|
|
393
|
-
req._user = that.config.defaultUser;
|
|
394
|
-
callback();
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
async function _validateUrlHook(item) {
|
|
399
|
-
try {
|
|
400
|
-
await axios.post(item.urlHook, {test: true}, {
|
|
401
|
-
timeout: that.config.hookTimeout,
|
|
402
|
-
validateStatus: status => status < 400
|
|
403
|
-
});
|
|
404
|
-
} catch (error) {
|
|
405
|
-
if (error.response) {
|
|
406
|
-
that.adapter.log.warn(`Cannot report to hook "${item.urlHook}": ${error.response.data || error.response.status}`);
|
|
407
|
-
} else {
|
|
408
|
-
that.adapter.log.warn(`Cannot report to hook "${item.urlHook}": ${JSON.stringify(error)}`);
|
|
409
|
-
}
|
|
410
|
-
item.errors = item.errors || 0;
|
|
411
|
-
item.errors++;
|
|
412
|
-
if (item.errors > 2) {
|
|
413
|
-
that.adapter.log.warn(`3 errors by "${item.urlHook}": all subscriptions removed`);
|
|
414
|
-
await that.unregisterSubscribe(item.urlHook, null, 'state');
|
|
415
|
-
await that.unregisterSubscribe(item.urlHook, null, 'object');
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
return 'Cannot validate URL';
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
async function reportChange(item, data) {
|
|
423
|
-
if (item.polling) {
|
|
424
|
-
if (item.promise) {
|
|
425
|
-
item.promise.resolve(JSON.stringify(data));
|
|
426
|
-
} else {
|
|
427
|
-
item.queue = item.queue || [];
|
|
428
|
-
const now = Date.now();
|
|
429
|
-
item.queue.push({data: JSON.stringify(data), ts: now});
|
|
430
|
-
|
|
431
|
-
// delete too old entries
|
|
432
|
-
for (let d = item.queue.length - 1; d >= 0; d--) {
|
|
433
|
-
if (now - item.queue[d].ts > 3000) {
|
|
434
|
-
that.adapter.log.debug(`[${item.urlHook}] Data update skipped, as no handler (${d + 1})`);
|
|
435
|
-
item.queue.splice(0, d);
|
|
436
|
-
break;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
} else {
|
|
441
|
-
try {
|
|
442
|
-
await axios.post(item.urlHook, data, {
|
|
443
|
-
timeout: that.config.hookTimeout,
|
|
444
|
-
validateStatus: status => status < 400
|
|
445
|
-
});
|
|
446
|
-
} catch (error) {
|
|
447
|
-
if (error.response) {
|
|
448
|
-
that.adapter.log.warn(`Cannot report to hook "${item.urlHook}": ${error.response.data || error.response.status}`);
|
|
449
|
-
} else {
|
|
450
|
-
that.adapter.log.warn(`Cannot report to hook "${item.urlHook}": ${JSON.stringify(error)}`);
|
|
451
|
-
}
|
|
452
|
-
item.errors = item.errors || 0;
|
|
453
|
-
item.errors++;
|
|
454
|
-
if (item.errors > 2) {
|
|
455
|
-
that.adapter.log.warn(`3 errors by "${item.urlHook}": all subscriptions removed`);
|
|
456
|
-
await that.unregisterSubscribe(item.urlHook, null, 'state');
|
|
457
|
-
await that.unregisterSubscribe(item.urlHook, null, 'object');
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
this._checkHooks = async () => {
|
|
464
|
-
const hooks = Object.keys(this.subscribes);
|
|
465
|
-
for (let i = 0; i < hooks.length; i++) {
|
|
466
|
-
if (!this.subscribes[hooks[i]].polling) {
|
|
467
|
-
await _validateUrlHook(this.subscribes[hooks[i]]);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
this._executeGC = () => {
|
|
473
|
-
const hashes = Object.keys(this.subscribes)
|
|
474
|
-
.filter(urlHash => this.subscribes[urlHash].polling);
|
|
475
|
-
|
|
476
|
-
if (!hashes.length) {
|
|
477
|
-
clearInterval(this.gcInterval);
|
|
478
|
-
this.gcInterval = null;
|
|
479
|
-
} else {
|
|
480
|
-
const now = Date.now();
|
|
481
|
-
hashes.forEach(async urlHash => {
|
|
482
|
-
// kill all subscriptions after 2 minutes
|
|
483
|
-
if (now - this.subscribes[urlHash].ts > (this.subscribes[urlHash].timeout || 30000) * 1.5) {
|
|
484
|
-
if (this.subscribes[urlHash].promise) {
|
|
485
|
-
debugger;
|
|
486
|
-
// this should never happen
|
|
487
|
-
this.subscribes[urlHash].promise.resolve();
|
|
488
|
-
}
|
|
489
|
-
// unsubscribe
|
|
490
|
-
if (this.subscribes[urlHash].state) {
|
|
491
|
-
for (let i = 0; i < this.subscribes[urlHash].state.length; i++) {
|
|
492
|
-
this.adapter.log.debug(`[${this.subscribes[urlHash].urlHook}] unsubscribe from state: ${this.subscribes[urlHash].state[i].id}`);
|
|
493
|
-
await this.adapter.unsubscribeForeignStatesAsync(this.subscribes[urlHash].state[i].id);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
if (this.subscribes[urlHash].object) {
|
|
497
|
-
for (let i = 0; i < this.subscribes[urlHash].object.length; i++) {
|
|
498
|
-
this.adapter.log.debug(`[${this.subscribes[urlHash].urlHook}] unsubscribe from object: ${this.subscribes[urlHash].object[i].id}`);
|
|
499
|
-
await this.adapter.unsubscribeForeignObjectsAsync(this.subscribes[urlHash].object[i].id);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
this.adapter.log.debug(`[${this.subscribes[urlHash].urlHook}] Destroy connection due inactivity`);
|
|
504
|
-
|
|
505
|
-
delete this.subscribes[urlHash];
|
|
506
|
-
}
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
this.startGC = () => {
|
|
512
|
-
this.gcInterval = this.gcInterval || setInterval(() => this._executeGC(), 30000);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
this.registerSubscribe = async (urlHook, id, type, user, options) => {
|
|
516
|
-
if (typeof options === 'string') {
|
|
517
|
-
options = {method: options};
|
|
518
|
-
}
|
|
519
|
-
if (options.delta) {
|
|
520
|
-
options.delta = parseFloat(options.delta);
|
|
521
|
-
} else {
|
|
522
|
-
delete options.delta;
|
|
523
|
-
}
|
|
524
|
-
if (options.onchange) {
|
|
525
|
-
options.onchange = options.onchange === true || options.onchange === 'true';
|
|
526
|
-
} else {
|
|
527
|
-
delete options.onchange;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const urlHash = crypto.createHash('md5').update(urlHook).digest('hex');
|
|
531
|
-
if (!this.subscribes[urlHash]) {
|
|
532
|
-
if (options.method !== 'polling') {
|
|
533
|
-
const error = await _validateUrlHook({urlHook});
|
|
534
|
-
if (error) {
|
|
535
|
-
return `No valid answer from URL hook: ${error}`;
|
|
536
|
-
}
|
|
537
|
-
} else
|
|
538
|
-
if (options.method === 'polling') {
|
|
539
|
-
this.adapter.log.debug(`[${urlHook}] Subscribe on connection`);
|
|
540
|
-
this.startGC();
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
this.subscribes[urlHash] = {
|
|
544
|
-
state: [],
|
|
545
|
-
object: [],
|
|
546
|
-
timeout: 30000,
|
|
547
|
-
urlHook,
|
|
548
|
-
polling: options.method === 'polling',
|
|
549
|
-
ts: Date.now()
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
if (!this.subscribes[urlHash][type].find(item => item.id === id && (!item.method || item.method === options.method))) {
|
|
554
|
-
const item = {id, delta: options.delta, onchange: options.onchange};
|
|
555
|
-
this.subscribes[urlHash][type].push(item);
|
|
556
|
-
if (item.id.includes('*')) {
|
|
557
|
-
item.regEx = new RegExp(pattern2RegEx(item.id));
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
if (type === 'state') {
|
|
561
|
-
this.adapter.log.debug(`[${urlHook}] Subscribe on state "${id}"`);
|
|
562
|
-
await this.adapter.subscribeForeignStatesAsync(id, {user, limitToOwnerRights: adapter.config.onlyAllowWhenUserIsOwner});
|
|
563
|
-
if (!item.regEx && (options.onchange || options.delta)) {
|
|
564
|
-
item.val = await this.adapter.getForeignStateAsync(id, {user, limitToOwnerRights: adapter.config.onlyAllowWhenUserIsOwner});
|
|
565
|
-
if (item.val) {
|
|
566
|
-
item.val = item.val.val;
|
|
567
|
-
} else {
|
|
568
|
-
item.val = null;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
} else {
|
|
572
|
-
this.adapter.log.debug(`[${urlHook}] Subscribe on object "${id}"`);
|
|
573
|
-
await this.adapter.subscribeForeignObjectsAsync(id, {user, limitToOwnerRights: adapter.config.onlyAllowWhenUserIsOwner});
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
if (this.config.checkInterval) {
|
|
578
|
-
if (this.checkInterval) {
|
|
579
|
-
this.adapter.log.debug(`start checker`);
|
|
580
|
-
this.checkInterval = setInterval(async () => await this._checkHooks(), this.config.checkInterval);
|
|
581
|
-
}
|
|
582
|
-
} else {
|
|
583
|
-
this.adapter.log.warn('No check interval set! The connections are valid forever.');
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
return null; // success
|
|
587
|
-
};
|
|
588
|
-
|
|
589
|
-
this.getSubscribes = async (urlHook, id, type) => {
|
|
590
|
-
const urlHash = crypto.createHash('md5').update(urlHook).digest('hex');
|
|
591
|
-
if (this.subscribes[urlHash]) {
|
|
592
|
-
return this.subscribes[urlHash][type].map(item => item.id);
|
|
593
|
-
} else {
|
|
594
|
-
return null;
|
|
595
|
-
}
|
|
596
|
-
};
|
|
597
|
-
|
|
598
|
-
this.unregisterSubscribe = async (urlHook, id, type, user, method) => {
|
|
599
|
-
const urlHash = crypto.createHash('md5').update(urlHook).digest('hex');
|
|
600
|
-
if (this.subscribes[urlHash]) {
|
|
601
|
-
if (id) {
|
|
602
|
-
let pos;
|
|
603
|
-
do {
|
|
604
|
-
pos = this.subscribes[urlHash][type].findIndex(item => item.id === id);
|
|
605
|
-
if (pos !== -1) {
|
|
606
|
-
this.subscribes[urlHash][type].splice(pos, 1);
|
|
607
|
-
if (type === 'state') {
|
|
608
|
-
this.adapter.log.debug(`Unsubscribe from state "${id}"`);
|
|
609
|
-
await this.adapter.unsubscribeForeignStatesAsync(id, {user: user || 'system.user.admin', limitToOwnerRights: adapter.config.onlyAllowWhenUserIsOwner}); // allow unsubscribing always
|
|
610
|
-
} else {
|
|
611
|
-
this.adapter.log.debug(`Unsubscribe from object "${id}"`);
|
|
612
|
-
await this.adapter.unsubscribeForeignObjectsAsync(id, {user: user || 'system.user.admin', limitToOwnerRights: adapter.config.onlyAllowWhenUserIsOwner}); // allow unsubscribing always
|
|
613
|
-
}
|
|
614
|
-
} else {
|
|
615
|
-
break;
|
|
616
|
-
}
|
|
617
|
-
} while (pos !== -1)
|
|
618
|
-
} else {
|
|
619
|
-
for (let i = 0; i < this.subscribes[urlHash][type].length; i++) {
|
|
620
|
-
if (type === 'state') {
|
|
621
|
-
await this.adapter.unsubscribeForeignStatesAsync(this.subscribes[urlHash][type][i].id, {user: user || 'system.user.admin', limitToOwnerRights: adapter.config.onlyAllowWhenUserIsOwner}); // allow unsubscribing always
|
|
622
|
-
} else {
|
|
623
|
-
await this.adapter.unsubscribeForeignObjectsAsync(this.subscribes[urlHash][type][i].id, {user: user || 'system.user.admin', limitToOwnerRights: adapter.config.onlyAllowWhenUserIsOwner}); // allow unsubscribing always
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
this.subscribes[urlHash][type] = [];
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
if (!this.subscribes[urlHash].state.length && !this.subscribes[urlHash].object.length) {
|
|
630
|
-
delete this.subscribes[urlHash];
|
|
631
|
-
|
|
632
|
-
if (!Object.keys(this.subscribes).length) {
|
|
633
|
-
this.adapter.log.debug(`Stop checker`);
|
|
634
|
-
this.checkInterval && clearInterval(this.checkInterval);
|
|
635
|
-
this.checkInterval = null;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
return null; // success
|
|
641
|
-
};
|
|
642
|
-
|
|
643
|
-
this.stateChange = (id, state) => {
|
|
644
|
-
if (state && state.ack) {
|
|
645
|
-
for (let t = this._waitFor.length - 1; t >= 0; t++) {
|
|
646
|
-
if (this._waitFor[t].id === id) {
|
|
647
|
-
const task = this._waitFor[t];
|
|
648
|
-
this._waitFor.splice(t, 1);
|
|
649
|
-
if (!Object.keys(this.subscribes).find(item => item.states.includes(id))) {
|
|
650
|
-
this.adapter.unsubscribeForeignStates(id);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
clearTimeout(task.timer);
|
|
654
|
-
|
|
655
|
-
setImmediate((_task, _val) => {
|
|
656
|
-
state.id = id;
|
|
657
|
-
_task.res.json(state);
|
|
658
|
-
}, task, state);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
Object.keys(this.subscribes).forEach(urlHash => {
|
|
664
|
-
this.subscribes[urlHash].state.forEach(async item => {
|
|
665
|
-
// check if id
|
|
666
|
-
if ((!item.regEx && item.id === id) || (item.regEx && item.regEx.test(id))) {
|
|
667
|
-
if (state && item.delta && item.val !== null && Math.abs(item.val - state.val) < item.delta) {
|
|
668
|
-
// ignore
|
|
669
|
-
this.adapter.log.debug(`State change for "${id}" ignored as delta (${item.val} - ${state.val}) is less than ${item.delta}`);
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
if (state && item.onchange && !item.delta && item.val === state.val) {
|
|
673
|
-
// ignore
|
|
674
|
-
this.adapter.log.debug(`State change for "${id}" ignored as does not changed (${state.val})`);
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
if (state && (item.delta || item.onchange)) {
|
|
678
|
-
// remember last value
|
|
679
|
-
item.val = state.val;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
await reportChange(this.subscribes[urlHash], {id, state});
|
|
683
|
-
}
|
|
684
|
-
});
|
|
685
|
-
});
|
|
686
|
-
};
|
|
687
|
-
|
|
688
|
-
this.objectChange = (id, obj) => {
|
|
689
|
-
Object.keys(this.subscribes).forEach(urlHash => {
|
|
690
|
-
this.subscribes[urlHash].object.forEach(async item => {
|
|
691
|
-
// check if id
|
|
692
|
-
if ((!item.regEx && item.id === id) || (item.regEx && item.regEx.test(id))) {
|
|
693
|
-
await reportChange(this.subscribes[urlHash].urlHook, {id, obj});
|
|
694
|
-
}
|
|
695
|
-
});
|
|
696
|
-
});
|
|
697
|
-
};
|
|
698
|
-
|
|
699
|
-
// wait for ack=true
|
|
700
|
-
this._waitFor = [];
|
|
701
|
-
this.adapter._addTimeout = async task => {
|
|
702
|
-
this._waitFor.push(task);
|
|
703
|
-
|
|
704
|
-
// if not already subscribed
|
|
705
|
-
if (!Object.keys(this.subscribes).find(item => item.states.includes(task.id))) {
|
|
706
|
-
await this.adapter.subscribeForeignStates(task.id);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
task.timer = setTimeout(_task => {
|
|
710
|
-
// remove this task from the list
|
|
711
|
-
const pos = this._waitFor.indexOf(_task);
|
|
712
|
-
if (pos !== -1) {
|
|
713
|
-
this._waitFor.splice(pos, 1);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
if (!Object.keys(this.subscribes).find(item => item.states.includes(task.id))) {
|
|
717
|
-
this.adapter.unsubscribeForeignStates(task.id);
|
|
718
|
-
}
|
|
719
|
-
_task.res.status(501).json({error: 'timeout', id: _task.id, val: _task.val});
|
|
720
|
-
_task = null;
|
|
721
|
-
}, task.timeout, task);
|
|
722
|
-
};
|
|
723
|
-
|
|
724
|
-
this.app.get('/favicon.ico', (req, res) => {
|
|
725
|
-
res.set('Content-Type', 'image/x-icon');
|
|
726
|
-
res.send(fs.readFileSync(`${__dirname}/../img/favicon.ico`));
|
|
727
|
-
});
|
|
728
|
-
|
|
729
|
-
// authenticate
|
|
730
|
-
this.app.use(`${this.routerPrefix}v1/*`, (req, res, next) => {
|
|
731
|
-
isAuthenticated(req, res, () => {
|
|
732
|
-
req._adapter = this.adapter;
|
|
733
|
-
req._swaggerObject = this;
|
|
734
|
-
next();
|
|
735
|
-
});
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
this.app.get(`${this.routerPrefix}v1/polling`, (req, res, next) => {
|
|
739
|
-
res.writeHead(200, {'Content-Type': 'text/plain'});
|
|
740
|
-
const ip = req.query.sid || req.headers['x-forwarded-for'] || req.socket.remoteAddress;
|
|
741
|
-
const urlHash = crypto.createHash('md5').update(ip).digest('hex');
|
|
742
|
-
|
|
743
|
-
let item = this.subscribes[urlHash];
|
|
744
|
-
|
|
745
|
-
this.startGC();
|
|
746
|
-
|
|
747
|
-
if (req.query.check === 'true' || req.query.connect === 'true' || req.query.connect === '') {
|
|
748
|
-
if (!item) {
|
|
749
|
-
this.adapter.log.debug(`[${ip}] Initiate connection`);
|
|
750
|
-
this.subscribes[urlHash] = {state: [], object: [], urlHook: ip, polling: true, timeout: parseInt(req.query.timeout, 10) || 30000, ts: Date.now()};
|
|
751
|
-
item = this.subscribes[urlHash];
|
|
752
|
-
} else if (req.query.timeout) {
|
|
753
|
-
item.timeout = parseInt(req.query.timeout, 10);
|
|
754
|
-
}
|
|
755
|
-
if (item.timeout < 1000) {
|
|
756
|
-
item.timeout = 1000;
|
|
757
|
-
} else
|
|
758
|
-
if (item.timeout > 60000) {
|
|
759
|
-
item.timeout = 60000;
|
|
760
|
-
}
|
|
761
|
-
return res.end('_');
|
|
762
|
-
} else if (!item) {
|
|
763
|
-
this.subscribes[urlHash] = {state: [], object: [], urlHook: ip, polling: true, timeout: parseInt(req.query.timeout, 10) || 30000, ts: Date.now()};
|
|
764
|
-
item = this.subscribes[urlHash];
|
|
765
|
-
} else {
|
|
766
|
-
item.ts = Date.now();
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// If some data wait to be sent
|
|
770
|
-
if (item.queue && item.queue.length) {
|
|
771
|
-
// delete too old entries
|
|
772
|
-
const now = Date.now();
|
|
773
|
-
for (let d = item.queue.length - 1; d >= 0; d--) {
|
|
774
|
-
if (now - item.queue[d].ts > 3000) {
|
|
775
|
-
that.adapter.log.debug(`[${item.urlHook}] Data update was too old and ignored`);
|
|
776
|
-
item.queue.splice(0, d);
|
|
777
|
-
break;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
if (item.queue.length) {
|
|
781
|
-
const chunk = item.queue.shift();
|
|
782
|
-
res.end(chunk.data);
|
|
783
|
-
return;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
new Promise(resolve => {
|
|
788
|
-
item.promise = {
|
|
789
|
-
resolve,
|
|
790
|
-
timer: setTimeout(() => {
|
|
791
|
-
if (item.promise) {
|
|
792
|
-
// could never happen
|
|
793
|
-
item.promise.timer = null;
|
|
794
|
-
}
|
|
795
|
-
resolve();
|
|
796
|
-
}, item ? item.timeout : 30000)};
|
|
797
|
-
})
|
|
798
|
-
.then(data => {
|
|
799
|
-
if (item.promise) {
|
|
800
|
-
item.promise.timer && clearTimeout(item.promise.timer);
|
|
801
|
-
item.promise.timer = null;
|
|
802
|
-
item.promise.resolve = null;
|
|
803
|
-
item.promise = null;
|
|
804
|
-
} else {
|
|
805
|
-
debugger;
|
|
806
|
-
}
|
|
807
|
-
res.end(data);
|
|
808
|
-
});
|
|
809
|
-
|
|
810
|
-
req.on('error', error => {
|
|
811
|
-
if (!error.message.includes('aborted')) {
|
|
812
|
-
this.adapter.log.warn(`[${item && item.urlHook}]Error in polling connection: ${error}`);
|
|
813
|
-
}
|
|
814
|
-
if (item.promise) {
|
|
815
|
-
item.promise.timer && clearTimeout(item.promise.timer);
|
|
816
|
-
item.promise.timer = null;
|
|
817
|
-
item.promise.resolve = null;
|
|
818
|
-
item.promise = null;
|
|
819
|
-
}
|
|
820
|
-
});
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
this.app.use(`${this.routerPrefix}v1/command/*`, async (req, res) => {
|
|
824
|
-
if (this.adapter.config.noCommands) {
|
|
825
|
-
res.status(404).json({error: `Commands are disabled`});
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
let command = req.originalUrl.startsWith(`/${WEB_EXTENSION_PREFIX}`) ? req.originalUrl.split('/')[4] : req.originalUrl.split('/')[3];
|
|
830
|
-
command = command.split('?')[0];
|
|
831
|
-
|
|
832
|
-
const handler = this.commands.getCommandHandler(command);
|
|
833
|
-
if (handler && command !== 'cmdExec' && command !== 'sendToHost') {
|
|
834
|
-
const args = common.getParamNames(handler).map(item => item[0] === '_' ? item.substring(1) : item);
|
|
835
|
-
|
|
836
|
-
args.shift(); // remove socket
|
|
837
|
-
// try to parse a query or body
|
|
838
|
-
let params = parseQuery(req.originalUrl);
|
|
839
|
-
if (req.body) {
|
|
840
|
-
Object.assign(params, req.body);
|
|
841
|
-
}
|
|
842
|
-
if (common.DEFAULT_VALUES[command]) {
|
|
843
|
-
params = Object.assign({}, common.DEFAULT_VALUES[command], params);
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
// check if all parameters are set
|
|
847
|
-
const problem = args.find(name =>
|
|
848
|
-
name !== 'callback' &&
|
|
849
|
-
name !== 'options' &&
|
|
850
|
-
name !== 'adapterName' &&
|
|
851
|
-
name !== 'update' &&
|
|
852
|
-
!params.hasOwnProperty(name)
|
|
853
|
-
);
|
|
854
|
-
|
|
855
|
-
if (problem) {
|
|
856
|
-
res.status(422).json({error: `Argument '${problem}' not found. Following arguments are expected: '${args.join("', '")}'`});
|
|
857
|
-
return;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
const _acl = {
|
|
861
|
-
user: req._user,
|
|
862
|
-
};
|
|
863
|
-
|
|
864
|
-
const _arguments = [{_acl}];
|
|
865
|
-
let error = '';
|
|
866
|
-
args.forEach(name => {
|
|
867
|
-
if (name !== 'callback') {
|
|
868
|
-
if (name === 'options' || name === 'params' || name === 'obj' || name === 'message') {
|
|
869
|
-
// try to convert
|
|
870
|
-
if (typeof params[name] === 'string' && params[name].startsWith('{') && params[name].endsWith('}')) {
|
|
871
|
-
try {
|
|
872
|
-
params[name] = JSON.parse(params[name]);
|
|
873
|
-
} catch (_error) {
|
|
874
|
-
error = `Cannot parse ${name}: ${_error}`;
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
if (name === 'update' && params[name] === undefined) {
|
|
881
|
-
params[name] = false;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
if (params[name] === 'true') {
|
|
885
|
-
params[name] = true;
|
|
886
|
-
} else if (params[name] === 'false') {
|
|
887
|
-
params[name] = false;
|
|
888
|
-
}
|
|
889
|
-
_arguments.push(params[name]);
|
|
890
|
-
}
|
|
891
|
-
});
|
|
892
|
-
|
|
893
|
-
// try to convert arguments for setState and setForeignState
|
|
894
|
-
if (command === 'setState' || command === 'setForeignState') {
|
|
895
|
-
// read object
|
|
896
|
-
try {
|
|
897
|
-
const obj = await adapter.getForeignObjectAsync(_arguments[1], {user: req._user});
|
|
898
|
-
if (obj && obj.common && obj.common.type) {
|
|
899
|
-
if (obj.common.type === 'number') {
|
|
900
|
-
_arguments[2] = parseFloat(_arguments[2]);
|
|
901
|
-
} else if (obj.common.type === 'boolean') {
|
|
902
|
-
_arguments[2] = _arguments[2] === 'true' || _arguments[2] === true || _arguments[2] === 1 || _arguments[2] === '1' || _arguments[2] === 'on' || _arguments[2] === 'ON';
|
|
903
|
-
} else if (obj.common.type === 'string') {
|
|
904
|
-
_arguments[2] = _arguments[2].toString();
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
} catch (error) {
|
|
908
|
-
res.status(501).json({error});
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
if (!error) {
|
|
914
|
-
if (args[args.length - 1] === 'callback') {
|
|
915
|
-
_arguments.push((error, ...args) => {
|
|
916
|
-
if (command === 'sendTo') {
|
|
917
|
-
res.json(error);
|
|
918
|
-
} else if (command === 'getHostByIp') {
|
|
919
|
-
res.json({ip: error, result: args[0]});
|
|
920
|
-
} else
|
|
921
|
-
if (command === 'authEnabled') {
|
|
922
|
-
res.json({secure: error, user: args[0]});
|
|
923
|
-
} else
|
|
924
|
-
if (args.length === 1) {
|
|
925
|
-
res.status(error ? 500 : 200).json({error, result: args[0]});
|
|
926
|
-
} else {
|
|
927
|
-
// if file
|
|
928
|
-
if ((params.binary === null || params.binary === true || params.binary === 'true') && Buffer.isBuffer(args[0])) {
|
|
929
|
-
if (args[1] && typeof args[1] === 'string' && args[1].includes('/')) {
|
|
930
|
-
res.set('Content-Type', args[1]);
|
|
931
|
-
}
|
|
932
|
-
res.send(args[0]);
|
|
933
|
-
} else if ((params.binary === null || params.binary === true || params.binary === 'true') && Buffer.isBuffer(args[1])) {
|
|
934
|
-
res.send(args[1]);
|
|
935
|
-
} else {
|
|
936
|
-
res.status(error ? 500 : 200).json({error, results: args});
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
});
|
|
940
|
-
handler.apply(null, _arguments);
|
|
941
|
-
} else {
|
|
942
|
-
handler.apply(null, _arguments);
|
|
943
|
-
// just execute. No callback
|
|
944
|
-
res.json({result: 'OK'});
|
|
945
|
-
}
|
|
946
|
-
} else {
|
|
947
|
-
res.status(422).json({error});
|
|
948
|
-
}
|
|
949
|
-
} else {
|
|
950
|
-
res.status(404).json({error: `Command ${command} not found`});
|
|
951
|
-
}
|
|
952
|
-
});
|
|
953
|
-
|
|
954
|
-
this.app.get(`${this.routerPrefix}log/*`,(req, res) => {
|
|
955
|
-
let parts = decodeURIComponent(req.url).split('/');
|
|
956
|
-
if (req.originalUrl.startsWith(`/${WEB_EXTENSION_PREFIX}`)) {
|
|
957
|
-
parts.shift();
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
if (parts.length === 5) {
|
|
961
|
-
parts.shift();
|
|
962
|
-
parts.shift();
|
|
963
|
-
const [host, transport] = parts;
|
|
964
|
-
parts = parts.splice(2);
|
|
965
|
-
let filename = parts.join('/');
|
|
966
|
-
this.adapter.sendToHost(`system.host.${host}`, 'getLogFile', {filename, transport}, result => {
|
|
967
|
-
if (!result || result.error) {
|
|
968
|
-
res.status(404).send(`File ${escapeHtml(filename)} not found`);
|
|
969
|
-
} else {
|
|
970
|
-
if (result.gz) {
|
|
971
|
-
if (result.size > 1024 * 1024) {
|
|
972
|
-
res.header('Content-Type', 'application/gzip');
|
|
973
|
-
res.send(result.data);
|
|
974
|
-
} else {
|
|
975
|
-
try {
|
|
976
|
-
unzipFile(filename, result.data, res);
|
|
977
|
-
} catch (e) {
|
|
978
|
-
res.header('Content-Type', 'application/gzip');
|
|
979
|
-
res.send(result.data);
|
|
980
|
-
adapter.log.error(`Cannot extract file ${filename}: ${e}`);
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
} else if (result.data === undefined || result.data === null) {
|
|
984
|
-
res.status(404).send(`File ${escapeHtml(filename)} not found`);
|
|
985
|
-
} else if (result.size > 2 * 1024 * 1024) {
|
|
986
|
-
res.header('Content-Type', 'text/plain');
|
|
987
|
-
res.send(result.data);
|
|
988
|
-
} else {
|
|
989
|
-
res.header('Content-Type', 'text/html');
|
|
990
|
-
res.send(decorateLogFile(null, result.data));
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
});
|
|
994
|
-
} else {
|
|
995
|
-
parts = parts.splice(2);
|
|
996
|
-
const transport = parts.shift();
|
|
997
|
-
let filename = parts.join('/');
|
|
998
|
-
const config = adapter.systemConfig;
|
|
999
|
-
|
|
1000
|
-
// detect file log
|
|
1001
|
-
if (config && config.log && config.log.transport) {
|
|
1002
|
-
if (config.log.transport.hasOwnProperty(transport) && config.log.transport[transport].type === 'file') {
|
|
1003
|
-
let logFolder;
|
|
1004
|
-
if (config.log.transport[transport].filename) {
|
|
1005
|
-
parts = config.log.transport[transport].filename.replace(/\\/g, '/').split('/');
|
|
1006
|
-
parts.pop();
|
|
1007
|
-
logFolder = path.normalize(parts.join('/'));
|
|
1008
|
-
} else {
|
|
1009
|
-
logFolder = path.join(process.cwd(), 'log');
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
if (logFolder[0] !== '/' && logFolder[0] !== '\\' && !logFolder.match(/^[a-zA-Z]:/)) {
|
|
1013
|
-
const _logFolder = path.normalize(path.join(`${__dirname}/../../../`, logFolder).replace(/\\/g, '/')).replace(/\\/g, '/');
|
|
1014
|
-
if (!fs.existsSync(_logFolder)) {
|
|
1015
|
-
logFolder = path.normalize(path.join(`${__dirname}/../../`, logFolder).replace(/\\/g, '/')).replace(/\\/g, '/');
|
|
1016
|
-
} else {
|
|
1017
|
-
logFolder = _logFolder;
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
filename = path.normalize(path.join(logFolder, filename).replace(/\\/g, '/')).replace(/\\/g, '/');
|
|
1022
|
-
|
|
1023
|
-
if (filename.startsWith(logFolder) && fs.existsSync(filename)) {
|
|
1024
|
-
const stat = fs.lstatSync(filename);
|
|
1025
|
-
// if file is archive
|
|
1026
|
-
if (filename.toLowerCase().endsWith('.gz')) {
|
|
1027
|
-
// try to not process to big files
|
|
1028
|
-
if (stat.size > 1024 * 1024/* || !fs.existsSync('/dev/null')*/) {
|
|
1029
|
-
res.header('Content-Type', 'application/gzip');
|
|
1030
|
-
res.sendFile(filename);
|
|
1031
|
-
} else {
|
|
1032
|
-
try {
|
|
1033
|
-
unzipFile(filename, fs.readFileSync(filename), res);
|
|
1034
|
-
} catch (e) {
|
|
1035
|
-
res.header('Content-Type', 'application/gzip');
|
|
1036
|
-
res.sendFile(filename);
|
|
1037
|
-
adapter.log.error(`Cannot extract file ${filename}: ${e}`);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
} else if (stat.size > 2 * 1024 * 1024) {
|
|
1041
|
-
res.header('Content-Type', 'text/plain');
|
|
1042
|
-
res.sendFile(filename);
|
|
1043
|
-
} else {
|
|
1044
|
-
res.header('Content-Type', 'text/html');
|
|
1045
|
-
res.send(decorateLogFile(filename));
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
return;
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
res.status(404).send(`File ${escapeHtml(filename)} not found`);
|
|
1054
|
-
}
|
|
1055
|
-
});
|
|
1056
|
-
|
|
1057
|
-
// parse binary files
|
|
1058
|
-
this.app.post(`${this.routerPrefix}v1/file/*`, multer().fields([{ name: 'file', maxCount: 1 }]), (req, res, next) => next());
|
|
1059
|
-
|
|
1060
|
-
this.unload = async () => {
|
|
1061
|
-
if (this.config.webInstance) {
|
|
1062
|
-
await this.adapter.setForeignStateAsync(`${this.namespace}.info.extension`, false, true);
|
|
1063
|
-
}
|
|
1064
|
-
this.checkInterval && clearInterval(this.checkInterval);
|
|
1065
|
-
this.checkInterval = null;
|
|
1066
|
-
this.gcInterval && clearInterval(this.gcInterval);
|
|
1067
|
-
this.gcInterval = null;
|
|
1068
|
-
// send to all hooks, disconnect event
|
|
1069
|
-
|
|
1070
|
-
const hooks = Object.keys(this.subscribes);
|
|
1071
|
-
for (let h = 0; h < hooks.length; h++) {
|
|
1072
|
-
await reportChange(this.subscribes[hooks[h]], {disconnect: true});
|
|
1073
|
-
}
|
|
1074
|
-
this.subscribes = {};
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
// deliver to web the link to Web interface
|
|
1078
|
-
this.welcomePage = () => {
|
|
1079
|
-
return {
|
|
1080
|
-
link: WEB_EXTENSION_PREFIX,
|
|
1081
|
-
name: 'REST-API',
|
|
1082
|
-
img: 'adapter/rest-api/rest-api.png',
|
|
1083
|
-
color: '#157c00',
|
|
1084
|
-
order: 10,
|
|
1085
|
-
pro: false
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
// Say to web instance to wait till this instance is initialized
|
|
1090
|
-
this.waitForReady = cb => {
|
|
1091
|
-
callback = cb;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
// read default history
|
|
1095
|
-
if (!this.config.dataSource) {
|
|
1096
|
-
this.adapter.getForeignObjectAsync('system.config')
|
|
1097
|
-
.then(obj => {
|
|
1098
|
-
if (obj && obj.common && obj.common.defaultHistory) {
|
|
1099
|
-
this.config.dataSource = obj.common.defaultHistory;
|
|
1100
|
-
}
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
this.adapter.WEB_EXTENSION_PREFIX = WEB_EXTENSION_PREFIX.replace('/', '');
|
|
1105
|
-
|
|
1106
|
-
SwaggerRunner.create(_options, (err, swaggerRunner) => {
|
|
1107
|
-
if (this.config.webInstance) {
|
|
1108
|
-
this.adapter.setForeignState(this.namespace + '.info.extension', true, true);
|
|
1109
|
-
}
|
|
1110
|
-
if (err) {
|
|
1111
|
-
throw err;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
this.swagger = swaggerRunner;
|
|
1115
|
-
|
|
1116
|
-
// install middleware
|
|
1117
|
-
swaggerRunner.expressMiddleware().register(this.app);
|
|
1118
|
-
|
|
1119
|
-
callback && callback(this.app);
|
|
1120
|
-
});
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
module.exports = SwaggerUI;
|