iobroker.rest-api 1.0.4 → 1.1.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 +16 -12
- package/io-package.json +27 -25
- package/lib/api/controllers/file.js +74 -0
- package/lib/api/swagger/swagger.yaml +181 -22
- package/lib/rest-api.js +69 -25
- package/main.js +1 -1
- package/package.json +17 -13
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 very useful web interface to play with the requests:
|
|
16
|
+
It has a very useful 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)
|
|
@@ -73,6 +73,7 @@ You cannot send POST request to commands via GUI.
|
|
|
73
73
|
<!-- START -->
|
|
74
74
|
### States
|
|
75
75
|
- `getStates(pattern)` - get the list of states for pattern (e.g. for system.adapter.admin.0.*). GUI can have problems by visualization of answer.
|
|
76
|
+
- `getForeignStates(pattern)` - same as getStates
|
|
76
77
|
- `getState(id)` - get state value by ID
|
|
77
78
|
- `setState(id, state)` - set state value with JSON object (e.g. `{"val": 1, "ack": true}`)
|
|
78
79
|
- `getBinaryState(id)` - get binary state by ID
|
|
@@ -126,7 +127,6 @@ You cannot send POST request to commands via GUI.
|
|
|
126
127
|
- `getAllObjects()` - read all objects as list. GUI can have problems by visualization of answer.
|
|
127
128
|
- `extendObject(id, obj)` - modify object by ID with JSON. (.e.g. `{"common":{"enabled": true}}`)
|
|
128
129
|
- `getForeignObjects(pattern, type)` - same as getObjects
|
|
129
|
-
- `getForeignStates(pattern)` - same as getStates
|
|
130
130
|
- `delObjects(id, options)` - delete objects by pattern
|
|
131
131
|
|
|
132
132
|
### Others
|
|
@@ -142,15 +142,19 @@ You cannot send POST request to commands via GUI.
|
|
|
142
142
|
|
|
143
143
|
<!-- END -->
|
|
144
144
|
|
|
145
|
-
## Todo
|
|
146
|
-
- [ ] Implement GET,PATCH,POST,DELETE file operations
|
|
147
|
-
|
|
148
145
|
<!--
|
|
149
146
|
Placeholder for the next version (at the beginning of the line):
|
|
150
147
|
### **WORK IN PROGRESS**
|
|
151
148
|
-->
|
|
152
149
|
|
|
153
150
|
## Changelog
|
|
151
|
+
### 1.1.0 (2023-05-03)
|
|
152
|
+
* (bluefox) Converting of the setState values to the according type
|
|
153
|
+
* (bluefox) Implemented file operations
|
|
154
|
+
|
|
155
|
+
### 1.0.5 (2023-03-27)
|
|
156
|
+
* (Apollon77) Prepare for future js-controller versions
|
|
157
|
+
|
|
154
158
|
### 1.0.4 (2022-08-31)
|
|
155
159
|
* (bluefox) Check if the port is occupied only on defined interface
|
|
156
160
|
|
|
@@ -190,4 +194,4 @@ You cannot send POST request to commands via GUI.
|
|
|
190
194
|
## License
|
|
191
195
|
Apache 2.0
|
|
192
196
|
|
|
193
|
-
Copyright (c) 2017-
|
|
197
|
+
Copyright (c) 2017-2023 bluefox <dogafox@gmail.com>
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "rest-api",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"1.1.0": {
|
|
7
|
+
"en": "Converting of the setState values to the according type\nImplemented file operations",
|
|
8
|
+
"de": "Umrechnung der setState-Werte in den entsprechenden Typ\nImplementierung von Dateioperationen",
|
|
9
|
+
"ru": "Преобразование значений setState в согласно типу\nРеализованные файловые операции",
|
|
10
|
+
"pt": "Convertendo dos valores setState para o tipo de acordo\nOperações de arquivo implementadas",
|
|
11
|
+
"nl": "Omkeren van de setstate waarden naar het type\nGeïmplementeerde bestand operaties",
|
|
12
|
+
"fr": "Conversion des valeurs déterminées de l'état dans le type suivant\nOpérations de fichiers mises en œuvre",
|
|
13
|
+
"it": "Convertizione dei valori setState secondo il tipo\nOperazioni di file implementate",
|
|
14
|
+
"es": "Convertir los valores establecidos en el tipo\nOperaciones de archivo aplicadas",
|
|
15
|
+
"pl": "Przetłumaczył wartości zbioru zgodnie z typem\nImplementacja",
|
|
16
|
+
"uk": "Перетворення значень setState до за типом\nРеалізовані операції файлів",
|
|
17
|
+
"zh-cn": "避免按类型划分国家数值\n执行档案业务"
|
|
18
|
+
},
|
|
19
|
+
"1.0.5": {
|
|
20
|
+
"en": "Prepare for future js-controller versions",
|
|
21
|
+
"de": "Bereiten Sie sich auf zukünftige js-Controller-Versionen",
|
|
22
|
+
"ru": "Подготовьтесь к будущим версиям js-controller",
|
|
23
|
+
"pt": "Prepare-se para futuras versões js-controller",
|
|
24
|
+
"nl": "Bereid je voor op toekomstige Js-controller versie",
|
|
25
|
+
"fr": "Préparez-vous pour les versions futures de js-controller",
|
|
26
|
+
"it": "Prepararsi per le future versioni js-controller",
|
|
27
|
+
"es": "Prepararse para futuras versiones js-controller",
|
|
28
|
+
"pl": "Prepare for future js-controller version",
|
|
29
|
+
"uk": "Підготовка до майбутніх версій js-controller",
|
|
30
|
+
"zh-cn": "今后防污版本的编制"
|
|
31
|
+
},
|
|
6
32
|
"1.0.4": {
|
|
7
33
|
"en": "Check if the port is occupied only on defined interface",
|
|
8
34
|
"de": "Überprüfen Sie, ob der Port nur auf definierter Schnittstelle besetzt ist",
|
|
@@ -62,30 +88,6 @@
|
|
|
62
88
|
"es": "Ruta de envío agregada",
|
|
63
89
|
"pl": "Dodano ścieżkę sendTo",
|
|
64
90
|
"zh-cn": "添加了 sendTo 路径"
|
|
65
|
-
},
|
|
66
|
-
"0.5.0": {
|
|
67
|
-
"en": "Some access errors were corrected",
|
|
68
|
-
"de": "Einige Zugriffsfehler wurden behoben",
|
|
69
|
-
"ru": "Исправлены некоторые ошибки доступа",
|
|
70
|
-
"pt": "Alguns erros de acesso foram corrigidos",
|
|
71
|
-
"nl": "Sommige toegangsfouten zijn gecorrigeerd",
|
|
72
|
-
"fr": "Certaines erreurs d'accès ont été corrigées",
|
|
73
|
-
"it": "Alcuni errori di accesso sono stati corretti",
|
|
74
|
-
"es": "Se corrigieron algunos errores de acceso",
|
|
75
|
-
"pl": "Poprawiono niektóre błędy dostępu",
|
|
76
|
-
"zh-cn": "一些访问错误已得到纠正"
|
|
77
|
-
},
|
|
78
|
-
"0.4.0": {
|
|
79
|
-
"en": "Added socket commands",
|
|
80
|
-
"de": "Socket-Befehle hinzugefügt",
|
|
81
|
-
"ru": "Добавлены команды сокета",
|
|
82
|
-
"pt": "Comandos de soquete adicionados",
|
|
83
|
-
"nl": "Socket-opdrachten toegevoegd",
|
|
84
|
-
"fr": "Commandes de socket ajoutées",
|
|
85
|
-
"it": "Aggiunti comandi socket",
|
|
86
|
-
"es": "Comandos de socket agregados",
|
|
87
|
-
"pl": "Dodano polecenia dotyczące gniazd",
|
|
88
|
-
"zh-cn": "添加了套接字命令"
|
|
89
91
|
}
|
|
90
92
|
},
|
|
91
93
|
"title": "REST API",
|
|
@@ -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
|
+
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
swagger: "2.0"
|
|
3
3
|
info:
|
|
4
4
|
description: "This is a REST server for ioBroker."
|
|
5
|
-
version: "1.0
|
|
5
|
+
version: "1.1.0"
|
|
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
|
|
@@ -625,9 +627,7 @@ paths:
|
|
|
625
627
|
200:
|
|
626
628
|
description: "successful operation"
|
|
627
629
|
schema:
|
|
628
|
-
|
|
629
|
-
items:
|
|
630
|
-
$ref: "#/definitions/Object"
|
|
630
|
+
$ref: "#/definitions/ObjectArray"
|
|
631
631
|
404:
|
|
632
632
|
description: "URL or session not found"
|
|
633
633
|
422:
|
|
@@ -997,8 +997,136 @@ paths:
|
|
|
997
997
|
description: "Invalid state ID supplied"
|
|
998
998
|
500:
|
|
999
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"
|
|
1000
1127
|
|
|
1001
|
-
|
|
1128
|
+
|
|
1129
|
+
# commands start
|
|
1002
1130
|
/command/getStates:
|
|
1003
1131
|
get:
|
|
1004
1132
|
tags:
|
|
@@ -1016,6 +1144,23 @@ paths:
|
|
|
1016
1144
|
responses:
|
|
1017
1145
|
200:
|
|
1018
1146
|
description: "successful operation"
|
|
1147
|
+
/command/getForeignStates:
|
|
1148
|
+
get:
|
|
1149
|
+
tags:
|
|
1150
|
+
- "commands"
|
|
1151
|
+
summary: "same as getStates"
|
|
1152
|
+
produces:
|
|
1153
|
+
- "application/json"
|
|
1154
|
+
parameters:
|
|
1155
|
+
|
|
1156
|
+
- name: "pattern"
|
|
1157
|
+
in: "query"
|
|
1158
|
+
description: ""
|
|
1159
|
+
type: "string"
|
|
1160
|
+
required: true
|
|
1161
|
+
responses:
|
|
1162
|
+
200:
|
|
1163
|
+
description: "successful operation"
|
|
1019
1164
|
/command/getState:
|
|
1020
1165
|
get:
|
|
1021
1166
|
tags:
|
|
@@ -1959,23 +2104,6 @@ paths:
|
|
|
1959
2104
|
responses:
|
|
1960
2105
|
200:
|
|
1961
2106
|
description: "successful operation"
|
|
1962
|
-
/command/getForeignStates:
|
|
1963
|
-
get:
|
|
1964
|
-
tags:
|
|
1965
|
-
- "commands"
|
|
1966
|
-
summary: "same as getStates"
|
|
1967
|
-
produces:
|
|
1968
|
-
- "application/json"
|
|
1969
|
-
parameters:
|
|
1970
|
-
|
|
1971
|
-
- name: "pattern"
|
|
1972
|
-
in: "query"
|
|
1973
|
-
description: ""
|
|
1974
|
-
type: "string"
|
|
1975
|
-
required: true
|
|
1976
|
-
responses:
|
|
1977
|
-
200:
|
|
1978
|
-
description: "successful operation"
|
|
1979
2107
|
/command/delObjects:
|
|
1980
2108
|
get:
|
|
1981
2109
|
tags:
|
|
@@ -2231,6 +2359,10 @@ definitions:
|
|
|
2231
2359
|
native:
|
|
2232
2360
|
type: "object"
|
|
2233
2361
|
description: "Native state description"
|
|
2362
|
+
ObjectArray:
|
|
2363
|
+
type: "object"
|
|
2364
|
+
additionalProperties:
|
|
2365
|
+
$ref: "#/definitions/Object"
|
|
2234
2366
|
UrlHook:
|
|
2235
2367
|
type: "object"
|
|
2236
2368
|
properties:
|
|
@@ -2408,6 +2540,33 @@ definitions:
|
|
|
2408
2540
|
type: "array"
|
|
2409
2541
|
items:
|
|
2410
2542
|
$ref: "#/definitions/EnumEntry"
|
|
2543
|
+
FileEntry:
|
|
2544
|
+
type: "object"
|
|
2545
|
+
properties:
|
|
2546
|
+
file:
|
|
2547
|
+
type: "string"
|
|
2548
|
+
description: "File name"
|
|
2549
|
+
stats:
|
|
2550
|
+
type: "object"
|
|
2551
|
+
description: "File size"
|
|
2552
|
+
properties:
|
|
2553
|
+
size:
|
|
2554
|
+
type: "number"
|
|
2555
|
+
description: "File size in bytes"
|
|
2556
|
+
isDir:
|
|
2557
|
+
type: "boolean"
|
|
2558
|
+
description: "Is directory"
|
|
2559
|
+
modifiedAt:
|
|
2560
|
+
type: "number"
|
|
2561
|
+
description: "Modification time in ms"
|
|
2562
|
+
createdAt:
|
|
2563
|
+
type: "number"
|
|
2564
|
+
description: "Creation time in ms"
|
|
2565
|
+
DirResponse:
|
|
2566
|
+
type: "array"
|
|
2567
|
+
items:
|
|
2568
|
+
$ref: "#/definitions/FileEntry"
|
|
2569
|
+
|
|
2411
2570
|
SendToData:
|
|
2412
2571
|
type: "object"
|
|
2413
2572
|
properties:
|
package/lib/rest-api.js
CHANGED
|
@@ -13,8 +13,9 @@ 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
|
-
const
|
|
18
|
+
const pattern2RegEx = utils.commonTools.pattern2RegEx;
|
|
18
19
|
const CommandsAdmin = require('@iobroker/socket-classes').SocketCommandsAdmin;
|
|
19
20
|
const CommandsCommon = require('@iobroker/socket-classes').SocketCommands;
|
|
20
21
|
const common = require('./common');
|
|
@@ -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 => {
|
|
@@ -533,7 +554,7 @@ function SwaggerUI(_ignore, webSettings, adapter, instanceSettings, app, callbac
|
|
|
533
554
|
const item = {id, delta: options.delta, onchange: options.onchange};
|
|
534
555
|
this.subscribes[urlHash][type].push(item);
|
|
535
556
|
if (item.id.includes('*')) {
|
|
536
|
-
item.regEx = new RegExp(
|
|
557
|
+
item.regEx = new RegExp(pattern2RegEx(item.id));
|
|
537
558
|
}
|
|
538
559
|
|
|
539
560
|
if (type === 'state') {
|
|
@@ -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 LE =
|
|
7
|
+
const LE = utils.commonTools.letsEncrypt;
|
|
8
8
|
const RestAPI = require('./lib/rest-api.js');
|
|
9
9
|
const adapterName = require('./package.json').name.split('.').pop();
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.rest-api",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "RESTful interface for ioBroker with GUI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "bluefox",
|
|
@@ -19,27 +19,29 @@
|
|
|
19
19
|
"url": "https://github.com/ioBroker/ioBroker.rest-api"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@iobroker/adapter-core": "^2.6.
|
|
23
|
-
"@iobroker/socket-classes": "^
|
|
24
|
-
"axios": "^
|
|
25
|
-
"body-parser": "^1.20.
|
|
22
|
+
"@iobroker/adapter-core": "^2.6.8",
|
|
23
|
+
"@iobroker/socket-classes": "^1.1.5",
|
|
24
|
+
"axios": "^1.4.0",
|
|
25
|
+
"body-parser": "^1.20.2",
|
|
26
26
|
"cors": "^2.8.5",
|
|
27
|
-
"express": "^4.18.
|
|
27
|
+
"express": "^4.18.2",
|
|
28
28
|
"swagger-node-runner-fork": "^0.8.0",
|
|
29
|
-
"swagger-ui-express": "^4.
|
|
30
|
-
"yamljs": "^0.3.0"
|
|
29
|
+
"swagger-ui-express": "^4.6.2",
|
|
30
|
+
"yamljs": "^0.3.0",
|
|
31
|
+
"multer": "^1.4.5-lts.1"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@alcalzone/release-script": "^3.5.9",
|
|
34
35
|
"@alcalzone/release-script-plugin-iobroker": "^3.5.9",
|
|
35
36
|
"@alcalzone/release-script-plugin-license": "^3.5.9",
|
|
37
|
+
"@iobroker/adapter-dev": "^1.2.0",
|
|
36
38
|
"@iobroker/testing": "^4.1.0",
|
|
37
|
-
"chai": "^4.3.
|
|
39
|
+
"chai": "^4.3.7",
|
|
38
40
|
"eslint-plugin-eqeqeq-fix": "^1.0.3",
|
|
39
|
-
"eslint-plugin-only-warn": "^1.0
|
|
40
|
-
"eslint-plugin-react": "^7.
|
|
41
|
+
"eslint-plugin-only-warn": "^1.1.0",
|
|
42
|
+
"eslint-plugin-react": "^7.32.2",
|
|
41
43
|
"gulp": "^4.0.2",
|
|
42
|
-
"mocha": "^
|
|
44
|
+
"mocha": "^10.2.0"
|
|
43
45
|
},
|
|
44
46
|
"bugs": {
|
|
45
47
|
"url": "https://github.com/ioBroker/ioBroker.rest-api/issues"
|
|
@@ -60,7 +62,9 @@
|
|
|
60
62
|
"release": "release-script",
|
|
61
63
|
"release-patch": "release-script patch --yes --no-update-lockfile",
|
|
62
64
|
"release-minor": "release-script minor --yes --no-update-lockfile",
|
|
63
|
-
"release-major": "release-script major --yes --no-update-lockfile"
|
|
65
|
+
"release-major": "release-script major --yes --no-update-lockfile",
|
|
66
|
+
"translate": "translate-adapter",
|
|
67
|
+
"update-packages": "ncu --upgrade"
|
|
64
68
|
},
|
|
65
69
|
"license": "Apache-2.0"
|
|
66
70
|
}
|