Note: As of version 4.0, the official Firebase JavaScript SDK has been opensourced! For that reason, I won't continue working on expanding or maintaning Openflame. I'm leaving the repo up as a curiosity.
Openflame is an RxJS-based, open-source SDK for Firebase being developed as an alternative to the official web SDK. It is not intended as a drop-in replacement.
This started just for fun, to be honest. I wanted to learn what the SDK was really doing behind the scenes and figure out how and when it communicated with the Firebase servers, so one weekend I started looking into it.
The communication part was easy. I could simply use the SDK to attach listeners, or change data, or do any other operation and then monitor the WebSocket frames being sent back an forth.
Figuring out what was actually happening on the client side, though, was trickier. The official SDK is closed-sourced and only available as a set of minified files, so I began implementing my own tiny version of it. At first it was just a playground, allowing me to send commands over the WebSocket and see and react to the server's response. But then it just kept growing and growing until I realized I was reimplementing the whole database SDK, so I decided to roll with it.
Openflame has been built with modularity in mind from the very beginning, even if only the database part of the SDK has been implemented for now. It's published as several independent packages, each adding the functionality of one the Firebase services.
-
This is the core component that ties everything together and offers the entry point to all the functionality. It is a required dependency.
-
Work in progress. Written from scratch, the database component offers an Observable-based API to interact with the Firebase Realtime Database. The basics have been implemented but several features are still missing. Only WebSocket communication is supported, no long polling. It supports authentication if used together with
@openflame/auth
(see below.) -
Authentication is serious business. It's really difficult to get it right but extremely easy—and painful—to mess things up. For that reason, and because I just don't have the time right now, I decided against implementing my own.
@openflame/auth
is just a wrapper around the official auth SDK, giving you access to all of its functionality but also allowing the database component to hook into authentication-related events.
-
Not implemented yet. Would allow access to Cloud Storage for Firebase.
-
Not implemented yet. Would allow access to Firebase Cloud Messaging (FCM).
import { Openflame } from '@openflame/core';
import { DataSnapshot } from '@openflame/database';
import '@openflame/core/add/database';
const openflame = new Openflame({
apiKey: "...",
authDomain: "...",
databaseURL: "...",
storageBucket: "...",
messagingSenderId: "..."
});
const newMessage$ = openflame.database.ref('/messages').childAdded$;
newMessage$.subscribe((snap: DataSnapshot) => {
console.log('Hey, new message! It says:', snap.child('content').val());
);
import { Openflame } from '@openflame/core';
import { DataSnapshot } from '@openflame/database';
import { User } from '@openflame/auth';
import '@openflame/core/add/database';
import '@openflame/core/add/auth';
import 'rxjs/add/operator/takeUntil';
import 'rxjs/add/operator/switchMap';
const openflame = new Openflame({
apiKey: "...",
authDomain: "...",
databaseURL: "...",
storageBucket: "...",
messagingSenderId: "..."
});
const private$ = openflame.auth.signIn$
.switchMap((user: User) => openflame.database
.ref(`private/${user.uid}`)
.value$
.takeUntil(openflame.auth.signOut$)
);
private$.subscribe((snap: DataSnapshot) => {
const displayName = openflame.auth.currentUser.displayName;
console.log(`Private value for ${displayName}:`, snap.val())
});
Effectively, this allows to only receive new data and data that has changed. Use your prefered local storage solution to store and retrieve the data:
import { Openflame } from '@openflame/core';
import { DataSnapshot } from '@openflame/database';
import '@openflame/core/add/database';
const openflame = new Openflame({
apiKey: "...",
authDomain: "...",
databaseURL: "...",
storageBucket: "...",
messagingSenderId: "..."
});
// You would usually retrieve this from local storage
const locallyStoredData = {
messages: {
'-Kdr27CeOcH5wT0Jq_Av': {
from: 'Bob',
content: 'Hello!'
},
'-KdvZFR-vkmbZoFcUaTh': {
from: 'Alice',
content: 'Hi there'
},
}
};
openflame.database.bootstrap(locallyStoredData);
const messagesRef = openflame.database.ref('/messages');
messagesRef.childAdded$.subscribe((snap: DataSnapshot) => {
/*
* From this point on you will only get notified of messages that you don't
* already have.
* Besides that, the Firebase server won't initially send data for /messages unless
* there's been any changes, either due to new messages that you didn't have or
* changes to the ones that you did.
* That means you don't waste bandwidth and your users are happier :)
*/
const msg = snap.val();
console.log(`New message from ${msg.from}! It says: "${msg.content}"`);
);
/*
* Let's send a new message.
* Openflame also does optimistic updates (yay!) which means the childAdded event
* will immediatley see the new message.
*/
messagesRef.push({
from: 'Josep',
content: 'Cool, huh?'
});
$ git clone https://github.com/jsayol/openflame.git
$ cd openflame
$ yarn install
$ yarn run build