├── .bowerrc
├── www
├── css
│ └── style.css
├── img
│ ├── License.pdf
│ ├── ionic.png
│ └── food-drink-17.jpg
├── js
│ ├── services.js
│ ├── app.js
│ ├── controller.js
│ └── angularfire.js
└── index.html
├── bower.json
├── .gitignore
├── ionic.project
├── scss
├── _custom.scss
└── ionic.app.scss
├── package.json
├── config.xml
├── LICENSE
├── README.md
├── gulpfile.js
└── hooks
├── after_prepare
└── 010_add_platform_class.js
└── README.md
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "www/lib"
3 | }
4 |
--------------------------------------------------------------------------------
/www/css/style.css:
--------------------------------------------------------------------------------
1 | /* Empty. Add your own CSS if you like */
2 |
--------------------------------------------------------------------------------
/www/img/License.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tutsplus/mobile-apps-with-ionic-and-firebase/HEAD/www/img/License.pdf
--------------------------------------------------------------------------------
/www/img/ionic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tutsplus/mobile-apps-with-ionic-and-firebase/HEAD/www/img/ionic.png
--------------------------------------------------------------------------------
/www/img/food-drink-17.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tutsplus/mobile-apps-with-ionic-and-firebase/HEAD/www/img/food-drink-17.jpg
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "HelloIonic",
3 | "private": "true",
4 | "devDependencies": {
5 | "ionic": "driftyco/ionic-bower#1.0.1"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Specifies intentionally untracked files to ignore when using Git
2 | # http://git-scm.com/docs/gitignore
3 |
4 | node_modules/
5 | www/lib/
6 | platforms/
7 | plugins/
8 |
--------------------------------------------------------------------------------
/ionic.project:
--------------------------------------------------------------------------------
1 | {
2 | "name": "foodbook",
3 | "app_id": "",
4 | "gulpStartupTasks": [
5 | "sass",
6 | "watch"
7 | ],
8 | "watchPatterns": [
9 | "www/**/*",
10 | "!www/lib/**/*"
11 | ]
12 | }
--------------------------------------------------------------------------------
/www/js/services.js:
--------------------------------------------------------------------------------
1 | fbook.factory('recipeService',function($firebaseArray) {
2 | var fb = new Firebase("https://firebase reference");
3 | var recs = $firebaseArray(fb);
4 | var recipeService = {
5 | all: recs,
6 | get: function(recId) {
7 | return recs.$getRecord(recId);
8 | }
9 | };
10 | return recipeService;
11 | });
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/scss/_custom.scss:
--------------------------------------------------------------------------------
1 | //background for homepage content
2 | .myContent {
3 | background: $dark;
4 | }
5 |
6 | //Remove border from header
7 | .bar-dark {
8 | border: none;
9 | }
10 |
11 | //border for text inputs on add page
12 | .myBorder {
13 | border: 2px solid black;
14 | }
15 |
16 | //recipe listing
17 | .recListing {
18 | text-align: center;
19 | a{
20 | text-decoration: none;
21 | }
22 | }
23 |
24 | //wrapping for ion-item
25 | .item {
26 | white-space: normal;
27 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "foodbook",
3 | "version": "1.0.0",
4 | "description": "foodbook: An Ionic project",
5 | "dependencies": {
6 | "angularfire": "^1.1.2",
7 | "gulp": "^3.5.6",
8 | "gulp-concat": "^2.2.0",
9 | "gulp-minify-css": "^0.3.0",
10 | "gulp-rename": "^1.2.0",
11 | "gulp-sass": "^1.3.3"
12 | },
13 | "devDependencies": {
14 | "bower": "^1.3.3",
15 | "gulp-util": "^2.2.14",
16 | "shelljs": "^0.3.0"
17 | },
18 | "cordovaPlugins": [
19 | "cordova-plugin-device",
20 | "cordova-plugin-console",
21 | "cordova-plugin-whitelist",
22 | "cordova-plugin-splashscreen",
23 | "com.ionic.keyboard"
24 | ],
25 | "cordovaPlatforms": []
26 | }
27 |
--------------------------------------------------------------------------------
/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | foodbook
4 |
5 | An Ionic Framework and Cordova project.
6 |
7 |
8 | Ionic Framework Team
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/scss/ionic.app.scss:
--------------------------------------------------------------------------------
1 | /*
2 | To customize the look and feel of Ionic, you can override the variables
3 | in ionic's _variables.scss file.
4 |
5 | For example, you might change some of the default colors:
6 |
7 | $light: #fff !default;
8 | $stable: #f8f8f8 !default;
9 | $positive: #387ef5 !default;
10 | $calm: #11c1f3 !default;
11 | $balanced: #33cd5f !default;
12 | $energized: #ffc900 !default;
13 | $assertive: #ef473a !default;
14 | $royal: #886aea !default;
15 | $dark: #444 !default;
16 | */
17 |
18 | // The path for our ionicons font files, relative to the built CSS in www/css
19 | $ionicons-font-path: "../lib/ionic/fonts" !default;
20 |
21 | // Include all of Ionic
22 | @import "www/lib/ionic/scss/ionic";
23 | @import "custom";
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Tuts+
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Mobile Apps With Ionic and Firebase][published url]
2 | ## Instructor: [Reginald Dawson][instructor url]
3 |
4 |
5 | Ionic is a framework for building cross-platform mobile apps with HTML, CSS and JavaScript. Not only does Ionic come with numerous mobile-optimized UI components, but it is also built on top of AngularJS for powerful templating and easy two-way data binding.
6 |
7 | Firebase is a perfect complement to Ionic. While Ionic is a great tool for building the front-end, Firebase can power the back-end. With Firebase, we don't need to worry about provisioning servers or building REST APIs - with a little bit of configuration, we can let Firebase do the work.
8 |
9 | In this course, we're going to build a recipe app called "Foodbook". We'll start by getting familiar with the Ionic components, then we'll take it further as we create controllers and services from AngularJS.
10 |
11 | ## Source Files Description
12 |
13 | This source repository contains the completed course project: the Foodbook recipe app that uses Firebase for storing and retrieving data.
14 |
15 |
16 | ------
17 |
18 | These are source files for the Tuts+ course: [Mobile Apps With Ionic and Firebase][published url]
19 |
20 | Available on [Tuts+](https://tutsplus.com). Teaching skills to millions worldwide.
21 |
22 | [published url]: https://code.tutsplus.com/courses/mobile-apps-with-ionic-and-firebase
23 | [instructor url]: https://tutsplus.com/authors/reginald-dawson
24 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var gutil = require('gulp-util');
3 | var bower = require('bower');
4 | var concat = require('gulp-concat');
5 | var sass = require('gulp-sass');
6 | var minifyCss = require('gulp-minify-css');
7 | var rename = require('gulp-rename');
8 | var sh = require('shelljs');
9 |
10 | var paths = {
11 | sass: ['./scss/**/*.scss']
12 | };
13 |
14 | gulp.task('default', ['sass']);
15 |
16 | gulp.task('sass', function(done) {
17 | gulp.src('./scss/ionic.app.scss')
18 | .pipe(sass({
19 | errLogToConsole: true
20 | }))
21 | .pipe(gulp.dest('./www/css/'))
22 | .pipe(minifyCss({
23 | keepSpecialComments: 0
24 | }))
25 | .pipe(rename({ extname: '.min.css' }))
26 | .pipe(gulp.dest('./www/css/'))
27 | .on('end', done);
28 | });
29 |
30 | gulp.task('watch', function() {
31 | gulp.watch(paths.sass, ['sass']);
32 | });
33 |
34 | gulp.task('install', ['git-check'], function() {
35 | return bower.commands.install()
36 | .on('log', function(data) {
37 | gutil.log('bower', gutil.colors.cyan(data.id), data.message);
38 | });
39 | });
40 |
41 | gulp.task('git-check', function(done) {
42 | if (!sh.which('git')) {
43 | console.log(
44 | ' ' + gutil.colors.red('Git is not installed.'),
45 | '\n Git, the version control system, is required to download Ionic.',
46 | '\n Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.',
47 | '\n Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.'
48 | );
49 | process.exit(1);
50 | }
51 | done();
52 | });
53 |
--------------------------------------------------------------------------------
/www/js/app.js:
--------------------------------------------------------------------------------
1 | // Ionic Starter App
2 |
3 | // angular.module is a global place for creating, registering and retrieving Angular modules
4 | // 'starter' is the name of this angular module example (also set in a
attribute in index.html)
5 | // the 2nd parameter is an array of 'requires'
6 | var fbook = angular.module('foodbook', ['ionic','firebase']);
7 |
8 | fbook.run(function($ionicPlatform) {
9 | $ionicPlatform.ready(function() {
10 | // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
11 | // for form inputs)
12 | if(window.cordova && window.cordova.plugins.Keyboard) {
13 | cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
14 | }
15 | if(window.StatusBar) {
16 | StatusBar.styleDefault();
17 | }
18 | });
19 | });
20 |
21 |
22 | fbook.config(function($stateProvider,$urlRouterProvider) {
23 |
24 | $stateProvider.state("home", {
25 | url: "/",
26 | templateUrl: "home.html"
27 | });
28 | $stateProvider.state("recList", {
29 | url: "/recList",
30 | templateUrl: "recList.html",
31 | controller: "listController"
32 | });
33 | $stateProvider.state("singleRecipe", {
34 | url: "/:id",
35 | templateUrl: "singleRec.html",
36 | controller: "recipeController"
37 | });
38 | $stateProvider.state("add", {
39 | url: "/add",
40 | templateUrl: "add.html",
41 | controller: "addController"
42 | });
43 | $stateProvider.state("del", {
44 | url: "/del",
45 | templateUrl: "delRec.html",
46 | controller: "deleteController"
47 | });
48 | $stateProvider.state("edit", {
49 | url: "/edit",
50 | templateUrl: "edit.html",
51 | controller: "editController"
52 | });
53 | $stateProvider.state("one", {
54 | url: "/edit/:id",
55 | templateUrl: "editOne.html",
56 | controller: "recipeEditController"
57 | });
58 |
59 | $urlRouterProvider.otherwise("/");
60 |
61 | });
--------------------------------------------------------------------------------
/www/js/controller.js:
--------------------------------------------------------------------------------
1 | //add controller
2 | fbook.controller('addController',function($scope,$firebaseArray,$state,recipeService){
3 | $scope.submitRecipe = function(){
4 | $scope.newRec = recipeService.all;
5 | $scope.newRec.$add({
6 | recipeName: $scope.recName,
7 | recipeIngredients: $scope.recIngredients,
8 | recipeDirections: $scope.recDirections
9 | });
10 | $state.go('home');
11 | };
12 | });
13 |
14 |
15 |
16 | fbook.controller('listController',function($scope,recipeService){
17 | $scope.recipes = recipeService.all;
18 | });
19 |
20 |
21 | fbook.controller('recipeController',function($scope,recipeService,$stateParams,$state){
22 | $scope.singleRecipe = recipeService.get($stateParams.id);
23 | $scope.ingList = $scope.singleRecipe.recipeIngredients.split(';');
24 | $scope.prepList = $scope.singleRecipe.recipeDirections.split(';');
25 | });
26 |
27 |
28 |
29 |
30 |
31 | fbook.controller('deleteController',function($scope,recipeService,$state,$firebaseArray,$ionicActionSheet){
32 | $scope.recs = recipeService.all;
33 |
34 | $scope.showDetails = function(id) {
35 | $ionicActionSheet.show({
36 | destructiveText: 'Delete',
37 | titleText: 'Sure you want to delete?',
38 | cancelText: 'Cancel',
39 | destructiveButtonClicked: function() {
40 | var rem = $scope.recs.$getRecord(id);
41 | $scope.recs.$remove(rem);
42 | return true;
43 | }
44 | });
45 | };
46 | });
47 |
48 |
49 | fbook.controller('editController',function($scope,recipeService){
50 | $scope.editRecipes = recipeService.all;
51 | });
52 |
53 |
54 | fbook.controller('recipeEditController',function($scope,recipeService,$stateParams,$state){
55 | $scope.allRecs = recipeService.all;
56 | $scope.singleRecipe = recipeService.get($stateParams.id);
57 | $scope.title = $scope.singleRecipe.recipeName;
58 | $scope.ingredients = $scope.singleRecipe.recipeIngredients;
59 | $scope.directions = $scope.singleRecipe.recipeDirections;
60 | $scope.myid = $scope.singleRecipe.$id;
61 | $scope.updateRecipe = function(id) {
62 | var ed = $scope.allRecs.$getRecord(id);
63 | ed.recipeName = $scope.title;
64 | ed.recipeIngredients = $scope.ingredients;
65 | ed.recipeDirections = $scope.directions;
66 | $scope.allRecs.$save(ed);
67 | $state.go('edit');
68 | };
69 | });
70 |
--------------------------------------------------------------------------------
/hooks/after_prepare/010_add_platform_class.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | // Add Platform Class
4 | // v1.0
5 | // Automatically adds the platform class to the body tag
6 | // after the `prepare` command. By placing the platform CSS classes
7 | // directly in the HTML built for the platform, it speeds up
8 | // rendering the correct layout/style for the specific platform
9 | // instead of waiting for the JS to figure out the correct classes.
10 |
11 | var fs = require('fs');
12 | var path = require('path');
13 |
14 | var rootdir = process.argv[2];
15 |
16 | function addPlatformBodyTag(indexPath, platform) {
17 | // add the platform class to the body tag
18 | try {
19 | var platformClass = 'platform-' + platform;
20 | var cordovaClass = 'platform-cordova platform-webview';
21 |
22 | var html = fs.readFileSync(indexPath, 'utf8');
23 |
24 | var bodyTag = findBodyTag(html);
25 | if(!bodyTag) return; // no opening body tag, something's wrong
26 |
27 | if(bodyTag.indexOf(platformClass) > -1) return; // already added
28 |
29 | var newBodyTag = bodyTag;
30 |
31 | var classAttr = findClassAttr(bodyTag);
32 | if(classAttr) {
33 | // body tag has existing class attribute, add the classname
34 | var endingQuote = classAttr.substring(classAttr.length-1);
35 | var newClassAttr = classAttr.substring(0, classAttr.length-1);
36 | newClassAttr += ' ' + platformClass + ' ' + cordovaClass + endingQuote;
37 | newBodyTag = bodyTag.replace(classAttr, newClassAttr);
38 |
39 | } else {
40 | // add class attribute to the body tag
41 | newBodyTag = bodyTag.replace('>', ' class="' + platformClass + ' ' + cordovaClass + '">');
42 | }
43 |
44 | html = html.replace(bodyTag, newBodyTag);
45 |
46 | fs.writeFileSync(indexPath, html, 'utf8');
47 |
48 | process.stdout.write('add to body class: ' + platformClass + '\n');
49 | } catch(e) {
50 | process.stdout.write(e);
51 | }
52 | }
53 |
54 | function findBodyTag(html) {
55 | // get the body tag
56 | try{
57 | return html.match(/])(.*?)>/gi)[0];
58 | }catch(e){}
59 | }
60 |
61 | function findClassAttr(bodyTag) {
62 | // get the body tag's class attribute
63 | try{
64 | return bodyTag.match(/ class=["|'](.*?)["|']/gi)[0];
65 | }catch(e){}
66 | }
67 |
68 | if (rootdir) {
69 |
70 | // go through each of the platform directories that have been prepared
71 | var platforms = (process.env.CORDOVA_PLATFORMS ? process.env.CORDOVA_PLATFORMS.split(',') : []);
72 |
73 | for(var x=0; x
21 | # Cordova Hooks
22 |
23 | This directory may contain scripts used to customize cordova commands. This
24 | directory used to exist at `.cordova/hooks`, but has now been moved to the
25 | project root. Any scripts you add to these directories will be executed before
26 | and after the commands corresponding to the directory name. Useful for
27 | integrating your own build systems or integrating with version control systems.
28 |
29 | __Remember__: Make your scripts executable.
30 |
31 | ## Hook Directories
32 | The following subdirectories will be used for hooks:
33 |
34 | after_build/
35 | after_compile/
36 | after_docs/
37 | after_emulate/
38 | after_platform_add/
39 | after_platform_rm/
40 | after_platform_ls/
41 | after_plugin_add/
42 | after_plugin_ls/
43 | after_plugin_rm/
44 | after_plugin_search/
45 | after_prepare/
46 | after_run/
47 | after_serve/
48 | before_build/
49 | before_compile/
50 | before_docs/
51 | before_emulate/
52 | before_platform_add/
53 | before_platform_rm/
54 | before_platform_ls/
55 | before_plugin_add/
56 | before_plugin_ls/
57 | before_plugin_rm/
58 | before_plugin_search/
59 | before_prepare/
60 | before_run/
61 | before_serve/
62 | pre_package/ <-- Windows 8 and Windows Phone only.
63 |
64 | ## Script Interface
65 |
66 | All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
67 |
68 | * CORDOVA_VERSION - The version of the Cordova-CLI.
69 | * CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
70 | * CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
71 | * CORDOVA_HOOK - Path to the hook that is being executed.
72 | * CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
73 |
74 | If a script returns a non-zero exit code, then the parent cordova command will be aborted.
75 |
76 |
77 | ## Writing hooks
78 |
79 | We highly recommend writting your hooks using Node.js so that they are
80 | cross-platform. Some good examples are shown here:
81 |
82 | [http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
83 |
84 |
--------------------------------------------------------------------------------
/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
58 |
59 |
60 |
83 |
84 |
85 |
86 |
97 |
98 |
99 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
--------------------------------------------------------------------------------
/www/js/angularfire.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * AngularFire is the officially supported AngularJS binding for Firebase. Firebase
3 | * is a full backend so you don't need servers to build your Angular app. AngularFire
4 | * provides you with the $firebase service which allows you to easily keep your $scope
5 | * variables in sync with your Firebase backend.
6 | *
7 | * AngularFire 1.1.2
8 | * https://github.com/firebase/angularfire/
9 | * Date: 06/25/2015
10 | * License: MIT
11 | */
12 | (function(exports) {
13 | "use strict";
14 |
15 | // Define the `firebase` module under which all AngularFire
16 | // services will live.
17 | angular.module("firebase", [])
18 | //todo use $window
19 | .value("Firebase", exports.Firebase);
20 |
21 | })(window);
22 | (function() {
23 | 'use strict';
24 | /**
25 | * Creates and maintains a synchronized list of data. This is a pseudo-read-only array. One should
26 | * not call splice(), push(), pop(), et al directly on this array, but should instead use the
27 | * $remove and $add methods.
28 | *
29 | * It is acceptable to .sort() this array, but it is important to use this in conjunction with
30 | * $watch(), so that it will be re-sorted any time the server data changes. Examples of this are
31 | * included in the $watch documentation.
32 | *
33 | * Internally, the $firebase object depends on this class to provide several $$ (i.e. protected)
34 | * methods, which it invokes to notify the array whenever a change has been made at the server:
35 | * $$added - called whenever a child_added event occurs
36 | * $$updated - called whenever a child_changed event occurs
37 | * $$moved - called whenever a child_moved event occurs
38 | * $$removed - called whenever a child_removed event occurs
39 | * $$error - called when listeners are canceled due to a security error
40 | * $$process - called immediately after $$added/$$updated/$$moved/$$removed
41 | * (assuming that these methods do not abort by returning false or null)
42 | * to splice/manipulate the array and invoke $$notify
43 | *
44 | * Additionally, these methods may be of interest to devs extending this class:
45 | * $$notify - triggers notifications to any $watch listeners, called by $$process
46 | * $$getKey - determines how to look up a record's key (returns $id by default)
47 | *
48 | * Instead of directly modifying this class, one should generally use the $extend
49 | * method to add or change how methods behave. $extend modifies the prototype of
50 | * the array class by returning a clone of $firebaseArray.
51 | *
52 | *
53 | * var ExtendedArray = $firebaseArray.$extend({
54 | * // add a new method to the prototype
55 | * foo: function() { return 'bar'; },
56 | *
57 | * // change how records are created
58 | * $$added: function(snap, prevChild) {
59 | * return new Widget(snap, prevChild);
60 | * },
61 | *
62 | * // change how records are updated
63 | * $$updated: function(snap) {
64 | * return this.$getRecord(snap.key()).update(snap);
65 | * }
66 | * });
67 | *
68 | * var list = new ExtendedArray(ref);
69 | *
70 | */
71 | angular.module('firebase').factory('$firebaseArray', ["$log", "$firebaseUtils", "$q",
72 | function($log, $firebaseUtils, $q) {
73 | /**
74 | * This constructor should probably never be called manually. It is used internally by
75 | * $firebase.$asArray().
76 | *
77 | * @param {Firebase} ref
78 | * @returns {Array}
79 | * @constructor
80 | */
81 | function FirebaseArray(ref) {
82 | if( !(this instanceof FirebaseArray) ) {
83 | return new FirebaseArray(ref);
84 | }
85 | var self = this;
86 | this._observers = [];
87 | this.$list = [];
88 | this._ref = ref;
89 | this._sync = new ArraySyncManager(this);
90 |
91 | $firebaseUtils.assertValidRef(ref, 'Must pass a valid Firebase reference ' +
92 | 'to $firebaseArray (not a string or URL)');
93 |
94 | // indexCache is a weak hashmap (a lazy list) of keys to array indices,
95 | // items are not guaranteed to stay up to date in this list (since the data
96 | // array can be manually edited without calling the $ methods) and it should
97 | // always be used with skepticism regarding whether it is accurate
98 | // (see $indexFor() below for proper usage)
99 | this._indexCache = {};
100 |
101 | // Array.isArray will not work on objects which extend the Array class.
102 | // So instead of extending the Array class, we just return an actual array.
103 | // However, it's still possible to extend FirebaseArray and have the public methods
104 | // appear on the array object. We do this by iterating the prototype and binding
105 | // any method that is not prefixed with an underscore onto the final array.
106 | $firebaseUtils.getPublicMethods(self, function(fn, key) {
107 | self.$list[key] = fn.bind(self);
108 | });
109 |
110 | this._sync.init(this.$list);
111 |
112 | return this.$list;
113 | }
114 |
115 | FirebaseArray.prototype = {
116 | /**
117 | * Create a new record with a unique ID and add it to the end of the array.
118 | * This should be used instead of Array.prototype.push, since those changes will not be
119 | * synchronized with the server.
120 | *
121 | * Any value, including a primitive, can be added in this way. Note that when the record
122 | * is created, the primitive value would be stored in $value (records are always objects
123 | * by default).
124 | *
125 | * Returns a future which is resolved when the data has successfully saved to the server.
126 | * The resolve callback will be passed a Firebase ref representing the new data element.
127 | *
128 | * @param data
129 | * @returns a promise resolved after data is added
130 | */
131 | $add: function(data) {
132 | this._assertNotDestroyed('$add');
133 | var def = $firebaseUtils.defer();
134 | var ref = this.$ref().ref().push();
135 | ref.set($firebaseUtils.toJSON(data), $firebaseUtils.makeNodeResolver(def));
136 | return def.promise.then(function() {
137 | return ref;
138 | });
139 | },
140 |
141 | /**
142 | * Pass either an item in the array or the index of an item and it will be saved back
143 | * to Firebase. While the array is read-only and its structure should not be changed,
144 | * it is okay to modify properties on the objects it contains and then save those back
145 | * individually.
146 | *
147 | * Returns a future which is resolved when the data has successfully saved to the server.
148 | * The resolve callback will be passed a Firebase ref representing the saved element.
149 | * If passed an invalid index or an object which is not a record in this array,
150 | * the promise will be rejected.
151 | *
152 | * @param {int|object} indexOrItem
153 | * @returns a promise resolved after data is saved
154 | */
155 | $save: function(indexOrItem) {
156 | this._assertNotDestroyed('$save');
157 | var self = this;
158 | var item = self._resolveItem(indexOrItem);
159 | var key = self.$keyAt(item);
160 | if( key !== null ) {
161 | var ref = self.$ref().ref().child(key);
162 | var data = $firebaseUtils.toJSON(item);
163 | return $firebaseUtils.doSet(ref, data).then(function() {
164 | self.$$notify('child_changed', key);
165 | return ref;
166 | });
167 | }
168 | else {
169 | return $firebaseUtils.reject('Invalid record; could determine key for '+indexOrItem);
170 | }
171 | },
172 |
173 | /**
174 | * Pass either an existing item in this array or the index of that item and it will
175 | * be removed both locally and in Firebase. This should be used in place of
176 | * Array.prototype.splice for removing items out of the array, as calling splice
177 | * will not update the value on the server.
178 | *
179 | * Returns a future which is resolved when the data has successfully removed from the
180 | * server. The resolve callback will be passed a Firebase ref representing the deleted
181 | * element. If passed an invalid index or an object which is not a record in this array,
182 | * the promise will be rejected.
183 | *
184 | * @param {int|object} indexOrItem
185 | * @returns a promise which resolves after data is removed
186 | */
187 | $remove: function(indexOrItem) {
188 | this._assertNotDestroyed('$remove');
189 | var key = this.$keyAt(indexOrItem);
190 | if( key !== null ) {
191 | var ref = this.$ref().ref().child(key);
192 | return $firebaseUtils.doRemove(ref).then(function() {
193 | return ref;
194 | });
195 | }
196 | else {
197 | return $firebaseUtils.reject('Invalid record; could not determine key for '+indexOrItem);
198 | }
199 | },
200 |
201 | /**
202 | * Given an item in this array or the index of an item in the array, this returns the
203 | * Firebase key (record.$id) for that record. If passed an invalid key or an item which
204 | * does not exist in this array, it will return null.
205 | *
206 | * @param {int|object} indexOrItem
207 | * @returns {null|string}
208 | */
209 | $keyAt: function(indexOrItem) {
210 | var item = this._resolveItem(indexOrItem);
211 | return this.$$getKey(item);
212 | },
213 |
214 | /**
215 | * The inverse of $keyAt, this method takes a Firebase key (record.$id) and returns the
216 | * index in the array where that record is stored. If the record is not in the array,
217 | * this method returns -1.
218 | *
219 | * @param {String} key
220 | * @returns {int} -1 if not found
221 | */
222 | $indexFor: function(key) {
223 | var self = this;
224 | var cache = self._indexCache;
225 | // evaluate whether our key is cached and, if so, whether it is up to date
226 | if( !cache.hasOwnProperty(key) || self.$keyAt(cache[key]) !== key ) {
227 | // update the hashmap
228 | var pos = self.$list.findIndex(function(rec) { return self.$$getKey(rec) === key; });
229 | if( pos !== -1 ) {
230 | cache[key] = pos;
231 | }
232 | }
233 | return cache.hasOwnProperty(key)? cache[key] : -1;
234 | },
235 |
236 | /**
237 | * The loaded method is invoked after the initial batch of data arrives from the server.
238 | * When this resolves, all data which existed prior to calling $asArray() is now cached
239 | * locally in the array.
240 | *
241 | * As a shortcut is also possible to pass resolve/reject methods directly into this
242 | * method just as they would be passed to .then()
243 | *
244 | * @param {Function} [resolve]
245 | * @param {Function} [reject]
246 | * @returns a promise
247 | */
248 | $loaded: function(resolve, reject) {
249 | var promise = this._sync.ready();
250 | if( arguments.length ) {
251 | // allow this method to be called just like .then
252 | // by passing any arguments on to .then
253 | promise = promise.then.call(promise, resolve, reject);
254 | }
255 | return promise;
256 | },
257 |
258 | /**
259 | * @returns {Firebase} the original Firebase ref used to create this object.
260 | */
261 | $ref: function() { return this._ref; },
262 |
263 | /**
264 | * Listeners passed into this method are notified whenever a new change (add, updated,
265 | * move, remove) is received from the server. Each invocation is sent an object
266 | * containing { type: 'child_added|child_updated|child_moved|child_removed',
267 | * key: 'key_of_item_affected'}
268 | *
269 | * Additionally, added and moved events receive a prevChild parameter, containing the
270 | * key of the item before this one in the array.
271 | *
272 | * This method returns a function which can be invoked to stop observing events.
273 | *
274 | * @param {Function} cb
275 | * @param {Object} [context]
276 | * @returns {Function} used to stop observing
277 | */
278 | $watch: function(cb, context) {
279 | var list = this._observers;
280 | list.push([cb, context]);
281 | // an off function for cancelling the listener
282 | return function() {
283 | var i = list.findIndex(function(parts) {
284 | return parts[0] === cb && parts[1] === context;
285 | });
286 | if( i > -1 ) {
287 | list.splice(i, 1);
288 | }
289 | };
290 | },
291 |
292 | /**
293 | * Informs $firebase to stop sending events and clears memory being used
294 | * by this array (delete's its local content).
295 | */
296 | $destroy: function(err) {
297 | if( !this._isDestroyed ) {
298 | this._isDestroyed = true;
299 | this._sync.destroy(err);
300 | this.$list.length = 0;
301 | }
302 | },
303 |
304 | /**
305 | * Returns the record for a given Firebase key (record.$id). If the record is not found
306 | * then returns null.
307 | *
308 | * @param {string} key
309 | * @returns {Object|null} a record in this array
310 | */
311 | $getRecord: function(key) {
312 | var i = this.$indexFor(key);
313 | return i > -1? this.$list[i] : null;
314 | },
315 |
316 | /**
317 | * Called to inform the array when a new item has been added at the server.
318 | * This method should return the record (an object) that will be passed into $$process
319 | * along with the add event. Alternately, the record will be skipped if this method returns
320 | * a falsey value.
321 | *
322 | * @param {object} snap a Firebase snapshot
323 | * @param {string} prevChild
324 | * @return {object} the record to be inserted into the array
325 | * @protected
326 | */
327 | $$added: function(snap/*, prevChild*/) {
328 | // check to make sure record does not exist
329 | var i = this.$indexFor($firebaseUtils.getKey(snap));
330 | if( i === -1 ) {
331 | // parse data and create record
332 | var rec = snap.val();
333 | if( !angular.isObject(rec) ) {
334 | rec = { $value: rec };
335 | }
336 | rec.$id = $firebaseUtils.getKey(snap);
337 | rec.$priority = snap.getPriority();
338 | $firebaseUtils.applyDefaults(rec, this.$$defaults);
339 |
340 | return rec;
341 | }
342 | return false;
343 | },
344 |
345 | /**
346 | * Called whenever an item is removed at the server.
347 | * This method does not physically remove the objects, but instead
348 | * returns a boolean indicating whether it should be removed (and
349 | * taking any other desired actions before the remove completes).
350 | *
351 | * @param {object} snap a Firebase snapshot
352 | * @return {boolean} true if item should be removed
353 | * @protected
354 | */
355 | $$removed: function(snap) {
356 | return this.$indexFor($firebaseUtils.getKey(snap)) > -1;
357 | },
358 |
359 | /**
360 | * Called whenever an item is changed at the server.
361 | * This method should apply the changes, including changes to data
362 | * and to $priority, and then return true if any changes were made.
363 | *
364 | * If this method returns false, then $$process will not be invoked,
365 | * which means that $$notify will not take place and no $watch events
366 | * will be triggered.
367 | *
368 | * @param {object} snap a Firebase snapshot
369 | * @return {boolean} true if any data changed
370 | * @protected
371 | */
372 | $$updated: function(snap) {
373 | var changed = false;
374 | var rec = this.$getRecord($firebaseUtils.getKey(snap));
375 | if( angular.isObject(rec) ) {
376 | // apply changes to the record
377 | changed = $firebaseUtils.updateRec(rec, snap);
378 | $firebaseUtils.applyDefaults(rec, this.$$defaults);
379 | }
380 | return changed;
381 | },
382 |
383 | /**
384 | * Called whenever an item changes order (moves) on the server.
385 | * This method should set $priority to the updated value and return true if
386 | * the record should actually be moved. It should not actually apply the move
387 | * operation.
388 | *
389 | * If this method returns false, then the record will not be moved in the array
390 | * and no $watch listeners will be notified. (When true, $$process is invoked
391 | * which invokes $$notify)
392 | *
393 | * @param {object} snap a Firebase snapshot
394 | * @param {string} prevChild
395 | * @protected
396 | */
397 | $$moved: function(snap/*, prevChild*/) {
398 | var rec = this.$getRecord($firebaseUtils.getKey(snap));
399 | if( angular.isObject(rec) ) {
400 | rec.$priority = snap.getPriority();
401 | return true;
402 | }
403 | return false;
404 | },
405 |
406 | /**
407 | * Called whenever a security error or other problem causes the listeners to become
408 | * invalid. This is generally an unrecoverable error.
409 | *
410 | * @param {Object} err which will have a `code` property and possibly a `message`
411 | * @protected
412 | */
413 | $$error: function(err) {
414 | $log.error(err);
415 | this.$destroy(err);
416 | },
417 |
418 | /**
419 | * Returns ID for a given record
420 | * @param {object} rec
421 | * @returns {string||null}
422 | * @protected
423 | */
424 | $$getKey: function(rec) {
425 | return angular.isObject(rec)? rec.$id : null;
426 | },
427 |
428 | /**
429 | * Handles placement of recs in the array, sending notifications,
430 | * and other internals. Called by the synchronization process
431 | * after $$added, $$updated, $$moved, and $$removed return a truthy value.
432 | *
433 | * @param {string} event one of child_added, child_removed, child_moved, or child_changed
434 | * @param {object} rec
435 | * @param {string} [prevChild]
436 | * @protected
437 | */
438 | $$process: function(event, rec, prevChild) {
439 | var key = this.$$getKey(rec);
440 | var changed = false;
441 | var curPos;
442 | switch(event) {
443 | case 'child_added':
444 | curPos = this.$indexFor(key);
445 | break;
446 | case 'child_moved':
447 | curPos = this.$indexFor(key);
448 | this._spliceOut(key);
449 | break;
450 | case 'child_removed':
451 | // remove record from the array
452 | changed = this._spliceOut(key) !== null;
453 | break;
454 | case 'child_changed':
455 | changed = true;
456 | break;
457 | default:
458 | throw new Error('Invalid event type: ' + event);
459 | }
460 | if( angular.isDefined(curPos) ) {
461 | // add it to the array
462 | changed = this._addAfter(rec, prevChild) !== curPos;
463 | }
464 | if( changed ) {
465 | // send notifications to anybody monitoring $watch
466 | this.$$notify(event, key, prevChild);
467 | }
468 | return changed;
469 | },
470 |
471 | /**
472 | * Used to trigger notifications for listeners registered using $watch. This method is
473 | * typically invoked internally by the $$process method.
474 | *
475 | * @param {string} event
476 | * @param {string} key
477 | * @param {string} [prevChild]
478 | * @protected
479 | */
480 | $$notify: function(event, key, prevChild) {
481 | var eventData = {event: event, key: key};
482 | if( angular.isDefined(prevChild) ) {
483 | eventData.prevChild = prevChild;
484 | }
485 | angular.forEach(this._observers, function(parts) {
486 | parts[0].call(parts[1], eventData);
487 | });
488 | },
489 |
490 | /**
491 | * Used to insert a new record into the array at a specific position. If prevChild is
492 | * null, is inserted first, if prevChild is not found, it is inserted last, otherwise,
493 | * it goes immediately after prevChild.
494 | *
495 | * @param {object} rec
496 | * @param {string|null} prevChild
497 | * @private
498 | */
499 | _addAfter: function(rec, prevChild) {
500 | var i;
501 | if( prevChild === null ) {
502 | i = 0;
503 | }
504 | else {
505 | i = this.$indexFor(prevChild)+1;
506 | if( i === 0 ) { i = this.$list.length; }
507 | }
508 | this.$list.splice(i, 0, rec);
509 | this._indexCache[this.$$getKey(rec)] = i;
510 | return i;
511 | },
512 |
513 | /**
514 | * Removes a record from the array by calling splice. If the item is found
515 | * this method returns it. Otherwise, this method returns null.
516 | *
517 | * @param {string} key
518 | * @returns {object|null}
519 | * @private
520 | */
521 | _spliceOut: function(key) {
522 | var i = this.$indexFor(key);
523 | if( i > -1 ) {
524 | delete this._indexCache[key];
525 | return this.$list.splice(i, 1)[0];
526 | }
527 | return null;
528 | },
529 |
530 | /**
531 | * Resolves a variable which may contain an integer or an item that exists in this array.
532 | * Returns the item or null if it does not exist.
533 | *
534 | * @param indexOrItem
535 | * @returns {*}
536 | * @private
537 | */
538 | _resolveItem: function(indexOrItem) {
539 | var list = this.$list;
540 | if( angular.isNumber(indexOrItem) && indexOrItem >= 0 && list.length >= indexOrItem ) {
541 | return list[indexOrItem];
542 | }
543 | else if( angular.isObject(indexOrItem) ) {
544 | // it must be an item in this array; it's not sufficient for it just to have
545 | // a $id or even a $id that is in the array, it must be an actual record
546 | // the fastest way to determine this is to use $getRecord (to avoid iterating all recs)
547 | // and compare the two
548 | var key = this.$$getKey(indexOrItem);
549 | var rec = this.$getRecord(key);
550 | return rec === indexOrItem? rec : null;
551 | }
552 | return null;
553 | },
554 |
555 | /**
556 | * Throws an error if $destroy has been called. Should be used for any function
557 | * which tries to write data back to $firebase.
558 | * @param {string} method
559 | * @private
560 | */
561 | _assertNotDestroyed: function(method) {
562 | if( this._isDestroyed ) {
563 | throw new Error('Cannot call ' + method + ' method on a destroyed $firebaseArray object');
564 | }
565 | }
566 | };
567 |
568 | /**
569 | * This method allows FirebaseArray to be inherited by child classes. Methods passed into this
570 | * function will be added onto the array's prototype. They can override existing methods as
571 | * well.
572 | *
573 | * In addition to passing additional methods, it is also possible to pass in a class function.
574 | * The prototype on that class function will be preserved, and it will inherit from
575 | * FirebaseArray. It's also possible to do both, passing a class to inherit and additional
576 | * methods to add onto the prototype.
577 | *
578 | *
579 | * var ExtendedArray = $firebaseArray.$extend({
580 | * // add a method onto the prototype that sums all items in the array
581 | * getSum: function() {
582 | * var ct = 0;
583 | * angular.forEach(this.$list, function(rec) { ct += rec.x; });
584 | * return ct;
585 | * }
586 | * });
587 | *
588 | * // use our new factory in place of $firebaseArray
589 | * var list = new ExtendedArray(ref);
590 | *
591 | *
592 | * @param {Function} [ChildClass] a child class which should inherit FirebaseArray
593 | * @param {Object} [methods] a list of functions to add onto the prototype
594 | * @returns {Function} a child class suitable for use with $firebase (this will be ChildClass if provided)
595 | * @static
596 | */
597 | FirebaseArray.$extend = function(ChildClass, methods) {
598 | if( arguments.length === 1 && angular.isObject(ChildClass) ) {
599 | methods = ChildClass;
600 | ChildClass = function(ref) {
601 | if( !(this instanceof ChildClass) ) {
602 | return new ChildClass(ref);
603 | }
604 | FirebaseArray.apply(this, arguments);
605 | return this.$list;
606 | };
607 | }
608 | return $firebaseUtils.inherit(ChildClass, FirebaseArray, methods);
609 | };
610 |
611 | function ArraySyncManager(firebaseArray) {
612 | function destroy(err) {
613 | if( !sync.isDestroyed ) {
614 | sync.isDestroyed = true;
615 | var ref = firebaseArray.$ref();
616 | ref.off('child_added', created);
617 | ref.off('child_moved', moved);
618 | ref.off('child_changed', updated);
619 | ref.off('child_removed', removed);
620 | firebaseArray = null;
621 | initComplete(err||'destroyed');
622 | }
623 | }
624 |
625 | function init($list) {
626 | var ref = firebaseArray.$ref();
627 |
628 | // listen for changes at the Firebase instance
629 | ref.on('child_added', created, error);
630 | ref.on('child_moved', moved, error);
631 | ref.on('child_changed', updated, error);
632 | ref.on('child_removed', removed, error);
633 |
634 | // determine when initial load is completed
635 | ref.once('value', function(snap) {
636 | if (angular.isArray(snap.val())) {
637 | $log.warn('Storing data using array indices in Firebase can result in unexpected behavior. See https://www.firebase.com/docs/web/guide/understanding-data.html#section-arrays-in-firebase for more information.');
638 | }
639 |
640 | initComplete(null, $list);
641 | }, initComplete);
642 | }
643 |
644 | // call initComplete(), do not call this directly
645 | function _initComplete(err, result) {
646 | if( !isResolved ) {
647 | isResolved = true;
648 | if( err ) { def.reject(err); }
649 | else { def.resolve(result); }
650 | }
651 | }
652 |
653 | var def = $firebaseUtils.defer();
654 | var created = function(snap, prevChild) {
655 | waitForResolution(firebaseArray.$$added(snap, prevChild), function(rec) {
656 | firebaseArray.$$process('child_added', rec, prevChild);
657 | });
658 | };
659 | var updated = function(snap) {
660 | var rec = firebaseArray.$getRecord($firebaseUtils.getKey(snap));
661 | if( rec ) {
662 | waitForResolution(firebaseArray.$$updated(snap), function() {
663 | firebaseArray.$$process('child_changed', rec);
664 | });
665 | }
666 | };
667 | var moved = function(snap, prevChild) {
668 | var rec = firebaseArray.$getRecord($firebaseUtils.getKey(snap));
669 | if( rec ) {
670 | waitForResolution(firebaseArray.$$moved(snap, prevChild), function() {
671 | firebaseArray.$$process('child_moved', rec, prevChild);
672 | });
673 | }
674 | };
675 | var removed = function(snap) {
676 | var rec = firebaseArray.$getRecord($firebaseUtils.getKey(snap));
677 | if( rec ) {
678 | waitForResolution(firebaseArray.$$removed(snap), function() {
679 | firebaseArray.$$process('child_removed', rec);
680 | });
681 | }
682 | };
683 |
684 | function waitForResolution(maybePromise, callback) {
685 | var promise = $q.when(maybePromise);
686 | promise.then(function(result){
687 | if (result) {
688 | callback(result);
689 | }
690 | });
691 | if (!isResolved) {
692 | resolutionPromises.push(promise);
693 | }
694 | }
695 |
696 | var resolutionPromises = [];
697 | var isResolved = false;
698 | var error = $firebaseUtils.batch(function(err) {
699 | _initComplete(err);
700 | if( firebaseArray ) {
701 | firebaseArray.$$error(err);
702 | }
703 | });
704 | var initComplete = $firebaseUtils.batch(_initComplete);
705 |
706 | var sync = {
707 | destroy: destroy,
708 | isDestroyed: false,
709 | init: init,
710 | ready: function() { return def.promise.then(function(result){
711 | return $q.all(resolutionPromises).then(function(){
712 | return result;
713 | });
714 | }); }
715 | };
716 |
717 | return sync;
718 | }
719 |
720 | return FirebaseArray;
721 | }
722 | ]);
723 |
724 | /** @deprecated */
725 | angular.module('firebase').factory('$FirebaseArray', ['$log', '$firebaseArray',
726 | function($log, $firebaseArray) {
727 | return function() {
728 | $log.warn('$FirebaseArray has been renamed. Use $firebaseArray instead.');
729 | return $firebaseArray.apply(null, arguments);
730 | };
731 | }
732 | ]);
733 | })();
734 |
735 | (function() {
736 | 'use strict';
737 | var FirebaseAuth;
738 |
739 | // Define a service which provides user authentication and management.
740 | angular.module('firebase').factory('$firebaseAuth', [
741 | '$q', '$firebaseUtils', function($q, $firebaseUtils) {
742 | /**
743 | * This factory returns an object allowing you to manage the client's authentication state.
744 | *
745 | * @param {Firebase} ref A Firebase reference to authenticate.
746 | * @return {object} An object containing methods for authenticating clients, retrieving
747 | * authentication state, and managing users.
748 | */
749 | return function(ref) {
750 | var auth = new FirebaseAuth($q, $firebaseUtils, ref);
751 | return auth.construct();
752 | };
753 | }
754 | ]);
755 |
756 | FirebaseAuth = function($q, $firebaseUtils, ref) {
757 | this._q = $q;
758 | this._utils = $firebaseUtils;
759 | if (typeof ref === 'string') {
760 | throw new Error('Please provide a Firebase reference instead of a URL when creating a `$firebaseAuth` object.');
761 | }
762 | this._ref = ref;
763 | this._initialAuthResolver = this._initAuthResolver();
764 | };
765 |
766 | FirebaseAuth.prototype = {
767 | construct: function() {
768 | this._object = {
769 | // Authentication methods
770 | $authWithCustomToken: this.authWithCustomToken.bind(this),
771 | $authAnonymously: this.authAnonymously.bind(this),
772 | $authWithPassword: this.authWithPassword.bind(this),
773 | $authWithOAuthPopup: this.authWithOAuthPopup.bind(this),
774 | $authWithOAuthRedirect: this.authWithOAuthRedirect.bind(this),
775 | $authWithOAuthToken: this.authWithOAuthToken.bind(this),
776 | $unauth: this.unauth.bind(this),
777 |
778 | // Authentication state methods
779 | $onAuth: this.onAuth.bind(this),
780 | $getAuth: this.getAuth.bind(this),
781 | $requireAuth: this.requireAuth.bind(this),
782 | $waitForAuth: this.waitForAuth.bind(this),
783 |
784 | // User management methods
785 | $createUser: this.createUser.bind(this),
786 | $changePassword: this.changePassword.bind(this),
787 | $changeEmail: this.changeEmail.bind(this),
788 | $removeUser: this.removeUser.bind(this),
789 | $resetPassword: this.resetPassword.bind(this)
790 | };
791 |
792 | return this._object;
793 | },
794 |
795 |
796 | /********************/
797 | /* Authentication */
798 | /********************/
799 |
800 | /**
801 | * Authenticates the Firebase reference with a custom authentication token.
802 | *
803 | * @param {string} authToken An authentication token or a Firebase Secret. A Firebase Secret
804 | * should only be used for authenticating a server process and provides full read / write
805 | * access to the entire Firebase.
806 | * @param {Object} [options] An object containing optional client arguments, such as configuring
807 | * session persistence.
808 | * @return {Promise