diff --git a/Firebase.cpp b/Firebase.cpp index 954689e5..6752811a 100644 --- a/Firebase.cpp +++ b/Firebase.cpp @@ -15,114 +15,145 @@ // #include "Firebase.h" -const char* firebaseFingerprint = "7A 54 06 9B DC 7A 25 B3 86 8D 66 53 48 2C 0B 96 42 C7 B3 0A"; -const uint16_t firebasePort = 443; +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; -Firebase::Firebase(const String& host) : _host(host) { - _http.setReuse(true); +String makeFirebaseURL(const String& path, const String& auth) { + String url; + if (path[0] != '/') { + url = "/"; + } + url += path + ".json"; + if (auth.length() > 0) { + url += "?auth=" + auth; + } + return url; +} + +} // namespace + +Firebase::Firebase(const String& host) : host_(host) { + http_.setReuse(true); } Firebase& Firebase::auth(const String& auth) { - _auth = auth; + auth_ = auth; return *this; } -String Firebase::get(const String& path) { - sendRequest("GET", path); - return readBody(); +FirebaseGet Firebase::get(const String& path) { + return FirebaseGet(host_, auth_, path, &http_); } -String Firebase::push(const String& path, const String& value) { - sendRequest("POST", path, value); - return readBody(); +FirebasePush Firebase::push(const String& path, const String& value) { + return FirebasePush(host_, auth_, path, value, &http_); } -void Firebase::remove(const String& path) { - sendRequest("DELETE", path); +FirebaseRemove Firebase::remove(const String& path) { + return FirebaseRemove(host_, auth_, path, &http_); } -String Firebase::set(const String& path, const String& value) { - sendRequest("PUT", path, value); - return readBody(); +FirebaseStream Firebase::stream(const String& path) { + // TODO: create new client dedicated to stream. + return FirebaseStream(host_, auth_, path, &http_); } -Firebase& Firebase::stream(const String& path) { - _error.reset(); - String url = makeURL(path); - const char* headers[] = {"Location"}; - _http.setReuse(true); - _http.begin(_host.c_str(), firebasePort, url.c_str(), true, firebaseFingerprint); - _http.collectHeaders(headers, 1); - _http.addHeader("Accept", "text/event-stream"); - int statusCode = _http.sendRequest("GET", (uint8_t*)NULL, 0); - String location; - // TODO(proppy): Add a max redirect check - while (statusCode == 307) { - location = _http.header("Location"); - _http.setReuse(false); - _http.end(); - _http.setReuse(true); - _http.begin(location, firebaseFingerprint); - statusCode = _http.sendRequest("GET", (uint8_t*)NULL, 0); +// FirebaseCall +FirebaseCall::FirebaseCall(const String& host, const String& auth, + const char* method, const String& path, + const String& data, HTTPClient* http) : http_(http) { + String url = makeFirebaseURL(path, auth); + http_->setReuse(true); + http_->begin(host, kFirebasePort, url, true, kFirebaseFingerprint); + + bool followRedirect = false; + if (method == "STREAM") { + method = "GET"; + http_->addHeader("Accept", "text/event-stream"); + followRedirect = true; } - if (statusCode != 200) { - _error.set(statusCode, - "stream " + location + ": " - + HTTPClient::errorToString(statusCode)); + + if (followRedirect) { + const char* headers[] = {"Location"}; + http_->collectHeaders(headers, 1); } - return *this; -} + + int status = http_->sendRequest(method, (uint8_t*)data.c_str(), data.length()); -String Firebase::makeURL(const String& path) { - String url; - if (path[0] != '/') { - url = "/"; + // 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("GET", (uint8_t*)NULL, 0); + } } - url += path + ".json"; - if (_auth.length() > 0) { - url += "?auth=" + _auth; + + if (status != 200) { + error_ = FirebaseError(status, String(method) + " " + url + ": " + HTTPClient::errorToString(status)); + } + + // if not streaming. + if (!followRedirect) { + response_ = http_->getString(); } - return url; } -void Firebase::sendRequest(const char* method, const String& path, const String& value) { - String url = makeURL(path); - _http.begin(_host.c_str(), firebasePort, url.c_str(), true, firebaseFingerprint); - int statusCode = _http.sendRequest(method, (uint8_t*)value.c_str(), value.length()); - _error.reset(); - if (statusCode < 0) { - _error.set(statusCode, - String(method) + " " + url + ": " - + HTTPClient::errorToString(statusCode)); - } +// FirebaseGet +FirebaseGet::FirebaseGet(const String& host, const String& auth, + const String& path, + HTTPClient* http) + : FirebaseCall(host, auth, "GET", path, "", http) { + + if (!error()) { + // TODO: parse json + json_ = response(); + } } -String Firebase::readBody() { - if (_error.code() != 0) { - return ""; +// 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(); } - // no _http.end() because of connection reuse. - return _http.getString(); } -bool Firebase::connected() { - return _http.connected(); +// FirebasePush +FirebaseRemove::FirebaseRemove(const String& host, const String& auth, + const String& path, + HTTPClient* http) + : FirebaseCall(host, auth, "DELETE", path, "", http) { +} + +// FirebaseStream +FirebaseStream::FirebaseStream(const String& host, const String& auth, + const String& path, + HTTPClient* http) + : FirebaseCall(host, auth, "STREAM", path, "", http) { } -bool Firebase::available() { - return _http.getStreamPtr()->available(); +bool FirebaseStream::available() { + return http_->getStreamPtr()->available(); } -Firebase::Event Firebase::read(String& event) { - auto client = _http.getStreamPtr(); - Event type;; +FirebaseStream::Event FirebaseStream::read(String& event) { + auto client = http_->getStreamPtr(); + Event type; String typeStr = client->readStringUntil('\n').substring(7); if (typeStr == "put") { - type = Firebase::Event::PUT; + type = Event::PUT; } else if (typeStr == "patch") { - type = Firebase::Event::PATCH; + type = Event::PATCH; } else { - type = Firebase::Event::UNKNOWN; + type = Event::UNKNOWN; } event = client->readStringUntil('\n').substring(6); client->readStringUntil('\n'); // consume separator diff --git a/Firebase.h b/Firebase.h index 0b3802a9..aac4e86c 100644 --- a/Firebase.h +++ b/Firebase.h @@ -25,56 +25,129 @@ #include #include -// FirebaseError represents a Firebase API error with a code and a -// message. +class FirebaseGet; +class FirebasePush; +class FirebaseRemove; +class FirebaseStream; + +// Primary client to the Firebase backend. +class Firebase { + public: + Firebase(const String& host); + Firebase& auth(const String& auth); + + // Fetch result at "path". + FirebaseGet get(const String& path); + + // 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 firebase. + FirebaseRemove remove(const String& path); + + // Starts a stream of events that affect object at "path". + // TODO: fix FirebaseStream lifecycle + // https://github.com/esp8266/Arduino/issues/500 + FirebaseStream stream(const String& path); + + private: + HTTPClient http_; + String host_; + String auth_; +}; + class FirebaseError { public: - operator bool() const { return _code < 0; } - int code() const { return _code; } - const String& message() const { return _message; } - void reset() { set(0, ""); } - void set(int code, const String& message) { - _code = code; - _message = 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 FirebaseCall { + public: + FirebaseCall() {} + 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_; + } + const String& response() { + return response_; } + protected: + HTTPClient* http_; + FirebaseError error_; + String response_; +}; + +class FirebaseGet : public FirebaseCall { + public: + FirebaseGet() {} + FirebaseGet(const String& host, const String& auth, + const String& path, HTTPClient* http = NULL); + + const String& json() const { + return json_; + } + private: - int _code = 0; - String _message = ""; + String json_; }; -// Firebase is the connection to firebase. -class Firebase { +class FirebasePush : public FirebaseCall { public: - Firebase(const String& host); - Firebase& auth(const String& auth); - const FirebaseError& error() const { - return _error; + FirebasePush() {} + FirebasePush(const String& host, const String& auth, + const String& path, const String& value, HTTPClient* http = NULL); + + const String& name() const { + return name_; } - String get(const String& path); - // write a new JSON `value` to the given `path`. - // Note: A String `value` must include double quotes to be valid json. - String set(const String& path, const String& value); - // append a new JSON `value` to the given `path`. - // Note: A String `value` must include double quotes to be valid json. - String push(const String& path, const String& value); - void remove(const String& path); - bool connected(); - Firebase& stream(const String& path); + + private: + String name_; +}; + +class FirebaseRemove : public FirebaseCall { + public: + FirebaseRemove() {} + FirebaseRemove(const String& host, const String& auth, + const String& path, HTTPClient* http = NULL); +}; + + +class FirebaseStream : public FirebaseCall { + public: + FirebaseStream() {} + FirebaseStream(const String& host, const String& auth, + const String& path, HTTPClient* http = NULL); + + // True if there is an event available. bool available(); + + // event type. enum Event { UNKNOWN, PUT, PATCH }; - Event read(String& event); - private: - String makeURL(const String& path); - void sendRequest(const char* method, const String& path, const String& value = ""); - String readBody(); - HTTPClient _http; - String _host; - String _auth; + // Read next event in stream. + Event read(String& event); + + const FirebaseError& error() const { + return _error; + } + + private: FirebaseError _error; }; diff --git a/examples/FirebasePush_ESP8266/FirebasePush_ESP8266.ino b/examples/FirebasePush_ESP8266/FirebasePush_ESP8266.ino index d04b6fed..c1bd368e 100644 --- a/examples/FirebasePush_ESP8266/FirebasePush_ESP8266.ino +++ b/examples/FirebasePush_ESP8266/FirebasePush_ESP8266.ino @@ -20,8 +20,8 @@ #include // create firebase client. -Firebase fbase = Firebase("example.firebaseio.com") - .auth("secret_or_token"); +Firebase fbase("example.firebaseio.com") + .auth("secret_or_token"); void setup() { Serial.begin(9600); @@ -37,19 +37,27 @@ void setup() { Serial.print("connected: "); Serial.println(WiFi.localIP()); - // add a new entry. - String l = fbase.push("/logs", "{\".sv\": \"timestamp\"}"); - // handle error. - if (fbase.error()) { - Serial.println("Firebase request failed"); - Serial.println(fbase.error().message()); + // add a new entry. + FirebasePush push = fbase.push("/logs", "{\".sv\": \"timestamp\"}"); + if (push.error()) { + Serial.println("Firebase push failed"); + Serial.println(push.error().message()); return; } - // print response. - Serial.println(l); - // print all entries. - Serial.println(fbase.get("/logs")); + + // print key. + Serial.println(push.name()); + + // 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() { -} \ No newline at end of file +} diff --git a/examples/FirebaseStream_ESP8266/FirebaseStream_ESP8266.ino b/examples/FirebaseStream_ESP8266/FirebaseStream_ESP8266.ino index 13bb2174..73b17149 100644 --- a/examples/FirebaseStream_ESP8266/FirebaseStream_ESP8266.ino +++ b/examples/FirebaseStream_ESP8266/FirebaseStream_ESP8266.ino @@ -24,7 +24,8 @@ #define OLED_RESET 10 Adafruit_SSD1306 display(OLED_RESET); -Firebase fbase = Firebase("publicdata-cryptocurrency.firebaseio.com"); +Firebase fbase("publicdata-cryptocurrency.firebaseio.com"); +FirebaseStream stream; void setup() { Serial.begin(9600); @@ -42,22 +43,22 @@ void setup() { Serial.println(); Serial.print("connected: "); Serial.println(WiFi.localIP()); - - fbase.stream("/bitcoin"); + stream = fbase.stream("/bitcoin"); } -void loop() { - if (fbase.error()) { +void loop() { + if (stream.error()) { Serial.println("streaming error"); - Serial.println(fbase.error().message()); + Serial.println(stream.error().message()); } - if (fbase.available()) { + + if (stream.available()) { String event; - auto type = fbase.read(event); + auto type = stream.read(event); Serial.print("event: "); Serial.println(type); - if (type != Firebase::Event::UNKNOWN) { + if (type != FirebaseStream::Event::UNKNOWN) { Serial.print("data: "); Serial.println(event); @@ -69,5 +70,5 @@ void loop() { display.println(event); display.display(); } - } -} \ No newline at end of file + } +}