From 7865ddc51f393e68f318054c8ee6cf4b1d432070 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Sat, 3 Jun 2023 16:36:53 +0200 Subject: [PATCH 001/163] use espMqttClient, qos2 fixed --- lib/espMqttClient/LICENSE | 21 + lib/espMqttClient/README.md | 54 ++ lib/espMqttClient/src/Config.h | 58 ++ lib/espMqttClient/src/Helpers.h | 49 ++ lib/espMqttClient/src/Logging.h | 43 + lib/espMqttClient/src/MqttClient.cpp | 754 ++++++++++++++++++ lib/espMqttClient/src/MqttClient.h | 190 +++++ lib/espMqttClient/src/MqttClientSetup.h | 116 +++ lib/espMqttClient/src/Outbox.h | 207 +++++ lib/espMqttClient/src/Packets/Constants.h | 77 ++ lib/espMqttClient/src/Packets/Packet.cpp | 438 ++++++++++ lib/espMqttClient/src/Packets/Packet.h | 155 ++++ lib/espMqttClient/src/Packets/Parser.cpp | 316 ++++++++ lib/espMqttClient/src/Packets/Parser.h | 100 +++ .../src/Packets/RemainingLength.cpp | 57 ++ .../src/Packets/RemainingLength.h | 32 + lib/espMqttClient/src/Packets/String.cpp | 26 + lib/espMqttClient/src/Packets/String.h | 22 + .../src/Transport/ClientAsync.cpp | 58 ++ lib/espMqttClient/src/Transport/ClientAsync.h | 44 + .../src/Transport/ClientPosix.cpp | 92 +++ lib/espMqttClient/src/Transport/ClientPosix.h | 51 ++ .../src/Transport/ClientSecureSync.cpp | 71 ++ .../src/Transport/ClientSecureSync.h | 34 + .../src/Transport/ClientSync.cpp | 71 ++ lib/espMqttClient/src/Transport/ClientSync.h | 34 + lib/espMqttClient/src/Transport/IPAddress.cpp | 32 + lib/espMqttClient/src/Transport/IPAddress.h | 28 + lib/espMqttClient/src/Transport/Transport.h | 28 + lib/espMqttClient/src/TypeDefs.cpp | 51 ++ lib/espMqttClient/src/TypeDefs.h | 73 ++ lib/espMqttClient/src/espMqttClient.cpp | 113 +++ lib/espMqttClient/src/espMqttClient.h | 80 ++ lib/espMqttClient/src/espMqttClientAsync.cpp | 61 ++ lib/espMqttClient/src/espMqttClientAsync.h | 36 + lib/framework/ESP8266React.h | 2 +- lib/framework/MqttPubSub.h | 163 ---- lib/framework/MqttSettingsService.cpp | 32 +- lib/framework/MqttSettingsService.h | 26 +- src/emsdevice.cpp | 10 +- src/emsesp.cpp | 3 + src/mqtt.cpp | 62 +- src/mqtt.h | 9 +- 43 files changed, 3749 insertions(+), 230 deletions(-) create mode 100644 lib/espMqttClient/LICENSE create mode 100644 lib/espMqttClient/README.md create mode 100644 lib/espMqttClient/src/Config.h create mode 100644 lib/espMqttClient/src/Helpers.h create mode 100644 lib/espMqttClient/src/Logging.h create mode 100644 lib/espMqttClient/src/MqttClient.cpp create mode 100644 lib/espMqttClient/src/MqttClient.h create mode 100644 lib/espMqttClient/src/MqttClientSetup.h create mode 100644 lib/espMqttClient/src/Outbox.h create mode 100644 lib/espMqttClient/src/Packets/Constants.h create mode 100644 lib/espMqttClient/src/Packets/Packet.cpp create mode 100644 lib/espMqttClient/src/Packets/Packet.h create mode 100644 lib/espMqttClient/src/Packets/Parser.cpp create mode 100644 lib/espMqttClient/src/Packets/Parser.h create mode 100644 lib/espMqttClient/src/Packets/RemainingLength.cpp create mode 100644 lib/espMqttClient/src/Packets/RemainingLength.h create mode 100644 lib/espMqttClient/src/Packets/String.cpp create mode 100644 lib/espMqttClient/src/Packets/String.h create mode 100644 lib/espMqttClient/src/Transport/ClientAsync.cpp create mode 100644 lib/espMqttClient/src/Transport/ClientAsync.h create mode 100644 lib/espMqttClient/src/Transport/ClientPosix.cpp create mode 100644 lib/espMqttClient/src/Transport/ClientPosix.h create mode 100644 lib/espMqttClient/src/Transport/ClientSecureSync.cpp create mode 100644 lib/espMqttClient/src/Transport/ClientSecureSync.h create mode 100644 lib/espMqttClient/src/Transport/ClientSync.cpp create mode 100644 lib/espMqttClient/src/Transport/ClientSync.h create mode 100644 lib/espMqttClient/src/Transport/IPAddress.cpp create mode 100644 lib/espMqttClient/src/Transport/IPAddress.h create mode 100644 lib/espMqttClient/src/Transport/Transport.h create mode 100644 lib/espMqttClient/src/TypeDefs.cpp create mode 100644 lib/espMqttClient/src/TypeDefs.h create mode 100644 lib/espMqttClient/src/espMqttClient.cpp create mode 100644 lib/espMqttClient/src/espMqttClient.h create mode 100644 lib/espMqttClient/src/espMqttClientAsync.cpp create mode 100644 lib/espMqttClient/src/espMqttClientAsync.h delete mode 100644 lib/framework/MqttPubSub.h diff --git a/lib/espMqttClient/LICENSE b/lib/espMqttClient/LICENSE new file mode 100644 index 000000000..1cc5546f9 --- /dev/null +++ b/lib/espMqttClient/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Bert Melis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/espMqttClient/README.md b/lib/espMqttClient/README.md new file mode 100644 index 000000000..586d3339e --- /dev/null +++ b/lib/espMqttClient/README.md @@ -0,0 +1,54 @@ +# espMqttClient + +MQTT client library for the Espressif devices ESP8266 and ESP32 on the Arduino framework. +Aims to be a non-blocking, fully compliant MQTT 3.1.1 client. + +![platformio](https://github.com/bertmelis/espMqttClient/actions/workflows/build_platformio.yml/badge.svg) +![cpplint](https://github.com/bertmelis/espMqttClient/actions/workflows/cpplint.yml/badge.svg) +![cppcheck](https://github.com/bertmelis/espMqttClient/actions/workflows/cppcheck.yml/badge.svg) +[![PlatformIO Registry](https://badges.registry.platformio.org/packages/bertmelis/library/espMqttClient.svg)](https://registry.platformio.org/libraries/bertmelis/espMqttClient) + +# Features + +- MQTT 3.1.1 compliant library +- Sending and receiving at all QoS levels +- TCP and TCP/TLS using standard WiFiClient and WiFiClientSecure connections +- Virtually unlimited incoming and outgoing payload sizes +- Readable and understandable code +- Fully async clients available via [AsyncTCP](https://github.com/me-no-dev/AsyncTCP) or [ESPAsnycTCP](https://github.com/me-no-dev/ESPAsyncTCP) (no TLS supported) +- Supported platforms: + - Espressif ESP8266 and ESP32 using the Arduino framework +- Basic Linux compatibility*. This includes WSL on Windows + + > Linux compatibility is mainly for automatic testing. It relies on a quick and dirty Arduino-style `Client` with a POSIX TCP client underneath and Arduino-style `IPAddress` class. These are lacking many features needed for proper Linux support. + +# Documentation + +See [documentation](https://www.emelis.net/espMqttClient/) and the [examples](examples/). + +## Limitations + +### MQTT 3.1.1 Compliancy + +Outgoing messages and session data are not stored in non-volatile memory. Any events like loss of power or sudden resets result in loss of data. Despite this limitation, one could still consider this library as fully complaint based on the non normative remark in point 4.1.1 of the specification. + +### Non-blocking + +This library aims to be fully non-blocking. It is however limited by the underlying `WiFiClient` library which is part of the Arduino framework and has a blocking `connect` method. This is not an issue on ESP32 because the call is offloaded to a separate task. On ESP8266 however, connecting will block until succesful or until the connection timeouts. + +If you need a fully asynchronous MQTT client, you can use `espMqttClientAsync` which uses AsyncTCP/ESPAsyncTCP under the hood. These underlying libraries do not support TLS (anymore). I will not provide support TLS for the async client. + +# Bugs and feature requests + +Please use Github's facilities to get in touch. + +# About this library + +This client wouldn't exist without [Async-mqtt-client](https://github.com/marvinroger/async-mqtt-client). It has been my go-to MQTT client for many years. It was fast, reliable and had features that were non-existing in alternative libraries. However, the underlying async TCP libraries are lacking updates, especially updates related to secure connections. Adapting this library to use up-to-date TCP clients would not be trivial. I eventually decided to write my own MQTT library, from scratch. + +The result is an almost non-blocking library with no external dependencies. The library is almost a drop-in replacement for the async-mqtt-client except a few parameter type changes (eg. `uint8_t*` instead of `char*` for payloads). + +# License + +This library is released under the MIT Licence. A copy is included in the repo. +Parts of this library, most notably the API, are based on [Async MQTT client for ESP8266 and ESP32](https://github.com/marvinroger/async-mqtt-client). diff --git a/lib/espMqttClient/src/Config.h b/lib/espMqttClient/src/Config.h new file mode 100644 index 000000000..aba779565 --- /dev/null +++ b/lib/espMqttClient/src/Config.h @@ -0,0 +1,58 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#ifndef EMC_TX_TIMEOUT +#define EMC_TX_TIMEOUT 5000 +#endif + +#ifndef EMC_RX_BUFFER_SIZE +#define EMC_RX_BUFFER_SIZE 1440 +#endif + +#ifndef EMC_TX_BUFFER_SIZE +#define EMC_TX_BUFFER_SIZE 1440 +#endif + +#ifndef EMC_MAX_TOPIC_LENGTH +#define EMC_MAX_TOPIC_LENGTH 128 +#endif + +#ifndef EMC_PAYLOAD_BUFFER_SIZE +#define EMC_PAYLOAD_BUFFER_SIZE 32 +#endif + +#ifndef EMC_MIN_FREE_MEMORY +#define EMC_MIN_FREE_MEMORY 4096 +#endif + +#ifndef EMC_ESP8266_MULTITHREADING +#define EMC_ESP8266_MULTITHREADING 0 +#endif + +#ifndef EMC_ALLOW_NOT_CONNECTED_PUBLISH +#define EMC_ALLOW_NOT_CONNECTED_PUBLISH 1 +#endif + +#ifndef EMC_WAIT_FOR_CONNACK +#define EMC_WAIT_FOR_CONNACK 1 +#endif + +#ifndef EMC_CLIENTID_LENGTH +// esp8266abc123 and esp32abcdef123456 +#define EMC_CLIENTID_LENGTH 23 + 1 +#endif + +#ifndef EMC_TASK_STACK_SIZE +#define EMC_TASK_STACK_SIZE 5120 +#endif + +#ifndef EMC_USE_WATCHDOG +#define EMC_USE_WATCHDOG 0 +#endif diff --git a/lib/espMqttClient/src/Helpers.h b/lib/espMqttClient/src/Helpers.h new file mode 100644 index 000000000..4a19224ba --- /dev/null +++ b/lib/espMqttClient/src/Helpers.h @@ -0,0 +1,49 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#if defined(ARDUINO_ARCH_ESP32) + #include // millis(), ESP.getFreeHeap(); + #include "freertos/FreeRTOS.h" + #include "freertos/task.h" + #include "esp_task_wdt.h" + #define EMC_SEMAPHORE_TAKE() xSemaphoreTake(_xSemaphore, portMAX_DELAY) + #define EMC_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) + #define EMC_GET_FREE_MEMORY() std::max(ESP.getMaxAllocHeap(), ESP.getMaxAllocPsram()) + #define EMC_YIELD() taskYIELD() + #define EMC_GENERATE_CLIENTID(x) snprintf(x, EMC_CLIENTID_LENGTH, "esp32%06llx", ESP.getEfuseMac()); +#elif defined(ARDUINO_ARCH_ESP8266) + #include // millis(), ESP.getFreeHeap(); + #if EMC_ESP8266_MULTITHREADING + // This lib doesn't run use multithreading on ESP8266 + // _xSemaphore defined as std::atomic + #define EMC_SEMAPHORE_TAKE() while (_xSemaphore) { /*ESP.wdtFeed();*/ } _xSemaphore = true + #define EMC_SEMAPHORE_GIVE() _xSemaphore = false + #else + #define EMC_SEMAPHORE_TAKE() + #define EMC_SEMAPHORE_GIVE() + #endif + #define EMC_GET_FREE_MEMORY() ESP.getMaxFreeBlockSize() + // no need to yield for ESP8266, the Arduino framework does this internally + // yielding in async is forbidden (will crash) + #define EMC_YIELD() + #define EMC_GENERATE_CLIENTID(x) snprintf(x, EMC_CLIENTID_LENGTH, "esp8266%06x", ESP.getChipId()); +#elif defined(__linux__) + #include // NOLINT [build/c++11] + #include // NOLINT [build/c++11] for yield() + #define millis() std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count() + #define EMC_GET_FREE_MEMORY() 1000000000 + #define EMC_YIELD() std::this_thread::yield() + #define EMC_GENERATE_CLIENTID(x) snprintf(x, EMC_CLIENTID_LENGTH, "Client%04d%04d%04d", rand()%10000, rand()%10000, rand()%10000) + #include // NOLINT [build/c++11] + #define EMC_SEMAPHORE_TAKE() mtx.lock(); + #define EMC_SEMAPHORE_GIVE() mtx.unlock(); +#else + #error Target platform not supported +#endif diff --git a/lib/espMqttClient/src/Logging.h b/lib/espMqttClient/src/Logging.h new file mode 100644 index 000000000..3ba096a8d --- /dev/null +++ b/lib/espMqttClient/src/Logging.h @@ -0,0 +1,43 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#if defined(ARDUINO_ARCH_ESP32) + #include + #include "freertos/FreeRTOS.h" + #include "freertos/task.h" + #if defined(DEBUG_ESP_MQTT_CLIENT) + // Logging is en/disabled by Arduino framework macros + #define emc_log_i(...) log_i(__VA_ARGS__) + #define emc_log_e(...) log_e(__VA_ARGS__) + #define emc_log_w(...) log_w(__VA_ARGS__) + #else + // Logging is disabled + #define emc_log_i(...) + #define emc_log_e(...) + #define emc_log_w(...) + #endif +#elif defined(ARDUINO_ARCH_ESP8266) + #if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MQTT_CLIENT) + #include + #define emc_log_i(...) DEBUG_ESP_PORT.printf(__VA_ARGS__); DEBUG_ESP_PORT.print("\n") + #define emc_log_e(...) DEBUG_ESP_PORT.printf(__VA_ARGS__); DEBUG_ESP_PORT.print("\n") + #define emc_log_w(...) DEBUG_ESP_PORT.printf(__VA_ARGS__); DEBUG_ESP_PORT.print("\n") + #else + #define emc_log_i(...) + #define emc_log_e(...) + #define emc_log_w(...) + #endif +#else + // when building for PC, always show debug statements as part of testing suite + #include + #define emc_log_i(...) std::cout << "[I] " << __FILE__ ":" << __LINE__ << ": "; printf(__VA_ARGS__); std::cout << std::endl + #define emc_log_e(...) std::cout << "[E] " << __FILE__ ":" << __LINE__ << ": "; printf(__VA_ARGS__); std::cout << std::endl + #define emc_log_w(...) std::cout << "[W] " << __FILE__ ":" << __LINE__ << ": "; printf(__VA_ARGS__); std::cout << std::endl +#endif diff --git a/lib/espMqttClient/src/MqttClient.cpp b/lib/espMqttClient/src/MqttClient.cpp new file mode 100644 index 000000000..314a3c385 --- /dev/null +++ b/lib/espMqttClient/src/MqttClient.cpp @@ -0,0 +1,754 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#include "MqttClient.h" + +using espMqttClientInternals::Packet; +using espMqttClientInternals::PacketType; +using espMqttClientTypes::DisconnectReason; +using espMqttClientTypes::Error; + +MqttClient::MqttClient(espMqttClientTypes::UseInternalTask useInternalTask, uint8_t priority, uint8_t core) +#if defined(ARDUINO_ARCH_ESP32) + : _useInternalTask(useInternalTask) + , _transport(nullptr) +#else + : _transport(nullptr) +#endif + , _onConnectCallback(nullptr) + , _onDisconnectCallback(nullptr) + , _onSubscribeCallback(nullptr) + , _onUnsubscribeCallback(nullptr) + , _onMessageCallback(nullptr) + , _onPublishCallback(nullptr) + , _onErrorCallback(nullptr) + , _clientId(nullptr) + , _ip() + , _host(nullptr) + , _port(1883) + , _useIp(false) + , _keepAlive(15000) + , _cleanSession(true) + , _username(nullptr) + , _password(nullptr) + , _willTopic(nullptr) + , _willPayload(nullptr) + , _willPayloadLength(0) + , _willQos(0) + , _willRetain(false) + , _timeout(EMC_TX_TIMEOUT) + , _state(State::disconnected) + , _generatedClientId{0} + , _packetId(0) +#if defined(ARDUINO_ARCH_ESP32) + , _xSemaphore(nullptr) + , _taskHandle(nullptr) +#endif + , _rxBuffer{0} + , _outbox() + , _bytesSent(0) + , _parser() + , _lastClientActivity(0) + , _lastServerActivity(0) + , _pingSent(false) + , _disconnectReason(DisconnectReason::TCP_DISCONNECTED) +#if defined(ARDUINO_ARCH_ESP32) && ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO + , _highWaterMark(4294967295) +#endif +{ + EMC_GENERATE_CLIENTID(_generatedClientId); +#if defined(ARDUINO_ARCH_ESP32) + _xSemaphore = xSemaphoreCreateMutex(); + EMC_SEMAPHORE_GIVE(); // release before first use + if (_useInternalTask == espMqttClientTypes::UseInternalTask::YES) { + xTaskCreatePinnedToCore((TaskFunction_t)_loop, "mqttclient", EMC_TASK_STACK_SIZE, this, priority, &_taskHandle, core); + } +#else + (void)useInternalTask; + (void)priority; + (void)core; +#endif + _clientId = _generatedClientId; +} + +MqttClient::~MqttClient() { + disconnect(true); + _clearQueue(2); +#if defined(ARDUINO_ARCH_ESP32) + vSemaphoreDelete(_xSemaphore); + if (_useInternalTask == espMqttClientTypes::UseInternalTask::YES) { +#if EMC_USE_WATCHDOG + esp_task_wdt_delete(_taskHandle); // not sure if this is really needed +#endif + vTaskDelete(_taskHandle); + } +#endif +} + +bool MqttClient::connected() const { + if (_state == State::connected) + return true; + return false; +} + +bool MqttClient::disconnected() const { + if (_state == State::disconnected) + return true; + return false; +} + +bool MqttClient::connect() { + bool result = true; + if (_state == State::disconnected) { + EMC_SEMAPHORE_TAKE(); + if (_addPacketFront(_cleanSession, + _username, + _password, + _willTopic, + _willRetain, + _willQos, + _willPayload, + _willPayloadLength, + (uint16_t)(_keepAlive / 1000), // 32b to 16b doesn't overflow because it comes from 16b orignally + _clientId)) { +#if defined(ARDUINO_ARCH_ESP32) + if (_useInternalTask == espMqttClientTypes::UseInternalTask::YES) { + vTaskResume(_taskHandle); + } +#endif + _state = State::connectingTcp1; + } else { + EMC_SEMAPHORE_GIVE(); + emc_log_e("Could not create CONNECT packet"); + _onError(0, Error::OUT_OF_MEMORY); + result = false; + } + EMC_SEMAPHORE_GIVE(); + } + return result; +} + +bool MqttClient::disconnect(bool force) { + if (force && _state != State::disconnected && _state != State::disconnectingTcp1 && _state != State::disconnectingTcp2) { + _state = State::disconnectingTcp1; + return true; + } + if (!force && _state == State::connected) { + _state = State::disconnectingMqtt1; + return true; + } + return false; +} + +uint16_t MqttClient::publish(const char * topic, uint8_t qos, bool retain, const uint8_t * payload, size_t length) { +#if !EMC_ALLOW_NOT_CONNECTED_PUBLISH + if (_state != State::connected) { +#else + if (_state > State::connected) { +#endif + return 0; + } + uint16_t packetId = (qos > 0) ? _getNextPacketId() : 1; + EMC_SEMAPHORE_TAKE(); + if (!_addPacket(packetId, topic, payload, length, qos, retain)) { + emc_log_e("Could not create PUBLISH packet"); + _onError(packetId, Error::OUT_OF_MEMORY); + packetId = 0; + } + EMC_SEMAPHORE_GIVE(); + return packetId; +} + +uint16_t MqttClient::publish(const char * topic, uint8_t qos, bool retain, const char * payload) { + size_t len = strlen(payload); + return publish(topic, qos, retain, reinterpret_cast(payload), len); +} + +uint16_t MqttClient::publish(const char * topic, uint8_t qos, bool retain, espMqttClientTypes::PayloadCallback callback, size_t length) { +#if !EMC_ALLOW_NOT_CONNECTED_PUBLISH + if (_state != State::connected) { +#else + if (_state > State::connected) { +#endif + return 0; + } + uint16_t packetId = (qos > 0) ? _getNextPacketId() : 1; + EMC_SEMAPHORE_TAKE(); + if (!_addPacket(packetId, topic, callback, length, qos, retain)) { + emc_log_e("Could not create PUBLISH packet"); + _onError(packetId, Error::OUT_OF_MEMORY); + packetId = 0; + } + EMC_SEMAPHORE_GIVE(); + return packetId; +} + +void MqttClient::clearQueue(bool deleteSessionData) { + _clearQueue(deleteSessionData ? 2 : 0); +} + +const char * MqttClient::getClientId() const { + return _clientId; +} + +void MqttClient::loop() { + switch (_state) { + case State::disconnected: +#if defined(ARDUINO_ARCH_ESP32) + if (_useInternalTask == espMqttClientTypes::UseInternalTask::YES) { + vTaskSuspend(_taskHandle); + } +#endif + break; + case State::connectingTcp1: + if (_useIp ? _transport->connect(_ip, _port) : _transport->connect(_host, _port)) { + _state = State::connectingTcp2; + } else { + _state = State::disconnectingTcp1; + _disconnectReason = DisconnectReason::TCP_DISCONNECTED; + break; + } + // Falling through to speed up connecting on blocking transport 'connect' implementations + [[fallthrough]]; + case State::connectingTcp2: + if (_transport->connected()) { + _parser.reset(); + _lastClientActivity = _lastServerActivity = millis(); + _state = State::connectingMqtt; + } + break; + case State::connectingMqtt: +#if EMC_WAIT_FOR_CONNACK + if (_transport->connected()) { + _sendPacket(); + _checkIncoming(); + _checkPing(); + } else { + _state = State::disconnectingTcp1; + _disconnectReason = DisconnectReason::TCP_DISCONNECTED; + } + break; +#else + // receipt of CONNACK packet will set state to CONNECTED + // client however is allowed to send packets before CONNACK is received + // so we fall through to 'connected' + [[fallthrough]]; +#endif + case State::connected: + [[fallthrough]]; + case State::disconnectingMqtt2: + if (_transport->connected()) { + // CONNECT packet is first in the queue + _checkOutbox(); + _checkIncoming(); + _checkPing(); + _checkTimeout(); + } else { + _state = State::disconnectingTcp1; + _disconnectReason = DisconnectReason::TCP_DISCONNECTED; + } + break; + case State::disconnectingMqtt1: + EMC_SEMAPHORE_TAKE(); + if (_outbox.empty()) { + if (!_addPacket(PacketType.DISCONNECT)) { + EMC_SEMAPHORE_GIVE(); + emc_log_e("Could not create DISCONNECT packet"); + _onError(0, Error::OUT_OF_MEMORY); + } else { + _state = State::disconnectingMqtt2; + } + } + EMC_SEMAPHORE_GIVE(); + _checkOutbox(); + _checkIncoming(); + _checkPing(); + _checkTimeout(); + break; + case State::disconnectingTcp1: + _transport->stop(); + _state = State::disconnectingTcp2; + break; // keep break to accomodate async clients + case State::disconnectingTcp2: + if (_transport->disconnected()) { + _clearQueue(0); + _bytesSent = 0; + _state = State::disconnected; + if (_onDisconnectCallback) + _onDisconnectCallback(_disconnectReason); + } + break; + // all cases covered, no default case + } + EMC_YIELD(); +#if defined(ARDUINO_ARCH_ESP32) && ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO + size_t waterMark = uxTaskGetStackHighWaterMark(NULL); + if (waterMark < _highWaterMark) { + _highWaterMark = waterMark; + emc_log_i("Stack usage: %zu/%i", EMC_TASK_STACK_SIZE - _highWaterMark, EMC_TASK_STACK_SIZE); + } +#endif +} + +#if defined(ARDUINO_ARCH_ESP32) +void MqttClient::_loop(MqttClient * c) { +#if EMC_USE_WATCHDOG + if (esp_task_wdt_add(NULL) != ESP_OK) { + emc_log_e("Failed to add async task to WDT"); + } +#endif + for (;;) { + c->loop(); +#if EMC_USE_WATCHDOG + esp_task_wdt_reset(); +#endif + } +} +#endif + +uint16_t MqttClient::_getNextPacketId() { + uint16_t packetId = 0; + EMC_SEMAPHORE_TAKE(); + // cppcheck-suppress knownConditionTrueFalse + packetId = (++_packetId == 0) ? ++_packetId : _packetId; + EMC_SEMAPHORE_GIVE(); + return packetId; +} + +void MqttClient::_checkOutbox() { + while (_sendPacket() > 0) { + if (!_advanceOutbox()) { + break; + } + } +} + +int MqttClient::_sendPacket() { + EMC_SEMAPHORE_TAKE(); + OutgoingPacket * packet = _outbox.getCurrent(); + + int32_t wantToWrite = 0; + int32_t written = 0; + if (packet && (wantToWrite == written)) { + // mixing signed with unsigned here but safe because of MQTT packet size limits + wantToWrite = packet->packet.available(_bytesSent); + if (wantToWrite == 0) { + EMC_SEMAPHORE_GIVE(); + return 0; + } + written = _transport->write(packet->packet.data(_bytesSent), wantToWrite); + if (written < 0) { + emc_log_w("Write error, check connection"); + EMC_SEMAPHORE_GIVE(); + return -1; + } + packet->timeSent = millis(); + _lastClientActivity = millis(); + _bytesSent += written; + emc_log_i("tx %zu/%zu (%02x)", _bytesSent, packet->packet.size(), packet->packet.packetType()); + } + EMC_SEMAPHORE_GIVE(); + return written; +} + +bool MqttClient::_advanceOutbox() { + EMC_SEMAPHORE_TAKE(); + OutgoingPacket * packet = _outbox.getCurrent(); + if (packet && _bytesSent == packet->packet.size()) { + if ((packet->packet.packetType()) == PacketType.DISCONNECT) { + _state = State::disconnectingTcp1; + _disconnectReason = DisconnectReason::USER_OK; + } + if (packet->packet.removable()) { + _outbox.removeCurrent(); + } else { + // we already set 'dup' here, in case we have to retry + if ((packet->packet.packetType()) == PacketType.PUBLISH) + packet->packet.setDup(); + _outbox.next(); + } + packet = _outbox.getCurrent(); + _bytesSent = 0; + } + EMC_SEMAPHORE_GIVE(); + return packet; +} + +void MqttClient::_checkIncoming() { + int32_t remainingBufferLength = _transport->read(_rxBuffer, EMC_RX_BUFFER_SIZE); + if (remainingBufferLength > 0) { + _lastServerActivity = millis(); + emc_log_i("rx len %i", remainingBufferLength); + size_t bytesParsed = 0; + size_t index = 0; + while (remainingBufferLength > 0) { + espMqttClientInternals::ParserResult result = _parser.parse(&_rxBuffer[index], remainingBufferLength, &bytesParsed); + if (result == espMqttClientInternals::ParserResult::packet) { + espMqttClientInternals::MQTTPacketType packetType = _parser.getPacket().fixedHeader.packetType & 0xF0; + if (_state == State::connectingMqtt && packetType != PacketType.CONNACK) { + emc_log_w("Disconnecting, expected CONNACK - protocol error"); + _state = State::disconnectingTcp1; + return; + } + switch (packetType & 0xF0) { + case PacketType.CONNACK: + _onConnack(); + if (_state != State::connected) { + return; + } + break; + case PacketType.PUBLISH: + if (_state >= State::disconnectingMqtt1) + break; // stop processing incoming once user has called disconnect + _onPublish(); + break; + case PacketType.PUBACK: + _onPuback(); + break; + case PacketType.PUBREC: + _onPubrec(); + break; + case PacketType.PUBREL: + _onPubrel(); + break; + case PacketType.PUBCOMP: + _onPubcomp(); + break; + case PacketType.SUBACK: + _onSuback(); + break; + case PacketType.UNSUBACK: + _onUnsuback(); + break; + case PacketType.PINGRESP: + _pingSent = false; + break; + } + } else if (result == espMqttClientInternals::ParserResult::protocolError) { + emc_log_w("Disconnecting, protocol error"); + _state = State::disconnectingTcp1; + _disconnectReason = DisconnectReason::TCP_DISCONNECTED; + return; + } + remainingBufferLength -= bytesParsed; + index += bytesParsed; + emc_log_i("Parsed %zu - remaining %i", bytesParsed, remainingBufferLength); + bytesParsed = 0; + } + } +} + +void MqttClient::_checkPing() { + if (_keepAlive == 0) + return; // keepalive is disabled + + uint32_t currentMillis = millis(); + + // disconnect when server was inactive for twice the keepalive time + if (currentMillis - _lastServerActivity > 2 * _keepAlive) { + emc_log_w("Disconnecting, server exceeded keepalive"); + _state = State::disconnectingTcp1; + _disconnectReason = DisconnectReason::TCP_DISCONNECTED; + return; + } + + // send ping when client was inactive during the keepalive time + // or when server hasn't responded within keepalive time (typically due to QOS 0) + if (!_pingSent && ((currentMillis - _lastClientActivity > _keepAlive) || (currentMillis - _lastServerActivity > _keepAlive))) { + EMC_SEMAPHORE_TAKE(); + if (!_addPacket(PacketType.PINGREQ)) { + EMC_SEMAPHORE_GIVE(); + emc_log_e("Could not create PING packet"); + return; + } + EMC_SEMAPHORE_GIVE(); + _pingSent = true; + } +} + +void MqttClient::_checkTimeout() { + EMC_SEMAPHORE_TAKE(); + espMqttClientInternals::Outbox::Iterator it = _outbox.front(); + // check that we're not busy sending + // don't check when first item hasn't been sent yet + if (it && _bytesSent == 0 && it.get() != _outbox.getCurrent()) { + if (millis() - it.get()->timeSent > _timeout) { + emc_log_w("Packet ack timeout, retrying"); + _outbox.resetCurrent(); + } + } + EMC_SEMAPHORE_GIVE(); +} + +void MqttClient::_onConnack() { + if (_parser.getPacket().variableHeader.fixed.connackVarHeader.returnCode == 0x00) { + _pingSent = false; // reset after keepalive timeout disconnect + _state = State::connected; + _advanceOutbox(); + if (_parser.getPacket().variableHeader.fixed.connackVarHeader.sessionPresent == 0) { + _clearQueue(1); + } + if (_onConnectCallback) { + _onConnectCallback(_parser.getPacket().variableHeader.fixed.connackVarHeader.sessionPresent); + } + } else { + _state = State::disconnectingTcp1; + // cast is safe because the parser already checked for a valid return code + _disconnectReason = static_cast(_parser.getPacket().variableHeader.fixed.connackVarHeader.returnCode); + } +} + +void MqttClient::_onPublish() { + espMqttClientInternals::IncomingPacket p = _parser.getPacket(); + uint8_t qos = p.qos(); + bool retain = p.retain(); + bool dup = p.dup(); + uint16_t packetId = p.variableHeader.fixed.packetId; + bool callback = true; + if (qos == 1) { + if (p.payload.index + p.payload.length == p.payload.total) { + EMC_SEMAPHORE_TAKE(); + if (!_addPacket(PacketType.PUBACK, packetId)) { + emc_log_e("Could not create PUBACK packet"); + } + EMC_SEMAPHORE_GIVE(); + } + } else if (qos == 2) { + EMC_SEMAPHORE_TAKE(); + espMqttClientInternals::Outbox::Iterator it = _outbox.front(); + while (it) { + if ((it.get()->packet.packetType()) == PacketType.PUBREC && it.get()->packet.packetId() == packetId) { + callback = false; + _outbox.remove(it); + emc_log_e("QoS2 packet previously delivered"); + break; + } + ++it; + } + if (p.payload.index + p.payload.length == p.payload.total) { + if (!_addPacket(PacketType.PUBREC, packetId)) { + emc_log_e("Could not create PUBREC packet"); + } + } + EMC_SEMAPHORE_GIVE(); + } + if (callback && _onMessageCallback) + _onMessageCallback({qos, dup, retain, packetId}, p.variableHeader.topic, p.payload.data, p.payload.length, p.payload.index, p.payload.total); +} + +void MqttClient::_onPuback() { + bool callback = false; + uint16_t idToMatch = _parser.getPacket().variableHeader.fixed.packetId; + EMC_SEMAPHORE_TAKE(); + espMqttClientInternals::Outbox::Iterator it = _outbox.front(); + while (it) { + // PUBACKs come in the order PUBs are sent. So we only check the first PUB packet in outbox + // if it doesn't match the ID, return + if ((it.get()->packet.packetType()) == PacketType.PUBLISH) { + if (it.get()->packet.packetId() == idToMatch) { + callback = true; + _outbox.remove(it); + break; + } + emc_log_w("Received out of order PUBACK"); + break; + } + ++it; + } + EMC_SEMAPHORE_GIVE(); + if (callback) { + if (_onPublishCallback) + _onPublishCallback(idToMatch); + } else { + emc_log_w("No matching PUBLISH packet found"); + } +} + +void MqttClient::_onPubrec() { + bool success = false; + uint16_t idToMatch = _parser.getPacket().variableHeader.fixed.packetId; + EMC_SEMAPHORE_TAKE(); + espMqttClientInternals::Outbox::Iterator it = _outbox.front(); + while (it) { + // PUBRECs come in the order PUBs are sent. So we only check the first PUB packet in outbox + // if it doesn't match the ID, return + if ((it.get()->packet.packetType()) == PacketType.PUBLISH) { + if (it.get()->packet.packetId() == idToMatch) { + if (!_addPacket(PacketType.PUBREL, idToMatch)) { + emc_log_e("Could not create PUBREL packet"); + } + _outbox.remove(it); + success = true; + break; + } + emc_log_w("Received out of order PUBREC"); + break; + } + ++it; + } + if (!success) { + emc_log_w("No matching PUBLISH packet found"); + } + EMC_SEMAPHORE_GIVE(); +} + +void MqttClient::_onPubrel() { + bool success = false; + uint16_t idToMatch = _parser.getPacket().variableHeader.fixed.packetId; + EMC_SEMAPHORE_TAKE(); + espMqttClientInternals::Outbox::Iterator it = _outbox.front(); + while (it) { + // PUBRELs come in the order PUBRECs are sent. So we only check the first PUBREC packet in outbox + // if it doesn't match the ID, return + if ((it.get()->packet.packetType()) == PacketType.PUBREC) { + if (it.get()->packet.packetId() == idToMatch) { + if (!_addPacket(PacketType.PUBCOMP, idToMatch)) { + emc_log_e("Could not create PUBCOMP packet"); + } + _outbox.remove(it); + success = true; + break; + } + emc_log_w("Received out of order PUBREL"); + break; + } + ++it; + } + if (!success) { + emc_log_w("No matching PUBREC packet found"); + } + EMC_SEMAPHORE_GIVE(); +} + +void MqttClient::_onPubcomp() { + bool callback = false; + EMC_SEMAPHORE_TAKE(); + espMqttClientInternals::Outbox::Iterator it = _outbox.front(); + uint16_t idToMatch = _parser.getPacket().variableHeader.fixed.packetId; + while (it) { + // PUBCOMPs come in the order PUBRELs are sent. So we only check the first PUBREL packet in outbox + // if it doesn't match the ID, return + if ((it.get()->packet.packetType()) == PacketType.PUBREL) { + if (it.get()->packet.packetId() == idToMatch) { + // if (!_addPacket(PacketType.PUBCOMP, idToMatch)) { + // emc_log_e("Could not create PUBCOMP packet"); + // } + callback = true; + _outbox.remove(it); + break; + } + emc_log_w("Received out of order PUBCOMP"); + break; + } + ++it; + } + EMC_SEMAPHORE_GIVE(); + if (callback) { + if (_onPublishCallback) + _onPublishCallback(idToMatch); + } else { + emc_log_w("No matching PUBREL packet found"); + } +} + +void MqttClient::_onSuback() { + bool callback = false; + uint16_t idToMatch = _parser.getPacket().variableHeader.fixed.packetId; + EMC_SEMAPHORE_TAKE(); + espMqttClientInternals::Outbox::Iterator it = _outbox.front(); + while (it) { + if (((it.get()->packet.packetType()) == PacketType.SUBSCRIBE) && it.get()->packet.packetId() == idToMatch) { + callback = true; + _outbox.remove(it); + break; + } + ++it; + } + EMC_SEMAPHORE_GIVE(); + if (callback) { + if (_onSubscribeCallback) + _onSubscribeCallback(idToMatch, + reinterpret_cast(_parser.getPacket().payload.data), + _parser.getPacket().payload.total); + } else { + emc_log_w("received SUBACK without SUB"); + } +} + +void MqttClient::_onUnsuback() { + bool callback = false; + EMC_SEMAPHORE_TAKE(); + espMqttClientInternals::Outbox::Iterator it = _outbox.front(); + uint16_t idToMatch = _parser.getPacket().variableHeader.fixed.packetId; + while (it) { + if (it.get()->packet.packetId() == idToMatch) { + callback = true; + _outbox.remove(it); + break; + } + ++it; + } + EMC_SEMAPHORE_GIVE(); + if (callback) { + if (_onUnsubscribeCallback) + _onUnsubscribeCallback(idToMatch); + } else { + emc_log_w("received UNSUBACK without UNSUB"); + } +} + +uint16_t MqttClient::getQueue() const { + EMC_SEMAPHORE_TAKE(); + espMqttClientInternals::Outbox::Iterator it = _outbox.front(); + uint16_t count = 0; + while (it) { + ++count; + ++it; + } + EMC_SEMAPHORE_GIVE(); + return count; +} + +void MqttClient::_clearQueue(int clearData) { + emc_log_i("clearing queue (clear session: %d)", clearData); + EMC_SEMAPHORE_TAKE(); + espMqttClientInternals::Outbox::Iterator it = _outbox.front(); + if (clearData == 0) { + // keep PUB (qos > 0, aka packetID != 0), PUBREC and PUBREL + // Spec only mentions PUB and PUBREL but this lib implements method B from point 4.3.3 (Fig. 4.3) + // and stores the packet id in the PUBREC packet. So we also must keep PUBREC. + while (it) { + espMqttClientInternals::MQTTPacketType type = it.get()->packet.packetType(); + if (type == PacketType.PUBREC || type == PacketType.PUBREL || (type == PacketType.PUBLISH && it.get()->packet.packetId() != 0)) { + ++it; + } else { + _outbox.remove(it); + } + } + } else if (clearData == 1) { + // keep PUB + while (it) { + if (it.get()->packet.packetType() == PacketType.PUBLISH) { + ++it; + } else { + _outbox.remove(it); + } + } + } else { // clearData == 2 + while (it) { + _outbox.remove(it); + } + } + EMC_SEMAPHORE_GIVE(); +} + +void MqttClient::_onError(uint16_t packetId, espMqttClientTypes::Error error) { + if (_onErrorCallback) { + _onErrorCallback(packetId, error); + } +} diff --git a/lib/espMqttClient/src/MqttClient.h b/lib/espMqttClient/src/MqttClient.h new file mode 100644 index 000000000..6d36e0af5 --- /dev/null +++ b/lib/espMqttClient/src/MqttClient.h @@ -0,0 +1,190 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +API is based on the original work of Marvin Roger: +https://github.com/marvinroger/async-mqtt-client + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#include +#include + +#include "Helpers.h" +#include "Config.h" +#include "TypeDefs.h" +#include "Logging.h" +#include "Outbox.h" +#include "Packets/Packet.h" +#include "Packets/Parser.h" +#include "Transport/Transport.h" + +class MqttClient { + public: + virtual ~MqttClient(); + bool connected() const; + bool disconnected() const; + bool connect(); + bool disconnect(bool force = false); + template + uint16_t subscribe(const char* topic, uint8_t qos, Args&&... args) { + uint16_t packetId = _getNextPacketId(); + if (_state != State::connected) { + packetId = 0; + } else { + EMC_SEMAPHORE_TAKE(); + if (!_addPacket(packetId, topic, qos, std::forward(args) ...)) { + emc_log_e("Could not create SUBSCRIBE packet"); + packetId = 0; + } + EMC_SEMAPHORE_GIVE(); + } + return packetId; + } + template + uint16_t unsubscribe(const char* topic, Args&&... args) { + uint16_t packetId = _getNextPacketId(); + if (_state != State::connected) { + packetId = 0; + } else { + EMC_SEMAPHORE_TAKE(); + if (!_addPacket(packetId, topic, std::forward(args) ...)) { + emc_log_e("Could not create UNSUBSCRIBE packet"); + packetId = 0; + } + EMC_SEMAPHORE_GIVE(); + } + return packetId; + } + uint16_t publish(const char* topic, uint8_t qos, bool retain, const uint8_t* payload, size_t length); + uint16_t publish(const char* topic, uint8_t qos, bool retain, const char* payload); + uint16_t publish(const char* topic, uint8_t qos, bool retain, espMqttClientTypes::PayloadCallback callback, size_t length); + void clearQueue(bool deleteSessionData = false); // Not MQTT compliant and may cause unpredictable results when `deleteSessionData` = true! + const char* getClientId() const; + uint16_t getQueue() const; + void loop(); + + protected: + explicit MqttClient(espMqttClientTypes::UseInternalTask useInternalTask, uint8_t priority = 1, uint8_t core = 1); + espMqttClientTypes::UseInternalTask _useInternalTask; + espMqttClientInternals::Transport* _transport; + + espMqttClientTypes::OnConnectCallback _onConnectCallback; + espMqttClientTypes::OnDisconnectCallback _onDisconnectCallback; + espMqttClientTypes::OnSubscribeCallback _onSubscribeCallback; + espMqttClientTypes::OnUnsubscribeCallback _onUnsubscribeCallback; + espMqttClientTypes::OnMessageCallback _onMessageCallback; + espMqttClientTypes::OnPublishCallback _onPublishCallback; + espMqttClientTypes::OnErrorCallback _onErrorCallback; + typedef void(*mqttClientHook)(void*); + const char* _clientId; + IPAddress _ip; + const char* _host; + uint16_t _port; + bool _useIp; + uint32_t _keepAlive; + bool _cleanSession; + const char* _username; + const char* _password; + const char* _willTopic; + const uint8_t* _willPayload; + uint16_t _willPayloadLength; + uint8_t _willQos; + bool _willRetain; + uint32_t _timeout; + + // state is protected to allow state changes by the transport system, defined in child classes + // eg. to allow AsyncTCP + enum class State { + disconnected = 0, + connectingTcp1 = 1, + connectingTcp2 = 2, + connectingMqtt = 3, + connected = 4, + disconnectingMqtt1 = 5, + disconnectingMqtt2 = 6, + disconnectingTcp1 = 7, + disconnectingTcp2 = 8 + }; + std::atomic _state; + + private: + char _generatedClientId[EMC_CLIENTID_LENGTH]; + uint16_t _packetId; + +#if defined(ARDUINO_ARCH_ESP32) + SemaphoreHandle_t _xSemaphore; + TaskHandle_t _taskHandle; + static void _loop(MqttClient* c); +#elif defined(ARDUINO_ARCH_ESP8266) && EMC_ESP8266_MULTITHREADING + std::atomic _xSemaphore = false; +#elif defined(__linux__) + std::mutex mtx; +#endif + + uint8_t _rxBuffer[EMC_RX_BUFFER_SIZE]; + struct OutgoingPacket { + uint32_t timeSent; + espMqttClientInternals::Packet packet; + template + OutgoingPacket(uint32_t t, espMqttClientTypes::Error error, Args&&... args) : + timeSent(t), + packet(error, std::forward(args) ...) {} + }; + espMqttClientInternals::Outbox _outbox; + size_t _bytesSent; + espMqttClientInternals::Parser _parser; + uint32_t _lastClientActivity; + uint32_t _lastServerActivity; + bool _pingSent; + espMqttClientTypes::DisconnectReason _disconnectReason; + + uint16_t _getNextPacketId(); + + template + bool _addPacket(Args&&... args) { + espMqttClientTypes::Error error(espMqttClientTypes::Error::SUCCESS); + espMqttClientInternals::Outbox::Iterator it = _outbox.emplace(0, error, std::forward(args) ...); + if (it && error == espMqttClientTypes::Error::SUCCESS) return true; + return false; + } + + template + bool _addPacketFront(Args&&... args) { + espMqttClientTypes::Error error(espMqttClientTypes::Error::SUCCESS); + espMqttClientInternals::Outbox::Iterator it = _outbox.emplaceFront(0, error, std::forward(args) ...); + if (it && error == espMqttClientTypes::Error::SUCCESS) return true; + return false; + } + + void _checkOutbox(); + int _sendPacket(); + bool _advanceOutbox(); + void _checkIncoming(); + void _checkPing(); + void _checkTimeout(); + + void _onConnack(); + void _onPublish(); + void _onPuback(); + void _onPubrec(); + void _onPubrel(); + void _onPubcomp(); + void _onSuback(); + void _onUnsuback(); + + void _clearQueue(int clearData); // 0: keep session, + // 1: keep only PUBLISH qos > 0 + // 2: delete all + void _onError(uint16_t packetId, espMqttClientTypes::Error error); + + #if defined(ARDUINO_ARCH_ESP32) + #if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO + size_t _highWaterMark; + #endif + #endif +}; diff --git a/lib/espMqttClient/src/MqttClientSetup.h b/lib/espMqttClient/src/MqttClientSetup.h new file mode 100644 index 000000000..73458d477 --- /dev/null +++ b/lib/espMqttClient/src/MqttClientSetup.h @@ -0,0 +1,116 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +API is based on the original work of Marvin Roger: +https://github.com/marvinroger/async-mqtt-client + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#include "MqttClient.h" + +template +class MqttClientSetup : public MqttClient { + public: + T& setKeepAlive(uint16_t keepAlive) { + _keepAlive = keepAlive * 1000; // s to ms conversion, will also do 16 to 32 bit conversion + return static_cast(*this); + } + + T& setClientId(const char* clientId) { + _clientId = clientId; + return static_cast(*this); + } + + T& setCleanSession(bool cleanSession) { + _cleanSession = cleanSession; + return static_cast(*this); + } + + T& setCredentials(const char* username, const char* password) { + _username = username; + _password = password; + return static_cast(*this); + } + + T& setWill(const char* topic, uint8_t qos, bool retain, const uint8_t* payload, size_t length) { + _willTopic = topic; + _willQos = qos; + _willRetain = retain; + _willPayload = payload; + if (!_willPayload) { + _willPayloadLength = 0; + } else { + _willPayloadLength = length; + } + return static_cast(*this); + } + + T& setWill(const char* topic, uint8_t qos, bool retain, const char* payload) { + return setWill(topic, qos, retain, reinterpret_cast(payload), strlen(payload)); + } + + T& setServer(IPAddress ip, uint16_t port) { + _ip = ip; + _port = port; + _useIp = true; + return static_cast(*this); + } + + T& setServer(const char* host, uint16_t port) { + _host = host; + _port = port; + _useIp = false; + return static_cast(*this); + } + + T& setTimeout(uint16_t timeout) { + _timeout = timeout * 1000; // s to ms conversion, will also do 16 to 32 bit conversion + return static_cast(*this); + } + + T& onConnect(espMqttClientTypes::OnConnectCallback callback) { + _onConnectCallback = callback; + return static_cast(*this); + } + + T& onDisconnect(espMqttClientTypes::OnDisconnectCallback callback) { + _onDisconnectCallback = callback; + return static_cast(*this); + } + + T& onSubscribe(espMqttClientTypes::OnSubscribeCallback callback) { + _onSubscribeCallback = callback; + return static_cast(*this); + } + + T& onUnsubscribe(espMqttClientTypes::OnUnsubscribeCallback callback) { + _onUnsubscribeCallback = callback; + return static_cast(*this); + } + + T& onMessage(espMqttClientTypes::OnMessageCallback callback) { + _onMessageCallback = callback; + return static_cast(*this); + } + + T& onPublish(espMqttClientTypes::OnPublishCallback callback) { + _onPublishCallback = callback; + return static_cast(*this); + } + + /* + T& onError(espMqttClientTypes::OnErrorCallback callback) { + _onErrorCallback = callback; + return static_cast(*this); + } + */ + + protected: + explicit MqttClientSetup(espMqttClientTypes::UseInternalTask useInternalTask, uint8_t priority = 1, uint8_t core = 1) + : MqttClient(useInternalTask, priority, core) {} +}; diff --git a/lib/espMqttClient/src/Outbox.h b/lib/espMqttClient/src/Outbox.h new file mode 100644 index 000000000..dfbbd13c0 --- /dev/null +++ b/lib/espMqttClient/src/Outbox.h @@ -0,0 +1,207 @@ + +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#include // new (std::nothrow) +#include // std::forward + +namespace espMqttClientInternals { + +/** + * @brief Singly linked queue with builtin non-invalidating forward iterator + * + * Queue items can only be emplaced, at front and back of the queue. + * Remove items using an iterator or the builtin iterator. + */ + +template +class Outbox { + public: + Outbox() + : _first(nullptr) + , _last(nullptr) + , _current(nullptr) + , _prev(nullptr) {} + ~Outbox() { + while (_first) { + Node* n = _first->next; + delete _first; + _first = n; + } + } + + struct Node { + public: + template + explicit Node(Args&&... args) + : data(std::forward(args) ...) + , next(nullptr) { + // empty + } + + T data; + Node* next; + }; + + class Iterator { + friend class Outbox; + public: + void operator++() { + if (_node) { + _prev = _node; + _node = _node->next; + } + } + + explicit operator bool() const { + if (_node) return true; + return false; + } + + T* get() const { + if (_node) return &(_node->data); + return nullptr; + } + + private: + Node* _node = nullptr; + Node* _prev = nullptr; + }; + + // add node to back, advance current to new if applicable + template + Iterator emplace(Args&&... args) { + Iterator it; + Node* node = new (std::nothrow) Node(std::forward(args) ...); + if (node != nullptr) { + if (!_first) { + // queue is empty + _first = _current = node; + } else { + // queue has at least one item + _last->next = node; + it._prev = _last; + } + _last = node; + it._node = node; + // point current to newly created if applicable + if (!_current) { + _current = _last; + } + } + return it; + } + + // add item to front, current points to newly created front. + template + Iterator emplaceFront(Args&&... args) { + Iterator it; + Node* node = new (std::nothrow) Node(std::forward(args) ...); + if (node != nullptr) { + if (!_first) { + // queue is empty + _last = node; + } else { + // queue has at least one item + node->next = _first; + } + _current = _first = node; + _prev = nullptr; + it._node = node; + } + return it; + } + + // remove node at iterator, iterator points to next + void remove(Iterator& it) { // NOLINT(runtime/references) + if (!it) return; + Node* node = it._node; + Node* prev = it._prev; + ++it; + _remove(prev, node); + } + + // remove current node, current points to next + void removeCurrent() { + _remove(_prev, _current); + } + + // Get current item or return nullptr + T* getCurrent() const { + if (_current) return &(_current->data); + return nullptr; + } + + void resetCurrent() { + _current = _first; + } + + Iterator front() const { + Iterator it; + it._node = _first; + return it; + } + + // Advance current item + void next() { + if (_current) { + _prev = _current; + _current = _current->next; + } + } + + // Outbox is empty + bool empty() { + if (!_first) return true; + return false; + } + + private: + Node* _first; + Node* _last; + Node* _current; + Node* _prev; // element just before _current + + void _remove(Node* prev, Node* node) { + if (!node) return; + + // set current to next, node->next may be nullptr + if (_current == node) { + _current = node->next; + } + + if (_prev == node) { + _prev = prev; + } + + // only one element in outbox + if (_first == _last) { + _first = _last = nullptr; + + // delete first el in longer outbox + } else if (_first == node) { + _first = node->next; + + // delete last in longer outbox + } else if (_last == node) { + _last = prev; + _last->next = nullptr; + + // delete somewhere in the middle + } else { + prev->next = node->next; + } + + // finally, delete the node + delete node; + } +}; + +} // end namespace espMqttClientInternals diff --git a/lib/espMqttClient/src/Packets/Constants.h b/lib/espMqttClient/src/Packets/Constants.h new file mode 100644 index 000000000..ee92e3114 --- /dev/null +++ b/lib/espMqttClient/src/Packets/Constants.h @@ -0,0 +1,77 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +Parts are based on the original work of Marvin Roger: +https://github.com/marvinroger/async-mqtt-client + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#include + +namespace espMqttClientInternals { + +constexpr const char PROTOCOL[] = "MQTT"; +constexpr const uint8_t PROTOCOL_LEVEL = 0b00000100; + +typedef uint8_t MQTTPacketType; + +constexpr struct { + const uint8_t RESERVED1 = 0; + const uint8_t CONNECT = 1 << 4; + const uint8_t CONNACK = 2 << 4; + const uint8_t PUBLISH = 3 << 4; + const uint8_t PUBACK = 4 << 4; + const uint8_t PUBREC = 5 << 4; + const uint8_t PUBREL = 6 << 4; + const uint8_t PUBCOMP = 7 << 4; + const uint8_t SUBSCRIBE = 8 << 4; + const uint8_t SUBACK = 9 << 4; + const uint8_t UNSUBSCRIBE = 10 << 4; + const uint8_t UNSUBACK = 11 << 4; + const uint8_t PINGREQ = 12 << 4; + const uint8_t PINGRESP = 13 << 4; + const uint8_t DISCONNECT = 14 << 4; + const uint8_t RESERVED2 = 1 << 4; +} PacketType; + +constexpr struct { + const uint8_t CONNECT_RESERVED = 0x00; + const uint8_t CONNACK_RESERVED = 0x00; + const uint8_t PUBLISH_DUP = 0x08; + const uint8_t PUBLISH_QOS0 = 0x00; + const uint8_t PUBLISH_QOS1 = 0x02; + const uint8_t PUBLISH_QOS2 = 0x04; + const uint8_t PUBLISH_QOSRESERVED = 0x06; + const uint8_t PUBLISH_RETAIN = 0x01; + const uint8_t PUBACK_RESERVED = 0x00; + const uint8_t PUBREC_RESERVED = 0x00; + const uint8_t PUBREL_RESERVED = 0x02; + const uint8_t PUBCOMP_RESERVED = 0x00; + const uint8_t SUBSCRIBE_RESERVED = 0x02; + const uint8_t SUBACK_RESERVED = 0x00; + const uint8_t UNSUBSCRIBE_RESERVED = 0x02; + const uint8_t UNSUBACK_RESERVED = 0x00; + const uint8_t PINGREQ_RESERVED = 0x00; + const uint8_t PINGRESP_RESERVED = 0x00; + const uint8_t DISCONNECT_RESERVED = 0x00; + const uint8_t RESERVED2_RESERVED = 0x00; +} HeaderFlag; + +constexpr struct { + const uint8_t USERNAME = 0x80; + const uint8_t PASSWORD = 0x40; + const uint8_t WILL_RETAIN = 0x20; + const uint8_t WILL_QOS0 = 0x00; + const uint8_t WILL_QOS1 = 0x08; + const uint8_t WILL_QOS2 = 0x10; + const uint8_t WILL = 0x04; + const uint8_t CLEAN_SESSION = 0x02; + const uint8_t RESERVED = 0x00; +} ConnectFlag; + +} // end namespace espMqttClientInternals diff --git a/lib/espMqttClient/src/Packets/Packet.cpp b/lib/espMqttClient/src/Packets/Packet.cpp new file mode 100644 index 000000000..df463ef7b --- /dev/null +++ b/lib/espMqttClient/src/Packets/Packet.cpp @@ -0,0 +1,438 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#include "Packet.h" + +namespace espMqttClientInternals { + +Packet::~Packet() { + free(_data); +} + +size_t Packet::available(size_t index) { + if (index >= _size) return 0; + if (!_getPayload) return _size - index; + return _chunkedAvailable(index); +} + +const uint8_t* Packet::data(size_t index) const { + if (!_getPayload) { + if (!_data) return nullptr; + if (index >= _size) return nullptr; + return &_data[index]; + } + return _chunkedData(index); +} + +size_t Packet::size() const { + return _size; +} + +void Packet::setDup() { + if (!_data) return; + if (packetType() != PacketType.PUBLISH) return; + if (_packetId == 0) return; + _data[0] |= 0x08; +} + +uint16_t Packet::packetId() const { + return _packetId; +} + +MQTTPacketType Packet::packetType() const { + if (_data) return static_cast(_data[0] & 0xF0); + return static_cast(0); +} + +bool Packet::removable() const { + if (_packetId == 0) return true; + if ((packetType() == PacketType.PUBACK) || (packetType() == PacketType.PUBCOMP)) return true; + return false; +} + +Packet::Packet(espMqttClientTypes::Error& error, + bool cleanSession, + const char* username, + const char* password, + const char* willTopic, + bool willRetain, + uint8_t willQos, + const uint8_t* willPayload, + uint16_t willPayloadLength, + uint16_t keepAlive, + const char* clientId) +: _packetId(0) +, _data(nullptr) +, _size(0) +, _payloadIndex(0) +, _payloadStartIndex(0) +, _payloadEndIndex(0) +, _getPayload(nullptr) { + if (willPayload && willPayloadLength == 0) { + size_t length = strlen(reinterpret_cast(willPayload)); + if (length > UINT16_MAX) { + emc_log_w("Payload length truncated (l:%zu)", length); + willPayloadLength = UINT16_MAX; + } else { + willPayloadLength = length; + } + } + if (!clientId || strlen(clientId) == 0) { + emc_log_w("clientId not set error"); + error = espMqttClientTypes::Error::MALFORMED_PARAMETER; + return; + } + + // Calculate size + size_t remainingLength = + 6 + // protocol + 1 + // protocol level + 1 + // connect flags + 2 + // keepalive + 2 + strlen(clientId) + + (willTopic ? 2 + strlen(willTopic) + 2 + willPayloadLength : 0) + + (username ? 2 + strlen(username) : 0) + + (password ? 2 + strlen(password) : 0); + + // allocate memory + if (!_allocate(remainingLength)) { + error = espMqttClientTypes::Error::OUT_OF_MEMORY; + return; + } + + // serialize + size_t pos = 0; + + // FIXED HEADER + _data[pos++] = PacketType.CONNECT | HeaderFlag.CONNECT_RESERVED; + pos += encodeRemainingLength(remainingLength, &_data[pos]); + pos += encodeString(PROTOCOL, &_data[pos]); + _data[pos++] = PROTOCOL_LEVEL; + uint8_t connectFlags = 0; + if (cleanSession) connectFlags |= espMqttClientInternals::ConnectFlag.CLEAN_SESSION; + if (username != nullptr) connectFlags |= espMqttClientInternals::ConnectFlag.USERNAME; + if (password != nullptr) connectFlags |= espMqttClientInternals::ConnectFlag.PASSWORD; + if (willTopic != nullptr) { + connectFlags |= espMqttClientInternals::ConnectFlag.WILL; + if (willRetain) connectFlags |= espMqttClientInternals::ConnectFlag.WILL_RETAIN; + switch (willQos) { + case 0: + connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS0; + break; + case 1: + connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS1; + break; + case 2: + connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS2; + break; + } + } + _data[pos++] = connectFlags; + _data[pos++] = keepAlive >> 8; + _data[pos++] = keepAlive & 0xFF; + + // PAYLOAD + // client ID + pos += encodeString(clientId, &_data[pos]); + // will + if (willTopic != nullptr && willPayload != nullptr) { + pos += encodeString(willTopic, &_data[pos]); + _data[pos++] = willPayloadLength >> 8; + _data[pos++] = willPayloadLength & 0xFF; + memcpy(&_data[pos], willPayload, willPayloadLength); + pos += willPayloadLength; + } + // credentials + if (username != nullptr) pos += encodeString(username, &_data[pos]); + if (password != nullptr) encodeString(password, &_data[pos]); + + error = espMqttClientTypes::Error::SUCCESS; +} + +Packet::Packet(espMqttClientTypes::Error& error, + uint16_t packetId, + const char* topic, + const uint8_t* payload, + size_t payloadLength, + uint8_t qos, + bool retain) +: _packetId(packetId) +, _data(nullptr) +, _size(0) +, _payloadIndex(0) +, _payloadStartIndex(0) +, _payloadEndIndex(0) +, _getPayload(nullptr) { + size_t remainingLength = + 2 + strlen(topic) + // topic length + topic + 2 + // packet ID + payloadLength; + + if (qos == 0) { + remainingLength -= 2; + _packetId = 0; + } + + if (!_allocate(remainingLength)) { + error = espMqttClientTypes::Error::OUT_OF_MEMORY; + return; + } + + size_t pos = _fillPublishHeader(packetId, topic, remainingLength, qos, retain); + + // PAYLOAD + memcpy(&_data[pos], payload, payloadLength); + + error = espMqttClientTypes::Error::SUCCESS; +} + +Packet::Packet(espMqttClientTypes::Error& error, + uint16_t packetId, + const char* topic, + espMqttClientTypes::PayloadCallback payloadCallback, + size_t payloadLength, + uint8_t qos, + bool retain) +: _packetId(packetId) +, _data(nullptr) +, _size(0) +, _payloadIndex(0) +, _payloadStartIndex(0) +, _payloadEndIndex(0) +, _getPayload(payloadCallback) { + size_t remainingLength = + 2 + strlen(topic) + // topic length + topic + 2 + // packet ID + payloadLength; + + if (qos == 0) { + remainingLength -= 2; + _packetId = 0; + } + + if (!_allocate(remainingLength - payloadLength + std::min(payloadLength, static_cast(EMC_RX_BUFFER_SIZE)))) { + error = espMqttClientTypes::Error::OUT_OF_MEMORY; + return; + } + + size_t pos = _fillPublishHeader(packetId, topic, remainingLength, qos, retain); + + // payload will be added by 'Packet::available' + _size = pos + payloadLength; + _payloadIndex = pos; + _payloadStartIndex = _payloadIndex; + _payloadEndIndex = _payloadIndex; + + error = espMqttClientTypes::Error::SUCCESS; +} + +Packet::Packet(espMqttClientTypes::Error& error, uint16_t packetId, const char* topic, uint8_t qos) +: _packetId(packetId) +, _data(nullptr) +, _size(0) +, _payloadIndex(0) +, _payloadStartIndex(0) +, _payloadEndIndex(0) +, _getPayload(nullptr) { + SubscribeItem list[1] = {topic, qos}; + _createSubscribe(error, list, 1); +} + +Packet::Packet(espMqttClientTypes::Error& error, MQTTPacketType type, uint16_t packetId) +: _packetId(packetId) +, _data(nullptr) +, _size(0) +, _payloadIndex(0) +, _payloadStartIndex(0) +, _payloadEndIndex(0) +, _getPayload(nullptr) { + if (!_allocate(2)) { + error = espMqttClientTypes::Error::OUT_OF_MEMORY; + return; + } + + size_t pos = 0; + _data[pos] = type; + if (type == PacketType.PUBREL) { + _data[pos++] |= HeaderFlag.PUBREL_RESERVED; + } else { + pos++; + } + pos += encodeRemainingLength(2, &_data[pos]); + _data[pos++] = packetId >> 8; + _data[pos] = packetId & 0xFF; + + error = espMqttClientTypes::Error::SUCCESS; +} + +Packet::Packet(espMqttClientTypes::Error& error, uint16_t packetId, const char* topic) +: _packetId(packetId) +, _data(nullptr) +, _size(0) +, _payloadIndex(0) +, _payloadStartIndex(0) +, _payloadEndIndex(0) +, _getPayload(nullptr) { + const char* list[1] = {topic}; + _createUnsubscribe(error, list, 1); +} + +Packet::Packet(espMqttClientTypes::Error& error, MQTTPacketType type) +: _packetId(0) +, _data(nullptr) +, _size(0) +, _payloadIndex(0) +, _payloadStartIndex(0) +, _payloadEndIndex(0) +, _getPayload(nullptr) { + if (!_allocate(0)) { + error = espMqttClientTypes::Error::OUT_OF_MEMORY; + return; + } + _data[0] |= type; + + error = espMqttClientTypes::Error::SUCCESS; +} + + +bool Packet::_allocate(size_t remainingLength) { + if (EMC_GET_FREE_MEMORY() < EMC_MIN_FREE_MEMORY) { + emc_log_w("Packet buffer not allocated: low memory"); + return false; + } + _size = 1 + remainingLengthLength(remainingLength) + remainingLength; + _data = reinterpret_cast(malloc(_size)); + if (!_data) { + _size = 0; + emc_log_w("Alloc failed (l:%zu)", _size); + return false; + } + emc_log_i("Alloc (l:%zu)", _size); + memset(_data, 0, _size); + return true; +} + +size_t Packet::_fillPublishHeader(uint16_t packetId, + const char* topic, + size_t remainingLength, + uint8_t qos, + bool retain) { + size_t index = 0; + + // FIXED HEADER + _data[index] = PacketType.PUBLISH; + if (retain) _data[index] |= HeaderFlag.PUBLISH_RETAIN; + if (qos == 0) { + _data[index++] |= HeaderFlag.PUBLISH_QOS0; + } else if (qos == 1) { + _data[index++] |= HeaderFlag.PUBLISH_QOS1; + } else if (qos == 2) { + _data[index++] |= HeaderFlag.PUBLISH_QOS2; + } + index += encodeRemainingLength(remainingLength, &_data[index]); + + // VARIABLE HEADER + index += encodeString(topic, &_data[index]); + if (qos > 0) { + _data[index++] = packetId >> 8; + _data[index++] = packetId & 0xFF; + } + + return index; +} + +void Packet::_createSubscribe(espMqttClientTypes::Error& error, + SubscribeItem* list, + size_t numberTopics) { + // Calculate size + size_t payload = 0; + for (size_t i = 0; i < numberTopics; ++i) { + payload += 2 + strlen(list[i].topic) + 1; // length bytes, string, qos + } + size_t remainingLength = 2 + payload; // packetId + payload + + // allocate memory + if (!_allocate(remainingLength)) { + error = espMqttClientTypes::Error::OUT_OF_MEMORY; + return; + } + + // serialize + size_t pos = 0; + _data[pos++] = PacketType.SUBSCRIBE | HeaderFlag.SUBSCRIBE_RESERVED; + pos += encodeRemainingLength(remainingLength, &_data[pos]); + _data[pos++] = _packetId >> 8; + _data[pos++] = _packetId & 0xFF; + for (size_t i = 0; i < numberTopics; ++i) { + pos += encodeString(list[i].topic, &_data[pos]); + _data[pos++] = list[i].qos; + } + + error = espMqttClientTypes::Error::SUCCESS; +} + +void Packet::_createUnsubscribe(espMqttClientTypes::Error& error, + const char** list, + size_t numberTopics) { + // Calculate size + size_t payload = 0; + for (size_t i = 0; i < numberTopics; ++i) { + payload += 2 + strlen(list[i]); // length bytes, string + } + size_t remainingLength = 2 + payload; // packetId + payload + + // allocate memory + if (!_allocate(remainingLength)) { + error = espMqttClientTypes::Error::OUT_OF_MEMORY; + return; + } + + // serialize + size_t pos = 0; + _data[pos++] = PacketType.UNSUBSCRIBE | HeaderFlag.UNSUBSCRIBE_RESERVED; + pos += encodeRemainingLength(remainingLength, &_data[pos]); + _data[pos++] = _packetId >> 8; + _data[pos++] = _packetId & 0xFF; + for (size_t i = 0; i < numberTopics; ++i) { + pos += encodeString(list[i], &_data[pos]); + } + + error = espMqttClientTypes::Error::SUCCESS; +} + +size_t Packet::_chunkedAvailable(size_t index) { + // index vs size check done in 'available(index)' + + // index points to header or first payload byte + if (index < _payloadIndex) { + if (_size > _payloadIndex && _payloadEndIndex != 0) { + size_t copied = _getPayload(&_data[_payloadIndex], std::min(static_cast(EMC_TX_BUFFER_SIZE), _size - _payloadStartIndex), index); + _payloadStartIndex = _payloadIndex; + _payloadEndIndex = _payloadStartIndex + copied - 1; + } + + // index points to payload unavailable + } else if (index > _payloadEndIndex || _payloadStartIndex > index) { + _payloadStartIndex = index; + size_t copied = _getPayload(&_data[_payloadIndex], std::min(static_cast(EMC_TX_BUFFER_SIZE), _size - _payloadStartIndex), index); + _payloadEndIndex = _payloadStartIndex + copied - 1; + } + + // now index points to header or payload available + return _payloadEndIndex - index + 1; +} + +const uint8_t* Packet::_chunkedData(size_t index) const { + // CAUTION!! available(index) has to be called first to check available data and possibly fill payloadbuffer + if (index < _payloadIndex) { + return &_data[index]; + } + return &_data[index - _payloadStartIndex + _payloadIndex]; +} + +} // end namespace espMqttClientInternals diff --git a/lib/espMqttClient/src/Packets/Packet.h b/lib/espMqttClient/src/Packets/Packet.h new file mode 100644 index 000000000..1af2f06af --- /dev/null +++ b/lib/espMqttClient/src/Packets/Packet.h @@ -0,0 +1,155 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#include +#include + +#include "Constants.h" +#include "../Config.h" +#include "../TypeDefs.h" +#include "../Helpers.h" +#include "../Logging.h" +#include "RemainingLength.h" +#include "String.h" + +namespace espMqttClientInternals { + +class Packet { + public: + ~Packet(); + size_t available(size_t index); + const uint8_t* data(size_t index) const; + + size_t size() const; + void setDup(); + uint16_t packetId() const; + MQTTPacketType packetType() const; + bool removable() const; + + protected: + uint16_t _packetId; // save as separate variable: will be accessed frequently + uint8_t* _data; + size_t _size; + + // variables for chunked payload handling + size_t _payloadIndex; + size_t _payloadStartIndex; + size_t _payloadEndIndex; + espMqttClientTypes::PayloadCallback _getPayload; + + struct SubscribeItem { + const char* topic; + uint8_t qos; + }; + + public: + // CONNECT + Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references) + bool cleanSession, + const char* username, + const char* password, + const char* willTopic, + bool willRetain, + uint8_t willQos, + const uint8_t* willPayload, + uint16_t willPayloadLength, + uint16_t keepAlive, + const char* clientId); + // PUBLISH + Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references) + uint16_t packetId, + const char* topic, + const uint8_t* payload, + size_t payloadLength, + uint8_t qos, + bool retain); + Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references) + uint16_t packetId, + const char* topic, + espMqttClientTypes::PayloadCallback payloadCallback, + size_t payloadLength, + uint8_t qos, + bool retain); + // SUBSCRIBE + Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references) + uint16_t packetId, + const char* topic, + uint8_t qos); + template + Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references) + uint16_t packetId, + const char* topic1, + uint8_t qos1, + const char* topic2, + uint8_t qos2, + Args&& ... args) + : _packetId(packetId) + , _data(nullptr) + , _size(0) + , _payloadIndex(0) + , _payloadStartIndex(0) + , _payloadEndIndex(0) + , _getPayload(nullptr) { + static_assert(sizeof...(Args) % 2 == 0, "Subscribe should be in topic/qos pairs"); + size_t numberTopics = 2 + (sizeof...(Args) / 2); + SubscribeItem list[numberTopics] = {topic1, qos1, topic2, qos2, args...}; + _createSubscribe(error, list, numberTopics); + } + // UNSUBSCRIBE + Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references) + uint16_t packetId, + const char* topic); + template + Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references) + uint16_t packetId, + const char* topic1, + const char* topic2, + Args&& ... args) + : _packetId(packetId) + , _data(nullptr) + , _size(0) + , _payloadIndex(0) + , _payloadStartIndex(0) + , _payloadEndIndex(0) + , _getPayload(nullptr) { + size_t numberTopics = 2 + sizeof...(Args); + const char* list[numberTopics] = {topic1, topic2, args...}; + _createUnsubscribe(error, list, numberTopics); + } + // PUBACK, PUBREC, PUBREL, PUBCOMP + Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references) + MQTTPacketType type, + uint16_t packetId); + // PING, DISCONN + explicit Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references) + MQTTPacketType type); + + private: + // pass remainingLength = total size - header - remainingLengthLength! + bool _allocate(size_t remainingLength); + + // fills header and returns index of next available byte in buffer + size_t _fillPublishHeader(uint16_t packetId, + const char* topic, + size_t remainingLength, + uint8_t qos, + bool retain); + void _createSubscribe(espMqttClientTypes::Error& error, // NOLINT(runtime/references) + SubscribeItem* list, + size_t numberTopics); + void _createUnsubscribe(espMqttClientTypes::Error& error, // NOLINT(runtime/references) + const char** list, + size_t numberTopics); + + size_t _chunkedAvailable(size_t index); + const uint8_t* _chunkedData(size_t index) const; +}; + +} // end namespace espMqttClientInternals diff --git a/lib/espMqttClient/src/Packets/Parser.cpp b/lib/espMqttClient/src/Packets/Parser.cpp new file mode 100644 index 000000000..07998a3da --- /dev/null +++ b/lib/espMqttClient/src/Packets/Parser.cpp @@ -0,0 +1,316 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#include "Parser.h" + +namespace espMqttClientInternals { + +uint8_t IncomingPacket::qos() const { + if ((fixedHeader.packetType & 0xF0) != PacketType.PUBLISH) return 0; + return (fixedHeader.packetType & 0x06) >> 1; // mask 0x00000110 +} + +bool IncomingPacket::retain() const { + if ((fixedHeader.packetType & 0xF0) != PacketType.PUBLISH) return 0; + return fixedHeader.packetType & 0x01; // mask 0x00000001 +} + +bool IncomingPacket::dup() const { + if ((fixedHeader.packetType & 0xF0) != PacketType.PUBLISH) return 0; + return fixedHeader.packetType & 0x08; // mask 0x00001000 +} + +void IncomingPacket::reset() { + fixedHeader.packetType = 0; + variableHeader.topicLength = 0; + variableHeader.fixed.packetId = 0; + payload.index = 0; + payload.length = 0; +} + +Parser::Parser() +: _data(nullptr) +, _len(0) +, _bytesRead(0) +, _bytePos(0) +, _parse(_fixedHeader) +, _packet() +, _payloadBuffer{0} { + // empty +} + +ParserResult Parser::parse(const uint8_t* data, size_t len, size_t* bytesRead) { + _data = data; + _len = len; + _bytesRead = 0; + ParserResult result = ParserResult::awaitData; + while (result == ParserResult::awaitData && _bytesRead < _len) { + result = _parse(this); + ++_bytesRead; + } + (*bytesRead) += _bytesRead; + return result; +} + +const IncomingPacket& Parser::getPacket() const { + return _packet; +} + +void Parser::reset() { + _parse = _fixedHeader; + _bytesRead = 0; + _bytePos = 0; + _packet.reset(); +} + +ParserResult Parser::_fixedHeader(Parser* p) { + p->_packet.reset(); + p->_packet.fixedHeader.packetType = p->_data[p->_bytesRead]; + + // keep PUBLISH out of the switch and handle in separate if/else + if ((p->_packet.fixedHeader.packetType & 0xF0) == PacketType.PUBLISH) { + uint8_t headerFlags = p->_packet.fixedHeader.packetType & 0x0F; + /* flags can be: 0b0000 --> no dup, qos 0, no retain + 0x0001 --> no dup, qos 0, retain + 0x0010 --> no dup, qos 1, no retain + 0x0011 --> no dup, qos 1, retain + 0x0100 --> no dup, qos 2, no retain + 0x0101 --> no dup, qos 2, retain + 0x1010 --> dup, qos 1, no retain + 0x1011 --> dup, qos 1, retain + 0x1100 --> dup, qos 2, no retain + 0x1101 --> dup, qos 2, retain + */ + if (headerFlags <= 0x05 || headerFlags >= 0x0A) { + p->_parse = _remainingLengthVariable; + p->_bytePos = 0; + } else { + emc_log_w("Invalid packet header: 0x%02x", p->_packet.fixedHeader.packetType); + return ParserResult::protocolError; + } + } else { + switch (p->_packet.fixedHeader.packetType) { + case PacketType.CONNACK | HeaderFlag.CONNACK_RESERVED: + case PacketType.PUBACK | HeaderFlag.PUBACK_RESERVED: + case PacketType.PUBREC | HeaderFlag.PUBREC_RESERVED: + case PacketType.PUBREL | HeaderFlag.PUBREL_RESERVED: + case PacketType.PUBCOMP | HeaderFlag.PUBCOMP_RESERVED: + case PacketType.UNSUBACK | HeaderFlag.UNSUBACK_RESERVED: + p->_parse = _remainingLengthFixed; + break; + case PacketType.SUBACK | HeaderFlag.SUBACK_RESERVED: + p->_parse = _remainingLengthVariable; + p->_bytePos = 0; + break; + case PacketType.PINGRESP | HeaderFlag.PINGRESP_RESERVED: + p->_parse = _remainingLengthNone; + break; + default: + emc_log_w("Invalid packet header: 0x%02x", p->_packet.fixedHeader.packetType); + return ParserResult::protocolError; + } + } + emc_log_i("Packet type: 0x%02x", p->_packet.fixedHeader.packetType); + return ParserResult::awaitData; +} + +ParserResult Parser::_remainingLengthFixed(Parser* p) { + p->_packet.fixedHeader.remainingLength.remainingLength = p->_data[p->_bytesRead]; + + if (p->_packet.fixedHeader.remainingLength.remainingLength == 2) { // variable header is 2 bytes long + if ((p->_packet.fixedHeader.packetType & 0xF0) != PacketType.CONNACK) { + p->_parse = _varHeaderPacketId1; + } else { + p->_parse = _varHeaderConnack1; + } + emc_log_i("Remaining length: %zu", p->_packet.fixedHeader.remainingLength.remainingLength); + return ParserResult::awaitData; + } + p->_parse = _fixedHeader; + emc_log_w("Invalid remaining length (fixed): %zu", p->_packet.fixedHeader.remainingLength.remainingLength); + return ParserResult::protocolError; +} + +ParserResult Parser::_remainingLengthVariable(Parser* p) { + p->_packet.fixedHeader.remainingLength.remainingLengthRaw[p->_bytePos] = p->_data[p->_bytesRead]; + if (p->_packet.fixedHeader.remainingLength.remainingLengthRaw[p->_bytePos] & 0x80) { + p->_bytePos++; + if (p->_bytePos == 4) { + emc_log_w("Invalid remaining length (variable)"); + return ParserResult::protocolError; + } else { + return ParserResult::awaitData; + } + } + + // no need to check for negative decoded length, check is already done + p->_packet.fixedHeader.remainingLength.remainingLength = decodeRemainingLength(p->_packet.fixedHeader.remainingLength.remainingLengthRaw); + + if ((p->_packet.fixedHeader.packetType & 0xF0) == PacketType.PUBLISH) { + p->_parse = _varHeaderTopicLength1; + emc_log_i("Remaining length: %zu", p->_packet.fixedHeader.remainingLength.remainingLength); + return ParserResult::awaitData; + } else { + int32_t payloadSize = p->_packet.fixedHeader.remainingLength.remainingLength - 2; // total - packet ID + if (0 < payloadSize && payloadSize < EMC_PAYLOAD_BUFFER_SIZE) { + p->_bytePos = 0; + p->_packet.payload.data = p->_payloadBuffer; + p->_packet.payload.index = 0; + p->_packet.payload.length = payloadSize; + p->_packet.payload.total = payloadSize; + p->_parse = _varHeaderPacketId1; + emc_log_i("Remaining length: %zu", p->_packet.fixedHeader.remainingLength.remainingLength); + return ParserResult::awaitData; + } else { + emc_log_w("Invalid payload length"); + } + } + p->_parse = _fixedHeader; + return ParserResult::protocolError; +} + +ParserResult Parser::_remainingLengthNone(Parser* p) { + p->_packet.fixedHeader.remainingLength.remainingLength = p->_data[p->_bytesRead]; + p->_parse = _fixedHeader; + if (p->_packet.fixedHeader.remainingLength.remainingLength == 0) { + emc_log_i("Remaining length: %zu", p->_packet.fixedHeader.remainingLength.remainingLength); + return ParserResult::packet; + } + emc_log_w("Invalid remaining length (none)"); + return ParserResult::protocolError; +} + +ParserResult Parser::_varHeaderConnack1(Parser* p) { + uint8_t data = p->_data[p->_bytesRead]; + if (data < 2) { // session present flag: equal to 0 or 1 + p->_packet.variableHeader.fixed.connackVarHeader.sessionPresent = data; + p->_parse = _varHeaderConnack2; + return ParserResult::awaitData; + } + p->_parse = _fixedHeader; + emc_log_w("Invalid session flags"); + return ParserResult::protocolError; +} + +ParserResult Parser::_varHeaderConnack2(Parser* p) { + uint8_t data = p->_data[p->_bytesRead]; + p->_parse = _fixedHeader; + if (data <= 5) { // connect return code max is 5 + p->_packet.variableHeader.fixed.connackVarHeader.returnCode = data; + emc_log_i("Packet complete"); + return ParserResult::packet; + } + emc_log_w("Invalid connack return code"); + return ParserResult::protocolError; +} + +ParserResult Parser::_varHeaderPacketId1(Parser* p) { + p->_packet.variableHeader.fixed.packetId |= p->_data[p->_bytesRead] << 8; + p->_parse = _varHeaderPacketId2; + return ParserResult::awaitData; +} + +ParserResult Parser::_varHeaderPacketId2(Parser* p) { + p->_packet.variableHeader.fixed.packetId |= p->_data[p->_bytesRead]; + p->_parse = _fixedHeader; + if (p->_packet.variableHeader.fixed.packetId != 0) { + emc_log_i("Packet variable header complete"); + if ((p->_packet.fixedHeader.packetType & 0xF0) == PacketType.SUBACK) { + p->_parse = _payloadSuback; + return ParserResult::awaitData; + } else if ((p->_packet.fixedHeader.packetType & 0xF0) == PacketType.PUBLISH) { + p->_packet.payload.total -= 2; // substract packet id length from payload + if (p->_packet.payload.total == 0) { + p->_parse = _fixedHeader; + return ParserResult::packet; + } else { + p->_parse = _payloadPublish; + } + return ParserResult::awaitData; + } else { + return ParserResult::packet; + } + } else { + emc_log_w("Invalid packet id"); + return ParserResult::protocolError; + } +} + +ParserResult Parser::_varHeaderTopicLength1(Parser* p) { + p->_packet.variableHeader.topicLength = p->_data[p->_bytesRead] << 8; + p->_parse = _varHeaderTopicLength2; + return ParserResult::awaitData; +} + +ParserResult Parser::_varHeaderTopicLength2(Parser* p) { + p->_packet.variableHeader.topicLength |= p->_data[p->_bytesRead]; + size_t maxTopicLength = + p->_packet.fixedHeader.remainingLength.remainingLength + - 2 // topic length bytes + - ((p->_packet.fixedHeader.packetType & (HeaderFlag.PUBLISH_QOS1 | HeaderFlag.PUBLISH_QOS2)) ? 2 : 0); + if (p->_packet.variableHeader.topicLength <= maxTopicLength) { + p->_parse = _varHeaderTopic; + p->_bytePos = 0; + p->_packet.payload.total = p->_packet.fixedHeader.remainingLength.remainingLength - 2 - p->_packet.variableHeader.topicLength; + return ParserResult::awaitData; + } + emc_log_w("Invalid topic length: %u > %zu", p->_packet.variableHeader.topicLength, maxTopicLength); + p->_parse = _fixedHeader; + return ParserResult::protocolError; +} + +ParserResult Parser::_varHeaderTopic(Parser* p) { + // no checking for character [MQTT-3.3.2-1] [MQTT-3.3.2-2] + p->_packet.variableHeader.topic[p->_bytePos] = static_cast(p->_data[p->_bytesRead]); + p->_bytePos++; + if (p->_bytePos == p->_packet.variableHeader.topicLength || p->_bytePos == EMC_MAX_TOPIC_LENGTH) { + p->_packet.variableHeader.topic[p->_bytePos] = 0x00; // add c-string delimiter + emc_log_i("Packet variable header topic complete"); + if (p->_packet.fixedHeader.packetType & (HeaderFlag.PUBLISH_QOS1 | HeaderFlag.PUBLISH_QOS2)) { + p->_parse = _varHeaderPacketId1; + } else if (p->_packet.payload.total == 0) { + p->_parse = _fixedHeader; + return ParserResult::packet; + } else { + p->_parse = _payloadPublish; + } + } + return ParserResult::awaitData; +} + +ParserResult Parser::_payloadSuback(Parser* p) { + uint8_t data = p->_data[p->_bytesRead]; + if (data < 0x03 || data == 0x80) { + p->_payloadBuffer[p->_bytePos] = data; + p->_bytePos++; + } else { + p->_parse = _fixedHeader; + emc_log_w("Invalid suback return code"); + return ParserResult::protocolError; + } + if (p->_bytePos == p->_packet.payload.total) { + p->_parse = _fixedHeader; + emc_log_i("Packet complete"); + return ParserResult::packet; + } + return ParserResult::awaitData; +} + +ParserResult Parser::_payloadPublish(Parser* p) { + p->_packet.payload.index += p->_packet.payload.length; + p->_packet.payload.data = &p->_data[p->_bytesRead]; + emc_log_i("payload: index %zu, total %zu, avail %zu/%zu", p->_packet.payload.index, p->_packet.payload.total, p->_len - p->_bytesRead, p->_len); + p->_packet.payload.length = std::min(p->_len - p->_bytesRead, p->_packet.payload.total - p->_packet.payload.index); + p->_bytesRead += p->_packet.payload.length - 1; // compensate for increment in _parse-loop + if (p->_packet.payload.index + p->_packet.payload.length == p->_packet.payload.total) { + p->_parse = _fixedHeader; + } + return ParserResult::packet; +} + +} // end namespace espMqttClientInternals diff --git a/lib/espMqttClient/src/Packets/Parser.h b/lib/espMqttClient/src/Packets/Parser.h new file mode 100644 index 000000000..2f6334e42 --- /dev/null +++ b/lib/espMqttClient/src/Packets/Parser.h @@ -0,0 +1,100 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#include +#include +#include + +#include "../Config.h" +#include "Constants.h" +#include "../Logging.h" +#include "RemainingLength.h" + +namespace espMqttClientInternals { + +struct IncomingPacket { + struct __attribute__((__packed__)) { + MQTTPacketType packetType; + union { + size_t remainingLength; + uint8_t remainingLengthRaw[4]; + } remainingLength; + } fixedHeader; + struct __attribute__((__packed__)) { + uint16_t topicLength; + char topic[EMC_MAX_TOPIC_LENGTH + 1]; // + 1 for c-string delimiter + union { + struct { + uint8_t sessionPresent; + uint8_t returnCode; + } connackVarHeader; + uint16_t packetId; + } fixed; + } variableHeader; + struct { + const uint8_t* data; + size_t length; + size_t index; + size_t total; + } payload; + + uint8_t qos() const; + bool retain() const; + bool dup() const; + void reset(); +}; + +enum class ParserResult : uint8_t { + awaitData, + packet, + protocolError +}; + +class Parser; +typedef ParserResult(*ParserFunc)(Parser*); + +class Parser { + public: + Parser(); + ParserResult parse(const uint8_t* data, size_t len, size_t* bytesRead); + const IncomingPacket& getPacket() const; + void reset(); + + private: + // keep data variables in class to avoid copying on every iteration of the parser + const uint8_t* _data; + size_t _len; + size_t _bytesRead; + size_t _bytePos; + ParserFunc _parse; + IncomingPacket _packet; + uint8_t _payloadBuffer[EMC_PAYLOAD_BUFFER_SIZE]; + + static ParserResult _fixedHeader(Parser* p); + static ParserResult _remainingLengthFixed(Parser* p); + static ParserResult _remainingLengthNone(Parser* p); + static ParserResult _remainingLengthVariable(Parser* p); + + + static ParserResult _varHeaderConnack1(Parser* p); + static ParserResult _varHeaderConnack2(Parser* p); + + static ParserResult _varHeaderPacketId1(Parser* p); + static ParserResult _varHeaderPacketId2(Parser* p); + + static ParserResult _varHeaderTopicLength1(Parser* p); + static ParserResult _varHeaderTopicLength2(Parser* p); + static ParserResult _varHeaderTopic(Parser* p); + + static ParserResult _payloadSuback(Parser* p); + static ParserResult _payloadPublish(Parser* p); +}; + +} // end namespace espMqttClientInternals diff --git a/lib/espMqttClient/src/Packets/RemainingLength.cpp b/lib/espMqttClient/src/Packets/RemainingLength.cpp new file mode 100644 index 000000000..d8644a33a --- /dev/null +++ b/lib/espMqttClient/src/Packets/RemainingLength.cpp @@ -0,0 +1,57 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#include "RemainingLength.h" + +namespace espMqttClientInternals { + +int32_t decodeRemainingLength(const uint8_t* stream) { + uint32_t multiplier = 1; + int32_t remainingLength = 0; + uint8_t currentByte = 0; + uint8_t encodedByte; + + do { + encodedByte = stream[currentByte++]; + remainingLength += (encodedByte & 127) * multiplier; + if (multiplier > 128 * 128 * 128) { + emc_log_e("Malformed Remaining Length"); + return -1; + } + multiplier *= 128; + } while ((encodedByte & 128) != 0); + + return remainingLength; +} + +uint8_t remainingLengthLength(uint32_t remainingLength) { + if (remainingLength < 128) return 1; + if (remainingLength < 16384) return 2; + if (remainingLength < 2097152) return 3; + if (remainingLength > 268435455) return 0; + return 4; +} + +uint8_t encodeRemainingLength(uint32_t remainingLength, uint8_t* destination) { + uint8_t currentByte = 0; + uint8_t bytesNeeded = 0; + + do { + uint8_t encodedByte = remainingLength % 128; + remainingLength /= 128; + if (remainingLength > 0) { + encodedByte = encodedByte | 128; + } + destination[currentByte++] = encodedByte; + bytesNeeded++; + } while (remainingLength > 0); + + return bytesNeeded; +} + +} // namespace espMqttClientInternals diff --git a/lib/espMqttClient/src/Packets/RemainingLength.h b/lib/espMqttClient/src/Packets/RemainingLength.h new file mode 100644 index 000000000..0b84e23e5 --- /dev/null +++ b/lib/espMqttClient/src/Packets/RemainingLength.h @@ -0,0 +1,32 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#include + +#include "../Logging.h" + +namespace espMqttClientInternals { + +// Calculations are based on non normative comment in section 2.2.3 Remaining Length of the MQTT specification + +// returns decoded length based on input stream +// stream is expected to contain full encoded remaining length +// return -1 on error. +int32_t decodeRemainingLength(const uint8_t* stream); + + +// returns the number of bytes needed to encode the remaining length +uint8_t remainingLengthLength(uint32_t remainingLength); + +// encodes the given remaining length to destination and returns number of bytes used +// destination is expected to be large enough to hold the number of bytes needed +uint8_t encodeRemainingLength(uint32_t remainingLength, uint8_t* destination); + +} // namespace espMqttClientInternals diff --git a/lib/espMqttClient/src/Packets/String.cpp b/lib/espMqttClient/src/Packets/String.cpp new file mode 100644 index 000000000..c3fe23fdc --- /dev/null +++ b/lib/espMqttClient/src/Packets/String.cpp @@ -0,0 +1,26 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#include "String.h" + +namespace espMqttClientInternals { + +size_t encodeString(const char* source, uint8_t* dest) { + size_t length = strlen(source); + if (length > 65535) { + emc_log_e("String length error"); + return 0; + } + + dest[0] = static_cast(length) >> 8; + dest[1] = static_cast(length) & 0xFF; + memcpy(&dest[2], source, length); + return 2 + length; +} + +} // namespace espMqttClientInternals diff --git a/lib/espMqttClient/src/Packets/String.h b/lib/espMqttClient/src/Packets/String.h new file mode 100644 index 000000000..7f1e1e848 --- /dev/null +++ b/lib/espMqttClient/src/Packets/String.h @@ -0,0 +1,22 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#include +#include // memcpy + +#include "../Logging.h" + +namespace espMqttClientInternals { + +// encodes the given source string into destination and returns number of bytes used +// destination is expected to be large enough to hold the number of bytes needed +size_t encodeString(const char* source, uint8_t* dest); + +} // namespace espMqttClientInternals diff --git a/lib/espMqttClient/src/Transport/ClientAsync.cpp b/lib/espMqttClient/src/Transport/ClientAsync.cpp new file mode 100644 index 000000000..4f8d69eeb --- /dev/null +++ b/lib/espMqttClient/src/Transport/ClientAsync.cpp @@ -0,0 +1,58 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + +#include "ClientAsync.h" + +namespace espMqttClientInternals { + +ClientAsync::ClientAsync() +: client() +, availableData(0) +, bufData(nullptr) { + // empty +} + +bool ClientAsync::connect(IPAddress ip, uint16_t port) { + return client.connect(ip, port); +} + +bool ClientAsync::connect(const char* host, uint16_t port) { + return client.connect(host, port); +} + +size_t ClientAsync::write(const uint8_t* buf, size_t size) { + return client.write(reinterpret_cast(buf), size); +} + +int ClientAsync::read(uint8_t* buf, size_t size) { + size_t willRead = std::min(size, availableData); + memcpy(buf, bufData, std::min(size, availableData)); + if (availableData > size) { + emc_log_w("Buffer is smaller than available data: %zu - %zu", size, availableData); + } + availableData = 0; + return willRead; +} + +void ClientAsync::stop() { + client.close(false); +} + +bool ClientAsync::connected() { + return client.connected(); +} + +bool ClientAsync::disconnected() { + return client.disconnected(); +} + +} // namespace espMqttClientInternals + +#endif diff --git a/lib/espMqttClient/src/Transport/ClientAsync.h b/lib/espMqttClient/src/Transport/ClientAsync.h new file mode 100644 index 000000000..c3ddd0388 --- /dev/null +++ b/lib/espMqttClient/src/Transport/ClientAsync.h @@ -0,0 +1,44 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + +#pragma once + +#if defined(ARDUINO_ARCH_ESP32) + #include "freertos/FreeRTOS.h" + #include +#elif defined(ARDUINO_ARCH_ESP8266) + #include +#endif + +#include "Transport.h" +#include "../Config.h" +#include "../Logging.h" + +namespace espMqttClientInternals { + +class ClientAsync : public Transport { + public: + ClientAsync(); + bool connect(IPAddress ip, uint16_t port) override; + bool connect(const char* host, uint16_t port) override; + size_t write(const uint8_t* buf, size_t size) override; + int read(uint8_t* buf, size_t size) override; + void stop() override; + bool connected() override; + bool disconnected() override; + + AsyncClient client; + size_t availableData; + uint8_t* bufData; +}; + +} // namespace espMqttClientInternals + +#endif diff --git a/lib/espMqttClient/src/Transport/ClientPosix.cpp b/lib/espMqttClient/src/Transport/ClientPosix.cpp new file mode 100644 index 000000000..83b555d1b --- /dev/null +++ b/lib/espMqttClient/src/Transport/ClientPosix.cpp @@ -0,0 +1,92 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#include "ClientPosix.h" + +#if defined(__linux__) + +namespace espMqttClientInternals { + +ClientPosix::ClientPosix() +: _sockfd(-1) +, _host() { + // empty +} + +ClientPosix::~ClientPosix() { + ClientPosix::stop(); +} + +bool ClientPosix::connect(IPAddress ip, uint16_t port) { + if (connected()) stop(); + + _sockfd = ::socket(AF_INET, SOCK_STREAM, 0); + if (_sockfd < 0) { + emc_log_e("Error %d opening socket", errno); + } + + int flag = 1; + if (setsockopt(_sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int)) < 0) { + emc_log_e("Error %d disabling nagle", errno); + } + + memset(&_host, 0, sizeof(_host)); + _host.sin_family = AF_INET; + _host.sin_addr.s_addr = htonl(uint32_t(ip)); + _host.sin_port = ::htons(port); + + int ret = ::connect(_sockfd, (struct sockaddr *)&_host, sizeof(_host)); + + if (ret < 0) { + emc_log_e("Error connecting: %d - (%d) %s", ret, errno, strerror(errno)); + return false; + } + + emc_log_i("Connected"); + return true; +} + +bool ClientPosix::connect(const char* host, uint16_t port) { + // tbi + (void) host; + (void) port; + return false; +} + +size_t ClientPosix::write(const uint8_t* buf, size_t size) { + return ::send(_sockfd, buf, size, 0); +} + +int ClientPosix::read(uint8_t* buf, size_t size) { + int ret = ::recv(_sockfd, buf, size, MSG_DONTWAIT); + /* + if (ret < 0) { + emc_log_e("Error reading: %s", strerror(errno)); + } + */ + return ret; +} + +void ClientPosix::stop() { + if (_sockfd >= 0) { + ::close(_sockfd); + _sockfd = -1; + } +} + +bool ClientPosix::connected() { + return _sockfd >= 0; +} + +bool ClientPosix::disconnected() { + return _sockfd < 0; +} + +} // namespace espMqttClientInternals + +#endif diff --git a/lib/espMqttClient/src/Transport/ClientPosix.h b/lib/espMqttClient/src/Transport/ClientPosix.h new file mode 100644 index 000000000..a2f9c9fae --- /dev/null +++ b/lib/espMqttClient/src/Transport/ClientPosix.h @@ -0,0 +1,51 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#if defined(__linux__) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Transport.h" // includes IPAddress +#include "../Logging.h" + +#ifndef EMC_POSIX_PEEK_SIZE +#define EMC_POSIX_PEEK_SIZE 1500 +#endif + +namespace espMqttClientInternals { + +class ClientPosix : public Transport { + public: + ClientPosix(); + ~ClientPosix(); + bool connect(IPAddress ip, uint16_t port) override; + bool connect(const char* host, uint16_t port) override; + size_t write(const uint8_t* buf, size_t size) override; + int read(uint8_t* buf, size_t size) override; + void stop() override; + bool connected() override; + bool disconnected() override; + + protected: + int _sockfd; + struct sockaddr_in _host; +}; + +} // namespace espMqttClientInternals + +#endif diff --git a/lib/espMqttClient/src/Transport/ClientSecureSync.cpp b/lib/espMqttClient/src/Transport/ClientSecureSync.cpp new file mode 100644 index 000000000..36288c6a5 --- /dev/null +++ b/lib/espMqttClient/src/Transport/ClientSecureSync.cpp @@ -0,0 +1,71 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + +#include "ClientSecureSync.h" +#include // socket options + +namespace espMqttClientInternals { + +ClientSecureSync::ClientSecureSync() +: client() { + // empty +} + +bool ClientSecureSync::connect(IPAddress ip, uint16_t port) { + bool ret = client.connect(ip, port); // implicit conversion of return code int --> bool + if (ret) { + #if defined(ARDUINO_ARCH_ESP8266) + client.setNoDelay(true); + #elif defined(ARDUINO_ARCH_ESP32) + // Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure + int val = true; + client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int)); + #endif + } + return ret; +} + +bool ClientSecureSync::connect(const char* host, uint16_t port) { + bool ret = client.connect(host, port); // implicit conversion of return code int --> bool + if (ret) { + #if defined(ARDUINO_ARCH_ESP8266) + client.setNoDelay(true); + #elif defined(ARDUINO_ARCH_ESP32) + // Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure + int val = true; + client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int)); + #endif + } + return ret; +} + +size_t ClientSecureSync::write(const uint8_t* buf, size_t size) { + return client.write(buf, size); +} + +int ClientSecureSync::read(uint8_t* buf, size_t size) { + return client.read(buf, size); +} + +void ClientSecureSync::stop() { + client.stop(); +} + +bool ClientSecureSync::connected() { + return client.connected(); +} + +bool ClientSecureSync::disconnected() { + return !client.connected(); +} + +} // namespace espMqttClientInternals + +#endif diff --git a/lib/espMqttClient/src/Transport/ClientSecureSync.h b/lib/espMqttClient/src/Transport/ClientSecureSync.h new file mode 100644 index 000000000..b81681e36 --- /dev/null +++ b/lib/espMqttClient/src/Transport/ClientSecureSync.h @@ -0,0 +1,34 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + +#include // includes IPAddress + +#include "Transport.h" + +namespace espMqttClientInternals { + +class ClientSecureSync : public Transport { + public: + ClientSecureSync(); + bool connect(IPAddress ip, uint16_t port) override; + bool connect(const char* host, uint16_t port) override; + size_t write(const uint8_t* buf, size_t size) override; + int read(uint8_t* buf, size_t size) override; + void stop() override; + bool connected() override; + bool disconnected() override; + WiFiClientSecure client; +}; + +} // namespace espMqttClientInternals + +#endif diff --git a/lib/espMqttClient/src/Transport/ClientSync.cpp b/lib/espMqttClient/src/Transport/ClientSync.cpp new file mode 100644 index 000000000..b2c4045f1 --- /dev/null +++ b/lib/espMqttClient/src/Transport/ClientSync.cpp @@ -0,0 +1,71 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + +#include "ClientSync.h" +#include // socket options + +namespace espMqttClientInternals { + +ClientSync::ClientSync() +: client() { + // empty +} + +bool ClientSync::connect(IPAddress ip, uint16_t port) { + bool ret = client.connect(ip, port); // implicit conversion of return code int --> bool + if (ret) { + #if defined(ARDUINO_ARCH_ESP8266) + client.setNoDelay(true); + #elif defined(ARDUINO_ARCH_ESP32) + // Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure (for consistency also here) + int val = true; + client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int)); + #endif + } + return ret; +} + +bool ClientSync::connect(const char* host, uint16_t port) { + bool ret = client.connect(host, port); // implicit conversion of return code int --> bool + if (ret) { + #if defined(ARDUINO_ARCH_ESP8266) + client.setNoDelay(true); + #elif defined(ARDUINO_ARCH_ESP32) + // Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure (for consistency also here) + int val = true; + client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int)); + #endif + } + return ret; +} + +size_t ClientSync::write(const uint8_t* buf, size_t size) { + return client.write(buf, size); +} + +int ClientSync::read(uint8_t* buf, size_t size) { + return client.read(buf, size); +} + +void ClientSync::stop() { + client.stop(); +} + +bool ClientSync::connected() { + return client.connected(); +} + +bool ClientSync::disconnected() { + return !client.connected(); +} + +} // namespace espMqttClientInternals + +#endif diff --git a/lib/espMqttClient/src/Transport/ClientSync.h b/lib/espMqttClient/src/Transport/ClientSync.h new file mode 100644 index 000000000..ccfbdbae5 --- /dev/null +++ b/lib/espMqttClient/src/Transport/ClientSync.h @@ -0,0 +1,34 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + +#include // includes IPAddress + +#include "Transport.h" + +namespace espMqttClientInternals { + +class ClientSync : public Transport { + public: + ClientSync(); + bool connect(IPAddress ip, uint16_t port) override; + bool connect(const char* host, uint16_t port) override; + size_t write(const uint8_t* buf, size_t size) override; + int read(uint8_t* buf, size_t size) override; + void stop() override; + bool connected() override; + bool disconnected() override; + WiFiClient client; +}; + +} // namespace espMqttClientInternals + +#endif diff --git a/lib/espMqttClient/src/Transport/IPAddress.cpp b/lib/espMqttClient/src/Transport/IPAddress.cpp new file mode 100644 index 000000000..b198429dc --- /dev/null +++ b/lib/espMqttClient/src/Transport/IPAddress.cpp @@ -0,0 +1,32 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#if defined(__linux__) + +#include "IPAddress.h" + +IPAddress::IPAddress() +: _address(0) { + // empty +} + +IPAddress::IPAddress(uint8_t p0, uint8_t p1, uint8_t p2, uint8_t p3) +: _address(0) { + _address = (uint32_t)p0 << 24 | (uint32_t)p1 << 16 | (uint32_t)p2 << 8 | p3; +} + +IPAddress::IPAddress(uint32_t address) +: _address(address) { + // empty +} + +IPAddress::operator uint32_t() { + return _address; +} + +#endif diff --git a/lib/espMqttClient/src/Transport/IPAddress.h b/lib/espMqttClient/src/Transport/IPAddress.h new file mode 100644 index 000000000..279a1957d --- /dev/null +++ b/lib/espMqttClient/src/Transport/IPAddress.h @@ -0,0 +1,28 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#if defined(ARDUINO) + #include +#else + +#include + +class IPAddress { + public: + IPAddress(); + IPAddress(uint8_t p0, uint8_t p1, uint8_t p2, uint8_t p3); + explicit IPAddress(uint32_t address); + operator uint32_t(); + + protected: + uint32_t _address; +}; + +#endif diff --git a/lib/espMqttClient/src/Transport/Transport.h b/lib/espMqttClient/src/Transport/Transport.h new file mode 100644 index 000000000..6720c024f --- /dev/null +++ b/lib/espMqttClient/src/Transport/Transport.h @@ -0,0 +1,28 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#include // size_t + +#include "IPAddress.h" + +namespace espMqttClientInternals { + +class Transport { + public: + virtual bool connect(IPAddress ip, uint16_t port) = 0; + virtual bool connect(const char* host, uint16_t port) = 0; + virtual size_t write(const uint8_t* buf, size_t size) = 0; + virtual int read(uint8_t* buf, size_t size) = 0; + virtual void stop() = 0; + virtual bool connected() = 0; + virtual bool disconnected() = 0; +}; + +} // namespace espMqttClientInternals diff --git a/lib/espMqttClient/src/TypeDefs.cpp b/lib/espMqttClient/src/TypeDefs.cpp new file mode 100644 index 000000000..4f92c1f64 --- /dev/null +++ b/lib/espMqttClient/src/TypeDefs.cpp @@ -0,0 +1,51 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +Parts are based on the original work of Marvin Roger: +https://github.com/marvinroger/async-mqtt-client + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#include "TypeDefs.h" + +namespace espMqttClientTypes { + +const char* disconnectReasonToString(DisconnectReason reason) { + switch (reason) { + case DisconnectReason::USER_OK: return "No error"; + case DisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION: return "Unacceptable protocol version"; + case DisconnectReason::MQTT_IDENTIFIER_REJECTED: return "Identified rejected"; + case DisconnectReason::MQTT_SERVER_UNAVAILABLE: return "Server unavailable"; + case DisconnectReason::MQTT_MALFORMED_CREDENTIALS: return "Malformed credentials"; + case DisconnectReason::MQTT_NOT_AUTHORIZED: return "Not authorized"; + case DisconnectReason::TLS_BAD_FINGERPRINT: return "Bad fingerprint"; + case DisconnectReason::TCP_DISCONNECTED: return "TCP disconnected"; + default: return ""; + } +} + +const char* subscribeReturncodeToString(SubscribeReturncode returnCode) { + switch (returnCode) { + case SubscribeReturncode::QOS0: return "QoS 0"; + case SubscribeReturncode::QOS1: return "QoS 1"; + case SubscribeReturncode::QOS2: return "QoS 2"; + case SubscribeReturncode::FAIL: return "Failed"; + default: return ""; + } +} + +const char* errorToString(Error error) { + switch (error) { + case Error::SUCCESS: return "Success"; + case Error::OUT_OF_MEMORY: return "Out of memory"; + case Error::MAX_RETRIES: return "Maximum retries exceeded"; + case Error::MALFORMED_PARAMETER: return "Malformed parameters"; + case Error::MISC_ERROR: return "Misc error"; + default: return ""; + } +} + +} // end namespace espMqttClientTypes diff --git a/lib/espMqttClient/src/TypeDefs.h b/lib/espMqttClient/src/TypeDefs.h new file mode 100644 index 000000000..0f1536069 --- /dev/null +++ b/lib/espMqttClient/src/TypeDefs.h @@ -0,0 +1,73 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +Parts are based on the original work of Marvin Roger: +https://github.com/marvinroger/async-mqtt-client + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#include +#include +#include + +namespace espMqttClientTypes { + +enum class DisconnectReason : uint8_t { + USER_OK = 0, + MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1, + MQTT_IDENTIFIER_REJECTED = 2, + MQTT_SERVER_UNAVAILABLE = 3, + MQTT_MALFORMED_CREDENTIALS = 4, + MQTT_NOT_AUTHORIZED = 5, + TLS_BAD_FINGERPRINT = 6, + TCP_DISCONNECTED = 7 +}; + +const char* disconnectReasonToString(DisconnectReason reason); + +enum class SubscribeReturncode : uint8_t { + QOS0 = 0x00, + QOS1 = 0x01, + QOS2 = 0x02, + FAIL = 0X80 +}; + +const char* subscribeReturncodeToString(SubscribeReturncode returnCode); + +enum class Error : uint8_t { + SUCCESS = 0, + OUT_OF_MEMORY = 1, + MAX_RETRIES = 2, + MALFORMED_PARAMETER = 3, + MISC_ERROR = 4 +}; + +const char* errorToString(Error error); + +struct MessageProperties { + uint8_t qos; + bool dup; + bool retain; + uint16_t packetId; +}; + +typedef std::function OnConnectCallback; +typedef std::function OnDisconnectCallback; +typedef std::function OnSubscribeCallback; +typedef std::function OnUnsubscribeCallback; +typedef std::function OnMessageCallback; +typedef std::function OnPublishCallback; +typedef std::function PayloadCallback; +typedef std::function OnErrorCallback; + +enum class UseInternalTask { + NO = 0, + YES = 1, +}; + +} // end namespace espMqttClientTypes diff --git a/lib/espMqttClient/src/espMqttClient.cpp b/lib/espMqttClient/src/espMqttClient.cpp new file mode 100644 index 000000000..833ece10b --- /dev/null +++ b/lib/espMqttClient/src/espMqttClient.cpp @@ -0,0 +1,113 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#include "espMqttClient.h" + +#if defined(ARDUINO_ARCH_ESP8266) +espMqttClient::espMqttClient() +: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO) +, _client() { + _transport = &_client; +} + +espMqttClientSecure::espMqttClientSecure() +: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO) +, _client() { + _transport = &_client; +} + +espMqttClientSecure& espMqttClientSecure::setInsecure() { + _client.client.setInsecure(); + return *this; +} + +espMqttClientSecure& espMqttClientSecure::setFingerprint(const uint8_t fingerprint[20]) { + _client.client.setFingerprint(fingerprint); + return *this; +} + +espMqttClientSecure& espMqttClientSecure::setTrustAnchors(const X509List *ta) { + _client.client.setTrustAnchors(ta); + return *this; +} + +espMqttClientSecure& espMqttClientSecure::setClientRSACert(const X509List *cert, const PrivateKey *sk) { + _client.client.setClientRSACert(cert, sk); + return *this; +} + +espMqttClientSecure& espMqttClientSecure::setClientECCert(const X509List *cert, const PrivateKey *sk, unsigned allowed_usages, unsigned cert_issuer_key_type) { + _client.client.setClientECCert(cert, sk, allowed_usages, cert_issuer_key_type); + return *this; +} + +espMqttClientSecure& espMqttClientSecure::setCertStore(CertStoreBase *certStore) { + _client.client.setCertStore(certStore); + return *this; +} +#endif + +#if defined(ARDUINO_ARCH_ESP32) +espMqttClient::espMqttClient(espMqttClientTypes::UseInternalTask useInternalTask) +: MqttClientSetup(useInternalTask) +, _client() { + _transport = &_client; +} + +espMqttClient::espMqttClient(uint8_t priority, uint8_t core) +: MqttClientSetup(espMqttClientTypes::UseInternalTask::YES, priority, core) +, _client() { + _transport = &_client; +} + +espMqttClientSecure::espMqttClientSecure(espMqttClientTypes::UseInternalTask useInternalTask) +: MqttClientSetup(useInternalTask) +, _client() { + _transport = &_client; +} + +espMqttClientSecure::espMqttClientSecure(uint8_t priority, uint8_t core) +: MqttClientSetup(espMqttClientTypes::UseInternalTask::YES, priority, core) +, _client() { + _transport = &_client; +} + +espMqttClientSecure& espMqttClientSecure::setInsecure() { + _client.client.setInsecure(); + return *this; +} + +espMqttClientSecure& espMqttClientSecure::setCACert(const char* rootCA) { + _client.client.setCACert(rootCA); + return *this; +} + +espMqttClientSecure& espMqttClientSecure::setCertificate(const char* clientCa) { + _client.client.setCertificate(clientCa); + return *this; +} + +espMqttClientSecure& espMqttClientSecure::setPrivateKey(const char* privateKey) { + _client.client.setPrivateKey(privateKey); + return *this; +} + +espMqttClientSecure& espMqttClientSecure::setPreSharedKey(const char* pskIdent, const char* psKey) { + _client.client.setPreSharedKey(pskIdent, psKey); + return *this; +} + +#endif + +#if defined(__linux__) +espMqttClient::espMqttClient() +: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO) +, _client() { + _transport = &_client; +} +#endif diff --git a/lib/espMqttClient/src/espMqttClient.h b/lib/espMqttClient/src/espMqttClient.h new file mode 100644 index 000000000..4e448011d --- /dev/null +++ b/lib/espMqttClient/src/espMqttClient.h @@ -0,0 +1,80 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +API is based on the original work of Marvin Roger: +https://github.com/marvinroger/async-mqtt-client + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +#include "Transport/ClientSync.h" +#include "Transport/ClientSecureSync.h" +#elif defined(__linux__) +#include "Transport/ClientPosix.h" +#endif + +#include "MqttClientSetup.h" + +#if defined(ARDUINO_ARCH_ESP8266) +class espMqttClient : public MqttClientSetup { + public: + espMqttClient(); + + protected: + espMqttClientInternals::ClientSync _client; +}; + +class espMqttClientSecure : public MqttClientSetup { + public: + espMqttClientSecure(); + espMqttClientSecure& setInsecure(); + espMqttClientSecure& setFingerprint(const uint8_t fingerprint[20]); + espMqttClientSecure& setTrustAnchors(const X509List *ta); + espMqttClientSecure& setClientRSACert(const X509List *cert, const PrivateKey *sk); + espMqttClientSecure& setClientECCert(const X509List *cert, const PrivateKey *sk, unsigned allowed_usages, unsigned cert_issuer_key_type); + espMqttClientSecure& setCertStore(CertStoreBase *certStore); + + protected: + espMqttClientInternals::ClientSecureSync _client; +}; +#endif + +#if defined(ARDUINO_ARCH_ESP32) +class espMqttClient : public MqttClientSetup { + public: + explicit espMqttClient(espMqttClientTypes::UseInternalTask useInternalTask); + explicit espMqttClient(uint8_t priority = 1, uint8_t core = 1); + + protected: + espMqttClientInternals::ClientSync _client; +}; + +class espMqttClientSecure : public MqttClientSetup { + public: + explicit espMqttClientSecure(espMqttClientTypes::UseInternalTask useInternalTask); + explicit espMqttClientSecure(uint8_t priority = 1, uint8_t core = 1); + espMqttClientSecure& setInsecure(); + espMqttClientSecure& setCACert(const char* rootCA); + espMqttClientSecure& setCertificate(const char* clientCa); + espMqttClientSecure& setPrivateKey(const char* privateKey); + espMqttClientSecure& setPreSharedKey(const char* pskIdent, const char* psKey); + + protected: + espMqttClientInternals::ClientSecureSync _client; +}; +#endif + +#if defined(__linux__) +class espMqttClient : public MqttClientSetup { + public: + espMqttClient(); + + protected: + espMqttClientInternals::ClientPosix _client; +}; +#endif diff --git a/lib/espMqttClient/src/espMqttClientAsync.cpp b/lib/espMqttClient/src/espMqttClientAsync.cpp new file mode 100644 index 000000000..98b7f1519 --- /dev/null +++ b/lib/espMqttClient/src/espMqttClientAsync.cpp @@ -0,0 +1,61 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + +#include "espMqttClientAsync.h" + +espMqttClientAsync::espMqttClientAsync() +: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO) +, _clientAsync() { + _transport = &_clientAsync; + _clientAsync.client.onConnect(onConnectCb, this); + _clientAsync.client.onDisconnect(onDisconnectCb, this); + _clientAsync.client.onData(onDataCb, this); + _clientAsync.client.onPoll(onPollCb, this); +} + +bool espMqttClientAsync::connect() { + bool ret = MqttClient::connect(); + loop(); + return ret; +} + +void espMqttClientAsync::_setupClient(espMqttClientAsync* c) { + (void)c; +} + +void espMqttClientAsync::onConnectCb(void* a, AsyncClient* c) { + c->setNoDelay(true); + espMqttClientAsync* client = reinterpret_cast(a); + client->_state = MqttClient::State::connectingTcp2; + client->loop(); +} + +void espMqttClientAsync::onDataCb(void* a, AsyncClient* c, void* data, size_t len) { + (void)c; + espMqttClientAsync* client = reinterpret_cast(a); + client->_clientAsync.bufData = reinterpret_cast(data); + client->_clientAsync.availableData = len; + client->loop(); +} + +void espMqttClientAsync::onDisconnectCb(void* a, AsyncClient* c) { + (void)c; + espMqttClientAsync* client = reinterpret_cast(a); + client->_state = MqttClient::State::disconnectingTcp2; + client->loop(); +} + +void espMqttClientAsync::onPollCb(void* a, AsyncClient* c) { + (void)c; + espMqttClientAsync* client = reinterpret_cast(a); + client->loop(); +} + +#endif diff --git a/lib/espMqttClient/src/espMqttClientAsync.h b/lib/espMqttClient/src/espMqttClientAsync.h new file mode 100644 index 000000000..1b9ed8beb --- /dev/null +++ b/lib/espMqttClient/src/espMqttClientAsync.h @@ -0,0 +1,36 @@ +/* +Copyright (c) 2022 Bert Melis. All rights reserved. + +API is based on the original work of Marvin Roger: +https://github.com/marvinroger/async-mqtt-client + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + +#include "Transport/ClientAsync.h" + +#include "MqttClientSetup.h" + +class espMqttClientAsync : public MqttClientSetup { + public: + espMqttClientAsync(); + bool connect(); + + protected: + espMqttClientInternals::ClientAsync _clientAsync; + static void _setupClient(espMqttClientAsync* c); + static void _disconnectClient(espMqttClientAsync* c); + + static void onConnectCb(void* a, AsyncClient* c); + static void onDataCb(void* a, AsyncClient* c, void* data, size_t len); + static void onDisconnectCb(void* a, AsyncClient* c); + static void onPollCb(void* a, AsyncClient* c); +}; + +#endif diff --git a/lib/framework/ESP8266React.h b/lib/framework/ESP8266React.h index 953232f78..32ca0f55c 100644 --- a/lib/framework/ESP8266React.h +++ b/lib/framework/ESP8266React.h @@ -59,7 +59,7 @@ class ESP8266React { return &_mqttSettingsService; } - AsyncMqttClient * getMqttClient() { + espMqttClient * getMqttClient() { return _mqttSettingsService.getMqttClient(); } diff --git a/lib/framework/MqttPubSub.h b/lib/framework/MqttPubSub.h deleted file mode 100644 index fe8e56434..000000000 --- a/lib/framework/MqttPubSub.h +++ /dev/null @@ -1,163 +0,0 @@ -#ifndef MqttPubSub_h -#define MqttPubSub_h - -#include -#include - -using namespace std::placeholders; // for `_1` etc - -#define MQTT_ORIGIN_ID "mqtt" - -template -class MqttConnector { - protected: - StatefulService * _statefulService; - AsyncMqttClient * _mqttClient; - size_t _bufferSize; - - MqttConnector(StatefulService * statefulService, AsyncMqttClient * mqttClient, size_t bufferSize) - : _statefulService(statefulService) - , _mqttClient(mqttClient) - , _bufferSize(bufferSize) { - _mqttClient->onConnect(std::bind(&MqttConnector::onConnect, this)); - } - - virtual void onConnect() = 0; - - public: - inline AsyncMqttClient * getMqttClient() const { - return _mqttClient; - } -}; - -template -class MqttPub : virtual public MqttConnector { - public: - MqttPub(JsonStateReader stateReader, - StatefulService * statefulService, - AsyncMqttClient * mqttClient, - const String & pubTopic = "", - size_t bufferSize = DEFAULT_BUFFER_SIZE) - : MqttConnector(statefulService, mqttClient, bufferSize) - , _stateReader(stateReader) - , _pubTopic(pubTopic) { - MqttConnector::_statefulService->addUpdateHandler([&](const String & originId) { publish(); }, false); - } - - void setPubTopic(const String & pubTopic) { - _pubTopic = pubTopic; - publish(); - } - - protected: - virtual void onConnect() { - publish(); - } - - private: - JsonStateReader _stateReader; - String _pubTopic; - - void publish() { - if (_pubTopic.length() > 0 && MqttConnector::_mqttClient->connected()) { - // serialize to json doc - DynamicJsonDocument json(MqttConnector::_bufferSize); - JsonObject jsonObject = json.to(); - MqttConnector::_statefulService->read(jsonObject, _stateReader); - - // serialize to string - String payload; - serializeJson(json, payload); - - // publish the payload - MqttConnector::_mqttClient->publish(_pubTopic.c_str(), 0, false, payload.c_str()); - } - } -}; - -template -class MqttSub : virtual public MqttConnector { - public: - MqttSub(JsonStateUpdater stateUpdater, - StatefulService * statefulService, - AsyncMqttClient * mqttClient, - const String & subTopic = "", - size_t bufferSize = DEFAULT_BUFFER_SIZE) - : MqttConnector(statefulService, mqttClient, bufferSize) - , _stateUpdater(stateUpdater) - , _subTopic(subTopic) { - MqttConnector::_mqttClient->onMessage(std::bind(&MqttSub::onMqttMessage, this, _1, _2, _3, _4, _5, _6)); - } - - void setSubTopic(const String & subTopic) { - if (!_subTopic.equals(subTopic)) { - // unsubscribe from the existing topic if one was set - if (_subTopic.length() > 0) { - MqttConnector::_mqttClient->unsubscribe(_subTopic.c_str()); - } - // set the new topic and re-configure the subscription - _subTopic = subTopic; - subscribe(); - } - } - - protected: - virtual void onConnect() { - subscribe(); - } - - private: - JsonStateUpdater _stateUpdater; - String _subTopic; - - void subscribe() { - if (_subTopic.length() > 0) { - MqttConnector::_mqttClient->subscribe(_subTopic.c_str(), 2); - } - } - - void onMqttMessage(char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { - // we only care about the topic we are watching in this class - if (strcmp(_subTopic.c_str(), topic)) { - return; - } - - // deserialize from string - DynamicJsonDocument json(MqttConnector::_bufferSize); - DeserializationError error = deserializeJson(json, payload, len); - if (!error && json.is()) { - JsonObject jsonObject = json.as(); - MqttConnector::_statefulService->update(jsonObject, _stateUpdater, MQTT_ORIGIN_ID); - } - } -}; - -template -class MqttPubSub : public MqttPub, public MqttSub { - public: - MqttPubSub(JsonStateReader stateReader, - JsonStateUpdater stateUpdater, - StatefulService * statefulService, - AsyncMqttClient * mqttClient, - const String & pubTopic = "", - const String & subTopic = "", - size_t bufferSize = DEFAULT_BUFFER_SIZE) - : MqttConnector(statefulService, mqttClient, bufferSize) - , MqttPub(stateReader, statefulService, mqttClient, pubTopic, bufferSize) - , MqttSub(stateUpdater, statefulService, mqttClient, subTopic, bufferSize) { - } - - public: - void configureTopics(const String & pubTopic, const String & subTopic) { - MqttSub::setSubTopic(subTopic); - MqttPub::setPubTopic(pubTopic); - } - - protected: - void onConnect() { - MqttSub::onConnect(); - MqttPub::onConnect(); - } -}; - -#endif diff --git a/lib/framework/MqttSettingsService.cpp b/lib/framework/MqttSettingsService.cpp index 13e9d243d..4658250cf 100644 --- a/lib/framework/MqttSettingsService.cpp +++ b/lib/framework/MqttSettingsService.cpp @@ -33,8 +33,8 @@ MqttSettingsService::MqttSettingsService(AsyncWebServer * server, FS * fs, Secur , _retainedPassword(nullptr) , _reconfigureMqtt(false) , _disconnectedAt(0) - , _disconnectReason(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) - , _mqttClient() { + , _disconnectReason(espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED) + , _mqttClient(espMqttClientTypes::UseInternalTask::NO) { WiFi.onEvent(std::bind(&MqttSettingsService::WiFiEvent, this, _1, _2)); _mqttClient.onConnect(std::bind(&MqttSettingsService::onMqttConnect, this, _1)); _mqttClient.onDisconnect(std::bind(&MqttSettingsService::onMqttDisconnect, this, _1)); @@ -51,12 +51,13 @@ void MqttSettingsService::begin() { void MqttSettingsService::loop() { if (_reconfigureMqtt || (_disconnectedAt && (uint32_t)(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) { // reconfigure MQTT client - configureMqtt(); - - // clear the reconnection flags + _disconnectedAt = uuid::get_uptime(); + if (configureMqtt()) { + _disconnectedAt = 0; + } _reconfigureMqtt = false; - _disconnectedAt = 0; } + _mqttClient.loop(); } bool MqttSettingsService::isEnabled() { @@ -71,22 +72,27 @@ const char * MqttSettingsService::getClientId() { return _mqttClient.getClientId(); } -AsyncMqttClientDisconnectReason MqttSettingsService::getDisconnectReason() { +espMqttClientTypes::DisconnectReason MqttSettingsService::getDisconnectReason() { return _disconnectReason; } -AsyncMqttClient * MqttSettingsService::getMqttClient() { +espMqttClient * MqttSettingsService::getMqttClient() { return &_mqttClient; } void MqttSettingsService::onMqttConnect(bool sessionPresent) { + // _disconnectedAt = 0; + emsesp::EMSESP::mqtt_.on_connect(); // emsesp::EMSESP::logger().info("Connected to MQTT, %s", (sessionPresent) ? ("with persistent session") : ("without persistent session")); } -void MqttSettingsService::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { +void MqttSettingsService::onMqttDisconnect(espMqttClientTypes::DisconnectReason reason) { // emsesp::EMSESP::logger().info("Disconnected from MQTT reason: %d", (uint8_t)reason); _disconnectReason = reason; - _disconnectedAt = uuid::get_uptime(); + if (!_disconnectedAt) { + _disconnectedAt = uuid::get_uptime(); + } + emsesp::EMSESP::mqtt_.on_disconnect(reason); } void MqttSettingsService::onConfigUpdated() { @@ -121,7 +127,7 @@ void MqttSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { } } -void MqttSettingsService::configureMqtt() { +bool MqttSettingsService::configureMqtt() { // disconnect if connected _mqttClient.disconnect(); // only connect if WiFi is connected and MQTT is enabled @@ -137,11 +143,11 @@ void MqttSettingsService::configureMqtt() { _mqttClient.setClientId(retainCstr(_state.clientId.c_str(), &_retainedClientId)); _mqttClient.setKeepAlive(_state.keepAlive); _mqttClient.setCleanSession(_state.cleanSession); - _mqttClient.setMaxTopicLength(FACTORY_MQTT_MAX_TOPIC_LENGTH); // hardcode. We don't take this from the settings anymore. - _mqttClient.connect(); + return _mqttClient.connect(); // } else { // emsesp::EMSESP::logger().info("Error configuring MQTT client"); } + return false; } void MqttSettings::read(MqttSettings & settings, JsonObject & root) { diff --git a/lib/framework/MqttSettingsService.h b/lib/framework/MqttSettingsService.h index d013853b5..1db25c0d4 100644 --- a/lib/framework/MqttSettingsService.h +++ b/lib/framework/MqttSettingsService.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -104,13 +104,13 @@ class MqttSettingsService : public StatefulService { MqttSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager); ~MqttSettingsService(); - void begin(); - void loop(); - bool isEnabled(); - bool isConnected(); - const char * getClientId(); - AsyncMqttClientDisconnectReason getDisconnectReason(); - AsyncMqttClient * getMqttClient(); + void begin(); + void loop(); + bool isEnabled(); + bool isConnected(); + const char * getClientId(); + espMqttClientTypes::DisconnectReason getDisconnectReason(); + espMqttClient * getMqttClient(); protected: void onConfigUpdated(); @@ -120,7 +120,7 @@ class MqttSettingsService : public StatefulService { FSPersistence _fsPersistence; // Pointers to hold retained copies of the mqtt client connection strings. - // This is required as AsyncMqttClient holds references to the supplied connection strings. + // This is required as espMqttClient holds references to the supplied connection strings. char * _retainedHost; char * _retainedClientId; char * _retainedUsername; @@ -131,15 +131,15 @@ class MqttSettingsService : public StatefulService { unsigned long _disconnectedAt; // connection status - AsyncMqttClientDisconnectReason _disconnectReason; + espMqttClientTypes::DisconnectReason _disconnectReason; // the MQTT client instance - AsyncMqttClient _mqttClient; + espMqttClient _mqttClient; void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info); void onMqttConnect(bool sessionPresent); - void onMqttDisconnect(AsyncMqttClientDisconnectReason reason); - void configureMqtt(); + void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason); + bool configureMqtt(); }; #endif diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 97d74f6dc..1b04ce9f7 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -1706,13 +1706,13 @@ void EMSdevice::mqtt_ha_entity_config_create() { Mqtt::publish_ha_sensor_config(dv, name(), brand_to_char(), false, create_device_config); dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED); create_device_config = false; // only create the main config once - } - #ifndef EMSESP_STANDALONE - if (ESP.getFreeHeap() < (65 * 1024)) { - break; - } + // always create minimum one config + if (ESP.getMaxAllocHeap() < (6 * 1024) || (!emsesp::EMSESP::system_.PSram() && ESP.getFreeHeap() < (65 * 1024))) { + break; + } #endif + } } ha_config_done(!create_device_config); diff --git a/src/emsesp.cpp b/src/emsesp.cpp index b269eb749..8d8436637 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -1297,6 +1297,9 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) { LOG_ERROR("Last Tx write rejected by host"); txservice_.send_poll(); // close the bus } + } else if (tx_state == Telegram::Operation::TX_READ && length == 1) { + EMSbus::tx_state(Telegram::Operation::TX_READ); // reset Tx wait state + return; } else if (tx_state == Telegram::Operation::TX_READ) { // got a telegram with data in it. See if the src/dest matches that from the last one we sent and continue to process it uint8_t src = data[0]; diff --git a/src/mqtt.cpp b/src/mqtt.cpp index eaba3391f..3461403c5 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -23,7 +23,7 @@ namespace emsesp { -AsyncMqttClient * Mqtt::mqttClient_; +espMqttClient * Mqtt::mqttClient_; // static parameters we make global std::string Mqtt::mqtt_base_; @@ -461,30 +461,6 @@ void Mqtt::start() { // add the 'publish' command ('call system publish' in console or via API) Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish, FL_(publish_cmd)); - mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); }); - - mqttClient_->onDisconnect([this](AsyncMqttClientDisconnectReason reason) { - // only show the error once, not every 2 seconds - if (!connecting_ && first_connect_attempted_) { - return; - } - first_connect_attempted_ = true; - connecting_ = false; - if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) { - LOG_WARNING("MQTT disconnected: TCP"); - } else if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) { - LOG_WARNING("MQTT disconnected: Identifier Rejected"); - } else if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) { - LOG_WARNING("MQTT disconnected: Server unavailable"); - } else if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) { - LOG_WARNING("MQTT disconnected: Malformed credentials"); - } else if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) { - LOG_WARNING("MQTT disconnected: Not authorized"); - } else { - LOG_WARNING("MQTT disconnected: code %d", reason); - } - }); - // create last will topic with the base prefixed. It has to be static because asyncmqttclient destroys the reference static char will_topic[MQTT_TOPIC_MAX_SIZE]; if (!mqtt_base_.empty()) { @@ -493,9 +469,10 @@ void Mqtt::start() { mqttClient_->setWill(will_topic, 1, true, "offline"); // with qos 1, retain true - mqttClient_->onMessage([this](const char * topic, const char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { - on_message(topic, payload, len); // receiving mqtt - }); + mqttClient_->onMessage( + [this](const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total) { + on_message(topic, (const char *)payload, len); // receiving mqtt + }); mqttClient_->onPublish([this](uint16_t packetId) { on_publish(packetId); // publish @@ -555,6 +532,30 @@ bool Mqtt::get_publish_onchange(uint8_t device_type) { } return false; } +void Mqtt::on_disconnect(espMqttClientTypes::DisconnectReason reason) { + // only show the error once, not every 2 seconds + if (!connecting_) { + return; + } + connecting_ = false; + if (reason == espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED) { + LOG_WARNING("MQTT disconnected: TCP"); + } else if (reason == espMqttClientTypes::DisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION) { + LOG_WARNING("MQTT disconnected: Unacceptable protocol version"); + } else if (reason == espMqttClientTypes::DisconnectReason::MQTT_IDENTIFIER_REJECTED) { + LOG_WARNING("MQTT disconnected: Identifier Rejected"); + } else if (reason == espMqttClientTypes::DisconnectReason::MQTT_SERVER_UNAVAILABLE) { + LOG_WARNING("MQTT disconnected: Server unavailable"); + } else if (reason == espMqttClientTypes::DisconnectReason::MQTT_MALFORMED_CREDENTIALS) { + LOG_WARNING("MQTT disconnected: Malformed credentials"); + } else if (reason == espMqttClientTypes::DisconnectReason::MQTT_NOT_AUTHORIZED) { + LOG_WARNING("MQTT disconnected: Not authorized"); + } else if (reason == espMqttClientTypes::DisconnectReason::TLS_BAD_FINGERPRINT) { + LOG_WARNING("MQTT disconnected: Server fingerprint invalid"); + } else { + LOG_WARNING("MQTT disconnected: code %d", reason); + } +} // MQTT onConnect - when an MQTT connect is established void Mqtt::on_connect() { @@ -680,7 +681,7 @@ void Mqtt::queue_message(const uint8_t operation, const std::string & topic, con #ifndef EMSESP_STANDALONE // anything below 60MB available free heap is dangerously low, so we stop adding to prevent a crash // instead of doing a mqtt_messages_.pop_front() - if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES || ESP.getFreeHeap() < (60 * 1024)) { + if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES || (!emsesp::EMSESP::system_.PSram() && ESP.getFreeHeap() < (60 * 1024))) { LOG_WARNING("Queue overflow (queue count=%d, topic=%s)", mqtt_messages_.size(), topic.c_str()); mqtt_publish_fails_++; return; // don't add to top of queue @@ -857,7 +858,8 @@ void Mqtt::process_queue() { // else try and publish it // this is where the *real* publish happens - uint16_t packet_id = mqttClient_->publish(topic, mqtt_qos_, message->retain, message->payload.c_str(), message->payload.size(), false, mqtt_message.id_); + // uint16_t packet_id = mqttClient_->publish(topic, mqtt_qos_, message->retain, message->payload.c_str(), message->payload.size(), false, mqtt_message.id_); + uint16_t packet_id = mqttClient_->publish(topic, mqtt_qos_, message->retain, message->payload.c_str()); lasttopic_ = topic; lastpayload_ = message->payload; diff --git a/src/mqtt.h b/src/mqtt.h index c1be14df7..ad77fb53b 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -19,7 +19,7 @@ #ifndef EMSESP_MQTT_H_ #define EMSESP_MQTT_H_ -#include +#include #include "helpers.h" #include "system.h" @@ -76,6 +76,7 @@ class Mqtt { static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // fixed, not a user setting anymore static void on_connect(); + static void on_disconnect(espMqttClientTypes::DisconnectReason reason); static void subscribe(const uint8_t device_type, const std::string & topic, mqtt_sub_function_p cb); static void subscribe(const std::string & topic); @@ -129,7 +130,7 @@ class Mqtt { #endif } - static AsyncMqttClient * client() { + static espMqttClient * client() { return mqttClient_; } @@ -266,7 +267,7 @@ class Mqtt { private: static uuid::log::Logger logger_; - static AsyncMqttClient * mqttClient_; + static espMqttClient * mqttClient_; static uint32_t mqtt_message_id_; static constexpr uint32_t MQTT_PUBLISH_WAIT = 100; // delay in ms between sending publishes, to account for large payloads @@ -306,8 +307,6 @@ class Mqtt { uint32_t last_publish_heartbeat_ = 0; uint32_t last_publish_queue_ = 0; - bool first_connect_attempted_ = false; - static bool connecting_; static bool initialized_; static uint32_t mqtt_publish_fails_; From cbc9607b26b434c0c51452efe24aff660a1e142d Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Sat, 3 Jun 2023 17:46:26 +0200 Subject: [PATCH 002/163] apply changes/blocker for manage users --- .../framework/security/ManageUsersForm.tsx | 51 ++++++++++++++----- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/interface/src/framework/security/ManageUsersForm.tsx b/interface/src/framework/security/ManageUsersForm.tsx index e4aa4862a..72b9303ea 100644 --- a/interface/src/framework/security/ManageUsersForm.tsx +++ b/interface/src/framework/security/ManageUsersForm.tsx @@ -1,22 +1,24 @@ +import CancelIcon from '@mui/icons-material/Cancel'; import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; import DeleteIcon from '@mui/icons-material/Delete'; import EditIcon from '@mui/icons-material/Edit'; import PersonAddIcon from '@mui/icons-material/PersonAdd'; -import SaveIcon from '@mui/icons-material/Save'; import VpnKeyIcon from '@mui/icons-material/VpnKey'; +import WarningIcon from '@mui/icons-material/Warning'; import { Button, IconButton, Box } from '@mui/material'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; import { useContext, useState } from 'react'; +import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import GenerateToken from './GenerateToken'; import UserForm from './UserForm'; import type { FC } from 'react'; import type { SecuritySettings, User } from 'types'; import * as SecurityApi from 'api/security'; -import { ButtonRow, FormLoader, MessageBox, SectionContent } from 'components'; +import { ButtonRow, FormLoader, MessageBox, SectionContent, BlockNavigation } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; import { useRest } from 'utils'; @@ -30,9 +32,11 @@ const ManageUsersForm: FC = () => { const [user, setUser] = useState(); const [creating, setCreating] = useState(false); + const [changed, setChanged] = useState(0); const [generatingToken, setGeneratingToken] = useState(); const authenticatedContext = useContext(AuthenticatedContext); + const blocker = useBlocker(changed !== 0); const { LL } = useI18nContext(); const table_theme = useTheme({ @@ -85,6 +89,7 @@ const ManageUsersForm: FC = () => { const removeUser = (toRemove: User) => { const users = data.users.filter((u) => u.username !== toRemove.username); setData({ ...data, users }); + setChanged(changed + 1); }; const createUser = () => { @@ -110,6 +115,7 @@ const ManageUsersForm: FC = () => { const users = [...data.users.filter((u) => u.username !== user.username), user]; setData({ ...data, users }); setUser(undefined); + setChanged(changed + 1); } }; @@ -124,6 +130,12 @@ const ManageUsersForm: FC = () => { const onSubmit = async () => { await saveData(); await authenticatedContext.refresh(); + setChanged(0); + }; + + const onCancelSubmit = () => { + loadData(); + setChanged(0); }; const user_table = data.users.map((u) => ({ ...u, id: u.username })); @@ -172,16 +184,30 @@ const ManageUsersForm: FC = () => { - + {changed !== 0 && ( + + + + + )} @@ -208,6 +234,7 @@ const ManageUsersForm: FC = () => { return ( + {blocker ? : null} {content()} ); From 2e63d602732b2c87acb06704ee58069a6cae3468 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 4 Jun 2023 15:22:09 +0200 Subject: [PATCH 003/163] alova implementation, testing --- interface/.env.development | 2 + interface/package.json | 4 +- interface/public/css/roboto.css | 2 +- interface/src/api/endpoints.ts | 60 +++++++- interface/src/api/unpack.ts | 3 - interface/src/project/DashboardDevices.tsx | 135 ++++++++++++------ .../src/project/DashboardDevicesDialog.tsx | 32 ++++- interface/src/project/api.ts | 45 +++--- interface/src/project/types.ts | 2 + interface/yarn.lock | 116 ++++++++------- mock-api/server.js | 23 ++- 11 files changed, 280 insertions(+), 144 deletions(-) create mode 100644 interface/.env.development diff --git a/interface/.env.development b/interface/.env.development new file mode 100644 index 000000000..19cc804fe --- /dev/null +++ b/interface/.env.development @@ -0,0 +1,2 @@ +VITE_ALOVA_TIPS=0 +REACT_APP_ALOVA_TIPS=0 \ No newline at end of file diff --git a/interface/package.json b/interface/package.json index 0898b2c16..ab3303400 100644 --- a/interface/package.json +++ b/interface/package.json @@ -19,6 +19,7 @@ "lint": "eslint . --cache --fix" }, "dependencies": { + "@alova/adapter-xhr": "^1.0.0", "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.11.16", @@ -29,6 +30,7 @@ "@types/react": "^18.2.8", "@types/react-dom": "^18.2.4", "@types/react-router-dom": "^5.3.3", + "alova": "^2.5.4", "async-validator": "^4.2.5", "axios": "^1.4.0", "history": "^5.3.0", @@ -47,7 +49,7 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.59.8", "@typescript-eslint/parser": "^5.59.8", - "@vitejs/plugin-react-swc": "^3.3.1", + "@vitejs/plugin-react-swc": "^3.3.2", "eslint": "^8.42.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.0.0", diff --git a/interface/public/css/roboto.css b/interface/public/css/roboto.css index dd5649d0c..0c05736a7 100644 --- a/interface/public/css/roboto.css +++ b/interface/public/css/roboto.css @@ -8,7 +8,7 @@ font-style: normal; font-weight: 400; /* src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2) format('woff2'); */ - src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.off2) format('woff2'); + src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+011E-011F, U+0130-0131, U+0141-0144, U+0152-0153, U+015A-015B, U+015E-015F, U+0179-017C, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index 7c003693b..19ce5b290 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -1,12 +1,18 @@ +import { xhrRequestAdapter } from '@alova/adapter-xhr'; +import { createAlova, useRequest } from 'alova'; +import GlobalFetch from 'alova/GlobalFetch'; +import ReactHook from 'alova/react'; import axios from 'axios'; import { unpack } from './unpack'; import type { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios'; export const WS_BASE_URL = '/ws/'; -export const API_BASE_URL = '/rest/'; export const ES_BASE_URL = '/es/'; -export const EMSESP_API_BASE_URL = '/api/'; + +export const REST_BASE_URL = '/rest/'; +export const API_BASE_URL = '/api/'; + export const ACCESS_TOKEN = 'access_token'; const location = window.location; @@ -14,8 +20,47 @@ const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; export const WEB_SOCKET_ROOT = webProtocol + '//' + location.host + WS_BASE_URL; export const EVENT_SOURCE_ROOT = location.protocol + '//' + location.host + ES_BASE_URL; +export const alovaInstance = createAlova({ + baseURL: '/rest/', + statesHook: ReactHook, + requestAdapter: xhrRequestAdapter(), + // requestAdapter: GlobalFetch(), + beforeRequest(method) { + // TODO check if bearer works + if (localStorage.getItem(ACCESS_TOKEN)) { + method.config.headers.token = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN); + } + }, + responsed: (response) => response.data + + // TODO add error handling for Push? + + // return JSON.stringify(response.data); + + // responded: { + // // When using the GlobalFetch request adapter, the first parameter receives the Response object + // // The second parameter is the method instance of the current request, you can use it to synchronize the configuration information before and after the request + // onSuccess: async (response, method) => { + // if (response.status >= 400) { + // throw new Error(response.statusText); + // } + // console.log('response', response); + // const json = await response.json(); + // // The parsed response data will be passed to the transformData hook function of the method instance, and these functions will be explained later + // return json; + // }, + + // // Interceptor for request failure + // // This interceptor will be entered when the request is wrong. + // // The second parameter is the method instance of the current request, you can use it to synchronize the configuration information before and after the request + // onError: (error, method) => { + // alert(error.message); + // } + // } +}); + export const AXIOS = axios.create({ - baseURL: API_BASE_URL, + baseURL: REST_BASE_URL, headers: { 'Content-Type': 'application/json' }, @@ -35,7 +80,7 @@ export const AXIOS = axios.create({ }); export const AXIOS_API = axios.create({ - baseURL: EMSESP_API_BASE_URL, + baseURL: API_BASE_URL, headers: { 'Content-Type': 'application/json' }, @@ -55,7 +100,7 @@ export const AXIOS_API = axios.create({ }); export const AXIOS_BIN = axios.create({ - baseURL: API_BASE_URL, + baseURL: REST_BASE_URL, headers: { 'Content-Type': 'application/json' }, @@ -73,10 +118,11 @@ export const AXIOS_BIN = axios.create({ return JSON.stringify(data); } ], - // transformResponse: [(data) => decode(data)] - transformResponse: [(data) => unpack(data)] // new using msgpackr + transformResponse: [(data) => unpack(data)] }); +// TODO replace with alova +// TODO see https://alova.js.org/next-step/download-upload-progress export interface FileUploadConfig { cancelToken?: CancelToken; onUploadProgress?: (progressEvent: AxiosProgressEvent) => void; diff --git a/interface/src/api/unpack.ts b/interface/src/api/unpack.ts index d28619786..a25078d5d 100644 --- a/interface/src/api/unpack.ts +++ b/interface/src/api/unpack.ts @@ -968,7 +968,6 @@ currentExtensions[0x69] = (data) => { if (!referenceMap) referenceMap = new Map(); const token = src[position]; let target; - // TODO: handle Maps, Sets, and other types that can cycle; this is complicated, because you potentially need to read // ahead past references to record structure definitions if ((token >= 0x90 && token < 0xa0) || token == 0xdc || token == 0xdd) target = []; else target = {}; @@ -1041,7 +1040,6 @@ currentExtensions[0xff] = (data) => { ((data[3] & 0x3) * 0x100000000 + data[4] * 0x1000000 + (data[5] << 16) + (data[6] << 8) + data[7]) * 1000 ); else if (data.length == 12) - // TODO: Implement support for negative return new Date( ((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 + ((data[4] & 0x80 ? -0x1000000000000 : 0) + @@ -1070,7 +1068,6 @@ function saveState(callback) { const savedReferenceMap = referenceMap; const savedBundledStrings = bundledStrings; - // TODO: We may need to revisit this if we do more external calls to user code (since it could be slow) const savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed const savedStructures = currentStructures; const savedStructuresContents = currentStructures.slice(0, currentStructures.length); diff --git a/interface/src/project/DashboardDevices.tsx b/interface/src/project/DashboardDevices.tsx index b49af0d35..1b7dd2013 100644 --- a/interface/src/project/DashboardDevices.tsx +++ b/interface/src/project/DashboardDevices.tsx @@ -30,6 +30,7 @@ import { useRowSelect } from '@table-library/react-table-library/select'; import { useSort, SortToggleType } from '@table-library/react-table-library/sort'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; +import { useRequest } from 'alova'; import { useState, useContext, useEffect, useCallback, useLayoutEffect } from 'react'; import { IconContext } from 'react-icons'; @@ -54,17 +55,45 @@ const DashboardDevices: FC = () => { const [size, setSize] = useState([0, 0]); const { me } = useContext(AuthenticatedContext); const { LL } = useI18nContext(); - const [deviceData, setDeviceData] = useState({ data: [] }); const [selectedDeviceValue, setSelectedDeviceValue] = useState(); const [onlyFav, setOnlyFav] = useState(false); const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false); const [showDeviceInfo, setShowDeviceInfo] = useState(false); const [selectedDevice, setSelectedDevice] = useState(); - const [coreData, setCoreData] = useState({ - connected: true, - devices: [] + + // TODO remove + // const [deviceData, setDeviceData] = useState({ data: [] }); + // const [coreData, setCoreData] = useState({ + // connected: true, + // devices: [] + // }); + + const { data: coreData, send: readCoreData } = useRequest(() => EMSESP.readCoreData(), { + initialData: { + connected: true, + devices: [] + }, + force: true, + immediate: false }); + // TODO prevent firing when page is loaded + const { data: deviceData, send: readDeviceData } = useRequest((id) => EMSESP.readDeviceData(id), { + initialData: { + data: [] + }, + force: true, + immediate: false + }); + + // TODO prevent firing when page is loaded + const { loading: submitting, send: writeDeviceValue } = useRequest( + (id: number, deviceValue: DeviceValue) => EMSESP.writeDeviceValue(id, deviceValue), + { + immediate: false + } + ); + useLayoutEffect(() => { function updateSize() { setSize([window.innerWidth, window.innerHeight]); @@ -212,19 +241,21 @@ const DashboardDevices: FC = () => { } ); - const fetchDeviceData = async (id: number) => { - try { - setDeviceData((await EMSESP.readDeviceData({ id })).data); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } - }; + // TODO remove + // const fetchDeviceData = async (id: number) => { + // try { + // setDeviceData((await EMSESP.readDeviceData({ id })).data); + // } catch (error) { + // toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); + // } + // }; - function onSelectChange(action: any, state: any) { - setDeviceData({ data: [] }); + async function onSelectChange(action: any, state: any) { + // TODO check if still needed + // setDeviceData({ data: [] }); setSelectedDevice(state.id); if (action.type === 'ADD_BY_ID_EXCLUSIVELY') { - void fetchDeviceData(state.id); + await readDeviceData(state.id); } } @@ -257,27 +288,29 @@ const DashboardDevices: FC = () => { }; }, [escFunction]); - const fetchCoreData = useCallback(async () => { - try { - setSelectedDevice(undefined); - setCoreData((await EMSESP.readCoreData()).data); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } - }, [LL]); + // TODO remove + // const fetchCoreData = useCallback(async () => { + // try { + // setSelectedDevice(undefined); + // setCoreData((await EMSESP.readCoreData()).data); + // } catch (error) { + // toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); + // } + // }, [LL]); - useEffect(() => { - void fetchCoreData(); - }, [fetchCoreData]); + // TODO remove + // useEffect(() => { + // void fetchCoreData2(); + // }, [fetchCoreData2]); const refreshData = () => { if (deviceValueDialogOpen) { return; } if (selectedDevice) { - void fetchDeviceData(selectedDevice); + void readDeviceData(selectedDevice); } else { - void fetchCoreData(); + void readCoreData(); } }; @@ -348,25 +381,32 @@ const DashboardDevices: FC = () => { const deviceValueDialogSave = async (dv: DeviceValue) => { const selectedDeviceID = Number(device_select.state.id); - try { - const response = await EMSESP.writeDeviceValue({ - id: selectedDeviceID, - devicevalue: dv - }); - if (response.status === 204) { - toast.error(LL.WRITE_CMD_FAILED()); - } else if (response.status === 403) { - toast.error(LL.ACCESS_DENIED()); - } else { - toast.success(LL.WRITE_CMD_SENT()); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } finally { - setDeviceValueDialogOpen(false); - await fetchDeviceData(selectedDeviceID); - setSelectedDeviceValue(undefined); - } + // TODO For all Push, do error handling? + const response = await writeDeviceValue(selectedDeviceID, dv); + console.log(response); + setDeviceValueDialogOpen(false); + await readDeviceData(selectedDeviceID); + setSelectedDeviceValue(undefined); + + // try { + // const response = await EMSESP.writeDeviceValue({ + // id: selectedDeviceID, + // devicevalue: dv + // }); + // if (response.status === 204) { + // toast.error(LL.WRITE_CMD_FAILED()); + // } else if (response.status === 403) { + // toast.error(LL.ACCESS_DENIED()); + // } else { + // toast.success(LL.WRITE_CMD_SENT()); + // } + // } catch (error) { + // toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); + // } finally { + // setDeviceValueDialogOpen(false); + // await readDeviceData(selectedDeviceID); + // setSelectedDeviceValue(undefined); + // } }; const renderDeviceDetails = () => { @@ -457,7 +497,7 @@ const DashboardDevices: FC = () => { }; const renderDeviceData = () => { - if (!selectedDevice) { + if (!selectedDevice || deviceData.data === undefined) { return; } @@ -612,6 +652,7 @@ const DashboardDevices: FC = () => { !hasMask(selectedDeviceValue.id, DeviceEntityMask.DV_READONLY) } validator={deviceValueItemValidation(selectedDeviceValue)} + progress={submitting} /> )} diff --git a/interface/src/project/DashboardDevicesDialog.tsx b/interface/src/project/DashboardDevicesDialog.tsx index f2ff2bc33..bc725f0a6 100644 --- a/interface/src/project/DashboardDevicesDialog.tsx +++ b/interface/src/project/DashboardDevicesDialog.tsx @@ -13,8 +13,10 @@ import { FormHelperText, Grid, Box, - Typography + Typography, + CircularProgress } from '@mui/material'; +import { green } from '@mui/material/colors'; import { useState, useEffect } from 'react'; import { DeviceValueUOM, DeviceValueUOM_s } from './types'; @@ -22,7 +24,7 @@ import type { DeviceValue } from './types'; import type Schema from 'async-validator'; import type { ValidateFieldsError } from 'async-validator'; -import { ValidatedTextField } from 'components'; +import { ButtonRow, ValidatedTextField } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import { updateValue } from 'utils'; @@ -35,6 +37,7 @@ type DashboardDevicesDialogProps = { selectedItem: DeviceValue; writeable: boolean; validator: Schema; + progress: boolean; }; const DashboarDevicesDialog = ({ @@ -43,7 +46,8 @@ const DashboarDevicesDialog = ({ onSave, selectedItem, writeable, - validator + validator, + progress }: DashboardDevicesDialogProps) => { const { LL } = useI18nContext(); const [editItem, setEditItem] = useState(selectedItem); @@ -184,14 +188,32 @@ const DashboarDevicesDialog = ({ {writeable ? ( - <> + - + {progress && ( + + )} + ) : ( diff --git a/interface/src/project/api.ts b/interface/src/project/api.ts index 2b7515142..b925027c1 100644 --- a/interface/src/project/api.ts +++ b/interface/src/project/api.ts @@ -1,86 +1,63 @@ import type { - BoardProfile, - BoardProfileName, APIcall, Settings, Status, CoreData, Devices, - DeviceData, DeviceEntity, - UniqueID, - CustomEntities, WriteTemperatureSensor, WriteAnalogSensor, SensorData, Schedule, - Entities + Entities, + DeviceData } from './types'; import type { AxiosPromise } from 'axios'; -import { AXIOS, AXIOS_API, AXIOS_BIN, alovaInstance } from 'api/endpoints'; +import { AXIOS, AXIOS_API, alovaInstance } from 'api/endpoints'; +// DashboardDevices export const readCoreData = () => alovaInstance.Get(`/coreData`); - export const readDeviceData = (id: number) => alovaInstance.Get('/deviceData', { params: { id }, responseType: 'arraybuffer' // uses msgpack }); - export const writeDeviceValue = (data: any) => alovaInstance.Post('/writeDeviceValue', data); // SettingsApplication export const readSettings = () => alovaInstance.Get('/settings'); export const writeSettings = (data: any) => alovaInstance.Post('/settings', data); +export const getBoardProfile = (boardProfile: string) => + alovaInstance.Get('/boardProfile', { + params: { boardProfile } + }); +export const restart = () => alovaInstance.Post('/restart'); -// -// TODO change below to use alova -// +// SettingsCustomization +export const readDeviceEntities = (id: number) => + alovaInstance.Get('/deviceEntities', { + params: { id }, + responseType: 'arraybuffer', + transformData(rawData: any) { + return rawData.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })); + } + }); +export const readDevices = () => alovaInstance.Get('/devices'); +export const resetCustomizations = () => alovaInstance.Post('/resetCustomizations'); +export const writeCustomEntities = (data: any) => alovaInstance.Post('/customEntities', data); -export function restart(): AxiosPromise { - return AXIOS.post('/restart'); -} +// DashboardSensors +export const readSensorData = () => alovaInstance.Get('/sensorData'); +export const writeTemperatureSensor = (ts: WriteTemperatureSensor) => alovaInstance.Post('/writeTemperatureSensor', ts); +export const writeAnalogSensor = (as: WriteAnalogSensor) => alovaInstance.Post('/writeAnalogSensor', as); -// TODO change to GET -export function getBoardProfile(boardProfile: BoardProfileName): AxiosPromise { - return AXIOS.post('/boardProfile', boardProfile); -} -// TODO change to GET -export function readDeviceEntities(unique_id: UniqueID): AxiosPromise { - return AXIOS_BIN.post('/deviceEntities', unique_id); -} +// TODO think about naming, get... and not get etc... -export function readStatus(): AxiosPromise { - return AXIOS.get('/status'); -} +// DashboardStatus +export const readStatus = () => alovaInstance.Get('/status'); +export const scanDevices = () => alovaInstance.Post('/scanDevices'); -export function readDevices(): AxiosPromise { - return AXIOS.get('/devices'); -} - -export function scanDevices(): AxiosPromise { - return AXIOS.post('/scanDevices'); // call command -} - -export function readSensorData(): AxiosPromise { - return AXIOS.get('/sensorData'); -} - -export function writeCustomEntities(customEntities: CustomEntities): AxiosPromise { - return AXIOS.post('/customEntities', customEntities); -} - -export function writeTemperatureSensor(ts: WriteTemperatureSensor): AxiosPromise { - return AXIOS.post('/writeTemperatureSensor', ts); -} - -export function writeAnalogSensor(as: WriteAnalogSensor): AxiosPromise { - return AXIOS.post('/writeAnalogSensor', as); -} - -export function resetCustomizations(): AxiosPromise { - return AXIOS.post('/resetCustomizations'); // command -} +// ALOVA goes here.... export function API(apiCall: APIcall): AxiosPromise { return AXIOS_API.post('/', apiCall); // command diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index 5f80d7bd2..a50b0281c 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -131,8 +131,6 @@ export interface DeviceValue { m?: number; // min, optional x?: number; // max, optional } - -// TODO can be refacvtored to DeviceValue[]? export interface DeviceData { data: DeviceValue[]; } @@ -152,16 +150,6 @@ export interface DeviceEntity { o_ma?: number; // original max value } -export interface CustomEntities { - id: number; - entity_ids: string[]; -} - -// TODO can be removed? -export interface UniqueID { - id: number; -} - export enum DeviceValueUOM { NONE = 0, DEGREES, @@ -258,10 +246,6 @@ export const BOARD_PROFILES: BoardProfiles = { S3MINI: 'Liligo S3' }; -export interface BoardProfileName { - board_profile: string; -} - export interface BoardProfile { board_profile: string; led_gpio: number; diff --git a/interface/src/utils/binding.ts b/interface/src/utils/binding.ts index 190584526..74a755a52 100644 --- a/interface/src/utils/binding.ts +++ b/interface/src/utils/binding.ts @@ -23,14 +23,13 @@ export const updateValue = }; export const updateValueDirty = - (origData: any, dirtyFlags: any, setDirtyFlags: any, updateEntity: any) => - // (origData: any, dirtyFlags: any, setDirtyFlags: any, updateEntity: UpdateEntity) => + (origData: any, dirtyFlags: any, setDirtyFlags: any, updateDataValue: any) => (event: React.ChangeEvent) => { const updated_value = extractEventValue(event); const name = event.target.name; // TODO not sure how this is even working!! - updateEntity((prevState) => ({ + updateDataValue((prevState) => ({ ...prevState, [name]: updated_value })); @@ -38,9 +37,9 @@ export const updateValueDirty = const arr: string[] = dirtyFlags; // TODO remove comments - // console.log('updating ' + name + ' to ' + updated_value); // TODO remove - // console.log('dirtyFlags:', dirtyFlags); // TODO remove - // console.log('binding.ts origData:', origData); // TODO remove + // console.log('updating ' + name + ' to ' + updated_value); + // console.log('dirtyFlags:', dirtyFlags); + // console.log('binding.ts origData:', origData); if (origData[name] !== updated_value) { if (!arr.includes(name)) { diff --git a/interface/src/utils/endpoints.ts b/interface/src/utils/endpoints.ts index 98b9c55ac..d0de377fe 100644 --- a/interface/src/utils/endpoints.ts +++ b/interface/src/utils/endpoints.ts @@ -1,4 +1,4 @@ -// TODO can be removed! +// TODO extractErrorMessage function can be removed! export const extractErrorMessage = (error: any, defaultMessage: string) => { if (error.request) { return defaultMessage + ' (' + error.request.status + ': ' + error.request.statusText + ')'; diff --git a/interface/src/utils/index.ts b/interface/src/utils/index.ts index 245acb6c7..c17d6949b 100644 --- a/interface/src/utils/index.ts +++ b/interface/src/utils/index.ts @@ -4,6 +4,6 @@ export * from './route'; export * from './submit'; export * from './time'; export * from './useRest'; -// TODO remove +// TODO remove useRest2 export * from './useRest2'; export * from './props'; diff --git a/interface/src/utils/useRest.ts b/interface/src/utils/useRest.ts index 9a5378802..1d71f1a07 100644 --- a/interface/src/utils/useRest.ts +++ b/interface/src/utils/useRest.ts @@ -52,8 +52,8 @@ export const useRest = ({ read, update }: RestRequestOptions) => { const response = await update(toSave); setOrigData(response.data); setData(response.data); - if (response.status === 202) { - setRestartNeeded(true); + if (response.status === 205) { + setRestartNeeded(true); // reboot required } else { toast.success(LL.UPDATED_OF(LL.SETTINGS())); } diff --git a/interface/src/utils/useRest2.ts b/interface/src/utils/useRest2.ts index 29cae6065..7e8688da0 100644 --- a/interface/src/utils/useRest2.ts +++ b/interface/src/utils/useRest2.ts @@ -7,14 +7,15 @@ import type { AlovaXHRRequestConfig, AlovaXHRResponse, AlovaXHRResponseHeaders } import { useI18nContext } from 'i18n/i18n-react'; -export interface RestRequestOptions { +export interface RestRequestOptions2 { read: () => Method, AlovaXHRResponseHeaders>; update: ( value: D ) => Method, AlovaXHRResponseHeaders>; } -export const useRest2 = ({ read, update }: RestRequestOptions) => { +// TODO rename back to useRest +export const useRest2 = ({ read, update }: RestRequestOptions2) => { const { LL } = useI18nContext(); const [errorMessage, setErrorMessage] = useState(); @@ -32,12 +33,8 @@ export const useRest2 = ({ read, update }: RestRequestOptions) => { onSuccess: onWriteSuccess } = useRequest((newData: D) => update(newData), { immediate: false }); - const setData = (new_data: D) => { - console.log('SET DATA'); // TODO remove console - console.log('new_data:', new_data); // TODO remove console - updateData({ - data: new_data - }); + const updateDataValue = (new_data: D) => { + updateData({ data: new_data }); }; onWriteSuccess(() => { @@ -46,7 +43,7 @@ export const useRest2 = ({ read, update }: RestRequestOptions) => { }); onReadComplete((event) => { - setOrigData(event.data); // make a copy + setOrigData(event.data); }); const loadData = async () => { @@ -62,7 +59,6 @@ export const useRest2 = ({ read, update }: RestRequestOptions) => { if (!data) { return; } - console.log('SAVE DATA'); // TODO remove console setRestartNeeded(false); setErrorMessage(undefined); await writeData(data).catch((error) => { @@ -79,7 +75,7 @@ export const useRest2 = ({ read, update }: RestRequestOptions) => { loadData, saveData, saving, - setData, + updateDataValue, data, origData, dirtyFlags, diff --git a/interface/yarn.lock b/interface/yarn.lock index c024a8686..ca56b2b5d 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -767,14 +767,14 @@ __metadata: languageName: node linkType: hard -"@mui/material@npm:^5.13.4": - version: 5.13.4 - resolution: "@mui/material@npm:5.13.4" +"@mui/material@npm:^5.13.5": + version: 5.13.5 + resolution: "@mui/material@npm:5.13.5" dependencies: "@babel/runtime": ^7.21.0 "@mui/base": 5.0.0-beta.4 "@mui/core-downloads-tracker": ^5.13.4 - "@mui/system": ^5.13.2 + "@mui/system": ^5.13.5 "@mui/types": ^7.2.4 "@mui/utils": ^5.13.1 "@types/react-transition-group": ^4.4.6 @@ -796,7 +796,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 1f0b26c74e06fb6849af7398b8bc1d211d0af146822c998a9c55c31552ffcf426834eac56dc909c15bae8a3b957df91f32d9c1b6cb1300e063d184bfb530512f + checksum: 325a99809efa041aa615b144bfde634ad7def22102b6267769e140aee0cdeaa3f611b79d4e3dc85a832b1f120da19b3e57933fb17487c7d5f67d7c2bbe7f3254 languageName: node linkType: hard @@ -838,9 +838,9 @@ __metadata: languageName: node linkType: hard -"@mui/system@npm:^5.13.2": - version: 5.13.2 - resolution: "@mui/system@npm:5.13.2" +"@mui/system@npm:^5.13.5": + version: 5.13.5 + resolution: "@mui/system@npm:5.13.5" dependencies: "@babel/runtime": ^7.21.0 "@mui/private-theming": ^5.13.1 @@ -862,7 +862,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 34ebb580e5dd83123cc397c3fd54c3430f66ab715eb1538cf2510821d88249814294f79ea046081b61249643383fd9c23552d9791322855fa2099bf8f1c4e51b + checksum: 0bef4c575d9c54e7d93ad14009aecf4a0f18440398a14a6523f3fcea665913ceb344dc496d02b56a3ef53e4dac828f8e7ca5a55fb60448a76363622159d18379 languageName: node linkType: hard @@ -1288,10 +1288,10 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20.3.0": - version: 20.3.0 - resolution: "@types/node@npm:20.3.0" - checksum: f717d92c29c4877db394b604771b3734216f013312f93252f72c2018aabe8083be905fbcf0644c859938c8183b6e0245faaeaab94c9e78268b87a449bc6ef4aa +"@types/node@npm:^20.3.1": + version: 20.3.1 + resolution: "@types/node@npm:20.3.1" + checksum: 7e8a6f5d6fc1ad3778f038f5f8df570741459984280fd2e9539af32620d93438c955fd1b90d00f9cc438cd132ec04d7669ada9e32502336e78713a3ad9b51d10 languageName: node linkType: hard @@ -1309,12 +1309,12 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.2.4": - version: 18.2.4 - resolution: "@types/react-dom@npm:18.2.4" +"@types/react-dom@npm:^18.2.5": + version: 18.2.5 + resolution: "@types/react-dom@npm:18.2.5" dependencies: "@types/react": "*" - checksum: dfeaabb4268d39bdd5addc6c0b7099d5c57a364e70f1087b7c3ee189374312dc65201abfd3d87fee0de11d27c225678ce39c22d14b3035cde5792678704c27b5 + checksum: 7f438f695c91735ff16e6465573a4378fabad6709d99dd08bee4932967ca7e4ccc26dc248f4b88569529885bbca9b1aca0075bee7b833bfa932a558c49d2a066 languageName: node linkType: hard @@ -1368,14 +1368,14 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.2.11": - version: 18.2.11 - resolution: "@types/react@npm:18.2.11" +"@types/react@npm:^18.2.12": + version: 18.2.12 + resolution: "@types/react@npm:18.2.12" dependencies: "@types/prop-types": "*" "@types/scheduler": "*" csstype: ^3.0.2 - checksum: 9360d7be13195050eb16598796056123ee9d30470e7073a914300fed9282585d0dd0638bb5ff65843e308c3ac213d25b3388e8186f3134490c758f18f11f3bd8 + checksum: dbaefc7732f77cd0c2bdf6e704ab4ee0f611540777c0b9506d972f165144b6fd6883a29dad61f6965c974c040264852aa8def1e3866b5775b3965a4e67bf92f9 languageName: node linkType: hard @@ -1393,14 +1393,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/eslint-plugin@npm:5.59.9" +"@typescript-eslint/eslint-plugin@npm:^5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/eslint-plugin@npm:5.59.11" dependencies: "@eslint-community/regexpp": ^4.4.0 - "@typescript-eslint/scope-manager": 5.59.9 - "@typescript-eslint/type-utils": 5.59.9 - "@typescript-eslint/utils": 5.59.9 + "@typescript-eslint/scope-manager": 5.59.11 + "@typescript-eslint/type-utils": 5.59.11 + "@typescript-eslint/utils": 5.59.11 debug: ^4.3.4 grapheme-splitter: ^1.0.4 ignore: ^5.2.0 @@ -1413,43 +1413,43 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 4bb9981bcc009c044ffd6b64288309480df2b6c9cdf6b345987e4b565d0973d1d98b7209f6b46b92880735d788f564e17553641087aa59f67990c84526622a27 + checksum: 77f29b8bde5d11a5f222b21e5a3546e86f79a07efd8352b3ebcde67fd2c568ba7c6946fa992dbb9d7d80b6f2455017266fdede1ed134bd181b03f98d81fe71c3 languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/parser@npm:5.59.9" +"@typescript-eslint/parser@npm:^5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/parser@npm:5.59.11" dependencies: - "@typescript-eslint/scope-manager": 5.59.9 - "@typescript-eslint/types": 5.59.9 - "@typescript-eslint/typescript-estree": 5.59.9 + "@typescript-eslint/scope-manager": 5.59.11 + "@typescript-eslint/types": 5.59.11 + "@typescript-eslint/typescript-estree": 5.59.11 debug: ^4.3.4 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: af0e041e8a541734ff237ec0eac47e355c2f78dd2b0db4eb4ab0c10ba1b6d5d70f84ddc16f856bc72c4cacd53ef04b5f4948baffb5c8cb2d9a0ffd83a8fbc547 + checksum: 3deea3a9b694ea97e315881ba37d0a90d7f37f0acbb5990270f44c79db9fc3d5675df856f8d1fb7d92c8d38dc63226a42402d57633513981ee526c06e6e8f3c3 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/scope-manager@npm:5.59.9" +"@typescript-eslint/scope-manager@npm:5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/scope-manager@npm:5.59.11" dependencies: - "@typescript-eslint/types": 5.59.9 - "@typescript-eslint/visitor-keys": 5.59.9 - checksum: 41622fd270e5b8574347ed5dd020bbb9752d85e6f40df180e944c1110d9bd2227a949067feb23dd4117dd2be0623c05a47bc363abe605c96deb295753f6dd080 + "@typescript-eslint/types": 5.59.11 + "@typescript-eslint/visitor-keys": 5.59.11 + checksum: e0173e9beb44408ba3e9a1eb173dd83f5c0281af09f936d03f635b7fac41abe1f829a88d0cba134f250dfa4ce527d8cfa32c96e7ada5de876a7aa8bcc933db3b languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/type-utils@npm:5.59.9" +"@typescript-eslint/type-utils@npm:5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/type-utils@npm:5.59.11" dependencies: - "@typescript-eslint/typescript-estree": 5.59.9 - "@typescript-eslint/utils": 5.59.9 + "@typescript-eslint/typescript-estree": 5.59.11 + "@typescript-eslint/utils": 5.59.11 debug: ^4.3.4 tsutils: ^3.21.0 peerDependencies: @@ -1457,23 +1457,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: c3a9773d2b81350923025933623e1572538f79bf119b40bed17389eda11632f6d364a49b385aa6d915d85f7c3d45376085cc55263d865dbc2b753598bba6473b + checksum: 9675cf17970bbf01814d8c8a94aa076fb7c5f5ab8c405800f972a68a72748db07070a526aa2c94d30b2e5ba43bdbef3929a588182bffc3387288b24223574f52 languageName: node linkType: hard -"@typescript-eslint/types@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/types@npm:5.59.9" - checksum: 951046891bcc9fa27d72a5489b496291e44cedcff204d3ce6c10c8916fc5e255332738efd4d7555200a55b49ff4ba1204e186960d216d51fea89fe92a982180e +"@typescript-eslint/types@npm:5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/types@npm:5.59.11" + checksum: 8d007c6c66323582d526429d059c477dcb522b904938a6aaf29804027e6f427533fabe9eb3c987491df74d4288dab238c8c948886f03244a1d908f2985631368 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/typescript-estree@npm:5.59.9" +"@typescript-eslint/typescript-estree@npm:5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/typescript-estree@npm:5.59.11" dependencies: - "@typescript-eslint/types": 5.59.9 - "@typescript-eslint/visitor-keys": 5.59.9 + "@typescript-eslint/types": 5.59.11 + "@typescript-eslint/visitor-keys": 5.59.11 debug: ^4.3.4 globby: ^11.1.0 is-glob: ^4.0.3 @@ -1482,35 +1482,35 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 2f3d8df6d454fbc52d305abfe8447bff8e8d63294ce47e4679c920f647643f5d15a1f693caf74f4fabece12d5ba27ebdb156d507b16fbd2751fc01ba6c4df3c8 + checksum: 4306317ad65668a05d9ec7b280eab94dd7e0d15394db633864bad5d9633fbb9dc516e6cd9f8a0fc2f7a4fc0d53658e5dac61cbee3e070b0b7ac99bdbb0bb45ef languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/utils@npm:5.59.9" +"@typescript-eslint/utils@npm:5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/utils@npm:5.59.11" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@types/json-schema": ^7.0.9 "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.59.9 - "@typescript-eslint/types": 5.59.9 - "@typescript-eslint/typescript-estree": 5.59.9 + "@typescript-eslint/scope-manager": 5.59.11 + "@typescript-eslint/types": 5.59.11 + "@typescript-eslint/typescript-estree": 5.59.11 eslint-scope: ^5.1.1 semver: ^7.3.7 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: b8a04a83c121faa3e36abb2b6113f2e0ec5cf86884d0cb8619bfc50f7442341ee17e4495d69f8abeb6edad9e0347de8382ea1708a5fd6da1e4c80b7b8215c6ab + checksum: c1927950d97afcf3cfc4c3901bc1932caa32bfc533f950f7dab420c478097d3d8daf030c27489e0d49ecdc1f87c52c782833cc505b245ab2a3094c707b0d776e languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/visitor-keys@npm:5.59.9" +"@typescript-eslint/visitor-keys@npm:5.59.11": + version: 5.59.11 + resolution: "@typescript-eslint/visitor-keys@npm:5.59.11" dependencies: - "@typescript-eslint/types": 5.59.9 + "@typescript-eslint/types": 5.59.11 eslint-visitor-keys: ^3.3.0 - checksum: 882fd03830cbe0eca8f9a547aecc6519ddbec10e55f5f3de66e605a3f3d42a6237abd3c09b34d9cc3343c8e11386e999876aec384efe523e1478cb22752d326d + checksum: 1644c5bcb87e26717bc587f1ed1d87c96e89dae7d5bdafc1eed35a7c49e3157f8c37936b3897571892f412b7dd8439ba9cf8903128bb847831696f6517bb2d7a languageName: node linkType: hard @@ -1533,17 +1533,17 @@ __metadata: "@emotion/react": ^11.11.1 "@emotion/styled": ^11.11.0 "@mui/icons-material": ^5.11.16 - "@mui/material": ^5.13.4 + "@mui/material": ^5.13.5 "@table-library/react-table-library": 4.1.4 "@types/lodash-es": ^4.17.7 - "@types/node": ^20.3.0 - "@types/react": ^18.2.11 - "@types/react-dom": ^18.2.4 + "@types/node": ^20.3.1 + "@types/react": ^18.2.12 + "@types/react-dom": ^18.2.5 "@types/react-router-dom": ^5.3.3 - "@typescript-eslint/eslint-plugin": ^5.59.9 - "@typescript-eslint/parser": ^5.59.9 + "@typescript-eslint/eslint-plugin": ^5.59.11 + "@typescript-eslint/parser": ^5.59.11 "@vitejs/plugin-react-swc": ^3.3.2 - alova: ^2.6.0 + alova: ^2.6.1 async-validator: ^4.2.5 axios: ^1.4.0 eslint: ^8.42.0 @@ -1571,7 +1571,7 @@ __metadata: react-toastify: ^9.1.3 rollup-plugin-visualizer: ^5.9.2 sockette: ^2.0.6 - terser: ^5.17.7 + terser: ^5.18.0 typesafe-i18n: ^5.24.3 typescript: ^5.1.3 vite: ^4.3.9 @@ -1647,10 +1647,10 @@ __metadata: languageName: node linkType: hard -"alova@npm:^2.6.0": - version: 2.6.0 - resolution: "alova@npm:2.6.0" - checksum: a99dd001f094cccbc6166c5cc56ed8d417434f9edf05aa5176992a3a3735600a3b626b41b50dd867b8a86b3edf44cfbd576568349af937626a7023f9b839226b +"alova@npm:^2.6.1": + version: 2.6.1 + resolution: "alova@npm:2.6.1" + checksum: 55504d1cfab8efff3679d5734e7f78891b3b9c581c6669e6a6df6cc854d05c5d275f6645e1347c633f4418a41d418105857c7d96e0f69dc24abeccc06f0a8c18 languageName: node linkType: hard @@ -5536,9 +5536,9 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.17.7": - version: 5.17.7 - resolution: "terser@npm:5.17.7" +"terser@npm:^5.18.0": + version: 5.18.0 + resolution: "terser@npm:5.18.0" dependencies: "@jridgewell/source-map": ^0.3.3 acorn: ^8.8.2 @@ -5546,7 +5546,7 @@ __metadata: source-map-support: ~0.5.20 bin: terser: bin/terser - checksum: 864154a1750daf516012e5add4f0749bfc71e8f4f918973ec3d504db6a148be976adf46ae490e795173eeff59ec579d7d464bb6354c1bb71f8e14ff398409aed + checksum: 37f562843537c57e119b2ed6d96c2113344d9182a83613abd8e933534b89a3c622ee7ee47d4023249c1d34a2dd1b41a0e56fd6d2e2251f48b79fb7671f269b01 languageName: node linkType: hard diff --git a/lib/ESPAsyncWebServer/WebResponses.cpp b/lib/ESPAsyncWebServer/WebResponses.cpp index d2c2f78a9..63bdcd0ff 100644 --- a/lib/ESPAsyncWebServer/WebResponses.cpp +++ b/lib/ESPAsyncWebServer/WebResponses.cpp @@ -23,225 +23,281 @@ #include "cbuf.h" // Since ESP8266 does not link memchr by default, here's its implementation. -void* memchr(void* ptr, int ch, size_t count) -{ - unsigned char* p = static_cast(ptr); - while(count--) - if(*p++ == static_cast(ch)) - return --p; - return nullptr; +void * memchr(void * ptr, int ch, size_t count) { + unsigned char * p = static_cast(ptr); + while (count--) + if (*p++ == static_cast(ch)) + return --p; + return nullptr; } /* * Abstract Response * */ -const char* AsyncWebServerResponse::_responseCodeToString(int code) { - switch (code) { - case 100: return ("Continue"); - case 101: return ("Switching Protocols"); - case 200: return ("OK"); - case 201: return ("Created"); - case 202: return ("Accepted"); - case 203: return ("Non-Authoritative Information"); - case 204: return ("No Content"); - case 205: return ("Reset Content"); - case 206: return ("Partial Content"); - case 300: return ("Multiple Choices"); - case 301: return ("Moved Permanently"); - case 302: return ("Found"); - case 303: return ("See Other"); - case 304: return ("Not Modified"); - case 305: return ("Use Proxy"); - case 307: return ("Temporary Redirect"); - case 400: return ("Bad Request"); - case 401: return ("Unauthorized"); - case 402: return ("Payment Required"); - case 403: return ("Forbidden"); - case 404: return ("Not Found"); - case 405: return ("Method Not Allowed"); - case 406: return ("Not Acceptable"); - case 407: return ("Proxy Authentication Required"); - case 408: return ("Request Time-out"); - case 409: return ("Conflict"); - case 410: return ("Gone"); - case 411: return ("Length Required"); - case 412: return ("Precondition Failed"); - case 413: return ("Request Entity Too Large"); - case 414: return ("Request-URI Too Large"); - case 415: return ("Unsupported Media Type"); - case 416: return ("Requested range not satisfiable"); - case 417: return ("Expectation Failed"); - case 500: return ("Internal Server Error"); - case 501: return ("Not Implemented"); - case 502: return ("Bad Gateway"); - case 503: return ("Service Unavailable"); - case 504: return ("Gateway Time-out"); - case 505: return ("HTTP Version not supported"); - case 507: return ("Insufficient Storage"); - default: return (""); - } +const char * AsyncWebServerResponse::_responseCodeToString(int code) { + switch (code) { + case 100: + return ("Continue"); + case 101: + return ("Switching Protocols"); + case 200: + return ("OK"); + case 201: + return ("Created"); + case 202: + return ("Accepted"); // proddy: used in wifi + case 203: + return ("Non-Authoritative Information"); + case 204: + return ("No Content"); + case 205: + return ("Reset Content"); // proddy: reboot required + case 206: + return ("Partial Content"); + case 300: + return ("Multiple Choices"); + case 301: + return ("Moved Permanently"); + case 302: + return ("Found"); + case 303: + return ("See Other"); + case 304: + return ("Not Modified"); + case 305: + return ("Use Proxy"); + case 307: + return ("Temporary Redirect"); + case 400: + return ("Bad Request"); + case 401: + return ("Unauthorized"); + case 402: + return ("Payment Required"); + case 403: + return ("Forbidden"); + case 404: + return ("Not Found"); + case 405: + return ("Method Not Allowed"); + case 406: + return ("Not Acceptable"); + case 407: + return ("Proxy Authentication Required"); + case 408: + return ("Request Time-out"); + case 409: + return ("Conflict"); + case 410: + return ("Gone"); + case 411: + return ("Length Required"); + case 412: + return ("Precondition Failed"); + case 413: + return ("Request Entity Too Large"); + case 414: + return ("Request-URI Too Large"); + case 415: + return ("Unsupported Media Type"); + case 416: + return ("Requested range not satisfiable"); + case 417: + return ("Expectation Failed"); + case 500: + return ("Internal Server Error"); + case 501: + return ("Not Implemented"); + case 502: + return ("Bad Gateway"); + case 503: + return ("Service Unavailable"); + case 504: + return ("Gateway Time-out"); + case 505: + return ("HTTP Version not supported"); + case 507: + return ("Insufficient Storage"); + default: + return (""); + } } -const __FlashStringHelper *AsyncWebServerResponse::responseCodeToString(int code) { - return reinterpret_cast(responseCodeToString(code)); +const __FlashStringHelper * AsyncWebServerResponse::responseCodeToString(int code) { + return reinterpret_cast(responseCodeToString(code)); } AsyncWebServerResponse::AsyncWebServerResponse() - : _code(0) - , _headers(LinkedList([](AsyncWebHeader *h){ delete h; })) - , _contentType() - , _contentLength(0) - , _sendContentLength(true) - , _chunked(false) - , _headLength(0) - , _sentLength(0) - , _ackedLength(0) - , _writtenLength(0) - , _state(RESPONSE_SETUP) -{ - for(auto header: DefaultHeaders::Instance()) { - _headers.add(new AsyncWebHeader(header->name(), header->value())); - } + : _code(0) + , _headers(LinkedList([](AsyncWebHeader * h) { delete h; })) + , _contentType() + , _contentLength(0) + , _sendContentLength(true) + , _chunked(false) + , _headLength(0) + , _sentLength(0) + , _ackedLength(0) + , _writtenLength(0) + , _state(RESPONSE_SETUP) { + for (auto header : DefaultHeaders::Instance()) { + _headers.add(new AsyncWebHeader(header->name(), header->value())); + } } -AsyncWebServerResponse::~AsyncWebServerResponse(){ - _headers.free(); +AsyncWebServerResponse::~AsyncWebServerResponse() { + _headers.free(); } -void AsyncWebServerResponse::setCode(int code){ - if(_state == RESPONSE_SETUP) - _code = code; +void AsyncWebServerResponse::setCode(int code) { + if (_state == RESPONSE_SETUP) + _code = code; } -void AsyncWebServerResponse::setContentLength(size_t len){ - if(_state == RESPONSE_SETUP) - _contentLength = len; +void AsyncWebServerResponse::setContentLength(size_t len) { + if (_state == RESPONSE_SETUP) + _contentLength = len; } -void AsyncWebServerResponse::setContentType(const String& type){ - if(_state == RESPONSE_SETUP) - _contentType = type; +void AsyncWebServerResponse::setContentType(const String & type) { + if (_state == RESPONSE_SETUP) + _contentType = type; } -void AsyncWebServerResponse::addHeader(const String& name, const String& value){ - _headers.add(new AsyncWebHeader(name, value)); +void AsyncWebServerResponse::addHeader(const String & name, const String & value) { + _headers.add(new AsyncWebHeader(name, value)); } -String AsyncWebServerResponse::_assembleHead(uint8_t version){ - if(version){ - addHeader(F("Accept-Ranges"), F("none")); - if(_chunked) - addHeader(F("Transfer-Encoding"), F("chunked")); - } - String out = String(); - int bufSize = 300; - char buf[bufSize]; +String AsyncWebServerResponse::_assembleHead(uint8_t version) { + if (version) { + addHeader(F("Accept-Ranges"), F("none")); + if (_chunked) + addHeader(F("Transfer-Encoding"), F("chunked")); + } + String out = String(); + int bufSize = 300; + char buf[bufSize]; - snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code)); - out.concat(buf); - - if(_sendContentLength) { - snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); + snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code)); out.concat(buf); - } - if(_contentType.length()) { - snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); - out.concat(buf); - } - for(const auto& header: _headers){ - snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header->name().c_str(), header->value().c_str()); - out.concat(buf); - } - _headers.free(); + if (_sendContentLength) { + snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); + out.concat(buf); + } + if (_contentType.length()) { + snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); + out.concat(buf); + } - out.concat(F("\r\n")); - _headLength = out.length(); - return out; + for (const auto & header : _headers) { + snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header->name().c_str(), header->value().c_str()); + out.concat(buf); + } + _headers.free(); + + out.concat(F("\r\n")); + _headLength = out.length(); + return out; } -bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } -bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } -bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } -bool AsyncWebServerResponse::_sourceValid() const { return false; } -void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); } -size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; } +bool AsyncWebServerResponse::_started() const { + return _state > RESPONSE_SETUP; +} +bool AsyncWebServerResponse::_finished() const { + return _state > RESPONSE_WAIT_ACK; +} +bool AsyncWebServerResponse::_failed() const { + return _state == RESPONSE_FAILED; +} +bool AsyncWebServerResponse::_sourceValid() const { + return false; +} +void AsyncWebServerResponse::_respond(AsyncWebServerRequest * request) { + _state = RESPONSE_END; + request->client()->close(); +} +size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) { + (void)request; + (void)len; + (void)time; + return 0; +} /* * String/Code Response * */ -AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){ - _code = code; - _content = content; - _contentType = contentType; - if(_content.length()){ - _contentLength = _content.length(); - if(!_contentType.length()) - _contentType = F("text/plain"); - } - addHeader(F("Connection"), F("close")); +AsyncBasicResponse::AsyncBasicResponse(int code, const String & contentType, const String & content) { + _code = code; + _content = content; + _contentType = contentType; + if (_content.length()) { + _contentLength = _content.length(); + if (!_contentType.length()) + _contentType = F("text/plain"); + } + addHeader(F("Connection"), F("close")); } -void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ - _state = RESPONSE_HEADERS; - String out = _assembleHead(request->version()); - size_t outLen = out.length(); - size_t space = request->client()->space(); - if(!_contentLength && space >= outLen){ - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_WAIT_ACK; - } else if(_contentLength && space >= outLen + _contentLength){ - out += _content; - outLen += _contentLength; - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_WAIT_ACK; - } else if(space && space < outLen){ - String partial = out.substring(0, space); - _content = out.substring(space) + _content; - _contentLength += outLen - space; - _writtenLength += request->client()->write(partial.c_str(), partial.length()); - _state = RESPONSE_CONTENT; - } else if(space > outLen && space < (outLen + _contentLength)){ - size_t shift = space - outLen; - outLen += shift; - _sentLength += shift; - out += _content.substring(0, shift); - _content = _content.substring(shift); - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_CONTENT; - } else { - _content = out + _content; - _contentLength += outLen; - _state = RESPONSE_CONTENT; - } +void AsyncBasicResponse::_respond(AsyncWebServerRequest * request) { + _state = RESPONSE_HEADERS; + String out = _assembleHead(request->version()); + size_t outLen = out.length(); + size_t space = request->client()->space(); + if (!_contentLength && space >= outLen) { + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if (_contentLength && space >= outLen + _contentLength) { + out += _content; + outLen += _contentLength; + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if (space && space < outLen) { + String partial = out.substring(0, space); + _content = out.substring(space) + _content; + _contentLength += outLen - space; + _writtenLength += request->client()->write(partial.c_str(), partial.length()); + _state = RESPONSE_CONTENT; + } else if (space > outLen && space < (outLen + _contentLength)) { + size_t shift = space - outLen; + outLen += shift; + _sentLength += shift; + out += _content.substring(0, shift); + _content = _content.substring(shift); + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_CONTENT; + } else { + _content = out + _content; + _contentLength += outLen; + _state = RESPONSE_CONTENT; + } } -size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ - (void)time; - _ackedLength += len; - if(_state == RESPONSE_CONTENT){ - size_t available = _contentLength - _sentLength; - size_t space = request->client()->space(); - //we can fit in this packet - if(space > available){ - _writtenLength += request->client()->write(_content.c_str(), available); - _content = String(); - _state = RESPONSE_WAIT_ACK; - return available; +size_t AsyncBasicResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) { + (void)time; + _ackedLength += len; + if (_state == RESPONSE_CONTENT) { + size_t available = _contentLength - _sentLength; + size_t space = request->client()->space(); + //we can fit in this packet + if (space > available) { + _writtenLength += request->client()->write(_content.c_str(), available); + _content = String(); + _state = RESPONSE_WAIT_ACK; + return available; + } + //send some data, the rest on ack + String out = _content.substring(0, space); + _content = _content.substring(space); + _sentLength += space; + _writtenLength += request->client()->write(out.c_str(), space); + return space; + } else if (_state == RESPONSE_WAIT_ACK) { + if (_ackedLength >= _writtenLength) { + _state = RESPONSE_END; + } } - //send some data, the rest on ack - String out = _content.substring(0, space); - _content = _content.substring(space); - _sentLength += space; - _writtenLength += request->client()->write(out.c_str(), space); - return space; - } else if(_state == RESPONSE_WAIT_ACK){ - if(_ackedLength >= _writtenLength){ - _state = RESPONSE_END; - } - } - return 0; + return 0; } @@ -249,232 +305,231 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint * Abstract Response * */ -AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback) -{ - // In case of template processing, we're unable to determine real response size - if(callback) { - _contentLength = 0; - _sendContentLength = false; - _chunked = true; - } -} - -void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){ - addHeader(F("Connection"), F("close")); - _head = _assembleHead(request->version()); - _state = RESPONSE_HEADERS; - _ack(request, 0, 0); -} - -size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ - (void)time; - if(!_sourceValid()){ - _state = RESPONSE_FAILED; - request->client()->close(); - return 0; - } - _ackedLength += len; - size_t space = request->client()->space(); - - size_t headLen = _head.length(); - if(_state == RESPONSE_HEADERS){ - if(space >= headLen){ - _state = RESPONSE_CONTENT; - space -= headLen; - } else { - String out = _head.substring(0, space); - _head = _head.substring(space); - _writtenLength += request->client()->write(out.c_str(), out.length()); - return out.length(); +AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) + : _callback(callback) { + // In case of template processing, we're unable to determine real response size + if (callback) { + _contentLength = 0; + _sendContentLength = false; + _chunked = true; } - } +} - if(_state == RESPONSE_CONTENT){ - size_t outLen; - if(_chunked){ - if(space <= 8){ +void AsyncAbstractResponse::_respond(AsyncWebServerRequest * request) { + addHeader(F("Connection"), F("close")); + _head = _assembleHead(request->version()); + _state = RESPONSE_HEADERS; + _ack(request, 0, 0); +} + +size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) { + (void)time; + if (!_sourceValid()) { + _state = RESPONSE_FAILED; + request->client()->close(); return 0; - } - outLen = space; - } else if(!_sendContentLength){ - outLen = space; - } else { - outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength); + } + _ackedLength += len; + size_t space = request->client()->space(); + + size_t headLen = _head.length(); + if (_state == RESPONSE_HEADERS) { + if (space >= headLen) { + _state = RESPONSE_CONTENT; + space -= headLen; + } else { + String out = _head.substring(0, space); + _head = _head.substring(space); + _writtenLength += request->client()->write(out.c_str(), out.length()); + return out.length(); + } } - uint8_t *buf = (uint8_t *)malloc(outLen+headLen); - if (!buf) { - // os_printf("_ack malloc %d failed\n", outLen+headLen); - return 0; + if (_state == RESPONSE_CONTENT) { + size_t outLen; + if (_chunked) { + if (space <= 8) { + return 0; + } + outLen = space; + } else if (!_sendContentLength) { + outLen = space; + } else { + outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength); + } + + uint8_t * buf = (uint8_t *)malloc(outLen + headLen); + if (!buf) { + // os_printf("_ack malloc %d failed\n", outLen+headLen); + return 0; + } + + if (headLen) { + memcpy(buf, _head.c_str(), _head.length()); + } + + size_t readLen = 0; + + if (_chunked) { + // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. + // See RFC2616 sections 2, 3.6.1. + readLen = _fillBufferAndProcessTemplates(buf + headLen + 6, outLen - 8); + if (readLen == RESPONSE_TRY_AGAIN) { + free(buf); + return 0; + } + outLen = snprintf_P((char *)buf + headLen, sizeof(buf) - headLen - 2, PSTR("%x"), readLen) + headLen; + while (outLen < headLen + 4) + buf[outLen++] = ' '; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + outLen += readLen; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + } else { + readLen = _fillBufferAndProcessTemplates(buf + headLen, outLen); + if (readLen == RESPONSE_TRY_AGAIN) { + free(buf); + return 0; + } + outLen = readLen + headLen; + } + + if (headLen) { + _head = String(); + } + + if (outLen) { + _writtenLength += request->client()->write((const char *)buf, outLen); + } + + if (_chunked) { + _sentLength += readLen; + } else { + _sentLength += outLen - headLen; + } + + free(buf); + + if ((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)) { + _state = RESPONSE_WAIT_ACK; + } + return outLen; + + } else if (_state == RESPONSE_WAIT_ACK) { + if (!_sendContentLength || _ackedLength >= _writtenLength) { + _state = RESPONSE_END; + if (!_chunked && !_sendContentLength) + request->client()->close(true); + } } - - if(headLen){ - memcpy(buf, _head.c_str(), _head.length()); - } - - size_t readLen = 0; - - if(_chunked){ - // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. - // See RFC2616 sections 2, 3.6.1. - readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8); - if(readLen == RESPONSE_TRY_AGAIN){ - free(buf); - return 0; - } - outLen = snprintf_P((char*)buf+headLen, sizeof(buf)-headLen-2, PSTR("%x"), readLen) + headLen; - while(outLen < headLen + 4) buf[outLen++] = ' '; - buf[outLen++] = '\r'; - buf[outLen++] = '\n'; - outLen += readLen; - buf[outLen++] = '\r'; - buf[outLen++] = '\n'; - } else { - readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen); - if(readLen == RESPONSE_TRY_AGAIN){ - free(buf); - return 0; - } - outLen = readLen + headLen; - } - - if(headLen){ - _head = String(); - } - - if(outLen){ - _writtenLength += request->client()->write((const char*)buf, outLen); - } - - if(_chunked){ - _sentLength += readLen; - } else { - _sentLength += outLen - headLen; - } - - free(buf); - - if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){ - _state = RESPONSE_WAIT_ACK; - } - return outLen; - - } else if(_state == RESPONSE_WAIT_ACK){ - if(!_sendContentLength || _ackedLength >= _writtenLength){ - _state = RESPONSE_END; - if(!_chunked && !_sendContentLength) - request->client()->close(true); - } - } - return 0; + return 0; } -size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) -{ +size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t * data, const size_t len) { // If we have something in cache, copy it to buffer const size_t readFromCache = std::min(len, _cache.size()); - if(readFromCache) { - memcpy(data, _cache.data(), readFromCache); - _cache.erase(_cache.begin(), _cache.begin() + readFromCache); + if (readFromCache) { + memcpy(data, _cache.data(), readFromCache); + _cache.erase(_cache.begin(), _cache.begin() + readFromCache); } // If we need to read more... - const size_t needFromFile = len - readFromCache; + const size_t needFromFile = len - readFromCache; const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile); return readFromCache + readFromContent; } -size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) -{ - if(!_callback) - return _fillBuffer(data, len); +size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t * data, size_t len) { + if (!_callback) + return _fillBuffer(data, len); - const size_t originalLen = len; - len = _readDataFromCacheOrContent(data, len); - // Now we've read 'len' bytes, either from cache or from file - // Search for template placeholders - uint8_t* pTemplateStart = data; - while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] - uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; - // temporary buffer to hold parameter name - uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; - String paramName; - // If closing placeholder is found: - if(pTemplateEnd) { - // prepare argument to callback - const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1)); - if(paramNameLength) { - memcpy(buf, pTemplateStart + 1, paramNameLength); - buf[paramNameLength] = 0; - paramName = String(reinterpret_cast(buf)); - } else { // double percent sign encountered, this is single percent sign escaped. - // remove the 2nd percent sign - memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); - len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; - ++pTemplateStart; - } - } else if(&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data - memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); - const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); - if(readFromCacheOrContent) { - pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); - if(pTemplateEnd) { - // prepare argument to callback - *pTemplateEnd = 0; - paramName = String(reinterpret_cast(buf)); - // Copy remaining read-ahead data into cache - _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); - pTemplateEnd = &data[len - 1]; + const size_t originalLen = len; + len = _readDataFromCacheOrContent(data, len); + // Now we've read 'len' bytes, either from cache or from file + // Search for template placeholders + uint8_t * pTemplateStart = data; + while ((pTemplateStart < &data[len]) + && (pTemplateStart = (uint8_t *)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] + uint8_t * pTemplateEnd = + (pTemplateStart < &data[len - 1]) ? (uint8_t *)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; + // temporary buffer to hold parameter name + uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; + String paramName; + // If closing placeholder is found: + if (pTemplateEnd) { + // prepare argument to callback + const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1)); + if (paramNameLength) { + memcpy(buf, pTemplateStart + 1, paramNameLength); + buf[paramNameLength] = 0; + paramName = String(reinterpret_cast(buf)); + } else { // double percent sign encountered, this is single percent sign escaped. + // remove the 2nd percent sign + memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; + ++pTemplateStart; + } + } else if (&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data + memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); + const size_t readFromCacheOrContent = + _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); + if (readFromCacheOrContent) { + pTemplateEnd = (uint8_t *)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); + if (pTemplateEnd) { + // prepare argument to callback + *pTemplateEnd = 0; + paramName = String(reinterpret_cast(buf)); + // Copy remaining read-ahead data into cache + _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); + pTemplateEnd = &data[len - 1]; + } else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position + { + // but first, store read file data in cache + _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); + ++pTemplateStart; + } + } else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position + ++pTemplateStart; + } else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position + ++pTemplateStart; + if (paramName.length()) { + // call callback and replace with result. + // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. + // Data after pTemplateEnd may need to be moved. + // The first byte of data after placeholder is located at pTemplateEnd + 1. + // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). + const String paramValue(_callback(paramName)); + const char * pvstr = paramValue.c_str(); + const unsigned int pvlen = paramValue.length(); + const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); + // make room for param value + // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store + if ((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { + _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); + //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); + len = originalLen; // fix issue with truncated data, not sure if it has any side effects + } else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied) + //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. + // Move the entire data after the placeholder + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + // 3. replace placeholder with actual value + memcpy(pTemplateStart, pvstr, numBytesCopied); + // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) + if (numBytesCopied < pvlen) { + _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); + } else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... + // there is some free room, fill it from cache + const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; + const size_t totalFreeRoom = originalLen - len + roomFreed; + len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; + } else { // result is copied fully; it is longer than placeholder text + const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; + len = std::min(len + roomTaken, originalLen); + } } - else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position - { - // but first, store read file data in cache - _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); - ++pTemplateStart; - } - } - else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position - ++pTemplateStart; - } - else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position - ++pTemplateStart; - if(paramName.length()) { - // call callback and replace with result. - // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. - // Data after pTemplateEnd may need to be moved. - // The first byte of data after placeholder is located at pTemplateEnd + 1. - // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). - const String paramValue(_callback(paramName)); - const char* pvstr = paramValue.c_str(); - const unsigned int pvlen = paramValue.length(); - const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); - // make room for param value - // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store - if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { - _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); - //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end - memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); - len = originalLen; // fix issue with truncated data, not sure if it has any side effects - } else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied) - //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. - // Move the entire data after the placeholder - memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); - // 3. replace placeholder with actual value - memcpy(pTemplateStart, pvstr, numBytesCopied); - // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) - if(numBytesCopied < pvlen) { - _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); - } else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... - // there is some free room, fill it from cache - const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; - const size_t totalFreeRoom = originalLen - len + roomFreed; - len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; - } else { // result is copied fully; it is longer than placeholder text - const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; - len = std::min(len + roomTaken, originalLen); - } - } - } // while(pTemplateStart) - return len; + } // while(pTemplateStart) + return len; } @@ -482,193 +537,218 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size * File Response * */ -AsyncFileResponse::~AsyncFileResponse(){ - if(_content) - _content.close(); +AsyncFileResponse::~AsyncFileResponse() { + if (_content) + _content.close(); } -void AsyncFileResponse::_setContentType(const String& path){ +void AsyncFileResponse::_setContentType(const String & path) { #if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION - extern const __FlashStringHelper *getContentType(const String &path); - _contentType = getContentType(path); + extern const __FlashStringHelper * getContentType(const String & path); + _contentType = getContentType(path); #else - if (path.endsWith(F(".html"))) _contentType = F("text/html"); - else if (path.endsWith(F(".htm"))) _contentType = F("text/html"); - else if (path.endsWith(F(".css"))) _contentType = F("text/css"); - else if (path.endsWith(F(".json"))) _contentType = F("application/json"); - else if (path.endsWith(F(".js"))) _contentType = F("application/javascript"); - else if (path.endsWith(F(".png"))) _contentType = F("image/png"); - else if (path.endsWith(F(".gif"))) _contentType = F("image/gif"); - else if (path.endsWith(F(".jpg"))) _contentType = F("image/jpeg"); - else if (path.endsWith(F(".ico"))) _contentType = F("image/x-icon"); - else if (path.endsWith(F(".svg"))) _contentType = F("image/svg+xml"); - else if (path.endsWith(F(".eot"))) _contentType = F("font/eot"); - else if (path.endsWith(F(".woff"))) _contentType = F("font/woff"); - else if (path.endsWith(F(".woff2"))) _contentType = F("font/woff2"); - else if (path.endsWith(F(".ttf"))) _contentType = F("font/ttf"); - else if (path.endsWith(F(".xml"))) _contentType = F("text/xml"); - else if (path.endsWith(F(".pdf"))) _contentType = F("application/pdf"); - else if (path.endsWith(F(".zip"))) _contentType = F("application/zip"); - else if(path.endsWith(F(".gz"))) _contentType = F("application/x-gzip"); - else _contentType = F("text/plain"); + if (path.endsWith(F(".html"))) + _contentType = F("text/html"); + else if (path.endsWith(F(".htm"))) + _contentType = F("text/html"); + else if (path.endsWith(F(".css"))) + _contentType = F("text/css"); + else if (path.endsWith(F(".json"))) + _contentType = F("application/json"); + else if (path.endsWith(F(".js"))) + _contentType = F("application/javascript"); + else if (path.endsWith(F(".png"))) + _contentType = F("image/png"); + else if (path.endsWith(F(".gif"))) + _contentType = F("image/gif"); + else if (path.endsWith(F(".jpg"))) + _contentType = F("image/jpeg"); + else if (path.endsWith(F(".ico"))) + _contentType = F("image/x-icon"); + else if (path.endsWith(F(".svg"))) + _contentType = F("image/svg+xml"); + else if (path.endsWith(F(".eot"))) + _contentType = F("font/eot"); + else if (path.endsWith(F(".woff"))) + _contentType = F("font/woff"); + else if (path.endsWith(F(".woff2"))) + _contentType = F("font/woff2"); + else if (path.endsWith(F(".ttf"))) + _contentType = F("font/ttf"); + else if (path.endsWith(F(".xml"))) + _contentType = F("text/xml"); + else if (path.endsWith(F(".pdf"))) + _contentType = F("application/pdf"); + else if (path.endsWith(F(".zip"))) + _contentType = F("application/zip"); + else if (path.endsWith(F(".gz"))) + _contentType = F("application/x-gzip"); + else + _contentType = F("text/plain"); #endif } -AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ - _code = 200; - _path = path; +AsyncFileResponse::AsyncFileResponse(FS & fs, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback) + : AsyncAbstractResponse(callback) { + _code = 200; + _path = path; - if(!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))){ - _path = _path + F(".gz"); - addHeader(F("Content-Encoding"), F("gzip")); - _callback = nullptr; // Unable to process zipped templates - _sendContentLength = true; - _chunked = false; - } + if (!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))) { + _path = _path + F(".gz"); + addHeader(F("Content-Encoding"), F("gzip")); + _callback = nullptr; // Unable to process zipped templates + _sendContentLength = true; + _chunked = false; + } - _content = fs.open(_path, fs::FileOpenMode::read); - _contentLength = _content.size(); + _content = fs.open(_path, fs::FileOpenMode::read); + _contentLength = _content.size(); - if(contentType.length() == 0) - _setContentType(path); - else - _contentType = contentType; + if (contentType.length() == 0) + _setContentType(path); + else + _contentType = contentType; - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26+path.length()-filenameStart]; - char* filename = (char*)path.c_str() + filenameStart; + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26 + path.length() - filenameStart]; + char * filename = (char *)path.c_str() + filenameStart; - if(download) { - // set filename and force download - snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); - } else { - // set filename and force rendering - snprintf_P(buf, sizeof (buf), PSTR("inline; filename=\"%s\""), filename); - } - addHeader(F("Content-Disposition"), buf); + if (download) { + // set filename and force download + snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename); + } else { + // set filename and force rendering + snprintf_P(buf, sizeof(buf), PSTR("inline; filename=\"%s\""), filename); + } + addHeader(F("Content-Disposition"), buf); } -AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ - _code = 200; - _path = path; +AsyncFileResponse::AsyncFileResponse(File content, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback) + : AsyncAbstractResponse(callback) { + _code = 200; + _path = path; - if(!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))){ - addHeader(F("Content-Encoding"), F("gzip")); - _callback = nullptr; // Unable to process gzipped templates - _sendContentLength = true; - _chunked = false; - } + if (!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))) { + addHeader(F("Content-Encoding"), F("gzip")); + _callback = nullptr; // Unable to process gzipped templates + _sendContentLength = true; + _chunked = false; + } - _content = content; - _contentLength = _content.size(); + _content = content; + _contentLength = _content.size(); - if(contentType.length() == 0) - _setContentType(path); - else - _contentType = contentType; + if (contentType.length() == 0) + _setContentType(path); + else + _contentType = contentType; - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26+path.length()-filenameStart]; - char* filename = (char*)path.c_str() + filenameStart; + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26 + path.length() - filenameStart]; + char * filename = (char *)path.c_str() + filenameStart; - if(download) { - snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); - } else { - snprintf_P(buf, sizeof (buf), PSTR("inline; filename=\"%s\""), filename); - } - addHeader(F("Content-Disposition"), buf); + if (download) { + snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename); + } else { + snprintf_P(buf, sizeof(buf), PSTR("inline; filename=\"%s\""), filename); + } + addHeader(F("Content-Disposition"), buf); } -size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){ - return _content.read(data, len); +size_t AsyncFileResponse::_fillBuffer(uint8_t * data, size_t len) { + return _content.read(data, len); } /* * Stream Response * */ -AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { - _code = 200; - _content = &stream; - _contentLength = len; - _contentType = contentType; +AsyncStreamResponse::AsyncStreamResponse(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback) + : AsyncAbstractResponse(callback) { + _code = 200; + _content = &stream; + _contentLength = len; + _contentType = contentType; } -size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t available = _content->available(); - size_t outLen = (available > len)?len:available; - size_t i; - for(i=0;iread(); - return outLen; +size_t AsyncStreamResponse::_fillBuffer(uint8_t * data, size_t len) { + size_t available = _content->available(); + size_t outLen = (available > len) ? len : available; + size_t i; + for (i = 0; i < outLen; i++) + data[i] = _content->read(); + return outLen; } /* * Callback Response * */ -AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) { - _code = 200; - _content = callback; - _contentLength = len; - if(!len) - _sendContentLength = false; - _contentType = contentType; - _filledLength = 0; +AsyncCallbackResponse::AsyncCallbackResponse(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) + : AsyncAbstractResponse(templateCallback) { + _code = 200; + _content = callback; + _contentLength = len; + if (!len) + _sendContentLength = false; + _contentType = contentType; + _filledLength = 0; } -size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t ret = _content(data, len, _filledLength); - if(ret != RESPONSE_TRY_AGAIN){ - _filledLength += ret; - } - return ret; +size_t AsyncCallbackResponse::_fillBuffer(uint8_t * data, size_t len) { + size_t ret = _content(data, len, _filledLength); + if (ret != RESPONSE_TRY_AGAIN) { + _filledLength += ret; + } + return ret; } /* * Chunked Response * */ -AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) { - _code = 200; - _content = callback; - _contentLength = 0; - _contentType = contentType; - _sendContentLength = false; - _chunked = true; - _filledLength = 0; +AsyncChunkedResponse::AsyncChunkedResponse(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) + : AsyncAbstractResponse(processorCallback) { + _code = 200; + _content = callback; + _contentLength = 0; + _contentType = contentType; + _sendContentLength = false; + _chunked = true; + _filledLength = 0; } -size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t ret = _content(data, len, _filledLength); - if(ret != RESPONSE_TRY_AGAIN){ - _filledLength += ret; - } - return ret; +size_t AsyncChunkedResponse::_fillBuffer(uint8_t * data, size_t len) { + size_t ret = _content(data, len, _filledLength); + if (ret != RESPONSE_TRY_AGAIN) { + _filledLength += ret; + } + return ret; } /* * Progmem Response * */ -AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { - _code = code; - _content = content; - _contentType = contentType; - _contentLength = len; - _readLength = 0; +AsyncProgmemResponse::AsyncProgmemResponse(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback) + : AsyncAbstractResponse(callback) { + _code = code; + _content = content; + _contentType = contentType; + _contentLength = len; + _readLength = 0; } -size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ - size_t left = _contentLength - _readLength; - if (left > len) { - memcpy_P(data, _content + _readLength, len); - _readLength += len; - return len; - } - memcpy_P(data, _content + _readLength, left); - _readLength += left; - return left; +size_t AsyncProgmemResponse::_fillBuffer(uint8_t * data, size_t len) { + size_t left = _contentLength - _readLength; + if (left > len) { + memcpy_P(data, _content + _readLength, len); + _readLength += len; + return len; + } + memcpy_P(data, _content + _readLength, left); + _readLength += left; + return left; } @@ -676,34 +756,34 @@ size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ * Response Stream (You can print/write/printf to it, up to the contentLen bytes) * */ -AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize){ - _code = 200; - _contentLength = 0; - _contentType = contentType; - _content = new cbuf(bufferSize); +AsyncResponseStream::AsyncResponseStream(const String & contentType, size_t bufferSize) { + _code = 200; + _contentLength = 0; + _contentType = contentType; + _content = new cbuf(bufferSize); } -AsyncResponseStream::~AsyncResponseStream(){ - delete _content; +AsyncResponseStream::~AsyncResponseStream() { + delete _content; } -size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){ - return _content->read((char*)buf, maxLen); +size_t AsyncResponseStream::_fillBuffer(uint8_t * buf, size_t maxLen) { + return _content->read((char *)buf, maxLen); } -size_t AsyncResponseStream::write(const uint8_t *data, size_t len){ - if(_started()) - return 0; +size_t AsyncResponseStream::write(const uint8_t * data, size_t len) { + if (_started()) + return 0; - if(len > _content->room()){ - size_t needed = len - _content->room(); - _content->resizeAdd(needed); - } - size_t written = _content->write((const char*)data, len); - _contentLength += written; - return written; + if (len > _content->room()) { + size_t needed = len - _content->room(); + _content->resizeAdd(needed); + } + size_t written = _content->write((const char *)data, len); + _contentLength += written; + return written; } -size_t AsyncResponseStream::write(uint8_t data){ - return write(&data, 1); +size_t AsyncResponseStream::write(uint8_t data) { + return write(&data, 1); } diff --git a/lib/framework/HttpEndpoint.h b/lib/framework/HttpEndpoint.h index 21ac7e610..0e0014049 100644 --- a/lib/framework/HttpEndpoint.h +++ b/lib/framework/HttpEndpoint.h @@ -115,7 +115,7 @@ class HttpPostEndpoint { response->setLength(); if (outcome == StateUpdateResult::CHANGED_RESTART) { - response->setCode(202); // added by proddy + response->setCode(205); // added by proddy, reboot required } request->send(response); } diff --git a/lib/framework/WiFiScanner.cpp b/lib/framework/WiFiScanner.cpp index b7ac8f718..708179686 100644 --- a/lib/framework/WiFiScanner.cpp +++ b/lib/framework/WiFiScanner.cpp @@ -16,7 +16,7 @@ void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) { WiFi.scanDelete(); WiFi.scanNetworks(true); } - request->send(202); + request->send(202); // special code to indicate scan in progress } void WiFiScanner::listNetworks(AsyncWebServerRequest * request) { @@ -36,7 +36,7 @@ void WiFiScanner::listNetworks(AsyncWebServerRequest * request) { response->setLength(); request->send(response); } else if (numNetworks == -1) { - request->send(202); + request->send(202); // special code to indicate scan in progress } else { scanNetworks(request); } diff --git a/mock-api/server.js b/mock-api/server.js index 04ede86d1..c805f7bc6 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -480,7 +480,7 @@ const emsesp_sensordata = { { id: '28-233D-9497-0C03', n: 'Dallas 1', t: 25.7, o: 1.2, u: 1 }, { id: '28-243D-7437-1E3A', n: 'Dallas 2 outside', t: 26.1, o: 0, u: 1 }, { id: '28-243E-7437-1E3B', n: 'Zolder', t: 27.1, o: 0, u: 16 }, - { id: '28-183D-1892-0C33', n: 'Roof', o: 2, u: 1 } + { id: '28-183D-1892-0C33', n: 'Roof', o: 2, u: 1 } // no temperature ], // as: [], as: [ @@ -512,95 +512,15 @@ const status = { }; // Dashboard data -// 7 - Nefit Trendline boiler // 1 - RC35 thermo // 2 - RC20 thermo // 3 - Buderus GB125 boiler // 4 - RC100 themo // 5 - Mixer MM10 // 6 - Solar SM10 +// 7 - Nefit Trendline boiler // 99 - Custom -const emsesp_devicedata_7 = { - data: [ - { v: '', u: 0, id: '08reset', c: 'reset', l: ['-', 'maintenance', 'error'] }, - { v: 'off', u: 0, id: '08heating active' }, - { v: 'off', u: 0, id: '04tapwater active' }, - { v: 5, u: 1, id: '04selected flow temperature', c: 'selflowtemp' }, - { v: 0, u: 3, id: '0Eburner selected max power', c: 'selburnpow' }, - { v: 0, u: 3, id: '00heating pump modulation' }, - { v: 53.4, u: 1, id: '00current flow temperature' }, - { v: 52.7, u: 1, id: '00return temperature' }, - { v: 1.3, u: 10, id: '00system pressure' }, - { v: 54.9, u: 1, id: '00actual boiler temperature' }, - { v: 'off', u: 0, id: '00gas' }, - { v: 'off', u: 0, id: '00gas stage 2' }, - { v: 0, u: 9, id: '00flame current' }, - { v: 'off', u: 0, id: '00heating pump' }, - { v: 'off', u: 0, id: '00fan' }, - { v: 'off', u: 0, id: '00ignition' }, - { v: 'off', u: 0, id: '00oil preheating' }, - { v: 'on', u: 0, id: '00heating activated', c: 'heatingactivated', l: ['off', 'on'] }, - { v: 80, u: 1, id: '00heating temperature', c: 'heatingtemp' }, - { v: 70, u: 3, id: '00burner pump max power', c: 'pumpmodmax' }, - { v: 30, u: 3, id: '00burner pump min power', c: 'pumpmodmin' }, - { v: 1, u: 8, id: '00pump delay', c: 'pumpdelay' }, - { v: 10, u: 8, id: '00burner min period', c: 'burnminperiod' }, - { v: 0, u: 3, id: '00burner min power', c: 'burnminpower' }, - { v: 50, u: 3, id: '00burner max power', c: 'burnmaxpower' }, - { v: -6, u: 2, id: '00hysteresis on temperature', c: 'boilhyston' }, - { v: 6, u: 2, id: '00hysteresis off temperature', c: 'boilhystoff' }, - { v: 0, u: 1, id: '00set flow temperature' }, - { v: 0, u: 3, id: '00burner set power' }, - { v: 0, u: 3, id: '00burner current power' }, - { v: 326323, u: 0, id: '00burner starts' }, - { v: 553437, u: 8, id: '00total burner operating time' }, - { v: 451286, u: 8, id: '00total heat operating time' }, - { v: 4672173, u: 8, id: '00total UBA operating time' }, - { v: '1C(210) 06.06.2020 12:07 (0 min)', u: 0, id: '00last error code' }, - { v: '0H', u: 0, id: '00service code' }, - { v: 203, u: 0, id: '00service code number' }, - { v: 'H00', u: 0, id: '00maintenance message' }, - { v: 'manual', u: 0, id: '00maintenance scheduled', c: 'maintenance', l: ['off', 'time', 'date', 'manual'] }, - { v: 6000, u: 7, id: '00time to next maintenance', c: 'maintenancetime' }, - { v: '01.01.2012', u: 0, id: '00next maintenance date', c: 'maintenancedate', o: 'Format: < dd.mm.yyyy >' }, - { v: 'on', u: 0, id: '00dhw turn on/off', c: 'wwtapactivated', l: ['off', 'on'] }, - { v: 62, u: 1, id: '00dhw set temperature' }, - { v: 60, u: 1, id: '00dhw selected temperature', c: 'wwseltemp' }, - { v: 'flow', u: 0, id: '00dhw type' }, - { v: 'hot', u: 0, id: '00dhw comfort', c: 'wwcomfort', l: ['hot', 'eco', 'intelligent'] }, - { v: 40, u: 2, id: '00dhw flow temperature offset', c: 'wwflowtempoffset' }, - { v: 100, u: 3, id: '00dhw max power', c: 'wwmaxpower' }, - { v: 'off', u: 0, id: '00dhw circulation pump available', c: 'wwcircpump', l: ['off', 'on'] }, - { v: '3-way valve', u: 0, id: '00dhw charging type' }, - { v: -5, u: 2, id: '00dhw hysteresis on temperature', c: 'wwhyston' }, - { v: 0, u: 2, id: '00dhw hysteresis off temperature', c: 'wwhystoff' }, - { v: 70, u: 1, id: '00dhw disinfection temperature', c: 'wwdisinfectiontemp' }, - { - v: 'off', - u: 0, - id: '00dhw circulation pump mode', - c: 'wwcircmode', - l: ['off', '1x3min', '2x3min', '3x3min', '4x3min', '5x3min', '6x3min', 'continuous'] - }, - { v: 'off', u: 0, id: '00dhw circulation active', c: 'wwcirc', l: ['off', 'on'] }, - { v: 47.3, u: 1, id: '00dhw current intern temperature' }, - { v: 0, u: 4, id: '00dhw current tap water flow' }, - { v: 47.3, u: 1, id: '00dhw storage intern temperature' }, - { v: 'on', u: 0, id: '00dhw activated', c: 'wwactivated', l: ['off', 'on'] }, - { v: 'off', u: 0, id: '00dhw one time charging', c: 'wwonetime', l: ['off', 'on'] }, - { v: 'off', u: 0, id: '00dhw disinfecting', c: 'wwdisinfecting', l: ['off', 'on'] }, - { v: 'off', u: 0, id: '00dhw charging' }, - { v: 'off', u: 0, id: '00dhw recharging' }, - { v: 'on', u: 0, id: '00dhw temperature ok' }, - { v: 'off', u: 0, id: '00dhw active' }, - { v: 'on', u: 0, id: '00dhw 3way valve active' }, - { v: 0, u: 3, id: '00dhw set pump power' }, - { v: 288768, u: 0, id: '00dhw starts' }, - { v: 102151, u: 8, id: '00dhw active time' } - ] -}; - const emsesp_devicedata_1 = { data: [ { @@ -1768,6 +1688,86 @@ const emsesp_devicedata_6 = { ] }; +const emsesp_devicedata_7 = { + data: [ + { v: '', u: 0, id: '08reset', c: 'reset', l: ['-', 'maintenance', 'error'] }, + { v: 'off', u: 0, id: '08heating active' }, + { v: 'off', u: 0, id: '04tapwater active' }, + { v: 5, u: 1, id: '04selected flow temperature', c: 'selflowtemp' }, + { v: 0, u: 3, id: '0Eburner selected max power', c: 'selburnpow' }, + { v: 0, u: 3, id: '00heating pump modulation' }, + { v: 53.4, u: 1, id: '00current flow temperature' }, + { v: 52.7, u: 1, id: '00return temperature' }, + { v: 1.3, u: 10, id: '00system pressure' }, + { v: 54.9, u: 1, id: '00actual boiler temperature' }, + { v: 'off', u: 0, id: '00gas' }, + { v: 'off', u: 0, id: '00gas stage 2' }, + { v: 0, u: 9, id: '00flame current' }, + { v: 'off', u: 0, id: '00heating pump' }, + { v: 'off', u: 0, id: '00fan' }, + { v: 'off', u: 0, id: '00ignition' }, + { v: 'off', u: 0, id: '00oil preheating' }, + { v: 'on', u: 0, id: '00heating activated', c: 'heatingactivated', l: ['off', 'on'] }, + { v: 80, u: 1, id: '00heating temperature', c: 'heatingtemp' }, + { v: 70, u: 3, id: '00burner pump max power', c: 'pumpmodmax' }, + { v: 30, u: 3, id: '00burner pump min power', c: 'pumpmodmin' }, + { v: 1, u: 8, id: '00pump delay', c: 'pumpdelay' }, + { v: 10, u: 8, id: '00burner min period', c: 'burnminperiod' }, + { v: 0, u: 3, id: '00burner min power', c: 'burnminpower' }, + { v: 50, u: 3, id: '00burner max power', c: 'burnmaxpower' }, + { v: -6, u: 2, id: '00hysteresis on temperature', c: 'boilhyston' }, + { v: 6, u: 2, id: '00hysteresis off temperature', c: 'boilhystoff' }, + { v: 0, u: 1, id: '00set flow temperature' }, + { v: 0, u: 3, id: '00burner set power' }, + { v: 0, u: 3, id: '00burner current power' }, + { v: 326323, u: 0, id: '00burner starts' }, + { v: 553437, u: 8, id: '00total burner operating time' }, + { v: 451286, u: 8, id: '00total heat operating time' }, + { v: 4672173, u: 8, id: '00total UBA operating time' }, + { v: '1C(210) 06.06.2020 12:07 (0 min)', u: 0, id: '00last error code' }, + { v: '0H', u: 0, id: '00service code' }, + { v: 203, u: 0, id: '00service code number' }, + { v: 'H00', u: 0, id: '00maintenance message' }, + { v: 'manual', u: 0, id: '00maintenance scheduled', c: 'maintenance', l: ['off', 'time', 'date', 'manual'] }, + { v: 6000, u: 7, id: '00time to next maintenance', c: 'maintenancetime' }, + { v: '01.01.2012', u: 0, id: '00next maintenance date', c: 'maintenancedate', o: 'Format: < dd.mm.yyyy >' }, + { v: 'on', u: 0, id: '00dhw turn on/off', c: 'wwtapactivated', l: ['off', 'on'] }, + { v: 62, u: 1, id: '00dhw set temperature' }, + { v: 60, u: 1, id: '00dhw selected temperature', c: 'wwseltemp' }, + { v: 'flow', u: 0, id: '00dhw type' }, + { v: 'hot', u: 0, id: '00dhw comfort', c: 'wwcomfort', l: ['hot', 'eco', 'intelligent'] }, + { v: 40, u: 2, id: '00dhw flow temperature offset', c: 'wwflowtempoffset' }, + { v: 100, u: 3, id: '00dhw max power', c: 'wwmaxpower' }, + { v: 'off', u: 0, id: '00dhw circulation pump available', c: 'wwcircpump', l: ['off', 'on'] }, + { v: '3-way valve', u: 0, id: '00dhw charging type' }, + { v: -5, u: 2, id: '00dhw hysteresis on temperature', c: 'wwhyston' }, + { v: 0, u: 2, id: '00dhw hysteresis off temperature', c: 'wwhystoff' }, + { v: 70, u: 1, id: '00dhw disinfection temperature', c: 'wwdisinfectiontemp' }, + { + v: 'off', + u: 0, + id: '00dhw circulation pump mode', + c: 'wwcircmode', + l: ['off', '1x3min', '2x3min', '3x3min', '4x3min', '5x3min', '6x3min', 'continuous'] + }, + { v: 'off', u: 0, id: '00dhw circulation active', c: 'wwcirc', l: ['off', 'on'] }, + { v: 47.3, u: 1, id: '00dhw current intern temperature' }, + { v: 0, u: 4, id: '00dhw current tap water flow' }, + { v: 47.3, u: 1, id: '00dhw storage intern temperature' }, + { v: 'on', u: 0, id: '00dhw activated', c: 'wwactivated', l: ['off', 'on'] }, + { v: 'off', u: 0, id: '00dhw one time charging', c: 'wwonetime', l: ['off', 'on'] }, + { v: 'off', u: 0, id: '00dhw disinfecting', c: 'wwdisinfecting', l: ['off', 'on'] }, + { v: 'off', u: 0, id: '00dhw charging' }, + { v: 'off', u: 0, id: '00dhw recharging' }, + { v: 'on', u: 0, id: '00dhw temperature ok' }, + { v: 'off', u: 0, id: '00dhw active' }, + { v: 'on', u: 0, id: '00dhw 3way valve active' }, + { v: 0, u: 3, id: '00dhw set pump power' }, + { v: 288768, u: 0, id: '00dhw starts' }, + { v: 102151, u: 8, id: '00dhw active time' } + ] +}; + const emsesp_devicedata_99 = { data: [ { @@ -2026,7 +2026,7 @@ rest_server.get(LIST_NETWORKS_ENDPOINT, (req, res) => { res.json(list_networks); }); rest_server.get(SCAN_NETWORKS_ENDPOINT, (req, res) => { - res.sendStatus(202); + res.sendStatus(202); // reboot required }); // AP @@ -2100,6 +2100,7 @@ rest_server.get(VERIFY_AUTHORIZATION_ENDPOINT, (req, res) => { res.json(verify_authentication); }); rest_server.post(RESTART_ENDPOINT, (req, res) => { + console.log('command: restart'); res.sendStatus(200); }); rest_server.post(FACTORY_RESET_ENDPOINT, (req, res) => { @@ -2128,7 +2129,7 @@ rest_server.get(EMSESP_SETTINGS_ENDPOINT, (req, res) => { rest_server.post(EMSESP_SETTINGS_ENDPOINT, (req, res) => { settings = req.body; console.log('Write settings: ' + JSON.stringify(settings)); - // res.status(202).json(settings); // restart needed + // res.status(205).json(settings); // restart needed res.status(200).json(settings); // no restart needed }); rest_server.get(EMSESP_CORE_DATA_ENDPOINT, (req, res) => { @@ -2184,8 +2185,9 @@ rest_server.get(EMSESP_DEVICEDATA_ENDPOINT, (req, res) => { res.end(null, 'binary'); }); -rest_server.post(EMSESP_DEVICEENTITIES_ENDPOINT, (req, res) => { - const id = req.body.id; +rest_server.get(EMSESP_DEVICEENTITIES_ENDPOINT, (req, res) => { + const id = Number(req.query.id); + console.log('deviceentities for device ' + id + ' received'); let data = null; if (id === 1) { @@ -2233,6 +2235,7 @@ function updateMask(entity, de, dd) { } // find in dd, either looking for fullname or custom name + // console.log('looking for ' + fullname + ' in ' + dd.data); dd_objIndex = dd.data.findIndex((obj) => obj.id.slice(2) === fullname); if (dd_objIndex !== -1) { let changed = new Boolean(false); @@ -2432,10 +2435,12 @@ rest_server.post(EMSESP_WRITE_ANALOG_ENDPOINT, (req, res) => { res.sendStatus(200); }); -rest_server.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => { - const board_profile = req.body.board_profile; +rest_server.get(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => { + const board_profile = req.query.boardProfile; + // default values const data = { + board_profile: board_profile, led_gpio: settings.led_gpio, dallas_gpio: settings.dallas_gpio, rx_gpio: settings.rx_gpio, @@ -2559,9 +2564,10 @@ rest_server.post(EMSESP_BOARDPROFILE_ENDPOINT, (req, res) => { data.eth_clock_mode = 0; } - console.log('boardProfile POST. Sending back, profile: ' + board_profile + ', ' + 'data: ' + JSON.stringify(data)); + console.log('boardProfile GET. Sending back, profile: ' + board_profile + ', ' + 'data: ' + JSON.stringify(data)); - res.send(data); + // res.sendStatus(400); // send back an error, for testing + res.json(data); }); // EMS-ESP API specific diff --git a/src/web/WebCustomizationService.cpp b/src/web/WebCustomizationService.cpp index 0b6233f08..2a1932778 100644 --- a/src/web/WebCustomizationService.cpp +++ b/src/web/WebCustomizationService.cpp @@ -33,10 +33,12 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f , _fsPersistence(WebCustomization::read, WebCustomization::update, this, fs, EMSESP_CUSTOMIZATION_FILE) , _masked_entities_handler(CUSTOM_ENTITIES_PATH, securityManager->wrapCallback(std::bind(&WebCustomizationService::custom_entities, this, _1, _2), - AuthenticationPredicates::IS_AUTHENTICATED)) - , _device_entities_handler(DEVICE_ENTITIES_PATH, - securityManager->wrapCallback(std::bind(&WebCustomizationService::device_entities, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) { + server->on(DEVICE_ENTITIES_PATH, + HTTP_GET, + securityManager->wrapRequest(std::bind(&WebCustomizationService::device_entities, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); + + server->on(DEVICES_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebCustomizationService::devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); @@ -49,10 +51,6 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f _masked_entities_handler.setMaxContentLength(2048); _masked_entities_handler.setMaxJsonBufferSize(2048); server->addHandler(&_masked_entities_handler); - - _device_entities_handler.setMethod(HTTP_POST); - _device_entities_handler.setMaxContentLength(256); - server->addHandler(&_device_entities_handler); } // this creates the customization file, saving it to the FS @@ -165,7 +163,7 @@ StateUpdateResult WebCustomization::update(JsonObject & root, WebCustomization & void WebCustomizationService::reset_customization(AsyncWebServerRequest * request) { #ifndef EMSESP_STANDALONE if (LittleFS.remove(EMSESP_CUSTOMIZATION_FILE)) { - AsyncWebServerResponse * response = request->beginResponse(200); // OK + AsyncWebServerResponse * response = request->beginResponse(205); // restart needed request->send(response); EMSESP::system_.restart_requested(true); return; @@ -199,17 +197,21 @@ void WebCustomizationService::devices(AsyncWebServerRequest * request) { } // send back list of device entities -void WebCustomizationService::device_entities(AsyncWebServerRequest * request, JsonVariant & json) { - if (json.is()) { +void WebCustomizationService::device_entities(AsyncWebServerRequest * request) { + uint8_t id; + if (request->hasParam(F_(id))) { + id = Helpers::atoint(request->getParam(F_(id))->value().c_str()); // get id from url + size_t buffer = EMSESP_JSON_SIZE_XXXXLARGE; auto * response = new MsgpackAsyncJsonResponse(true, buffer); + while (!response->getSize()) { delete response; buffer -= 1024; response = new MsgpackAsyncJsonResponse(true, buffer); } for (const auto & emsdevice : EMSESP::emsdevices) { - if (emsdevice->unique_id() == json["id"]) { + if (emsdevice->unique_id() == id) { #ifndef EMSESP_STANDALONE JsonArray output = response->getRoot(); emsdevice->generate_values_web_customization(output); @@ -319,7 +321,7 @@ void WebCustomizationService::custom_entities(AsyncWebServerRequest * request, J } } - AsyncWebServerResponse * response = request->beginResponse(need_reboot ? 201 : 200); // OK + AsyncWebServerResponse * response = request->beginResponse(need_reboot ? 205 : 200); // reboot or just OK request->send(response); } diff --git a/src/web/WebCustomizationService.h b/src/web/WebCustomizationService.h index 7e977959a..85435496c 100644 --- a/src/web/WebCustomizationService.h +++ b/src/web/WebCustomizationService.h @@ -24,9 +24,9 @@ // GET #define DEVICES_SERVICE_PATH "/rest/devices" #define EMSESP_CUSTOMIZATION_SERVICE_PATH "/rest/customization" +#define DEVICE_ENTITIES_PATH "/rest/deviceEntities" // POST -#define DEVICE_ENTITIES_PATH "/rest/deviceEntities" #define CUSTOM_ENTITIES_PATH "/rest/customEntities" #define RESET_CUSTOMIZATION_SERVICE_PATH "/rest/resetCustomizations" @@ -91,13 +91,13 @@ class WebCustomizationService : public StatefulService { // GET void devices(AsyncWebServerRequest * request); + void device_entities(AsyncWebServerRequest * request); // POST void custom_entities(AsyncWebServerRequest * request, JsonVariant & json); - void device_entities(AsyncWebServerRequest * request, JsonVariant & json); - void reset_customization(AsyncWebServerRequest * request); + void reset_customization(AsyncWebServerRequest * request); // command - AsyncCallbackJsonWebHandler _masked_entities_handler, _device_entities_handler; + AsyncCallbackJsonWebHandler _masked_entities_handler; }; } // namespace emsesp diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index 85e6a0353..77ec4566b 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -327,6 +327,7 @@ void WebDataService::write_temperature_sensor(AsyncWebServerRequest * request, J if (EMSESP::system_.fahrenheit()) { offset10 = offset / 0.18; } + ok = EMSESP::temperaturesensor_.update(id, name, offset10); } diff --git a/src/web/WebSettingsService.cpp b/src/web/WebSettingsService.cpp index 759d47a32..51d0537cc 100644 --- a/src/web/WebSettingsService.cpp +++ b/src/web/WebSettingsService.cpp @@ -26,12 +26,11 @@ using namespace std::placeholders; // for `_1` etc WebSettingsService::WebSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager) : _httpEndpoint(WebSettings::read, WebSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager) - , _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE) - , _boardProfileHandler(EMSESP_BOARD_PROFILE_SERVICE_PATH, - securityManager->wrapCallback(std::bind(&WebSettingsService::board_profile, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) { - _boardProfileHandler.setMethod(HTTP_POST); - _boardProfileHandler.setMaxContentLength(256); - server->addHandler(&_boardProfileHandler); + , _fsPersistence(WebSettings::read, WebSettings::update, this, fs, EMSESP_SETTINGS_FILE) { + // GET + server->on(EMSESP_BOARD_PROFILE_SERVICE_PATH, + HTTP_GET, + securityManager->wrapRequest(std::bind(&WebSettingsService::board_profile, this, _1), AuthenticationPredicates::IS_ADMIN)); addUpdateHandler([&](const String & originId) { onUpdate(); }, false); } @@ -342,29 +341,29 @@ void WebSettingsService::save() { } // build the json profile to send back -void WebSettingsService::board_profile(AsyncWebServerRequest * request, JsonVariant & json) { - if (json.is()) { +void WebSettingsService::board_profile(AsyncWebServerRequest * request) { + if (request->hasParam("boardProfile")) { + std::string board_profile = request->getParam("boardProfile")->value().c_str(); + auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_MEDIUM); JsonObject root = response->getRoot(); - if (json.containsKey("board_profile")) { - String board_profile = json["board_profile"]; - std::vector data; // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode - (void)System::load_board_profile(data, board_profile.c_str()); - root["led_gpio"] = data[0]; - root["dallas_gpio"] = data[1]; - root["rx_gpio"] = data[2]; - root["tx_gpio"] = data[3]; - root["pbutton_gpio"] = data[4]; - root["phy_type"] = data[5]; - root["eth_power"] = data[6]; - root["eth_phy_addr"] = data[7]; - root["eth_clock_mode"] = data[8]; + std::vector data; // led, dallas, rx, tx, button, phy_type, eth_power, eth_phy_addr, eth_clock_mode + (void)System::load_board_profile(data, board_profile); + root["board_profile"] = board_profile; + root["led_gpio"] = data[0]; + root["dallas_gpio"] = data[1]; + root["rx_gpio"] = data[2]; + root["tx_gpio"] = data[3]; + root["pbutton_gpio"] = data[4]; + root["phy_type"] = data[5]; + root["eth_power"] = data[6]; + root["eth_phy_addr"] = data[7]; + root["eth_clock_mode"] = data[8]; - response->setLength(); - request->send(response); - return; - } + response->setLength(); + request->send(response); + return; } AsyncWebServerResponse * response = request->beginResponse(200); diff --git a/src/web/WebSettingsService.h b/src/web/WebSettingsService.h index 61af4161b..0e892328e 100644 --- a/src/web/WebSettingsService.h +++ b/src/web/WebSettingsService.h @@ -122,11 +122,10 @@ class WebSettingsService : public StatefulService { void save(); private: - HttpEndpoint _httpEndpoint; - FSPersistence _fsPersistence; - AsyncCallbackJsonWebHandler _boardProfileHandler; + HttpEndpoint _httpEndpoint; + FSPersistence _fsPersistence; - void board_profile(AsyncWebServerRequest * request, JsonVariant & json); + void board_profile(AsyncWebServerRequest * request); void onUpdate(); }; From adc4760b5fdcf444ed1a0a6d5b6c1e76953d94b0 Mon Sep 17 00:00:00 2001 From: proddy Date: Wed, 14 Jun 2023 23:30:52 +0200 Subject: [PATCH 016/163] alova - implementing UpdateState --- interface/package.json | 2 +- interface/src/api/ap.ts | 3 +- interface/src/api/endpoints.ts | 8 +- interface/src/framework/ap/APStatusForm.tsx | 2 +- .../framework/system/GeneralFileUpload.tsx | 80 ++++++------ interface/src/project/DashboardStatus.tsx | 9 -- interface/src/project/HelpInformation.tsx | 35 +++--- .../src/project/SettingsCustomization.tsx | 6 +- .../project/SettingsCustomizationDialog.tsx | 2 +- interface/src/project/SettingsScheduler.tsx | 119 ++++++++---------- .../src/project/SettingsSchedulerDialog.tsx | 2 +- interface/src/project/api.ts | 101 ++++++++------- interface/src/project/types.ts | 6 +- interface/src/project/validators.ts | 2 + interface/src/utils/useRest2.ts | 8 +- interface/yarn.lock | 20 +-- mock-api/server.js | 34 +++-- 17 files changed, 202 insertions(+), 237 deletions(-) diff --git a/interface/package.json b/interface/package.json index dc09f548f..5c4fb7ae5 100644 --- a/interface/package.json +++ b/interface/package.json @@ -40,7 +40,7 @@ "react-dom": "latest", "react-dropzone": "^14.2.3", "react-icons": "^4.9.0", - "react-router-dom": "^6.12.1", + "react-router-dom": "^6.13.0", "react-toastify": "^9.1.3", "sockette": "^2.0.6", "typesafe-i18n": "^5.24.3", diff --git a/interface/src/api/ap.ts b/interface/src/api/ap.ts index f54ad1f59..a314227c2 100644 --- a/interface/src/api/ap.ts +++ b/interface/src/api/ap.ts @@ -5,11 +5,10 @@ import type { APSettings, APStatus } from 'types'; export const readAPStatus = () => alovaInstance.Get('/apStatus'); -// TODO change AXIOS to Alova +// TODO change APSettings AXIOS to Alova export function readAPSettings(): AxiosPromise { return AXIOS.get('/apSettings'); } - export function updateAPSettings(apSettings: APSettings): AxiosPromise { return AXIOS.post('/apSettings', apSettings); } diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index 3af7431db..987978963 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -6,9 +6,6 @@ import { unpack } from '../api/unpack'; import type { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios'; -export const WS_BASE_URL = '/ws/'; -export const ES_BASE_URL = '/es/'; - export const REST_BASE_URL = '/rest/'; export const API_BASE_URL = '/api/'; @@ -16,13 +13,14 @@ export const ACCESS_TOKEN = 'access_token'; const location = window.location; const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; +export const WS_BASE_URL = '/ws/'; +export const ES_BASE_URL = '/es/'; export const WEB_SOCKET_ROOT = webProtocol + '//' + location.host + WS_BASE_URL; export const EVENT_SOURCE_ROOT = location.protocol + '//' + location.host + ES_BASE_URL; export const alovaInstance = createAlova({ - baseURL: '/rest/', statesHook: ReactHook, - timeout: 50000, + timeout: 5000, requestAdapter: xhrRequestAdapter(), beforeRequest(method) { if (localStorage.getItem(ACCESS_TOKEN)) { diff --git a/interface/src/framework/ap/APStatusForm.tsx b/interface/src/framework/ap/APStatusForm.tsx index 611353513..ec754f796 100644 --- a/interface/src/framework/ap/APStatusForm.tsx +++ b/interface/src/framework/ap/APStatusForm.tsx @@ -28,7 +28,7 @@ export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => { }; const APStatusForm: FC = () => { - const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); + const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); const { LL } = useI18nContext(); diff --git a/interface/src/framework/system/GeneralFileUpload.tsx b/interface/src/framework/system/GeneralFileUpload.tsx index 11ade4405..9c0a04e6c 100644 --- a/interface/src/framework/system/GeneralFileUpload.tsx +++ b/interface/src/framework/system/GeneralFileUpload.tsx @@ -1,5 +1,6 @@ import DownloadIcon from '@mui/icons-material/GetApp'; import { Typography, Button, Box } from '@mui/material'; +import { useRequest } from 'alova'; import { toast } from 'react-toastify'; import type { FileUploadConfig } from 'api/endpoints'; import type { AxiosPromise } from 'axios'; @@ -9,7 +10,6 @@ import { SingleUpload, useFileUpload } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import * as EMSESP from 'project/api'; -import { extractErrorMessage } from 'utils'; interface UploadFileProps { uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise; @@ -18,6 +18,19 @@ interface UploadFileProps { const GeneralFileUpload: FC = ({ uploadGeneralFile }) => { const [uploadFile, cancelUpload, uploading, uploadProgress, md5] = useFileUpload({ upload: uploadGeneralFile }); + const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest(EMSESP.getSettings(), { + immediate: false + }); + const { send: getCustomizations, onSuccess: onSuccessgetCustomizations } = useRequest(EMSESP.getCustomizations(), { + immediate: false + }); + const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest(EMSESP.getEntities(), { + immediate: false + }); + const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(EMSESP.getSchedule(), { + immediate: false + }); + const { LL } = useI18nContext(); const saveFile = (json: any, endpoint: string) => { @@ -35,56 +48,41 @@ const GeneralFileUpload: FC = ({ uploadGeneralFile }) => { toast.info(LL.DOWNLOAD_SUCCESSFUL()); }; + onSuccessGetSettings((event) => { + saveFile(event.data, 'settings'); + }); + onSuccessgetCustomizations((event) => { + saveFile(event.data, 'customizations'); + }); + onSuccessGetEntities((event) => { + saveFile(event.data, 'entities'); + }); + onSuccessGetSchedule((event) => { + saveFile(event.data, 'schedule'); + }); + const downloadSettings = async () => { - try { - const response = await EMSESP.getSettings(); - if (response.status !== 200) { - toast.error(LL.PROBLEM_LOADING()); - } else { - saveFile(response.data, 'settings'); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } + await getSettings().catch((error) => { + toast.error(error.message); + }); }; const downloadCustomizations = async () => { - try { - const response = await EMSESP.getCustomizations(); - if (response.status !== 200) { - toast.error(LL.PROBLEM_LOADING()); - } else { - saveFile(response.data, 'customizations'); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } + await getCustomizations().catch((error) => { + toast.error(error.message); + }); }; const downloadEntities = async () => { - try { - const response = await EMSESP.getEntities(); - if (response.status !== 200) { - toast.error(LL.PROBLEM_LOADING()); - } else { - saveFile(response.data, 'entities'); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } + await getEntities().catch((error) => { + toast.error(error.message); + }); }; const downloadSchedule = async () => { - try { - const response = await EMSESP.getSchedule(); - if (response.status !== 200) { - toast.error(LL.PROBLEM_LOADING()); - } else { - saveFile(response.data, 'schedule'); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } + await getSchedule().catch((error) => { + toast.error(error.message); + }); }; return ( diff --git a/interface/src/project/DashboardStatus.tsx b/interface/src/project/DashboardStatus.tsx index 9ef093b98..084c78820 100644 --- a/interface/src/project/DashboardStatus.tsx +++ b/interface/src/project/DashboardStatus.tsx @@ -170,15 +170,6 @@ const DashboardStatus: FC = () => { toast.error(err.message); }); setConfirmScan(false); - - // try { - // await EMSESP.scanDevices(); - // toast.info(LL.SCANNING() + '...'); - // } catch (error) { - // toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - // } finally { - // setConfirmScan(false); - // } }; const renderScanDialog = () => ( diff --git a/interface/src/project/HelpInformation.tsx b/interface/src/project/HelpInformation.tsx index e849ac19e..b85720508 100644 --- a/interface/src/project/HelpInformation.tsx +++ b/interface/src/project/HelpInformation.tsx @@ -4,6 +4,7 @@ import DownloadIcon from '@mui/icons-material/GetApp'; import GitHubIcon from '@mui/icons-material/GitHub'; import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone'; import { Typography, Button, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@mui/material'; +import { useRequest } from 'alova'; import { toast } from 'react-toastify'; import * as EMSESP from './api'; import type { FC } from 'react'; @@ -11,16 +12,19 @@ import type { FC } from 'react'; import { SectionContent } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { extractErrorMessage } from 'utils'; const HelpInformation: FC = () => { const { LL } = useI18nContext(); - const saveFile = (json: any, endpoint: string) => { + const { send: API, onSuccess: onSuccessAPI } = useRequest((data) => EMSESP.API(data), { + immediate: false + }); + + onSuccessAPI((event) => { const a = document.createElement('a'); - const filename = 'emsesp_' + endpoint + '.txt'; + const filename = 'emsesp_info.txt'; a.href = URL.createObjectURL( - new Blob([JSON.stringify(json, null, 2)], { + new Blob([JSON.stringify(event.data, null, 2)], { type: 'text/plain' }) ); @@ -29,23 +33,12 @@ const HelpInformation: FC = () => { a.click(); document.body.removeChild(a); toast.info(LL.DOWNLOAD_SUCCESSFUL()); - }; + }); - const callAPI = async (endpoint: string) => { - try { - const response = await EMSESP.API({ - device: 'system', - entity: endpoint, - id: 0 - }); - if (response.status !== 200) { - toast.error(LL.PROBLEM_LOADING()); - } else { - saveFile(response.data, endpoint); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } + const callAPI = async () => { + await API({ device: 'system', entity: 'info', id: 0 }).catch((error) => { + toast.error(error.message); + }); }; return ( @@ -96,7 +89,7 @@ const HelpInformation: FC = () => { size="small" variant="outlined" color="primary" - onClick={() => callAPI('info')} + onClick={() => callAPI()} > {LL.SUPPORT_INFO()} diff --git a/interface/src/project/SettingsCustomization.tsx b/interface/src/project/SettingsCustomization.tsx index 24fa60ecc..7883cf484 100644 --- a/interface/src/project/SettingsCustomization.tsx +++ b/interface/src/project/SettingsCustomization.tsx @@ -61,7 +61,7 @@ const SettingsCustomization: FC = () => { immediate: false }); - const { data: devices } = useRequest(EMSESP.readDevices()); + const { data: devices } = useRequest(EMSESP.readDevices); const { send: writeCustomEntities } = useRequest((data) => EMSESP.writeCustomEntities(data), { immediate: false }); @@ -236,8 +236,9 @@ const SettingsCustomization: FC = () => { }; const maskDisabled = (set: boolean) => { + // TODO fix update! updateDeviceEntities( - deviceEntities?.map(function (de) { + deviceEntities.map(function (de) { if ((de.m & selectedFilters || !selectedFilters) && de.id.toLowerCase().includes(search.toLowerCase())) { return { ...de, @@ -329,7 +330,6 @@ const SettingsCustomization: FC = () => { } } ); - // does this work or use onSuccess hook? setOriginalSettings(deviceEntities); } }; diff --git a/interface/src/project/SettingsCustomizationDialog.tsx b/interface/src/project/SettingsCustomizationDialog.tsx index 065a2dd00..50601785d 100644 --- a/interface/src/project/SettingsCustomizationDialog.tsx +++ b/interface/src/project/SettingsCustomizationDialog.tsx @@ -124,7 +124,7 @@ const SettingsCustomizationDialog = ({ open, onClose, onSave, selectedItem }: Se {LL.CANCEL()} diff --git a/interface/src/project/SettingsScheduler.tsx b/interface/src/project/SettingsScheduler.tsx index 59fdee05b..8c93252e4 100644 --- a/interface/src/project/SettingsScheduler.tsx +++ b/interface/src/project/SettingsScheduler.tsx @@ -6,6 +6,7 @@ import WarningIcon from '@mui/icons-material/Warning'; import { Box, Typography, Divider, Stack, Button } from '@mui/material'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; +import { updateState, useRequest } from 'alova'; import { useState, useEffect, useCallback } from 'react'; import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -19,23 +20,30 @@ import type { FC } from 'react'; import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { extractErrorMessage } from 'utils'; const SettingsScheduler: FC = () => { const { LL, locale } = useI18nContext(); const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); - const [schedule, setSchedule] = useState([]); const [selectedScheduleItem, setSelectedScheduleItem] = useState(); const [dow, setDow] = useState([]); - const [errorMessage, setErrorMessage] = useState(); const [creating, setCreating] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); + const { + data: schedule, + send: fetchSchedule, + error + } = useRequest(EMSESP.readSchedule, { + initialData: [] + }); + + const { send: writeSchedule } = useRequest((data) => EMSESP.writeSchedule(data), { immediate: false }); + function hasScheduleChanged(si: ScheduleItem) { return ( si.id !== si.o_id || - (si?.name || '') !== (si?.o_name || '') || + (si.name || '') !== (si.o_name || '') || si.active !== si.o_active || si.deleted !== si.o_deleted || si.flags !== si.o_flags || @@ -46,10 +54,13 @@ const SettingsScheduler: FC = () => { } useEffect(() => { - if (schedule) { - setNumChanges(schedule ? schedule.filter((si) => hasScheduleChanged(si)).length : 0); - } - }, [schedule]); + const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }); + const days = [1, 2, 3, 4, 5, 6, 7].map((day) => { + const dd = day < 10 ? `0${day}` : day; + return new Date(`2017-01-${dd}T00:00:00+00:00`); + }); + setDow(days.map((date) => formatter.format(date))); + }, [locale]); const schedule_theme = useTheme({ Table: ` @@ -96,63 +107,30 @@ const SettingsScheduler: FC = () => { ` }); - const fetchSchedule = useCallback(async () => { - try { - const response = await EMSESP.readSchedule(); - setSchedule( - response.data.schedule.map((si) => ({ - ...si, - o_id: si.id, - o_active: si.active, - o_deleted: si.deleted, - o_flags: si.flags, - o_time: si.time, - o_cmd: si.cmd, - o_value: si.value, - o_name: si.name - })) - ); - } catch (error) { - setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } - }, [LL]); - - useEffect(() => { - const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }); - const days = [1, 2, 3, 4, 5, 6, 7].map((day) => { - const dd = day < 10 ? `0${day}` : day; - return new Date(`2017-01-${dd}T00:00:00+00:00`); - }); - setDow(days.map((date) => formatter.format(date))); - void fetchSchedule(); - }, [locale, fetchSchedule]); - const saveSchedule = async () => { - if (schedule) { - try { - const response = await EMSESP.writeSchedule({ - schedule: schedule - .filter((si) => !si.deleted) - .map((condensed_si) => ({ - id: condensed_si.id, - active: condensed_si.active, - flags: condensed_si.flags, - time: condensed_si.time, - cmd: condensed_si.cmd, - value: condensed_si.value, - name: condensed_si.name - })) - }); - if (response.status === 200) { - toast.success(LL.SCHEDULE_UPDATED()); - } else { - toast.error(LL.PROBLEM_UPDATING()); - } + await writeSchedule( + schedule + .filter((si) => !si.deleted) + .map((condensed_si) => ({ + id: condensed_si.id, + active: condensed_si.active, + flags: condensed_si.flags, + time: condensed_si.time, + cmd: condensed_si.cmd, + value: condensed_si.value, + name: condensed_si.name + })) + ) + .then(() => { + toast.success(LL.SCHEDULE_UPDATED()); + }) + .catch((err) => { + toast.error(err.message); + }) + .finally(async () => { await fetchSchedule(); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } - } + setNumChanges(0); + }); }; const editScheduleItem = useCallback((si: ScheduleItem) => { @@ -167,11 +145,14 @@ const SettingsScheduler: FC = () => { const onDialogSave = (updatedItem: ScheduleItem) => { setDialogOpen(false); - if (schedule && creating) { - setSchedule([...schedule.filter((si) => creating || si.o_id !== updatedItem.o_id), updatedItem]); - } else { - setSchedule(schedule?.map((si) => (si.id === updatedItem.id ? { ...si, ...updatedItem } : si))); - } + + updateState('schedule', (data) => { + const new_data = creating + ? [...data.filter((si) => creating || si.o_id !== updatedItem.o_id), updatedItem] + : data.map((si) => (si.id === updatedItem.id ? { ...si, ...updatedItem } : si)); + setNumChanges(new_data.filter((si) => hasScheduleChanged(si)).length); + return new_data; + }); }; const addScheduleItem = () => { @@ -191,7 +172,7 @@ const SettingsScheduler: FC = () => { const renderSchedule = () => { if (!schedule) { - return ; + return ; } const dayBox = (si: ScheduleItem, flag: number) => ( diff --git a/interface/src/project/SettingsSchedulerDialog.tsx b/interface/src/project/SettingsSchedulerDialog.tsx index 49870a4d0..d074848a4 100644 --- a/interface/src/project/SettingsSchedulerDialog.tsx +++ b/interface/src/project/SettingsSchedulerDialog.tsx @@ -215,7 +215,7 @@ const SettingsSchedulerDialog = ({ /> alovaInstance.Get(`/coreData`); +export const readCoreData = () => alovaInstance.Get(`/rest/coreData`); export const readDeviceData = (id: number) => - alovaInstance.Get('/deviceData', { + alovaInstance.Get('/rest/deviceData', { params: { id }, responseType: 'arraybuffer' // uses msgpack }); -export const writeDeviceValue = (data: any) => alovaInstance.Post('/writeDeviceValue', data); +export const writeDeviceValue = (data: any) => alovaInstance.Post('/rest/writeDeviceValue', data); // SettingsApplication -export const readSettings = () => alovaInstance.Get('/settings'); -export const writeSettings = (data: any) => alovaInstance.Post('/settings', data); +export const readSettings = () => alovaInstance.Get('/rest/settings'); +export const writeSettings = (data: any) => alovaInstance.Post('/rest/settings', data); export const getBoardProfile = (boardProfile: string) => - alovaInstance.Get('/boardProfile', { + alovaInstance.Get('/rest/boardProfile', { params: { boardProfile } }); -export const restart = () => alovaInstance.Post('/restart'); +export const restart = () => alovaInstance.Post('/rest/restart'); // SettingsCustomization export const readDeviceEntities = (id: number) => - alovaInstance.Get('/deviceEntities', { + alovaInstance.Get('/rest/deviceEntities', { params: { id }, responseType: 'arraybuffer', - transformData(rawData: any) { - return rawData.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })); + transformData(data: any) { + return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })); } }); -export const readDevices = () => alovaInstance.Get('/devices'); -export const resetCustomizations = () => alovaInstance.Post('/resetCustomizations'); -export const writeCustomEntities = (data: any) => alovaInstance.Post('/customEntities', data); +export const readDevices = () => alovaInstance.Get('/rest/devices'); +export const resetCustomizations = () => alovaInstance.Post('/rest/resetCustomizations'); +export const writeCustomEntities = (data: any) => alovaInstance.Post('/rest/customEntities', data); // DashboardSensors -export const readSensorData = () => alovaInstance.Get('/sensorData'); -export const writeTemperatureSensor = (ts: WriteTemperatureSensor) => alovaInstance.Post('/writeTemperatureSensor', ts); -export const writeAnalogSensor = (as: WriteAnalogSensor) => alovaInstance.Post('/writeAnalogSensor', as); - -// TODO think about naming, get... and not get etc... +export const readSensorData = () => alovaInstance.Get('/rest/sensorData'); +export const writeTemperatureSensor = (ts: WriteTemperatureSensor) => + alovaInstance.Post('/rest/writeTemperatureSensor', ts); +export const writeAnalogSensor = (as: WriteAnalogSensor) => alovaInstance.Post('/rest/writeAnalogSensor', as); // DashboardStatus -export const readStatus = () => alovaInstance.Get('/status'); -export const scanDevices = () => alovaInstance.Post('/scanDevices'); +export const readStatus = () => alovaInstance.Get('/rest/status'); +export const scanDevices = () => alovaInstance.Post('/rest/scanDevices'); -// ALOVA goes here.... +// HelpInformation +export const API = (apiCall: APIcall) => alovaInstance.Post('/api', apiCall); -export function API(apiCall: APIcall): AxiosPromise { - return AXIOS_API.post('/', apiCall); // command -} +// GeneralFileUpload +export const getSettings = () => alovaInstance.Get('/rest/getSettings'); +export const getCustomizations = () => alovaInstance.Get('/rest/getCustomizations'); +export const getEntities = () => alovaInstance.Get('/rest/getEntities'); +export const getSchedule = () => alovaInstance.Get('/rest/getSchedule'); -export function getSettings(): AxiosPromise { - return AXIOS.get('/getSettings'); -} - -export function getCustomizations(): AxiosPromise { - return AXIOS.get('/getCustomizations'); -} - -export function getSchedule(): AxiosPromise { - return AXIOS.get('/getSchedule'); -} - -export function readSchedule(): AxiosPromise { - return AXIOS.get('/schedule'); -} - -export function writeSchedule(schedule: Schedule): AxiosPromise { - return AXIOS.post('/schedule', schedule); -} - -export function getEntities(): AxiosPromise { - return AXIOS.get('/getEntities'); -} +// SettingsScheduler +export const readSchedule = () => + alovaInstance.Get('/rest/schedule', { + name: 'schedule', + transformData(data: any) { + return data.map((si: ScheduleItem) => ({ + ...si, + o_id: si.id, + o_active: si.active, + o_deleted: si.deleted, + o_flags: si.flags, + o_time: si.time, + o_cmd: si.cmd, + o_value: si.value, + o_name: si.name + })); + } + }); +export const writeSchedule = (data: any) => alovaInstance.Post('/rest/schedule', data); +// SettingsCustomization +// TODO move last Entities* to Alova export function readEntities(): AxiosPromise { return AXIOS.get('/entities'); } - export function writeEntities(entities: Entities): AxiosPromise { return AXIOS.post('/entities', entities); } diff --git a/interface/src/project/types.ts b/interface/src/project/types.ts index a50b0281c..bdbbbd167 100644 --- a/interface/src/project/types.ts +++ b/interface/src/project/types.ts @@ -292,7 +292,7 @@ export interface ScheduleItem { time: string; cmd: string; value: string; - name?: string; // optional + name: string; // optional o_id?: number; o_active?: boolean; o_deleted?: boolean; @@ -303,10 +303,6 @@ export interface ScheduleItem { o_name?: string; } -export interface Schedule { - schedule: ScheduleItem[]; -} - export enum ScheduleFlag { SCHEDULE_SUN = 1, SCHEDULE_MON = 2, diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index 32b4f2505..bc7147a1d 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -90,6 +90,8 @@ export const schedulerItemValidation = () => new Schema({ name: [ { + // TODO name must be unique - add check + required: true, type: 'string', pattern: /^[a-zA-Z0-9_\\.]{0,15}$/, message: "Must be <15 characters: alpha numeric, '_' or '.'" diff --git a/interface/src/utils/useRest2.ts b/interface/src/utils/useRest2.ts index 7e8688da0..b71b1c9cf 100644 --- a/interface/src/utils/useRest2.ts +++ b/interface/src/utils/useRest2.ts @@ -3,15 +3,11 @@ import { useState } from 'react'; import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import { toast } from 'react-toastify'; -import type { AlovaXHRRequestConfig, AlovaXHRResponse, AlovaXHRResponseHeaders } from '@alova/adapter-xhr'; - import { useI18nContext } from 'i18n/i18n-react'; export interface RestRequestOptions2 { - read: () => Method, AlovaXHRResponseHeaders>; - update: ( - value: D - ) => Method, AlovaXHRResponseHeaders>; + read: () => Method; + update: (value: D) => Method; } // TODO rename back to useRest diff --git a/interface/yarn.lock b/interface/yarn.lock index ca56b2b5d..6803c97b3 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1567,7 +1567,7 @@ __metadata: react-dom: latest react-dropzone: ^14.2.3 react-icons: ^4.9.0 - react-router-dom: ^6.12.1 + react-router-dom: ^6.13.0 react-toastify: ^9.1.3 rollup-plugin-visualizer: ^5.9.2 sockette: ^2.0.6 @@ -4807,27 +4807,27 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.12.1": - version: 6.12.1 - resolution: "react-router-dom@npm:6.12.1" +"react-router-dom@npm:^6.13.0": + version: 6.13.0 + resolution: "react-router-dom@npm:6.13.0" dependencies: "@remix-run/router": 1.6.3 - react-router: 6.12.1 + react-router: 6.13.0 peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 4de0d4159a9dd2de0477d7608e9055262ebdd5dc41fc918b44b38170cc8ed407fa7dbb73bdb85e9469614502ad4772523b8a7f32c2609e62973feb41b70d871b + checksum: 760c57063e305b0623f7b39bca3c7f5c169af557867eac7d1b19419111f7b6a9ed95d2083305c40ce98fd3be27e82c61f5674c90dd066a67653cb15901d00991 languageName: node linkType: hard -"react-router@npm:6.12.1": - version: 6.12.1 - resolution: "react-router@npm:6.12.1" +"react-router@npm:6.13.0": + version: 6.13.0 + resolution: "react-router@npm:6.13.0" dependencies: "@remix-run/router": 1.6.3 peerDependencies: react: ">=16.8" - checksum: 33a39eca122f3519f1aa91d8e6585fab669b3b06b621aa624f7db1ffda6b84676facd57028652f2fba7325a208abef906a5ba6c91d55d5a5d29993583d82dd61 + checksum: c58b4b943d3a76d328c6ef299567a85d84a69b417f5f5d07596b0b9bd40632a828591d6f470b0f233ff9095f489f9fd5c8666fb03513e9470927447169a91d8a languageName: node linkType: hard diff --git a/mock-api/server.js b/mock-api/server.js index c805f7bc6..3b42ce97a 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -1798,8 +1798,9 @@ let emsesp_entities = { }; // SCHEDULE -let emsesp_schedule = { - schedule: [ +let emsesp_schedule = + // schedule: [ + [ { id: 1, active: true, @@ -1836,11 +1837,9 @@ let emsesp_schedule = { value: '', name: 'auto_restart' } - ] -}; + ]; // CUSTOMIZATIONS - const emsesp_deviceentities_1 = [{}]; const emsesp_deviceentities_3 = [{}]; const emsesp_deviceentities_5 = [{}]; @@ -2327,7 +2326,7 @@ rest_server.post(EMSESP_CUSTOM_ENTITIES_ENDPOINT, (req, res) => { rest_server.post(EMSESP_WRITE_SCHEDULE_ENDPOINT, (req, res) => { console.log('write schedule'); - console.log(req.body.schedule); + console.log(req.body); emsesp_schedule = req.body; res.sendStatus(200); }); @@ -2625,6 +2624,7 @@ rest_server.post(API_ENDPOINT_ROOT, (req, res) => { if (req.body.device === 'system') { if (req.body.entity === 'info') { console.log('sending system info: ' + JSON.stringify(emsesp_info)); + res.json(emsesp_info); } else if (req.body.entity === 'settings') { console.log('sending system settings: ' + JSON.stringify(settings)); res.json(settings); @@ -2652,25 +2652,37 @@ rest_server.get(SYSTEM_INFO_ENDPOINT, (req, res) => { const GET_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'getSettings'; rest_server.get(GET_SETTINGS_ENDPOINT, (req, res) => { - console.log('System Settings:'); + console.log('getSettings:'); res.json(settings); }); const GET_CUSTOMIZATIONS_ENDPOINT = REST_ENDPOINT_ROOT + 'getCustomizations'; rest_server.get(GET_CUSTOMIZATIONS_ENDPOINT, (req, res) => { - console.log('Customization'); + console.log('getCustomization'); // not implemented yet res.sendStatus(200); }); -const GET_SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'schedule'; +const GET_ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'getEntities'; +rest_server.get(GET_ENTITIES_ENDPOINT, (req, res) => { + console.log('getEntities'); + res.json(emsesp_entities); +}); + +const GET_SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'getSchedule'; rest_server.get(GET_SCHEDULE_ENDPOINT, (req, res) => { + console.log('getSchedule'); + res.json(emsesp_schedule); +}); + +const SCHEDULE_ENDPOINT = REST_ENDPOINT_ROOT + 'schedule'; +rest_server.get(SCHEDULE_ENDPOINT, (req, res) => { console.log('Sending Schedule data'); res.json(emsesp_schedule); }); -const GET_ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'entities'; -rest_server.get(GET_ENTITIES_ENDPOINT, (req, res) => { +const ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'entities'; +rest_server.get(ENTITIES_ENDPOINT, (req, res) => { console.log('Sending Entities data'); res.json(emsesp_entities); }); From f58dbf6ec1091f6f316e453c7adc09feb5364f40 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 15 Jun 2023 16:08:25 +0200 Subject: [PATCH 017/163] alova - update comments --- interface/src/AuthenticatedRouting.tsx | 1 + interface/src/api/authentication.ts | 2 +- interface/src/api/endpoints.ts | 13 +- interface/src/api/features.ts | 1 + interface/src/api/mqtt.ts | 1 + interface/src/api/network.ts | 1 + interface/src/api/ntp.ts | 1 + interface/src/api/security.ts | 1 + interface/src/api/system.ts | 1 + .../src/framework/mqtt/MqttStatusForm.tsx | 2 +- .../framework/network/NetworkStatusForm.tsx | 2 +- interface/src/framework/ntp/NTPStatusForm.tsx | 2 +- interface/src/framework/system/SystemLog.tsx | 2 +- .../src/framework/system/SystemStatusForm.tsx | 2 +- .../src/project/SettingsCustomization.tsx | 9 +- interface/src/project/SettingsEntities.tsx | 115 +++++++----------- interface/src/project/api.ts | 59 +++++---- interface/src/project/validators.ts | 2 +- interface/src/utils/binding.ts | 6 - mock-api/server.js | 31 +++-- 20 files changed, 117 insertions(+), 137 deletions(-) diff --git a/interface/src/AuthenticatedRouting.tsx b/interface/src/AuthenticatedRouting.tsx index eaf6041f2..7474821c8 100644 --- a/interface/src/AuthenticatedRouting.tsx +++ b/interface/src/AuthenticatedRouting.tsx @@ -33,6 +33,7 @@ const AuthenticatedRouting: FC = () => { ); useEffect(() => { + // TODO how to replace AXIOS.interceptors.response.use ??? const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError); return () => AXIOS.interceptors.response.eject(axiosHandlerId); }, [handleApiResponseError]); diff --git a/interface/src/api/authentication.ts b/interface/src/api/authentication.ts index 9b8b01030..d66042236 100644 --- a/interface/src/api/authentication.ts +++ b/interface/src/api/authentication.ts @@ -9,10 +9,10 @@ import type { Me, SignInRequest, SignInResponse } from 'types'; export const SIGN_IN_PATHNAME = 'loginPathname'; export const SIGN_IN_SEARCH = 'loginSearch'; +// TODO move to Alova export function verifyAuthorization(): AxiosPromise { return AXIOS.get('/verifyAuthorization'); } - export function signIn(request: SignInRequest): AxiosPromise { return AXIOS.post('/signIn', request); } diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index 987978963..cd1b4b574 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -4,23 +4,20 @@ import ReactHook from 'alova/react'; import axios from 'axios'; import { unpack } from '../api/unpack'; +// TODO axios can be removed import type { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios'; - export const REST_BASE_URL = '/rest/'; export const API_BASE_URL = '/api/'; export const ACCESS_TOKEN = 'access_token'; -const location = window.location; -const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; -export const WS_BASE_URL = '/ws/'; -export const ES_BASE_URL = '/es/'; -export const WEB_SOCKET_ROOT = webProtocol + '//' + location.host + WS_BASE_URL; -export const EVENT_SOURCE_ROOT = location.protocol + '//' + location.host + ES_BASE_URL; +const host = window.location.host; +export const WEB_SOCKET_ROOT = 'ws://' + host + '/ws/'; +export const EVENT_SOURCE_ROOT = 'http://' + host + '/es/'; export const alovaInstance = createAlova({ statesHook: ReactHook, - timeout: 5000, + timeout: 3000, requestAdapter: xhrRequestAdapter(), beforeRequest(method) { if (localStorage.getItem(ACCESS_TOKEN)) { diff --git a/interface/src/api/features.ts b/interface/src/api/features.ts index 6ada2405c..6d8b663fa 100644 --- a/interface/src/api/features.ts +++ b/interface/src/api/features.ts @@ -3,6 +3,7 @@ import type { AxiosPromise } from 'axios'; import type { Features } from 'types'; +// TODO move to Alova export function readFeatures(): AxiosPromise { return AXIOS.get('/features'); } diff --git a/interface/src/api/mqtt.ts b/interface/src/api/mqtt.ts index d599a3121..a2668193b 100644 --- a/interface/src/api/mqtt.ts +++ b/interface/src/api/mqtt.ts @@ -2,6 +2,7 @@ import { AXIOS } from './endpoints'; import type { AxiosPromise } from 'axios'; import type { MqttSettings, MqttStatus } from 'types'; +// TODO move to alova export function readMqttStatus(): AxiosPromise { return AXIOS.get('/mqttStatus'); } diff --git a/interface/src/api/network.ts b/interface/src/api/network.ts index a9535336c..2c50bfdf2 100644 --- a/interface/src/api/network.ts +++ b/interface/src/api/network.ts @@ -3,6 +3,7 @@ import type { AxiosPromise } from 'axios'; import type { WiFiNetworkList, NetworkSettings, NetworkStatus } from 'types'; +// TODO move to alova export function readNetworkStatus(): AxiosPromise { return AXIOS.get('/networkStatus'); } diff --git a/interface/src/api/ntp.ts b/interface/src/api/ntp.ts index dcd3c7ca9..c0cd94283 100644 --- a/interface/src/api/ntp.ts +++ b/interface/src/api/ntp.ts @@ -2,6 +2,7 @@ import { AXIOS } from './endpoints'; import type { AxiosPromise } from 'axios'; import type { NTPSettings, NTPStatus, Time } from 'types'; +// TODO move to Alova export function readNTPStatus(): AxiosPromise { return AXIOS.get('/ntpStatus'); } diff --git a/interface/src/api/security.ts b/interface/src/api/security.ts index f46194432..1fecb6611 100644 --- a/interface/src/api/security.ts +++ b/interface/src/api/security.ts @@ -3,6 +3,7 @@ import type { AxiosPromise } from 'axios'; import type { SecuritySettings, Token } from 'types'; +// TODO move to Alova export function readSecuritySettings(): AxiosPromise { return AXIOS.get('/securitySettings'); } diff --git a/interface/src/api/system.ts b/interface/src/api/system.ts index dc9b34db6..80bf6df1d 100644 --- a/interface/src/api/system.ts +++ b/interface/src/api/system.ts @@ -4,6 +4,7 @@ import type { AxiosPromise } from 'axios'; import type { OTASettings, SystemStatus, LogSettings, LogEntries } from 'types'; +// TODO move to Alova export function readSystemStatus(timeout?: number): AxiosPromise { return AXIOS.get('/systemStatus', { timeout }); } diff --git a/interface/src/framework/mqtt/MqttStatusForm.tsx b/interface/src/framework/mqtt/MqttStatusForm.tsx index 4c2daa4c6..dc914f7e7 100644 --- a/interface/src/framework/mqtt/MqttStatusForm.tsx +++ b/interface/src/framework/mqtt/MqttStatusForm.tsx @@ -38,7 +38,7 @@ export const mqttQueueHighlight = ({ mqtt_queued }: MqttStatus, theme: Theme) => }; const MqttStatusForm: FC = () => { - // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); + // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); const { loadData, data, errorMessage } = useRest({ read: MqttApi.readMqttStatus }); const { LL } = useI18nContext(); diff --git a/interface/src/framework/network/NetworkStatusForm.tsx b/interface/src/framework/network/NetworkStatusForm.tsx index 5a720fbaf..6d0ca1b55 100644 --- a/interface/src/framework/network/NetworkStatusForm.tsx +++ b/interface/src/framework/network/NetworkStatusForm.tsx @@ -59,7 +59,7 @@ const IPs = (status: NetworkStatus) => { }; const NetworkStatusForm: FC = () => { - // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); + // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); const { loadData, data, errorMessage } = useRest({ read: NetworkApi.readNetworkStatus }); const { LL } = useI18nContext(); diff --git a/interface/src/framework/ntp/NTPStatusForm.tsx b/interface/src/framework/ntp/NTPStatusForm.tsx index 971246c29..4b77adaf5 100644 --- a/interface/src/framework/ntp/NTPStatusForm.tsx +++ b/interface/src/framework/ntp/NTPStatusForm.tsx @@ -52,7 +52,7 @@ export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => { }; const NTPStatusForm: FC = () => { - // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); + // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); const { loadData, data, errorMessage } = useRest({ read: NTPApi.readNTPStatus }); const [localTime, setLocalTime] = useState(''); diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index e7a1c761c..e9ef0d388 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -49,7 +49,7 @@ const levelLabel = (level: LogLevel) => { const SystemLog: FC = () => { const { LL } = useI18nContext(); - // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); + // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); const { loadData, data, setData, origData, dirtyFlags, blocker, setDirtyFlags, setOrigData } = useRest({ read: SystemApi.readLogSettings }); diff --git a/interface/src/framework/system/SystemStatusForm.tsx b/interface/src/framework/system/SystemStatusForm.tsx index 719fd5a22..6d62e2101 100644 --- a/interface/src/framework/system/SystemStatusForm.tsx +++ b/interface/src/framework/system/SystemStatusForm.tsx @@ -53,7 +53,7 @@ const SystemStatusForm: FC = () => { const { LL } = useI18nContext(); const [restarting, setRestarting] = useState(); - // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus()); + // TODO replace with const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus); const { loadData, data, errorMessage } = useRest({ read: SystemApi.readSystemStatus }); const { me } = useContext(AuthenticatedContext); diff --git a/interface/src/project/SettingsCustomization.tsx b/interface/src/project/SettingsCustomization.tsx index 7883cf484..eaf5e426d 100644 --- a/interface/src/project/SettingsCustomization.tsx +++ b/interface/src/project/SettingsCustomization.tsx @@ -65,11 +65,7 @@ const SettingsCustomization: FC = () => { const { send: writeCustomEntities } = useRequest((data) => EMSESP.writeCustomEntities(data), { immediate: false }); - const { - send: readDeviceEntities, - update: updateDeviceEntities, - onSuccess: onSuccess - } = useRequest((data) => EMSESP.readDeviceEntities(data), { + const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest((data) => EMSESP.readDeviceEntities(data), { initialData: [], immediate: false, force: true @@ -236,8 +232,7 @@ const SettingsCustomization: FC = () => { }; const maskDisabled = (set: boolean) => { - // TODO fix update! - updateDeviceEntities( + setDeviceEntities( deviceEntities.map(function (de) { if ((de.m & selectedFilters || !selectedFilters) && de.id.toLowerCase().includes(search.toLowerCase())) { return { diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index daec44a35..ce1a1276e 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -4,7 +4,8 @@ import WarningIcon from '@mui/icons-material/Warning'; import { Button, Typography, Box } from '@mui/material'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; -import { useState, useEffect, useCallback } from 'react'; +import { updateState, useRequest } from 'alova'; +import { useState, useCallback } from 'react'; import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -18,18 +19,25 @@ import type { FC } from 'react'; import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { extractErrorMessage } from 'utils'; const SettingsEntities: FC = () => { const { LL } = useI18nContext(); const [numChanges, setNumChanges] = useState(0); const blocker = useBlocker(numChanges !== 0); - const [entities, setEntities] = useState(); const [selectedEntityItem, setSelectedEntityItem] = useState(); - const [errorMessage, setErrorMessage] = useState(); const [creating, setCreating] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); + const { + data: entities, + send: fetchEntities, + error + } = useRequest(EMSESP.readEntities, { + initialData: [] + }); + + const { send: writeEntities } = useRequest((data) => EMSESP.writeEntities(data), { immediate: false }); + function hasEntityChanged(ei: EntityItem) { return ( ei.id !== ei.o_id || @@ -45,12 +53,6 @@ const SettingsEntities: FC = () => { ); } - useEffect(() => { - if (entities) { - setNumChanges(entities ? entities.filter((ei) => hasEntityChanged(ei)).length : 0); - } - }, [entities]); - const entity_theme = useTheme({ Table: ` --data-table-library_grid-template-columns: repeat(1, minmax(60px, 1fr)) minmax(80px, auto) 80px 80px 80px; @@ -105,62 +107,32 @@ const SettingsEntities: FC = () => { ` }); - const fetchEntities = useCallback(async () => { - try { - const response = await EMSESP.readEntities(); - setEntities( - response.data.entities.map((ei) => ({ - ...ei, - o_id: ei.id, - o_device_id: ei.device_id, - o_type_id: ei.type_id, - o_offset: ei.offset, - o_factor: ei.factor, - o_uom: ei.uom, - o_value_type: ei.value_type, - o_name: ei.name, - o_writeable: ei.writeable, - o_deleted: ei.deleted - })) - ); - } catch (error) { - setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } - }, [LL]); - - useEffect(() => { - void fetchEntities(); - }, [fetchEntities]); - const saveEntities = async () => { - if (entities) { - try { - const response = await EMSESP.writeEntities({ - entities: entities - .filter((ei) => !ei.deleted) - .map((condensed_ei) => ({ - id: condensed_ei.id, - name: condensed_ei.name, - device_id: condensed_ei.device_id, - type_id: condensed_ei.type_id, - offset: condensed_ei.offset, - factor: condensed_ei.factor, - uom: condensed_ei.uom, - writeable: condensed_ei.writeable, - value_type: condensed_ei.value_type - })) - }); - - if (response.status === 200) { - toast.success(LL.ENTITIES_UPDATED()); - } else { - toast.error(LL.PROBLEM_UPDATING()); - } + await writeEntities( + entities + .filter((ei) => !ei.deleted) + .map((condensed_ei) => ({ + id: condensed_ei.id, + name: condensed_ei.name, + device_id: condensed_ei.device_id, + type_id: condensed_ei.type_id, + offset: condensed_ei.offset, + factor: condensed_ei.factor, + uom: condensed_ei.uom, + writeable: condensed_ei.writeable, + value_type: condensed_ei.value_type + })) + ) + .then(() => { + toast.success(LL.ENTITIES_UPDATED()); + }) + .catch((err) => { + toast.error(err.message); + }) + .finally(async () => { await fetchEntities(); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } - } + setNumChanges(0); + }); }; const editEntityItem = useCallback((ei: EntityItem) => { @@ -175,11 +147,14 @@ const SettingsEntities: FC = () => { const onDialogSave = (updatedItem: EntityItem) => { setDialogOpen(false); - if (entities && creating) { - setEntities([...entities.filter((ei) => creating || ei.o_id !== updatedItem.o_id), updatedItem]); - } else { - setEntities(entities?.map((ei) => (ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei))); - } + + updateState('entities', (data) => { + const new_data = creating + ? [...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id), updatedItem] + : data.map((ei) => (ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei)); + setNumChanges(new_data.filter((ei) => hasEntityChanged(ei)).length); + return new_data; + }); }; const addEntityItem = () => { @@ -215,7 +190,7 @@ const SettingsEntities: FC = () => { const renderEntity = () => { if (!entities) { - return ; + return ; } return ( diff --git a/interface/src/project/api.ts b/interface/src/project/api.ts index 2476090d3..3d872829a 100644 --- a/interface/src/project/api.ts +++ b/interface/src/project/api.ts @@ -10,10 +10,10 @@ import type { SensorData, Entities, DeviceData, - ScheduleItem + ScheduleItem, + EntityItem } from './types'; -import type { AxiosPromise } from 'axios'; -import { AXIOS, alovaInstance } from 'api/endpoints'; +import { alovaInstance } from 'api/endpoints'; // DashboardDevices export const readCoreData = () => alovaInstance.Get(`/rest/coreData`); @@ -33,19 +33,6 @@ export const getBoardProfile = (boardProfile: string) => }); export const restart = () => alovaInstance.Post('/rest/restart'); -// SettingsCustomization -export const readDeviceEntities = (id: number) => - alovaInstance.Get('/rest/deviceEntities', { - params: { id }, - responseType: 'arraybuffer', - transformData(data: any) { - return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })); - } - }); -export const readDevices = () => alovaInstance.Get('/rest/devices'); -export const resetCustomizations = () => alovaInstance.Post('/rest/resetCustomizations'); -export const writeCustomEntities = (data: any) => alovaInstance.Post('/rest/customEntities', data); - // DashboardSensors export const readSensorData = () => alovaInstance.Get('/rest/sensorData'); export const writeTemperatureSensor = (ts: WriteTemperatureSensor) => @@ -65,6 +52,19 @@ export const getCustomizations = () => alovaInstance.Get('/rest/getCustomization export const getEntities = () => alovaInstance.Get('/rest/getEntities'); export const getSchedule = () => alovaInstance.Get('/rest/getSchedule'); +// SettingsCustomization +export const readDeviceEntities = (id: number) => + alovaInstance.Get('/rest/deviceEntities', { + params: { id }, + responseType: 'arraybuffer', + transformData(data: any) { + return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma })); + } + }); +export const readDevices = () => alovaInstance.Get('/rest/devices'); +export const resetCustomizations = () => alovaInstance.Post('/rest/resetCustomizations'); +export const writeCustomEntities = (data: any) => alovaInstance.Post('/rest/customEntities', data); + // SettingsScheduler export const readSchedule = () => alovaInstance.Get('/rest/schedule', { @@ -86,10 +86,23 @@ export const readSchedule = () => export const writeSchedule = (data: any) => alovaInstance.Post('/rest/schedule', data); // SettingsCustomization -// TODO move last Entities* to Alova -export function readEntities(): AxiosPromise { - return AXIOS.get('/entities'); -} -export function writeEntities(entities: Entities): AxiosPromise { - return AXIOS.post('/entities', entities); -} +export const readEntities = () => + alovaInstance.Get('/rest/entities', { + name: 'entities', + transformData(data: any) { + return data.map((ei: EntityItem) => ({ + ...ei, + o_id: ei.id, + o_device_id: ei.device_id, + o_type_id: ei.type_id, + o_offset: ei.offset, + o_factor: ei.factor, + o_uom: ei.uom, + o_value_type: ei.value_type, + o_name: ei.name, + o_writeable: ei.writeable, + o_deleted: ei.deleted + })); + } + }); +export const writeEntities = (data: any) => alovaInstance.Post('/rest/entities', data); diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index bc7147a1d..02724df3a 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -90,7 +90,7 @@ export const schedulerItemValidation = () => new Schema({ name: [ { - // TODO name must be unique - add check + // TODO validator: add check for unique name required: true, type: 'string', pattern: /^[a-zA-Z0-9_\\.]{0,15}$/, diff --git a/interface/src/utils/binding.ts b/interface/src/utils/binding.ts index 74a755a52..3586f40f7 100644 --- a/interface/src/utils/binding.ts +++ b/interface/src/utils/binding.ts @@ -28,7 +28,6 @@ export const updateValueDirty = const updated_value = extractEventValue(event); const name = event.target.name; - // TODO not sure how this is even working!! updateDataValue((prevState) => ({ ...prevState, [name]: updated_value @@ -36,11 +35,6 @@ export const updateValueDirty = const arr: string[] = dirtyFlags; - // TODO remove comments - // console.log('updating ' + name + ' to ' + updated_value); - // console.log('dirtyFlags:', dirtyFlags); - // console.log('binding.ts origData:', origData); - if (origData[name] !== updated_value) { if (!arr.includes(name)) { arr.push(name); diff --git a/mock-api/server.js b/mock-api/server.js index 3b42ce97a..c07cb523a 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -1780,22 +1780,21 @@ const emsesp_devicedata_99 = { }; // CUSTOM ENTITIES -let emsesp_entities = { +let emsesp_entities = [ // entities: [] - entities: [ - { - id: 0, - device_id: 8, - type_id: 24, - offset: 0, - factor: 1, - name: 'boiler_flowtemp', - uom: 1, - value_type: 1, - writeable: true - } - ] -}; + // entities: [ + { + id: 0, + device_id: 8, + type_id: 24, + offset: 0, + factor: 1, + name: 'boiler_flowtemp', + uom: 1, + value_type: 1, + writeable: true + } +]; // SCHEDULE let emsesp_schedule = @@ -2333,7 +2332,7 @@ rest_server.post(EMSESP_WRITE_SCHEDULE_ENDPOINT, (req, res) => { rest_server.post(EMSESP_WRITE_ENTITIES_ENDPOINT, (req, res) => { console.log('write entities'); - console.log(req.body.entities); + console.log(req.body); emsesp_entities = req.body; res.sendStatus(200); }); From 2ae45ecd6e5b416c8c9b62aa8b9a252a561814a0 Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 16 Jun 2023 06:44:49 +0200 Subject: [PATCH 018/163] merging to sync - still need to fix system.ts --- interface/src/api/ap.ts | 15 ++------ interface/src/api/endpoints.ts | 8 ++++ interface/src/api/mqtt.ts | 18 ++------- interface/src/api/network.ts | 30 ++++----------- interface/src/api/ntp.ts | 25 ++++--------- interface/src/api/system.ts | 15 ++++++-- interface/src/framework/ap/APSettingsForm.tsx | 24 ++++++++---- .../src/framework/mqtt/MqttSettingsForm.tsx | 24 ++++++++---- .../src/framework/mqtt/MqttStatusForm.tsx | 7 ++-- .../framework/network/NetworkSettingsForm.tsx | 32 +++++++++------- .../framework/network/NetworkStatusForm.tsx | 7 ++-- .../src/framework/ntp/NTPSettingsForm.tsx | 32 +++++++++++----- interface/src/framework/ntp/NTPStatusForm.tsx | 37 +++++++++++-------- .../src/framework/system/SystemStatusForm.tsx | 6 +-- interface/src/project/DashboardDevices.tsx | 5 +-- interface/src/project/DashboardSensors.tsx | 4 +- interface/src/project/DashboardStatus.tsx | 2 +- interface/src/project/SettingsApplication.tsx | 2 +- .../src/project/SettingsCustomization.tsx | 3 +- mock-api/server.js | 25 +++++++++---- 20 files changed, 171 insertions(+), 150 deletions(-) diff --git a/interface/src/api/ap.ts b/interface/src/api/ap.ts index a314227c2..7ffda7668 100644 --- a/interface/src/api/ap.ts +++ b/interface/src/api/ap.ts @@ -1,14 +1,7 @@ -import { AXIOS, alovaInstance } from './endpoints'; -import type { AxiosPromise } from 'axios'; +import { alovaInstance } from './endpoints'; import type { APSettings, APStatus } from 'types'; -export const readAPStatus = () => alovaInstance.Get('/apStatus'); - -// TODO change APSettings AXIOS to Alova -export function readAPSettings(): AxiosPromise { - return AXIOS.get('/apSettings'); -} -export function updateAPSettings(apSettings: APSettings): AxiosPromise { - return AXIOS.post('/apSettings', apSettings); -} +export const readAPStatus = () => alovaInstance.Get('/rest/apStatus'); +export const readAPSettings = () => alovaInstance.Get('/rest/apSettings'); +export const updateAPSettings = (data: APSettings) => alovaInstance.Post('/rest/apSettings', data); diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index cd1b4b574..cf2fbe799 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -18,6 +18,14 @@ export const EVENT_SOURCE_ROOT = 'http://' + host + '/es/'; export const alovaInstance = createAlova({ statesHook: ReactHook, timeout: 3000, + localCache: { + GET: { + mode: 'placeholder', + // expire: 60 * 10 * 1000 + // see https://alova.js.org/learning/response-cache/#cache-replaceholder-mode + expire: 2000 + } + }, requestAdapter: xhrRequestAdapter(), beforeRequest(method) { if (localStorage.getItem(ACCESS_TOKEN)) { diff --git a/interface/src/api/mqtt.ts b/interface/src/api/mqtt.ts index a2668193b..d75dc4134 100644 --- a/interface/src/api/mqtt.ts +++ b/interface/src/api/mqtt.ts @@ -1,16 +1,6 @@ -import { AXIOS } from './endpoints'; -import type { AxiosPromise } from 'axios'; +import { alovaInstance } from './endpoints'; import type { MqttSettings, MqttStatus } from 'types'; -// TODO move to alova -export function readMqttStatus(): AxiosPromise { - return AXIOS.get('/mqttStatus'); -} - -export function readMqttSettings(): AxiosPromise { - return AXIOS.get('/mqttSettings'); -} - -export function updateMqttSettings(mqttSettings: MqttSettings): AxiosPromise { - return AXIOS.post('/mqttSettings', mqttSettings); -} +export const readMqttStatus = () => alovaInstance.Get('/rest/mqttStatus'); +export const readMqttSettings = () => alovaInstance.Get('/rest/mqttSettings'); +export const updateMqttSettings = (data: MqttSettings) => alovaInstance.Post('/rest/mqttSettings', data); diff --git a/interface/src/api/network.ts b/interface/src/api/network.ts index 2c50bfdf2..03bb7ead6 100644 --- a/interface/src/api/network.ts +++ b/interface/src/api/network.ts @@ -1,25 +1,11 @@ -import { AXIOS } from './endpoints'; -import type { AxiosPromise } from 'axios'; +import { alovaInstance } from './endpoints'; import type { WiFiNetworkList, NetworkSettings, NetworkStatus } from 'types'; -// TODO move to alova -export function readNetworkStatus(): AxiosPromise { - return AXIOS.get('/networkStatus'); -} - -export function scanNetworks(): AxiosPromise { - return AXIOS.get('/scanNetworks'); -} - -export function listNetworks(): AxiosPromise { - return AXIOS.get('/listNetworks'); -} - -export function readNetworkSettings(): AxiosPromise { - return AXIOS.get('/networkSettings'); -} - -export function updateNetworkSettings(wifiSettings: NetworkSettings): AxiosPromise { - return AXIOS.post('/networkSettings', wifiSettings); -} +export const readNetworkStatus = () => alovaInstance.Get('/rest/networkStatus'); +export const scanNetworks = () => alovaInstance.Get('/rest/scanNetworks'); +export const listNetworks = () => alovaInstance.Get('/rest/listNetworks'); +export const readNetworkSettings = () => + alovaInstance.Get('/rest/networkSettings', { name: 'networkSettings' }); +export const updateNetworkSettings = (wifiSettings: NetworkSettings) => + alovaInstance.Post('/rest/networkSettings', wifiSettings); diff --git a/interface/src/api/ntp.ts b/interface/src/api/ntp.ts index c0cd94283..74da189d1 100644 --- a/interface/src/api/ntp.ts +++ b/interface/src/api/ntp.ts @@ -1,20 +1,11 @@ -import { AXIOS } from './endpoints'; -import type { AxiosPromise } from 'axios'; +import { alovaInstance } from './endpoints'; import type { NTPSettings, NTPStatus, Time } from 'types'; -// TODO move to Alova -export function readNTPStatus(): AxiosPromise { - return AXIOS.get('/ntpStatus'); -} +export const readNTPStatus = () => alovaInstance.Get('/rest/ntpStatus'); +export const readNTPSettings = () => + alovaInstance.Get('/rest/ntpSettings', { + name: 'ntpSettings' + }); +export const updateNTPSettings = (data: NTPSettings) => alovaInstance.Post('/rest/ntpSettings', data); -export function readNTPSettings(): AxiosPromise { - return AXIOS.get('/ntpSettings'); -} - -export function updateNTPSettings(ntpSettings: NTPSettings): AxiosPromise { - return AXIOS.post('/ntpSettings', ntpSettings); -} - -export function updateTime(time: Time): AxiosPromise ); diff --git a/interface/src/framework/system/SystemStatusForm.tsx b/interface/src/framework/system/SystemStatusForm.tsx index 0913ea02a..54d3b922f 100644 --- a/interface/src/framework/system/SystemStatusForm.tsx +++ b/interface/src/framework/system/SystemStatusForm.tsx @@ -35,12 +35,11 @@ import { toast } from 'react-toastify'; import RestartMonitor from './RestartMonitor'; import type { FC } from 'react'; -import type { SystemStatus, Version } from 'types'; +import type { Version } from 'types'; import * as SystemApi from 'api/system'; import { ButtonRow, FormLoader, SectionContent, MessageBox } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; -import { extractErrorMessage } from 'utils'; export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest'; export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest'; @@ -52,9 +51,6 @@ function formatNumber(num: number) { const SystemStatusForm: FC = () => { const { LL } = useI18nContext(); - const [restarting, setRestarting] = useState(); - - const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus); const { me } = useContext(AuthenticatedContext); const [confirmRestart, setConfirmRestart] = useState(false); @@ -63,6 +59,21 @@ const SystemStatusForm: FC = () => { const [showingVersion, setShowingVersion] = useState(false); const [latestVersion, setLatestVersion] = useState(); const [latestDevVersion, setLatestDevVersion] = useState(); + const [restarting, setRestarting] = useState(); + + const { send: restartCommand } = useRequest(SystemApi.restart(), { + immediate: false + }); + + const { send: factoryResetCommand } = useRequest(SystemApi.factoryReset(), { + immediate: false + }); + + const { send: partitionCommand } = useRequest(SystemApi.partition(), { + immediate: false + }); + + const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus); useEffect(() => { void axios.get(VERSIONCHECK_ENDPOINT).then((response) => { @@ -83,30 +94,47 @@ const SystemStatusForm: FC = () => { const restart = async () => { setProcessing(true); - try { - const response = await SystemApi.restart(); - if (response.status === 200) { + await restartCommand() + .then(() => { setRestarting(true); - } - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } finally { - setConfirmRestart(false); - setProcessing(false); - } + }) + .catch((err) => { + toast.error(err.message); + }) + .finally(() => { + setConfirmRestart(false); + setProcessing(false); + }); + }; + + const factoryReset = async () => { + setProcessing(true); + await factoryResetCommand() + .then(() => { + setRestarting(true); + }) + .catch((err) => { + toast.error(err.message); + }) + .finally(() => { + setConfirmFactoryReset(false); + setProcessing(false); + }); }; const partition = async () => { setProcessing(true); - try { - await SystemApi.partition(); - setRestarting(true); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING())); - } finally { - setConfirmRestart(false); - setProcessing(false); - } + await partitionCommand() + .then(() => { + setRestarting(true); + }) + .catch((err) => { + toast.error(err.message); + }) + .finally(() => { + setConfirmRestart(false); + setProcessing(false); + }); }; const renderRestartDialog = () => ( @@ -201,19 +229,6 @@ const SystemStatusForm: FC = () => { ); - const factoryReset = async () => { - setProcessing(true); - try { - await SystemApi.factoryReset(); - setRestarting(true); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } finally { - setConfirmFactoryReset(false); - setProcessing(false); - } - }; - const renderFactoryResetDialog = () => ( setConfirmFactoryReset(false)}> {LL.FACTORY_RESET()} @@ -243,7 +258,7 @@ const SystemStatusForm: FC = () => { const content = () => { if (!data) { - return ; + return ; } return ( diff --git a/interface/src/framework/system/UploadFileForm.tsx b/interface/src/framework/system/UploadFileForm.tsx index 568738124..5cb437cb7 100644 --- a/interface/src/framework/system/UploadFileForm.tsx +++ b/interface/src/framework/system/UploadFileForm.tsx @@ -15,6 +15,7 @@ const UploadFileForm: FC = () => { const { LL } = useI18nContext(); const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => { + // TODO fileupload move to alova const response = await SystemApi.uploadFile(file, config); if (response.status === 200) { setRestarting(true); diff --git a/interface/src/project/SettingsApplication.tsx b/interface/src/project/SettingsApplication.tsx index 9c085dead..9a27e997f 100644 --- a/interface/src/project/SettingsApplication.tsx +++ b/interface/src/project/SettingsApplication.tsx @@ -12,6 +12,7 @@ import { createSettingsValidator } from './validators'; import type { Settings } from './types'; import type { ValidateFieldsError } from 'async-validator'; import type { FC } from 'react'; +import * as SystemApi from 'api/system'; import { SectionContent, FormLoader, @@ -69,7 +70,7 @@ const SettingsApplication: FC = () => { immediate: false }); - const { send: restartCommand } = useRequest(EMSESP.restart(), { + const { send: restartCommand } = useRequest(SystemApi.restart(), { immediate: false }); diff --git a/interface/src/project/SettingsCustomization.tsx b/interface/src/project/SettingsCustomization.tsx index 442a5335d..8c032ea16 100644 --- a/interface/src/project/SettingsCustomization.tsx +++ b/interface/src/project/SettingsCustomization.tsx @@ -35,6 +35,7 @@ import * as EMSESP from './api'; import { DeviceEntityMask } from './types'; import type { DeviceShort, DeviceEntity } from './types'; import type { FC } from 'react'; +import * as SystemApi from 'api/system'; import { ButtonRow, SectionContent, MessageBox, BlockNavigation } from 'components'; import RestartMonitor from 'framework/system/RestartMonitor'; @@ -78,7 +79,7 @@ const SettingsCustomization: FC = () => { setOriginalSettings(event.data); }); - const { send: restartCommand } = useRequest(EMSESP.restart(), { + const { send: restartCommand } = useRequest(SystemApi.restart(), { immediate: false }); diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index ce1a1276e..9d83cd329 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -4,6 +4,7 @@ import WarningIcon from '@mui/icons-material/Warning'; import { Button, Typography, Box } from '@mui/material'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; +// eslint-disable-next-line import/named import { updateState, useRequest } from 'alova'; import { useState, useCallback } from 'react'; import { unstable_useBlocker as useBlocker } from 'react-router-dom'; @@ -108,8 +109,8 @@ const SettingsEntities: FC = () => { }); const saveEntities = async () => { - await writeEntities( - entities + await writeEntities({ + entities: entities .filter((ei) => !ei.deleted) .map((condensed_ei) => ({ id: condensed_ei.id, @@ -122,7 +123,7 @@ const SettingsEntities: FC = () => { writeable: condensed_ei.writeable, value_type: condensed_ei.value_type })) - ) + }) .then(() => { toast.success(LL.ENTITIES_UPDATED()); }) diff --git a/interface/src/project/SettingsScheduler.tsx b/interface/src/project/SettingsScheduler.tsx index 8c93252e4..b7259d613 100644 --- a/interface/src/project/SettingsScheduler.tsx +++ b/interface/src/project/SettingsScheduler.tsx @@ -6,6 +6,7 @@ import WarningIcon from '@mui/icons-material/Warning'; import { Box, Typography, Divider, Stack, Button } from '@mui/material'; import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; +// eslint-disable-next-line import/named import { updateState, useRequest } from 'alova'; import { useState, useEffect, useCallback } from 'react'; import { unstable_useBlocker as useBlocker } from 'react-router-dom'; @@ -108,8 +109,8 @@ const SettingsScheduler: FC = () => { }); const saveSchedule = async () => { - await writeSchedule( - schedule + await writeSchedule({ + schedule: schedule .filter((si) => !si.deleted) .map((condensed_si) => ({ id: condensed_si.id, @@ -120,7 +121,7 @@ const SettingsScheduler: FC = () => { value: condensed_si.value, name: condensed_si.name })) - ) + }) .then(() => { toast.success(LL.SCHEDULE_UPDATED()); }) diff --git a/interface/src/project/api.ts b/interface/src/project/api.ts index 3d872829a..f1e2e37ff 100644 --- a/interface/src/project/api.ts +++ b/interface/src/project/api.ts @@ -31,7 +31,6 @@ export const getBoardProfile = (boardProfile: string) => alovaInstance.Get('/rest/boardProfile', { params: { boardProfile } }); -export const restart = () => alovaInstance.Post('/rest/restart'); // DashboardSensors export const readSensorData = () => alovaInstance.Get('/rest/sensorData'); @@ -70,7 +69,7 @@ export const readSchedule = () => alovaInstance.Get('/rest/schedule', { name: 'schedule', transformData(data: any) { - return data.map((si: ScheduleItem) => ({ + return data.schedule.map((si: ScheduleItem) => ({ ...si, o_id: si.id, o_active: si.active, @@ -85,12 +84,12 @@ export const readSchedule = () => }); export const writeSchedule = (data: any) => alovaInstance.Post('/rest/schedule', data); -// SettingsCustomization +// SettingsEntities export const readEntities = () => alovaInstance.Get('/rest/entities', { name: 'entities', transformData(data: any) { - return data.map((ei: EntityItem) => ({ + return data.entities.map((ei: EntityItem) => ({ ...ei, o_id: ei.id, o_device_id: ei.device_id, diff --git a/interface/src/types/system.ts b/interface/src/types/system.ts index 5ad12c59b..ec2b00469 100644 --- a/interface/src/types/system.ts +++ b/interface/src/types/system.ts @@ -42,10 +42,6 @@ export interface LogEntry { m: string; } -export interface LogEntries { - events: LogEntry[]; -} - export interface LogSettings { level: number; max_messages: number; diff --git a/interface/yarn.lock b/interface/yarn.lock index 6803c97b3..111162d73 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -620,10 +620,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:8.42.0": - version: 8.42.0 - resolution: "@eslint/js@npm:8.42.0" - checksum: 4ae46df1f32095cf9527d1f6a8a30512151f8eb66dd883a226face17c9e7cfdd2dcb3d4e3124fb67ac5801e0a776b1d7bba368276cfb1e1e4eefb047e38b41d6 +"@eslint/js@npm:8.43.0": + version: 8.43.0 + resolution: "@eslint/js@npm:8.43.0" + checksum: ff1a1587e8f28c21dda36a331cf70ca16b76e5897cecf10f6b4c326abddf18db565ee5f71feb89cbb0d3d20ff321a2536357562c0233868eed70784640b73cf4 languageName: node linkType: hard @@ -1546,7 +1546,7 @@ __metadata: alova: ^2.6.1 async-validator: ^4.2.5 axios: ^1.4.0 - eslint: ^8.42.0 + eslint: ^8.43.0 eslint-config-airbnb: ^19.0.4 eslint-config-airbnb-typescript: ^17.0.0 eslint-config-prettier: ^8.8.0 @@ -2850,14 +2850,14 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.42.0": - version: 8.42.0 - resolution: "eslint@npm:8.42.0" +"eslint@npm:^8.43.0": + version: 8.43.0 + resolution: "eslint@npm:8.43.0" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@eslint-community/regexpp": ^4.4.0 "@eslint/eslintrc": ^2.0.3 - "@eslint/js": 8.42.0 + "@eslint/js": 8.43.0 "@humanwhocodes/config-array": ^0.11.10 "@humanwhocodes/module-importer": ^1.0.1 "@nodelib/fs.walk": ^1.2.8 @@ -2895,7 +2895,7 @@ __metadata: text-table: ^0.2.0 bin: eslint: bin/eslint.js - checksum: 8ab5a3c1619008c946497a16b88a811b1f6c49a750a9bd0f81085dff4166418b9da4e46108b09d920877ab2f5981e3613332653b7f5e3917d8088bc4b8d40b5a + checksum: 1f9ff2c774e852c179ba569a3b672cbc4cf91aa59843ee32f7da363c10b5aad842672005ac04c760f6077b3471da428562274e0fcb0a78c2056866b3d36be948 languageName: node linkType: hard diff --git a/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp b/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp index 9fb5a4acb..b0843c78d 100644 --- a/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp @@ -15,549 +15,535 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE template class MsgPackDeserializer { - public: - MsgPackDeserializer(MemoryPool* pool, TReader reader, - TStringStorage stringStorage) - : _pool(pool), - _reader(reader), - _stringStorage(stringStorage), - _foundSomething(false) {} - - template - DeserializationError parse(VariantData& variant, TFilter filter, - DeserializationOption::NestingLimit nestingLimit) { - DeserializationError::Code err; - err = parseVariant(&variant, filter, nestingLimit); - return _foundSomething ? err : DeserializationError::EmptyInput; - } - - private: - template - DeserializationError::Code parseVariant( - VariantData* variant, TFilter filter, - DeserializationOption::NestingLimit nestingLimit) { - DeserializationError::Code err; - - uint8_t code = 0; // TODO: why do we need to initialize this variable? - err = readByte(code); - if (err) - return err; - - _foundSomething = true; - - bool allowValue = filter.allowValue(); - - if (allowValue) { - // callers pass a null pointer only when value must be ignored - ARDUINOJSON_ASSERT(variant != 0); + public: + MsgPackDeserializer(MemoryPool * pool, TReader reader, TStringStorage stringStorage) + : _pool(pool) + , _reader(reader) + , _stringStorage(stringStorage) + , _foundSomething(false) { } - switch (code) { - case 0xc0: - // already null - return DeserializationError::Ok; + template + DeserializationError parse(VariantData & variant, TFilter filter, DeserializationOption::NestingLimit nestingLimit) { + DeserializationError::Code err; + err = parseVariant(&variant, filter, nestingLimit); + return _foundSomething ? err : DeserializationError::EmptyInput; + } - case 0xc1: - return DeserializationError::InvalidInput; + private: + template + DeserializationError::Code parseVariant(VariantData * variant, TFilter filter, DeserializationOption::NestingLimit nestingLimit) { + DeserializationError::Code err; - case 0xc2: - if (allowValue) - variant->setBoolean(false); - return DeserializationError::Ok; + uint8_t code = 0; + err = readByte(code); + if (err) + return err; - case 0xc3: - if (allowValue) - variant->setBoolean(true); - return DeserializationError::Ok; + _foundSomething = true; - case 0xc4: // bin 8 (not supported) - return skipString(); + bool allowValue = filter.allowValue(); - case 0xc5: // bin 16 (not supported) - return skipString(); + if (allowValue) { + // callers pass a null pointer only when value must be ignored + ARDUINOJSON_ASSERT(variant != 0); + } - case 0xc6: // bin 32 (not supported) - return skipString(); + switch (code) { + case 0xc0: + // already null + return DeserializationError::Ok; - case 0xc7: // ext 8 (not supported) - return skipExt(); + case 0xc1: + return DeserializationError::InvalidInput; - case 0xc8: // ext 16 (not supported) - return skipExt(); + case 0xc2: + if (allowValue) + variant->setBoolean(false); + return DeserializationError::Ok; - case 0xc9: // ext 32 (not supported) - return skipExt(); + case 0xc3: + if (allowValue) + variant->setBoolean(true); + return DeserializationError::Ok; - case 0xca: - if (allowValue) - return readFloat(variant); - else - return skipBytes(4); + case 0xc4: // bin 8 (not supported) + return skipString(); - case 0xcb: - if (allowValue) - return readDouble(variant); - else - return skipBytes(8); + case 0xc5: // bin 16 (not supported) + return skipString(); - case 0xcc: - if (allowValue) - return readInteger(variant); - else - return skipBytes(1); + case 0xc6: // bin 32 (not supported) + return skipString(); - case 0xcd: - if (allowValue) - return readInteger(variant); - else - return skipBytes(2); + case 0xc7: // ext 8 (not supported) + return skipExt(); - case 0xce: - if (allowValue) - return readInteger(variant); - else - return skipBytes(4); + case 0xc8: // ext 16 (not supported) + return skipExt(); - case 0xcf: + case 0xc9: // ext 32 (not supported) + return skipExt(); + + case 0xca: + if (allowValue) + return readFloat(variant); + else + return skipBytes(4); + + case 0xcb: + if (allowValue) + return readDouble(variant); + else + return skipBytes(8); + + case 0xcc: + if (allowValue) + return readInteger(variant); + else + return skipBytes(1); + + case 0xcd: + if (allowValue) + return readInteger(variant); + else + return skipBytes(2); + + case 0xce: + if (allowValue) + return readInteger(variant); + else + return skipBytes(4); + + case 0xcf: #if ARDUINOJSON_USE_LONG_LONG - if (allowValue) - return readInteger(variant); - else - return skipBytes(8); + if (allowValue) + return readInteger(variant); + else + return skipBytes(8); #else - return skipBytes(8); // not supported + return skipBytes(8); // not supported #endif - case 0xd0: - if (allowValue) - return readInteger(variant); - else - return skipBytes(1); + case 0xd0: + if (allowValue) + return readInteger(variant); + else + return skipBytes(1); - case 0xd1: - if (allowValue) - return readInteger(variant); - else - return skipBytes(2); + case 0xd1: + if (allowValue) + return readInteger(variant); + else + return skipBytes(2); - case 0xd2: - if (allowValue) - return readInteger(variant); - else - return skipBytes(4); + case 0xd2: + if (allowValue) + return readInteger(variant); + else + return skipBytes(4); - case 0xd3: + case 0xd3: #if ARDUINOJSON_USE_LONG_LONG - if (allowValue) - return readInteger(variant); - else - return skipBytes(8); // not supported + if (allowValue) + return readInteger(variant); + else + return skipBytes(8); // not supported #else - return skipBytes(8); + return skipBytes(8); #endif - case 0xd4: // fixext 1 (not supported) - return skipBytes(2); + case 0xd4: // fixext 1 (not supported) + return skipBytes(2); - case 0xd5: // fixext 2 (not supported) - return skipBytes(3); + case 0xd5: // fixext 2 (not supported) + return skipBytes(3); - case 0xd6: // fixext 4 (not supported) - return skipBytes(5); + case 0xd6: // fixext 4 (not supported) + return skipBytes(5); - case 0xd7: // fixext 8 (not supported) - return skipBytes(9); + case 0xd7: // fixext 8 (not supported) + return skipBytes(9); - case 0xd8: // fixext 16 (not supported) - return skipBytes(17); + case 0xd8: // fixext 16 (not supported) + return skipBytes(17); + + case 0xd9: + if (allowValue) + return readString(variant); + else + return skipString(); + + case 0xda: + if (allowValue) + return readString(variant); + else + return skipString(); + + case 0xdb: + if (allowValue) + return readString(variant); + else + return skipString(); + + case 0xdc: + return readArray(variant, filter, nestingLimit); + + case 0xdd: + return readArray(variant, filter, nestingLimit); + + case 0xde: + return readObject(variant, filter, nestingLimit); + + case 0xdf: + return readObject(variant, filter, nestingLimit); + } + + switch (code & 0xf0) { + case 0x80: + return readObject(variant, code & 0x0F, filter, nestingLimit); + + case 0x90: + return readArray(variant, code & 0x0F, filter, nestingLimit); + } + + if ((code & 0xe0) == 0xa0) { + if (allowValue) + return readString(variant, code & 0x1f); + else + return skipBytes(code & 0x1f); + } - case 0xd9: if (allowValue) - return readString(variant); - else - return skipString(); + variant->setInteger(static_cast(code)); - case 0xda: - if (allowValue) - return readString(variant); - else - return skipString(); - - case 0xdb: - if (allowValue) - return readString(variant); - else - return skipString(); - - case 0xdc: - return readArray(variant, filter, nestingLimit); - - case 0xdd: - return readArray(variant, filter, nestingLimit); - - case 0xde: - return readObject(variant, filter, nestingLimit); - - case 0xdf: - return readObject(variant, filter, nestingLimit); + return DeserializationError::Ok; } - switch (code & 0xf0) { - case 0x80: - return readObject(variant, code & 0x0F, filter, nestingLimit); - - case 0x90: - return readArray(variant, code & 0x0F, filter, nestingLimit); + DeserializationError::Code readByte(uint8_t & value) { + int c = _reader.read(); + if (c < 0) + return DeserializationError::IncompleteInput; + value = static_cast(c); + return DeserializationError::Ok; } - if ((code & 0xe0) == 0xa0) { - if (allowValue) - return readString(variant, code & 0x1f); - else - return skipBytes(code & 0x1f); - } - - if (allowValue) - variant->setInteger(static_cast(code)); - - return DeserializationError::Ok; - } - - DeserializationError::Code readByte(uint8_t& value) { - int c = _reader.read(); - if (c < 0) - return DeserializationError::IncompleteInput; - value = static_cast(c); - return DeserializationError::Ok; - } - - DeserializationError::Code readBytes(uint8_t* p, size_t n) { - if (_reader.readBytes(reinterpret_cast(p), n) == n) - return DeserializationError::Ok; - return DeserializationError::IncompleteInput; - } - - template - DeserializationError::Code readBytes(T& value) { - return readBytes(reinterpret_cast(&value), sizeof(value)); - } - - DeserializationError::Code skipBytes(size_t n) { - for (; n; --n) { - if (_reader.read() < 0) + DeserializationError::Code readBytes(uint8_t * p, size_t n) { + if (_reader.readBytes(reinterpret_cast(p), n) == n) + return DeserializationError::Ok; return DeserializationError::IncompleteInput; } - return DeserializationError::Ok; - } - template - DeserializationError::Code readInteger(T& value) { - DeserializationError::Code err; - - err = readBytes(value); - if (err) - return err; - - fixEndianess(value); - - return DeserializationError::Ok; - } - - template - DeserializationError::Code readInteger(VariantData* variant) { - DeserializationError::Code err; - T value; - - err = readInteger(value); - if (err) - return err; - - variant->setInteger(value); - - return DeserializationError::Ok; - } - - template - typename enable_if::type - readFloat(VariantData* variant) { - DeserializationError::Code err; - T value; - - err = readBytes(value); - if (err) - return err; - - fixEndianess(value); - variant->setFloat(value); - - return DeserializationError::Ok; - } - - template - typename enable_if::type - readDouble(VariantData* variant) { - DeserializationError::Code err; - T value; - - err = readBytes(value); - if (err) - return err; - - fixEndianess(value); - variant->setFloat(value); - - return DeserializationError::Ok; - } - - template - typename enable_if::type - readDouble(VariantData* variant) { - DeserializationError::Code err; - uint8_t i[8]; // input is 8 bytes - T value; // output is 4 bytes - uint8_t* o = reinterpret_cast(&value); - - err = readBytes(i, 8); - if (err) - return err; - - doubleToFloat(i, o); - fixEndianess(value); - variant->setFloat(value); - - return DeserializationError::Ok; - } - - template - DeserializationError::Code readString(VariantData* variant) { - DeserializationError::Code err; - T size; - - err = readInteger(size); - if (err) - return err; - - return readString(variant, size); - } - - template - DeserializationError::Code readString() { - DeserializationError::Code err; - T size; - - err = readInteger(size); - if (err) - return err; - - return readString(size); - } - - template - DeserializationError::Code skipString() { - DeserializationError::Code err; - T size; - - err = readInteger(size); - if (err) - return err; - - return skipBytes(size); - } - - DeserializationError::Code readString(VariantData* variant, size_t n) { - DeserializationError::Code err; - - err = readString(n); - if (err) - return err; - - variant->setString(_stringStorage.save()); - return DeserializationError::Ok; - } - - DeserializationError::Code readString(size_t n) { - DeserializationError::Code err; - - _stringStorage.startString(); - for (; n; --n) { - uint8_t c; - - err = readBytes(c); - if (err) - return err; - - _stringStorage.append(static_cast(c)); + template + DeserializationError::Code readBytes(T & value) { + return readBytes(reinterpret_cast(&value), sizeof(value)); } - if (!_stringStorage.isValid()) - return DeserializationError::NoMemory; - - return DeserializationError::Ok; - } - - template - DeserializationError::Code readArray( - VariantData* variant, TFilter filter, - DeserializationOption::NestingLimit nestingLimit) { - DeserializationError::Code err; - TSize size; - - err = readInteger(size); - if (err) - return err; - - return readArray(variant, size, filter, nestingLimit); - } - - template - DeserializationError::Code readArray( - VariantData* variant, size_t n, TFilter filter, - DeserializationOption::NestingLimit nestingLimit) { - DeserializationError::Code err; - - if (nestingLimit.reached()) - return DeserializationError::TooDeep; - - bool allowArray = filter.allowArray(); - - CollectionData* array; - if (allowArray) { - ARDUINOJSON_ASSERT(variant != 0); - array = &variant->toArray(); - } else { - array = 0; + DeserializationError::Code skipBytes(size_t n) { + for (; n; --n) { + if (_reader.read() < 0) + return DeserializationError::IncompleteInput; + } + return DeserializationError::Ok; } - TFilter memberFilter = filter[0U]; + template + DeserializationError::Code readInteger(T & value) { + DeserializationError::Code err; - for (; n; --n) { - VariantData* value; + err = readBytes(value); + if (err) + return err; - if (memberFilter.allow()) { - ARDUINOJSON_ASSERT(array != 0); - value = array->addElement(_pool); - if (!value) - return DeserializationError::NoMemory; - } else { - value = 0; - } + fixEndianess(value); - err = parseVariant(value, memberFilter, nestingLimit.decrement()); - if (err) - return err; + return DeserializationError::Ok; } - return DeserializationError::Ok; - } + template + DeserializationError::Code readInteger(VariantData * variant) { + DeserializationError::Code err; + T value; - template - DeserializationError::Code readObject( - VariantData* variant, TFilter filter, - DeserializationOption::NestingLimit nestingLimit) { - DeserializationError::Code err; - TSize size; + err = readInteger(value); + if (err) + return err; - err = readInteger(size); - if (err) - return err; + variant->setInteger(value); - return readObject(variant, size, filter, nestingLimit); - } - - template - DeserializationError::Code readObject( - VariantData* variant, size_t n, TFilter filter, - DeserializationOption::NestingLimit nestingLimit) { - DeserializationError::Code err; - - if (nestingLimit.reached()) - return DeserializationError::TooDeep; - - CollectionData* object; - if (filter.allowObject()) { - ARDUINOJSON_ASSERT(variant != 0); - object = &variant->toObject(); - } else { - object = 0; + return DeserializationError::Ok; } - for (; n; --n) { - err = readKey(); - if (err) - return err; + template + typename enable_if::type readFloat(VariantData * variant) { + DeserializationError::Code err; + T value; - JsonString key = _stringStorage.str(); - TFilter memberFilter = filter[key.c_str()]; - VariantData* member; + err = readBytes(value); + if (err) + return err; - if (memberFilter.allow()) { - ARDUINOJSON_ASSERT(object != 0); + fixEndianess(value); + variant->setFloat(value); - // Save key in memory pool. - // This MUST be done before adding the slot. - key = _stringStorage.save(); - - VariantSlot* slot = object->addSlot(_pool); - if (!slot) - return DeserializationError::NoMemory; - - slot->setKey(key); - - member = slot->data(); - } else { - member = 0; - } - - err = parseVariant(member, memberFilter, nestingLimit.decrement()); - if (err) - return err; + return DeserializationError::Ok; } - return DeserializationError::Ok; - } + template + typename enable_if::type readDouble(VariantData * variant) { + DeserializationError::Code err; + T value; - DeserializationError::Code readKey() { - DeserializationError::Code err; - uint8_t code; + err = readBytes(value); + if (err) + return err; - err = readByte(code); - if (err) - return err; + fixEndianess(value); + variant->setFloat(value); - if ((code & 0xe0) == 0xa0) - return readString(code & 0x1f); - - switch (code) { - case 0xd9: - return readString(); - - case 0xda: - return readString(); - - case 0xdb: - return readString(); - - default: - return DeserializationError::InvalidInput; + return DeserializationError::Ok; } - } - template - DeserializationError::Code skipExt() { - DeserializationError::Code err; - T size; + template + typename enable_if::type readDouble(VariantData * variant) { + DeserializationError::Code err; + uint8_t i[8]; // input is 8 bytes + T value; // output is 4 bytes + uint8_t * o = reinterpret_cast(&value); - err = readInteger(size); - if (err) - return err; + err = readBytes(i, 8); + if (err) + return err; - return skipBytes(size + 1U); - } + doubleToFloat(i, o); + fixEndianess(value); + variant->setFloat(value); - MemoryPool* _pool; - TReader _reader; - TStringStorage _stringStorage; - bool _foundSomething; + return DeserializationError::Ok; + } + + template + DeserializationError::Code readString(VariantData * variant) { + DeserializationError::Code err; + T size; + + err = readInteger(size); + if (err) + return err; + + return readString(variant, size); + } + + template + DeserializationError::Code readString() { + DeserializationError::Code err; + T size; + + err = readInteger(size); + if (err) + return err; + + return readString(size); + } + + template + DeserializationError::Code skipString() { + DeserializationError::Code err; + T size; + + err = readInteger(size); + if (err) + return err; + + return skipBytes(size); + } + + DeserializationError::Code readString(VariantData * variant, size_t n) { + DeserializationError::Code err; + + err = readString(n); + if (err) + return err; + + variant->setString(_stringStorage.save()); + return DeserializationError::Ok; + } + + DeserializationError::Code readString(size_t n) { + DeserializationError::Code err; + + _stringStorage.startString(); + for (; n; --n) { + uint8_t c; + + err = readBytes(c); + if (err) + return err; + + _stringStorage.append(static_cast(c)); + } + + if (!_stringStorage.isValid()) + return DeserializationError::NoMemory; + + return DeserializationError::Ok; + } + + template + DeserializationError::Code readArray(VariantData * variant, TFilter filter, DeserializationOption::NestingLimit nestingLimit) { + DeserializationError::Code err; + TSize size; + + err = readInteger(size); + if (err) + return err; + + return readArray(variant, size, filter, nestingLimit); + } + + template + DeserializationError::Code readArray(VariantData * variant, size_t n, TFilter filter, DeserializationOption::NestingLimit nestingLimit) { + DeserializationError::Code err; + + if (nestingLimit.reached()) + return DeserializationError::TooDeep; + + bool allowArray = filter.allowArray(); + + CollectionData * array; + if (allowArray) { + ARDUINOJSON_ASSERT(variant != 0); + array = &variant->toArray(); + } else { + array = 0; + } + + TFilter memberFilter = filter[0U]; + + for (; n; --n) { + VariantData * value; + + if (memberFilter.allow()) { + ARDUINOJSON_ASSERT(array != 0); + value = array->addElement(_pool); + if (!value) + return DeserializationError::NoMemory; + } else { + value = 0; + } + + err = parseVariant(value, memberFilter, nestingLimit.decrement()); + if (err) + return err; + } + + return DeserializationError::Ok; + } + + template + DeserializationError::Code readObject(VariantData * variant, TFilter filter, DeserializationOption::NestingLimit nestingLimit) { + DeserializationError::Code err; + TSize size; + + err = readInteger(size); + if (err) + return err; + + return readObject(variant, size, filter, nestingLimit); + } + + template + DeserializationError::Code readObject(VariantData * variant, size_t n, TFilter filter, DeserializationOption::NestingLimit nestingLimit) { + DeserializationError::Code err; + + if (nestingLimit.reached()) + return DeserializationError::TooDeep; + + CollectionData * object; + if (filter.allowObject()) { + ARDUINOJSON_ASSERT(variant != 0); + object = &variant->toObject(); + } else { + object = 0; + } + + for (; n; --n) { + err = readKey(); + if (err) + return err; + + JsonString key = _stringStorage.str(); + TFilter memberFilter = filter[key.c_str()]; + VariantData * member; + + if (memberFilter.allow()) { + ARDUINOJSON_ASSERT(object != 0); + + // Save key in memory pool. + // This MUST be done before adding the slot. + key = _stringStorage.save(); + + VariantSlot * slot = object->addSlot(_pool); + if (!slot) + return DeserializationError::NoMemory; + + slot->setKey(key); + + member = slot->data(); + } else { + member = 0; + } + + err = parseVariant(member, memberFilter, nestingLimit.decrement()); + if (err) + return err; + } + + return DeserializationError::Ok; + } + + DeserializationError::Code readKey() { + DeserializationError::Code err; + uint8_t code; + + err = readByte(code); + if (err) + return err; + + if ((code & 0xe0) == 0xa0) + return readString(code & 0x1f); + + switch (code) { + case 0xd9: + return readString(); + + case 0xda: + return readString(); + + case 0xdb: + return readString(); + + default: + return DeserializationError::InvalidInput; + } + } + + template + DeserializationError::Code skipExt() { + DeserializationError::Code err; + T size; + + err = readInteger(size); + if (err) + return err; + + return skipBytes(size + 1U); + } + + MemoryPool * _pool; + TReader _reader; + TStringStorage _stringStorage; + bool _foundSomething; }; ARDUINOJSON_END_PRIVATE_NAMESPACE @@ -567,19 +553,17 @@ ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE // Parses a MessagePack input and puts the result in a JsonDocument. // https://arduinojson.org/v6/api/msgpack/deserializemsgpack/ template -DeserializationError deserializeMsgPack(JsonDocument& doc, Args&&... args) { - using namespace detail; - return deserialize(doc, detail::forward(args)...); +DeserializationError deserializeMsgPack(JsonDocument & doc, Args &&... args) { + using namespace detail; + return deserialize(doc, detail::forward(args)...); } // Parses a MessagePack input and puts the result in a JsonDocument. // https://arduinojson.org/v6/api/msgpack/deserializemsgpack/ template -DeserializationError deserializeMsgPack(JsonDocument& doc, TChar* input, - Args&&... args) { - using namespace detail; - return deserialize(doc, input, - detail::forward(args)...); +DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, Args &&... args) { + using namespace detail; + return deserialize(doc, input, detail::forward(args)...); } ARDUINOJSON_END_PUBLIC_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Variant/VariantCompare.hpp b/lib/ArduinoJson/src/ArduinoJson/Variant/VariantCompare.hpp index bb60b8a4f..dde086206 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Variant/VariantCompare.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Variant/VariantCompare.hpp @@ -20,188 +20,198 @@ template struct Comparer; template -struct Comparer::value>::type> - : ComparerBase { - T rhs; // TODO: store adapted string? +struct Comparer::value>::type> : ComparerBase { + T rhs; - explicit Comparer(T value) : rhs(value) {} + explicit Comparer(T value) + : rhs(value) { + } - CompareResult visitString(const char* lhs, size_t n) { - int i = stringCompare(adaptString(rhs), adaptString(lhs, n)); - if (i < 0) - return COMPARE_RESULT_GREATER; - else if (i > 0) - return COMPARE_RESULT_LESS; - else - return COMPARE_RESULT_EQUAL; - } + CompareResult visitString(const char * lhs, size_t n) { + int i = stringCompare(adaptString(rhs), adaptString(lhs, n)); + if (i < 0) + return COMPARE_RESULT_GREATER; + else if (i > 0) + return COMPARE_RESULT_LESS; + else + return COMPARE_RESULT_EQUAL; + } - CompareResult visitNull() { - if (adaptString(rhs).isNull()) - return COMPARE_RESULT_EQUAL; - else - return COMPARE_RESULT_DIFFER; - } + CompareResult visitNull() { + if (adaptString(rhs).isNull()) + return COMPARE_RESULT_EQUAL; + else + return COMPARE_RESULT_DIFFER; + } }; template -struct Comparer::value || - is_floating_point::value>::type> - : ComparerBase { - T rhs; +struct Comparer::value || is_floating_point::value>::type> : ComparerBase { + T rhs; - explicit Comparer(T value) : rhs(value) {} + explicit Comparer(T value) + : rhs(value) { + } - CompareResult visitFloat(JsonFloat lhs) { - return arithmeticCompare(lhs, rhs); - } + CompareResult visitFloat(JsonFloat lhs) { + return arithmeticCompare(lhs, rhs); + } - CompareResult visitSignedInteger(JsonInteger lhs) { - return arithmeticCompare(lhs, rhs); - } + CompareResult visitSignedInteger(JsonInteger lhs) { + return arithmeticCompare(lhs, rhs); + } - CompareResult visitUnsignedInteger(JsonUInt lhs) { - return arithmeticCompare(lhs, rhs); - } + CompareResult visitUnsignedInteger(JsonUInt lhs) { + return arithmeticCompare(lhs, rhs); + } - CompareResult visitBoolean(bool lhs) { - return visitUnsignedInteger(static_cast(lhs)); - } + CompareResult visitBoolean(bool lhs) { + return visitUnsignedInteger(static_cast(lhs)); + } }; struct NullComparer : ComparerBase { - CompareResult visitNull() { - return COMPARE_RESULT_EQUAL; - } + CompareResult visitNull() { + return COMPARE_RESULT_EQUAL; + } }; template <> struct Comparer : NullComparer { - explicit Comparer(decltype(nullptr)) : NullComparer() {} + explicit Comparer(decltype(nullptr)) + : NullComparer() { + } }; struct ArrayComparer : ComparerBase { - const CollectionData* _rhs; + const CollectionData * _rhs; - explicit ArrayComparer(const CollectionData& rhs) : _rhs(&rhs) {} + explicit ArrayComparer(const CollectionData & rhs) + : _rhs(&rhs) { + } - CompareResult visitArray(const CollectionData& lhs) { - if (JsonArrayConst(&lhs) == JsonArrayConst(_rhs)) - return COMPARE_RESULT_EQUAL; - else - return COMPARE_RESULT_DIFFER; - } + CompareResult visitArray(const CollectionData & lhs) { + if (JsonArrayConst(&lhs) == JsonArrayConst(_rhs)) + return COMPARE_RESULT_EQUAL; + else + return COMPARE_RESULT_DIFFER; + } }; struct ObjectComparer : ComparerBase { - const CollectionData* _rhs; + const CollectionData * _rhs; - explicit ObjectComparer(const CollectionData& rhs) : _rhs(&rhs) {} + explicit ObjectComparer(const CollectionData & rhs) + : _rhs(&rhs) { + } - CompareResult visitObject(const CollectionData& lhs) { - if (JsonObjectConst(&lhs) == JsonObjectConst(_rhs)) - return COMPARE_RESULT_EQUAL; - else - return COMPARE_RESULT_DIFFER; - } + CompareResult visitObject(const CollectionData & lhs) { + if (JsonObjectConst(&lhs) == JsonObjectConst(_rhs)) + return COMPARE_RESULT_EQUAL; + else + return COMPARE_RESULT_DIFFER; + } }; struct RawComparer : ComparerBase { - const char* _rhsData; - size_t _rhsSize; + const char * _rhsData; + size_t _rhsSize; - explicit RawComparer(const char* rhsData, size_t rhsSize) - : _rhsData(rhsData), _rhsSize(rhsSize) {} + explicit RawComparer(const char * rhsData, size_t rhsSize) + : _rhsData(rhsData) + , _rhsSize(rhsSize) { + } - CompareResult visitRawJson(const char* lhsData, size_t lhsSize) { - size_t size = _rhsSize < lhsSize ? _rhsSize : lhsSize; - int n = memcmp(lhsData, _rhsData, size); - if (n < 0) - return COMPARE_RESULT_LESS; - else if (n > 0) - return COMPARE_RESULT_GREATER; - else - return COMPARE_RESULT_EQUAL; - } + CompareResult visitRawJson(const char * lhsData, size_t lhsSize) { + size_t size = _rhsSize < lhsSize ? _rhsSize : lhsSize; + int n = memcmp(lhsData, _rhsData, size); + if (n < 0) + return COMPARE_RESULT_LESS; + else if (n > 0) + return COMPARE_RESULT_GREATER; + else + return COMPARE_RESULT_EQUAL; + } }; struct VariantComparer : ComparerBase { - const VariantData* rhs; + const VariantData * rhs; - explicit VariantComparer(const VariantData* value) : rhs(value) {} - - CompareResult visitArray(const CollectionData& lhs) { - ArrayComparer comparer(lhs); - return accept(comparer); - } - - CompareResult visitObject(const CollectionData& lhs) { - ObjectComparer comparer(lhs); - return accept(comparer); - } - - CompareResult visitFloat(JsonFloat lhs) { - Comparer comparer(lhs); - return accept(comparer); - } - - CompareResult visitString(const char* lhs, size_t) { - Comparer comparer(lhs); - return accept(comparer); - } - - CompareResult visitRawJson(const char* lhsData, size_t lhsSize) { - RawComparer comparer(lhsData, lhsSize); - return accept(comparer); - } - - CompareResult visitSignedInteger(JsonInteger lhs) { - Comparer comparer(lhs); - return accept(comparer); - } - - CompareResult visitUnsignedInteger(JsonUInt lhs) { - Comparer comparer(lhs); - return accept(comparer); - } - - CompareResult visitBoolean(bool lhs) { - Comparer comparer(lhs); - return accept(comparer); - } - - CompareResult visitNull() { - NullComparer comparer; - return accept(comparer); - } - - private: - template - CompareResult accept(TComparer& comparer) { - CompareResult reversedResult = variantAccept(rhs, comparer); - switch (reversedResult) { - case COMPARE_RESULT_GREATER: - return COMPARE_RESULT_LESS; - case COMPARE_RESULT_LESS: - return COMPARE_RESULT_GREATER; - default: - return reversedResult; + explicit VariantComparer(const VariantData * value) + : rhs(value) { + } + + CompareResult visitArray(const CollectionData & lhs) { + ArrayComparer comparer(lhs); + return accept(comparer); + } + + CompareResult visitObject(const CollectionData & lhs) { + ObjectComparer comparer(lhs); + return accept(comparer); + } + + CompareResult visitFloat(JsonFloat lhs) { + Comparer comparer(lhs); + return accept(comparer); + } + + CompareResult visitString(const char * lhs, size_t) { + Comparer comparer(lhs); + return accept(comparer); + } + + CompareResult visitRawJson(const char * lhsData, size_t lhsSize) { + RawComparer comparer(lhsData, lhsSize); + return accept(comparer); + } + + CompareResult visitSignedInteger(JsonInteger lhs) { + Comparer comparer(lhs); + return accept(comparer); + } + + CompareResult visitUnsignedInteger(JsonUInt lhs) { + Comparer comparer(lhs); + return accept(comparer); + } + + CompareResult visitBoolean(bool lhs) { + Comparer comparer(lhs); + return accept(comparer); + } + + CompareResult visitNull() { + NullComparer comparer; + return accept(comparer); + } + + private: + template + CompareResult accept(TComparer & comparer) { + CompareResult reversedResult = variantAccept(rhs, comparer); + switch (reversedResult) { + case COMPARE_RESULT_GREATER: + return COMPARE_RESULT_LESS; + case COMPARE_RESULT_LESS: + return COMPARE_RESULT_GREATER; + default: + return reversedResult; + } } - } }; template -struct Comparer::value>::type> - : VariantComparer { - explicit Comparer(const T& value) - : VariantComparer(VariantAttorney::getData(value)) {} +struct Comparer::value>::type> : VariantComparer { + explicit Comparer(const T & value) + : VariantComparer(VariantAttorney::getData(value)) { + } }; template -CompareResult compare(ArduinoJson::JsonVariantConst lhs, const T& rhs) { - Comparer comparer(rhs); - return variantAccept(VariantAttorney::getData(lhs), comparer); +CompareResult compare(ArduinoJson::JsonVariantConst lhs, const T & rhs) { + Comparer comparer(rhs); + return variantAccept(VariantAttorney::getData(lhs), comparer); } ARDUINOJSON_END_PRIVATE_NAMESPACE diff --git a/mock-api/server.js b/mock-api/server.js index 0c066335c..df2096afb 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -18,6 +18,9 @@ const delay = (ms) => new Promise((res) => setTimeout(res, ms)); const API_ENDPOINT_ROOT = '/api/'; const REST_ENDPOINT_ROOT = '/rest/'; +// network poll +let countWifiScanPoll = 0; + // LOG const LOG_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'logSettings'; log_settings = { @@ -27,7 +30,7 @@ log_settings = { }; const FETCH_LOG_ENDPOINT = REST_ENDPOINT_ROOT + 'fetchLog'; -const fetch_log = { +let fetch_log = { events: [ { t: '000+00:00:00.001', @@ -1780,26 +1783,26 @@ const emsesp_devicedata_99 = { }; // CUSTOM ENTITIES -let emsesp_entities = [ +let emsesp_entities = { // entities: [] - // entities: [ - { - id: 0, - device_id: 8, - type_id: 24, - offset: 0, - factor: 1, - name: 'boiler_flowtemp', - uom: 1, - value_type: 1, - writeable: true - } -]; + entities: [ + { + id: 0, + device_id: 8, + type_id: 24, + offset: 0, + factor: 1, + name: 'boiler_flowtemp', + uom: 1, + value_type: 1, + writeable: true + } + ] +}; // SCHEDULE -let emsesp_schedule = - // schedule: [ - [ +let emsesp_schedule = { + schedule: [ { id: 1, active: true, @@ -1836,7 +1839,8 @@ let emsesp_schedule = value: '', name: 'auto_restart' } - ]; + ] +}; // CUSTOMIZATIONS const emsesp_deviceentities_1 = [{}]; @@ -1993,16 +1997,13 @@ const emsesp_deviceentities_4 = [ ]; // LOG -rest_server.get(FETCH_LOG_ENDPOINT, (req, res) => { - const encoded = msgpack.encode(fetch_log); - console.log('fetchlog'); - res.write(encoded, 'binary'); - res.end(null, 'binary'); +rest_server.post(FETCH_LOG_ENDPOINT, (req, res) => { + console.log('command: fetchLog'); + res.sendStatus(200); }); rest_server.get(LOG_SETTINGS_ENDPOINT, (req, res) => { res.json(log_settings); }); -// TODO do we need to send back here a res.SendStatus(200) ? rest_server.post(LOG_SETTINGS_ENDPOINT, (req, res) => { log_settings = req.body; console.log(JSON.stringify(log_settings)); @@ -2019,14 +2020,21 @@ rest_server.get(NETWORK_SETTINGS_ENDPOINT, (req, res) => { rest_server.post(NETWORK_SETTINGS_ENDPOINT, (req, res) => { network_settings = req.body; console.log(JSON.stringify(network_settings)); - // TODO do we need to send back here a res.SendStatus(200) ? res.sendStatus(200); }); rest_server.get(LIST_NETWORKS_ENDPOINT, (req, res) => { + countWifiScanPoll = 0; // stop the poll res.json(list_networks); }); rest_server.get(SCAN_NETWORKS_ENDPOINT, (req, res) => { - res.sendStatus(202); // reboot required + console.log('scan networks'); + if (countWifiScanPoll++ === 2) { + console.log('done, have list'); + res.sendStatus(200); // ready to send list + } else { + console.log('...waiting #' + countWifiScanPoll); + res.sendStatus(202); // waiting.... + } }); // AP @@ -2050,8 +2058,7 @@ rest_server.get(OTA_SETTINGS_ENDPOINT, (req, res) => { rest_server.post(OTA_SETTINGS_ENDPOINT, (req, res) => { ota_settings = req.body; console.log(JSON.stringify(ota_settings)); - res.json(ota_settings); - // TODO do we need to send back a res.sendStatus(200); ? + res.sendStatus(200); }); // MQTT @@ -2061,8 +2068,7 @@ rest_server.get(MQTT_SETTINGS_ENDPOINT, (req, res) => { rest_server.post(MQTT_SETTINGS_ENDPOINT, (req, res) => { mqtt_settings = req.body; console.log(JSON.stringify(mqtt_settings)); - res.json(mqtt_settings); - // TODO do we need to send back a res.sendStatus(200); ? + res.sendStatus(200); }); rest_server.get(MQTT_STATUS_ENDPOINT, (req, res) => { res.json(mqtt_status); @@ -2075,9 +2081,7 @@ rest_server.get(NTP_SETTINGS_ENDPOINT, (req, res) => { rest_server.post(NTP_SETTINGS_ENDPOINT, (req, res) => { ntp_settings = req.body; console.log(JSON.stringify(ntp_settings)); - // TODO do we need to send back a res.sendStatus(200); ? - - res.json(ntp_settings); + res.sendStatus(200); }); rest_server.get(NTP_STATUS_ENDPOINT, (req, res) => { res.json(ntp_status); @@ -2096,9 +2100,7 @@ rest_server.get(SECURITY_SETTINGS_ENDPOINT, (req, res) => { rest_server.post(SECURITY_SETTINGS_ENDPOINT, (req, res) => { security_settings = req.body; console.log(JSON.stringify(security_settings)); - // TODO do we need to send back a res.sendStatus(200); ? - - res.json(security_settings); + res.sendStatus(200); }); rest_server.get(FEATURES_ENDPOINT, (req, res) => { res.json(features); @@ -2118,9 +2120,7 @@ rest_server.post(UPLOAD_FILE_ENDPOINT, (req, res) => { }); rest_server.post(SIGN_IN_ENDPOINT, (req, res) => { console.log('Signed in as ' + req.body.username); - // TODO do we need to send back a res.sendStatus(200); ? - - res.json(signin); + res.json(signin); // watch out, this has a return value }); rest_server.get(GENERATE_TOKEN_ENDPOINT, (req, res) => { res.json(generate_token); @@ -2693,7 +2693,7 @@ rest_server.get(SCHEDULE_ENDPOINT, (req, res) => { const ENTITIES_ENDPOINT = REST_ENDPOINT_ROOT + 'entities'; rest_server.get(ENTITIES_ENDPOINT, (req, res) => { - console.log('Sending Entities data'); + console.log('Sending Custom Entities data'); res.json(emsesp_entities); }); @@ -2734,5 +2734,5 @@ rest_server.get(ES_LOG_ENDPOINT, function (req, res) { log_index = 0; } fetch_log.events.push(data); // append to buffer - }, 1000); + }, 3000); }); diff --git a/src/web/WebLogService.cpp b/src/web/WebLogService.cpp index 5848a50d2..1800f0c25 100644 --- a/src/web/WebLogService.cpp +++ b/src/web/WebLogService.cpp @@ -1,7 +1,7 @@ /* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020-2023 Paul Derbyshire - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -24,25 +24,16 @@ namespace emsesp { WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * securityManager) : events_(EVENT_SOURCE_LOG_PATH) - , setValues_(LOG_SETTINGS_PATH, std::bind(&WebLogService::setValues, this, _1, _2), 256) { // for POSTS - + , setValues_(LOG_SETTINGS_PATH, std::bind(&WebLogService::setValues, this, _1, _2), 256) { events_.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN)); - server->addHandler(&events_); - server->on(EVENT_SOURCE_LOG_PATH, HTTP_GET, std::bind(&WebLogService::forbidden, this, _1)); + server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getValues, this, _1)); // get settings - // for bring back the whole log - server->on(FETCH_LOG_PATH, HTTP_GET, std::bind(&WebLogService::fetchLog, this, _1)); + // for bring back the whole log - is a command, hence a POST + server->on(FETCH_LOG_PATH, HTTP_POST, std::bind(&WebLogService::fetchLog, this, _1)); - // get when page is loaded - server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getValues, this, _1)); - - // for setting a level server->addHandler(&setValues_); -} - -void WebLogService::forbidden(AsyncWebServerRequest * request) { - request->send(403); + server->addHandler(&events_); } // start the log service with INFO level @@ -211,6 +202,7 @@ void WebLogService::transmit(const QueuedLogMessage & message) { } // send the complete log buffer to the API, not filtering on log level +// done by resetting the pointer void WebLogService::fetchLog(AsyncWebServerRequest * request) { log_message_id_tail_ = 0; request->send(200); @@ -224,6 +216,8 @@ void WebLogService::setValues(AsyncWebServerRequest * request, JsonVariant & jso auto && body = json.as(); + // TODO refactor into one load and one save + uuid::log::Level level = body["level"]; log_level(level); diff --git a/src/web/WebLogService.h b/src/web/WebLogService.h index 1f069f922..aa852fabb 100644 --- a/src/web/WebLogService.h +++ b/src/web/WebLogService.h @@ -58,7 +58,6 @@ class WebLogService : public uuid::log::Handler { const std::shared_ptr content_; // Log message content }; - void forbidden(AsyncWebServerRequest * request); void transmit(const QueuedLogMessage & message); void fetchLog(AsyncWebServerRequest * request); void getValues(AsyncWebServerRequest * request); diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index 9dd8d25f6..22ffed2f4 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -50,7 +50,7 @@ void WebScheduler::read(WebScheduler & webScheduler, JsonObject & root) { } } -// call on initialization and also when the Scheduile web page is updated +// call on initialization and also when the Scheduile web page is saved // this loads the data into the internal class StateUpdateResult WebScheduler::update(JsonObject & root, WebScheduler & webScheduler) { #ifdef EMSESP_STANDALONE From b1d666d7b97f69d107dae088fa262caf486d8129 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 18 Jun 2023 10:46:50 +0200 Subject: [PATCH 020/163] alova - refactor wifi scan --- interface/src/api/endpoints.ts | 7 +-- interface/src/api/network.ts | 5 +- interface/src/api/security.ts | 2 +- interface/src/api/system.ts | 7 ++- .../framework/network/WiFiNetworkScanner.tsx | 49 +++++++------------ .../framework/security/ManageUsersForm.tsx | 1 + .../security/SecuritySettingsForm.tsx | 1 + .../framework/system/GeneralFileUpload.tsx | 2 +- .../src/framework/system/RestartMonitor.tsx | 6 +-- .../src/framework/system/SystemStatusForm.tsx | 2 +- interface/src/project/SettingsEntities.tsx | 6 +-- .../src/project/SettingsEntitiesDialog.tsx | 4 +- lib/framework/WiFiScanner.cpp | 3 +- mock-api/server.js | 23 +++++---- 14 files changed, 58 insertions(+), 60 deletions(-) diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index 9153dd17c..ddb1514af 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -35,9 +35,10 @@ export const alovaInstance = createAlova({ responded: { onSuccess: async (response) => { - if (response.status === 202) { - throw new Error('Wait'); - } else if (response.status === 205) { + // if (response.status === 202) { + // throw new Error('Wait'); // wifi scan in progress + // } else + if (response.status === 205) { throw new Error('Reboot required'); } else if (response.status === 400) { throw new Error('Request Failed'); diff --git a/interface/src/api/network.ts b/interface/src/api/network.ts index 03bb7ead6..77610f9ed 100644 --- a/interface/src/api/network.ts +++ b/interface/src/api/network.ts @@ -4,7 +4,10 @@ import type { WiFiNetworkList, NetworkSettings, NetworkStatus } from 'types'; export const readNetworkStatus = () => alovaInstance.Get('/rest/networkStatus'); export const scanNetworks = () => alovaInstance.Get('/rest/scanNetworks'); -export const listNetworks = () => alovaInstance.Get('/rest/listNetworks'); +export const listNetworks = () => + alovaInstance.Get('/rest/listNetworks', { + name: 'listNetworks' + }); export const readNetworkSettings = () => alovaInstance.Get('/rest/networkSettings', { name: 'networkSettings' }); export const updateNetworkSettings = (wifiSettings: NetworkSettings) => diff --git a/interface/src/api/security.ts b/interface/src/api/security.ts index 1fecb6611..ff4cb4e64 100644 --- a/interface/src/api/security.ts +++ b/interface/src/api/security.ts @@ -3,7 +3,7 @@ import type { AxiosPromise } from 'axios'; import type { SecuritySettings, Token } from 'types'; -// TODO move to Alova +// TODO move these 3 to Alova export function readSecuritySettings(): AxiosPromise { return AXIOS.get('/securitySettings'); } diff --git a/interface/src/api/system.ts b/interface/src/api/system.ts index 24bdaa10e..41c4bb639 100644 --- a/interface/src/api/system.ts +++ b/interface/src/api/system.ts @@ -4,10 +4,8 @@ import type { AxiosPromise } from 'axios'; import type { OTASettings, SystemStatus, LogSettings } from 'types'; -export const readSystemStatus = (timeout?: number) => - alovaInstance.Get('/rest/systemStatus', { - params: { timeout } - }); +// SystemStatus - also used to ping in Restart monitor +export const readSystemStatus = () => alovaInstance.Get('/rest/systemStatus'); // commands export const restart = () => alovaInstance.Post('/rest/restart'); @@ -23,5 +21,6 @@ export const readLogSettings = () => alovaInstance.Get(`/rest/logSe export const updateLogSettings = (data: any) => alovaInstance.Post('/rest/logSettings', data); export const fetchLog = () => alovaInstance.Post('/rest/fetchLog'); +// TODO fileupload move to Alova export const uploadFile = (file: File, config?: FileUploadConfig): AxiosPromise => startUploadFile('/uploadFile', file, config); diff --git a/interface/src/framework/network/WiFiNetworkScanner.tsx b/interface/src/framework/network/WiFiNetworkScanner.tsx index 72a967f21..f7d227104 100644 --- a/interface/src/framework/network/WiFiNetworkScanner.tsx +++ b/interface/src/framework/network/WiFiNetworkScanner.tsx @@ -1,8 +1,8 @@ import PermScanWifiIcon from '@mui/icons-material/PermScanWifi'; import { Button } from '@mui/material'; -import { useRequest } from 'alova'; -import { useState, useCallback, useRef } from 'react'; -import { toast } from 'react-toastify'; +// eslint-disable-next-line import/named +import { updateState, useRequest } from 'alova'; +import { useState, useRef } from 'react'; import WiFiNetworkSelector from './WiFiNetworkSelector'; import type { FC } from 'react'; @@ -15,48 +15,37 @@ const NUM_POLLS = 10; const POLLING_FREQUENCY = 1000; const WiFiNetworkScanner: FC = () => { - const { LL } = useI18nContext(); - const pollCount = useRef(0); + const { LL } = useI18nContext(); const [errorMessage, setErrorMessage] = useState(); - const { data: networkList, send: getNetworkList } = useRequest(NetworkApi.listNetworks, { + const { send: scanNetworks, onComplete: onCompleteScanNetworks } = useRequest(NetworkApi.scanNetworks); // is called on page load to start network scan + const { + data: networkList, + send: getNetworkList, + onSuccess: onSuccessNetworkList + } = useRequest(NetworkApi.listNetworks, { immediate: false }); - const { - send: scanNetworks, - onSuccess: onSuccessScanNetworks, - onError: onErrorScanNetworks - } = useRequest(NetworkApi.scanNetworks); - - const finishedWithError = useCallback((message: string) => { - toast.error(message); - setErrorMessage(message); - pollCount.current = 0; - }, []); - - onErrorScanNetworks((event) => { - console.log('onErrorScanNetworks'); // TODO fix - if (event.error?.message === 'Wait') { - // 202 - console.log('not ready...: ', event.error?.message); // TODO fix + onSuccessNetworkList((event) => { + if (!event.data) { const completedPollCount = pollCount.current + 1; if (completedPollCount < NUM_POLLS) { pollCount.current = completedPollCount; - setTimeout(scanNetworks, POLLING_FREQUENCY); + setTimeout(getNetworkList, POLLING_FREQUENCY); } else { - finishedWithError(LL.PROBLEM_LOADING()); + setErrorMessage(LL.PROBLEM_LOADING()); + pollCount.current = 0; } - } else { - finishedWithError(LL.PROBLEM_LOADING()); } }); - onSuccessScanNetworks(() => { - console.log('onCompleteScanNetworks'); // TODO fix + onCompleteScanNetworks(() => { pollCount.current = 0; - void getNetworkList(); // fetch the list + setErrorMessage(undefined); + updateState('listNetworks', () => undefined); + void getNetworkList(); }); const renderNetworkScanner = () => { diff --git a/interface/src/framework/security/ManageUsersForm.tsx b/interface/src/framework/security/ManageUsersForm.tsx index e4aa4862a..1c0f6a856 100644 --- a/interface/src/framework/security/ManageUsersForm.tsx +++ b/interface/src/framework/security/ManageUsersForm.tsx @@ -23,6 +23,7 @@ import { useRest } from 'utils'; import { createUserValidator } from 'validators'; const ManageUsersForm: FC = () => { + // TODO move to Alova const { loadData, saving, data, setData, saveData, errorMessage } = useRest({ read: SecurityApi.readSecuritySettings, update: SecurityApi.updateSecuritySettings diff --git a/interface/src/framework/security/SecuritySettingsForm.tsx b/interface/src/framework/security/SecuritySettingsForm.tsx index 14930d399..67839522d 100644 --- a/interface/src/framework/security/SecuritySettingsForm.tsx +++ b/interface/src/framework/security/SecuritySettingsForm.tsx @@ -18,6 +18,7 @@ const SecuritySettingsForm: FC = () => { const { LL } = useI18nContext(); const [fieldErrors, setFieldErrors] = useState(); + // TODO move to Alova const { loadData, saving, data, setData, origData, dirtyFlags, blocker, setDirtyFlags, saveData, errorMessage } = useRest({ read: SecurityApi.readSecuritySettings, diff --git a/interface/src/framework/system/GeneralFileUpload.tsx b/interface/src/framework/system/GeneralFileUpload.tsx index 8e2243fed..e7336dc18 100644 --- a/interface/src/framework/system/GeneralFileUpload.tsx +++ b/interface/src/framework/system/GeneralFileUpload.tsx @@ -12,7 +12,7 @@ import { useI18nContext } from 'i18n/i18n-react'; import * as EMSESP from 'project/api'; interface UploadFileProps { - // TODO fileupload upload move to alova + // TODO fileupload move to alova uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise; } diff --git a/interface/src/framework/system/RestartMonitor.tsx b/interface/src/framework/system/RestartMonitor.tsx index 83ad443ba..c7112a244 100644 --- a/interface/src/framework/system/RestartMonitor.tsx +++ b/interface/src/framework/system/RestartMonitor.tsx @@ -8,7 +8,6 @@ import { FormLoader } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; const RESTART_TIMEOUT = 2 * 60 * 1000; -const POLL_TIMEOUT = 2000; const POLL_INTERVAL = 5000; const RestartMonitor: FC = () => { @@ -17,14 +16,15 @@ const RestartMonitor: FC = () => { const { LL } = useI18nContext(); - const { send: readSystemStatus } = useRequest((timeout) => SystemApi.readSystemStatus(timeout), { + const { send: readSystemStatus } = useRequest(SystemApi.readSystemStatus(), { + force: true, immediate: false }); const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT); const poll = useRef(async () => { try { - await readSystemStatus(POLL_TIMEOUT); + await readSystemStatus(); document.location.href = '/fileUpdated'; } catch (error) { if (new Date().getTime() < timeoutAt.current) { diff --git a/interface/src/framework/system/SystemStatusForm.tsx b/interface/src/framework/system/SystemStatusForm.tsx index 54d3b922f..c90202624 100644 --- a/interface/src/framework/system/SystemStatusForm.tsx +++ b/interface/src/framework/system/SystemStatusForm.tsx @@ -73,7 +73,7 @@ const SystemStatusForm: FC = () => { immediate: false }); - const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus); + const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus, { force: true }); useEffect(() => { void axios.get(VERSIONCHECK_ENDPOINT).then((response) => { diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index 9d83cd329..cec062a6a 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -184,9 +184,7 @@ const SettingsEntities: FC = () => { } function showHex(value: number, digit: number) { - return digit === 4 - ? '0x' + ('000' + value.toString(16).toUpperCase()).slice(-4) - : '0x' + ('0' + value.toString(16).toUpperCase()).slice(-2); + return '0x' + value.toString(16).toUpperCase().padStart(digit, '0'); } const renderEntity = () => { @@ -212,7 +210,7 @@ const SettingsEntities: FC = () => { editEntityItem(ei)}> {ei.name} {showHex(ei.device_id as number, 2)} - {showHex(ei.type_id as number, 4)} + {showHex(ei.type_id as number, 3)} {ei.offset} {formatValue(ei.value, ei.uom)} diff --git a/interface/src/project/SettingsEntitiesDialog.tsx b/interface/src/project/SettingsEntitiesDialog.tsx index 1ae09bbd0..5e16f627b 100644 --- a/interface/src/project/SettingsEntitiesDialog.tsx +++ b/interface/src/project/SettingsEntitiesDialog.tsx @@ -58,8 +58,8 @@ const SettingsEntitiesDialog = ({ // convert to hex strings straight away setEditItem({ ...selectedItem, - device_id: selectedItem.device_id.toString(16).toUpperCase().slice(-2), - type_id: selectedItem.type_id.toString(16).toUpperCase().slice(-4) + device_id: selectedItem.device_id.toString(16).toUpperCase(), + type_id: selectedItem.type_id.toString(16).toUpperCase() }); } }, [open, selectedItem]); diff --git a/lib/framework/WiFiScanner.cpp b/lib/framework/WiFiScanner.cpp index 708179686..3813c9ff6 100644 --- a/lib/framework/WiFiScanner.cpp +++ b/lib/framework/WiFiScanner.cpp @@ -12,11 +12,12 @@ WiFiScanner::WiFiScanner(AsyncWebServer * server, SecurityManager * securityMana }; void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) { + request->send(202); // special code to indicate scan in progress + if (WiFi.scanComplete() != -1) { WiFi.scanDelete(); WiFi.scanNetworks(true); } - request->send(202); // special code to indicate scan in progress } void WiFiScanner::listNetworks(AsyncWebServerRequest * request) { diff --git a/mock-api/server.js b/mock-api/server.js index df2096afb..045ab6695 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -271,7 +271,8 @@ const FACTORY_RESET_ENDPOINT = REST_ENDPOINT_ROOT + 'factoryReset'; const UPLOAD_FILE_ENDPOINT = REST_ENDPOINT_ROOT + 'uploadFile'; const SIGN_IN_ENDPOINT = REST_ENDPOINT_ROOT + 'signIn'; const GENERATE_TOKEN_ENDPOINT = REST_ENDPOINT_ROOT + 'generateToken'; -const system_status = { + +let system_status = { emsesp_version: '3.6.0-demo', esp_platform: 'ESP32', max_alloc_heap: 89, @@ -2023,19 +2024,20 @@ rest_server.post(NETWORK_SETTINGS_ENDPOINT, (req, res) => { res.sendStatus(200); }); rest_server.get(LIST_NETWORKS_ENDPOINT, (req, res) => { - countWifiScanPoll = 0; // stop the poll - res.json(list_networks); -}); -rest_server.get(SCAN_NETWORKS_ENDPOINT, (req, res) => { - console.log('scan networks'); - if (countWifiScanPoll++ === 2) { + if (countWifiScanPoll++ === 4) { console.log('done, have list'); - res.sendStatus(200); // ready to send list + res.json(list_networks); // send list } else { console.log('...waiting #' + countWifiScanPoll); - res.sendStatus(202); // waiting.... + res.sendStatus(200); // waiting.... } }); +// TODO should be a post as its a command? +rest_server.get(SCAN_NETWORKS_ENDPOINT, (req, res) => { + console.log('start scan networks'); + countWifiScanPoll = 0; // stop the poll + res.sendStatus(200); // always 202, poll for list +}); // AP rest_server.get(AP_SETTINGS_ENDPOINT, (req, res) => { @@ -2092,6 +2094,9 @@ rest_server.post(TIME_ENDPOINT, (req, res) => { // SYSTEM rest_server.get(SYSTEM_STATUS_ENDPOINT, (req, res) => { + console.log('get systemStatus'); + // create some random data to see if caching works + system_status.fs_used = Math.floor(Math.random() * (Math.floor(200) - 100) + 100); res.json(system_status); }); rest_server.get(SECURITY_SETTINGS_ENDPOINT, (req, res) => { From ce1b9f22cb62c5846338241b0dab464d0cd53c45 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 18 Jun 2023 16:35:32 +0200 Subject: [PATCH 021/163] alova - add interceptor --- interface/src/AuthenticatedRouting.tsx | 31 ++--- interface/src/SignIn.tsx | 29 +++-- interface/src/api/authentication.ts | 13 +-- interface/src/api/endpoints.ts | 64 +++------- interface/src/api/security.ts | 20 ++-- interface/src/api/system.ts | 28 ++++- .../src/components/upload/useFileUpload.ts | 4 +- .../authentication/Authentication.tsx | 26 +++-- interface/src/framework/ap/APSettingsForm.tsx | 4 +- .../src/framework/mqtt/MqttSettingsForm.tsx | 4 +- .../framework/network/NetworkSettingsForm.tsx | 4 +- .../src/framework/ntp/NTPSettingsForm.tsx | 4 +- .../src/framework/security/GenerateToken.tsx | 24 ++-- .../framework/security/ManageUsersForm.tsx | 7 +- .../security/SecuritySettingsForm.tsx | 23 ++-- .../src/framework/system/OTASettingsForm.tsx | 4 +- interface/src/framework/system/SystemLog.tsx | 4 +- .../src/framework/system/SystemStatusForm.tsx | 22 +--- interface/src/project/SettingsApplication.tsx | 4 +- interface/src/utils/endpoints.ts | 9 -- interface/src/utils/index.ts | 3 - interface/src/utils/useRest.ts | 109 ++++++++---------- interface/src/utils/useRest2.ts | 84 -------------- mock-api/server.js | 7 +- 24 files changed, 200 insertions(+), 331 deletions(-) delete mode 100644 interface/src/utils/endpoints.ts delete mode 100644 interface/src/utils/useRest2.ts diff --git a/interface/src/AuthenticatedRouting.tsx b/interface/src/AuthenticatedRouting.tsx index 2c99ce3c3..b0fe722ff 100644 --- a/interface/src/AuthenticatedRouting.tsx +++ b/interface/src/AuthenticatedRouting.tsx @@ -21,22 +21,23 @@ const AuthenticatedRouting: FC = () => { const location = useLocation(); const navigate = useNavigate(); - const handleApiResponseError = useCallback( - (error: AxiosError) => { - if (error.response && error.response.status === 401) { - AuthenticationApi.storeLoginRedirect(location); - navigate('/unauthorized'); - } - return Promise.reject(error); - }, - [location, navigate] - ); + // TODO fix this - how to redirect on a 401 + // const handleApiResponseError = useCallback( + // (error: AxiosError) => { + // if (error.response && error.response.status === 401) { + // AuthenticationApi.storeLoginRedirect(location); + // navigate('/unauthorized'); + // } + // return Promise.reject(error); + // }, + // [location, navigate] + // ); - useEffect(() => { - // TODO replace AXIOS.interceptors.response.use ??? - const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError); - return () => AXIOS.interceptors.response.eject(axiosHandlerId); - }, [handleApiResponseError]); + // useEffect(() => { + // // TODO replace AXIOS.interceptors.response.use ??? + // const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError); + // return () => AXIOS.interceptors.response.eject(axiosHandlerId); + // }, [handleApiResponseError]); return ( diff --git a/interface/src/SignIn.tsx b/interface/src/SignIn.tsx index bcd618fd2..138427b55 100644 --- a/interface/src/SignIn.tsx +++ b/interface/src/SignIn.tsx @@ -1,5 +1,6 @@ import ForwardIcon from '@mui/icons-material/Forward'; import { Box, Fab, Paper, Typography, Button } from '@mui/material'; +import { useRequest } from 'alova'; import { useContext, useState } from 'react'; import { toast } from 'react-toastify'; import type { ValidateFieldsError } from 'async-validator'; @@ -23,7 +24,7 @@ import { ReactComponent as SVflag } from 'i18n/SV.svg'; import { ReactComponent as TRflag } from 'i18n/TR.svg'; import { I18nContext } from 'i18n/i18n-react'; import { loadLocaleAsync } from 'i18n/i18n-util.async'; -import { extractErrorMessage, onEnterCallback, updateValue } from 'utils'; +import { onEnterCallback, updateValue } from 'utils'; import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators'; const SignIn: FC = () => { @@ -38,23 +39,27 @@ const SignIn: FC = () => { const [processing, setProcessing] = useState(false); const [fieldErrors, setFieldErrors] = useState(); + const { send: callSignIn, onSuccess } = useRequest((request: SignInRequest) => AuthenticationApi.signIn(request), { + immediate: false + }); + + onSuccess((response) => { + if (response.data) { + authenticationContext.signIn(response.data.access_token); + } + }); + const updateLoginRequestValue = updateValue(setSignInRequest); const signIn = async () => { - try { - // TODO move to Alova - const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest); - authenticationContext.signIn(loginResponse.access_token); - } catch (error) { - if (error.response) { - if (error.response?.status === 401) { - toast.warn(LL.INVALID_LOGIN()); - } + await callSignIn(signInRequest).catch((event) => { + if (event.message === 'Unauthorized') { + toast.warn(LL.INVALID_LOGIN()); } else { - toast.error(extractErrorMessage(error, LL.ERROR())); + toast.error(LL.ERROR() + ' ' + event.message); } setProcessing(false); - } + }); }; const validateAndSignIn = async () => { diff --git a/interface/src/api/authentication.ts b/interface/src/api/authentication.ts index 805596a3c..b4f3a9bd3 100644 --- a/interface/src/api/authentication.ts +++ b/interface/src/api/authentication.ts @@ -1,6 +1,5 @@ import jwtDecode from 'jwt-decode'; -import { ACCESS_TOKEN, AXIOS } from './endpoints'; -import type { AxiosPromise } from 'axios'; +import { ACCESS_TOKEN, alovaInstance } from './endpoints'; import type * as H from 'history'; import type { Path } from 'react-router-dom'; @@ -9,14 +8,8 @@ import type { Me, SignInRequest, SignInResponse } from 'types'; export const SIGN_IN_PATHNAME = 'loginPathname'; export const SIGN_IN_SEARCH = 'loginSearch'; -// TODO move verifyAuthorization to Alova -export function verifyAuthorization(): AxiosPromise { - return AXIOS.get('/verifyAuthorization'); -} -// TODO move signIn to Alova -export function signIn(request: SignInRequest): AxiosPromise { - return AXIOS.post('/signIn', request); -} +export const verifyAuthorization = () => alovaInstance.Get('/rest/verifyAuthorization'); +export const signIn = (request: SignInRequest) => alovaInstance.Post('/rest/signIn', request); export function getStorage() { return localStorage || sessionStorage; diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index ddb1514af..10463d74e 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -4,10 +4,7 @@ import ReactHook from 'alova/react'; import axios from 'axios'; import { unpack } from '../api/unpack'; -// TODO axios can be removed import type { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios'; -export const REST_BASE_URL = '/rest/'; -export const API_BASE_URL = '/api/'; export const ACCESS_TOKEN = 'access_token'; @@ -52,14 +49,26 @@ export const alovaInstance = createAlova({ return data; }, - onError: (error) => { + // TODO handle errors + // Interceptor for request failure + // This interceptor will be entered when the request is wrong. + // The second parameter is the method instance of the current request, you can use it to synchronize the configuration information before and after the request + onError: (error, method) => { + console.log('error:', error); // TODO fix me + console.log('method:', method); // TODO fix me alert(error.message); } } }); +export const alovaInstanceGH = createAlova({ + baseURL: 'https://api.github.com/repos/emsesp/EMS-ESP32', + statesHook: ReactHook, + requestAdapter: xhrRequestAdapter() +}); + export const AXIOS = axios.create({ - baseURL: REST_BASE_URL, + baseURL: '/rest/', headers: { 'Content-Type': 'application/json' }, @@ -78,49 +87,8 @@ export const AXIOS = axios.create({ ] }); -export const AXIOS_API = axios.create({ - baseURL: API_BASE_URL, - headers: { - 'Content-Type': 'application/json' - }, - transformRequest: [ - (data, headers) => { - if (headers) { - if (localStorage.getItem(ACCESS_TOKEN)) { - headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN); - } - if (headers['Content-Type'] !== 'application/json') { - return data; - } - } - return JSON.stringify(data); - } - ] -}); - -export const AXIOS_BIN = axios.create({ - baseURL: REST_BASE_URL, - headers: { - 'Content-Type': 'application/json' - }, - responseType: 'arraybuffer', - transformRequest: [ - (data, headers) => { - if (headers) { - if (localStorage.getItem(ACCESS_TOKEN)) { - headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN); - } - if (headers['Content-Type'] !== 'application/json') { - return data; - } - } - return JSON.stringify(data); - } - ], - transformResponse: [(data) => unpack(data)] -}); - -// TODO replace fileupload with alova, see https://alova.js.org/next-step/download-upload-progress +// TODO fileupload move to alova +// see https://alova.js.org/next-step/download-upload-progress export interface FileUploadConfig { cancelToken?: CancelToken; onUploadProgress?: (progressEvent: AxiosProgressEvent) => void; diff --git a/interface/src/api/security.ts b/interface/src/api/security.ts index ff4cb4e64..6cbcce6d9 100644 --- a/interface/src/api/security.ts +++ b/interface/src/api/security.ts @@ -1,17 +1,13 @@ -import { AXIOS } from './endpoints'; -import type { AxiosPromise } from 'axios'; +import { alovaInstance } from './endpoints'; import type { SecuritySettings, Token } from 'types'; -// TODO move these 3 to Alova -export function readSecuritySettings(): AxiosPromise { - return AXIOS.get('/securitySettings'); -} +export const readSecuritySettings = () => alovaInstance.Get('/rest/securitySettings'); -export function updateSecuritySettings(securitySettings: SecuritySettings): AxiosPromise { - return AXIOS.post('/securitySettings', securitySettings); -} +export const updateSecuritySettings = (securitySettings: SecuritySettings) => + alovaInstance.Post('/rest/securitySettings', securitySettings); -export function generateToken(username?: string): AxiosPromise { - return AXIOS.get('/generateToken', { params: { username } }); -} +export const generateToken = (username?: string) => + alovaInstance.Get('/rest/generateToken', { + params: { username } + }); diff --git a/interface/src/api/system.ts b/interface/src/api/system.ts index 41c4bb639..5e21a1292 100644 --- a/interface/src/api/system.ts +++ b/interface/src/api/system.ts @@ -1,8 +1,8 @@ -import { alovaInstance, startUploadFile } from './endpoints'; +import { alovaInstance, alovaInstanceGH, startUploadFile } from './endpoints'; import type { FileUploadConfig } from './endpoints'; import type { AxiosPromise } from 'axios'; -import type { OTASettings, SystemStatus, LogSettings } from 'types'; +import type { OTASettings, SystemStatus, LogSettings, Version } from 'types'; // SystemStatus - also used to ping in Restart monitor export const readSystemStatus = () => alovaInstance.Get('/rest/systemStatus'); @@ -21,6 +21,28 @@ export const readLogSettings = () => alovaInstance.Get(`/rest/logSe export const updateLogSettings = (data: any) => alovaInstance.Post('/rest/logSettings', data); export const fetchLog = () => alovaInstance.Post('/rest/fetchLog'); -// TODO fileupload move to Alova +// Get versions from github +export const getStableVersion = () => + alovaInstanceGH.Get('releases/latest', { + transformData(reponse: any) { + return { + version: reponse.data.name, + url: reponse.data.assets[1].browser_download_url, + changelog: reponse.data.assets[0].browser_download_url + }; + } + }); +export const getDevVersion = () => + alovaInstanceGH.Get('releases/tags/latest', { + transformData(reponse: any) { + return { + version: reponse.data.name.split(/\s+/).splice(-1), + url: reponse.data.assets[1].browser_download_url, + changelog: reponse.data.assets[0].browser_download_url + }; + } + }); + +// TODO fileupload move to alova export const uploadFile = (file: File, config?: FileUploadConfig): AxiosPromise => startUploadFile('/uploadFile', file, config); diff --git a/interface/src/components/upload/useFileUpload.ts b/interface/src/components/upload/useFileUpload.ts index b3a62a39b..dcd92ebc5 100644 --- a/interface/src/components/upload/useFileUpload.ts +++ b/interface/src/components/upload/useFileUpload.ts @@ -6,7 +6,6 @@ import type { FileUploadConfig } from 'api/endpoints'; import type { AxiosPromise, CancelTokenSource, AxiosProgressEvent } from 'axios'; import { useI18nContext } from 'i18n/i18n-react'; -import { extractErrorMessage } from 'utils'; interface MediaUploadOptions { // TODO fileupload move to alova @@ -40,6 +39,7 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => { [uploadCancelToken] ); + // TODO fileupload move to alova const uploadFile = async (images: File[]) => { try { const cancelToken = axios.CancelToken.source(); @@ -61,7 +61,7 @@ const useFileUpload = ({ upload }: MediaUploadOptions) => { toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED()); } else { resetUploadingStates(); - toast.error(extractErrorMessage(error, LL.UPLOAD() + ' ' + LL.FAILED(0))); + toast.error(LL.UPLOAD() + ' ' + LL.FAILED(0)); } } }; diff --git a/interface/src/contexts/authentication/Authentication.tsx b/interface/src/contexts/authentication/Authentication.tsx index 1ff343b19..f9982bffd 100644 --- a/interface/src/contexts/authentication/Authentication.tsx +++ b/interface/src/contexts/authentication/Authentication.tsx @@ -1,3 +1,4 @@ +import { useRequest } from 'alova'; import { useCallback, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -19,6 +20,10 @@ const Authentication: FC = ({ children }) => { const [initialized, setInitialized] = useState(false); const [me, setMe] = useState(); + const { send: verifyAuthorization } = useRequest(AuthenticationApi.verifyAuthorization(), { + immediate: false + }); + const signIn = (accessToken: string) => { try { AuthenticationApi.getStorage().setItem(ACCESS_TOKEN, accessToken); @@ -42,18 +47,17 @@ const Authentication: FC = ({ children }) => { const refresh = useCallback(async () => { const accessToken = AuthenticationApi.getStorage().getItem(ACCESS_TOKEN); if (accessToken) { - try { - await AuthenticationApi.verifyAuthorization(); - setMe(AuthenticationApi.decodeMeJWT(accessToken)); - setInitialized(true); - } catch (error) { - setMe(undefined); - setInitialized(true); - } - } else { - setMe(undefined); - setInitialized(true); + await verifyAuthorization() + .then(() => { + setMe(AuthenticationApi.decodeMeJWT(accessToken)); + setInitialized(true); + }) + .catch(() => { + setMe(undefined); + setInitialized(true); + }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { diff --git a/interface/src/framework/ap/APSettingsForm.tsx b/interface/src/framework/ap/APSettingsForm.tsx index b6e7cacf9..ade6dab56 100644 --- a/interface/src/framework/ap/APSettingsForm.tsx +++ b/interface/src/framework/ap/APSettingsForm.tsx @@ -20,7 +20,7 @@ import { import { useI18nContext } from 'i18n/i18n-react'; import { APProvisionMode } from 'types'; -import { numberValue, updateValueDirty, useRest2 } from 'utils'; +import { numberValue, updateValueDirty, useRest } from 'utils'; import { createAPSettingsValidator, validate } from 'validators'; @@ -39,7 +39,7 @@ const APSettingsForm: FC = () => { blocker, saveData, errorMessage - } = useRest2({ + } = useRest({ read: APApi.readAPSettings, update: APApi.updateAPSettings }); diff --git a/interface/src/framework/mqtt/MqttSettingsForm.tsx b/interface/src/framework/mqtt/MqttSettingsForm.tsx index eba8d018c..d6b825add 100644 --- a/interface/src/framework/mqtt/MqttSettingsForm.tsx +++ b/interface/src/framework/mqtt/MqttSettingsForm.tsx @@ -17,7 +17,7 @@ import { BlockNavigation } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { numberValue, updateValueDirty, useRest2 } from 'utils'; +import { numberValue, updateValueDirty, useRest } from 'utils'; import { createMqttSettingsValidator, validate } from 'validators'; @@ -33,7 +33,7 @@ const MqttSettingsForm: FC = () => { blocker, saveData, errorMessage - } = useRest2({ + } = useRest({ read: MqttApi.readMqttSettings, update: MqttApi.updateMqttSettings }); diff --git a/interface/src/framework/network/NetworkSettingsForm.tsx b/interface/src/framework/network/NetworkSettingsForm.tsx index 3ca414935..f4fefbb2a 100644 --- a/interface/src/framework/network/NetworkSettingsForm.tsx +++ b/interface/src/framework/network/NetworkSettingsForm.tsx @@ -43,7 +43,7 @@ import { } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { numberValue, updateValueDirty, useRest2 } from 'utils'; +import { numberValue, updateValueDirty, useRest } from 'utils'; import { validate } from 'validators'; import { createNetworkSettingsValidator } from 'validators/network'; @@ -68,7 +68,7 @@ const WiFiSettingsForm: FC = () => { saveData, errorMessage, restartNeeded - } = useRest2({ + } = useRest({ read: NetworkApi.readNetworkSettings, update: NetworkApi.updateNetworkSettings }); diff --git a/interface/src/framework/ntp/NTPSettingsForm.tsx b/interface/src/framework/ntp/NTPSettingsForm.tsx index b1da11b97..02ba28ef2 100644 --- a/interface/src/framework/ntp/NTPSettingsForm.tsx +++ b/interface/src/framework/ntp/NTPSettingsForm.tsx @@ -19,7 +19,7 @@ import { BlockNavigation } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { updateValueDirty, useRest2 } from 'utils'; +import { updateValueDirty, useRest } from 'utils'; import { validate } from 'validators'; import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp'; @@ -35,7 +35,7 @@ const NTPSettingsForm: FC = () => { blocker, saveData, errorMessage - } = useRest2({ + } = useRest({ read: NTPApi.readNTPSettings, update: NTPApi.updateNTPSettings }); diff --git a/interface/src/framework/security/GenerateToken.tsx b/interface/src/framework/security/GenerateToken.tsx index d3937b109..0a2e8a0ef 100644 --- a/interface/src/framework/security/GenerateToken.tsx +++ b/interface/src/framework/security/GenerateToken.tsx @@ -10,16 +10,14 @@ import { TextField, Button } from '@mui/material'; -import { useCallback, useState, useEffect } from 'react'; +import { useRequest } from 'alova'; +import { useEffect } from 'react'; -import { toast } from 'react-toastify'; import type { FC } from 'react'; -import type { Token } from 'types'; import * as SecurityApi from 'api/security'; import { MessageBox } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { extractErrorMessage } from 'utils'; interface GenerateTokenProps { username?: string; @@ -27,24 +25,18 @@ interface GenerateTokenProps { } const GenerateToken: FC = ({ username, onClose }) => { - const [token, setToken] = useState(); + const { LL } = useI18nContext(); const open = !!username; - const { LL } = useI18nContext(); - - const getToken = useCallback(async () => { - try { - setToken((await SecurityApi.generateToken(username)).data); - } catch (error) { - toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING())); - } - }, [username, LL]); + const { data: token, send: generateToken } = useRequest(SecurityApi.generateToken(username), { + immediate: false + }); useEffect(() => { if (open) { - void getToken(); + void generateToken(); } - }, [open, getToken]); + }, [open, generateToken]); return ( diff --git a/interface/src/framework/security/ManageUsersForm.tsx b/interface/src/framework/security/ManageUsersForm.tsx index 1c0f6a856..e4c14c91f 100644 --- a/interface/src/framework/security/ManageUsersForm.tsx +++ b/interface/src/framework/security/ManageUsersForm.tsx @@ -23,8 +23,7 @@ import { useRest } from 'utils'; import { createUserValidator } from 'validators'; const ManageUsersForm: FC = () => { - // TODO move to Alova - const { loadData, saving, data, setData, saveData, errorMessage } = useRest({ + const { loadData, saveData, saving, data, updateDataValue, errorMessage } = useRest({ read: SecurityApi.readSecuritySettings, update: SecurityApi.updateSecuritySettings }); @@ -85,7 +84,7 @@ const ManageUsersForm: FC = () => { const removeUser = (toRemove: User) => { const users = data.users.filter((u) => u.username !== toRemove.username); - setData({ ...data, users }); + updateDataValue({ ...data, users }); }; const createUser = () => { @@ -109,7 +108,7 @@ const ManageUsersForm: FC = () => { const doneEditingUser = () => { if (user) { const users = [...data.users.filter((u) => u.username !== user.username), user]; - setData({ ...data, users }); + updateDataValue({ ...data, users }); setUser(undefined); } }; diff --git a/interface/src/framework/security/SecuritySettingsForm.tsx b/interface/src/framework/security/SecuritySettingsForm.tsx index 67839522d..3af9474c5 100644 --- a/interface/src/framework/security/SecuritySettingsForm.tsx +++ b/interface/src/framework/security/SecuritySettingsForm.tsx @@ -18,16 +18,25 @@ const SecuritySettingsForm: FC = () => { const { LL } = useI18nContext(); const [fieldErrors, setFieldErrors] = useState(); - // TODO move to Alova - const { loadData, saving, data, setData, origData, dirtyFlags, blocker, setDirtyFlags, saveData, errorMessage } = - useRest({ - read: SecurityApi.readSecuritySettings, - update: SecurityApi.updateSecuritySettings - }); + const { + loadData, + saving, + data, + updateDataValue, + origData, + dirtyFlags, + setDirtyFlags, + blocker, + saveData, + errorMessage + } = useRest({ + read: SecurityApi.readSecuritySettings, + update: SecurityApi.updateSecuritySettings + }); const authenticatedContext = useContext(AuthenticatedContext); - const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData); + const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue); const content = () => { if (!data) { diff --git a/interface/src/framework/system/OTASettingsForm.tsx b/interface/src/framework/system/OTASettingsForm.tsx index e6cbaf6a5..77ccb0f88 100644 --- a/interface/src/framework/system/OTASettingsForm.tsx +++ b/interface/src/framework/system/OTASettingsForm.tsx @@ -18,7 +18,7 @@ import { } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -import { numberValue, updateValueDirty, useRest2 } from 'utils'; +import { numberValue, updateValueDirty, useRest } from 'utils'; import { validate } from 'validators'; import { OTA_SETTINGS_VALIDATOR } from 'validators/system'; @@ -35,7 +35,7 @@ const OTASettingsForm: FC = () => { setDirtyFlags, blocker, errorMessage - } = useRest2({ + } = useRest({ read: SystemApi.readOTASettings, update: SystemApi.updateOTASettings }); diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index 4cff7ebea..6f400efe9 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -16,7 +16,7 @@ import { SectionContent, FormLoader, BlockFormControlLabel, BlockNavigation } fr import { useI18nContext } from 'i18n/i18n-react'; import { LogLevel } from 'types'; -import { updateValueDirty, useRest2 } from 'utils'; +import { updateValueDirty, useRest } from 'utils'; export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log'; @@ -52,7 +52,7 @@ const SystemLog: FC = () => { const { LL } = useI18nContext(); const { loadData, data, updateDataValue, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } = - useRest2({ + useRest({ read: SystemApi.readLogSettings, update: SystemApi.updateLogSettings }); diff --git a/interface/src/framework/system/SystemStatusForm.tsx b/interface/src/framework/system/SystemStatusForm.tsx index c90202624..333262a2a 100644 --- a/interface/src/framework/system/SystemStatusForm.tsx +++ b/interface/src/framework/system/SystemStatusForm.tsx @@ -57,8 +57,6 @@ const SystemStatusForm: FC = () => { const [confirmFactoryReset, setConfirmFactoryReset] = useState(false); const [processing, setProcessing] = useState(false); const [showingVersion, setShowingVersion] = useState(false); - const [latestVersion, setLatestVersion] = useState(); - const [latestDevVersion, setLatestDevVersion] = useState(); const [restarting, setRestarting] = useState(); const { send: restartCommand } = useRequest(SystemApi.restart(), { @@ -73,24 +71,10 @@ const SystemStatusForm: FC = () => { immediate: false }); - const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus, { force: true }); + const { data: latestVersion } = useRequest(SystemApi.getStableVersion); + const { data: latestDevVersion } = useRequest(SystemApi.getDevVersion); - useEffect(() => { - void axios.get(VERSIONCHECK_ENDPOINT).then((response) => { - setLatestVersion({ - version: response.data.name, - url: response.data.assets[1].browser_download_url, - changelog: response.data.assets[0].browser_download_url - }); - }); - void axios.get(VERSIONCHECK_DEV_ENDPOINT).then((response) => { - setLatestDevVersion({ - version: response.data.name.split(/\s+/).splice(-1), - url: response.data.assets[1].browser_download_url, - changelog: response.data.assets[0].browser_download_url - }); - }); - }, []); + const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus, { force: true }); const restart = async () => { setProcessing(true); diff --git a/interface/src/project/SettingsApplication.tsx b/interface/src/project/SettingsApplication.tsx index 9a27e997f..8dc037f06 100644 --- a/interface/src/project/SettingsApplication.tsx +++ b/interface/src/project/SettingsApplication.tsx @@ -25,7 +25,7 @@ import { import RestartMonitor from 'framework/system/RestartMonitor'; import { useI18nContext } from 'i18n/i18n-react'; -import { numberValue, updateValueDirty, useRest2 } from 'utils'; +import { numberValue, updateValueDirty, useRest } from 'utils'; import { validate } from 'validators'; export function boardProfileSelectItems() { @@ -49,7 +49,7 @@ const SettingsApplication: FC = () => { blocker, errorMessage, restartNeeded - } = useRest2({ + } = useRest({ read: EMSESP.readSettings, update: EMSESP.writeSettings }); diff --git a/interface/src/utils/endpoints.ts b/interface/src/utils/endpoints.ts deleted file mode 100644 index d0de377fe..000000000 --- a/interface/src/utils/endpoints.ts +++ /dev/null @@ -1,9 +0,0 @@ -// TODO extractErrorMessage function can be removed! -export const extractErrorMessage = (error: any, defaultMessage: string) => { - if (error.request) { - return defaultMessage + ' (' + error.request.status + ': ' + error.request.statusText + ')'; - } else if (error instanceof Error) { - return defaultMessage + ' (' + error.message + ')'; - } - return defaultMessage; -}; diff --git a/interface/src/utils/index.ts b/interface/src/utils/index.ts index c17d6949b..7ee4414df 100644 --- a/interface/src/utils/index.ts +++ b/interface/src/utils/index.ts @@ -1,9 +1,6 @@ export * from './binding'; -export * from './endpoints'; export * from './route'; export * from './submit'; export * from './time'; export * from './useRest'; -// TODO remove useRest2 -export * from './useRest2'; export * from './props'; diff --git a/interface/src/utils/useRest.ts b/interface/src/utils/useRest.ts index 1d71f1a07..1a7fbcb21 100644 --- a/interface/src/utils/useRest.ts +++ b/interface/src/utils/useRest.ts @@ -1,85 +1,76 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useRequest, type Method } from 'alova'; +import { useState } from 'react'; import { unstable_useBlocker as useBlocker } from 'react-router-dom'; import { toast } from 'react-toastify'; -import { extractErrorMessage } from '.'; -import type { AxiosPromise } from 'axios'; - import { useI18nContext } from 'i18n/i18n-react'; -export interface RestRequestOptions { - read: () => AxiosPromise; - update?: (value: D) => AxiosPromise; +export interface RestRequestOptions2 { + read: () => Method; + update: (value: D) => Method; } -export const useRest = ({ read, update }: RestRequestOptions) => { +export const useRest = ({ read, update }: RestRequestOptions2) => { const { LL } = useI18nContext(); - const [data, setData] = useState(); - const [saving, setSaving] = useState(false); const [errorMessage, setErrorMessage] = useState(); const [restartNeeded, setRestartNeeded] = useState(false); - const [origData, setOrigData] = useState(); - const [dirtyFlags, setDirtyFlags] = useState(); - const blocker = useBlocker(dirtyFlags?.length !== 0); + const [dirtyFlags, setDirtyFlags] = useState([]); + const blocker = useBlocker(dirtyFlags.length !== 0); - const loadData = useCallback(async () => { - setData(undefined); + const { data: data, send: readData, update: updateData, onComplete: onReadComplete } = useRequest(read()); + + const { + loading: saving, + send: writeData, + onSuccess: onWriteSuccess + } = useRequest((newData: D) => update(newData), { immediate: false }); + + const updateDataValue = (new_data: D) => { + updateData({ data: new_data }); + }; + + onWriteSuccess(() => { + toast.success(LL.UPDATED_OF(LL.SETTINGS())); + setDirtyFlags([]); + }); + + onReadComplete((event) => { + setOrigData(event.data); + }); + + const loadData = async () => { setDirtyFlags([]); setErrorMessage(undefined); - try { - const fetch_data = (await read()).data; - setData(fetch_data); - setOrigData(fetch_data); - } catch (error) { - const message = extractErrorMessage(error, LL.PROBLEM_LOADING()); - toast.error(message); - setErrorMessage(message); + await readData().catch((error) => { + toast.error(error.message); + setErrorMessage(error.message); + }); + }; + + const saveData = async () => { + if (!data) { + return; } - }, [read, LL]); - - const save = useCallback( - async (toSave: D) => { - if (!update) { - return; + setRestartNeeded(false); + setErrorMessage(undefined); + await writeData(data).catch((error) => { + if (error.message === 'Reboot required') { + setRestartNeeded(true); + } else { + toast.error(error.message); + setErrorMessage(error.message); } - setSaving(true); - setRestartNeeded(false); - setErrorMessage(undefined); - try { - const response = await update(toSave); - setOrigData(response.data); - setData(response.data); - if (response.status === 205) { - setRestartNeeded(true); // reboot required - } else { - toast.success(LL.UPDATED_OF(LL.SETTINGS())); - } - } catch (error) { - const message = extractErrorMessage(error, LL.PROBLEM_UPDATING()); - toast.error(message); - setErrorMessage(message); - } finally { - setSaving(false); - setDirtyFlags([]); - } - }, - [update, LL] - ); - - const saveData = () => data && save(data); - - useEffect(() => { - void loadData(); - }, [loadData]); + }); + }; return { loadData, saveData, saving, - setData, + updateDataValue, data, origData, dirtyFlags, diff --git a/interface/src/utils/useRest2.ts b/interface/src/utils/useRest2.ts deleted file mode 100644 index b71b1c9cf..000000000 --- a/interface/src/utils/useRest2.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { useRequest, type Method } from 'alova'; -import { useState } from 'react'; -import { unstable_useBlocker as useBlocker } from 'react-router-dom'; -import { toast } from 'react-toastify'; - -import { useI18nContext } from 'i18n/i18n-react'; - -export interface RestRequestOptions2 { - read: () => Method; - update: (value: D) => Method; -} - -// TODO rename back to useRest -export const useRest2 = ({ read, update }: RestRequestOptions2) => { - const { LL } = useI18nContext(); - - const [errorMessage, setErrorMessage] = useState(); - const [restartNeeded, setRestartNeeded] = useState(false); - const [origData, setOrigData] = useState(); - - const [dirtyFlags, setDirtyFlags] = useState([]); - const blocker = useBlocker(dirtyFlags.length !== 0); - - const { data: data, send: readData, update: updateData, onComplete: onReadComplete } = useRequest(read()); - - const { - loading: saving, - send: writeData, - onSuccess: onWriteSuccess - } = useRequest((newData: D) => update(newData), { immediate: false }); - - const updateDataValue = (new_data: D) => { - updateData({ data: new_data }); - }; - - onWriteSuccess(() => { - toast.success(LL.UPDATED_OF(LL.SETTINGS())); - setDirtyFlags([]); - }); - - onReadComplete((event) => { - setOrigData(event.data); - }); - - const loadData = async () => { - setDirtyFlags([]); - setErrorMessage(undefined); - await readData().catch((error) => { - toast.error(error.message); - setErrorMessage(error.message); - }); - }; - - const saveData = async () => { - if (!data) { - return; - } - setRestartNeeded(false); - setErrorMessage(undefined); - await writeData(data).catch((error) => { - if (error.message === 'Reboot required') { - setRestartNeeded(true); - } else { - toast.error(error.message); - setErrorMessage(error.message); - } - }); - }; - - return { - loadData, - saveData, - saving, - updateDataValue, - data, - origData, - dirtyFlags, - setDirtyFlags, - setOrigData, - blocker, - errorMessage, - restartNeeded - } as const; -}; diff --git a/mock-api/server.js b/mock-api/server.js index 045ab6695..cf2423da4 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -2024,11 +2024,11 @@ rest_server.post(NETWORK_SETTINGS_ENDPOINT, (req, res) => { res.sendStatus(200); }); rest_server.get(LIST_NETWORKS_ENDPOINT, (req, res) => { - if (countWifiScanPoll++ === 4) { - console.log('done, have list'); + if (countWifiScanPoll++ === 3) { + // console.log('done, have list'); res.json(list_networks); // send list } else { - console.log('...waiting #' + countWifiScanPoll); + // console.log('...waiting #' + countWifiScanPoll); res.sendStatus(200); // waiting.... } }); @@ -2124,6 +2124,7 @@ rest_server.post(UPLOAD_FILE_ENDPOINT, (req, res) => { res.sendStatus(200); }); rest_server.post(SIGN_IN_ENDPOINT, (req, res) => { + // res.sendStatus(401); // test bad user console.log('Signed in as ' + req.body.username); res.json(signin); // watch out, this has a return value }); From bc6b48bd07a15c532566683864e25d5a3fd9ebfa Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 18 Jun 2023 17:12:43 +0200 Subject: [PATCH 022/163] fix signin --- interface/src/AuthenticatedRouting.tsx | 80 +++++++++---------- interface/src/api/endpoints.ts | 6 +- .../authentication/Authentication.tsx | 3 + .../src/framework/system/SystemStatusForm.tsx | 5 +- 4 files changed, 45 insertions(+), 49 deletions(-) diff --git a/interface/src/AuthenticatedRouting.tsx b/interface/src/AuthenticatedRouting.tsx index b0fe722ff..8bfe280b5 100644 --- a/interface/src/AuthenticatedRouting.tsx +++ b/interface/src/AuthenticatedRouting.tsx @@ -1,13 +1,13 @@ -import { useCallback, useEffect } from 'react'; -import { Navigate, Routes, Route, useNavigate, useLocation } from 'react-router-dom'; +// import { useCallback, useEffect } from 'react'; +import { Navigate, Routes, Route } from 'react-router-dom'; import Dashboard from './project/Dashboard'; import Help from './project/Help'; import Settings from './project/Settings'; -import type { AxiosError } from 'axios'; +// import type { AxiosError } from 'axios'; import type { FC } from 'react'; -import * as AuthenticationApi from 'api/authentication'; -import { AXIOS } from 'api/endpoints'; +// import * as AuthenticationApi from 'api/authentication'; +// import { AXIOS } from 'api/endpoints'; import { Layout, RequireAdmin } from 'components'; import AccessPoint from 'framework/ap/AccessPoint'; @@ -17,11 +17,11 @@ import NetworkTime from 'framework/ntp/NetworkTime'; import Security from 'framework/security/Security'; import System from 'framework/system/System'; -const AuthenticatedRouting: FC = () => { - const location = useLocation(); - const navigate = useNavigate(); +const AuthenticatedRouting: FC = () => ( + // const location = useLocation(); + // const navigate = useNavigate(); - // TODO fix this - how to redirect on a 401 + // TODO not sure if this is needed, to redirect on 401. If so add incerceptor to Alova // const handleApiResponseError = useCallback( // (error: AxiosError) => { // if (error.response && error.response.status === 401) { @@ -34,42 +34,38 @@ const AuthenticatedRouting: FC = () => { // ); // useEffect(() => { - // // TODO replace AXIOS.interceptors.response.use ??? // const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError); // return () => AXIOS.interceptors.response.eject(axiosHandlerId); // }, [handleApiResponseError]); - return ( - - - } /> - - - - } - /> - } /> - - } /> - } /> - } /> - } /> - - - - } - /> - } /> - } /> - - - ); -}; + + + } /> + + + + } + /> + } /> + } /> + } /> + } /> + } /> + + + + } + /> + } /> + } /> + + +); export default AuthenticatedRouting; diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index 10463d74e..d5840f823 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -49,13 +49,11 @@ export const alovaInstance = createAlova({ return data; }, - // TODO handle errors + // TODO how best to handle alova http errors like 401 // Interceptor for request failure // This interceptor will be entered when the request is wrong. // The second parameter is the method instance of the current request, you can use it to synchronize the configuration information before and after the request - onError: (error, method) => { - console.log('error:', error); // TODO fix me - console.log('method:', method); // TODO fix me + onError: (error) => { alert(error.message); } } diff --git a/interface/src/contexts/authentication/Authentication.tsx b/interface/src/contexts/authentication/Authentication.tsx index f9982bffd..bf9e05bae 100644 --- a/interface/src/contexts/authentication/Authentication.tsx +++ b/interface/src/contexts/authentication/Authentication.tsx @@ -56,6 +56,9 @@ const Authentication: FC = ({ children }) => { setMe(undefined); setInitialized(true); }); + } else { + setMe(undefined); + setInitialized(true); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/interface/src/framework/system/SystemStatusForm.tsx b/interface/src/framework/system/SystemStatusForm.tsx index 333262a2a..48ac93ea7 100644 --- a/interface/src/framework/system/SystemStatusForm.tsx +++ b/interface/src/framework/system/SystemStatusForm.tsx @@ -29,13 +29,11 @@ import { } from '@mui/material'; import { useRequest } from 'alova'; -import axios from 'axios'; -import { useContext, useState, useEffect } from 'react'; +import { useContext, useState } from 'react'; import { toast } from 'react-toastify'; import RestartMonitor from './RestartMonitor'; import type { FC } from 'react'; -import type { Version } from 'types'; import * as SystemApi from 'api/system'; import { ButtonRow, FormLoader, SectionContent, MessageBox } from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; @@ -71,6 +69,7 @@ const SystemStatusForm: FC = () => { immediate: false }); + // fetch versions from GH on load const { data: latestVersion } = useRequest(SystemApi.getStableVersion); const { data: latestDevVersion } = useRequest(SystemApi.getDevVersion); From 0e52deae7b9b582f0a8551432466bca49b62666a Mon Sep 17 00:00:00 2001 From: Proddy Date: Tue, 20 Jun 2023 22:52:16 +0200 Subject: [PATCH 023/163] package updates --- interface/package.json | 12 ++-- interface/yarn.lock | 140 ++++++++++++++++++++--------------------- pio_local.ini_example | 1 + 3 files changed, 77 insertions(+), 76 deletions(-) diff --git a/interface/package.json b/interface/package.json index 981b20850..00737d839 100644 --- a/interface/package.json +++ b/interface/package.json @@ -27,10 +27,10 @@ "@table-library/react-table-library": "4.1.4", "@types/lodash-es": "^4.17.7", "@types/node": "^20.3.1", - "@types/react": "^18.2.12", - "@types/react-dom": "^18.2.5", + "@types/react": "^18.2.13", + "@types/react-dom": "^18.2.6", "@types/react-router-dom": "^5.3.3", - "alova": "^2.6.1", + "alova": "^2.6.2", "async-validator": "^4.2.5", "axios": "^1.4.0", "history": "^5.3.0", @@ -47,8 +47,8 @@ "typescript": "^5.1.3" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.59.11", - "@typescript-eslint/parser": "^5.59.11", + "@typescript-eslint/eslint-plugin": "^5.60.0", + "@typescript-eslint/parser": "^5.60.0", "@vitejs/plugin-react-swc": "^3.3.2", "eslint": "^8.43.0", "eslint-config-airbnb": "^19.0.4", @@ -65,7 +65,7 @@ "npm-run-all": "^4.1.5", "prettier": "^2.8.8", "rollup-plugin-visualizer": "^5.9.2", - "terser": "^5.18.0", + "terser": "^5.18.1", "vite": "^4.3.9", "vite-plugin-svgr": "^3.2.0", "vite-tsconfig-paths": "^4.2.0" diff --git a/interface/yarn.lock b/interface/yarn.lock index 111162d73..e6a1587ea 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -1309,12 +1309,12 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.2.5": - version: 18.2.5 - resolution: "@types/react-dom@npm:18.2.5" +"@types/react-dom@npm:^18.2.6": + version: 18.2.6 + resolution: "@types/react-dom@npm:18.2.6" dependencies: "@types/react": "*" - checksum: 7f438f695c91735ff16e6465573a4378fabad6709d99dd08bee4932967ca7e4ccc26dc248f4b88569529885bbca9b1aca0075bee7b833bfa932a558c49d2a066 + checksum: bd734ca04c52b3c96891a7f9c1139486807dac7a2449fb72e8f8e23018bc6eeeb87a490a105cb39d05ccb7ddf80ed7a441e5bd3e5866c6f6ae8870cd723599e8 languageName: node linkType: hard @@ -1368,14 +1368,14 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.2.12": - version: 18.2.12 - resolution: "@types/react@npm:18.2.12" +"@types/react@npm:^18.2.13": + version: 18.2.13 + resolution: "@types/react@npm:18.2.13" dependencies: "@types/prop-types": "*" "@types/scheduler": "*" csstype: ^3.0.2 - checksum: dbaefc7732f77cd0c2bdf6e704ab4ee0f611540777c0b9506d972f165144b6fd6883a29dad61f6965c974c040264852aa8def1e3866b5775b3965a4e67bf92f9 + checksum: 59b434ab9f2cf4874c8715a3d848d7cfd257419af8c0e486e2af5b6760c99aab62a511cb3456a3044d8250c93539016ca84fbd23eaac988e74987e268df2eef2 languageName: node linkType: hard @@ -1393,14 +1393,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^5.59.11": - version: 5.59.11 - resolution: "@typescript-eslint/eslint-plugin@npm:5.59.11" +"@typescript-eslint/eslint-plugin@npm:^5.60.0": + version: 5.60.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.60.0" dependencies: "@eslint-community/regexpp": ^4.4.0 - "@typescript-eslint/scope-manager": 5.59.11 - "@typescript-eslint/type-utils": 5.59.11 - "@typescript-eslint/utils": 5.59.11 + "@typescript-eslint/scope-manager": 5.60.0 + "@typescript-eslint/type-utils": 5.60.0 + "@typescript-eslint/utils": 5.60.0 debug: ^4.3.4 grapheme-splitter: ^1.0.4 ignore: ^5.2.0 @@ -1413,43 +1413,43 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 77f29b8bde5d11a5f222b21e5a3546e86f79a07efd8352b3ebcde67fd2c568ba7c6946fa992dbb9d7d80b6f2455017266fdede1ed134bd181b03f98d81fe71c3 + checksum: e41d1a45f330e766afb594429fad535f4db06efc458e74cc05109c4555550efdad57141aa088d5cb836aeb90b822e08e7690e5b650fd7b2419da1d64113d5360 languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.59.11": - version: 5.59.11 - resolution: "@typescript-eslint/parser@npm:5.59.11" +"@typescript-eslint/parser@npm:^5.60.0": + version: 5.60.0 + resolution: "@typescript-eslint/parser@npm:5.60.0" dependencies: - "@typescript-eslint/scope-manager": 5.59.11 - "@typescript-eslint/types": 5.59.11 - "@typescript-eslint/typescript-estree": 5.59.11 + "@typescript-eslint/scope-manager": 5.60.0 + "@typescript-eslint/types": 5.60.0 + "@typescript-eslint/typescript-estree": 5.60.0 debug: ^4.3.4 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 3deea3a9b694ea97e315881ba37d0a90d7f37f0acbb5990270f44c79db9fc3d5675df856f8d1fb7d92c8d38dc63226a42402d57633513981ee526c06e6e8f3c3 + checksum: a9b4875a3ed37cfe8205173caf85b21f8025cf21bc295036c6265010ff622054b137fa7f3251476104086804bf55b420431efa887935b67c506800e3adcc8910 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.59.11": - version: 5.59.11 - resolution: "@typescript-eslint/scope-manager@npm:5.59.11" +"@typescript-eslint/scope-manager@npm:5.60.0": + version: 5.60.0 + resolution: "@typescript-eslint/scope-manager@npm:5.60.0" dependencies: - "@typescript-eslint/types": 5.59.11 - "@typescript-eslint/visitor-keys": 5.59.11 - checksum: e0173e9beb44408ba3e9a1eb173dd83f5c0281af09f936d03f635b7fac41abe1f829a88d0cba134f250dfa4ce527d8cfa32c96e7ada5de876a7aa8bcc933db3b + "@typescript-eslint/types": 5.60.0 + "@typescript-eslint/visitor-keys": 5.60.0 + checksum: 87c742ea716359206244e1c7a8d2805b9e1caf04bce127f84b790046ae994849f25bf38af05de7a283eec58b34ecc701f441f23dfcccb59b9185260667bfe6e7 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.59.11": - version: 5.59.11 - resolution: "@typescript-eslint/type-utils@npm:5.59.11" +"@typescript-eslint/type-utils@npm:5.60.0": + version: 5.60.0 + resolution: "@typescript-eslint/type-utils@npm:5.60.0" dependencies: - "@typescript-eslint/typescript-estree": 5.59.11 - "@typescript-eslint/utils": 5.59.11 + "@typescript-eslint/typescript-estree": 5.60.0 + "@typescript-eslint/utils": 5.60.0 debug: ^4.3.4 tsutils: ^3.21.0 peerDependencies: @@ -1457,23 +1457,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 9675cf17970bbf01814d8c8a94aa076fb7c5f5ab8c405800f972a68a72748db07070a526aa2c94d30b2e5ba43bdbef3929a588182bffc3387288b24223574f52 + checksum: 0baa4baa9c059e3a0d4da19cb62b821ababce781208cf18965e54916ea718a993d969f8f42b4356409ac1ce74228532e9d1cd0f2e9d3e0815c405467775b4015 languageName: node linkType: hard -"@typescript-eslint/types@npm:5.59.11": - version: 5.59.11 - resolution: "@typescript-eslint/types@npm:5.59.11" - checksum: 8d007c6c66323582d526429d059c477dcb522b904938a6aaf29804027e6f427533fabe9eb3c987491df74d4288dab238c8c948886f03244a1d908f2985631368 +"@typescript-eslint/types@npm:5.60.0": + version: 5.60.0 + resolution: "@typescript-eslint/types@npm:5.60.0" + checksum: 008aedc2322120b9b760204ae26b5ecf5a1a61da84e77427048d076074cef703914a7a2db0286f891bbd045c1246238823671ec97192e03eabec35e9f75288e2 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.59.11": - version: 5.59.11 - resolution: "@typescript-eslint/typescript-estree@npm:5.59.11" +"@typescript-eslint/typescript-estree@npm:5.60.0": + version: 5.60.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.60.0" dependencies: - "@typescript-eslint/types": 5.59.11 - "@typescript-eslint/visitor-keys": 5.59.11 + "@typescript-eslint/types": 5.60.0 + "@typescript-eslint/visitor-keys": 5.60.0 debug: ^4.3.4 globby: ^11.1.0 is-glob: ^4.0.3 @@ -1482,35 +1482,35 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 4306317ad65668a05d9ec7b280eab94dd7e0d15394db633864bad5d9633fbb9dc516e6cd9f8a0fc2f7a4fc0d53658e5dac61cbee3e070b0b7ac99bdbb0bb45ef + checksum: 83352afbd5b32a2c0d939ba17dc3420c0e72b5d920146b96af863acda675d4f307bb5b8cff25637761dfcba0cbe71c624307f45e2b87810798967b5af0798d43 languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.59.11": - version: 5.59.11 - resolution: "@typescript-eslint/utils@npm:5.59.11" +"@typescript-eslint/utils@npm:5.60.0": + version: 5.60.0 + resolution: "@typescript-eslint/utils@npm:5.60.0" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@types/json-schema": ^7.0.9 "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.59.11 - "@typescript-eslint/types": 5.59.11 - "@typescript-eslint/typescript-estree": 5.59.11 + "@typescript-eslint/scope-manager": 5.60.0 + "@typescript-eslint/types": 5.60.0 + "@typescript-eslint/typescript-estree": 5.60.0 eslint-scope: ^5.1.1 semver: ^7.3.7 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: c1927950d97afcf3cfc4c3901bc1932caa32bfc533f950f7dab420c478097d3d8daf030c27489e0d49ecdc1f87c52c782833cc505b245ab2a3094c707b0d776e + checksum: 00556a31fc288d2d59e85c139077c111ad2218ce817e24d02d9a50fb29c62293be7ab5200ae2a0cecce9c193a43519b690e9fd263bdc8bcef940eec005dc2bef languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.59.11": - version: 5.59.11 - resolution: "@typescript-eslint/visitor-keys@npm:5.59.11" +"@typescript-eslint/visitor-keys@npm:5.60.0": + version: 5.60.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.60.0" dependencies: - "@typescript-eslint/types": 5.59.11 + "@typescript-eslint/types": 5.60.0 eslint-visitor-keys: ^3.3.0 - checksum: 1644c5bcb87e26717bc587f1ed1d87c96e89dae7d5bdafc1eed35a7c49e3157f8c37936b3897571892f412b7dd8439ba9cf8903128bb847831696f6517bb2d7a + checksum: 797888d1e9cfd42b92382443956f0a46d093c49bca2789699f638d79387f26c91c55b8545bfaba7b9b6d846efc6b1134df640c3975d51a8c3b57d8e6a837ab5b languageName: node linkType: hard @@ -1537,13 +1537,13 @@ __metadata: "@table-library/react-table-library": 4.1.4 "@types/lodash-es": ^4.17.7 "@types/node": ^20.3.1 - "@types/react": ^18.2.12 - "@types/react-dom": ^18.2.5 + "@types/react": ^18.2.13 + "@types/react-dom": ^18.2.6 "@types/react-router-dom": ^5.3.3 - "@typescript-eslint/eslint-plugin": ^5.59.11 - "@typescript-eslint/parser": ^5.59.11 + "@typescript-eslint/eslint-plugin": ^5.60.0 + "@typescript-eslint/parser": ^5.60.0 "@vitejs/plugin-react-swc": ^3.3.2 - alova: ^2.6.1 + alova: ^2.6.2 async-validator: ^4.2.5 axios: ^1.4.0 eslint: ^8.43.0 @@ -1571,7 +1571,7 @@ __metadata: react-toastify: ^9.1.3 rollup-plugin-visualizer: ^5.9.2 sockette: ^2.0.6 - terser: ^5.18.0 + terser: ^5.18.1 typesafe-i18n: ^5.24.3 typescript: ^5.1.3 vite: ^4.3.9 @@ -1647,10 +1647,10 @@ __metadata: languageName: node linkType: hard -"alova@npm:^2.6.1": - version: 2.6.1 - resolution: "alova@npm:2.6.1" - checksum: 55504d1cfab8efff3679d5734e7f78891b3b9c581c6669e6a6df6cc854d05c5d275f6645e1347c633f4418a41d418105857c7d96e0f69dc24abeccc06f0a8c18 +"alova@npm:^2.6.2": + version: 2.6.2 + resolution: "alova@npm:2.6.2" + checksum: ad9e6d7e1edb18e9f608440c6ad263d075326754ccc6c2e305a6088021007b7c52f8cedb403ec0788bef21545d324f41da5bb23aa6742a9a9ad6092e08ed88d3 languageName: node linkType: hard @@ -5536,9 +5536,9 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.18.0": - version: 5.18.0 - resolution: "terser@npm:5.18.0" +"terser@npm:^5.18.1": + version: 5.18.1 + resolution: "terser@npm:5.18.1" dependencies: "@jridgewell/source-map": ^0.3.3 acorn: ^8.8.2 @@ -5546,7 +5546,7 @@ __metadata: source-map-support: ~0.5.20 bin: terser: bin/terser - checksum: 37f562843537c57e119b2ed6d96c2113344d9182a83613abd8e933534b89a3c622ee7ee47d4023249c1d34a2dd1b41a0e56fd6d2e2251f48b79fb7671f269b01 + checksum: f3ab58c6193f05cf4a4c06999dd95f23151542701782a3e91348828b184b7f54efebcbad3cc462b39b96b788a38936a4f6388edb022e9c696acf73af93692fdb languageName: node linkType: hard diff --git a/pio_local.ini_example b/pio_local.ini_example index f436048a9..123610389 100644 --- a/pio_local.ini_example +++ b/pio_local.ini_example @@ -49,6 +49,7 @@ platform = espressif32 board_build.partitions = esp32_partition_debug.csv upload_protocol = esptool build_type = debug +monitor_raw = no monitor_filters = esp32_exception_decoder debug_tool = esp-prog debug_init_break = tbreak setup From 2b24f2585f6b87f4ec8e552f934f770ccf308037 Mon Sep 17 00:00:00 2001 From: proddy Date: Wed, 21 Jun 2023 23:15:38 +0200 Subject: [PATCH 024/163] optimize restart using alova lib --- interface/package.json | 1 + .../src/framework/system/RestartMonitor.tsx | 39 +++++++------------ interface/yarn.lock | 8 ++++ 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/interface/package.json b/interface/package.json index 00737d839..264071a9e 100644 --- a/interface/package.json +++ b/interface/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "@alova/adapter-xhr": "^1.0.0", + "@alova/scene-react": "^1.1.0", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.11.16", diff --git a/interface/src/framework/system/RestartMonitor.tsx b/interface/src/framework/system/RestartMonitor.tsx index c7112a244..0ab20debb 100644 --- a/interface/src/framework/system/RestartMonitor.tsx +++ b/interface/src/framework/system/RestartMonitor.tsx @@ -1,5 +1,5 @@ -import { useRequest } from 'alova'; -import { useRef, useState, useEffect } from 'react'; +import { useRetriableRequest } from '@alova/scene-react'; +import { useState } from 'react'; import type { FC } from 'react'; import * as SystemApi from 'api/system'; @@ -7,39 +7,26 @@ import { FormLoader } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; -const RESTART_TIMEOUT = 2 * 60 * 1000; -const POLL_INTERVAL = 5000; - const RestartMonitor: FC = () => { const [failed, setFailed] = useState(false); - const [timeoutId, setTimeoutId] = useState(); const { LL } = useI18nContext(); - const { send: readSystemStatus } = useRequest(SystemApi.readSystemStatus(), { - force: true, - immediate: false - }); - - const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT); - const poll = useRef(async () => { - try { - await readSystemStatus(); - document.location.href = '/fileUpdated'; - } catch (error) { - if (new Date().getTime() < timeoutAt.current) { - setTimeoutId(setTimeout(poll.current, POLL_INTERVAL)); - } else { - setFailed(true); - } + // eslint-disable-next-line @typescript-eslint/unbound-method + const { onFail, onSuccess } = useRetriableRequest(SystemApi.readSystemStatus(), { + retry: 10, + backoff: { + delay: 1500 } }); - useEffect(() => { - void poll.current(); - }, []); + onFail(() => { + setFailed(true); + }); - useEffect(() => () => timeoutId && clearTimeout(timeoutId), [timeoutId]); + onSuccess(() => { + document.location.href = '/fileUpdated'; + }); return ; }; diff --git a/interface/yarn.lock b/interface/yarn.lock index e6a1587ea..b3437dcf9 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -12,6 +12,13 @@ __metadata: languageName: node linkType: hard +"@alova/scene-react@npm:^1.1.0": + version: 1.1.0 + resolution: "@alova/scene-react@npm:1.1.0" + checksum: e1b956a5ea1f0b93f7e60896bb43aa124106e7b527dbdcd77cf200975b2b403240a91c050a43d43c083941f91f106d50ceb91dc0ef0a97feabd91a3945568b43 + languageName: node + linkType: hard + "@ampproject/remapping@npm:^2.2.0": version: 2.2.0 resolution: "@ampproject/remapping@npm:2.2.0" @@ -1530,6 +1537,7 @@ __metadata: resolution: "EMS-ESP@workspace:." dependencies: "@alova/adapter-xhr": ^1.0.0 + "@alova/scene-react": ^1.1.0 "@emotion/react": ^11.11.1 "@emotion/styled": ^11.11.0 "@mui/icons-material": ^5.11.16 From e615274f83232b4840a0403f4f982974730d8926 Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 23 Jun 2023 19:12:29 +0200 Subject: [PATCH 025/163] package updates --- interface/package.json | 4 +-- interface/yarn.lock | 76 +++++++++++++++++++++++++++--------------- 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/interface/package.json b/interface/package.json index 264071a9e..dfeda40b1 100644 --- a/interface/package.json +++ b/interface/package.json @@ -24,7 +24,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.11.16", - "@mui/material": "^5.13.5", + "@mui/material": "^5.13.6", "@table-library/react-table-library": "4.1.4", "@types/lodash-es": "^4.17.7", "@types/node": "^20.3.1", @@ -40,7 +40,7 @@ "react": "latest", "react-dom": "latest", "react-dropzone": "^14.2.3", - "react-icons": "^4.9.0", + "react-icons": "^4.10.1", "react-router-dom": "^6.13.0", "react-toastify": "^9.1.3", "sockette": "^2.0.6", diff --git a/interface/yarn.lock b/interface/yarn.lock index b3437dcf9..a1d4bb93d 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -241,6 +241,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/runtime@npm:7.22.5" + dependencies: + regenerator-runtime: ^0.13.11 + checksum: 11dcaeecd2246857ccf22f939fcae28a58d29e410607bfa28b95d9b03e298a3e3df8a530e22637d5bfccfc1661fb39cc50c06b404b5d53454bd93889c7dd3eb8 + languageName: node + linkType: hard + "@babel/template@npm:^7.20.7": version: 7.20.7 resolution: "@babel/template@npm:7.20.7" @@ -728,14 +737,14 @@ __metadata: languageName: node linkType: hard -"@mui/base@npm:5.0.0-beta.4": - version: 5.0.0-beta.4 - resolution: "@mui/base@npm:5.0.0-beta.4" +"@mui/base@npm:5.0.0-beta.5": + version: 5.0.0-beta.5 + resolution: "@mui/base@npm:5.0.0-beta.5" dependencies: - "@babel/runtime": ^7.21.0 + "@babel/runtime": ^7.22.5 "@emotion/is-prop-valid": ^1.2.1 "@mui/types": ^7.2.4 - "@mui/utils": ^5.13.1 + "@mui/utils": ^5.13.6 "@popperjs/core": ^2.11.8 clsx: ^1.2.1 prop-types: ^15.8.1 @@ -747,7 +756,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 3d809a8bd82bfd7e0838a455d13a23f83ba7464859ce2efa3909b285c494f312df4c56d4f997672d1ce36154bab8e9140db654909efd8ddb4f426d56bea1eac9 + checksum: 3d36edb5648e2e1dfd65f5c832650c0d394f96d5cee6ef638632a0cd3a6540eb654c453c4a168226bef33dc7616c7a7ac67c4a707403b1b4584b7c0f89d4db4f languageName: node linkType: hard @@ -774,16 +783,16 @@ __metadata: languageName: node linkType: hard -"@mui/material@npm:^5.13.5": - version: 5.13.5 - resolution: "@mui/material@npm:5.13.5" +"@mui/material@npm:^5.13.6": + version: 5.13.6 + resolution: "@mui/material@npm:5.13.6" dependencies: - "@babel/runtime": ^7.21.0 - "@mui/base": 5.0.0-beta.4 + "@babel/runtime": ^7.22.5 + "@mui/base": 5.0.0-beta.5 "@mui/core-downloads-tracker": ^5.13.4 - "@mui/system": ^5.13.5 + "@mui/system": ^5.13.6 "@mui/types": ^7.2.4 - "@mui/utils": ^5.13.1 + "@mui/utils": ^5.13.6 "@types/react-transition-group": ^4.4.6 clsx: ^1.2.1 csstype: ^3.1.2 @@ -803,7 +812,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 325a99809efa041aa615b144bfde634ad7def22102b6267769e140aee0cdeaa3f611b79d4e3dc85a832b1f120da19b3e57933fb17487c7d5f67d7c2bbe7f3254 + checksum: 208dc3f77471d823ba83fac62c5752a5e5aadef983419b4dbd5756ac16c2ce91b3405e771dce33fcbbd51459a7ff65e8d02b08c2a46c812f0012dce3dbf93550 languageName: node linkType: hard @@ -845,15 +854,15 @@ __metadata: languageName: node linkType: hard -"@mui/system@npm:^5.13.5": - version: 5.13.5 - resolution: "@mui/system@npm:5.13.5" +"@mui/system@npm:^5.13.6": + version: 5.13.6 + resolution: "@mui/system@npm:5.13.6" dependencies: - "@babel/runtime": ^7.21.0 + "@babel/runtime": ^7.22.5 "@mui/private-theming": ^5.13.1 "@mui/styled-engine": ^5.13.2 "@mui/types": ^7.2.4 - "@mui/utils": ^5.13.1 + "@mui/utils": ^5.13.6 clsx: ^1.2.1 csstype: ^3.1.2 prop-types: ^15.8.1 @@ -869,7 +878,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 0bef4c575d9c54e7d93ad14009aecf4a0f18440398a14a6523f3fcea665913ceb344dc496d02b56a3ef53e4dac828f8e7ca5a55fb60448a76363622159d18379 + checksum: 10d2e668a09429d55572aa62e033107ead4bb6edf9addb48c9f2aa1c937989f0d7c307f868c4a947408b43fac8ce4f0b3307396b4011a0c31d6c0b844ec0fbf4 languageName: node linkType: hard @@ -900,6 +909,21 @@ __metadata: languageName: node linkType: hard +"@mui/utils@npm:^5.13.6": + version: 5.13.6 + resolution: "@mui/utils@npm:5.13.6" + dependencies: + "@babel/runtime": ^7.22.5 + "@types/prop-types": ^15.7.5 + "@types/react-is": ^18.2.0 + prop-types: ^15.8.1 + react-is: ^18.2.0 + peerDependencies: + react: ^17.0.0 || ^18.0.0 + checksum: 9e82c91ed3ae47705f44ba5b31d50b7c8a9591c855d05764389d0c3cbb10687628e1ffd6af1e04e490d979c4d32c669ddd24bc72643f040550674919ab94a3ec + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -1541,7 +1565,7 @@ __metadata: "@emotion/react": ^11.11.1 "@emotion/styled": ^11.11.0 "@mui/icons-material": ^5.11.16 - "@mui/material": ^5.13.5 + "@mui/material": ^5.13.6 "@table-library/react-table-library": 4.1.4 "@types/lodash-es": ^4.17.7 "@types/node": ^20.3.1 @@ -1574,7 +1598,7 @@ __metadata: react: latest react-dom: latest react-dropzone: ^14.2.3 - react-icons: ^4.9.0 + react-icons: ^4.10.1 react-router-dom: ^6.13.0 react-toastify: ^9.1.3 rollup-plugin-visualizer: ^5.9.2 @@ -4792,12 +4816,12 @@ __metadata: languageName: node linkType: hard -"react-icons@npm:^4.9.0": - version: 4.9.0 - resolution: "react-icons@npm:4.9.0" +"react-icons@npm:^4.10.1": + version: 4.10.1 + resolution: "react-icons@npm:4.10.1" peerDependencies: react: "*" - checksum: 66eb6d170643d2a35a92418e9fa4416f728ebb537d99d3d0d875faa3e46952667da31777f9ca10b7351515785b3d92dd052fd67b7863471d332cd3b68beec7d1 + checksum: aa4ecd390751bf61fc7967c2256eace1351c4af9a941ea4459464ac5cae012fe99970430ab659ad38a633ca55b0392d9ea5624ab3953c0bc86e1809199d6e2e2 languageName: node linkType: hard From 27b9aa6ddd60c279e151150bd6d76d6cb6312799 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 24 Jun 2023 09:09:34 +0200 Subject: [PATCH 026/163] fix timeout --- interface/package.json | 4 ++-- interface/src/api/endpoints.ts | 17 ++++++-------- interface/yarn.lock | 42 +++++++++++++++++----------------- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/interface/package.json b/interface/package.json index dfeda40b1..ccd332f9c 100644 --- a/interface/package.json +++ b/interface/package.json @@ -28,7 +28,7 @@ "@table-library/react-table-library": "4.1.4", "@types/lodash-es": "^4.17.7", "@types/node": "^20.3.1", - "@types/react": "^18.2.13", + "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", "@types/react-router-dom": "^5.3.3", "alova": "^2.6.2", @@ -41,7 +41,7 @@ "react-dom": "latest", "react-dropzone": "^14.2.3", "react-icons": "^4.10.1", - "react-router-dom": "^6.13.0", + "react-router-dom": "^6.14.0", "react-toastify": "^9.1.3", "sockette": "^2.0.6", "typesafe-i18n": "^5.24.3", diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index d5840f823..1064bedc9 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -14,11 +14,10 @@ export const EVENT_SOURCE_ROOT = 'http://' + host + '/es/'; export const alovaInstance = createAlova({ statesHook: ReactHook, - // timeout: 3000, + timeout: 2000, localCache: { GET: { mode: 'placeholder', - // expire: 60 * 10 * 1000 // see https://alova.js.org/learning/response-cache/#cache-replaceholder-mode expire: 2000 } @@ -47,15 +46,13 @@ export const alovaInstance = createAlova({ return unpack(data); } return data; - }, - - // TODO how best to handle alova http errors like 401 - // Interceptor for request failure - // This interceptor will be entered when the request is wrong. - // The second parameter is the method instance of the current request, you can use it to synchronize the configuration information before and after the request - onError: (error) => { - alert(error.message); } + + // Interceptor for request failure. This interceptor will be entered when the request is wrong. + // TODO how best to handle http errors like 401 (unauthorized) but I think this is handled correctly in AppRouting? + // onError: (error, method) => { + // alert(error.message); + // } } }); diff --git a/interface/yarn.lock b/interface/yarn.lock index a1d4bb93d..3964e3cdd 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -992,10 +992,10 @@ __metadata: languageName: node linkType: hard -"@remix-run/router@npm:1.6.3": - version: 1.6.3 - resolution: "@remix-run/router@npm:1.6.3" - checksum: d419fab24288123d8564a051c4a0e8cf46fb0f8fb7285701a819efb7f30b67785d0b63d21900a781485dea2d27271d78f2c56cb8b4b3fdb2ad72b61483989bd3 +"@remix-run/router@npm:1.7.0": + version: 1.7.0 + resolution: "@remix-run/router@npm:1.7.0" + checksum: 05ac3b300eb6676f359c18280dc3870b3f0dde5553b58d914c2ef9d1c2feae71ad4b81ad8dd3b20182cc8fba40a6f6e5236f68d1fe02989f71cd5b55ae25a75c languageName: node linkType: hard @@ -1399,14 +1399,14 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.2.13": - version: 18.2.13 - resolution: "@types/react@npm:18.2.13" +"@types/react@npm:^18.2.14": + version: 18.2.14 + resolution: "@types/react@npm:18.2.14" dependencies: "@types/prop-types": "*" "@types/scheduler": "*" csstype: ^3.0.2 - checksum: 59b434ab9f2cf4874c8715a3d848d7cfd257419af8c0e486e2af5b6760c99aab62a511cb3456a3044d8250c93539016ca84fbd23eaac988e74987e268df2eef2 + checksum: a728a90e242fb41c233729fa46885cc47aca7df2035ed803f83bf0b582dde81143d465ecbf04a056bc6404f0f746f219d7043245ebd99baf83a178bbbb856c76 languageName: node linkType: hard @@ -1569,7 +1569,7 @@ __metadata: "@table-library/react-table-library": 4.1.4 "@types/lodash-es": ^4.17.7 "@types/node": ^20.3.1 - "@types/react": ^18.2.13 + "@types/react": ^18.2.14 "@types/react-dom": ^18.2.6 "@types/react-router-dom": ^5.3.3 "@typescript-eslint/eslint-plugin": ^5.60.0 @@ -1599,7 +1599,7 @@ __metadata: react-dom: latest react-dropzone: ^14.2.3 react-icons: ^4.10.1 - react-router-dom: ^6.13.0 + react-router-dom: ^6.14.0 react-toastify: ^9.1.3 rollup-plugin-visualizer: ^5.9.2 sockette: ^2.0.6 @@ -4839,27 +4839,27 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.13.0": - version: 6.13.0 - resolution: "react-router-dom@npm:6.13.0" +"react-router-dom@npm:^6.14.0": + version: 6.14.0 + resolution: "react-router-dom@npm:6.14.0" dependencies: - "@remix-run/router": 1.6.3 - react-router: 6.13.0 + "@remix-run/router": 1.7.0 + react-router: 6.14.0 peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 760c57063e305b0623f7b39bca3c7f5c169af557867eac7d1b19419111f7b6a9ed95d2083305c40ce98fd3be27e82c61f5674c90dd066a67653cb15901d00991 + checksum: 104a09aa48b00bdf7bf021bc8c915d36e2045ff0a41b3b23b92b5d3ba5c16cbb0dd4eb00e01188a419e143af2212cbcd3c983ebde26e52c1ed33916746edba31 languageName: node linkType: hard -"react-router@npm:6.13.0": - version: 6.13.0 - resolution: "react-router@npm:6.13.0" +"react-router@npm:6.14.0": + version: 6.14.0 + resolution: "react-router@npm:6.14.0" dependencies: - "@remix-run/router": 1.6.3 + "@remix-run/router": 1.7.0 peerDependencies: react: ">=16.8" - checksum: c58b4b943d3a76d328c6ef299567a85d84a69b417f5f5d07596b0b9bd40632a828591d6f470b0f233ff9095f489f9fd5c8666fb03513e9470927447169a91d8a + checksum: 60a87b4e1bc684ddced3418f4cd91983ac5f7ea4aa1ac07dd9d336c67eb2511b22eda866a7d7bacd45abb81322ecc9a93afea98fb9daaef6b939a14eebbe4f73 languageName: node linkType: hard From 158617f56be67c6036fd42dcb9ecf1a3356f33c3 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 24 Jun 2023 13:28:48 +0200 Subject: [PATCH 027/163] fixes #1196 - use basename as ids in HA to support multiple EMS-ESP's --- src/analogsensor.cpp | 4 ++-- src/mqtt.cpp | 18 ++++++++++-------- src/shower.cpp | 4 ++-- src/temperaturesensor.cpp | 4 ++-- src/web/WebEntityService.cpp | 7 ++++--- src/web/WebLogService.cpp | 3 +-- src/web/WebSchedulerService.cpp | 4 ++-- 7 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/analogsensor.cpp b/src/analogsensor.cpp index 160c12004..f37c0947a 100644 --- a/src/analogsensor.cpp +++ b/src/analogsensor.cpp @@ -479,7 +479,7 @@ void AnalogSensor::publish_values(const bool force) { StaticJsonDocument config; char stat_t[50]; - snprintf(stat_t, sizeof(stat_t), "%s/analogsensor_data", Mqtt::base().c_str()); // use base path + snprintf(stat_t, sizeof(stat_t), "%s/analogsensor_data", Mqtt::basename().c_str()); // use basename config["stat_t"] = stat_t; char val_obj[50]; @@ -570,7 +570,7 @@ void AnalogSensor::publish_values(const bool force) { JsonObject dev = config.createNestedObject("dev"); JsonArray ids = dev.createNestedArray("ids"); - ids.add("ems-esp"); + ids.add(Mqtt::basename()); // add "availability" section Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); diff --git a/src/mqtt.cpp b/src/mqtt.cpp index eaba3391f..1b1cea873 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -621,7 +621,7 @@ void Mqtt::ha_status() { doc["uniq_id"] = uniq; doc["obj_id"] = uniq; - doc["stat_t"] = mqtt_base_ + "/status"; + doc["stat_t"] = mqtt_basename_ + "/status"; doc["name"] = "EMS-ESP status"; doc["pl_on"] = "online"; doc["pl_off"] = "offline"; @@ -633,7 +633,7 @@ void Mqtt::ha_status() { // doc["json_attr_t"] = "~/heartbeat"; // store also as HA attributes JsonObject dev = doc.createNestedObject("dev"); - dev["name"] = "EMS-ESP"; + dev["name"] = Mqtt::basename(); // take basename dev["sw"] = "v" + std::string(EMSESP_APP_VERSION); dev["mf"] = "proddy"; dev["mdl"] = "EMS-ESP"; @@ -641,7 +641,7 @@ void Mqtt::ha_status() { dev["cu"] = "http://" + (EMSESP::system_.ethernet_connected() ? ETH.localIP().toString() : WiFi.localIP().toString()); #endif JsonArray ids = dev.createNestedArray("ids"); - ids.add("ems-esp"); + ids.add(Mqtt::basename()); char topic[MQTT_TOPIC_MAX_SIZE]; snprintf(topic, sizeof(topic), "binary_sensor/%s/system_status/config", mqtt_basename_.c_str()); @@ -910,13 +910,13 @@ void Mqtt::publish_ha_sensor_config(DeviceValue & dv, const char * model, const JsonArray ids = dev_json.createNestedArray("ids"); char ha_device[40]; auto device_type_name = EMSdevice::device_type_2_device_name(dv.device_type); - snprintf(ha_device, sizeof(ha_device), "ems-esp-%s", device_type_name); + snprintf(ha_device, sizeof(ha_device), "%s-%s", Mqtt::basename().c_str(), device_type_name); ids.add(ha_device); if (create_device_config) { auto cap_name = strdup(device_type_name); Helpers::CharToUpperUTF8(cap_name); // capitalize first letter - dev_json["name"] = std::string("EMS-ESP ") + cap_name; + dev_json["name"] = Mqtt::basename() + " " + cap_name; dev_json["mf"] = brand; dev_json["mdl"] = model; dev_json["via_device"] = "ems-esp"; @@ -955,7 +955,7 @@ void Mqtt::publish_system_ha_sensor_config(uint8_t type, const char * name, cons JsonObject dev_json = doc.createNestedObject("dev"); JsonArray ids = dev_json.createNestedArray("ids"); - ids.add("ems-esp"); + ids.add(Mqtt::basename()); publish_ha_sensor_config(type, DeviceValueTAG::TAG_HEARTBEAT, name, name, EMSdevice::DeviceType::SYSTEM, entity, uom, false, false, nullptr, 0, 0, 0, 0, dev_json); } @@ -1302,7 +1302,6 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev // add "availability" section add_avty_to_doc(stat_t, doc.as(), val_cond); - // TODO queue it or send it directly via publish? queue_ha(topic, doc.as()); } @@ -1408,7 +1407,10 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, JsonObject dev = doc.createNestedObject("dev"); JsonArray ids = dev.createNestedArray("ids"); - ids.add("ems-esp-thermostat"); + + char ha_device[40]; + snprintf(ha_device, sizeof(ha_device), "%s-thermostat", Mqtt::basename().c_str()); + ids.add(ha_device); // add "availability" section add_avty_to_doc(topic_t, doc.as(), seltemp_cond, has_roomtemp ? currtemp_cond : nullptr, hc_mode_cond); diff --git a/src/shower.cpp b/src/shower.cpp index 847801d13..477230671 100644 --- a/src/shower.cpp +++ b/src/shower.cpp @@ -164,7 +164,7 @@ void Shower::set_shower_state(bool state, bool force) { doc["object_id"] = str; char stat_t[50]; - snprintf(stat_t, sizeof(stat_t), "%s/shower_active", Mqtt::base().c_str()); // use base path + snprintf(stat_t, sizeof(stat_t), "%s/shower_active", Mqtt::basename().c_str()); doc["stat_t"] = stat_t; if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) { @@ -181,7 +181,7 @@ void Shower::set_shower_state(bool state, bool force) { JsonObject dev = doc.createNestedObject("dev"); JsonArray ids = dev.createNestedArray("ids"); - ids.add("ems-esp"); + ids.add(Mqtt::basename()); // add "availability" section Mqtt::add_avty_to_doc(stat_t, doc.as()); diff --git a/src/temperaturesensor.cpp b/src/temperaturesensor.cpp index ea313a0c2..a13b848db 100644 --- a/src/temperaturesensor.cpp +++ b/src/temperaturesensor.cpp @@ -511,7 +511,7 @@ void TemperatureSensor::publish_values(const bool force) { config["dev_cla"] = "temperature"; char stat_t[50]; - snprintf(stat_t, sizeof(stat_t), "%s/temperaturesensor_data", Mqtt::base().c_str()); // use base path + snprintf(stat_t, sizeof(stat_t), "%s/temperaturesensor_data", Mqtt::basename().c_str()); config["stat_t"] = stat_t; config["unit_of_meas"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES); @@ -543,7 +543,7 @@ void TemperatureSensor::publish_values(const bool force) { JsonObject dev = config.createNestedObject("dev"); JsonArray ids = dev.createNestedArray("ids"); - ids.add("ems-esp"); + ids.add(Mqtt::basename()); // add "availability" section Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); diff --git a/src/web/WebEntityService.cpp b/src/web/WebEntityService.cpp index a52d8fc1b..457c3e1d1 100644 --- a/src/web/WebEntityService.cpp +++ b/src/web/WebEntityService.cpp @@ -294,7 +294,7 @@ void WebEntityService::publish(const bool force) { if (Mqtt::ha_enabled() && !ha_registered_) { StaticJsonDocument config; char stat_t[50]; - snprintf(stat_t, sizeof(stat_t), "%s/custom_data", Mqtt::base().c_str()); + snprintf(stat_t, sizeof(stat_t), "%s/custom_data", Mqtt::basename().c_str()); config["stat_t"] = stat_t; char val_obj[50]; @@ -312,13 +312,14 @@ void WebEntityService::publish(const bool force) { char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; snprintf(topic, sizeof(topic), "sensor/%s/custom_%s/config", Mqtt::basename().c_str(), entityItem.name.c_str()); - //char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; + + // char command_topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; // snprintf(command_topic, sizeof(command_topic), "%s/custom/%s", Mqtt::basename().c_str(), entityItem.name.c_str()); // config["cmd_t"] = command_topic; JsonObject dev = config.createNestedObject("dev"); JsonArray ids = dev.createNestedArray("ids"); - ids.add("ems-esp"); + ids.add(Mqtt::basename()); // add "availability" section Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); diff --git a/src/web/WebLogService.cpp b/src/web/WebLogService.cpp index 1800f0c25..33fc6a170 100644 --- a/src/web/WebLogService.cpp +++ b/src/web/WebLogService.cpp @@ -216,8 +216,7 @@ void WebLogService::setValues(AsyncWebServerRequest * request, JsonVariant & jso auto && body = json.as(); - // TODO refactor into one load and one save - + // TODO refactor into one load and one save method uuid::log::Level level = body["level"]; log_level(level); diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index 22ffed2f4..753cd9a82 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -237,7 +237,7 @@ void WebSchedulerService::publish(const bool force) { if (Mqtt::ha_enabled() && force) { StaticJsonDocument config; char stat_t[50]; - snprintf(stat_t, sizeof(stat_t), "%s/scheduler_data", Mqtt::base().c_str()); + snprintf(stat_t, sizeof(stat_t), "%s/scheduler_data", Mqtt::basename().c_str()); config["stat_t"] = stat_t; char val_obj[50]; @@ -272,7 +272,7 @@ void WebSchedulerService::publish(const bool force) { JsonObject dev = config.createNestedObject("dev"); JsonArray ids = dev.createNestedArray("ids"); - ids.add("ems-esp"); + ids.add(Mqtt::basename()); // add "availability" section Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); From 8b8b023665fcf9f87bea79b7a0793a5f42c93916 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sat, 24 Jun 2023 14:51:58 +0200 Subject: [PATCH 028/163] update with 1196 --- CHANGELOG_LATEST.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index b40755b3d..f67cbcf99 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -22,6 +22,7 @@ There are breaking changes in 3.6.0. Please read carefully before applying the u - AM200 code 10 [#1161](https://github.com/emsesp/EMS-ESP32/issues/1161) - Ventilation device [#1172](https://github.com/emsesp/EMS-ESP32/issues/1172) - Turn ETH off on wifi connect [#1167](https://github.com/emsesp/EMS-ESP32/issues/1167) +- Support for multiple EMS-ESPs with HA [#1196](https://github.com/emsesp/EMS-ESP32/issues/1196) ## Fixed From f61c447ea5e0a6f717cd23ff67652e71dfe3cfb2 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 24 Jun 2023 16:50:38 +0200 Subject: [PATCH 029/163] add duplicate name check --- interface/src/project/validators.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index 02724df3a..cb4d93160 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -1,5 +1,5 @@ import Schema from 'async-validator'; -import type { AnalogSensor, DeviceValue, Settings } from './types'; +import type { AnalogSensor, DeviceValue, ScheduleItem, Settings } from './types'; import type { InternalRuleItem } from 'async-validator'; import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared'; @@ -86,16 +86,26 @@ export const createSettingsValidator = (settings: Settings) => }) }); -export const schedulerItemValidation = () => +export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) => ({ + validator(rule: InternalRuleItem, name: string, callback: (error?: string) => void) { + if ((o_name === undefined || o_name !== name) && schedule.find((si) => si.name === name)) { + callback('Name already in use'); + } else { + callback(); + } + } +}); + +export const schedulerItemValidation = (schedule: ScheduleItem[], scheduleItem: ScheduleItem) => new Schema({ name: [ { - // TODO validator: add check for unique name required: true, type: 'string', pattern: /^[a-zA-Z0-9_\\.]{0,15}$/, message: "Must be <15 characters: alpha numeric, '_' or '.'" - } + }, + ...[uniqueNameValidator(schedule, scheduleItem.o_name)] ], cmd: [ { required: true, message: 'Command is required' }, From 5fddb083301cbe0ef4decf6826056404f699c505 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 24 Jun 2023 16:50:49 +0200 Subject: [PATCH 030/163] increase timeout to 3 secs --- interface/src/api/endpoints.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index 1064bedc9..25a457dc7 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -14,7 +14,7 @@ export const EVENT_SOURCE_ROOT = 'http://' + host + '/es/'; export const alovaInstance = createAlova({ statesHook: ReactHook, - timeout: 2000, + timeout: 3000, localCache: { GET: { mode: 'placeholder', From 89f14f1dba32f6b6eb08e19087087a843e1cb5b7 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 24 Jun 2023 16:50:59 +0200 Subject: [PATCH 031/163] add comments --- mock-api/server.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mock-api/server.js b/mock-api/server.js index cf2423da4..ed0ef1276 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -2113,8 +2113,9 @@ rest_server.get(FEATURES_ENDPOINT, (req, res) => { rest_server.get(VERIFY_AUTHORIZATION_ENDPOINT, (req, res) => { res.json(verify_authentication); }); -rest_server.post(RESTART_ENDPOINT, (req, res) => { +rest_server.post(RESTART_ENDPOINT, async (req, res) => { console.log('command: restart'); + // await delay(1000); res.sendStatus(200); }); rest_server.post(FACTORY_RESET_ENDPOINT, (req, res) => { From d6c5c87412f0b1ee7e45495603bf71edd57e9da5 Mon Sep 17 00:00:00 2001 From: proddy Date: Sat, 24 Jun 2023 16:51:04 +0200 Subject: [PATCH 032/163] fix Cancel --- interface/src/project/SettingsEntities.tsx | 11 +++++++++-- interface/src/project/SettingsScheduler.tsx | 13 ++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/interface/src/project/SettingsEntities.tsx b/interface/src/project/SettingsEntities.tsx index cec062a6a..2efe981fa 100644 --- a/interface/src/project/SettingsEntities.tsx +++ b/interface/src/project/SettingsEntities.tsx @@ -34,7 +34,8 @@ const SettingsEntities: FC = () => { send: fetchEntities, error } = useRequest(EMSESP.readEntities, { - initialData: [] + initialData: [], + force: true }); const { send: writeEntities } = useRequest((data) => EMSESP.writeEntities(data), { immediate: false }); @@ -146,6 +147,12 @@ const SettingsEntities: FC = () => { setDialogOpen(false); }; + const onDialogCancel = async () => { + await fetchEntities().then(() => { + setNumChanges(0); + }); + }; + const onDialogSave = (updatedItem: EntityItem) => { setDialogOpen(false); @@ -246,7 +253,7 @@ const SettingsEntities: FC = () => { {numChanges > 0 && ( - - - - {LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '} - - - - - - - {LL.DOWNLOAD_SCHEDULE_TEXT()}{' '} - - - - - )} - - ); -}; - -export default GeneralFileUpload; diff --git a/interface/src/framework/system/UploadFileForm.tsx b/interface/src/framework/system/UploadFileForm.tsx index 362adc8d3..e3d2a753b 100644 --- a/interface/src/framework/system/UploadFileForm.tsx +++ b/interface/src/framework/system/UploadFileForm.tsx @@ -1,43 +1,174 @@ +import DownloadIcon from '@mui/icons-material/GetApp'; +import { Typography, Button, Box } from '@mui/material'; import { useRequest } from 'alova'; -import { useRef, useState } from 'react'; -import GeneralFileUpload from './GeneralFileUpload'; +import { useState, type FC } from 'react'; +import { toast } from 'react-toastify'; import RestartMonitor from './RestartMonitor'; -import type { FC } from 'react'; import * as SystemApi from 'api/system'; -import { SectionContent } from 'components'; +import { SectionContent, SingleUpload } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; +import * as EMSESP from 'project/api'; const UploadFileForm: FC = () => { - const [restarting, setRestarting] = useState(); - const { LL } = useI18nContext(); + const [restarting, setRestarting] = useState(false); + const [md5, setMd5] = useState(); - const { - loading, - data, - uploading, - send: sendUpload - } = useRequest(SystemApi.uploadFile, { + const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest(EMSESP.getSettings(), { + immediate: false + }); + const { send: getCustomizations, onSuccess: onSuccessgetCustomizations } = useRequest(EMSESP.getCustomizations(), { + immediate: false + }); + const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest(EMSESP.getEntities(), { + immediate: false + }); + const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(EMSESP.getSchedule(), { immediate: false }); - const uploadFile = useRef(async (file: File) => { - // TODO fileupload move to alova - console.log('UploadFileForm.tsx: uploadFile duplicate!!!'); // TODO do we need this function??? duplicate? - await sendUpload(file); - - // const response = await SystemApi.uploadFile(file); - // if (response.status === 200) { - // setRestarting(true); - // } - // return response; + const { + loading: isUploading, + uploading: progress, + send: sendUpload, + onSuccess: onSuccessUpload, + abort + } = useRequest(SystemApi.uploadFile, { + immediate: false, + force: true }); + onSuccessUpload(({ data }: any) => { + if (data) { + setMd5(data.md5); + toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL()); + } else { + toast.success(LL.UPLOAD() + ' ' + LL.SUCCESSFUL()); + setRestarting(true); + } + }); + + const cancelUpload = () => { + console.log('aborting upload...'); // TODO remove debug + abort(); + toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED()); + }; + + const startUpload = async (files: File[]) => { + await sendUpload(files[0]); + }; + + const saveFile = (json: any, endpoint: string) => { + const anchor = document.createElement('a'); + anchor.href = URL.createObjectURL( + new Blob([JSON.stringify(json, null, 2)], { + type: 'text/plain' + }) + ); + anchor.download = 'emsesp_' + endpoint + '.json'; + anchor.click(); + URL.revokeObjectURL(anchor.href); + toast.info(LL.DOWNLOAD_SUCCESSFUL()); + }; + + onSuccessGetSettings((event) => { + saveFile(event.data, 'settings'); + }); + onSuccessgetCustomizations((event) => { + saveFile(event.data, 'customizations'); + }); + onSuccessGetEntities((event) => { + saveFile(event.data, 'entities'); + }); + onSuccessGetSchedule((event) => { + saveFile(event.data, 'schedule'); + }); + + const downloadSettings = async () => { + await getSettings().catch((error) => { + toast.error(error.message); + }); + }; + + const downloadCustomizations = async () => { + await getCustomizations().catch((error) => { + toast.error(error.message); + }); + }; + + const downloadEntities = async () => { + await getEntities().catch((error) => { + toast.error(error.message); + }); + }; + + const downloadSchedule = async () => { + await getSchedule().catch((error) => { + toast.error(error.message); + }); + }; + + const content = () => ( + <> + + {LL.UPLOAD()} + + + {LL.UPLOAD_TEXT()} + + {md5 && ( + + {'MD5: ' + md5} + + )} + + {!isUploading && ( + <> + + {LL.DOWNLOAD(0)} + + + + {LL.DOWNLOAD_SETTINGS_TEXT()} + + + + + + {LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '} + + + + + + + {LL.DOWNLOAD_SCHEDULE_TEXT()}{' '} + + + + + )} + + ); return ( - {restarting ? : } + {restarting ? : content()} ); }; diff --git a/interface/src/project/api.ts b/interface/src/project/api.ts index f1e2e37ff..409f6def2 100644 --- a/interface/src/project/api.ts +++ b/interface/src/project/api.ts @@ -45,7 +45,7 @@ export const scanDevices = () => alovaInstance.Post('/rest/scanDevices'); // HelpInformation export const API = (apiCall: APIcall) => alovaInstance.Post('/api', apiCall); -// GeneralFileUpload +// UploadFileForm export const getSettings = () => alovaInstance.Get('/rest/getSettings'); export const getCustomizations = () => alovaInstance.Get('/rest/getCustomizations'); export const getEntities = () => alovaInstance.Get('/rest/getEntities'); diff --git a/interface/yarn.lock b/interface/yarn.lock index c003f024b..e5db77c63 100644 --- a/interface/yarn.lock +++ b/interface/yarn.lock @@ -5,6 +5,13 @@ __metadata: version: 6 cacheKey: 8c0 +"@aashutoshrathi/word-wrap@npm:^1.2.3": + version: 1.2.6 + resolution: "@aashutoshrathi/word-wrap@npm:1.2.6" + checksum: 53c2b231a61a46792b39a0d43bc4f4f776bb4542aa57ee04930676802e5501282c2fc8aac14e4cd1f1120ff8b52616b6ff5ab539ad30aa2277d726444b71619f + languageName: node + linkType: hard + "@alova/adapter-xhr@npm:^1.0.1": version: 1.0.1 resolution: "@alova/adapter-xhr@npm:1.0.1" @@ -20,228 +27,203 @@ __metadata: linkType: hard "@ampproject/remapping@npm:^2.2.0": - version: 2.2.0 - resolution: "@ampproject/remapping@npm:2.2.0" + version: 2.2.1 + resolution: "@ampproject/remapping@npm:2.2.1" dependencies: - "@jridgewell/gen-mapping": ^0.1.0 + "@jridgewell/gen-mapping": ^0.3.0 "@jridgewell/trace-mapping": ^0.3.9 - checksum: d267d8def81d75976bed4f1f81418a234a75338963ed0b8565342ef3918b07e9043806eb3a1736df7ac0774edb98e2890f880bba42817f800495e4ae3fac995e + checksum: 92ce5915f8901d8c7cd4f4e6e2fe7b9fd335a29955b400caa52e0e5b12ca3796ada7c2f10e78c9c5b0f9c2539dff0ffea7b19850a56e1487aa083531e1e46d43 languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.18.6, @babel/code-frame@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/code-frame@npm:7.21.4" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/code-frame@npm:7.22.5" dependencies: - "@babel/highlight": ^7.18.6 - checksum: c357e4b3b7a56927cb26fcb057166fef3cc701a4e35b2fa8a87402c31be0fd41d0144c61c87bf7d3b2a8f1c4d9ef00592dc0c7e8b9500dae43340a1e9f1096de + "@babel/highlight": ^7.22.5 + checksum: 0b6c5eaf9e58be7140ac790b7bdf8148e8a24e26502dcaa50f157259c083b0584285748fd90d342ae311a5bb1eaad7835aec625296d2b46853464f9bd8991e28 languageName: node linkType: hard -"@babel/compat-data@npm:^7.21.5": - version: 7.21.7 - resolution: "@babel/compat-data@npm:7.21.7" - checksum: cd6bc85364a569cc74bcf0bfdc27161a1cb423c60c624e06f44b53c9e6fe7708bd0af3e389d376aec8ed9b2795907c43d01e4163dbc2a3a3142a2de55464a51d +"@babel/compat-data@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/compat-data@npm:7.22.5" + checksum: 97f3c24a71b4e7d5f91c5807f6206a9cdb4123e595c51b34a19e9ea22b837003f969f732fde8819928d66e7b64047fd736c6717c8a1b96bf27fbfc30f6834aff languageName: node linkType: hard "@babel/core@npm:^7.21.3": - version: 7.21.8 - resolution: "@babel/core@npm:7.21.8" + version: 7.22.5 + resolution: "@babel/core@npm:7.22.5" dependencies: "@ampproject/remapping": ^2.2.0 - "@babel/code-frame": ^7.21.4 - "@babel/generator": ^7.21.5 - "@babel/helper-compilation-targets": ^7.21.5 - "@babel/helper-module-transforms": ^7.21.5 - "@babel/helpers": ^7.21.5 - "@babel/parser": ^7.21.8 - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.5 - "@babel/types": ^7.21.5 + "@babel/code-frame": ^7.22.5 + "@babel/generator": ^7.22.5 + "@babel/helper-compilation-targets": ^7.22.5 + "@babel/helper-module-transforms": ^7.22.5 + "@babel/helpers": ^7.22.5 + "@babel/parser": ^7.22.5 + "@babel/template": ^7.22.5 + "@babel/traverse": ^7.22.5 + "@babel/types": ^7.22.5 convert-source-map: ^1.7.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 json5: ^2.2.2 semver: ^6.3.0 - checksum: bf6bb92bd78fb8b6628bb0612ac0915407b996b179e1404108f92ed32972978340b4457b08f2abf86390a58fb51815cab419edb2dbbc8846efc39eaa61b8cde3 + checksum: c00e1474a41c18b669511dd1a1bd757d854cc8128218421a73c3b1c76b44fb22a57bbbd29a73b7a156cb1460af7a94602f81bed76b8d78c6ffae4de954b32a50 languageName: node linkType: hard -"@babel/generator@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/generator@npm:7.21.5" +"@babel/generator@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/generator@npm:7.22.5" dependencies: - "@babel/types": ^7.21.5 + "@babel/types": ^7.22.5 "@jridgewell/gen-mapping": ^0.3.2 "@jridgewell/trace-mapping": ^0.3.17 jsesc: ^2.5.1 - checksum: e98b51440cbbcee68e66c66684b5334f5929dba512067a6c3c1aecc77131b308bf61eca74a5ae1fb73028089d22a188ca2219c364596117f27695102afc18e95 + checksum: 0613eddb4d1f7d82d88ad304e1acf48fddc3cdfb4c94bc3d2a9128cf0cdeedc0aa8d60301715c3b67537c00d9c9c9d50aad4339e7af1295c90def21893b17f7f languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-compilation-targets@npm:7.21.5" +"@babel/helper-compilation-targets@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-compilation-targets@npm:7.22.5" dependencies: - "@babel/compat-data": ^7.21.5 - "@babel/helper-validator-option": ^7.21.0 + "@babel/compat-data": ^7.22.5 + "@babel/helper-validator-option": ^7.22.5 browserslist: ^4.21.3 lru-cache: ^5.1.1 semver: ^6.3.0 peerDependencies: "@babel/core": ^7.0.0 - checksum: 36752452eb70d6a6f52f68846344a739089374a97619e5a4857e31e7d067bdad8270efd9dd0dd5dfc483dd2d98bf0c1c6f08e3315fe949e7bfffef67eaf669ad + checksum: f36a2f27d970fa61b32090840ec847f73c6ada50becf7222c8778dd7ae07661c56f83d57e4c18437160e221512f91c442e3b86703741b45fc1277a548a6fd819 languageName: node linkType: hard -"@babel/helper-environment-visitor@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-environment-visitor@npm:7.21.5" - checksum: d3f965d9691e3e2e11036d23ba9993a42d18f9be3d4589d3bb3d09d02e9d4d204026965633e36fb43b35fde905c2dfe753fb59b72ae0c3841f5a627fb1738d8a +"@babel/helper-environment-visitor@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-environment-visitor@npm:7.22.5" + checksum: c9377464c1839741a0a77bbad56de94c896f4313eb034c988fc2ab01293e7c4027244c93b4256606c5f4e34c68cf599a7d31a548d537577c7da836bbca40551b languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/helper-function-name@npm:7.21.0" +"@babel/helper-function-name@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-function-name@npm:7.22.5" dependencies: - "@babel/template": ^7.20.7 - "@babel/types": ^7.21.0 - checksum: 5b4387afd34cd98a3a7f24f42250a5db6f7192a46e57bdbc151dc311b6299ceac151c5236018469af193dfb887b0b7ef8fe7ed89459cd05f00d69b3710c17498 + "@babel/template": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: 3ce2e87967fe54aa463d279150ddda0dae3b5bc3f8c2773b90670b553b61e8fe62da7edcd7b1e1891c5b25af4924a6700dad2e9d8249b910a5bf7caa2eaf4c13 languageName: node linkType: hard -"@babel/helper-hoist-variables@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-hoist-variables@npm:7.18.6" +"@babel/helper-hoist-variables@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-hoist-variables@npm:7.22.5" dependencies: - "@babel/types": ^7.18.6 - checksum: 830aa7ca663b0d2a025513ab50a9a10adb2a37d8cf3ba40bb74b8ac14d45fbc3d08c37b1889b10d36558edfbd34ff914909118ae156c2f0915f2057901b90eff + "@babel/types": ^7.22.5 + checksum: 60a3077f756a1cd9f14eb89f0037f487d81ede2b7cfe652ea6869cd4ec4c782b0fb1de01b8494b9a2d2050e3d154d7d5ad3be24806790acfb8cbe2073bf1e208 languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/helper-module-imports@npm:7.21.4" +"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-module-imports@npm:7.22.5" dependencies: - "@babel/types": ^7.21.4 - checksum: ce62c86e8e1af9921fa2d7253a540fb5aaab424a79de47a626c4e8855950d6ac14c0d46a9ec117e8e2e256ea1062583533947202988be889a5ff2076c213be18 + "@babel/types": ^7.22.5 + checksum: 04f8c0586c485c33017c63e0fc5fc16bd33b883cef3c88e4b3a8bf7bc807b3f9a7bcb9372fbcc01c0a539a5d1cdb477e7bdec77e250669edab00f796683b6b07 languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-module-transforms@npm:7.21.5" +"@babel/helper-module-transforms@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-module-transforms@npm:7.22.5" dependencies: - "@babel/helper-environment-visitor": ^7.21.5 - "@babel/helper-module-imports": ^7.21.4 - "@babel/helper-simple-access": ^7.21.5 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/helper-validator-identifier": ^7.19.1 - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.5 - "@babel/types": ^7.21.5 - checksum: a3b6ceaa995bf35e7a072066c3c9ba9ee6983cf36605f0c6a0ffcaab94d6dc13eba21b00434a023bf99d66c080fec335cf464619b97f7af39e1a5269cf0d7169 + "@babel/helper-environment-visitor": ^7.22.5 + "@babel/helper-module-imports": ^7.22.5 + "@babel/helper-simple-access": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.5 + "@babel/template": ^7.22.5 + "@babel/traverse": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: a28cf9a91ed657392f75ada08d96a46e8d0df420b7d5d1ac0bb1633d1404807d0cb6e6a3b0666c747d30f378fbb34985d30c6f25e2fcdd69dc58656e47aafe92 languageName: node linkType: hard -"@babel/helper-simple-access@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-simple-access@npm:7.21.5" +"@babel/helper-simple-access@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-simple-access@npm:7.22.5" dependencies: - "@babel/types": ^7.21.5 - checksum: 682cd80b47c2424c31afe70bcc8ad3e401c612f6923c432e4b8245c5b6bc5ccddf3e405ea41ba890ccab79c0b5b95da3db125944ac0decc8d31d48469e593a0e + "@babel/types": ^7.22.5 + checksum: f0cf81a30ba3d09a625fd50e5a9069e575c5b6719234e04ee74247057f8104beca89ed03e9217b6e9b0493434cedc18c5ecca4cea6244990836f1f893e140369 languageName: node linkType: hard -"@babel/helper-split-export-declaration@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-split-export-declaration@npm:7.18.6" +"@babel/helper-split-export-declaration@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-split-export-declaration@npm:7.22.5" dependencies: - "@babel/types": ^7.18.6 - checksum: 1335b510a9aefcbf60d89648e622715774e56040d72302dc5e176c8d837c9ab81414ccfa9ed771a9f98da7192579bb12ab7a95948bfdc69b03b4a882b3983e48 + "@babel/types": ^7.22.5 + checksum: a1e463086f97778584c44129c5c37282d033bf97867b300ff42e64279df18d41fe0e56ebe6a1b27f907afa66ad2a313558db8d2e83e73384c5b22ac726c9c52a languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.19.4": - version: 7.19.4 - resolution: "@babel/helper-string-parser@npm:7.19.4" - checksum: e20c81582e75df2a020a1c547376668a6e1e1c2ca535a6b7abb25b83d5536c99c0d113184bbe87c1a26e923a9bb0c6e5279fca8db6bd609cd3499fafafc01598 +"@babel/helper-string-parser@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-string-parser@npm:7.22.5" + checksum: 6b0ff8af724377ec41e5587fffa7605198da74cb8e7d8d48a36826df0c0ba210eb9fedb3d9bef4d541156e0bd11040f021945a6cbb731ccec4aefb4affa17aa4 languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-string-parser@npm:7.21.5" - checksum: 4d0834c4a67c283e9277f5e565551fede00b7d68007e368c95c776e13d05002e8f9861716e11613880889d6f3463329d2af687ceea5fc5263f8b3d25a53d31da +"@babel/helper-validator-identifier@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-validator-identifier@npm:7.22.5" + checksum: 2ff1d3833154d17ccf773b8a71fdc0cd0e7356aa8033179d0e3133787dfb33d97796cbff8b92a97c56268205337dfc720227aeddc677c1bc08ae1b67a95252d7 languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": - version: 7.19.1 - resolution: "@babel/helper-validator-identifier@npm:7.19.1" - checksum: f978ecfea840f65b64ab9e17fac380625a45f4fe1361eeb29867fcfd1c9eaa72abd7023f2f40ac3168587d7e5153660d16cfccb352a557be2efd347a051b4b20 +"@babel/helper-validator-option@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-validator-option@npm:7.22.5" + checksum: 23e310bf1b90d085b1ae250f31d423fb6cc004da882f0d3409266e5e4c7fd41ed0a172283a6a9a16083c5f2e11f987b32c815c80c60d9a948e23dd6dcf2e0437 languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/helper-validator-option@npm:7.21.0" - checksum: a5efbf3f09f1514d1704f3f7bf0e5fac401fff48a9b84a9eb47a52a4c13beee9802c6cf212a82c5fb95f6cc6b5932cb32e756cf33075be17352f64827a8ec066 - languageName: node - linkType: hard - -"@babel/helpers@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helpers@npm:7.21.5" +"@babel/helpers@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helpers@npm:7.22.5" dependencies: - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.5 - "@babel/types": ^7.21.5 - checksum: 5e58854afa1d0896185dcb12a1b6feacefb7d913d52bafa84792274651af2d3172923bdc26d1320fd6b04a2e208dc0d6730951043f17d10c08ca87231e5b84ec + "@babel/template": ^7.22.5 + "@babel/traverse": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: efa2d0fc2107e270782a784af3a52e5e0b97187b7b34feeeeb00454bc322e802ff4007b22410c387c05580c793f517c4bafc8a6a3acfdb0e3a1b349728f270c4 languageName: node linkType: hard -"@babel/highlight@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/highlight@npm:7.18.6" +"@babel/highlight@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/highlight@npm:7.22.5" dependencies: - "@babel/helper-validator-identifier": ^7.18.6 + "@babel/helper-validator-identifier": ^7.22.5 chalk: ^2.0.0 js-tokens: ^4.0.0 - checksum: a6a6928d25099ef04c337fcbb829fab8059bb67d31ac37212efd611bdbe247d0e71a5096c4524272cb56399f40251fac57c025e42d3bc924db0183a6435a60ac + checksum: e8cc07b5de76a9bf779982096ccbbe5a867c36d3786b26151eb570d9344a68af8aa065ed97d431e0d18ba55fe792c7c4301e0d62afff7a52ee0d20678443be54 languageName: node linkType: hard -"@babel/parser@npm:^7.20.7": - version: 7.21.4 - resolution: "@babel/parser@npm:7.21.4" +"@babel/parser@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/parser@npm:7.22.5" bin: parser: ./bin/babel-parser.js - checksum: 01ca14d5f1a849e2e34c4cf53809c12f8406d0961554576e025ac2283058e2bf4e168275b034744cad32574c443aa3a65ba08d7a17a7c8c56641257394cbea6c + checksum: d6a1b1e1f375cf7f81263c57f0b6d41d67e9f498d75960ec7ab62a194d7c232a125a951009edc0c991cb7d6cc6b78b006b15e1e8fb83e0de3fe0ceb6bf3d95ef languageName: node linkType: hard -"@babel/parser@npm:^7.21.5, @babel/parser@npm:^7.21.8": - version: 7.21.8 - resolution: "@babel/parser@npm:7.21.8" - bin: - parser: ./bin/babel-parser.js - checksum: 58789e972e5acce3abbd9dd4c8d4be7e15e071818d2038d195bc56664722f238abb8842d91da5c8894ab0b8f8c0841eabc675f681925c2fba12675bf3ec5c5fc - languageName: node - linkType: hard - -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.7": - version: 7.21.0 - resolution: "@babel/runtime@npm:7.21.0" - dependencies: - regenerator-runtime: ^0.13.11 - checksum: 8fc28acf3b353390a8188a63d443719847b24b66028fdc8bb301c08e2ee013b52aaeb9d0e9783fa5dcd72bb3c0172fb647419db32392101001738356bdc1f4ab - languageName: node - linkType: hard - -"@babel/runtime@npm:^7.22.5": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.7": version: 7.22.5 resolution: "@babel/runtime@npm:7.22.5" dependencies: @@ -250,54 +232,43 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/template@npm:7.20.7" +"@babel/template@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/template@npm:7.22.5" dependencies: - "@babel/code-frame": ^7.18.6 - "@babel/parser": ^7.20.7 - "@babel/types": ^7.20.7 - checksum: 1c6dcf9ac92769e6ab5e3d9048975537d26ab00b869646462ab4583d45e419c01db5144715ec0d70548835a3098c5d5416148c4a0b996a95e8e0b9dc8d042dd3 + "@babel/code-frame": ^7.22.5 + "@babel/parser": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: dd8fc1b0bfe0128bace25da0e0a708e26320e8030322d3a53bb6366f199b46a277bfa4281dd370d73ab19087c7e27d166070a0659783b4715f7470448c7342b1 languageName: node linkType: hard -"@babel/traverse@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/traverse@npm:7.21.5" +"@babel/traverse@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/traverse@npm:7.22.5" dependencies: - "@babel/code-frame": ^7.21.4 - "@babel/generator": ^7.21.5 - "@babel/helper-environment-visitor": ^7.21.5 - "@babel/helper-function-name": ^7.21.0 - "@babel/helper-hoist-variables": ^7.18.6 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/parser": ^7.21.5 - "@babel/types": ^7.21.5 + "@babel/code-frame": ^7.22.5 + "@babel/generator": ^7.22.5 + "@babel/helper-environment-visitor": ^7.22.5 + "@babel/helper-function-name": ^7.22.5 + "@babel/helper-hoist-variables": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.5 + "@babel/parser": ^7.22.5 + "@babel/types": ^7.22.5 debug: ^4.1.0 globals: ^11.1.0 - checksum: 1b126b71b98aaff01ec1f0f0389d08beb6eda3d0b71878af4c6cf386686933a076d969240f270c6a01910d8036a1fb9013d53bd5c136b9b24025204a4dc48d03 + checksum: 0217ec5ece6e4e3b6fd39dc4a23903d2d8ec76a7163731ae51a8cca03a450fb592782d620b8525219a5df9268b22901f3328a23440646d5ec2db4e3952817121 languageName: node linkType: hard -"@babel/types@npm:^7.18.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.0, @babel/types@npm:^7.21.4, @babel/types@npm:^7.8.3": - version: 7.21.4 - resolution: "@babel/types@npm:7.21.4" +"@babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.8.3": + version: 7.22.5 + resolution: "@babel/types@npm:7.22.5" dependencies: - "@babel/helper-string-parser": ^7.19.4 - "@babel/helper-validator-identifier": ^7.19.1 + "@babel/helper-string-parser": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.5 to-fast-properties: ^2.0.0 - checksum: 3820dc7b32706241ff3c0d02d034108f94586c7e8fa39cf3e2f0f0c46645f554d3c23f72c91ba7c62290ea33e21c3296dbacc40fd9fbf6cd22c3fa939e711d01 - languageName: node - linkType: hard - -"@babel/types@npm:^7.21.3, @babel/types@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/types@npm:7.21.5" - dependencies: - "@babel/helper-string-parser": ^7.21.5 - "@babel/helper-validator-identifier": ^7.19.1 - to-fast-properties: ^2.0.0 - checksum: 23c943aa2c0d11b798e9298b55b1993da8b386504aac2f781a49b4bbf2cf2ad5e1003409241578574e421c999ff7a3aab2cf30ad3581d33eb9053d82b9e20408 + checksum: 2473295056520432ec0b5fe2dc7b37914292d211ccdbc2cb05650f9c44d5168a760bca0f492a9fff7c72459defee15cd48ef152e74961cfdc03144c7a4b8bec8 languageName: node linkType: hard @@ -447,156 +418,156 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/android-arm64@npm:0.17.15" +"@esbuild/android-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/android-arm64@npm:0.17.19" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/android-arm@npm:0.17.15" +"@esbuild/android-arm@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/android-arm@npm:0.17.19" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/android-x64@npm:0.17.15" +"@esbuild/android-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/android-x64@npm:0.17.19" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/darwin-arm64@npm:0.17.15" +"@esbuild/darwin-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/darwin-arm64@npm:0.17.19" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/darwin-x64@npm:0.17.15" +"@esbuild/darwin-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/darwin-x64@npm:0.17.19" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/freebsd-arm64@npm:0.17.15" +"@esbuild/freebsd-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/freebsd-arm64@npm:0.17.19" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/freebsd-x64@npm:0.17.15" +"@esbuild/freebsd-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/freebsd-x64@npm:0.17.19" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-arm64@npm:0.17.15" +"@esbuild/linux-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-arm64@npm:0.17.19" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-arm@npm:0.17.15" +"@esbuild/linux-arm@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-arm@npm:0.17.19" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-ia32@npm:0.17.15" +"@esbuild/linux-ia32@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-ia32@npm:0.17.19" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-loong64@npm:0.17.15" +"@esbuild/linux-loong64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-loong64@npm:0.17.19" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-mips64el@npm:0.17.15" +"@esbuild/linux-mips64el@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-mips64el@npm:0.17.19" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-ppc64@npm:0.17.15" +"@esbuild/linux-ppc64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-ppc64@npm:0.17.19" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-riscv64@npm:0.17.15" +"@esbuild/linux-riscv64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-riscv64@npm:0.17.19" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-s390x@npm:0.17.15" +"@esbuild/linux-s390x@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-s390x@npm:0.17.19" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/linux-x64@npm:0.17.15" +"@esbuild/linux-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-x64@npm:0.17.19" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/netbsd-x64@npm:0.17.15" +"@esbuild/netbsd-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/netbsd-x64@npm:0.17.19" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/openbsd-x64@npm:0.17.15" +"@esbuild/openbsd-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/openbsd-x64@npm:0.17.19" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/sunos-x64@npm:0.17.15" +"@esbuild/sunos-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/sunos-x64@npm:0.17.19" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/win32-arm64@npm:0.17.15" +"@esbuild/win32-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/win32-arm64@npm:0.17.19" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/win32-ia32@npm:0.17.15" +"@esbuild/win32-ia32@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/win32-ia32@npm:0.17.19" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.17.15": - version: 0.17.15 - resolution: "@esbuild/win32-x64@npm:0.17.15" +"@esbuild/win32-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/win32-x64@npm:0.17.19" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -613,40 +584,33 @@ __metadata: linkType: hard "@eslint-community/regexpp@npm:^4.4.0": - version: 4.5.0 - resolution: "@eslint-community/regexpp@npm:4.5.0" - checksum: 7a828a8cf9422c4d6676f3b207237cabf3cd3c4327a28c5990b726630677ddc35ea9f9488d3c9c449db924cd5d9d58ded7824339774ca3592d292e0d6f945bde + version: 4.5.1 + resolution: "@eslint-community/regexpp@npm:4.5.1" + checksum: d79cbd99cc4dcfbb17e8dd30a30bb5aec5da9c60b9471043f886f116615bb15f0d417cb0ca638cefedba0b4c67c339e2011b53d88264a4540775f042a5879e01 languageName: node linkType: hard -"@eslint/eslintrc@npm:^2.0.3": - version: 2.0.3 - resolution: "@eslint/eslintrc@npm:2.0.3" +"@eslint/eslintrc@npm:^2.1.0": + version: 2.1.0 + resolution: "@eslint/eslintrc@npm:2.1.0" dependencies: ajv: ^6.12.4 debug: ^4.3.2 - espree: ^9.5.2 + espree: ^9.6.0 globals: ^13.19.0 ignore: ^5.2.0 import-fresh: ^3.2.1 js-yaml: ^4.1.0 minimatch: ^3.1.2 strip-json-comments: ^3.1.1 - checksum: 46291c33bf580ab12101fb7f20adabaa60326a7de094409ab4a5ca4611552ab2325f8d677d6c1d2d9f45f83f93360b115a0b4488bc48180cca0d0f386804d829 + checksum: 6ffbc3e7867b377754492539af0e2f5b55645a2c67279a70508fe09080bc76d49ba64b579e59a2a04014f84d0768301736fbcdd94c7b3ad4f0e648c32bf21e43 languageName: node linkType: hard -"@eslint/js@npm:8.43.0": - version: 8.43.0 - resolution: "@eslint/js@npm:8.43.0" - checksum: ff1a1587e8f28c21dda36a331cf70ca16b76e5897cecf10f6b4c326abddf18db565ee5f71feb89cbb0d3d20ff321a2536357562c0233868eed70784640b73cf4 - languageName: node - linkType: hard - -"@gar/promisify@npm:^1.1.3": - version: 1.1.3 - resolution: "@gar/promisify@npm:1.1.3" - checksum: 0b3c9958d3cd17f4add3574975e3115ae05dc7f1298a60810414b16f6f558c137b5fb3cd3905df380bacfd955ec13f67c1e6710cbb5c246a7e8d65a8289b2bff +"@eslint/js@npm:8.44.0": + version: 8.44.0 + resolution: "@eslint/js@npm:8.44.0" + checksum: ce7b966f8804228e4d5725d44d3c8fb7fc427176f077401323a02e082f628d207133a25704330e610ebe3254fdf1acb186f779d1242fd145a758fdcc4486a660 languageName: node linkType: hard @@ -675,24 +639,28 @@ __metadata: languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.1.0": - version: 0.1.1 - resolution: "@jridgewell/gen-mapping@npm:0.1.1" +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" dependencies: - "@jridgewell/set-array": ^1.0.0 - "@jridgewell/sourcemap-codec": ^1.4.10 - checksum: 3d784d87aee604bc4d48d3d9e547e0466d9f4a432cd9b3a4f3e55d104313bf3945e7e970cd5fa767bc145df11f1d568a01ab6659696be41f0ed2a817f3b583a3 + string-width: ^5.1.2 + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: ^7.0.1 + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: ^8.1.0 + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e languageName: node linkType: hard "@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2": - version: 0.3.2 - resolution: "@jridgewell/gen-mapping@npm:0.3.2" + version: 0.3.3 + resolution: "@jridgewell/gen-mapping@npm:0.3.3" dependencies: "@jridgewell/set-array": ^1.0.1 "@jridgewell/sourcemap-codec": ^1.4.10 "@jridgewell/trace-mapping": ^0.3.9 - checksum: 82685c8735c63fe388badee45e2970a6bc83eed1c84d46d8652863bafeca22a6c6cc15812f5999a4535366f4668ccc9ba6d5c67dfb72e846fa8a063806f10afd + checksum: 376fc11cf5a967318ba3ddd9d8e91be528eab6af66810a713c49b0c3f8dc67e9949452c51c38ab1b19aa618fb5e8594da5a249977e26b1e7fea1ee5a1fcacc74 languageName: node linkType: hard @@ -703,7 +671,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/set-array@npm:^1.0.0, @jridgewell/set-array@npm:^1.0.1": +"@jridgewell/set-array@npm:^1.0.1": version: 1.1.2 resolution: "@jridgewell/set-array@npm:1.1.2" checksum: bc7ab4c4c00470de4e7562ecac3c0c84f53e7ee8a711e546d67c47da7febe7c45cd67d4d84ee3c9b2c05ae8e872656cdded8a707a283d30bd54fbc65aef821ab @@ -711,29 +679,33 @@ __metadata: linkType: hard "@jridgewell/source-map@npm:^0.3.3": - version: 0.3.3 - resolution: "@jridgewell/source-map@npm:0.3.3" - dependencies: - "@jridgewell/gen-mapping": ^0.3.0 - "@jridgewell/trace-mapping": ^0.3.9 - checksum: f341e3ed1e9dfe5ae95201e9e820bee7c0518f20f2831b9964ce6c4bfe59477fb7e3257a45fac193cb4aea0019f0a4f8ed68abb12fd3956610317946f7341e3f + version: 0.3.4 + resolution: "@jridgewell/source-map@npm:0.3.4" + checksum: 4b83e015c3df8552b303f0d7d3f46b9fc297ae08e67dfe621ccbacf2879193e0890c397668a75ec90444de701616905376b02305d1a762c813ca03e86b8d9def languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10": +"@jridgewell/sourcemap-codec@npm:1.4.14": version: 1.4.14 resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" checksum: 3fbaff1387c1338b097eeb6ff92890d7838f7de0dde259e4983763b44540bfd5ca6a1f7644dc8ad003a57f7e80670d5b96a8402f1386ba9aee074743ae9bad51 languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.4.10": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: 0c6b5ae663087558039052a626d2d7ed5208da36cfd707dcc5cea4a07cfc918248403dcb5989a8f7afaf245ce0573b7cc6fd94c4a30453bd10e44d9363940ba5 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.17 - resolution: "@jridgewell/trace-mapping@npm:0.3.17" + version: 0.3.18 + resolution: "@jridgewell/trace-mapping@npm:0.3.18" dependencies: "@jridgewell/resolve-uri": 3.1.0 "@jridgewell/sourcemap-codec": 1.4.14 - checksum: 40b65fcbdd7cc5a60dbe0a2780b6670ebbc1a31c96e43833e0bf2fee0773b1ba5137ab7d137b28fc3f215567bd5f9d06b7b30634ba15636c13bd8a863c20ae9a + checksum: e5045775f076022b6c7cc64a7b55742faa5442301cb3389fd0e6712fafc46a2bb13c68fa1ffaf7b8bb665a91196f050b4115885fc802094ebc06a1cf665935ac languageName: node linkType: hard @@ -894,22 +866,7 @@ __metadata: languageName: node linkType: hard -"@mui/utils@npm:^5.13.1": - version: 5.13.1 - resolution: "@mui/utils@npm:5.13.1" - dependencies: - "@babel/runtime": ^7.21.0 - "@types/prop-types": ^15.7.5 - "@types/react-is": ^18.2.0 - prop-types: ^15.8.1 - react-is: ^18.2.0 - peerDependencies: - react: ^17.0.0 || ^18.0.0 - checksum: 05f28ed16c7c15deecb7d55962efa21f073f09342758e01bf6a618dd4532a8bf074d6e3c306cdf5f3c6d7b92f6729b0b157e828a13aff9d2445b1da997e1a7eb - languageName: node - linkType: hard - -"@mui/utils@npm:^5.13.6": +"@mui/utils@npm:^5.13.1, @mui/utils@npm:^5.13.6": version: 5.13.6 resolution: "@mui/utils@npm:5.13.6" dependencies: @@ -951,37 +908,33 @@ __metadata: languageName: node linkType: hard -"@npmcli/fs@npm:^2.1.0": - version: 2.1.2 - resolution: "@npmcli/fs@npm:2.1.2" +"@npmcli/fs@npm:^3.1.0": + version: 3.1.0 + resolution: "@npmcli/fs@npm:3.1.0" dependencies: - "@gar/promisify": ^1.1.3 semver: ^7.3.5 - checksum: c50d087733d0d8df23be24f700f104b19922a28677aa66fdbe06ff6af6431cc4a5bb1e27683cbc661a5dafa9bafdc603e6a0378121506dfcd394b2b6dd76a187 + checksum: 162b4a0b8705cd6f5c2470b851d1dc6cd228c86d2170e1769d738c1fbb69a87160901411c3c035331e9e99db72f1f1099a8b734bf1637cc32b9a5be1660e4e1e languageName: node linkType: hard -"@npmcli/move-file@npm:^2.0.0": - version: 2.0.1 - resolution: "@npmcli/move-file@npm:2.0.1" - dependencies: - mkdirp: ^1.0.4 - rimraf: ^3.0.2 - checksum: 11b2151e6d1de6f6eb23128de5aa8a429fd9097d839a5190cb77aa47a6b627022c42d50fa7c47a00f1c9f8f0c1560092b09b061855d293fa0741a2a94cfb174d +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd languageName: node linkType: hard "@pkgr/utils@npm:^2.3.1": - version: 2.3.1 - resolution: "@pkgr/utils@npm:2.3.1" + version: 2.4.1 + resolution: "@pkgr/utils@npm:2.4.1" dependencies: cross-spawn: ^7.0.3 + fast-glob: ^3.2.12 is-glob: ^4.0.3 - open: ^8.4.0 + open: ^9.1.0 picocolors: ^1.0.0 - tiny-glob: ^0.2.9 - tslib: ^2.4.0 - checksum: 50c2480c3580c0f75b9325271deeb4f4cb24f6a29f1ebc5a7de0c6991380e23625fd554ecdbc7d7e93ad6dab92532a254f7490433cf2b8f1b18d75c9e01636ea + tslib: ^2.5.0 + checksum: 0ddbb8265b508bcc4e020054960b88324212e1c4b697153211842b8e85167af430b2761459d5e0b5c960668987e92818343b2bec6ee9140b5b3f560d8e68bbfe languageName: node linkType: hard @@ -992,10 +945,10 @@ __metadata: languageName: node linkType: hard -"@remix-run/router@npm:1.7.0": - version: 1.7.0 - resolution: "@remix-run/router@npm:1.7.0" - checksum: 05ac3b300eb6676f359c18280dc3870b3f0dde5553b58d914c2ef9d1c2feae71ad4b81ad8dd3b20182cc8fba40a6f6e5236f68d1fe02989f71cd5b55ae25a75c +"@remix-run/router@npm:1.7.1": + version: 1.7.1 + resolution: "@remix-run/router@npm:1.7.1" + checksum: 4b0828529dfb2628e8e737c19bbb360dd373c0452803942611184a2ed304e39fe13996333edd05fda3c265875a78a7f31af743dbb098cbf1186105b76e10949f languageName: node linkType: hard @@ -1139,90 +1092,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.62": - version: 1.3.62 - resolution: "@swc/core-darwin-arm64@npm:1.3.62" +"@swc/core-darwin-arm64@npm:1.3.67": + version: 1.3.67 + resolution: "@swc/core-darwin-arm64@npm:1.3.67" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.62": - version: 1.3.62 - resolution: "@swc/core-darwin-x64@npm:1.3.62" +"@swc/core-darwin-x64@npm:1.3.67": + version: 1.3.67 + resolution: "@swc/core-darwin-x64@npm:1.3.67" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.62": - version: 1.3.62 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.62" +"@swc/core-linux-arm-gnueabihf@npm:1.3.67": + version: 1.3.67 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.67" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.62": - version: 1.3.62 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.62" +"@swc/core-linux-arm64-gnu@npm:1.3.67": + version: 1.3.67 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.67" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.62": - version: 1.3.62 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.62" +"@swc/core-linux-arm64-musl@npm:1.3.67": + version: 1.3.67 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.67" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.62": - version: 1.3.62 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.62" +"@swc/core-linux-x64-gnu@npm:1.3.67": + version: 1.3.67 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.67" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.62": - version: 1.3.62 - resolution: "@swc/core-linux-x64-musl@npm:1.3.62" +"@swc/core-linux-x64-musl@npm:1.3.67": + version: 1.3.67 + resolution: "@swc/core-linux-x64-musl@npm:1.3.67" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.62": - version: 1.3.62 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.62" +"@swc/core-win32-arm64-msvc@npm:1.3.67": + version: 1.3.67 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.67" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.62": - version: 1.3.62 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.62" +"@swc/core-win32-ia32-msvc@npm:1.3.67": + version: 1.3.67 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.67" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.62": - version: 1.3.62 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.62" +"@swc/core-win32-x64-msvc@npm:1.3.67": + version: 1.3.67 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.67" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.3.61": - version: 1.3.62 - resolution: "@swc/core@npm:1.3.62" + version: 1.3.67 + resolution: "@swc/core@npm:1.3.67" dependencies: - "@swc/core-darwin-arm64": 1.3.62 - "@swc/core-darwin-x64": 1.3.62 - "@swc/core-linux-arm-gnueabihf": 1.3.62 - "@swc/core-linux-arm64-gnu": 1.3.62 - "@swc/core-linux-arm64-musl": 1.3.62 - "@swc/core-linux-x64-gnu": 1.3.62 - "@swc/core-linux-x64-musl": 1.3.62 - "@swc/core-win32-arm64-msvc": 1.3.62 - "@swc/core-win32-ia32-msvc": 1.3.62 - "@swc/core-win32-x64-msvc": 1.3.62 + "@swc/core-darwin-arm64": 1.3.67 + "@swc/core-darwin-x64": 1.3.67 + "@swc/core-linux-arm-gnueabihf": 1.3.67 + "@swc/core-linux-arm64-gnu": 1.3.67 + "@swc/core-linux-arm64-musl": 1.3.67 + "@swc/core-linux-x64-gnu": 1.3.67 + "@swc/core-linux-x64-musl": 1.3.67 + "@swc/core-win32-arm64-msvc": 1.3.67 + "@swc/core-win32-ia32-msvc": 1.3.67 + "@swc/core-win32-x64-msvc": 1.3.67 peerDependencies: "@swc/helpers": ^0.5.0 dependenciesMeta: @@ -1249,7 +1202,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: aaa0827960f656c762733836938d31b2d596495b8430eb6feb0d1f6b1416b3444e7b59c326ae37ee410d8d3d25fff20ac8ff0f66ebe8a87e7fae1ca651aff915 + checksum: e09135963d5e21607d4f6f4ba818353976c13252f4fe4d40f27715dd51d83996738496f9d9e0b0106700d6720e102dfcf74cb729614881660a909e3c2a5fae0a languageName: node linkType: hard @@ -1276,9 +1229,9 @@ __metadata: linkType: hard "@types/estree@npm:^1.0.0": - version: 1.0.0 - resolution: "@types/estree@npm:1.0.0" - checksum: 4e73ff606bf7c7ccdaa66092de650c410a4ad2ecc388fdbed8242cac9dbcad72407e1ceff041b7da691babb02ff74ab885d6231fb09368fdd1eabbf1b5253d49 + version: 1.0.1 + resolution: "@types/estree@npm:1.0.1" + checksum: b4022067f834d86766f23074a1a7ac6c460e823b00cd8fe94c997bc491e7794615facd3e1520a934c42bd8c0689dbff81e5c643b01f1dee143fc758cac19669e languageName: node linkType: hard @@ -1290,9 +1243,9 @@ __metadata: linkType: hard "@types/json-schema@npm:^7.0.9": - version: 7.0.11 - resolution: "@types/json-schema@npm:7.0.11" - checksum: bd1f9a7b898ff15c4bb494eb19124f2d688b804c39f07cbf135ac73f35324970e9e8329b72aae1fb543d925ea295a1568b23056c26658cecec4741fa28c3b81a + version: 7.0.12 + resolution: "@types/json-schema@npm:7.0.12" + checksum: 2c39946ae321fe42d085c61a85872a81bbee70f9b2054ad344e8811dfc478fdbaf1ebf5f2989bb87c895ba2dfc3b1dcba85db11e467bbcdc023708814207791c languageName: node linkType: hard @@ -1313,9 +1266,9 @@ __metadata: linkType: hard "@types/lodash@npm:*": - version: 4.14.192 - resolution: "@types/lodash@npm:4.14.192" - checksum: 6807402e293cb943808a444d1ef514ce13de4f870b3b382fe729f8235ea280c4d037b9514443723afd3d04b2cf9e8f1b3fc0075d947edfeb1078347dc2b471b0 + version: 4.14.195 + resolution: "@types/lodash@npm:4.14.195" + checksum: 6d733276df592614a0943a0053056140398b3c263cdf2557d4301b3a47b07ff561926cb9339a4725acbc7d8766f91ded218df11e0a4288cee369eafb5141d94d languageName: node linkType: hard @@ -1326,10 +1279,10 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20.3.2": - version: 20.3.2 - resolution: "@types/node@npm:20.3.2" - checksum: d857cbe388d11fefd6c598144db42a32e1c15c09624b9e0669ec65e9d72e080093db3ec6b536037e6575574e33413479d4b3762140c2544ff30eb0c2111b5596 +"@types/node@npm:^20.3.3": + version: 20.3.3 + resolution: "@types/node@npm:20.3.3" + checksum: d172311e9e2d42e060eacb78ecdfd4e1596dbd1e54939bacf42003c84559bdcb5d7dbe539dc5262db4c8f408e21f3abd65435fec9bc0ff8968a3b32dafde0bbf languageName: node linkType: hard @@ -1357,11 +1310,11 @@ __metadata: linkType: hard "@types/react-is@npm:^18.2.0": - version: 18.2.0 - resolution: "@types/react-is@npm:18.2.0" + version: 18.2.1 + resolution: "@types/react-is@npm:18.2.1" dependencies: "@types/react": "*" - checksum: 7bbc931874da3f41917416b9e44f2e0749c99d7c94a3803b96342890579aad2abca473bf7505fd2202cd61c84d6ed9da41f951eb19ccee860554682327087c96 + checksum: 0d426ef34c23383760c718b9902a8262099ff81466685034594bf3b7a183356627806ba19610b16da51358f6389f1d83e6f843b7781c39218cfc7f4da5536e8b languageName: node linkType: hard @@ -1395,18 +1348,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*": - version: 18.2.0 - resolution: "@types/react@npm:18.2.0" - dependencies: - "@types/prop-types": "*" - "@types/scheduler": "*" - csstype: ^3.0.2 - checksum: e38f98b7524817459bb1214d39f4cfcb1dd7ffb31992a427b4494f3988aa6195dc349dfb66b299270b399b34568d045bf1cb6230349a6d343e183052ee486eaa - languageName: node - linkType: hard - -"@types/react@npm:^18.2.14": +"@types/react@npm:*, @types/react@npm:^18.2.14": version: 18.2.14 resolution: "@types/react@npm:18.2.14" dependencies: @@ -1425,9 +1367,9 @@ __metadata: linkType: hard "@types/semver@npm:^7.3.12": - version: 7.3.13 - resolution: "@types/semver@npm:7.3.13" - checksum: 73295bb1fee46f8c76c7a759feeae5a3022f5bedfdc17d16982092e4b33af17560234fb94861560c20992a702a1e1b9a173bb623a96f95f80892105f5e7d25e3 + version: 7.5.0 + resolution: "@types/semver@npm:7.5.0" + checksum: ca4ba4642b5972b6e88e73c5bc02bbaceb8d76bce71748d86e3e95042d4e5a44603113a1dcd2cb9b73ad6f91f6e4ab73185eb41bbfc9c73b11f0ed3db3b7443a languageName: node linkType: hard @@ -1576,17 +1518,16 @@ __metadata: "@table-library/react-table-library": 4.1.4 "@types/lodash-es": ^4.17.7 "@types/mime-types": ^2 - "@types/node": ^20.3.2 + "@types/node": ^20.3.3 "@types/react": ^18.2.14 "@types/react-dom": ^18.2.6 "@types/react-router-dom": ^5.3.3 "@typescript-eslint/eslint-plugin": ^5.60.1 "@typescript-eslint/parser": ^5.60.1 "@vitejs/plugin-react-swc": ^3.3.2 - alova: ^2.8.0 + alova: ^2.8.1 async-validator: ^4.2.5 - dev: ^0.1.3 - eslint: ^8.43.0 + eslint: ^8.44.0 eslint-config-airbnb: ^19.0.4 eslint-config-airbnb-typescript: ^17.0.0 eslint-config-prettier: ^8.8.0 @@ -1608,7 +1549,7 @@ __metadata: react-dom: latest react-dropzone: ^14.2.3 react-icons: ^4.10.1 - react-router-dom: ^6.14.0 + react-router-dom: ^6.14.1 react-toastify: ^9.1.3 rollup-plugin-visualizer: ^5.9.2 sockette: ^2.0.6 @@ -1637,12 +1578,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.8.0, acorn@npm:^8.8.2": - version: 8.8.2 - resolution: "acorn@npm:8.8.2" +"acorn@npm:^8.8.0, acorn@npm:^8.8.2, acorn@npm:^8.9.0": + version: 8.9.0 + resolution: "acorn@npm:8.9.0" bin: acorn: bin/acorn - checksum: b5c54e736af5ed753911c6752fafd02d0a74cf4d55be606bd81fe71faba4f986dc090952329931ac2aba165803fd0005c59eeef08f9c6c689e8dc420031f3df0 + checksum: 5b51689d56f1ca5d6ea1fa58af478affd8d3396403637abcbc7caf28e1a47beb537cf1654f537b6cf4c73377f3e1aa99fd4a50674e64daefe08cb25c799ded28 languageName: node linkType: hard @@ -1688,10 +1629,10 @@ __metadata: languageName: node linkType: hard -"alova@npm:^2.8.0": - version: 2.8.0 - resolution: "alova@npm:2.8.0" - checksum: 378da1832558947c0daa55483d91da4824a0d9f7461addf7bfe09689d4526f578460437ac9c7d1695da9680de35d3f52941ed2754d4240152b90e7b0ca876836 +"alova@npm:^2.8.1": + version: 2.8.1 + resolution: "alova@npm:2.8.1" + checksum: 28b6ce7f45ac1d9a38b35e92db7886c3ba899cc54109f239dc590f38da9cb0ccbc650c0455b7e3f08bbd0d714e3e4fa19648342536ceae0cd8f4898f1d9f9b64 languageName: node linkType: hard @@ -1702,6 +1643,13 @@ __metadata: languageName: node linkType: hard +"ansi-regex@npm:^6.0.1": + version: 6.0.1 + resolution: "ansi-regex@npm:6.0.1" + checksum: cbe16dbd2c6b2735d1df7976a7070dd277326434f0212f43abf6d87674095d247968209babdaad31bb00882fa68807256ba9be340eec2f1004de14ca75f52a08 + languageName: node + linkType: hard + "ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" @@ -1720,6 +1668,13 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: 5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c + languageName: node + linkType: hard + "anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" @@ -1755,11 +1710,11 @@ __metadata: linkType: hard "aria-query@npm:^5.1.3": - version: 5.1.3 - resolution: "aria-query@npm:5.1.3" + version: 5.3.0 + resolution: "aria-query@npm:5.3.0" dependencies: - deep-equal: ^2.0.5 - checksum: edcbc8044c4663d6f88f785e983e6784f98cb62b4ba1e9dd8d61b725d0203e4cfca38d676aee984c31f354103461102a3d583aa4fbe4fd0a89b679744f4e5faf + dequal: ^2.0.3 + checksum: 2bff0d4eba5852a9dd578ecf47eaef0e82cc52569b48469b0aac2db5145db0b17b7a58d9e01237706d1e14b7a1b0ac9b78e9c97027ad97679dd8f91b85da1469 languageName: node linkType: hard @@ -1773,7 +1728,7 @@ __metadata: languageName: node linkType: hard -"array-includes@npm:^3.1.5, array-includes@npm:^3.1.6": +"array-includes@npm:^3.1.6": version: 3.1.6 resolution: "array-includes@npm:3.1.6" dependencies: @@ -1859,18 +1814,18 @@ __metadata: linkType: hard "axe-core@npm:^4.6.2": - version: 4.6.3 - resolution: "axe-core@npm:4.6.3" - checksum: b26ee77b5c1f9c399a4ed5dadf82c5302fd70326f36b68f5023a57b7ec213d5db126aade0a2cd2866b9563e213192f4257bc5dc35edebb10a73f90155baa39da + version: 4.7.2 + resolution: "axe-core@npm:4.7.2" + checksum: 8dfc61f038fbd9623ae8a264c8a475d887113a027fb440a2b377b82ffd300e71d1a0bcf042ff13b517a8d548b34c44b4159eff693725c5d7cde240d0aa68feac languageName: node linkType: hard "axobject-query@npm:^3.1.1": - version: 3.1.1 - resolution: "axobject-query@npm:3.1.1" + version: 3.2.1 + resolution: "axobject-query@npm:3.2.1" dependencies: - deep-equal: ^2.0.5 - checksum: fff3175a22fd1f41fceb7ae0cd25f6594a0d7fba28c2335dd904538b80eb4e1040432564a3c643025cd2bb748f68d35aaabffb780b794da97ecfc748810b25ad + dequal: ^2.0.3 + checksum: f7debc2012e456139b57d888c223f6d3cb4b61eb104164a85e3d346273dd6ef0bc9a04b6660ca9407704a14a8e05fa6b6eb9d55f44f348c7210de7ffb350c3a7 languageName: node linkType: hard @@ -1892,6 +1847,13 @@ __metadata: languageName: node linkType: hard +"big-integer@npm:^1.6.44": + version: 1.6.51 + resolution: "big-integer@npm:1.6.51" + checksum: c8139662d57f8833a44802f4b65be911679c569535ea73c5cfd3c1c8994eaead1b84b6f63e1db63833e4d4cacb6b6a9e5522178113dfdc8e4c81ed8436f1e8cc + languageName: node + linkType: hard + "binary-extensions@npm:^2.0.0": version: 2.2.0 resolution: "binary-extensions@npm:2.2.0" @@ -1899,12 +1861,12 @@ __metadata: languageName: node linkType: hard -"bindings@npm:^1.3.1": - version: 1.5.0 - resolution: "bindings@npm:1.5.0" +"bplist-parser@npm:^0.2.0": + version: 0.2.0 + resolution: "bplist-parser@npm:0.2.0" dependencies: - file-uri-to-path: 1.0.0 - checksum: 3dab2491b4bb24124252a91e656803eac24292473e56554e35bbfe3cc1875332cfa77600c3bac7564049dc95075bf6fcc63a4609920ff2d64d0fe405fcf0d4ba + big-integer: ^1.6.44 + checksum: ce79c69e0f6efe506281e7c84e3712f7d12978991675b6e3a58a295b16f13ca81aa9b845c335614a545e0af728c8311b6aa3142af76ba1cb616af9bbac5c4a9f languageName: node linkType: hard @@ -1937,16 +1899,16 @@ __metadata: linkType: hard "browserslist@npm:^4.21.3": - version: 4.21.5 - resolution: "browserslist@npm:4.21.5" + version: 4.21.9 + resolution: "browserslist@npm:4.21.9" dependencies: - caniuse-lite: ^1.0.30001449 - electron-to-chromium: ^1.4.284 - node-releases: ^2.0.8 - update-browserslist-db: ^1.0.10 + caniuse-lite: ^1.0.30001503 + electron-to-chromium: ^1.4.431 + node-releases: ^2.0.12 + update-browserslist-db: ^1.0.11 bin: browserslist: cli.js - checksum: 903040d2c45b733e1177c288b4f146ff21d45e8a44ccc87d1d7fc2f6a8d021c7ee54b514fd7722529c282381969382a54bd2ab4263f5b6c8981a856b457ea162 + checksum: 903189787141f645f47ec46ec482dc85985d1297948062690dc2ea8480eb98fd6213507234eb17177825acaae49c53888445910f1af984abce5373fb65c270b8 languageName: node linkType: hard @@ -1957,29 +1919,32 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^16.1.0": - version: 16.1.3 - resolution: "cacache@npm:16.1.3" +"bundle-name@npm:^3.0.0": + version: 3.0.0 + resolution: "bundle-name@npm:3.0.0" dependencies: - "@npmcli/fs": ^2.1.0 - "@npmcli/move-file": ^2.0.0 - chownr: ^2.0.0 - fs-minipass: ^2.1.0 - glob: ^8.0.1 - infer-owner: ^1.0.4 + run-applescript: ^5.0.0 + checksum: 57bc7f8b025d83961b04db2f1eff6a87f2363c2891f3542a4b82471ff8ebb5d484af48e9784fcdb28ef1d48bb01f03d891966dc3ef58758e46ea32d750ce40f8 + languageName: node + linkType: hard + +"cacache@npm:^17.0.0": + version: 17.1.3 + resolution: "cacache@npm:17.1.3" + dependencies: + "@npmcli/fs": ^3.1.0 + fs-minipass: ^3.0.0 + glob: ^10.2.2 lru-cache: ^7.7.1 - minipass: ^3.1.6 + minipass: ^5.0.0 minipass-collect: ^1.0.2 minipass-flush: ^1.0.5 minipass-pipeline: ^1.2.4 - mkdirp: ^1.0.4 p-map: ^4.0.0 - promise-inflight: ^1.0.1 - rimraf: ^3.0.2 - ssri: ^9.0.0 + ssri: ^10.0.0 tar: ^6.1.11 - unique-filename: ^2.0.0 - checksum: cdf6836e1c457d2a5616abcaf5d8240c0346b1f5bd6fdb8866b9d84b6dff0b54e973226dc11e0d099f35394213d24860d1989c8358d2a41b39eb912b3000e749 + unique-filename: ^3.0.0 + checksum: fcb0843c8e152b0e1440328508a2c0d6435c431198155e31daa591b348a1739b089ce2a72a4528690ed10a2bf086c180ee4980e2116457131b4c8a6e65e10976 languageName: node linkType: hard @@ -2007,10 +1972,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001449": - version: 1.0.30001474 - resolution: "caniuse-lite@npm:1.0.30001474" - checksum: 4a5d1435c38c8f24c00b99bac7b70b1968f3f0772c8b34838a9247dcc630b2dda544ba5d4053efd3171c0e24240133cced26df054290c7413ce4540763588f93 +"caniuse-lite@npm:^1.0.30001503": + version: 1.0.30001509 + resolution: "caniuse-lite@npm:1.0.30001509" + checksum: 631e61b8de50174ffe0baeb0b87d52f0b873bae6c13b4b4eb7eb9e2dbd7d48fb05c8426cc9eccc21e4e4156d2d2bdfe7f7d9677a6f9484c2f24404e737020700 languageName: node linkType: hard @@ -2183,14 +2148,14 @@ __metadata: linkType: hard "cosmiconfig@npm:^8.1.3": - version: 8.1.3 - resolution: "cosmiconfig@npm:8.1.3" + version: 8.2.0 + resolution: "cosmiconfig@npm:8.2.0" dependencies: import-fresh: ^3.2.1 js-yaml: ^4.1.0 parse-json: ^5.0.0 path-type: ^4.0.0 - checksum: 80144be230b89857e7c4cafd59ba8feb3f5f7e6dae90faa324629fdecf9a6fc3f5b4106c3623f69a1a3d77cb11ef90e5ab65a67f21d73ffda3d76b18f8e4e6c2 + checksum: 4180aa6d1881b75ba591b2fc04b022741a3a4b67e9e243c0eb8d169b6e1efbd3cdf7e8ca19243c0f2e53a9d59ac3eccd5cad5f95f487fcbf4e740f9e86745747 languageName: node linkType: hard @@ -2207,7 +2172,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" dependencies: @@ -2253,31 +2218,6 @@ __metadata: languageName: node linkType: hard -"deep-equal@npm:^2.0.5": - version: 2.2.0 - resolution: "deep-equal@npm:2.2.0" - dependencies: - call-bind: ^1.0.2 - es-get-iterator: ^1.1.2 - get-intrinsic: ^1.1.3 - is-arguments: ^1.1.1 - is-array-buffer: ^3.0.1 - is-date-object: ^1.0.5 - is-regex: ^1.1.4 - is-shared-array-buffer: ^1.0.2 - isarray: ^2.0.5 - object-is: ^1.1.5 - object-keys: ^1.1.1 - object.assign: ^4.1.4 - regexp.prototype.flags: ^1.4.3 - side-channel: ^1.0.4 - which-boxed-primitive: ^1.0.2 - which-collection: ^1.0.1 - which-typed-array: ^1.1.9 - checksum: 31de99f3c1b516ef67ba82cbe54fdc1691cdd93ab8ede561eee94f7f8baff6594ddc0860c48707f6cd12e4efd5421e3450e20c40ca71906a9d0abe9017944cd3 - languageName: node - linkType: hard - "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -2285,6 +2225,28 @@ __metadata: languageName: node linkType: hard +"default-browser-id@npm:^3.0.0": + version: 3.0.0 + resolution: "default-browser-id@npm:3.0.0" + dependencies: + bplist-parser: ^0.2.0 + untildify: ^4.0.0 + checksum: 8db3ab882eb3e1e8b59d84c8641320e6c66d8eeb17eb4bb848b7dd549b1e6fd313988e4a13542e95fbaeff03f6e9dedc5ad191ad4df7996187753eb0d45c00b7 + languageName: node + linkType: hard + +"default-browser@npm:^4.0.0": + version: 4.0.0 + resolution: "default-browser@npm:4.0.0" + dependencies: + bundle-name: ^3.0.0 + default-browser-id: ^3.0.0 + execa: ^7.1.1 + titleize: ^3.0.0 + checksum: 7c8848badc139ecf9d878e562bc4e7ab4301e51ba120b24d8dcb14739c30152115cc612065ac3ab73c02aace4afa29db5a044257b2f0cf234f16e3a58f6c925e + languageName: node + linkType: hard + "define-lazy-prop@npm:^2.0.0": version: 2.0.0 resolution: "define-lazy-prop@npm:2.0.0" @@ -2292,7 +2254,14 @@ __metadata: languageName: node linkType: hard -"define-properties@npm:^1.1.3, define-properties@npm:^1.1.4": +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 5ab0b2bf3fa58b3a443140bbd4cd3db1f91b985cc8a246d330b9ac3fc0b6a325a6d82bddc0b055123d745b3f9931afeea74a5ec545439a1630b9c8512b0eeb49 + languageName: node + linkType: hard + +"define-properties@npm:^1.1.3, define-properties@npm:^1.1.4, define-properties@npm:^1.2.0": version: 1.2.0 resolution: "define-properties@npm:1.2.0" dependencies: @@ -2316,14 +2285,10 @@ __metadata: languageName: node linkType: hard -"dev@npm:^0.1.3": - version: 0.1.3 - resolution: "dev@npm:0.1.3" - dependencies: - inotify: ">= 0.1.6" - bin: - node-dev: ./node-dev.sh - checksum: 125fb02ab0e693b6f4863edf0304b5e6a37303e66a71795a8d14a96501764620910a4419280c1110fb7d896191ddd707647ea1145b28b6649abb607162a033bc +"dequal@npm:^2.0.3": + version: 2.0.3 + resolution: "dequal@npm:2.0.3" + checksum: f98860cdf58b64991ae10205137c0e97d384c3a4edc7f807603887b7c4b850af1224a33d88012009f150861cbee4fa2d322c4cc04b9313bee312e47f6ecaa888 languageName: node linkType: hard @@ -2364,10 +2329,17 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.284": - version: 1.4.354 - resolution: "electron-to-chromium@npm:1.4.354" - checksum: 73599af30e512c43cd5d6d763ddcfdb22ded9000f9616ce5de006277eeb101bfe8ca17371cb4bf883df63600d88824ee987c31ef9fe5f7e9be0d7052ba67ae63 +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.4.431": + version: 1.4.446 + resolution: "electron-to-chromium@npm:1.4.446" + checksum: 207de6583e24d643a62223b623cb93dfd553bbf767be31eaf91ba6c503795eebb4393f21b5d30fbc615551b284848c09566e3464b224f2ea5140eb3c992ace44 languageName: node linkType: hard @@ -2395,19 +2367,19 @@ __metadata: linkType: hard "enhanced-resolve@npm:^5.12.0": - version: 5.12.0 - resolution: "enhanced-resolve@npm:5.12.0" + version: 5.15.0 + resolution: "enhanced-resolve@npm:5.15.0" dependencies: graceful-fs: ^4.2.4 tapable: ^2.2.0 - checksum: 5738924cfe3641d04b89c2856fee3d109d7bd71bbe234fb7f54843dda65f293e5f3eee6d5970ced70dbb09016085b961e60d1eb26cac72a21044479954b6cdfd + checksum: 69984a7990913948b4150855aed26a84afb4cb1c5a94fb8e3a65bd00729a73fc2eaff6871fb8e345377f294831afe349615c93560f2f54d61b43cdfdf668f19a languageName: node linkType: hard "entities@npm:^4.4.0": - version: 4.4.0 - resolution: "entities@npm:4.4.0" - checksum: b7971419897622d3996bbbff99249e166caaaf3ea95d3841d6dc5d3bf315f133b649fbe932623e3cc527d871112e7563a8284e24f23e472126aa90c4e9c3215b + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 languageName: node linkType: hard @@ -2476,23 +2448,6 @@ __metadata: languageName: node linkType: hard -"es-get-iterator@npm:^1.1.2": - version: 1.1.3 - resolution: "es-get-iterator@npm:1.1.3" - dependencies: - call-bind: ^1.0.2 - get-intrinsic: ^1.1.3 - has-symbols: ^1.0.3 - is-arguments: ^1.1.1 - is-map: ^2.0.2 - is-set: ^2.0.2 - is-string: ^1.0.7 - isarray: ^2.0.5 - stop-iteration-iterator: ^1.0.0 - checksum: ebd11effa79851ea75d7f079405f9d0dc185559fd65d986c6afea59a0ff2d46c2ed8675f19f03dce7429d7f6c14ff9aede8d121fbab78d75cfda6a263030bac0 - languageName: node - linkType: hard - "es-set-tostringtag@npm:^2.0.1": version: 2.0.1 resolution: "es-set-tostringtag@npm:2.0.1" @@ -2525,31 +2480,31 @@ __metadata: linkType: hard "esbuild@npm:^0.17.5": - version: 0.17.15 - resolution: "esbuild@npm:0.17.15" + version: 0.17.19 + resolution: "esbuild@npm:0.17.19" dependencies: - "@esbuild/android-arm": 0.17.15 - "@esbuild/android-arm64": 0.17.15 - "@esbuild/android-x64": 0.17.15 - "@esbuild/darwin-arm64": 0.17.15 - "@esbuild/darwin-x64": 0.17.15 - "@esbuild/freebsd-arm64": 0.17.15 - "@esbuild/freebsd-x64": 0.17.15 - "@esbuild/linux-arm": 0.17.15 - "@esbuild/linux-arm64": 0.17.15 - "@esbuild/linux-ia32": 0.17.15 - "@esbuild/linux-loong64": 0.17.15 - "@esbuild/linux-mips64el": 0.17.15 - "@esbuild/linux-ppc64": 0.17.15 - "@esbuild/linux-riscv64": 0.17.15 - "@esbuild/linux-s390x": 0.17.15 - "@esbuild/linux-x64": 0.17.15 - "@esbuild/netbsd-x64": 0.17.15 - "@esbuild/openbsd-x64": 0.17.15 - "@esbuild/sunos-x64": 0.17.15 - "@esbuild/win32-arm64": 0.17.15 - "@esbuild/win32-ia32": 0.17.15 - "@esbuild/win32-x64": 0.17.15 + "@esbuild/android-arm": 0.17.19 + "@esbuild/android-arm64": 0.17.19 + "@esbuild/android-x64": 0.17.19 + "@esbuild/darwin-arm64": 0.17.19 + "@esbuild/darwin-x64": 0.17.19 + "@esbuild/freebsd-arm64": 0.17.19 + "@esbuild/freebsd-x64": 0.17.19 + "@esbuild/linux-arm": 0.17.19 + "@esbuild/linux-arm64": 0.17.19 + "@esbuild/linux-ia32": 0.17.19 + "@esbuild/linux-loong64": 0.17.19 + "@esbuild/linux-mips64el": 0.17.19 + "@esbuild/linux-ppc64": 0.17.19 + "@esbuild/linux-riscv64": 0.17.19 + "@esbuild/linux-s390x": 0.17.19 + "@esbuild/linux-x64": 0.17.19 + "@esbuild/netbsd-x64": 0.17.19 + "@esbuild/openbsd-x64": 0.17.19 + "@esbuild/sunos-x64": 0.17.19 + "@esbuild/win32-arm64": 0.17.19 + "@esbuild/win32-ia32": 0.17.19 + "@esbuild/win32-x64": 0.17.19 dependenciesMeta: "@esbuild/android-arm": optional: true @@ -2597,7 +2552,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 3c61a990737b86f3970bc01353fec61697fff8652c4beb8dc6f033aad8cc0e1aaa3caad50cd30410b930ec6ff46ddfe4d9fe805b4a095582e18beb59925c1024 + checksum: c7ac14bfaaebe4745d5d18347b4f6854fd1140acb9389e88dbfa5c20d4e2122451d9647d5498920470a880a605d6e5502b5c2102da6c282b01f129ddd49d2874 languageName: node linkType: hard @@ -2710,14 +2665,14 @@ __metadata: linkType: hard "eslint-module-utils@npm:^2.7.4": - version: 2.7.4 - resolution: "eslint-module-utils@npm:2.7.4" + version: 2.8.0 + resolution: "eslint-module-utils@npm:2.8.0" dependencies: debug: ^3.2.7 peerDependenciesMeta: eslint: optional: true - checksum: a14368a03d01824e4780e76df08460bbd5dcbf9d58944faf8660079559d169ab2b163b9b1b21fa2955c31c76f4ad348fdcde1bf0ef50cda7e14b89f6257b0eda + checksum: c7a8d1a58d76ec8217a8fea49271ec8132d1b9390965a75f6a4ecbc9e5983d742195b46d2e4378231d2186801439fe1aa5700714b0bfd4eb17aac6e1b65309df languageName: node linkType: hard @@ -2863,28 +2818,21 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.0": - version: 3.4.0 - resolution: "eslint-visitor-keys@npm:3.4.0" - checksum: 8b8cc611219b8864952a7485540482763e33289d734161bd6fe00cb6c1fc98af6bd8fe5c1d02d6d2b2657ff5cc52d30828fd52606ed50924412953a3e7d95cb7 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^3.4.1": +"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1": version: 3.4.1 resolution: "eslint-visitor-keys@npm:3.4.1" checksum: b4ebd35aed5426cd81b1fb92487825f1acf47a31e91d76597a3ee0664d69627140c4dafaf9b319cfeb1f48c1113a393e21a734c669e6565a72e6fcc311bd9911 languageName: node linkType: hard -"eslint@npm:^8.43.0": - version: 8.43.0 - resolution: "eslint@npm:8.43.0" +"eslint@npm:^8.44.0": + version: 8.44.0 + resolution: "eslint@npm:8.44.0" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@eslint-community/regexpp": ^4.4.0 - "@eslint/eslintrc": ^2.0.3 - "@eslint/js": 8.43.0 + "@eslint/eslintrc": ^2.1.0 + "@eslint/js": 8.44.0 "@humanwhocodes/config-array": ^0.11.10 "@humanwhocodes/module-importer": ^1.0.1 "@nodelib/fs.walk": ^1.2.8 @@ -2896,7 +2844,7 @@ __metadata: escape-string-regexp: ^4.0.0 eslint-scope: ^7.2.0 eslint-visitor-keys: ^3.4.1 - espree: ^9.5.2 + espree: ^9.6.0 esquery: ^1.4.2 esutils: ^2.0.2 fast-deep-equal: ^3.1.3 @@ -2916,28 +2864,17 @@ __metadata: lodash.merge: ^4.6.2 minimatch: ^3.1.2 natural-compare: ^1.4.0 - optionator: ^0.9.1 + optionator: ^0.9.3 strip-ansi: ^6.0.1 strip-json-comments: ^3.1.0 text-table: ^0.2.0 bin: eslint: bin/eslint.js - checksum: 1f9ff2c774e852c179ba569a3b672cbc4cf91aa59843ee32f7da363c10b5aad842672005ac04c760f6077b3471da428562274e0fcb0a78c2056866b3d36be948 + checksum: a31ca4571a67012629936d891141a4a5747d5902fb7f4e10119a5acd632e0976b9ba1b761d8c81cff8a9cc3e796df2c56f86c02535fd977de962a98ce585624a languageName: node linkType: hard "espree@npm:^9.0.0": - version: 9.5.1 - resolution: "espree@npm:9.5.1" - dependencies: - acorn: ^8.8.0 - acorn-jsx: ^5.3.2 - eslint-visitor-keys: ^3.4.0 - checksum: a67a1551895aa25c59c182a58e45d31a34cbeffb4a3731812db0a859fa0373cd9921af22a8aae15f42c3bf22c75a1dbd2304cdeb6530a5e7f672af87a9f9ef5f - languageName: node - linkType: hard - -"espree@npm:^9.5.2": version: 9.5.2 resolution: "espree@npm:9.5.2" dependencies: @@ -2948,6 +2885,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:^9.6.0": + version: 9.6.0 + resolution: "espree@npm:9.6.0" + dependencies: + acorn: ^8.9.0 + acorn-jsx: ^5.3.2 + eslint-visitor-keys: ^3.4.1 + checksum: f064a43bcf7f435d34e600c056320dde1c15b3eeb5da24e7585ed6cf83adcbbeafb4fa4d062ff14281b0d246b0a9645dd9d3796a638099f19595004eee4ac8be + languageName: node + linkType: hard + "esquery@npm:^1.4.2": version: 1.5.0 resolution: "esquery@npm:1.5.0" @@ -2994,6 +2942,47 @@ __metadata: languageName: node linkType: hard +"execa@npm:^5.0.0": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + dependencies: + cross-spawn: ^7.0.3 + get-stream: ^6.0.0 + human-signals: ^2.1.0 + is-stream: ^2.0.0 + merge-stream: ^2.0.0 + npm-run-path: ^4.0.1 + onetime: ^5.1.2 + signal-exit: ^3.0.3 + strip-final-newline: ^2.0.0 + checksum: c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f + languageName: node + linkType: hard + +"execa@npm:^7.1.1": + version: 7.1.1 + resolution: "execa@npm:7.1.1" + dependencies: + cross-spawn: ^7.0.3 + get-stream: ^6.0.1 + human-signals: ^4.3.0 + is-stream: ^3.0.0 + merge-stream: ^2.0.0 + npm-run-path: ^5.1.0 + onetime: ^6.0.0 + signal-exit: ^3.0.7 + strip-final-newline: ^3.0.0 + checksum: 0da5ee1c895b62142bc3d1567d1974711c28c2cfa6bae96e1923379bd597e476d762a13f282f92815d8ebfa33407949634fa32a0d6db8334a20e625fe11d4351 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.1 + resolution: "exponential-backoff@npm:3.1.1" + checksum: 160456d2d647e6019640bd07111634d8c353038d9fa40176afb7cd49b0548bdae83b56d05e907c2cce2300b81cae35d800ef92fefb9d0208e190fa3b7d6bb579 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -3002,22 +2991,22 @@ __metadata: linkType: hard "fast-diff@npm:^1.1.2": - version: 1.2.0 - resolution: "fast-diff@npm:1.2.0" - checksum: 2fbcb23957fb0bc920832a94ba627b860400f9cce45e1594e931dabf62e858369a58c6c2603e2ecc4f7679580f710b5b5b6e698a355a9a9bfcfd93c06c7c4350 + version: 1.3.0 + resolution: "fast-diff@npm:1.3.0" + checksum: 5c19af237edb5d5effda008c891a18a585f74bf12953be57923f17a3a4d0979565fc64dbc73b9e20926b9d895f5b690c618cbb969af0cf022e3222471220ad29 languageName: node linkType: hard -"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9": - version: 3.2.12 - resolution: "fast-glob@npm:3.2.12" +"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.9": + version: 3.3.0 + resolution: "fast-glob@npm:3.3.0" dependencies: "@nodelib/fs.stat": ^2.0.2 "@nodelib/fs.walk": ^1.2.3 glob-parent: ^5.1.2 merge2: ^1.3.0 micromatch: ^4.0.4 - checksum: 08604fb8ef6442ce74068bef3c3104382bb1f5ab28cf75e4ee904662778b60ad620e1405e692b7edea598ef445f5d387827a965ba034e1892bf54b1dfde97f26 + checksum: 4700063a2d7c9aae178f575648580bee1fc3f02ab3f358236d77811f52332bc10a398e75c6d5ecde61216996f3308247b37d70e2ee605a0748abe147f01b8f64 languageName: node linkType: hard @@ -3062,13 +3051,6 @@ __metadata: languageName: node linkType: hard -"file-uri-to-path@npm:1.0.0": - version: 1.0.0 - resolution: "file-uri-to-path@npm:1.0.0" - checksum: 3b545e3a341d322d368e880e1c204ef55f1d45cdea65f7efc6c6ce9e0c4d22d802d5629320eb779d006fe59624ac17b0e848d83cc5af7cd101f206cb704f5519 - languageName: node - linkType: hard - "fill-range@npm:^7.0.1": version: 7.0.1 resolution: "fill-range@npm:7.0.1" @@ -3121,7 +3103,17 @@ __metadata: languageName: node linkType: hard -"fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": +"foreground-child@npm:^3.1.0": + version: 3.1.1 + resolution: "foreground-child@npm:3.1.1" + dependencies: + cross-spawn: ^7.0.0 + signal-exit: ^4.0.1 + checksum: 9700a0285628abaeb37007c9a4d92bd49f67210f09067638774338e146c8e9c825c5c877f072b2f75f41dc6a2d0be8664f79ffc03f6576649f54a84fb9b47de0 + languageName: node + linkType: hard + +"fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" dependencies: @@ -3130,6 +3122,15 @@ __metadata: languageName: node linkType: hard +"fs-minipass@npm:^3.0.0": + version: 3.0.2 + resolution: "fs-minipass@npm:3.0.2" + dependencies: + minipass: ^5.0.0 + checksum: 34726f25b968ac05f6122ea7e9457fe108c7ae3b82beff0256953b0e405def61af2850570e32be2eb05c1e7660b663f24e14b6ab882d1d8a858314faacc4c972 + languageName: node + linkType: hard + "fs.realpath@npm:^1.0.0": version: 1.0.0 resolution: "fs.realpath@npm:1.0.0" @@ -3175,7 +3176,7 @@ __metadata: languageName: node linkType: hard -"functions-have-names@npm:^1.2.2": +"functions-have-names@npm:^1.2.2, functions-have-names@npm:^1.2.3": version: 1.2.3 resolution: "functions-have-names@npm:1.2.3" checksum: 33e77fd29bddc2d9bb78ab3eb854c165909201f88c75faa8272e35899e2d35a8a642a15e7420ef945e1f64a9670d6aa3ec744106b2aa42be68ca5114025954ca @@ -3213,13 +3214,21 @@ __metadata: linkType: hard "get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.0": - version: 1.2.0 - resolution: "get-intrinsic@npm:1.2.0" + version: 1.2.1 + resolution: "get-intrinsic@npm:1.2.1" dependencies: function-bind: ^1.1.1 has: ^1.0.3 + has-proto: ^1.0.1 has-symbols: ^1.0.3 - checksum: 7c564f6b1061e6ca9eb1abab424a2cf80b93e75dcde65229d504e4055aa0ea54f88330e9b75d10e41c72bca881a947e84193b3549a4692d836f304239a178d63 + checksum: 49eab47f9de8f1a4f9b458b8b74ee5199fb2614414a91973eb175e07db56b52b6df49b255cc7ff704cb0786490fb93bfe8f2ad138b590a8de09b47116a366bc9 + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: 49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 languageName: node linkType: hard @@ -3234,9 +3243,11 @@ __metadata: linkType: hard "get-tsconfig@npm:^4.5.0": - version: 4.5.0 - resolution: "get-tsconfig@npm:4.5.0" - checksum: 0ff157b0f6cd9f92e4112b24522aa74c23b3207cb8ae7f491c2cac99fd65b811e955f61aace805121478b23619da2dbfc4cac3135f584e80038be86d16c5121a + version: 4.6.2 + resolution: "get-tsconfig@npm:4.6.2" + dependencies: + resolve-pkg-maps: ^1.0.0 + checksum: 352c7313720b0f1172de5b6697da55c02744bacd8587f4cd989bfa25d8bb1af702128c2869121e6e4eef06c5c2f013406d2840905a8e898fd35351a305298ee1 languageName: node linkType: hard @@ -3258,6 +3269,21 @@ __metadata: languageName: node linkType: hard +"glob@npm:^10.2.2": + version: 10.3.1 + resolution: "glob@npm:10.3.1" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^2.0.3 + minimatch: ^9.0.1 + minipass: ^5.0.0 || ^6.0.2 + path-scurry: ^1.10.0 + bin: + glob: dist/cjs/src/bin.js + checksum: b39d24c093ce2ffa992dc5b412dbc871af0ccd38a6b2356f67dc906857f0c4c811039a4a4665d19443e1bb484ce2d97855cc7fcfb9a7d0b7e0dadfef4dad5b82 + languageName: node + linkType: hard + "glob@npm:^7.1.3, glob@npm:^7.1.4": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -3272,19 +3298,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.1": - version: 8.1.0 - resolution: "glob@npm:8.1.0" - dependencies: - fs.realpath: ^1.0.0 - inflight: ^1.0.4 - inherits: 2 - minimatch: ^5.0.1 - once: ^1.3.0 - checksum: cb0b5cab17a59c57299376abe5646c7070f8acb89df5595b492dba3bfb43d301a46c01e5695f01154e6553168207cb60d4eaf07d3be4bc3eb9b0457c5c561d0f - languageName: node - linkType: hard - "globals@npm:^11.1.0": version: 11.12.0 resolution: "globals@npm:11.12.0" @@ -3310,13 +3323,6 @@ __metadata: languageName: node linkType: hard -"globalyzer@npm:0.1.0": - version: 0.1.0 - resolution: "globalyzer@npm:0.1.0" - checksum: e16e47a5835cbe8a021423d4c7fcd9f5f85815b4190a7f50c1fdb95fc559d72e4fb30be96f106c66a99413f36d72da0f8323d19d27f60a8feec9d936139ec5a8 - languageName: node - linkType: hard - "globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" @@ -3332,15 +3338,15 @@ __metadata: linkType: hard "globby@npm:^13.1.3": - version: 13.1.3 - resolution: "globby@npm:13.1.3" + version: 13.2.0 + resolution: "globby@npm:13.2.0" dependencies: dir-glob: ^3.0.1 fast-glob: ^3.2.11 ignore: ^5.2.0 merge2: ^1.4.1 slash: ^4.0.0 - checksum: 34199932fad67ae6a4cca764eaad8e7678efabd4321f553bfb8a52046e03f8e8e2f9c14216a6734b692b7c26c4da1b1cfe9ce23733d28d1777d73f4bf34b09c7 + checksum: d1ea2b09dbb24961d16413cdb45764cb63280a2a7066739df5e5b33292ce4980d9da1d168a6a135c332ea1856f921e28d8ffcc2c6c24b82d4f4208477bfe62b4 languageName: node linkType: hard @@ -3475,7 +3481,7 @@ __metadata: languageName: node linkType: hard -"http-cache-semantics@npm:^4.1.0": +"http-cache-semantics@npm:^4.1.1": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" checksum: ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc @@ -3503,6 +3509,20 @@ __metadata: languageName: node linkType: hard +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: 695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a + languageName: node + linkType: hard + +"human-signals@npm:^4.3.0": + version: 4.3.1 + resolution: "human-signals@npm:4.3.1" + checksum: 40498b33fe139f5cc4ef5d2f95eb1803d6318ac1b1c63eaf14eeed5484d26332c828de4a5a05676b6c83d7b9e57727c59addb4b1dea19cb8d71e83689e5b336c + languageName: node + linkType: hard + "humanize-ms@npm:^1.2.1": version: 1.2.1 resolution: "humanize-ms@npm:1.2.1" @@ -3559,13 +3579,6 @@ __metadata: languageName: node linkType: hard -"infer-owner@npm:^1.0.4": - version: 1.0.4 - resolution: "infer-owner@npm:1.0.4" - checksum: a7b241e3149c26e37474e3435779487f42f36883711f198c45794703c7556bc38af224088bd4d1a221a45b8208ae2c2bcf86200383621434d0c099304481c5b9 - languageName: node - linkType: hard - "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -3583,19 +3596,7 @@ __metadata: languageName: node linkType: hard -"inotify@npm:>= 0.1.6": - version: 1.4.6 - resolution: "inotify@npm:1.4.6" - dependencies: - bindings: ^1.3.1 - nan: ^2.12.1 - node-gyp: latest - checksum: 8415dbb54cd3ab8232951cdf1d91b1f0ca3c5925617a47d985e650ff3a038efb265acfff0f4f826db1ef1f2f484f516e3e7874d47d059015957818149d9d932e - conditions: os=linux - languageName: node - linkType: hard - -"internal-slot@npm:^1.0.3, internal-slot@npm:^1.0.4, internal-slot@npm:^1.0.5": +"internal-slot@npm:^1.0.3, internal-slot@npm:^1.0.5": version: 1.0.5 resolution: "internal-slot@npm:1.0.5" dependencies: @@ -3613,16 +3614,6 @@ __metadata: languageName: node linkType: hard -"is-arguments@npm:^1.1.1": - version: 1.1.1 - resolution: "is-arguments@npm:1.1.1" - dependencies: - call-bind: ^1.0.2 - has-tostringtag: ^1.0.0 - checksum: 5ff1f341ee4475350adfc14b2328b38962564b7c2076be2f5bac7bd9b61779efba99b9f844a7b82ba7654adccf8e8eb19d1bb0cc6d1c1a085e498f6793d4328f - languageName: node - linkType: hard - "is-array-buffer@npm:^3.0.1, is-array-buffer@npm:^3.0.2": version: 3.0.2 resolution: "is-array-buffer@npm:3.0.2" @@ -3676,16 +3667,16 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.11.0, is-core-module@npm:^2.9.0": - version: 2.11.0 - resolution: "is-core-module@npm:2.11.0" +"is-core-module@npm:^2.11.0, is-core-module@npm:^2.12.0, is-core-module@npm:^2.9.0": + version: 2.12.1 + resolution: "is-core-module@npm:2.12.1" dependencies: has: ^1.0.3 - checksum: fd8f78ef4e243c295deafa809f89381d89aff5aaf38bb63266b17ee6e34b6a051baa5bdc2365456863336d56af6a59a4c1df1256b4eff7d6b4afac618586b004 + checksum: ff1d0dfc0b7851310d289398e416eb92ae8a9ac7ea8b8b9737fa8c0725f5a78c5f3db6edd4dff38c9ed731f3aaa1f6410a320233fcb52a2c8f1cf58eebf10a4b languageName: node linkType: hard -"is-date-object@npm:^1.0.1, is-date-object@npm:^1.0.5": +"is-date-object@npm:^1.0.1": version: 1.0.5 resolution: "is-date-object@npm:1.0.5" dependencies: @@ -3703,6 +3694,15 @@ __metadata: languageName: node linkType: hard +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 + languageName: node + linkType: hard + "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -3726,6 +3726,17 @@ __metadata: languageName: node linkType: hard +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: ^3.0.0 + bin: + is-inside-container: cli.js + checksum: a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -3733,13 +3744,6 @@ __metadata: languageName: node linkType: hard -"is-map@npm:^2.0.1, is-map@npm:^2.0.2": - version: 2.0.2 - resolution: "is-map@npm:2.0.2" - checksum: 119ff9137a37fd131a72fab3f4ab8c9d6a24b0a1ee26b4eff14dc625900d8675a97785eea5f4174265e2006ed076cc24e89f6e57ebd080a48338d914ec9168a5 - languageName: node - linkType: hard - "is-negative-zero@npm:^2.0.2": version: 2.0.2 resolution: "is-negative-zero@npm:2.0.2" @@ -3780,13 +3784,6 @@ __metadata: languageName: node linkType: hard -"is-set@npm:^2.0.1, is-set@npm:^2.0.2": - version: 2.0.2 - resolution: "is-set@npm:2.0.2" - checksum: 5f8bd1880df8c0004ce694e315e6e1e47a3452014be792880bb274a3b2cdb952fdb60789636ca6e084c7947ca8b7ae03ccaf54c93a7fcfed228af810559e5432 - languageName: node - linkType: hard - "is-shared-array-buffer@npm:^1.0.2": version: 1.0.2 resolution: "is-shared-array-buffer@npm:1.0.2" @@ -3796,6 +3793,20 @@ __metadata: languageName: node linkType: hard +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 + languageName: node + linkType: hard + +"is-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "is-stream@npm:3.0.0" + checksum: eb2f7127af02ee9aa2a0237b730e47ac2de0d4e76a4a905a50a11557f2339df5765eaea4ceb8029f1efa978586abe776908720bfcb1900c20c6ec5145f6f29d8 + languageName: node + linkType: hard + "is-string@npm:^1.0.5, is-string@npm:^1.0.7": version: 1.0.7 resolution: "is-string@npm:1.0.7" @@ -3827,13 +3838,6 @@ __metadata: languageName: node linkType: hard -"is-weakmap@npm:^2.0.1": - version: 2.0.1 - resolution: "is-weakmap@npm:2.0.1" - checksum: 9c9fec9efa7bf5030a4a927f33fff2a6976b93646259f92b517d3646c073cc5b98283a162ce75c412b060a46de07032444b530f0a4c9b6e012ef8f1741c3a987 - languageName: node - linkType: hard - "is-weakref@npm:^1.0.2": version: 1.0.2 resolution: "is-weakref@npm:1.0.2" @@ -3843,16 +3847,6 @@ __metadata: languageName: node linkType: hard -"is-weakset@npm:^2.0.1": - version: 2.0.2 - resolution: "is-weakset@npm:2.0.2" - dependencies: - call-bind: ^1.0.2 - get-intrinsic: ^1.1.1 - checksum: ef5136bd446ae4603229b897f73efd0720c6ab3ec6cc05c8d5c4b51aa9f95164713c4cad0a22ff1fedf04865ff86cae4648bc1d5eead4b6388e1150525af1cc1 - languageName: node - linkType: hard - "is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" @@ -3862,13 +3856,6 @@ __metadata: languageName: node linkType: hard -"isarray@npm:^2.0.5": - version: 2.0.5 - resolution: "isarray@npm:2.0.5" - checksum: 4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd - languageName: node - linkType: hard - "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -3876,6 +3863,19 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^2.0.3": + version: 2.2.1 + resolution: "jackspeak@npm:2.2.1" + dependencies: + "@isaacs/cliui": ^8.0.2 + "@pkgjs/parseargs": ^0.11.0 + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 510860a5d1eaf12cba509a09a8f7d1696090bfa7c8ae75c6d9c836890d2897409f3b3dd91039cf0020627d6eba8c024f571ae4d78bd956162b07794ddfb9dd62 + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -3952,12 +3952,14 @@ __metadata: linkType: hard "jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.3": - version: 3.3.3 - resolution: "jsx-ast-utils@npm:3.3.3" + version: 3.3.4 + resolution: "jsx-ast-utils@npm:3.3.4" dependencies: - array-includes: ^3.1.5 - object.assign: ^4.1.3 - checksum: fb69ce100931e50d42c8f72a01495b7d090064824ce481cf7746449609c148a29aae6984624cf9066ac14bdf7978f8774461e120d5b50fa90b3bfe0a0e21ff77 + array-includes: ^3.1.6 + array.prototype.flat: ^1.3.1 + object.assign: ^4.1.4 + object.values: ^1.1.6 + checksum: 6761ccd830deab6a4cb8ca182c7b3627f4478138b6f4e2b680afc2b5e954635feb460ff75218b67f8694a9f8a0da6f0833a013e34961a16fbe4457fb34a0a7b2 languageName: node linkType: hard @@ -4079,27 +4081,33 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^10.0.3": - version: 10.2.1 - resolution: "make-fetch-happen@npm:10.2.1" +"lru-cache@npm:^9.1.1 || ^10.0.0": + version: 10.0.0 + resolution: "lru-cache@npm:10.0.0" + checksum: 347b7b391091e9f91182b6f683ce04329932a542376a2d7d300637213b99f06c222a3bb0f0db59adf246dac6cef1bb509cab352451a96621d07c41b10a20495f + languageName: node + linkType: hard + +"make-fetch-happen@npm:^11.0.3": + version: 11.1.1 + resolution: "make-fetch-happen@npm:11.1.1" dependencies: agentkeepalive: ^4.2.1 - cacache: ^16.1.0 - http-cache-semantics: ^4.1.0 + cacache: ^17.0.0 + http-cache-semantics: ^4.1.1 http-proxy-agent: ^5.0.0 https-proxy-agent: ^5.0.0 is-lambda: ^1.0.1 lru-cache: ^7.7.1 - minipass: ^3.1.6 - minipass-collect: ^1.0.2 - minipass-fetch: ^2.0.3 + minipass: ^5.0.0 + minipass-fetch: ^3.0.0 minipass-flush: ^1.0.5 minipass-pipeline: ^1.2.4 negotiator: ^0.6.3 promise-retry: ^2.0.1 socks-proxy-agent: ^7.0.0 - ssri: ^9.0.0 - checksum: 28ec392f63ab93511f400839dcee83107eeecfaad737d1e8487ea08b4332cd89a8f3319584222edd9f6f1d0833cf516691469496d46491863f9e88c658013949 + ssri: ^10.0.0 + checksum: c161bde51dbc03382f9fac091734526a64dd6878205db6c338f70d2133df797b5b5166bff3091cf7d4785869d4b21e99a58139c1790c2fb1b5eec00f528f5f0b languageName: node linkType: hard @@ -4117,6 +4125,13 @@ __metadata: languageName: node linkType: hard +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + "merge2@npm:^1.3.0, merge2@npm:^1.4.1": version: 1.4.1 resolution: "merge2@npm:1.4.1" @@ -4150,6 +4165,20 @@ __metadata: languageName: node linkType: hard +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 + languageName: node + linkType: hard + +"mimic-fn@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-fn@npm:4.0.0" + checksum: de9cc32be9996fd941e512248338e43407f63f6d497abe8441fa33447d922e927de54d4cc3c1a3c6d652857acd770389d5a3823f311a744132760ce2be15ccbf + languageName: node + linkType: hard + "minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -4159,12 +4188,12 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^5.0.1": - version: 5.1.6 - resolution: "minimatch@npm:5.1.6" +"minimatch@npm:^9.0.1": + version: 9.0.2 + resolution: "minimatch@npm:9.0.2" dependencies: brace-expansion: ^2.0.1 - checksum: 3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 + checksum: 39157d5fd831a7981f7c0c5b22a0e0c2ae8a987ec4a4aeaacc21d3e85da24ce812808cbf7c07cde0d63ad1cf307f73be581131a7a84eeda65f00be1f51972471 languageName: node linkType: hard @@ -4184,18 +4213,18 @@ __metadata: languageName: node linkType: hard -"minipass-fetch@npm:^2.0.3": - version: 2.1.2 - resolution: "minipass-fetch@npm:2.1.2" +"minipass-fetch@npm:^3.0.0": + version: 3.0.3 + resolution: "minipass-fetch@npm:3.0.3" dependencies: encoding: ^0.1.13 - minipass: ^3.1.6 + minipass: ^5.0.0 minipass-sized: ^1.0.3 minizlib: ^2.1.2 dependenciesMeta: encoding: optional: true - checksum: 33ab2c5bdb3d91b9cb8bc6ae42d7418f4f00f7f7beae14b3bb21ea18f9224e792f560a6e17b6f1be12bbeb70dbe99a269f4204c60e5d99130a0777b153505c43 + checksum: 12e0fde7e8fdb1bd923b9243b4788e7d3df305c6ddb3b79ab2da4587fa608c126157c7f6dd43746e8063ee99ec5abbb898d0426c812e9c9b68260c4fea9b279a languageName: node linkType: hard @@ -4226,7 +4255,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^3.0.0, minipass@npm:^3.1.1, minipass@npm:^3.1.6": +"minipass@npm:^3.0.0": version: 3.3.6 resolution: "minipass@npm:3.3.6" dependencies: @@ -4235,10 +4264,17 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^4.0.0": - version: 4.2.5 - resolution: "minipass@npm:4.2.5" - checksum: 98ef3a4f0e7cbb2a152139069fd48eca331296948946c69fd569eee24d875e1dbbe989609b561b9ff8ecdc3438f46ed6c287efd150facd7d374a8fd6ba689d02 +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2": + version: 6.0.2 + resolution: "minipass@npm:6.0.2" + checksum: 3878076578f44ef4078ceed10af2cfebbec1b6217bf9f7a3d8b940da8153769db29bf88498b2de0d1e0c12dfb7b634c5729b7ca03457f46435e801578add210a languageName: node linkType: hard @@ -4252,7 +4288,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": +"mkdirp@npm:^1.0.3": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" bin: @@ -4275,15 +4311,6 @@ __metadata: languageName: node linkType: hard -"nan@npm:^2.12.1": - version: 2.17.0 - resolution: "nan@npm:2.17.0" - dependencies: - node-gyp: latest - checksum: 4a231a62dba025f4c4fa814c1e6ffeb450c5cd0852b780f19fe4ea22b86ba0f1f394406dfd628c67fb7f0987e982fa230da1fbd3632258f927b8defd7046c1ad - languageName: node - linkType: hard - "nanoid@npm:^3.3.6": version: 3.3.6 resolution: "nanoid@npm:3.3.6" @@ -4322,13 +4349,14 @@ __metadata: linkType: hard "node-gyp@npm:latest": - version: 9.3.1 - resolution: "node-gyp@npm:9.3.1" + version: 9.4.0 + resolution: "node-gyp@npm:9.4.0" dependencies: env-paths: ^2.2.0 + exponential-backoff: ^3.1.1 glob: ^7.1.4 graceful-fs: ^4.2.6 - make-fetch-happen: ^10.0.3 + make-fetch-happen: ^11.0.3 nopt: ^6.0.0 npmlog: ^6.0.0 rimraf: ^3.0.2 @@ -4337,14 +4365,14 @@ __metadata: which: ^2.0.2 bin: node-gyp: bin/node-gyp.js - checksum: 3285c110768eb65aadd9aa1d056f917e594ea22611d21fd535ab3677ea433d0a281e7f09bc73d53e64b02214f4379dbca476dc33faffe455b0ac1d5ba92802f4 + checksum: e8dfbe2b02f23d056f69e01c409381963e92c71cafba6c9cfbf63b038f65ca19ab8183bb6891d080e59c4eb2cc425fc736f42e90afc0f0030ecd97bfc64fb7ad languageName: node linkType: hard -"node-releases@npm:^2.0.8": - version: 2.0.10 - resolution: "node-releases@npm:2.0.10" - checksum: 90947653e8e3d85bda4bcbf28d019693ccfb5d5947838ca64e91208b51d7bfc13ba930b8216389a4faffbad8145b2c616bf1f7a09c97a1a9ac57fd6ef6d01c5c +"node-releases@npm:^2.0.12": + version: 2.0.12 + resolution: "node-releases@npm:2.0.12" + checksum: 01f9a7c135be5c8bc989b6c10b9840a7aee09040d46ba4e64b5ea0174fb8891f1277514aef75033ce42031f6cb72a04d4a7e99c70ca25488ad63ad6fc5a5b6a0 languageName: node linkType: hard @@ -4430,6 +4458,24 @@ __metadata: languageName: node linkType: hard +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: ^3.0.0 + checksum: 6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac + languageName: node + linkType: hard + +"npm-run-path@npm:^5.1.0": + version: 5.1.0 + resolution: "npm-run-path@npm:5.1.0" + dependencies: + path-key: ^4.0.0 + checksum: ff6d77514489f47fa1c3b1311d09cd4b6d09a874cc1866260f9dea12cbaabda0436ed7f8c2ee44d147bf99a3af29307c6f63b0f83d242b0b6b0ab25dff2629e3 + languageName: node + linkType: hard + "npmlog@npm:^6.0.0": version: 6.0.2 resolution: "npmlog@npm:6.0.2" @@ -4456,16 +4502,6 @@ __metadata: languageName: node linkType: hard -"object-is@npm:^1.1.5": - version: 1.1.5 - resolution: "object-is@npm:1.1.5" - dependencies: - call-bind: ^1.0.2 - define-properties: ^1.1.3 - checksum: 8c263fb03fc28f1ffb54b44b9147235c5e233dc1ca23768e7d2569740b5d860154d7cc29a30220fe28ed6d8008e2422aefdebfe987c103e1c5d190cf02d9d886 - languageName: node - linkType: hard - "object-keys@npm:^1.1.1": version: 1.1.1 resolution: "object-keys@npm:1.1.1" @@ -4473,7 +4509,7 @@ __metadata: languageName: node linkType: hard -"object.assign@npm:^4.1.2, object.assign@npm:^4.1.3, object.assign@npm:^4.1.4": +"object.assign@npm:^4.1.2, object.assign@npm:^4.1.4": version: 4.1.4 resolution: "object.assign@npm:4.1.4" dependencies: @@ -4537,6 +4573,24 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: ^2.1.0 + checksum: ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f + languageName: node + linkType: hard + +"onetime@npm:^6.0.0": + version: 6.0.0 + resolution: "onetime@npm:6.0.0" + dependencies: + mimic-fn: ^4.0.0 + checksum: 4eef7c6abfef697dd4479345a4100c382d73c149d2d56170a54a07418c50816937ad09500e1ed1e79d235989d073a9bade8557122aee24f0576ecde0f392bb6c + languageName: node + linkType: hard + "open@npm:^8.4.0": version: 8.4.2 resolution: "open@npm:8.4.2" @@ -4548,17 +4602,29 @@ __metadata: languageName: node linkType: hard -"optionator@npm:^0.9.1": - version: 0.9.1 - resolution: "optionator@npm:0.9.1" +"open@npm:^9.1.0": + version: 9.1.0 + resolution: "open@npm:9.1.0" dependencies: + default-browser: ^4.0.0 + define-lazy-prop: ^3.0.0 + is-inside-container: ^1.0.0 + is-wsl: ^2.2.0 + checksum: 8073ec0dd8994a7a7d9bac208bd17d093993a65ce10f2eb9b62b6d3a91c9366ae903938a237c275493c130171d339f6dcbdd2a2de7e32953452c0867b97825af + languageName: node + linkType: hard + +"optionator@npm:^0.9.3": + version: 0.9.3 + resolution: "optionator@npm:0.9.3" + dependencies: + "@aashutoshrathi/word-wrap": ^1.2.3 deep-is: ^0.1.3 fast-levenshtein: ^2.0.6 levn: ^0.4.1 prelude-ls: ^1.2.1 type-check: ^0.4.0 - word-wrap: ^1.2.3 - checksum: 8b574d50b032f34713dc09bfacdc351824f713c3c80773ead3a05ab977364de88f2f3962a6f15437747b93a5e0636928253949970daea3aaeeefbd3a525da6a4 + checksum: 66fba794d425b5be51353035cf3167ce6cfa049059cbb93229b819167687e0f48d2bc4603fcb21b091c99acb516aae1083624675b15c4765b2e4693a085e959c languageName: node linkType: hard @@ -4641,13 +4707,20 @@ __metadata: languageName: node linkType: hard -"path-key@npm:^3.1.0": +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" checksum: 748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c languageName: node linkType: hard +"path-key@npm:^4.0.0": + version: 4.0.0 + resolution: "path-key@npm:4.0.0" + checksum: 794efeef32863a65ac312f3c0b0a99f921f3e827ff63afa5cb09a377e202c262b671f7b3832a4e64731003fa94af0263713962d317b9887bd1e0c48a342efba3 + languageName: node + linkType: hard + "path-parse@npm:^1.0.7": version: 1.0.7 resolution: "path-parse@npm:1.0.7" @@ -4655,6 +4728,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^1.10.0": + version: 1.10.0 + resolution: "path-scurry@npm:1.10.0" + dependencies: + lru-cache: ^9.1.1 || ^10.0.0 + minipass: ^5.0.0 || ^6.0.2 + checksum: dcc4109928c9a0991f0e1719c73b0a184eb7f313fe3eb2242f25274b1e761f53041989fb6b069541b88f58ee6dfbbecf94922225e0c5a3fba8112c9c60abb391 + languageName: node + linkType: hard + "path-type@npm:^3.0.0": version: 3.0.0 resolution: "path-type@npm:3.0.0" @@ -4702,13 +4785,13 @@ __metadata: linkType: hard "postcss@npm:^8.4.23": - version: 8.4.23 - resolution: "postcss@npm:8.4.23" + version: 8.4.24 + resolution: "postcss@npm:8.4.24" dependencies: nanoid: ^3.3.6 picocolors: ^1.0.0 source-map-js: ^1.0.2 - checksum: 35c2e26496be286a63706a0b8240fc4d2075a746466df530989208f60ea33cbc80c89420221cffb7d4fdd605afc385993f5f60302447e3047a7c0a8756b6471d + checksum: 37704ee03a2cbdebf2c99a76d399d6e0250742b5f6c699a12d475c84cedfcbeb26e180d9c780e0219dd2ad70cac963ceaf1d6763a1aec3e63d0c19fceb0eab23 languageName: node linkType: hard @@ -4737,13 +4820,6 @@ __metadata: languageName: node linkType: hard -"promise-inflight@npm:^1.0.1": - version: 1.0.1 - resolution: "promise-inflight@npm:1.0.1" - checksum: d179d148d98fbff3d815752fa9a08a87d3190551d1420f17c4467f628214db12235ae068d98cd001f024453676d8985af8f28f002345646c4ece4600a79620bc - languageName: node - linkType: hard - "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -4786,7 +4862,7 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:latest": +react-dom@latest: version: 18.2.0 resolution: "react-dom@npm:18.2.0" dependencies: @@ -4834,27 +4910,27 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.14.0": - version: 6.14.0 - resolution: "react-router-dom@npm:6.14.0" +"react-router-dom@npm:^6.14.1": + version: 6.14.1 + resolution: "react-router-dom@npm:6.14.1" dependencies: - "@remix-run/router": 1.7.0 - react-router: 6.14.0 + "@remix-run/router": 1.7.1 + react-router: 6.14.1 peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 104a09aa48b00bdf7bf021bc8c915d36e2045ff0a41b3b23b92b5d3ba5c16cbb0dd4eb00e01188a419e143af2212cbcd3c983ebde26e52c1ed33916746edba31 + checksum: 9d3a00263125668a9b703ddc908f98218598c216bc77d99931d0cdf7d6a0f7f9d57649f4261e429a5a1c0c51f5203379a6d8e5181e24d0af6c7623fdd966843c languageName: node linkType: hard -"react-router@npm:6.14.0": - version: 6.14.0 - resolution: "react-router@npm:6.14.0" +"react-router@npm:6.14.1": + version: 6.14.1 + resolution: "react-router@npm:6.14.1" dependencies: - "@remix-run/router": 1.7.0 + "@remix-run/router": 1.7.1 peerDependencies: react: ">=16.8" - checksum: 60a87b4e1bc684ddced3418f4cd91983ac5f7ea4aa1ac07dd9d336c67eb2511b22eda866a7d7bacd45abb81322ecc9a93afea98fb9daaef6b939a14eebbe4f73 + checksum: da870d0739038bb9630a9b3268ec79224bf47aa9f1015e30820cb29a29eabe0bee723991df5cbcb12473d82b7a2e49598e9d539a6f63fa3f2d64f5cc9cc4db64 languageName: node linkType: hard @@ -4908,7 +4984,7 @@ __metadata: languageName: node linkType: hard -"react@npm:latest": +react@latest: version: 18.2.0 resolution: "react@npm:18.2.0" dependencies: @@ -4956,13 +5032,13 @@ __metadata: linkType: hard "regexp.prototype.flags@npm:^1.4.3": - version: 1.4.3 - resolution: "regexp.prototype.flags@npm:1.4.3" + version: 1.5.0 + resolution: "regexp.prototype.flags@npm:1.5.0" dependencies: call-bind: ^1.0.2 - define-properties: ^1.1.3 - functions-have-names: ^1.2.2 - checksum: 5d797c7fb95f72a52dd9685a485faf0af3c55a4d1f2fafc1153a7be3df036cc3274b195b3ae051ee3d896a01960b446d726206e0d9a90b749e90d93445bb781f + define-properties: ^1.2.0 + functions-have-names: ^1.2.3 + checksum: 312b7966c5cd2e6837da4073e0e6450191e3c6e8f07276cbed35e170ea5606f91487b435eb3290593f8aed39b1191c44f5340e6e5392650feaf2b34a98378464 languageName: node linkType: hard @@ -4980,16 +5056,23 @@ __metadata: languageName: node linkType: hard +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab + languageName: node + linkType: hard + "resolve@npm:^1.10.0, resolve@npm:^1.19.0, resolve@npm:^1.22.1": - version: 1.22.2 - resolution: "resolve@npm:1.22.2" + version: 1.22.3 + resolution: "resolve@npm:1.22.3" dependencies: - is-core-module: ^2.11.0 + is-core-module: ^2.12.0 path-parse: ^1.0.7 supports-preserve-symlinks-flag: ^1.0.0 bin: resolve: bin/resolve - checksum: f9f424a8117d1c68371b4fbc64e6ac045115a3beacc4bd3617b751f7624b69ad40c47dc995585c7f13d4a09723a8f167847defb7d39fad70b0d43bbba05ff851 + checksum: 5ebd90dc08467e7d9af8f89a67f127c90d77e58d3bfc65da5221699cc15679c5bae5e410e6795ee4b9f717cd711c495a52a3b650ce6720b0626de46e5074e796 languageName: node linkType: hard @@ -5007,15 +5090,15 @@ __metadata: linkType: hard "resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin": - version: 1.22.2 - resolution: "resolve@patch:resolve@npm%3A1.22.2#~builtin::version=1.22.2&hash=c3c19d" + version: 1.22.3 + resolution: "resolve@patch:resolve@npm%3A1.22.3#~builtin::version=1.22.3&hash=c3c19d" dependencies: - is-core-module: ^2.11.0 + is-core-module: ^2.12.0 path-parse: ^1.0.7 supports-preserve-symlinks-flag: ^1.0.0 bin: resolve: bin/resolve - checksum: dcf068c4391941734efda06b6f778c013fd349cd4340f126de17c265a7b006c67de7e80e7aa06ecd29f3922e49f5561622b9faf98531f16aa9a896d22148c661 + checksum: 6267bdbbbb1da23975463e979dadf5135fcc40c4b9281c5af4581afa848ced98090ab4e2dbc9085e58f8ea48c0eb7c4fe94b1e8f55ebdd17a725d86982eb5288 languageName: node linkType: hard @@ -5077,8 +5160,8 @@ __metadata: linkType: hard "rollup@npm:^3.21.0": - version: 3.21.0 - resolution: "rollup@npm:3.21.0" + version: 3.26.0 + resolution: "rollup@npm:3.26.0" dependencies: fsevents: ~2.3.2 dependenciesMeta: @@ -5086,7 +5169,16 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: d88fdc7ea41e1db44b903575782bd7075886e158b31d9869505a06f7afa72ff17f9c6030973736c93142904ad1a9ee730724063a2df227740e587ec62aa4cde3 + checksum: 948b028978dc943c2dee30713c12e2cdc5f63c99871a86e8de1fa31038209dd14a1408146e6d112105d9f82231f2cdfca319cadd29459eec81cb37a152692311 + languageName: node + linkType: hard + +"run-applescript@npm:^5.0.0": + version: 5.0.0 + resolution: "run-applescript@npm:5.0.0" + dependencies: + execa: ^5.0.0 + checksum: f9977db5770929f3f0db434b8e6aa266498c70dec913c84320c0a06add510cf44e3a048c44da088abee312006f9cbf572fd065cdc8f15d7682afda8755f4114c languageName: node linkType: hard @@ -5152,13 +5244,13 @@ __metadata: linkType: hard "semver@npm:^7.3.5, semver@npm:^7.3.7": - version: 7.3.8 - resolution: "semver@npm:7.3.8" + version: 7.5.3 + resolution: "semver@npm:7.5.3" dependencies: lru-cache: ^6.0.0 bin: semver: bin/semver.js - checksum: 7e581d679530db31757301c2117721577a2bb36a301a443aac833b8efad372cda58e7f2a464fe4412ae1041cc1f63a6c1fe0ced8c57ce5aca1e0b57bb0d627b9 + checksum: 4cf3bab7e8cf8c2ae521fc4bcc50a4d6912a836360796b23b9f1c26f45d27a73f870e47664df4770bde0dd60dc4d4781a05fd49fe91d72376ea5519b9e791459 languageName: node linkType: hard @@ -5211,9 +5303,9 @@ __metadata: linkType: hard "shell-quote@npm:^1.6.1": - version: 1.8.0 - resolution: "shell-quote@npm:1.8.0" - checksum: 651a201a1af981d49326fac8c005bbe2af97bc56fcabded0b22944c08eea0ba3bccfa497168d4bcb70508ca5802fe1cb83ca89a7e121eb0701d4c8b1d6c71a5d + version: 1.8.1 + resolution: "shell-quote@npm:1.8.1" + checksum: 8cec6fd827bad74d0a49347057d40dfea1e01f12a6123bf82c4649f3ef152fc2bc6d6176e6376bffcd205d9d0ccb4f1f9acae889384d20baff92186f01ea455a languageName: node linkType: hard @@ -5228,13 +5320,20 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: 25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 languageName: node linkType: hard +"signal-exit@npm:^4.0.1": + version: 4.0.2 + resolution: "signal-exit@npm:4.0.2" + checksum: 3c36ae214f4774b4a7cbbd2d090b2864f8da4dc3f9140ba5b76f38bea7605c7aa8042adf86e48ee8a0955108421873f9b0f20281c61b8a65da4d9c1c1de4929f + languageName: node + linkType: hard + "simple-update-notifier@npm:^1.0.7": version: 1.1.0 resolution: "simple-update-notifier@npm:1.1.0" @@ -5365,21 +5464,12 @@ __metadata: languageName: node linkType: hard -"ssri@npm:^9.0.0": - version: 9.0.1 - resolution: "ssri@npm:9.0.1" +"ssri@npm:^10.0.0": + version: 10.0.4 + resolution: "ssri@npm:10.0.4" dependencies: - minipass: ^3.1.1 - checksum: c5d153ce03b5980d683ecaa4d805f6a03d8dc545736213803e168a1907650c46c08a4e5ce6d670a0205482b35c35713d9d286d9133bdd79853a406e22ad81f04 - languageName: node - linkType: hard - -"stop-iteration-iterator@npm:^1.0.0": - version: 1.0.0 - resolution: "stop-iteration-iterator@npm:1.0.0" - dependencies: - internal-slot: ^1.0.4 - checksum: c4158d6188aac510d9e92925b58709207bd94699e9c31186a040c80932a687f84a51356b5895e6dc72710aad83addb9411c22171832c9ae0e6e11b7d61b0dfb9 + minipass: ^5.0.0 + checksum: d085474ea6b439623a9a6a2c67570cb9e68e1bb6060e46e4d387f113304d75a51946d57c524be3a90ebfa3c73026edf76eb1a2d79a7f6cff0b04f21d99f127ab languageName: node linkType: hard @@ -5390,7 +5480,7 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -5401,6 +5491,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: ^0.2.0 + emoji-regex: ^9.2.2 + strip-ansi: ^7.0.1 + checksum: ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + languageName: node + linkType: hard + "string.prototype.matchall@npm:^4.0.8": version: 4.0.8 resolution: "string.prototype.matchall@npm:4.0.8" @@ -5470,7 +5571,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" dependencies: @@ -5479,6 +5580,15 @@ __metadata: languageName: node linkType: hard +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" + dependencies: + ansi-regex: ^6.0.1 + checksum: a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 + languageName: node + linkType: hard + "strip-bom@npm:^3.0.0": version: 3.0.0 resolution: "strip-bom@npm:3.0.0" @@ -5486,6 +5596,20 @@ __metadata: languageName: node linkType: hard +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f + languageName: node + linkType: hard + +"strip-final-newline@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-final-newline@npm:3.0.0" + checksum: a771a17901427bac6293fd416db7577e2bc1c34a19d38351e9d5478c3c415f523f391003b42ed475f27e33a78233035df183525395f731d3bfb8cdcbd4da08ce + languageName: node + linkType: hard + "strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" @@ -5550,16 +5674,16 @@ __metadata: linkType: hard "tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.1.13 - resolution: "tar@npm:6.1.13" + version: 6.1.15 + resolution: "tar@npm:6.1.15" dependencies: chownr: ^2.0.0 fs-minipass: ^2.0.0 - minipass: ^4.0.0 + minipass: ^5.0.0 minizlib: ^2.1.1 mkdirp: ^1.0.3 yallist: ^4.0.0 - checksum: eee5f264f3f3c27cd8d4934f80c568470f92811c416144ab671bb36b45a8ed55fbfbbd31f0146f3eddaca91fd564c9a7ec4d2086940968b836f4a2c54146c060 + checksum: bb2babe7b14442f690d83c2b2c571c9dd0bf802314773e05f4a3e4a241fdecd7fb560b8e4e7d6ea34533c8cd692e1b8418a3b8ba3b9687fe78a683dfbad7f82d languageName: node linkType: hard @@ -5584,13 +5708,10 @@ __metadata: languageName: node linkType: hard -"tiny-glob@npm:^0.2.9": - version: 0.2.9 - resolution: "tiny-glob@npm:0.2.9" - dependencies: - globalyzer: 0.1.0 - globrex: ^0.1.2 - checksum: cbe072f0d213a1395d30aa94845a051d4af18fe8ffb79c8e99ac1787cd25df69083f17791a53997cb65f469f48950cb61426ccc0683cc9df170ac2430e883702 +"titleize@npm:^3.0.0": + version: 3.0.0 + resolution: "titleize@npm:3.0.0" + checksum: 5ae6084ba299b5782f95e3fe85ea9f0fa4d74b8ae722b6b3208157e975589fbb27733aeba4e5080fa9314a856044ef52caa61b87caea4b1baade951a55c06336 languageName: node linkType: hard @@ -5655,9 +5776,9 @@ __metadata: linkType: hard "tslib@npm:^2.4.0, tslib@npm:^2.5.0": - version: 2.5.0 - resolution: "tslib@npm:2.5.0" - checksum: e32fc99cc730dd514e53c44e668d76016e738f0bcc726aad5dbd2d335cf19b87a95a9b1e4f0a9993e370f1d702b5e471cdd4acabcac428a3099d496b9af2021e + version: 2.6.0 + resolution: "tslib@npm:2.6.0" + checksum: 8d18020a8b9e70ecc529a744c883c095f177805efdbc9786bd50bd82a46c17547923133c5444fbcaf1f7f1c44e0e29c89f73ecf6d8fd1039668024a073a81dc6 languageName: node linkType: hard @@ -5749,35 +5870,42 @@ __metadata: languageName: node linkType: hard -"unique-filename@npm:^2.0.0": - version: 2.0.1 - resolution: "unique-filename@npm:2.0.1" +"unique-filename@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-filename@npm:3.0.0" dependencies: - unique-slug: ^3.0.0 - checksum: 55d95cd670c4a86117ebc34d394936d712d43b56db6bc511f9ca00f666373818bf9f075fb0ab76bcbfaf134592ef26bb75aad20786c1ff1ceba4457eaba90fb8 + unique-slug: ^4.0.0 + checksum: 6363e40b2fa758eb5ec5e21b3c7fb83e5da8dcfbd866cc0c199d5534c42f03b9ea9ab069769cc388e1d7ab93b4eeef28ef506ab5f18d910ef29617715101884f languageName: node linkType: hard -"unique-slug@npm:^3.0.0": - version: 3.0.0 - resolution: "unique-slug@npm:3.0.0" +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" dependencies: imurmurhash: ^0.1.4 - checksum: 617240eb921af803b47d322d75a71a363dacf2e56c29ae5d1404fad85f64f4ec81ef10ee4fd79215d0202cbe1e5a653edb0558d59c9c81d3bd538c2d58e4c026 + checksum: cb811d9d54eb5821b81b18205750be84cb015c20a4a44280794e915f5a0a70223ce39066781a354e872df3572e8155c228f43ff0cce94c7cbf4da2cc7cbdd635 languageName: node linkType: hard -"update-browserslist-db@npm:^1.0.10": - version: 1.0.10 - resolution: "update-browserslist-db@npm:1.0.10" +"untildify@npm:^4.0.0": + version: 4.0.0 + resolution: "untildify@npm:4.0.0" + checksum: d758e624c707d49f76f7511d75d09a8eda7f2020d231ec52b67ff4896bcf7013be3f9522d8375f57e586e9a2e827f5641c7e06ee46ab9c435fc2b2b2e9de517a + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.0.11": + version: 1.0.11 + resolution: "update-browserslist-db@npm:1.0.11" dependencies: escalade: ^3.1.1 picocolors: ^1.0.0 peerDependencies: browserslist: ">= 4.21.0" bin: - browserslist-lint: cli.js - checksum: e6fa55b515a674cc3b6c045d1f37f72780ddbbbb48b3094391fb2e43357b859ca5cee4c7d3055fd654d442ef032777d0972494a9a2e6c30d3660ee57b7138ae9 + update-browserslist-db: cli.js + checksum: 280d5cf92e302d8de0c12ef840a6af26ec024a5158aa2020975cd01bf0ded09c709793a6f421e6d0f1a47557d6a1a10dc43af80f9c30b8fd0df9691eb98c1c69 languageName: node linkType: hard @@ -5886,18 +6014,6 @@ __metadata: languageName: node linkType: hard -"which-collection@npm:^1.0.1": - version: 1.0.1 - resolution: "which-collection@npm:1.0.1" - dependencies: - is-map: ^2.0.1 - is-set: ^2.0.1 - is-weakmap: ^2.0.1 - is-weakset: ^2.0.1 - checksum: 249f913e1758ed2f06f00706007d87dc22090a80591a56917376e70ecf8fc9ab6c41d98e1c87208bb9648676f65d4b09c0e4d23c56c7afb0f0a73a27d701df5d - languageName: node - linkType: hard - "which-typed-array@npm:^1.1.9": version: 1.1.9 resolution: "which-typed-array@npm:1.1.9" @@ -5943,14 +6059,7 @@ __metadata: languageName: node linkType: hard -"word-wrap@npm:^1.2.3": - version: 1.2.3 - resolution: "word-wrap@npm:1.2.3" - checksum: 1cb6558996deb22c909330db1f01d672feee41d7f0664492912de3de282da3f28ba2d49e87b723024e99d56ba2dac2f3ab28f8db07ac199f5e5d5e2e437833de - languageName: node - linkType: hard - -"wrap-ansi@npm:^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" dependencies: @@ -5961,6 +6070,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: ^6.1.0 + string-width: ^5.0.1 + strip-ansi: ^7.0.1 + checksum: 138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 + languageName: node + linkType: hard + "wrappy@npm:1": version: 1.0.2 resolution: "wrappy@npm:1.0.2" @@ -6004,8 +6124,8 @@ __metadata: linkType: hard "yargs@npm:^17.5.1": - version: 17.7.1 - resolution: "yargs@npm:17.7.1" + version: 17.7.2 + resolution: "yargs@npm:17.7.2" dependencies: cliui: ^8.0.1 escalade: ^3.1.1 @@ -6014,7 +6134,7 @@ __metadata: string-width: ^4.2.3 y18n: ^5.0.5 yargs-parser: ^21.1.1 - checksum: 0ed3b7694d94da777f3591f1d786d947ed2e59b897da0a0c30e541109ae087979ac26b4ec39557f5e9c4592f19806447963fb132049b9806a1d416bcdd24d2b4 + checksum: ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 languageName: node linkType: hard diff --git a/lib/framework/UploadFileService.cpp b/lib/framework/UploadFileService.cpp index 865e453d8..d06427ade 100644 --- a/lib/framework/UploadFileService.cpp +++ b/lib/framework/UploadFileService.cpp @@ -42,12 +42,12 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri return; } else { md5[0] = '\0'; - return; // not support file type + return; // unsupported file type } if (is_firmware) { // Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip offset 12: esp32:0, S2:2, C3:5 -#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 +#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) { handleError(request, 503); // service unavailable return; @@ -76,7 +76,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri } request->onDisconnect(UploadFileService::handleEarlyDisconnect); // success, let's make sure we end the update if the client hangs up } else { - handleError(request, 507); // failed to begin, send an error response Insufficient Storage + handleError(request, 507); // failed to begin, send an error response Insufficient Storage return; } } else { @@ -115,7 +115,7 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) { } // check if it was a firmware upgrade - // if no error, send the success response + // if no error, send the success response as a JSON if (is_firmware && !request->_tempObject) { request->onDisconnect(RestartService::restartNow); AsyncWebServerResponse * response = request->beginResponse(200); @@ -123,8 +123,13 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) { return; } if (strlen(md5) == 32) { - AsyncWebServerResponse * response = request->beginResponse(201, "text/plain", md5); // created + auto * response = new AsyncJsonResponse(false, 256); + JsonObject root = response->getRoot(); + root["md5"] = md5; + response->setLength(); request->send(response); + // AsyncWebServerResponse * response = request->beginResponse(201, "text/plain", md5); // created + // request->send(response); return; } diff --git a/mock-api/server.js b/mock-api/server.js index 50fb6d225..87c504f78 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -24,7 +24,7 @@ function progress_middleware(req, res, next) { const percentage = (progress / file_size) * 100; console.log(`Progress: ${Math.round(percentage)}%`); // await delay(1000); // slow it down - delay_blocking(200); // slow it down + delay_blocking(100); // slow it down }); next(); // invoke next middleware which is multer } @@ -2151,18 +2151,21 @@ rest_server.post(FACTORY_RESET_ENDPOINT, (req, res) => { rest_server.post(UPLOAD_FILE_ENDPOINT, progress_middleware, upload.single('file'), (req, res) => { console.log('command: uploadFile completed.'); - console.log(req.file); if (req.file) { - return res.sendStatus(200); + const filename = req.file.originalname; + const ext = filename.substring(filename.lastIndexOf('.') + 1); + console.log(req.file); + console.log('ext: ' + ext); + + if (ext === 'bin') { + return res.sendStatus(200); + } else if (ext === 'md5') { + return res.json({ md5: 'ef4304fc4d9025a58dcf25d71c882d2c' }); + } } return res.sendStatus(400); }); -rest_server.post(SIGN_IN_ENDPOINT, (req, res) => { - // res.sendStatus(401); // test bad user - console.log('Signed in as ' + req.body.username); - res.json(signin); // watch out, this has a return value -}); rest_server.get(GENERATE_TOKEN_ENDPOINT, (req, res) => { res.json(generate_token); }); diff --git a/src/version.h b/src/version.h index f5295cb6a..70ce71c7e 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.6.0-dev.13a" +#define EMSESP_APP_VERSION "3.6.0-dev.13b" From 9b21607da525aa5c20a04e9458327beaddf70af7 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 2 Jul 2023 10:12:17 +0200 Subject: [PATCH 043/163] show EMS-ESP welcome message first --- src/emsesp.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/emsesp.cpp b/src/emsesp.cpp index b269eb749..f36b13740 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -1444,9 +1444,10 @@ void EMSESP::start() { bool factory_settings = false; #endif - esp8266React.begin(); // loads core system services settings (network, mqtt, ap, ntp etc) - webLogService.begin(); // start web log service. now we can start capturing logs to the web log + esp8266React.begin(); // loads core system services settings (network, mqtt, ap, ntp etc) + webLogService.begin(); // start web log service. now we can start capturing logs to the web log + LOG_INFO("Starting EMS-ESP version %s", EMSESP_APP_VERSION); // welcome message LOG_DEBUG("System is running in Debug mode"); LOG_INFO("Last system reset reason Core0: %s, Core1: %s", system_.reset_reason(0).c_str(), system_.reset_reason(1).c_str()); @@ -1481,17 +1482,12 @@ void EMSESP::start() { #endif } - // start all the EMS-ESP services - mqtt_.start(); // mqtt init - - system_.start(); // starts commands, led, adc, button, network, syslog & uart - - LOG_INFO(("Starting EMS-ESP version %s (hostname: %s)"), EMSESP_APP_VERSION, system_.hostname().c_str()); // welcome message - - shower_.start(); // initialize shower timer and shower alert - temperaturesensor_.start(); // Temperature external sensors - analogsensor_.start(); // Analog external sensors - webLogService.start(); // apply settings to weblog service + mqtt_.start(); // mqtt init + system_.start(); // starts commands, led, adc, button, network (sets hostname), syslog & uart + shower_.start(); // initialize shower timer and shower alert + temperaturesensor_.start(); // Temperature external sensors + analogsensor_.start(); // Analog external sensors + webLogService.start(); // apply settings to weblog service // Load our library of known devices into stack mem. Names are stored in Flash memory device_library_ = { From 34113c369842031922c11f1349a2e940334a3e43 Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 2 Jul 2023 10:12:31 +0200 Subject: [PATCH 044/163] fix progress bar when uploading --- interface/src/components/upload/SingleUpload.tsx | 11 ++++++----- interface/src/framework/system/UploadFileForm.tsx | 3 +-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/interface/src/components/upload/SingleUpload.tsx b/interface/src/components/upload/SingleUpload.tsx index 8c5358706..498e4b913 100644 --- a/interface/src/components/upload/SingleUpload.tsx +++ b/interface/src/components/upload/SingleUpload.tsx @@ -26,11 +26,12 @@ const getBorderColor = (theme: Theme, props: DropzoneState) => { export interface SingleUploadProps { onDrop: (acceptedFiles: File[]) => void; onCancel: () => void; + isUploading: boolean; progress: Progress; } -const SingleUpload: FC = ({ onDrop, onCancel, progress }) => { - const isUploading = progress.total > 0; +const SingleUpload: FC = ({ onDrop, onCancel, isUploading, progress }) => { + const uploading = isUploading && progress.total > 0; const dropzoneState = useDropzone({ onDrop, @@ -48,7 +49,7 @@ const SingleUpload: FC = ({ onDrop, onCancel, progress }) => const { LL } = useI18nContext(); const progressText = () => { - if (isUploading) { + if (uploading) { if (progress.total) { return LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%'; } @@ -68,7 +69,7 @@ const SingleUpload: FC = ({ onDrop, onCancel, progress }) => color: theme.palette.grey[400], transition: 'border .24s ease-in-out', width: '100%', - cursor: isUploading ? 'default' : 'pointer', + cursor: uploading ? 'default' : 'pointer', borderColor: getBorderColor(theme, dropzoneState) } })} @@ -77,7 +78,7 @@ const SingleUpload: FC = ({ onDrop, onCancel, progress }) => {progressText()} - {isUploading && ( + {uploading && ( { setMd5(data.md5); toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL()); } else { - toast.success(LL.UPLOAD() + ' ' + LL.SUCCESSFUL()); setRestarting(true); } }); @@ -123,7 +122,7 @@ const UploadFileForm: FC = () => { {'MD5: ' + md5} )} - + {!isUploading && ( <> From 09aea280b51367f8dd907eb74c0ae92c580b7b1d Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Sun, 2 Jul 2023 12:09:14 +0200 Subject: [PATCH 045/163] add it-translations --- interface/package.json | 6 +- interface/src/SignIn.tsx | 5 + .../src/components/layout/LayoutAuthMenu.tsx | 5 + interface/src/i18n/IT.svg | 1 + interface/src/i18n/it/index.ts | 331 +++++ interface/src/project/SettingsApplication.tsx | 1 + interface/yarn.lock | 121 +- src/locale_translations.h | 1307 +++++++++-------- src/system.cpp | 2 +- 9 files changed, 1066 insertions(+), 713 deletions(-) create mode 100644 interface/src/i18n/IT.svg create mode 100644 interface/src/i18n/it/index.ts diff --git a/interface/package.json b/interface/package.json index cfc140991..d7dff3ee4 100644 --- a/interface/package.json +++ b/interface/package.json @@ -25,7 +25,7 @@ "@mui/material": "^5.13.6", "@table-library/react-table-library": "4.1.4", "@types/lodash-es": "^4.17.7", - "@types/node": "^20.3.2", + "@types/node": "^20.3.3", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", "@types/react-router-dom": "^5.3.3", @@ -38,7 +38,7 @@ "react-dom": "latest", "react-dropzone": "^14.2.3", "react-icons": "^4.10.1", - "react-router-dom": "^6.14.0", + "react-router-dom": "^6.14.1", "react-toastify": "^9.1.3", "sockette": "^2.0.6", "typesafe-i18n": "^5.24.3", @@ -48,7 +48,7 @@ "@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/parser": "^5.60.1", "@vitejs/plugin-react-swc": "^3.3.2", - "eslint": "^8.43.0", + "eslint": "^8.44.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-prettier": "^8.8.0", diff --git a/interface/src/SignIn.tsx b/interface/src/SignIn.tsx index 0cca183c9..63f453bdc 100644 --- a/interface/src/SignIn.tsx +++ b/interface/src/SignIn.tsx @@ -16,6 +16,7 @@ import { AuthenticationContext } from 'contexts/authentication'; import { ReactComponent as DEflag } from 'i18n/DE.svg'; import { ReactComponent as FRflag } from 'i18n/FR.svg'; import { ReactComponent as GBflag } from 'i18n/GB.svg'; +import { ReactComponent as ITflag } from 'i18n/IT.svg'; import { ReactComponent as NLflag } from 'i18n/NL.svg'; import { ReactComponent as NOflag } from 'i18n/NO.svg'; import { ReactComponent as PLflag } from 'i18n/PL.svg'; @@ -122,6 +123,10 @@ const SignIn: FC = () => {  FR +