├── .gitignore ├── .eslintrc ├── package.json ├── LICENSE.md ├── README.md ├── src └── firebase-promisified.js └── dist └── firebase-promisified.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | 7 | "rules": { 8 | "quotes": [2, "single", "avoid-escape"], 9 | "no-mixed-requires": [1], 10 | "comma-spacing": [1, {"before": false, "after": true}] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firebase-promisified", 3 | "version": "0.0.1", 4 | "description": "Promisifies and Rx-ifies Firebase prototype (ES6)", 5 | "main": "dist/firebase-promisified.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/nemanja-stanarevic/firebase-promisified.git" 12 | }, 13 | "keywords": [ 14 | "Firebase", 15 | "Promise", 16 | "RxJS", 17 | "ES6" 18 | ], 19 | "author": "Nemanja Stanarevic", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/nemanja-stanarevic/firebase-promisified/issues" 23 | }, 24 | "homepage": "https://github.com/nemanja-stanarevic/firebase-promisified#readme", 25 | "devDependencies": { 26 | "eslint": "^0.22.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nemanja Stanarevic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # firebase-promisified 2 | Firebase with Promises and RxJS 3 | 4 | ## Motivation 5 | Firebase is awesome, but callbacks suck. hard. This simple module adds to the 6 | Firebase prototype so that you can write code like this: 7 | 8 | ```javascript 9 | firebase 10 | .child('users') 11 | .child(userId) 12 | .promiseUpdate({ 13 | firstName: 'foo', 14 | lastName: 'bar' }) 15 | .then( newData => ... ) 16 | .catch( error => ... ) 17 | ``` 18 | 19 | or like this: 20 | 21 | ```javascript 22 | firebase 23 | .child('userRegistration') 24 | .observe('child_added') 25 | .map(observedData => ({ 26 | id: observedData.snapshot.key(), 27 | userInfo: observedData.snapshot.val() }) ) 28 | .forEach(request => processUserRegistration(request.id, request.userInfo)); 29 | ``` 30 | 31 | ## Currently implemented Firebase functions 32 | 33 | ### Functions returning Rx.Observable 34 | * observe(eventType) 35 | 36 | ### Functions returning Promise 37 | * promiseAuthAnonymously(options) 38 | * promiseAuthWithCustomToken(token) 39 | * promiseAuthWithPassword(email, password, options) 40 | * promiseChangePassword(email, oldPassword, newPassword) 41 | * promiseResetPassword(email) 42 | * promiseCreateUser(email, password) 43 | * promiseSet(value) 44 | * promiseUpdate(value) 45 | * promisePush(value) 46 | * promiseTransaction(updateFunction) 47 | * promiseRemove() 48 | * promiseOnce(eventType) 49 | * promiseValue() 50 | 51 | ### Misc. functions 52 | * getTimestamp() (returns Firebase.ServerValue.TIMESTAMP) 53 | 54 | ## TO DOs: 55 | * Documentation 56 | * Tests 57 | * Any missing Firebase functions 58 | 59 | Pull requests are welcome. 60 | 61 | ## Usage 62 | 63 | First, install the package with npm: 64 | ``` 65 | npm install firebase-promisified --save 66 | ``` 67 | 68 | Then, simply require firebase-promisified and invoke it after Firebase and 69 | optionally Rx are in scope. You can then export a Firebase instance and use 70 | this module elsewhere in your code. 71 | 72 | ```javascript 73 | 'use strict'; 74 | 75 | const Firebase = require('firebase'); 76 | const Rx = require('rx'); 77 | const firebasePromisified = require('firebase-promisified'); 78 | 79 | // adds Rx and Promises to the Firebase prototype 80 | firebasePromisified(Firebase, Promise, Rx); 81 | 82 | module.exports = new Firebase('your firebase url'); 83 | ``` 84 | 85 | ## License 86 | MIT 87 | -------------------------------------------------------------------------------- /src/firebase-promisified.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*eslint no-proto: 0 */ 4 | 5 | // add Rx and Promises to Firebase prototype 6 | module.exports = function(Firebase, Promise, Rx) { 7 | 8 | if (Rx) { 9 | const makeCallback = function(eventType, observer) { 10 | if (eventType === 'value') { 11 | return function(snap) { 12 | observer.onNext(snap); 13 | }; 14 | } else { 15 | return function(snap, previousChildKey) { 16 | // Wrap into an object, since we can only pass one argument through 17 | observer.onNext({snapshot: snap, previousChildKey: previousChildKey}); 18 | }; 19 | } 20 | }; 21 | 22 | Firebase.prototype.__proto__.observe = function(eventType) { 23 | var query = this; 24 | return Rx.Observable.create(function(observer) { 25 | var listener = query.on(eventType, makeCallback(eventType, observer), function(error) { 26 | observer.onError(error); 27 | }); 28 | return function() { 29 | query.off(eventType, listener); 30 | }; 31 | }).publish().refCount(); 32 | }; 33 | } 34 | 35 | const makePromiseCallback = function(eventType, resolve) { 36 | return (eventType === 'value') ? 37 | (snap) => resolve({snapshot: snap}) : 38 | (snap, previousChildKey) => resolve({snapshot: snap, previousChildKey: previousChildKey}); 39 | }; 40 | 41 | Firebase.prototype.__proto__.promiseAuthAnonymously = function(options) { 42 | var fb = this; 43 | return new Promise( (resolve, reject) => 44 | fb.authAnonymously( 45 | (error, auth) => (error) ? reject(error) : resolve(auth), 46 | options 47 | ) 48 | ); 49 | }; 50 | 51 | Firebase.prototype.__proto__.promiseAuthWithCustomToken = function(token) { 52 | var fb = this; 53 | return new Promise( (resolve, reject) => 54 | fb.authWithCustomToken(token, 55 | (error, auth) => (error) ? reject(error) : resolve(auth) 56 | ) 57 | ); 58 | }; 59 | 60 | Firebase.prototype.__proto__.promiseSet = function(value) { 61 | var query = this; 62 | return new Promise( (resolve, reject) => 63 | query.set( 64 | value, 65 | (error) => (error) ? reject(error) : resolve(value) 66 | ) 67 | ); 68 | }; 69 | 70 | Firebase.prototype.__proto__.promiseUpdate = function(value) { 71 | var query = this; 72 | return new Promise( (resolve, reject) => 73 | query.update( 74 | value, 75 | (error) => (error) ? reject(error) : resolve(value) 76 | ) 77 | ); 78 | }; 79 | 80 | Firebase.prototype.__proto__.promisePush = function(value) { 81 | var query = this; 82 | return new Promise( (resolve, reject) => { 83 | const fbReference = query.push( 84 | value, 85 | (error) => (error) ? reject(error) : resolve(fbReference) 86 | ); 87 | } 88 | ); 89 | }; 90 | 91 | Firebase.prototype.__proto__.promiseTransaction = function(updateFunction) { 92 | var query = this; 93 | return new Promise( (resolve, reject) => { 94 | query.transaction( 95 | updateFunction, 96 | (error, commited, snap) => 97 | (error) ? 98 | reject(error) : 99 | (commited ? 100 | resolve(snap) : 101 | reject(new Error('transaction failed')) ), 102 | false /* applyLocally = false for isolation */ 103 | ); 104 | }); 105 | }; 106 | 107 | Firebase.prototype.__proto__.promiseRemove = function() { 108 | var query = this; 109 | return new Promise( (resolve, reject) => 110 | query.remove( 111 | (error) => (error) ? reject(error) : resolve() 112 | ) 113 | ); 114 | }; 115 | 116 | Firebase.prototype.__proto__.promiseOnce = function(eventType) { 117 | var query = this; 118 | return new Promise( (resolve, reject) => 119 | query.once(eventType, 120 | makePromiseCallback(eventType, resolve), 121 | (error) => reject(error) 122 | ) 123 | ); 124 | }; 125 | 126 | Firebase.prototype.__proto__.promiseValue = function() { 127 | var query = this; 128 | return new Promise( (resolve, reject) => { 129 | var listener = query.on('value', 130 | (snap) => { 131 | if (snap.exists()) { 132 | query.off('value', listener); 133 | resolve(snap); 134 | } 135 | }, 136 | (error) => reject(error) 137 | ); 138 | }); 139 | }; 140 | 141 | Firebase.prototype.__proto__.promiseAuthWithPassword = function(email, password, options) { 142 | var fb = this; 143 | return new Promise( (resolve, reject) => 144 | fb.authWithPassword({ 145 | email: email, 146 | password: password 147 | }, 148 | (error, authData) => error ? reject(error) : resolve(authData), 149 | options) 150 | ); 151 | }; 152 | 153 | Firebase.prototype.__proto__.promiseChangePassword = function(email, oldPassword, newPassword) { 154 | var fb = this; 155 | return new Promise( (resolve, reject) => 156 | fb.changePassword({ 157 | email: email, 158 | oldPassword: oldPassword, 159 | newPassword: newPassword 160 | }, 161 | (error) => error ? reject(error) : resolve(email)) 162 | ); 163 | }; 164 | 165 | Firebase.prototype.__proto__.promiseResetPassword = function(email) { 166 | var fb = this; 167 | return new Promise( (resolve, reject) => 168 | fb.resetPassword({ 169 | email: email 170 | }, (error) => error ? reject(error) : resolve(email) ) 171 | ); 172 | }; 173 | 174 | Firebase.prototype.__proto__.promiseCreateUser = function(email, password) { 175 | var fb = this; 176 | return new Promise( (resolve, reject) => 177 | fb.createUser({ 178 | email: email, 179 | password: password 180 | }, (error, userInfo) => error ? reject(error) : resolve(userInfo) ) 181 | ); 182 | }; 183 | 184 | Firebase.prototype.__proto__.getTimestamp = function() { 185 | return Firebase.ServerValue.TIMESTAMP; 186 | }; 187 | }; 188 | -------------------------------------------------------------------------------- /dist/firebase-promisified.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*eslint no-proto: 0 */ 4 | 5 | // add Rx and Promises to Firebase prototype 6 | module.exports = function (Firebase, Promise, Rx) { 7 | 8 | if (Rx) { 9 | (function () { 10 | var makeCallback = function makeCallback(eventType, observer) { 11 | if (eventType === 'value') { 12 | return function (snap) { 13 | observer.onNext(snap); 14 | }; 15 | } else { 16 | return function (snap, previousChildKey) { 17 | // Wrap into an object, since we can only pass one argument through 18 | observer.onNext({ snapshot: snap, previousChildKey: previousChildKey }); 19 | }; 20 | } 21 | }; 22 | 23 | Firebase.prototype.__proto__.observe = function (eventType) { 24 | var query = this; 25 | return Rx.Observable.create(function (observer) { 26 | var listener = query.on(eventType, makeCallback(eventType, observer), function (error) { 27 | observer.onError(error); 28 | }); 29 | return function () { 30 | query.off(eventType, listener); 31 | }; 32 | }).publish().refCount(); 33 | }; 34 | })(); 35 | } 36 | 37 | var makePromiseCallback = function makePromiseCallback(eventType, resolve) { 38 | return eventType === 'value' ? function (snap) { 39 | return resolve({ snapshot: snap }); 40 | } : function (snap, previousChildKey) { 41 | return resolve({ snapshot: snap, previousChildKey: previousChildKey }); 42 | }; 43 | }; 44 | 45 | Firebase.prototype.__proto__.promiseAuthAnonymously = function (options) { 46 | var fb = this; 47 | return new Promise(function (resolve, reject) { 48 | return fb.authAnonymously(function (error, auth) { 49 | return error ? reject(error) : resolve(auth); 50 | }, options); 51 | }); 52 | }; 53 | 54 | Firebase.prototype.__proto__.promiseAuthWithCustomToken = function (token) { 55 | var fb = this; 56 | return new Promise(function (resolve, reject) { 57 | return fb.authWithCustomToken(token, function (error, auth) { 58 | return error ? reject(error) : resolve(auth); 59 | }); 60 | }); 61 | }; 62 | 63 | Firebase.prototype.__proto__.promiseSet = function (value) { 64 | var query = this; 65 | return new Promise(function (resolve, reject) { 66 | return query.set(value, function (error) { 67 | return error ? reject(error) : resolve(value); 68 | }); 69 | }); 70 | }; 71 | 72 | Firebase.prototype.__proto__.promiseUpdate = function (value) { 73 | var query = this; 74 | return new Promise(function (resolve, reject) { 75 | return query.update(value, function (error) { 76 | return error ? reject(error) : resolve(value); 77 | }); 78 | }); 79 | }; 80 | 81 | Firebase.prototype.__proto__.promisePush = function (value) { 82 | var query = this; 83 | return new Promise(function (resolve, reject) { 84 | var fbReference = query.push(value, function (error) { 85 | return error ? reject(error) : resolve(fbReference); 86 | }); 87 | }); 88 | }; 89 | 90 | Firebase.prototype.__proto__.promiseTransaction = function (updateFunction) { 91 | var query = this; 92 | return new Promise(function (resolve, reject) { 93 | query.transaction(updateFunction, function (error, commited, snap) { 94 | return error ? reject(error) : commited ? resolve(snap) : reject(new Error('transaction failed')); 95 | }, false /* applyLocally = false for isolation */ 96 | ); 97 | }); 98 | }; 99 | 100 | Firebase.prototype.__proto__.promiseRemove = function () { 101 | var query = this; 102 | return new Promise(function (resolve, reject) { 103 | return query.remove(function (error) { 104 | return error ? reject(error) : resolve(); 105 | }); 106 | }); 107 | }; 108 | 109 | Firebase.prototype.__proto__.promiseOnce = function (eventType) { 110 | var query = this; 111 | return new Promise(function (resolve, reject) { 112 | return query.once(eventType, makePromiseCallback(eventType, resolve), function (error) { 113 | return reject(error); 114 | }); 115 | }); 116 | }; 117 | 118 | Firebase.prototype.__proto__.promiseValue = function () { 119 | var query = this; 120 | return new Promise(function (resolve, reject) { 121 | var listener = query.on('value', function (snap) { 122 | if (snap.exists()) { 123 | query.off('value', listener); 124 | resolve(snap); 125 | } 126 | }, function (error) { 127 | return reject(error); 128 | }); 129 | }); 130 | }; 131 | 132 | Firebase.prototype.__proto__.promiseAuthWithPassword = function (email, password, options) { 133 | var fb = this; 134 | return new Promise(function (resolve, reject) { 135 | return fb.authWithPassword({ 136 | email: email, 137 | password: password 138 | }, function (error, authData) { 139 | return error ? reject(error) : resolve(authData); 140 | }, options); 141 | }); 142 | }; 143 | 144 | Firebase.prototype.__proto__.promiseChangePassword = function (email, oldPassword, newPassword) { 145 | var fb = this; 146 | return new Promise(function (resolve, reject) { 147 | return fb.changePassword({ 148 | email: email, 149 | oldPassword: oldPassword, 150 | newPassword: newPassword 151 | }, function (error) { 152 | return error ? reject(error) : resolve(email); 153 | }); 154 | }); 155 | }; 156 | 157 | Firebase.prototype.__proto__.promiseResetPassword = function (email) { 158 | var fb = this; 159 | return new Promise(function (resolve, reject) { 160 | return fb.resetPassword({ 161 | email: email 162 | }, function (error) { 163 | return error ? reject(error) : resolve(email); 164 | }); 165 | }); 166 | }; 167 | 168 | Firebase.prototype.__proto__.promiseCreateUser = function (email, password) { 169 | var fb = this; 170 | return new Promise(function (resolve, reject) { 171 | return fb.createUser({ 172 | email: email, 173 | password: password 174 | }, function (error, userInfo) { 175 | return error ? reject(error) : resolve(userInfo); 176 | }); 177 | }); 178 | }; 179 | 180 | Firebase.prototype.__proto__.getTimestamp = function () { 181 | return Firebase.ServerValue.TIMESTAMP; 182 | }; 183 | }; 184 | --------------------------------------------------------------------------------