diff --git a/Firebase.cpp b/Firebase.cpp index 9d86a712..69cd0150 100644 --- a/Firebase.cpp +++ b/Firebase.cpp @@ -19,7 +19,7 @@ namespace { const char* kFirebaseFingerprint = "7A 54 06 9B DC 7A 25 B3 86 8D 66 53 48 2C 0B 96 42 C7 B3 0A"; const uint16_t kFirebasePort = 443; -String makeUrl(const String& path, const String& auth) { +String makeFirebaseURL(const String& path, const String& auth) { String url; if (path[0] != '/') { url = "/"; @@ -31,40 +31,6 @@ String makeUrl(const String& path, const String& auth) { return url; } -class FirebaseCall { - public: - FirebaseCall(const String& host, const String& auth, - const char* method, const String& path, const String& value, - HTTPClient* http); - FirebaseCall(const String& host, const String& auth, - const char* method, const String& path, - HTTPClient* http); - - - // True if there was an error completing call. - bool isError() const; - String errorMessage() const; - - // True if http status code is 200(OK). - bool isOk() const; - - // Message sent back from Firebase backend. This pulls value to local memory, - // be careful if value can be large. - String rawResponse(); - - int httpStatus() const { - return status_; - } - - private: - FirebaseCall(HTTPClient* http); - - HTTPClient* http_; - - int status_; - String error_message_; -}; - } // namespace Firebase::Firebase(const String& host) : host_(host) { @@ -76,180 +42,122 @@ Firebase& Firebase::auth(const String& auth) { return *this; } -FirebaseGetResult Firebase::get(const String& path) { - FirebaseCall call(host_, auth_, "GET", path, &http_); - return call.isError() ? FirebaseGetResult::FromError(call.errorMessage()) - : FirebaseGetResult::FromResponse(call.rawResponse()); +FirebaseGet Firebase::get(const String& path) { + return FirebaseGet(host_, auth_, path, &http_); } -FirebasePushResult Firebase::push(const String& path, const String& value) { - FirebaseCall call(host_, auth_, "POST", path, value, &http_); - return call.isError() ? FirebasePushResult::FromError(call.errorMessage()) - : FirebasePushResult::FromResponse(call.rawResponse()); +FirebasePush Firebase::push(const String& path, const String& value) { + return FirebasePush(host_, auth_, path, value, &http_); } -FirebaseRemoveResult Firebase::remove(const String& path) { - FirebaseCall call(host_, auth_, "DELETE", path, &http_); - if (call.isError()) { - return FirebaseRemoveResult::FromError(call.errorMessage()); - } - // Remove is only complete if returned status is OK(200). - if (!call.isOk()) { - return FirebaseRemoveResult::FromError( - "Remove " + path + " returned with status " + call.httpStatus()); - } - return FirebaseRemoveResult::Ok(); +FirebaseRemove Firebase::remove(const String& path) { + return FirebaseRemove(host_, auth_, path, &http_); } -FirebaseEventStream Firebase::stream(const String& path) { - return FirebaseEventStream(host_, auth_, path); +FirebaseStream Firebase::stream(const String& path) { + return FirebaseStream(host_, auth_, path); // stream doesn't reuse http client. } -/* FirebaseCall */ +// FirebaseCall FirebaseCall::FirebaseCall(const String& host, const String& auth, - const char* method, const String& path, const String& value, - HTTPClient* http) : http_(http) { - const String url = makeUrl(path, auth); - http_->begin(host.c_str(), kFirebasePort, url.c_str(), true, kFirebaseFingerprint); - status_ = http_->sendRequest(method, (uint8_t*)value.c_str(), value.length()); - if (isError()) { - error_message_ = String(method) + " " + url + ": " + HTTPClient::errorToString(status_); + const char* method, const String& path, + const String& data, HTTPClient* http) { + if (!http) { + http = &http_; } -} -FirebaseCall::FirebaseCall(const String& host, const String& auth, - const char* method, const String& path, - HTTPClient* http) : FirebaseCall(host, auth, method, path, "", http) { -} + String url = makeFirebaseURL(path, auth); + http->setReuse(true); + http->begin(host, kFirebasePort, url, true, kFirebaseFingerprint); -bool FirebaseCall::isOk() const { - return status_ == HTTP_CODE_OK; -} + bool followRedirect = false; + if (method == "STREAM") { + method = "GET"; + http->addHeader("Accept", "text/event-stream"); + followRedirect = true; + } -bool FirebaseCall::isError() const { - return status_ < 0; -} + if (followRedirect) { + const char* headers[] = {"Location"}; + http->collectHeaders(headers, 1); + } + + int status = http->sendRequest(method, (uint8_t*)data.c_str(), data.length()); + + // TODO: Add a max redirect check + if (followRedirect) { + while (status == HTTP_CODE_TEMPORARY_REDIRECT) { + String location = http->header("Location"); + http->setReuse(false); + http->end(); + http->setReuse(true); + http->begin(location, kFirebaseFingerprint); + status = http->sendRequest(method, (uint8_t*)NULL, 0); + } + } -String FirebaseCall::errorMessage() const { - return error_message_; -} + if (status != 200) { + error_ = FirebaseError(status, String(method) + " " + url + ": " + HTTPClient::errorToString(status)); + } -String FirebaseCall::rawResponse() { - return http_->getString(); + // if not streaming. + if (!followRedirect) { + response_ = http->getString(); + } } -/* FirebaseEventStream */ +// FirebaseGet +FirebaseGet::FirebaseGet(const String& host, const String& auth, + const String& path, + HTTPClient* http) + : FirebaseCall(host, auth, "GET", path, "", http) { -FirebaseEventStream::FirebaseEventStream(const String& host, const String& auth, - const String& path) { - const String url = makeUrl(path, auth); - http_.setReuse(true); - http_.begin(host.c_str(), kFirebasePort, url.c_str(), true, - kFirebaseFingerprint); - const char* headers[] = {"Location"}; - http_.collectHeaders(headers, 1); - http_.addHeader("Accept", "text/event-stream"); - status_ = http_.sendRequest("GET", (uint8_t*)NULL, 0); - - String location; - // TODO(proppy): Add a max redirect check - while (status_ == HTTP_CODE_TEMPORARY_REDIRECT) { - location = http_.header("Location"); - http_.setReuse(false); - http_.end(); - http_.setReuse(true); - http_.begin(location, kFirebaseFingerprint); - status_ = http_.sendRequest("GET", (uint8_t*)NULL, 0); + if (!error()) { + // TODO: parse json + json_ = response(); } +} - if (status_ != 200) { - error_message_ = "stream " + location + ": " - + HTTPClient::errorToString(status_); +// FirebasePush +FirebasePush::FirebasePush(const String& host, const String& auth, + const String& path, const String& value, + HTTPClient* http) + : FirebaseCall(host, auth, "POST", path, value, http) { + if (!error()) { + // TODO: parse name + name_ = response(); } } -bool FirebaseEventStream::connected() { - return http_.connected(); +// FirebasePush +FirebaseRemove::FirebaseRemove(const String& host, const String& auth, + const String& path, + HTTPClient* http) + : FirebaseCall(host, auth, "DELETE", path, "", http) { } -bool FirebaseEventStream::available() { +// FirebaseStream +FirebaseStream::FirebaseStream(const String& host, const String& auth, + const String& path) + : FirebaseCall(host, auth, "STREAM", path) { +} + +bool FirebaseStream::available() { return http_.getStreamPtr()->available(); } -FirebaseEventStream::Event FirebaseEventStream::read(String& event) { +FirebaseStream::Event FirebaseStream::read(String& event) { auto client = http_.getStreamPtr(); Event type; String typeStr = client->readStringUntil('\n').substring(7); if (typeStr == "put") { - type = FirebaseEventStream::Event::PUT; + type = Event::PUT; } else if (typeStr == "patch") { - type = FirebaseEventStream::Event::PATCH; + type = Event::PATCH; } else { - type = FirebaseEventStream::Event::UNKNOWN; + type = Event::UNKNOWN; } event = client->readStringUntil('\n').substring(6); client->readStringUntil('\n'); // consume separator return type; } - -bool FirebaseEventStream::isError() const { - return status_ < 0; -} - -String FirebaseEventStream::errorMessage() const { - return error_message_; -} - -FirebaseEventStream::operator bool() { - return !isError() && connected(); -} - -/* FirebaseResult */ - -FirebaseResult::FirebaseResult(const String& error_message) - : is_error_(true), error_message_(error_message) {} - -FirebaseResult::FirebaseResult() {} - -FirebaseResult::operator bool() const { - return !isError(); -} - -bool FirebaseResult::isError() const { - return is_error_; -} - -const String& FirebaseResult::errorMessage() const { - return error_message_; -} - -/* FirebaseRemoveResult */ - -FirebaseRemoveResult::FirebaseRemoveResult(const String& error_message) - : FirebaseResult(error_message) {} - -FirebaseRemoveResult::FirebaseRemoveResult() {} - - -/* FirebasePushResult */ - -FirebasePushResult::FirebasePushResult(const String& error_message) - : FirebaseResult(error_message) {} - -FirebasePushResult::FirebasePushResult() {} - -const String& FirebasePushResult::name() const { - return name_; -} - -/* FirebaseGetResult */ - -FirebaseGetResult::FirebaseGetResult(const String& error_message) - : FirebaseResult(error_message) {} - -FirebaseGetResult::FirebaseGetResult() {} - -const String& FirebaseGetResult::rawResponse() { - return response_; -} - diff --git a/Firebase.h b/Firebase.h index c7eb28be..99927568 100644 --- a/Firebase.h +++ b/Firebase.h @@ -25,10 +25,10 @@ #include #include -class FirebaseGetResult; -class FirebasePushResult; -class FirebaseRemoveResult; -class FirebaseEventStream; +class FirebaseGet; +class FirebasePush; +class FirebaseRemove; +class FirebaseStream; // Primary client to the Firebase backend. class Firebase { @@ -37,106 +37,96 @@ class Firebase { Firebase& auth(const String& auth); // Fetch result at "path". - FirebaseGetResult get(const String& path); + FirebaseGet get(const String& path); - // Add new value to list at "path", will return child name of new item. - FirebasePushResult push(const String& path, const String& value); + // Add new value to list at "path", will return key for the new item. + FirebasePush push(const String& path, const String& value); - // Deletes value at "path" from server. - FirebaseRemoveResult remove(const String& path); + // Deletes value at "path" from firebase. + FirebaseRemove remove(const String& path); - // Starts a stream of events that effect object at "path". - FirebaseEventStream stream(const String& path); + // Starts a stream of events that affect object at "path". + FirebaseStream stream(const String& path); private: - - int sendRequest(const char* method, const String& path, const String& value); - HTTPClient http_; String host_; String auth_; }; -// Base class for Results from a Firebase call. -class FirebaseResult { +class FirebaseError { public: - // Constructor for error result. - FirebaseResult(const String& error_message); - // Constructor if no error. - FirebaseResult(); - - // True if no error. - operator bool() const; - - bool isError() const; - - const String& errorMessage() const; - - private: - bool is_error_ = false; - String error_message_; + FirebaseError() {} + FirebaseError(int code, const String& message) : code_(code), message_(message) { + } + operator bool() const { return code_ != 0; } + int code() const { return code_; } + const String& message() const { return message_; } + private: + int code_ = 0; + String message_ = ""; }; -class FirebaseRemoveResult : public FirebaseResult { +class FirebaseCall { public: - static FirebaseRemoveResult FromError(const String& error_message) { - return FirebaseRemoveResult(error_message); + FirebaseCall(const String& host, const String& auth, + const char* method, const String& path, + const String& data = "", + HTTPClient* http = NULL); + const FirebaseError& error() const { + return error_; } - - static FirebaseRemoveResult Ok() { - return FirebaseRemoveResult(); + const String& response() { + return response_; } - - private: - FirebaseRemoveResult(const String& error_message); - FirebaseRemoveResult(); + protected: + HTTPClient http_; + FirebaseError error_; + String response_; }; -class FirebasePushResult : public FirebaseResult { +class FirebaseGet : public FirebaseCall { public: - static FirebasePushResult FromError(const String& error_message) { - return FirebasePushResult(error_message); - } - - static FirebasePushResult FromResponse(const String& response) { - FirebasePushResult result; - // TODO(edcoyne): add json parsing to get name object. - result.name_ = response; - return result; + FirebaseGet(const String& host, const String& auth, + const String& path, HTTPClient* http = NULL); + + const String& json() const { + return json_; } - const String& name() const; - private: - FirebasePushResult(const String& error_message); - FirebasePushResult(); - - String name_; + String json_; }; -class FirebaseGetResult : public FirebaseResult { +class FirebasePush : public FirebaseCall { public: - static FirebaseGetResult FromError(const String& error_message) { - return FirebaseGetResult(error_message); - } + FirebasePush(const String& host, const String& auth, + const String& path, const String& value, HTTPClient* http = NULL); - static FirebaseGetResult FromResponse(const String& response) { - FirebaseGetResult result; - result.response_ = response; - return result; + const String& name() const { + return name_; } - const String& rawResponse(); - private: - FirebaseGetResult(const String& error_message); - FirebaseGetResult(); + String name_; +}; - String response_; +class FirebaseRemove : public FirebaseCall { + public: + FirebaseRemove(const String& host, const String& auth, + const String& path, HTTPClient* http = NULL); }; -class FirebaseEventStream { + +class FirebaseStream : public FirebaseCall { public: + FirebaseStream(const String& host, const String& auth, + const String &path); + + // True if there is an event available. + bool available(); + + // event type. enum Event { UNKNOWN, PUT, @@ -144,25 +134,15 @@ class FirebaseEventStream { }; // Read next event in stream. - Event read(String& event); - - // True if connected to backend. - bool connected(); - - // True if there is an event available. - bool available(); - - // True if no error and stream is connected. - operator bool(); - - // True if there was an error. - bool isError() const; - String errorMessage() const; + Event read(String& event); + const FirebaseError& error() const { + return _error; + } + private: HTTPClient http_; - int status_; - String error_message_; + FirebaseError _error; }; #endif // firebase_h diff --git a/examples/FirebasePush_ESP8266/FirebasePush_ESP8266.ino b/examples/FirebasePush_ESP8266/FirebasePush_ESP8266.ino index a9cdab8c..1f30d68c 100644 --- a/examples/FirebasePush_ESP8266/FirebasePush_ESP8266.ino +++ b/examples/FirebasePush_ESP8266/FirebasePush_ESP8266.ino @@ -38,21 +38,25 @@ void setup() { Serial.println(WiFi.localIP()); // add a new entry. - FirebasePushResult push = fbase.push("/logs", "{\".sv\": \"timestamp\"}"); - if (!push) { - Serial.println("Firebase request failed"); - Serial.println(push.errorMessage()); + FirebasePush push = fbase.push("/logs", "{\".sv\": \"timestamp\"}"); + if (push.error()) { + Serial.println("Firebase push failed"); + Serial.println(push.error().message()); return; } - // print response. + // print key. Serial.println(push.name()); - - // print all entries. - FirebaseGetResult get = fbase.get("/logs"); - if (get) { - Serial.println(get.rawResponse()); + + // get all entries. + FirebaseGet get = fbase.get("/logs"); + if (get.error()) { + Serial.println("Firebase get failed"); + Serial.println(push.error().message()); + return; } + // print json. + Serial.println(get.json()); } void loop() { diff --git a/examples/FirebaseStream_ESP8266/FirebaseStream_ESP8266.ino b/examples/FirebaseStream_ESP8266/FirebaseStream_ESP8266.ino index 6bcac3dc..92743215 100644 --- a/examples/FirebaseStream_ESP8266/FirebaseStream_ESP8266.ino +++ b/examples/FirebaseStream_ESP8266/FirebaseStream_ESP8266.ino @@ -46,10 +46,10 @@ void setup() { void loop() { - static FirebaseEventStream stream = fbase.stream("/bitcoin"); - if (!stream) { + static FirebaseStream stream = fbase.stream("/bitcoin"); + if (!stream.error()) { Serial.println("streaming error"); - Serial.println(stream.errorMessage()); + Serial.println(stream.error().message()); } if (stream.available()) { @@ -57,7 +57,7 @@ void loop() { auto type = stream.read(event); Serial.print("event: "); Serial.println(type); - if (type != FirebaseEventStream::Event::UNKNOWN) { + if (type != FirebaseEvent::Event::UNKNOWN) { Serial.print("data: "); Serial.println(event);