diff --git a/README.md b/README.md index 7deded08..0242e574 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/mobizt/FirebaseClient/.github%2Fworkflows%2Fcompile_library.yml?logo=github&label=compile) [![Github Stars](https://img.shields.io/github/stars/mobizt/FirebaseClient?logo=github)](https://github.com/mobizt/FirebaseClient/stargazers) ![Github Issues](https://img.shields.io/github/issues/mobizt/FirebaseClient?logo=github) -![GitHub Release](https://img.shields.io/github/v/release/mobizt/FirebaseClient) ![Arduino](https://img.shields.io/badge/Arduino-v1.4.18-57C207?logo=arduino) ![PlatformIO](https://badges.registry.platformio.org/packages/mobizt/library/FirebaseClient.svg) ![GitHub Release Date](https://img.shields.io/github/release-date/mobizt/FirebaseClient) +![GitHub Release](https://img.shields.io/github/v/release/mobizt/FirebaseClient) ![Arduino](https://img.shields.io/badge/Arduino-v1.5.0-57C207?logo=arduino) ![PlatformIO](https://badges.registry.platformio.org/packages/mobizt/library/FirebaseClient.svg) ![GitHub Release Date](https://img.shields.io/github/release-date/mobizt/FirebaseClient) [![GitHub Sponsors](https://img.shields.io/github/sponsors/mobizt?logo=github)](https://github.com/sponsors/mobizt) -Revision `2025-01-13T05:09:36Z` +Revision `2025-01-17T14:42:44Z` ## Table of Contents diff --git a/library.json b/library.json index 8d959d25..682ea190 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "FirebaseClient", - "version": "1.4.18", + "version": "1.5.0", "keywords": "communication, REST, esp32, esp8266, arduino", "description": "Async Firebase Client library for Arduino.", "repository": { diff --git a/library.properties b/library.properties index 6317cd55..cf77aaf6 100644 --- a/library.properties +++ b/library.properties @@ -1,6 +1,6 @@ name=FirebaseClient -version=1.4.18 +version=1.5.0 author=Mobizt diff --git a/resources/docs/async_result.md b/resources/docs/async_result.md index 1262f5ca..30ed1483 100644 --- a/resources/docs/async_result.md +++ b/resources/docs/async_result.md @@ -29,11 +29,19 @@ Get the copy of server response payload string. String payload() const ``` +3. ## 🔹 size_t length() const + +Get the length of response payload string. + +```cpp +size_t length() const +``` + **Returns:** - `String` - The copy of payload string. -3. ## 🔹 String path() const +4. ## 🔹 String path() const Get the path of the resource of the request. @@ -46,7 +54,7 @@ String path() const - `String` - The path of the resource of the request. -4. ## 🔹 String etag() const +5. ## 🔹 String etag() const Get the Etag of the server response headers. @@ -59,7 +67,7 @@ String etag() const - `String` - The ETag of response header. -5. ## 🔹 String uid() const +6. ## 🔹 String uid() const Get the unique identifier of async task. @@ -72,7 +80,7 @@ String uid() const - `String` - The UID of async task. -6. ## 🔹 String debug() +7. ## 🔹 String debug() Get the debug information. @@ -84,7 +92,7 @@ String debug() - `String` - The debug information. -7. ## 🔹 void clear() +8. ## 🔹 void clear() Clear the async result. @@ -92,7 +100,7 @@ Clear the async result. void clear() ``` -8. ## 🔹 RealtimeDatabaseResult &to() +9. ## 🔹 RealtimeDatabaseResult &to() Get the reference to the internal RealtimeDatabaseResult object. @@ -105,7 +113,7 @@ RealtimeDatabaseResult &to() - `RealtimeDatabaseResult &` - The reference to the internal RealtimeDatabaseResult object. -9. ## 🔹 int available() +10. ## 🔹 int available() Get the number of bytes of available response payload. @@ -118,7 +126,7 @@ int available() - `int` - The number of bytes available. -10. ## 🔹 app_event_t &appEvent() +11. ## 🔹 app_event_t &appEvent() Get the reference of internal app event information. @@ -131,7 +139,7 @@ app_event_t &appEvent() - `app_event_t &` - The reference of internal app event. -11. ## 🔹 bool uploadProgress() +12. ## 🔹 bool uploadProgress() Check if file/BLOB upload information is available. @@ -144,7 +152,7 @@ bool uploadProgress() - `bool` - Returns true if upload information is available. -12. ## 🔹 upload_data_t uploadInfo() const +13. ## 🔹 upload_data_t uploadInfo() const Get the file/BLOB upload information. @@ -157,7 +165,7 @@ upload_data_t uploadInfo() const - `upload_data_t` - The file/BLOB upload information. -13. ## 🔹 bool downloadProgress() +14. ## 🔹 bool downloadProgress() Check if the file/BLOB download information is availablle. @@ -170,7 +178,7 @@ bool downloadProgress() - `bool` - Returns true if download information is available. -14. ## 🔹 download_data_t downloadInfo() const +15. ## 🔹 download_data_t downloadInfo() const Get the file/BLOB download information. @@ -183,7 +191,7 @@ download_data_t downloadInfo() const - `download_data_t` - The file/BLOB download information. -15. ## 🔹 bool isOTA() const +16. ## 🔹 bool isOTA() const Check if the result is from OTA download task. @@ -196,7 +204,7 @@ bool isOTA() const - `bool` - Returns true if the result is from OTA download task. -16. ## 🔹 bool isError() +17. ## 🔹 bool isError() Check if the error occurred in async task. @@ -209,7 +217,7 @@ bool isError() - `bool` - Returns true if error occurred. -17. ## 🔹 bool isDebug() +18. ## 🔹 bool isDebug() Check if the debug information in available. @@ -222,7 +230,7 @@ bool isDebug() - `bool` - Returns true if debug information in available. -18. ## 🔹 bool isEvent() +19. ## 🔹 bool isEvent() Check if the app event information in available. @@ -235,7 +243,7 @@ bool isEvent() - `bool` - Returns true if app event information in available. -19. ## 🔹 FirebaseError &error() +20. ## 🔹 FirebaseError &error() Get the reference of internal FirebaseError object. diff --git a/src/core/AsyncClient/AsyncClient.h b/src/core/AsyncClient/AsyncClient.h index ed75e0f0..5d3710fb 100644 --- a/src/core/AsyncClient/AsyncClient.h +++ b/src/core/AsyncClient/AsyncClient.h @@ -1,5 +1,5 @@ /** - * Created January 14, 2025 + * Created January 17, 2025 * * For MCU build target (CORE_ARDUINO_XXXX), see Options.h. * @@ -858,11 +858,14 @@ class AsyncClientClass : public ResultBase, RTDBResultBase if (!readPayload(sData)) return false; + String *payload = &sData->response.val[res_hndlr_ns::payload]; + if (sData->response.flags.sse || !sData->response.flags.payload_remaining) { if (!sData->auth_used) { - sData->aResult.setPayload(sData->response.val[res_hndlr_ns::payload]); + if (!sData->response.flags.sse) + sData->aResult.setPayload(*payload); if (sData->aResult.download_data.total > 0) clearAppData(sData->aResult.app_data); @@ -872,37 +875,43 @@ class AsyncClientClass : public ResultBase, RTDBResultBase if (sData->request.method == async_request_handler_t::http_post) parseNodeName(&sData->aResult.rtdbResult); - // data available from sse event - if (sData->response.flags.sse && sData->response.val[res_hndlr_ns::payload].length()) + // Data available from sse event + if (sData->response.flags.sse && payload->length()) { - // order of checking: event, data, newline - if (sData->response.val[res_hndlr_ns::payload].indexOf("event: ") > -1 && sData->response.val[res_hndlr_ns::payload].indexOf("data: ") > -1 && sData->response.val[res_hndlr_ns::payload].indexOf("\n") > -1) + + if (payload->indexOf("event: ") > -1 && payload->indexOf("data: ") > -1 && payload->indexOf("\n") > -1) { + // Prevent sse timeout due to large sse Stream playload + feedSSETimer(&sData->aResult.rtdbResult); - parseSSE(&sData->aResult.rtdbResult); - - // Event filtering. - String event = sData->aResult.rtdbResult.event(); - if (sse_events_filter.length() == 0 || - (sData->response.flags.http_response && sse_events_filter.indexOf("get") > -1 && event.indexOf("put") > -1) || - (!sData->response.flags.http_response && sse_events_filter.indexOf("put") > -1 && event.indexOf("put") > -1) || - (sse_events_filter.indexOf("patch") > -1 && event.indexOf("patch") > -1) || - (sse_events_filter.indexOf("keep-alive") > -1 && event.indexOf("keep-alive") > -1) || - (sse_events_filter.indexOf("cancel") > -1 && event.indexOf("cancel") > -1) || - (sse_events_filter.indexOf("auth_revoked") > -1 && event.indexOf("auth_revoked") > -1)) - { - // save payload to slot result - sData->aResult.setPayload(sData->response.val[res_hndlr_ns::payload]); - clear(sData->response.val[res_hndlr_ns::payload]); - sData->response.flags.payload_available = true; - returnResult(sData, true); - } - else + if ((*payload)[payload->length() - 1] == '\n' && sData->response.tcpAvailable(client_type, client, async_tcp_config) == 0) { - clear(sData->response.val[res_hndlr_ns::payload]); - } + setRefPayload(&sData->aResult.rtdbResult, payload); + parseSSE(&sData->aResult.rtdbResult); + + // Event filtering. + String event = sData->aResult.rtdbResult.event(); + if (sse_events_filter.length() == 0 || + (sData->response.flags.http_response && sse_events_filter.indexOf("get") > -1 && event.indexOf("put") > -1) || + (!sData->response.flags.http_response && sse_events_filter.indexOf("put") > -1 && event.indexOf("put") > -1) || + (sse_events_filter.indexOf("patch") > -1 && event.indexOf("patch") > -1) || + (sse_events_filter.indexOf("keep-alive") > -1 && event.indexOf("keep-alive") > -1) || + (sse_events_filter.indexOf("cancel") > -1 && event.indexOf("cancel") > -1) || + (sse_events_filter.indexOf("auth_revoked") > -1 && event.indexOf("auth_revoked") > -1)) + { + // save payload to slot result + sData->aResult.setPayload(*payload); + clear(*payload); + sData->response.flags.payload_available = true; + returnResult(sData, true); + } + else + { + clear(*payload); + } - sData->response.flags.http_response = false; + sData->response.flags.http_response = false; + } } } #endif @@ -1142,7 +1151,16 @@ class AsyncClientClass : public ResultBase, RTDBResultBase if (sData->response.flags.chunks) { - if (decodeChunks(sData, client, &sData->response.val[res_hndlr_ns::payload]) == -1) + // Use temporary String buffer for decodeChunks + String temp; + int res = decodeChunks(sData, client, &temp); + if (temp.length()) + { + reserveString(sData); + sData->response.val[res_hndlr_ns::payload] += temp; + } + + if (res == -1) sData->response.flags.payload_remaining = false; } else @@ -1294,7 +1312,14 @@ class AsyncClientClass : public ResultBase, RTDBResultBase } } else - sData->response.payloadRead += readLine(sData, sData->response.val[res_hndlr_ns::payload]); + { + // Use temporary String buffer for readLine + String temp; + size_t len = readLine(sData, temp); + sData->response.payloadRead += len; + reserveString(sData); + sData->response.val[res_hndlr_ns::payload] += temp; + } } } } @@ -1354,6 +1379,17 @@ class AsyncClientClass : public ResultBase, RTDBResultBase return sData->error.code == 0; } + void reserveString(async_data_item_t *sData) + { + // String memory reservation is needed to hadle large data in external memory. +#if defined(ENABLE_PSRAM) && ((defined(ESP8266) && defined(MMU_EXTERNAL_HEAP)) || (defined(ESP32) && defined(BOARD_HAS_PSRAM))) + String old = sData->response.val[res_hndlr_ns::payload]; + sData->response.val[res_hndlr_ns::payload].remove(0, sData->response.val[res_hndlr_ns::payload].length()); + sData->response.val[res_hndlr_ns::payload].reserve(sData->response.payloadRead + 1); + sData->response.val[res_hndlr_ns::payload] = old; +#endif + } + // non-block memory buffer for collecting the multiple of 4 data prepared for base64 decoding uint8_t *asyncBase64Buffer(async_data_item_t *sData, Memory &mem, int &toRead, int &read) { diff --git a/src/core/AsyncResult/AsyncResult.h b/src/core/AsyncResult/AsyncResult.h index e739c596..4d1e646a 100644 --- a/src/core/AsyncResult/AsyncResult.h +++ b/src/core/AsyncResult/AsyncResult.h @@ -1,5 +1,5 @@ /** - * Created December 27, 2024 + * Created January 17, 2025 * * The MIT License (MIT) * Copyright (c) 2025 K. Suwatchai (Mobizt) @@ -228,6 +228,13 @@ class AsyncResult : public ResultBase, RealtimeDatabaseResult */ String payload() const { return val[ares_ns::data_payload].c_str(); } + /** + * Get the length of response payload string. + * + * @return The payload string length. + */ + size_t length() const { return val[ares_ns::data_payload].length(); } + /** * Get the path of the resource of the request. * diff --git a/src/core/AsyncResult/ResultBase.h b/src/core/AsyncResult/ResultBase.h index ccafc51e..ce67615f 100644 --- a/src/core/AsyncResult/ResultBase.h +++ b/src/core/AsyncResult/ResultBase.h @@ -1,5 +1,5 @@ /** - * Created June 12, 2024 + * Created January 17, 2025 * * The MIT License (MIT) * Copyright (c) 2025 K. Suwatchai (Mobizt) @@ -65,7 +65,8 @@ namespace firebase bool sse = false; event_resume_status_t event_resume_status = event_resume_status_undefined; String node_name, etag; - uint16_t data_path_p1 = 0, data_path_p2 = 0, event_p1 = 0, event_p2 = 0, data_p1 = 0, data_p2 = 0; + // Large data supports + uint32_t data_path_p1 = 0, data_path_p2 = 0, event_p1 = 0, event_p2 = 0, data_p1 = 0, data_p2 = 0; bool null_etag = false; String *ref_payload = nullptr; @@ -91,6 +92,8 @@ namespace firebase } } + void feed() { sse_timer.feed(FIREBASE_SSE_TIMEOUT_MS / 1000); } + void parseSSE() { clearSSE(); @@ -151,6 +154,7 @@ namespace firebase void clearSSE(RealtimeDatabaseResult *rtdbResult) { rtdbResult->clearSSE(); } void parseNodeName(RealtimeDatabaseResult *rtdbResult) { rtdbResult->parseNodeName(); } void parseSSE(RealtimeDatabaseResult *rtdbResult) { rtdbResult->parseSSE(); } + void feedSSETimer(RealtimeDatabaseResult *rtdbResult) { rtdbResult->feed(); } void setEventResumeStatus(RealtimeDatabaseResult *rtdbResult, event_resume_status_t status) { rtdbResult->setEventResumeStatus(status); } event_resume_status_t eventResumeStatus(const RealtimeDatabaseResult *rtdbResult) { return rtdbResult->eventResumeStatus(); } @@ -191,14 +195,14 @@ namespace firebase * * @return String The relative path of data that has been changed. */ - String dataPath() const { return ref_payload ? ref_payload->substring(data_path_p1, data_path_p2).c_str() : ""; } + String dataPath() const { return ref_payload ? ref_payload->substring(data_path_p1, data_path_p2).c_str() : String(); } /** * Get the `SSE mode (HTTP Streaming)` event type string. * * @return String The event type string e.g. `put`, `patch`, `keep-alive`, `cancel` and `auth_revoked`. */ - String event() { return ref_payload ? ref_payload->substring(event_p1, event_p2).c_str() : ""; } + String event() { return ref_payload ? ref_payload->substring(event_p1, event_p2).c_str() : String(); } /** * Get the SSE mode (HTTP Streaming) event data that has been changed. @@ -208,8 +212,8 @@ namespace firebase String data() { if (data_p1 > 0) - return ref_payload->substring(data_p1, data_p2).c_str(); - return ref_payload ? ref_payload->c_str() : ""; + return ref_payload->substring(data_p1, data_p2); + return ref_payload ? ref_payload->c_str() : String(); } /** diff --git a/src/core/Core.h b/src/core/Core.h index b3a3cea7..c49fa197 100644 --- a/src/core/Core.h +++ b/src/core/Core.h @@ -7,7 +7,7 @@ #undef FIREBASE_CLIENT_VERSION #endif -#define FIREBASE_CLIENT_VERSION "1.4.18" +#define FIREBASE_CLIENT_VERSION "1.5.0" static void sys_idle() {