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 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 beneficial web interface to play with the requests:
17
17
 
18
18
  ![Screenshot](img/screen.png)
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 on state or object changes
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 subscribe on data changes via long polling.
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-extension. In this case the path is available under http://iipaddress:8082/rest
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-2023 bluefox <dogafox@gmail.com>
203
+ Copyright (c) 2017-2024 bluefox <dogafox@gmail.com>
@@ -162,7 +162,7 @@
162
162
  "leTab": {
163
163
  "type": "panel",
164
164
  "label": "Let's Encrypt SSL",
165
- "disabled": "!data.secure || !!data.webInstance",
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
- "_link": {
178
- "newLine": true,
179
- "type": "staticLink",
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": "1.0.5",
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: "1.0.5"
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
- # commands start
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 + '/api/swagger/swagger.yaml';
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 + '/api/swagger/swaggerEdited.yaml';
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 + '/api/swagger/swaggerEdited.yaml';
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 + '/api/swagger/swagger_extension.yaml';
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 + 'api-docs/swagger.json', (req, res) =>
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 + 'api-doc/', swaggerUi.serve, swaggerUi.setup(swaggerDocument, options));
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 + 'api-doc/'));
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 = 'system.user.' + 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 + '/../img/favicon.ico'));
726
+ res.send(fs.readFileSync(`${__dirname}/../img/favicon.ico`));
706
727
  });
707
728
 
708
729
  // authenticate
709
- this.app.use(this.routerPrefix + 'v1/*', (req, res, next) => {
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 + 'v1/polling', (req, res, next) => {
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 + 'v1/command/*', (req, res) => {
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('/' + WEB_EXTENSION_PREFIX) ? req.originalUrl.split('/')[4] : req.originalUrl.split('/')[3];
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] == 'string' && params[name].startsWith('{') && params[name].endsWith('}')) {
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 + 'log/*',(req, res) => {
954
+ this.app.get(`${this.routerPrefix}log/*`,(req, res) => {
914
955
  let parts = decodeURIComponent(req.url).split('/');
915
- if (req.originalUrl.startsWith('/' + WEB_EXTENSION_PREFIX)) {
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('system.host.' + host, 'getLogFile', {filename, transport}, result => {
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 + '/../../../', logFolder).replace(/\\/g, '/')).replace(/\\/g, '/');
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 + '/../../', logFolder).replace(/\\/g, '/')).replace(/\\/g, '/');
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 + '.info.extension', false, true);
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 = utils.commonTools.letsEncrypt;
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
- server.server = await LE.createServerAsync(app, settings, adapter.config.certificates, adapter.config.leConfig, adapter.log);
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": "1.0.5",
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": "^2.6.7",
23
- "@iobroker/socket-classes": "^1.1.5",
24
- "axios": "^1.3.4",
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.18.2",
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": "^4.6.2",
34
+ "swagger-ui-express": "^5.0.0",
30
35
  "yamljs": "^0.3.0"
31
36
  },
32
37
  "devDependencies": {
33
- "@alcalzone/release-script": "^3.5.9",
34
- "@alcalzone/release-script-plugin-iobroker": "^3.5.9",
35
- "@alcalzone/release-script-plugin-license": "^3.5.9",
36
- "@iobroker/adapter-dev": "^1.2.0",
37
- "@iobroker/testing": "^4.1.0",
38
- "chai": "^4.3.7",
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.32.2",
46
+ "eslint-plugin-react": "^7.34.1",
42
47
  "gulp": "^4.0.2",
43
- "mocha": "^10.2.0"
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
  }