iobroker.rest-api 1.0.5 → 2.0.1
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 +21 -14
- package/admin/jsonConfig.json +4 -33
- package/io-package.json +45 -38
- package/lib/api/controllers/file.js +74 -0
- package/lib/api/controllers/history.js +8 -8
- package/lib/api/swagger/swagger.yaml +214 -21
- package/lib/rest-api.js +67 -23
- package/main.js +7 -2
- package/package.json +21 -15
package/README.md
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
**This adapter uses Sentry libraries to automatically report exceptions and code errors to the developers.** For more details and for information how to disable the error reporting see [Sentry-Plugin Documentation](https://github.com/ioBroker/plugin-sentry#plugin-sentry)! Sentry reporting is used starting with js-controller 3.0.
|
|
11
11
|
|
|
12
|
-
This is RESTFul interface to read the objects and states from ioBroker and to write/control the states over HTTP Get/Post requests.
|
|
12
|
+
This is a RESTFul interface to read the objects and states from ioBroker and to write/control the states over HTTP Get/Post requests.
|
|
13
13
|
|
|
14
14
|
The purpose of this adapter is similar to simple-api. But this adapter supports long-polling and URL hooks for subscribes.
|
|
15
15
|
|
|
16
|
-
It has
|
|
16
|
+
It has a beneficial web interface to play with the requests:
|
|
17
17
|
|
|
18
18
|

|
|
19
19
|
|
|
@@ -24,22 +24,22 @@ Some request examples:
|
|
|
24
24
|
- `http://ipaddress:8093/v1/state/system.adapter.rest-api.0.memHeapTotal` - read state as JSON
|
|
25
25
|
- `http://ipaddress:8093/v1/state/system.adapter.rest-api.0.memHeapTotal/plain` - read state as string (only value)
|
|
26
26
|
- `http://ipaddress:8093/v1/state/system.adapter.rest-api.0.memHeapTotal?value=5` - write state with GET (only for back compatibility with simple-api)
|
|
27
|
-
- `http://ipaddress:8093/v1/sendto/javascript.0?message=toScript&data={"message":"MESSAGE","data":"FROM REST-API"}` - send message to javascript.0 in script `scriptName`
|
|
27
|
+
- `http://ipaddress:8093/v1/sendto/javascript.0?message=toScript&data={"message":"MESSAGE","data":"FROM REST-API"}` - send a message to javascript.0 in script `scriptName`
|
|
28
28
|
|
|
29
|
-
## Subscribe
|
|
29
|
+
## Subscribe to the state's or object's changes
|
|
30
30
|
Your application could get notifications by every change of the state or object.
|
|
31
31
|
|
|
32
|
-
For that your application must provide an HTTP(S) end-point to accept the updates.
|
|
32
|
+
For that, your application must provide an HTTP(S) end-point to accept the updates.
|
|
33
33
|
|
|
34
34
|
Example in node.js see here [demoNodeClient.js](examples/demoNodeClient.js)
|
|
35
35
|
|
|
36
36
|
## Long polling
|
|
37
|
-
This adapter supports
|
|
37
|
+
This adapter supports a subscribing on data changes via long polling.
|
|
38
38
|
|
|
39
39
|
Example for browser could be found here: [demoNodeClient.js](examples/demoBrowserClient.html)
|
|
40
40
|
|
|
41
41
|
## Web extension
|
|
42
|
-
This adapter can run as web
|
|
42
|
+
This adapter can run as a web extension. In this case, the path is available under http://iipaddress:8082/rest
|
|
43
43
|
|
|
44
44
|
## Notice
|
|
45
45
|
- `POST` is always for creating a resource (does not matter if it was duplicated)
|
|
@@ -57,7 +57,7 @@ E.g.
|
|
|
57
57
|
- `http://ipaddress:8093/v1/command/readFile?adapter=admin.admin&fileName=admin.png?binary` - to read the file `admin.admin/admin.png` as file
|
|
58
58
|
- `http://ipaddress:8093/v1/command/extendObject?id=system.adapter.admin.0?obj={"common":{"enabled":true}}` - to restart admin
|
|
59
59
|
|
|
60
|
-
You can request all commands with POST method too. As body must be an object with parameters. E.g
|
|
60
|
+
You can request all commands with POST method too. As body must be an object with parameters. E.g.:
|
|
61
61
|
```
|
|
62
62
|
curl --location --request POST 'http://ipaddress:8093/v1/command/sendTo' \
|
|
63
63
|
--header 'Content-Type: application/json' \
|
|
@@ -81,7 +81,7 @@ You cannot send POST request to commands via GUI.
|
|
|
81
81
|
|
|
82
82
|
### Objects
|
|
83
83
|
- `getObject(id)` - get object by ID
|
|
84
|
-
- `getObjects()` - get all states and rooms. GUI can have problems by visualization of answer.
|
|
84
|
+
- `getObjects(list)` - get all states and rooms. GUI can have problems by visualization of answer.
|
|
85
85
|
- `getObjectView(design, search, params)` - get specific objects, e.g. design=system, search=state, params=`{"startkey": "system.adapter.admin.", "endkey": "system.adapter.admin.\u9999"}`
|
|
86
86
|
- `setObject(id, obj)` - set object with JSON object (e.g. `{"common": {"type": "boolean"}, "native": {}, "type": "state"}`)
|
|
87
87
|
- `delObject(id, options)` - delete object by ID
|
|
@@ -107,7 +107,6 @@ You cannot send POST request to commands via GUI.
|
|
|
107
107
|
- `delState(id)` - delete state and object. Same as delObject
|
|
108
108
|
- `getRatings(update)` - read adapter ratings (as in admin)
|
|
109
109
|
- `getCurrentInstance()` - read adapter namespace (always rest-api.0)
|
|
110
|
-
- `checkFeatureSupported(feature)` - check if feature is supported by js-controller.
|
|
111
110
|
- `decrypt(encryptedText)` - decrypt string with system secret
|
|
112
111
|
- `encrypt(plainText)` - encrypt string with system secret
|
|
113
112
|
- `getAdapters(adapterName)` - get objects of type "adapter". You can define optionally adapterName
|
|
@@ -131,6 +130,7 @@ You cannot send POST request to commands via GUI.
|
|
|
131
130
|
|
|
132
131
|
### Others
|
|
133
132
|
- `log(text, level[info])` - no answer - add log entry to ioBroker log
|
|
133
|
+
- `checkFeatureSupported(feature)` - check if feature is supported by js-controller.
|
|
134
134
|
- `getHistory(id, options)` - read history. See for options: https://github.com/ioBroker/ioBroker.history/blob/master/docs/en/README.md#access-values-from-javascript-adapter
|
|
135
135
|
- `httpGet(url)` - read URL from server. You can set binary=true to get answer as file
|
|
136
136
|
- `sendTo(adapterInstance, command, message)` - send command to instance. E.g. adapterInstance=history.0, command=getHistory, message=`{"id": "system.adapter.admin.0.memRss","options": {"aggregate": "onchange", "addId": true}}`
|
|
@@ -138,19 +138,26 @@ You cannot send POST request to commands via GUI.
|
|
|
138
138
|
- `getUserPermissions()` - read object with user permissions
|
|
139
139
|
- `getVersion()` - read adapter name and version
|
|
140
140
|
- `getAdapterName()` - read adapter name (always rest-api)
|
|
141
|
+
- `clientSubscribe(targetInstance, messageType, data)`
|
|
141
142
|
- `getAdapterInstances(adapterName)` - get objects of type "instance". You can define optionally adapterName
|
|
142
143
|
|
|
143
144
|
<!-- END -->
|
|
144
145
|
|
|
145
|
-
## Todo
|
|
146
|
-
- [ ] Implement GET,PATCH,POST,DELETE file operations
|
|
147
|
-
|
|
148
146
|
<!--
|
|
149
147
|
Placeholder for the next version (at the beginning of the line):
|
|
150
148
|
### **WORK IN PROGRESS**
|
|
151
149
|
-->
|
|
152
150
|
|
|
153
151
|
## Changelog
|
|
152
|
+
### 2.0.1 (2024-05-23)
|
|
153
|
+
* (foxriver76) ported to `@iobroker/webserver`
|
|
154
|
+
* (theshengfui) Fixed history requests
|
|
155
|
+
* (bluefox) Minimum required node.js version is 16
|
|
156
|
+
|
|
157
|
+
### 1.1.0 (2023-05-03)
|
|
158
|
+
* (bluefox) Converting of the setState values to the according type
|
|
159
|
+
* (bluefox) Implemented file operations
|
|
160
|
+
|
|
154
161
|
### 1.0.5 (2023-03-27)
|
|
155
162
|
* (Apollon77) Prepare for future js-controller versions
|
|
156
163
|
|
|
@@ -193,4 +200,4 @@ You cannot send POST request to commands via GUI.
|
|
|
193
200
|
## License
|
|
194
201
|
Apache 2.0
|
|
195
202
|
|
|
196
|
-
Copyright (c) 2017-
|
|
203
|
+
Copyright (c) 2017-2024 bluefox <dogafox@gmail.com>
|
package/admin/jsonConfig.json
CHANGED
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
"leTab": {
|
|
163
163
|
"type": "panel",
|
|
164
164
|
"label": "Let's Encrypt SSL",
|
|
165
|
-
"disabled": "!data.secure
|
|
165
|
+
"disabled": "!data.secure",
|
|
166
166
|
"items": {
|
|
167
167
|
"_image": {
|
|
168
168
|
"type": "staticImage",
|
|
@@ -174,38 +174,9 @@
|
|
|
174
174
|
"height": 59
|
|
175
175
|
}
|
|
176
176
|
},
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"href": "https://github.com/ioBroker/ioBroker.admin/blob/master/README.md#lets-encrypt-certificates",
|
|
181
|
-
"text": "Read about Let's Encrypt certificates",
|
|
182
|
-
"style": {
|
|
183
|
-
"fontSize": 16,
|
|
184
|
-
"marginBottom": 20
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
"leEnabled": {
|
|
188
|
-
"newLine": true,
|
|
189
|
-
"type": "checkbox",
|
|
190
|
-
"label": "Use Lets Encrypt certificates"
|
|
191
|
-
},
|
|
192
|
-
"leUpdate": {
|
|
193
|
-
"newLine": true,
|
|
194
|
-
"type": "checkbox",
|
|
195
|
-
"hidden": "!data.leEnabled",
|
|
196
|
-
"label": "Use this instance for automatic update"
|
|
197
|
-
},
|
|
198
|
-
"lePort": {
|
|
199
|
-
"newLine": true,
|
|
200
|
-
"sm": 11,
|
|
201
|
-
"lg": 4,
|
|
202
|
-
"type": "number",
|
|
203
|
-
"hidden": "!data.leEnabled || !data.leUpdate",
|
|
204
|
-
"label": "Port to check the domain",
|
|
205
|
-
"style": {
|
|
206
|
-
"marginTop": 15,
|
|
207
|
-
"maxWidth": 200
|
|
208
|
-
}
|
|
177
|
+
"_staticText": {
|
|
178
|
+
"type": "staticText",
|
|
179
|
+
"text": "ra_Use iobroker.acme adapter for letsencrypt certificates"
|
|
209
180
|
}
|
|
210
181
|
}
|
|
211
182
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "rest-api",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"2.0.1": {
|
|
7
|
+
"en": "ported to `@iobroker/webserver`\nFixed history requests\nMinimum required node.js version is 16",
|
|
8
|
+
"de": "an `@iobroker/webserver `\nGeschichtsanfragen behoben\nMinimum erforderlich node.js Version ist 16",
|
|
9
|
+
"ru": "в порту `@iobroker/webserver \"\nИсправленные просьбы об истории\nМинимальная требуемая версия node.js - 16",
|
|
10
|
+
"pt": "portado para `@iobroker/webserver \"\nPedidos de histórico fixo\nA versão mínima necessária do node.js é 16",
|
|
11
|
+
"nl": "geporteerd naar .@iobroker/webserver wat\nVaste historische verzoeken\nMinimum vereiste node.js versie is 16",
|
|
12
|
+
"fr": "porté à `@iobroker/webserver \"\nDemande d'historique fixe\nLa version minimum requise node.js est 16",
|
|
13
|
+
"it": "inviato a `@iobroker/webserver #\nRisolte richieste di storia\nVersione minima richiesta node.js è 16",
|
|
14
|
+
"es": "portada a `@iobroker/webserver `\nSolicitudes de historia fija\nLa versión mínima requerida node.js es 16",
|
|
15
|
+
"pl": "wysłany do '@ iobroker / webserver'\nPoprawione żądania dotyczące historii\nMinimalna wymagana node.js wersja jest 16",
|
|
16
|
+
"uk": "сайт: www.iobroker.com й\nВиправлені запити історії\nМінімальний необхідний вузол.js версія 16",
|
|
17
|
+
"zh-cn": "移植到 xqio 经纪人/网络服务器 `\n固定历史请求\n最低要求的节点.js版本为16"
|
|
18
|
+
},
|
|
19
|
+
"2.0.0": {
|
|
20
|
+
"en": "Fixed history requests\nMinimum required node.js version is 16",
|
|
21
|
+
"de": "Geschichtsanfragen behoben\nMinimum erforderlich node.js Version ist 16",
|
|
22
|
+
"ru": "Исправленные просьбы об истории\nМинимальная требуемая версия node.js - 16",
|
|
23
|
+
"pt": "Pedidos de histórico fixo\nA versão mínima necessária do node.js é 16",
|
|
24
|
+
"nl": "Vaste historische verzoeken\nMinimum vereiste node.js versie is 16",
|
|
25
|
+
"fr": "Demande d'historique fixe\nLa version minimum requise node.js est 16",
|
|
26
|
+
"it": "Risolte richieste di storia\nVersione minima richiesta node.js è 16",
|
|
27
|
+
"es": "Solicitudes de historia fija\nLa versión mínima requerida node.js es 16",
|
|
28
|
+
"pl": "Poprawione żądania dotyczące historii\nMinimalna wymagana node.js wersja jest 16",
|
|
29
|
+
"uk": "Виправлені запити історії\nМінімальний необхідний вузол.js версія 16",
|
|
30
|
+
"zh-cn": "固定历史请求\n最低要求的节点.js版本为16"
|
|
31
|
+
},
|
|
32
|
+
"1.1.0": {
|
|
33
|
+
"en": "Converting of the setState values to the according type\nImplemented file operations",
|
|
34
|
+
"de": "Umrechnung der setState-Werte in den entsprechenden Typ\nImplementierung von Dateioperationen",
|
|
35
|
+
"ru": "Преобразование значений setState в согласно типу\nРеализованные файловые операции",
|
|
36
|
+
"pt": "Convertendo dos valores setState para o tipo de acordo\nOperações de arquivo implementadas",
|
|
37
|
+
"nl": "Omkeren van de setstate waarden naar het type\nGeïmplementeerde bestand operaties",
|
|
38
|
+
"fr": "Conversion des valeurs déterminées de l'état dans le type suivant\nOpérations de fichiers mises en œuvre",
|
|
39
|
+
"it": "Convertizione dei valori setState secondo il tipo\nOperazioni di file implementate",
|
|
40
|
+
"es": "Convertir los valores establecidos en el tipo\nOperaciones de archivo aplicadas",
|
|
41
|
+
"pl": "Przetłumaczył wartości zbioru zgodnie z typem\nImplementacja",
|
|
42
|
+
"uk": "Перетворення значень setState до за типом\nРеалізовані операції файлів",
|
|
43
|
+
"zh-cn": "避免按类型划分国家数值\n执行档案业务"
|
|
44
|
+
},
|
|
6
45
|
"1.0.5": {
|
|
7
46
|
"en": "Prepare for future js-controller versions",
|
|
8
47
|
"de": "Bereiten Sie sich auf zukünftige js-Controller-Versionen",
|
|
@@ -51,42 +90,6 @@
|
|
|
51
90
|
"es": "Aumento del tamaño máximo del cuerpo a 100Mb",
|
|
52
91
|
"pl": "Zwiększono maksymalną wielkość ciała do 100 Mb",
|
|
53
92
|
"zh-cn": "提高机构规模,使之达到100千兆瓦"
|
|
54
|
-
},
|
|
55
|
-
"1.0.0": {
|
|
56
|
-
"en": "Final release",
|
|
57
|
-
"de": "Endgültige Veröffentlichung",
|
|
58
|
-
"ru": "Окончательный релиз",
|
|
59
|
-
"pt": "Último lançamento",
|
|
60
|
-
"nl": "Laatste versie",
|
|
61
|
-
"fr": "Version finale",
|
|
62
|
-
"it": "Rilascio finale",
|
|
63
|
-
"es": "Lanzamiento final",
|
|
64
|
-
"pl": "Ostateczne wydanie",
|
|
65
|
-
"zh-cn": "最终版本"
|
|
66
|
-
},
|
|
67
|
-
"0.6.0": {
|
|
68
|
-
"en": "Added sendTo path",
|
|
69
|
-
"de": "sendTo-Pfad hinzugefügt",
|
|
70
|
-
"ru": "Добавлен путь отправки",
|
|
71
|
-
"pt": "Adicionado caminho sendTo",
|
|
72
|
-
"nl": "SendTo-pad toegevoegd",
|
|
73
|
-
"fr": "Ajout du chemin sendTo",
|
|
74
|
-
"it": "Aggiunto il percorso sendTo",
|
|
75
|
-
"es": "Ruta de envío agregada",
|
|
76
|
-
"pl": "Dodano ścieżkę sendTo",
|
|
77
|
-
"zh-cn": "添加了 sendTo 路径"
|
|
78
|
-
},
|
|
79
|
-
"0.5.0": {
|
|
80
|
-
"en": "Some access errors were corrected",
|
|
81
|
-
"de": "Einige Zugriffsfehler wurden behoben",
|
|
82
|
-
"ru": "Исправлены некоторые ошибки доступа",
|
|
83
|
-
"pt": "Alguns erros de acesso foram corrigidos",
|
|
84
|
-
"nl": "Sommige toegangsfouten zijn gecorrigeerd",
|
|
85
|
-
"fr": "Certaines erreurs d'accès ont été corrigées",
|
|
86
|
-
"it": "Alcuni errori di accesso sono stati corretti",
|
|
87
|
-
"es": "Se corrigieron algunos errores de acceso",
|
|
88
|
-
"pl": "Poprawiono niektóre błędy dostępu",
|
|
89
|
-
"zh-cn": "一些访问错误已得到纠正"
|
|
90
93
|
}
|
|
91
94
|
},
|
|
92
95
|
"title": "REST API",
|
|
@@ -108,7 +111,6 @@
|
|
|
108
111
|
"authors": [
|
|
109
112
|
"bluefox <dogafox@gmail.com>"
|
|
110
113
|
],
|
|
111
|
-
"license": "Apache-2.0",
|
|
112
114
|
"platform": "Javascript/Node.js",
|
|
113
115
|
"mode": "daemon",
|
|
114
116
|
"connectionType": "local",
|
|
@@ -134,6 +136,11 @@
|
|
|
134
136
|
"adminUI": {
|
|
135
137
|
"config": "json"
|
|
136
138
|
},
|
|
139
|
+
"licenseInformation": {
|
|
140
|
+
"type": "free",
|
|
141
|
+
"license": "Apache-2.0"
|
|
142
|
+
},
|
|
143
|
+
"tier": 3,
|
|
137
144
|
"dependencies": [
|
|
138
145
|
{
|
|
139
146
|
"js-controller": ">=4.0.0"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const commonLib = require('./common.js');
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
readFile: function (req, res) {
|
|
6
|
+
commonLib.checkPermissions(req._adapter, req._user, [{type: 'file', operation: 'read'}], async error => {
|
|
7
|
+
if (error) {
|
|
8
|
+
commonLib.errorResponse(req, res, error);
|
|
9
|
+
} else {
|
|
10
|
+
const params = commonLib.parseUrl(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX);
|
|
11
|
+
try {
|
|
12
|
+
const data = await req._adapter.readFileAsync(params.objectId, params.fileName, {user: req._user, limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner});
|
|
13
|
+
if (data && data.mimeType) {
|
|
14
|
+
res.set('Content-Type', data.mimeType);
|
|
15
|
+
res.send(data.file);
|
|
16
|
+
} else {
|
|
17
|
+
res.status(404).send(Buffer.from(''));
|
|
18
|
+
}
|
|
19
|
+
} catch (error) {
|
|
20
|
+
commonLib.errorResponse(req, res, error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
deleteFile: function (req, res) {
|
|
26
|
+
commonLib.checkPermissions(req._adapter, req._user, [{type: 'file', operation: 'delete'}], async error => {
|
|
27
|
+
if (error) {
|
|
28
|
+
commonLib.errorResponse(req, res, error);
|
|
29
|
+
} else {
|
|
30
|
+
const params = commonLib.parseUrl(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX);
|
|
31
|
+
try {
|
|
32
|
+
await req._adapter.delFileAsync(params.objectId, params.fileName, {user: req._user, limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner});
|
|
33
|
+
res.json({success: true});
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (err.toString().includes('Not exists')) {
|
|
36
|
+
res.status(404).json({error: err.toString()});
|
|
37
|
+
} else {
|
|
38
|
+
commonLib.errorResponse(req, res, err);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
writeFile: function (req, res) {
|
|
45
|
+
commonLib.checkPermissions(req._adapter, req._user, [{type: 'file', operation: 'write'}], async error => {
|
|
46
|
+
if (error) {
|
|
47
|
+
commonLib.errorResponse(req, res, error);
|
|
48
|
+
} else {
|
|
49
|
+
const params = commonLib.parseUrl(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX);
|
|
50
|
+
try {
|
|
51
|
+
await req._adapter.writeFileAsync(params.objectId, params.fileName, req.files.file[0].buffer, {user: req._user, limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner});
|
|
52
|
+
res.json({success: true});
|
|
53
|
+
} catch (err) {
|
|
54
|
+
commonLib.errorResponse(req, res, err);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
readDir: function (req, res) {
|
|
60
|
+
commonLib.checkPermissions(req._adapter, req._user, [{type: 'file', operation: 'list'}], async error => {
|
|
61
|
+
if (error) {
|
|
62
|
+
commonLib.errorResponse(req, res, error);
|
|
63
|
+
} else {
|
|
64
|
+
const params = commonLib.parseUrl(req.url, req.swagger, req._adapter.WEB_EXTENSION_PREFIX);
|
|
65
|
+
try {
|
|
66
|
+
const response = await req._adapter.readDirAsync(params.objectId, params.dirName || '', {user: req._user, limitToOwnerRights: req._adapter.config.onlyAllowWhenUserIsOwner});
|
|
67
|
+
res.json(response);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
commonLib.errorResponse(req, res, err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
};
|
|
@@ -96,17 +96,17 @@ module.exports = {
|
|
|
96
96
|
Object.keys(PARAMETERS).forEach(attr => {
|
|
97
97
|
if (Object.hasOwnProperty.call(req.body.options, attr)) {
|
|
98
98
|
if (PARAMETERS[attr] === 'boolean') {
|
|
99
|
-
options[attr] = req.body.options[attr] === 'true';
|
|
99
|
+
options.options[attr] = req.body.options[attr] === 'true';
|
|
100
100
|
} else if (PARAMETERS[attr] === 'number') {
|
|
101
|
-
options[attr] = parseFloat(req.body.options[attr]);
|
|
101
|
+
options.options[attr] = parseFloat(req.body.options[attr]);
|
|
102
102
|
} else if (Array.isArray(PARAMETERS[attr])) {
|
|
103
103
|
if (PARAMETERS[attr].includes(req.body.options[attr])) {
|
|
104
|
-
options[attr] = req.body.options[attr];
|
|
104
|
+
options.options[attr] = req.body.options[attr];
|
|
105
105
|
} else {
|
|
106
106
|
req._adapter.log.warn(`Unknown value ${req.body.options[attr]} for attribute ${attr}. Allowed: ${PARAMETERS[attr].join(', ')}`);
|
|
107
107
|
}
|
|
108
108
|
} else if (PARAMETERS[attr] === 'string') {
|
|
109
|
-
options[attr] = req.body.options[attr];
|
|
109
|
+
options.options[attr] = req.body.options[attr];
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
});
|
|
@@ -204,11 +204,11 @@ module.exports = {
|
|
|
204
204
|
Object.keys(PARAMETERS_ADD).forEach(attr => {
|
|
205
205
|
if (Object.hasOwnProperty.call(req.body.state, attr)) {
|
|
206
206
|
if (PARAMETERS_ADD[attr] === 'boolean') {
|
|
207
|
-
options[attr] = req.body.state[attr] === 'true';
|
|
207
|
+
options.state[attr] = req.body.state[attr] === 'true';
|
|
208
208
|
} else if (PARAMETERS_ADD[attr] === 'number') {
|
|
209
|
-
options[attr] = parseFloat(req.body.state[attr]);
|
|
209
|
+
options.state[attr] = parseFloat(req.body.state[attr]);
|
|
210
210
|
} else if (PARAMETERS_ADD[attr] === 'string') {
|
|
211
|
-
options[attr] = req.body.state[attr];
|
|
211
|
+
options.state[attr] = req.body.state[attr];
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
});
|
|
@@ -236,4 +236,4 @@ module.exports = {
|
|
|
236
236
|
}
|
|
237
237
|
});
|
|
238
238
|
},
|
|
239
|
-
};
|
|
239
|
+
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
swagger: "2.0"
|
|
3
3
|
info:
|
|
4
4
|
description: "This is a REST server for ioBroker."
|
|
5
|
-
version: "
|
|
5
|
+
version: "2.0.1"
|
|
6
6
|
title: "ioBroker Swagger UI"
|
|
7
7
|
contact:
|
|
8
8
|
email: "admin@iobroker.net"
|
|
@@ -28,6 +28,8 @@ tags:
|
|
|
28
28
|
description: "Socket commands"
|
|
29
29
|
- name: "sendTo"
|
|
30
30
|
description: "Send message to instance"
|
|
31
|
+
- name: "file"
|
|
32
|
+
description: "Read/Write files and directories"
|
|
31
33
|
securityDefinitions:
|
|
32
34
|
basicAuth:
|
|
33
35
|
type: basic
|
|
@@ -995,8 +997,136 @@ paths:
|
|
|
995
997
|
description: "Invalid state ID supplied"
|
|
996
998
|
500:
|
|
997
999
|
description: "instance is offline"
|
|
1000
|
+
/file/{objectId}/{fileName}:
|
|
1001
|
+
x-swagger-router-controller: file
|
|
1002
|
+
get:
|
|
1003
|
+
tags:
|
|
1004
|
+
- "file"
|
|
1005
|
+
summary: "Reads file"
|
|
1006
|
+
operationId: "readFile"
|
|
1007
|
+
produces:
|
|
1008
|
+
- "application/octet-stream"
|
|
1009
|
+
parameters:
|
|
1010
|
+
- name: "objectId"
|
|
1011
|
+
in: "path"
|
|
1012
|
+
description: "Object ID, like vis.0"
|
|
1013
|
+
type: "string"
|
|
1014
|
+
required: true
|
|
1015
|
+
- name: "fileName"
|
|
1016
|
+
in: "path"
|
|
1017
|
+
description: "File name, like main/vis-views.json"
|
|
1018
|
+
type: "string"
|
|
1019
|
+
required: true
|
|
1020
|
+
responses:
|
|
1021
|
+
200:
|
|
1022
|
+
description: "successful operation"
|
|
1023
|
+
404:
|
|
1024
|
+
description: "File not found"
|
|
1025
|
+
post:
|
|
1026
|
+
tags:
|
|
1027
|
+
- "file"
|
|
1028
|
+
summary: "Writes file"
|
|
1029
|
+
operationId: "writeFile"
|
|
1030
|
+
produces:
|
|
1031
|
+
- "application/json"
|
|
1032
|
+
consumes:
|
|
1033
|
+
- "multipart/form-data"
|
|
1034
|
+
parameters:
|
|
1035
|
+
- name: "objectId"
|
|
1036
|
+
in: "path"
|
|
1037
|
+
description: "Object ID, like vis.0"
|
|
1038
|
+
type: "string"
|
|
1039
|
+
required: true
|
|
1040
|
+
- name: "fileName"
|
|
1041
|
+
in: "path"
|
|
1042
|
+
description: "File name, like main/vis-views.json"
|
|
1043
|
+
type: "string"
|
|
1044
|
+
required: true
|
|
1045
|
+
- name: "file"
|
|
1046
|
+
in: "formData"
|
|
1047
|
+
description: "File content"
|
|
1048
|
+
required: true
|
|
1049
|
+
type: "file"
|
|
1050
|
+
responses:
|
|
1051
|
+
200:
|
|
1052
|
+
description: "successful operation"
|
|
1053
|
+
404:
|
|
1054
|
+
description: "File not found"
|
|
1055
|
+
delete:
|
|
1056
|
+
tags:
|
|
1057
|
+
- "file"
|
|
1058
|
+
summary: "deletes file"
|
|
1059
|
+
operationId: "deleteFile"
|
|
1060
|
+
produces:
|
|
1061
|
+
- "application/json"
|
|
1062
|
+
parameters:
|
|
1063
|
+
- name: "objectId"
|
|
1064
|
+
in: "path"
|
|
1065
|
+
description: "Object ID, like vis.0"
|
|
1066
|
+
type: "string"
|
|
1067
|
+
required: true
|
|
1068
|
+
- name: "fileName"
|
|
1069
|
+
in: "path"
|
|
1070
|
+
description: "File name, like main/vis-views.json"
|
|
1071
|
+
type: "string"
|
|
1072
|
+
required: true
|
|
1073
|
+
responses:
|
|
1074
|
+
200:
|
|
1075
|
+
description: "successful operation"
|
|
1076
|
+
404:
|
|
1077
|
+
description: "File not found"
|
|
1078
|
+
/dir/{objectId}/{dirName}:
|
|
1079
|
+
x-swagger-router-controller: file
|
|
1080
|
+
get:
|
|
1081
|
+
tags:
|
|
1082
|
+
- "file"
|
|
1083
|
+
summary: "List directory"
|
|
1084
|
+
operationId: "readDir"
|
|
1085
|
+
produces:
|
|
1086
|
+
- "application/json"
|
|
1087
|
+
parameters:
|
|
1088
|
+
- name: "objectId"
|
|
1089
|
+
in: "path"
|
|
1090
|
+
description: "Object ID, like vis.0"
|
|
1091
|
+
type: "string"
|
|
1092
|
+
required: true
|
|
1093
|
+
- name: "dirName"
|
|
1094
|
+
in: "path"
|
|
1095
|
+
description: "File name, like main/vis-views.json"
|
|
1096
|
+
type: "string"
|
|
1097
|
+
required: true
|
|
1098
|
+
responses:
|
|
1099
|
+
200:
|
|
1100
|
+
description: "successful operation"
|
|
1101
|
+
schema:
|
|
1102
|
+
$ref: "#/definitions/DirResponse"
|
|
1103
|
+
404:
|
|
1104
|
+
description: "File not found"
|
|
1105
|
+
/dir/{objectId}:
|
|
1106
|
+
x-swagger-router-controller: file
|
|
1107
|
+
get:
|
|
1108
|
+
tags:
|
|
1109
|
+
- "file"
|
|
1110
|
+
summary: "List directory"
|
|
1111
|
+
operationId: "readDir"
|
|
1112
|
+
produces:
|
|
1113
|
+
- "application/json"
|
|
1114
|
+
parameters:
|
|
1115
|
+
- name: "objectId"
|
|
1116
|
+
in: "path"
|
|
1117
|
+
description: "Object ID, like vis.0"
|
|
1118
|
+
type: "string"
|
|
1119
|
+
required: true
|
|
1120
|
+
responses:
|
|
1121
|
+
200:
|
|
1122
|
+
description: "successful operation"
|
|
1123
|
+
schema:
|
|
1124
|
+
$ref: "#/definitions/DirResponse"
|
|
1125
|
+
404:
|
|
1126
|
+
description: "File not found"
|
|
998
1127
|
|
|
999
|
-
|
|
1128
|
+
|
|
1129
|
+
# commands start
|
|
1000
1130
|
/command/getStates:
|
|
1001
1131
|
get:
|
|
1002
1132
|
tags:
|
|
@@ -1134,7 +1264,14 @@ paths:
|
|
|
1134
1264
|
- "commands"
|
|
1135
1265
|
summary: "get all states and rooms. GUI can have problems by visualization of answer."
|
|
1136
1266
|
produces:
|
|
1137
|
-
- "application/json"
|
|
1267
|
+
- "application/json"
|
|
1268
|
+
parameters:
|
|
1269
|
+
|
|
1270
|
+
- name: "list"
|
|
1271
|
+
in: "query"
|
|
1272
|
+
description: ""
|
|
1273
|
+
type: "string"
|
|
1274
|
+
required: true
|
|
1138
1275
|
responses:
|
|
1139
1276
|
200:
|
|
1140
1277
|
description: "successful operation"
|
|
@@ -1633,24 +1770,6 @@ paths:
|
|
|
1633
1770
|
responses:
|
|
1634
1771
|
200:
|
|
1635
1772
|
description: "successful operation"
|
|
1636
|
-
/command/checkFeatureSupported:
|
|
1637
|
-
get:
|
|
1638
|
-
tags:
|
|
1639
|
-
- "commands"
|
|
1640
|
-
summary: "check if feature is supported by js-controller."
|
|
1641
|
-
produces:
|
|
1642
|
-
- "application/json"
|
|
1643
|
-
parameters:
|
|
1644
|
-
|
|
1645
|
-
- name: "feature"
|
|
1646
|
-
in: "query"
|
|
1647
|
-
description: ""
|
|
1648
|
-
type: "string"
|
|
1649
|
-
required: true
|
|
1650
|
-
enum: [ALIAS, ALIAS_SEPARATE_READ_WRITE_ID, ADAPTER_GETPORT_BIND, ADAPTER_DEL_OBJECT_RECURSIVE, ADAPTER_SET_OBJECT_SETS_DEFAULT_VALUE, ADAPTER_AUTO_DECRYPT_NATIVE, PLUGINS, CONTROLLER_NPM_AUTO_REBUILD, CONTROLLER_READWRITE_BASE_SETTINGS, CONTROLLER_MULTI_REPO, CONTROLLER_LICENSE_MANAGER, DEL_INSTANCE_CUSTOM]
|
|
1651
|
-
responses:
|
|
1652
|
-
200:
|
|
1653
|
-
description: "successful operation"
|
|
1654
1773
|
/command/decrypt:
|
|
1655
1774
|
get:
|
|
1656
1775
|
tags:
|
|
@@ -2021,6 +2140,24 @@ paths:
|
|
|
2021
2140
|
responses:
|
|
2022
2141
|
200:
|
|
2023
2142
|
description: "successful operation"
|
|
2143
|
+
/command/checkFeatureSupported:
|
|
2144
|
+
get:
|
|
2145
|
+
tags:
|
|
2146
|
+
- "commands"
|
|
2147
|
+
summary: "check if feature is supported by js-controller."
|
|
2148
|
+
produces:
|
|
2149
|
+
- "application/json"
|
|
2150
|
+
parameters:
|
|
2151
|
+
|
|
2152
|
+
- name: "feature"
|
|
2153
|
+
in: "query"
|
|
2154
|
+
description: ""
|
|
2155
|
+
type: "string"
|
|
2156
|
+
required: true
|
|
2157
|
+
enum: [ALIAS, ALIAS_SEPARATE_READ_WRITE_ID, ADAPTER_GETPORT_BIND, ADAPTER_DEL_OBJECT_RECURSIVE, ADAPTER_SET_OBJECT_SETS_DEFAULT_VALUE, ADAPTER_AUTO_DECRYPT_NATIVE, PLUGINS, CONTROLLER_NPM_AUTO_REBUILD, CONTROLLER_READWRITE_BASE_SETTINGS, CONTROLLER_MULTI_REPO, CONTROLLER_LICENSE_MANAGER, DEL_INSTANCE_CUSTOM]
|
|
2158
|
+
responses:
|
|
2159
|
+
200:
|
|
2160
|
+
description: "successful operation"
|
|
2024
2161
|
/command/getHistory:
|
|
2025
2162
|
get:
|
|
2026
2163
|
tags:
|
|
@@ -2130,6 +2267,35 @@ paths:
|
|
|
2130
2267
|
responses:
|
|
2131
2268
|
200:
|
|
2132
2269
|
description: "successful operation"
|
|
2270
|
+
/command/clientSubscribe:
|
|
2271
|
+
get:
|
|
2272
|
+
tags:
|
|
2273
|
+
- "commands"
|
|
2274
|
+
summary: ""
|
|
2275
|
+
produces:
|
|
2276
|
+
- "application/json"
|
|
2277
|
+
parameters:
|
|
2278
|
+
|
|
2279
|
+
- name: "targetInstance"
|
|
2280
|
+
in: "query"
|
|
2281
|
+
description: ""
|
|
2282
|
+
type: "string"
|
|
2283
|
+
required: true
|
|
2284
|
+
|
|
2285
|
+
- name: "messageType"
|
|
2286
|
+
in: "query"
|
|
2287
|
+
description: ""
|
|
2288
|
+
type: "string"
|
|
2289
|
+
required: true
|
|
2290
|
+
|
|
2291
|
+
- name: "data"
|
|
2292
|
+
in: "query"
|
|
2293
|
+
description: ""
|
|
2294
|
+
type: "string"
|
|
2295
|
+
required: true
|
|
2296
|
+
responses:
|
|
2297
|
+
200:
|
|
2298
|
+
description: "successful operation"
|
|
2133
2299
|
/command/getAdapterInstances:
|
|
2134
2300
|
get:
|
|
2135
2301
|
tags:
|
|
@@ -2410,6 +2576,33 @@ definitions:
|
|
|
2410
2576
|
type: "array"
|
|
2411
2577
|
items:
|
|
2412
2578
|
$ref: "#/definitions/EnumEntry"
|
|
2579
|
+
FileEntry:
|
|
2580
|
+
type: "object"
|
|
2581
|
+
properties:
|
|
2582
|
+
file:
|
|
2583
|
+
type: "string"
|
|
2584
|
+
description: "File name"
|
|
2585
|
+
stats:
|
|
2586
|
+
type: "object"
|
|
2587
|
+
description: "File size"
|
|
2588
|
+
properties:
|
|
2589
|
+
size:
|
|
2590
|
+
type: "number"
|
|
2591
|
+
description: "File size in bytes"
|
|
2592
|
+
isDir:
|
|
2593
|
+
type: "boolean"
|
|
2594
|
+
description: "Is directory"
|
|
2595
|
+
modifiedAt:
|
|
2596
|
+
type: "number"
|
|
2597
|
+
description: "Modification time in ms"
|
|
2598
|
+
createdAt:
|
|
2599
|
+
type: "number"
|
|
2600
|
+
description: "Creation time in ms"
|
|
2601
|
+
DirResponse:
|
|
2602
|
+
type: "array"
|
|
2603
|
+
items:
|
|
2604
|
+
$ref: "#/definitions/FileEntry"
|
|
2605
|
+
|
|
2413
2606
|
SendToData:
|
|
2414
2607
|
type: "object"
|
|
2415
2608
|
properties:
|
package/lib/rest-api.js
CHANGED
|
@@ -13,6 +13,7 @@ const axios = require('axios');
|
|
|
13
13
|
const cors = require('cors');
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
+
const multer = require('multer');
|
|
16
17
|
const utils = require('@iobroker/adapter-core'); // Get common adapter utils
|
|
17
18
|
const pattern2RegEx = utils.commonTools.pattern2RegEx;
|
|
18
19
|
const CommandsAdmin = require('@iobroker/socket-classes').SocketCommandsAdmin;
|
|
@@ -23,6 +24,26 @@ process.env.SUPPRESS_NO_CONFIG_WARNING = 'true';
|
|
|
23
24
|
|
|
24
25
|
const WEB_EXTENSION_PREFIX = 'rest-api/';
|
|
25
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
|
+
*/
|
|
26
47
|
function parseQuery(_url) {
|
|
27
48
|
let url = decodeURI(_url);
|
|
28
49
|
const pos = url.indexOf('?');
|
|
@@ -286,17 +307,17 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
286
307
|
};
|
|
287
308
|
|
|
288
309
|
// prepare yaml
|
|
289
|
-
_options.swaggerFile = __dirname
|
|
310
|
+
_options.swaggerFile = `${__dirname}/api/swagger/swagger.yaml`;
|
|
290
311
|
if (this.adapter.config.noCommands) {
|
|
291
312
|
const newText = removeTextFromFile(_options.swaggerFile, '# commands start', '# commands stop');
|
|
292
313
|
|
|
293
|
-
_options.swaggerFile = __dirname
|
|
314
|
+
_options.swaggerFile = `${__dirname}/api/swagger/swaggerEdited.yaml`;
|
|
294
315
|
if (!fs.existsSync(_options.swaggerFile) || fs.readFileSync(_options.swaggerFile).toString('utf8') !== newText) {
|
|
295
316
|
fs.writeFileSync(_options.swaggerFile, newText);
|
|
296
317
|
}
|
|
297
318
|
} else if (this.adapter.config.noAdminCommands) {
|
|
298
319
|
const newText = removeTextFromFile(_options.swaggerFile, '# admin commands start', '# admin commands end');
|
|
299
|
-
_options.swaggerFile = __dirname
|
|
320
|
+
_options.swaggerFile = `${__dirname}/api/swagger/swaggerEdited.yaml`;
|
|
300
321
|
if (!fs.existsSync(_options.swaggerFile) || fs.readFileSync(_options.swaggerFile).toString('utf8') !== newText) {
|
|
301
322
|
fs.writeFileSync(_options.swaggerFile, newText);
|
|
302
323
|
}
|
|
@@ -307,7 +328,7 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
307
328
|
let file = fs.readFileSync(_options.swaggerFile).toString('utf8')
|
|
308
329
|
file = file.replace('basePath: "/v1"', `basePath: "/${WEB_EXTENSION_PREFIX}v1"`);
|
|
309
330
|
|
|
310
|
-
_options.swaggerFile = __dirname
|
|
331
|
+
_options.swaggerFile = `${__dirname}/api/swagger/swagger_extension.yaml`;
|
|
311
332
|
|
|
312
333
|
if (!fs.existsSync(_options.swaggerFile) || fs.readFileSync(_options.swaggerFile).toString('utf8') !== file) {
|
|
313
334
|
fs.writeFileSync(_options.swaggerFile, file);
|
|
@@ -322,16 +343,16 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
322
343
|
|
|
323
344
|
|
|
324
345
|
if (!this.config.noUI) {
|
|
325
|
-
this.app.get(this.routerPrefix
|
|
346
|
+
this.app.get(`${this.routerPrefix}api-docs/swagger.json`, (req, res) =>
|
|
326
347
|
res.json(swaggerDocument));
|
|
327
348
|
|
|
328
349
|
const options = {
|
|
329
350
|
customCss: '.swagger-ui .topbar { background-color: #4dabf5; }',
|
|
330
351
|
};
|
|
331
352
|
// show WEB CSS and so on
|
|
332
|
-
this.app.use(this.routerPrefix
|
|
353
|
+
this.app.use(`${this.routerPrefix}api-doc/`, swaggerUi.serve, swaggerUi.setup(swaggerDocument, options));
|
|
333
354
|
this.app.get(this.routerPrefix, (req, res) =>
|
|
334
|
-
res.redirect(this.routerPrefix
|
|
355
|
+
res.redirect(`${this.routerPrefix}api-doc/`));
|
|
335
356
|
}
|
|
336
357
|
|
|
337
358
|
function isAuthenticated(req, res, callback) {
|
|
@@ -354,7 +375,7 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
354
375
|
}
|
|
355
376
|
}
|
|
356
377
|
if (!values.user.match(/^system\.user\./)) {
|
|
357
|
-
values.user =
|
|
378
|
+
values.user = `system.user.${values.user}`;
|
|
358
379
|
}
|
|
359
380
|
|
|
360
381
|
that.adapter.checkPassword(values.user, values.pass, result => {
|
|
@@ -686,7 +707,7 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
686
707
|
}
|
|
687
708
|
|
|
688
709
|
task.timer = setTimeout(_task => {
|
|
689
|
-
// remove this task from list
|
|
710
|
+
// remove this task from the list
|
|
690
711
|
const pos = this._waitFor.indexOf(_task);
|
|
691
712
|
if (pos !== -1) {
|
|
692
713
|
this._waitFor.splice(pos, 1);
|
|
@@ -702,11 +723,11 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
702
723
|
|
|
703
724
|
this.app.get('/favicon.ico', (req, res) => {
|
|
704
725
|
res.set('Content-Type', 'image/x-icon');
|
|
705
|
-
res.send(fs.readFileSync(__dirname
|
|
726
|
+
res.send(fs.readFileSync(`${__dirname}/../img/favicon.ico`));
|
|
706
727
|
});
|
|
707
728
|
|
|
708
729
|
// authenticate
|
|
709
|
-
this.app.use(this.routerPrefix
|
|
730
|
+
this.app.use(`${this.routerPrefix}v1/*`, (req, res, next) => {
|
|
710
731
|
isAuthenticated(req, res, () => {
|
|
711
732
|
req._adapter = this.adapter;
|
|
712
733
|
req._swaggerObject = this;
|
|
@@ -714,7 +735,7 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
714
735
|
});
|
|
715
736
|
});
|
|
716
737
|
|
|
717
|
-
this.app.get(this.routerPrefix
|
|
738
|
+
this.app.get(`${this.routerPrefix}v1/polling`, (req, res, next) => {
|
|
718
739
|
res.writeHead(200, {'Content-Type': 'text/plain'});
|
|
719
740
|
const ip = req.query.sid || req.headers['x-forwarded-for'] || req.socket.remoteAddress;
|
|
720
741
|
const urlHash = crypto.createHash('md5').update(ip).digest('hex');
|
|
@@ -799,13 +820,13 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
799
820
|
});
|
|
800
821
|
});
|
|
801
822
|
|
|
802
|
-
this.app.use(this.routerPrefix
|
|
823
|
+
this.app.use(`${this.routerPrefix}v1/command/*`, async (req, res) => {
|
|
803
824
|
if (this.adapter.config.noCommands) {
|
|
804
825
|
res.status(404).json({error: `Commands are disabled`});
|
|
805
826
|
return;
|
|
806
827
|
}
|
|
807
828
|
|
|
808
|
-
let command = req.originalUrl.startsWith(
|
|
829
|
+
let command = req.originalUrl.startsWith(`/${WEB_EXTENSION_PREFIX}`) ? req.originalUrl.split('/')[4] : req.originalUrl.split('/')[3];
|
|
809
830
|
command = command.split('?')[0];
|
|
810
831
|
|
|
811
832
|
const handler = this.commands.getCommandHandler(command);
|
|
@@ -813,7 +834,7 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
813
834
|
const args = common.getParamNames(handler).map(item => item[0] === '_' ? item.substring(1) : item);
|
|
814
835
|
|
|
815
836
|
args.shift(); // remove socket
|
|
816
|
-
// try to parse query or body
|
|
837
|
+
// try to parse a query or body
|
|
817
838
|
let params = parseQuery(req.originalUrl);
|
|
818
839
|
if (req.body) {
|
|
819
840
|
Object.assign(params, req.body);
|
|
@@ -837,7 +858,7 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
837
858
|
}
|
|
838
859
|
|
|
839
860
|
const _acl = {
|
|
840
|
-
user: req._user
|
|
861
|
+
user: req._user,
|
|
841
862
|
};
|
|
842
863
|
|
|
843
864
|
const _arguments = [{_acl}];
|
|
@@ -846,7 +867,7 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
846
867
|
if (name !== 'callback') {
|
|
847
868
|
if (name === 'options' || name === 'params' || name === 'obj' || name === 'message') {
|
|
848
869
|
// try to convert
|
|
849
|
-
if (typeof params[name]
|
|
870
|
+
if (typeof params[name] === 'string' && params[name].startsWith('{') && params[name].endsWith('}')) {
|
|
850
871
|
try {
|
|
851
872
|
params[name] = JSON.parse(params[name]);
|
|
852
873
|
} catch (_error) {
|
|
@@ -869,6 +890,26 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
869
890
|
}
|
|
870
891
|
});
|
|
871
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
|
+
|
|
872
913
|
if (!error) {
|
|
873
914
|
if (args[args.length - 1] === 'callback') {
|
|
874
915
|
_arguments.push((error, ...args) => {
|
|
@@ -910,9 +951,9 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
910
951
|
}
|
|
911
952
|
});
|
|
912
953
|
|
|
913
|
-
this.app.get(this.routerPrefix
|
|
954
|
+
this.app.get(`${this.routerPrefix}log/*`,(req, res) => {
|
|
914
955
|
let parts = decodeURIComponent(req.url).split('/');
|
|
915
|
-
if (req.originalUrl.startsWith(
|
|
956
|
+
if (req.originalUrl.startsWith(`/${WEB_EXTENSION_PREFIX}`)) {
|
|
916
957
|
parts.shift();
|
|
917
958
|
}
|
|
918
959
|
|
|
@@ -922,7 +963,7 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
922
963
|
const [host, transport] = parts;
|
|
923
964
|
parts = parts.splice(2);
|
|
924
965
|
let filename = parts.join('/');
|
|
925
|
-
this.adapter.sendToHost(
|
|
966
|
+
this.adapter.sendToHost(`system.host.${host}`, 'getLogFile', {filename, transport}, result => {
|
|
926
967
|
if (!result || result.error) {
|
|
927
968
|
res.status(404).send(`File ${escapeHtml(filename)} not found`);
|
|
928
969
|
} else {
|
|
@@ -969,9 +1010,9 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
969
1010
|
}
|
|
970
1011
|
|
|
971
1012
|
if (logFolder[0] !== '/' && logFolder[0] !== '\\' && !logFolder.match(/^[a-zA-Z]:/)) {
|
|
972
|
-
const _logFolder = path.normalize(path.join(__dirname
|
|
1013
|
+
const _logFolder = path.normalize(path.join(`${__dirname}/../../../`, logFolder).replace(/\\/g, '/')).replace(/\\/g, '/');
|
|
973
1014
|
if (!fs.existsSync(_logFolder)) {
|
|
974
|
-
logFolder = path.normalize(path.join(__dirname
|
|
1015
|
+
logFolder = path.normalize(path.join(`${__dirname}/../../`, logFolder).replace(/\\/g, '/')).replace(/\\/g, '/');
|
|
975
1016
|
} else {
|
|
976
1017
|
logFolder = _logFolder;
|
|
977
1018
|
}
|
|
@@ -1013,9 +1054,12 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
1013
1054
|
}
|
|
1014
1055
|
});
|
|
1015
1056
|
|
|
1057
|
+
// parse binary files
|
|
1058
|
+
this.app.post(`${this.routerPrefix}v1/file/*`, multer().fields([{ name: 'file', maxCount: 1 }]), (req, res, next) => next());
|
|
1059
|
+
|
|
1016
1060
|
this.unload = async () => {
|
|
1017
1061
|
if (this.config.webInstance) {
|
|
1018
|
-
await this.adapter.setForeignStateAsync(this.namespace
|
|
1062
|
+
await this.adapter.setForeignStateAsync(`${this.namespace}.info.extension`, false, true);
|
|
1019
1063
|
}
|
|
1020
1064
|
this.checkInterval && clearInterval(this.checkInterval);
|
|
1021
1065
|
this.checkInterval = null;
|
package/main.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
6
|
const utils = require('@iobroker/adapter-core'); // Get common adapter utils
|
|
7
|
-
const
|
|
7
|
+
const { WebServer } = require('@iobroker/webserver');
|
|
8
8
|
const RestAPI = require('./lib/rest-api.js');
|
|
9
9
|
const adapterName = require('./package.json').name.split('.').pop();
|
|
10
10
|
|
|
@@ -106,7 +106,12 @@ function initWebServer(settings, callback) {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
try {
|
|
109
|
-
|
|
109
|
+
const webserver = new WebServer({
|
|
110
|
+
app,
|
|
111
|
+
adapter,
|
|
112
|
+
secure: adapter.config.secure
|
|
113
|
+
});
|
|
114
|
+
server.server = await webserver.init();
|
|
110
115
|
} catch (err) {
|
|
111
116
|
adapter.log.error(`Cannot create webserver: ${err}`);
|
|
112
117
|
adapter.terminate ? adapter.terminate(1) : process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.rest-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "RESTful interface for ioBroker with GUI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "bluefox",
|
|
@@ -14,33 +14,38 @@
|
|
|
14
14
|
"swagger-ui",
|
|
15
15
|
"web"
|
|
16
16
|
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=16"
|
|
19
|
+
},
|
|
17
20
|
"repository": {
|
|
18
21
|
"type": "git",
|
|
19
22
|
"url": "https://github.com/ioBroker/ioBroker.rest-api"
|
|
20
23
|
},
|
|
21
24
|
"dependencies": {
|
|
22
|
-
"@iobroker/adapter-core": "^
|
|
23
|
-
"@iobroker/socket-classes": "^1.
|
|
24
|
-
"
|
|
25
|
+
"@iobroker/adapter-core": "^3.1.4",
|
|
26
|
+
"@iobroker/socket-classes": "^1.5.0",
|
|
27
|
+
"@iobroker/webserver": "^1.0.3",
|
|
28
|
+
"axios": "^1.6.8",
|
|
25
29
|
"body-parser": "^1.20.2",
|
|
26
30
|
"cors": "^2.8.5",
|
|
27
|
-
"express": "^4.
|
|
31
|
+
"express": "^4.19.2",
|
|
32
|
+
"multer": "^1.4.5-lts.1",
|
|
28
33
|
"swagger-node-runner-fork": "^0.8.0",
|
|
29
|
-
"swagger-ui-express": "^
|
|
34
|
+
"swagger-ui-express": "^5.0.0",
|
|
30
35
|
"yamljs": "^0.3.0"
|
|
31
36
|
},
|
|
32
37
|
"devDependencies": {
|
|
33
|
-
"@alcalzone/release-script": "^3.
|
|
34
|
-
"@alcalzone/release-script-plugin-iobroker": "^3.
|
|
35
|
-
"@alcalzone/release-script-plugin-license": "^3.
|
|
36
|
-
"@iobroker/adapter-dev": "^1.
|
|
37
|
-
"@iobroker/testing": "^4.1.
|
|
38
|
-
"chai": "^4.
|
|
38
|
+
"@alcalzone/release-script": "^3.7.0",
|
|
39
|
+
"@alcalzone/release-script-plugin-iobroker": "^3.7.0",
|
|
40
|
+
"@alcalzone/release-script-plugin-license": "^3.7.0",
|
|
41
|
+
"@iobroker/adapter-dev": "^1.3.0",
|
|
42
|
+
"@iobroker/testing": "^4.1.3",
|
|
43
|
+
"chai": "^4.4.1",
|
|
39
44
|
"eslint-plugin-eqeqeq-fix": "^1.0.3",
|
|
40
45
|
"eslint-plugin-only-warn": "^1.1.0",
|
|
41
|
-
"eslint-plugin-react": "^7.
|
|
46
|
+
"eslint-plugin-react": "^7.34.1",
|
|
42
47
|
"gulp": "^4.0.2",
|
|
43
|
-
"mocha": "^10.
|
|
48
|
+
"mocha": "^10.4.0"
|
|
44
49
|
},
|
|
45
50
|
"bugs": {
|
|
46
51
|
"url": "https://github.com/ioBroker/ioBroker.rest-api/issues"
|
|
@@ -62,7 +67,8 @@
|
|
|
62
67
|
"release-patch": "release-script patch --yes --no-update-lockfile",
|
|
63
68
|
"release-minor": "release-script minor --yes --no-update-lockfile",
|
|
64
69
|
"release-major": "release-script major --yes --no-update-lockfile",
|
|
65
|
-
"translate": "translate-adapter"
|
|
70
|
+
"translate": "translate-adapter",
|
|
71
|
+
"update-packages": "ncu --upgrade"
|
|
66
72
|
},
|
|
67
73
|
"license": "Apache-2.0"
|
|
68
74
|
}
|