├── .bowerrc ├── .jshintrc ├── firebase.json ├── app ├── components │ ├── auth │ │ ├── auth.js │ │ └── auth_test.js │ ├── reverse │ │ ├── reverse-filter.js │ │ └── reverse-filter_test.js │ ├── appversion │ │ ├── appversion-directive.js │ │ └── appversion-directive_test.js │ ├── firebase.utils │ │ ├── firebase.utils_test.js │ │ └── firebase.utils.js │ ├── ngcloak │ │ └── ngcloak-decorator.js │ └── security │ │ └── security.js ├── chat │ ├── chat.html │ ├── chat_test.js │ └── chat.js ├── config_test.js ├── home │ ├── home.html │ ├── home_test.js │ └── home.js ├── app.js ├── login │ ├── login_test.js │ ├── login.html │ └── login.js ├── config.js ├── app.css ├── account │ ├── account_test.js │ ├── account.html │ └── account.js ├── index.html └── index-async.html ├── e2e-tests ├── protractor-conf.js └── scenarios.js ├── test └── lib │ ├── mock.fbutil.js │ └── mock.firebase.js ├── .travis.yml ├── bower.json ├── .gitattributes ├── karma.conf.js ├── LICENSE ├── security-rules.json ├── package.json ├── .gitignore └── README.md /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globalstrict": true, 3 | "globals": { 4 | "angular": false 5 | } 6 | } -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firebase": "INSTANCE", 3 | "public": "app", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /app/components/auth/auth.js: -------------------------------------------------------------------------------- 1 | angular.module('firebase.auth', ['firebase', 'firebase.utils']) 2 | .factory('Auth', ['$firebaseAuth', 'fbutil', function($firebaseAuth, fbutil) { 3 | return $firebaseAuth(fbutil.ref()); 4 | }]); 5 | -------------------------------------------------------------------------------- /app/components/reverse/reverse-filter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Filters */ 4 | 5 | angular.module('myApp') 6 | .filter('reverse', function() { 7 | return function(items) { 8 | return items.slice().reverse(); 9 | }; 10 | }); -------------------------------------------------------------------------------- /app/components/appversion/appversion-directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Directives */ 4 | 5 | 6 | angular.module('myApp') 7 | 8 | .directive('appVersion', ['version', function(version) { 9 | return function(scope, elm) { 10 | elm.text(version); 11 | }; 12 | }]); 13 | -------------------------------------------------------------------------------- /app/chat/chat.html: -------------------------------------------------------------------------------- 1 |

Chat

2 | 3 |
4 | 5 | 6 |
7 | 8 | 11 | -------------------------------------------------------------------------------- /e2e-tests/protractor-conf.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | allScriptsTimeout: 11000, 3 | 4 | specs: [ 5 | '*.js' 6 | ], 7 | 8 | capabilities: { 9 | 'browserName': 'chrome' 10 | }, 11 | 12 | baseUrl: 'http://localhost:8000/app/', 13 | 14 | framework: 'jasmine', 15 | 16 | jasmineNodeOpts: { 17 | defaultTimeoutInterval: 30000 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /app/components/reverse/reverse-filter_test.js: -------------------------------------------------------------------------------- 1 | describe('reverse', function() { 2 | var reverse; 3 | beforeEach(function() { 4 | module('myApp'); 5 | inject(function (reverseFilter) { 6 | reverse = reverseFilter; 7 | }); 8 | }); 9 | 10 | it('should reverse contents of an array', function() { 11 | expect(reverse([3,2,1])).toEqual([1,2,3]); 12 | }); 13 | }); -------------------------------------------------------------------------------- /test/lib/mock.fbutil.js: -------------------------------------------------------------------------------- 1 | angular.module('mock.fbutil', ['firebase.utils']) 2 | .config(function($provide) { 3 | $provide.decorator('fbutil', function($delegate) { 4 | // always use the same reference so we can reference 5 | // it in the test units 6 | $delegate.$$ref = new MockFirebase(); 7 | $delegate.ref = function() { 8 | return $delegate.$$ref; 9 | }; 10 | return $delegate; 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /app/components/firebase.utils/firebase.utils_test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | describe('fbutil', function() { 3 | beforeEach(function() { 4 | module('mock.firebase'); 5 | module('firebase.utils'); 6 | }); 7 | 8 | describe('handler', function() { 9 | it('should have tests'); 10 | }); 11 | 12 | describe('defer', function() { 13 | it('should have tests'); 14 | }); 15 | 16 | describe('ref', function() { 17 | it('should have tests'); 18 | }); 19 | }); -------------------------------------------------------------------------------- /app/config_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* verify config settings are present */ 4 | 5 | describe('config', function() { 6 | beforeEach(module('myApp.config')); 7 | 8 | it('should have a valid FBURL', inject(function(FBURL) { 9 | expect(FBURL).toMatch(/^https:\/\/[a-zA-Z0-9_-]+\.firebaseio\.com$/i); 10 | })); 11 | 12 | it('should have a valid SEMVER version', inject(function(version) { 13 | expect(version).toMatch(/^\d\d*(\.\d+)+$/); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /app/components/auth/auth_test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | describe('Auth', function() { 3 | beforeEach(function() { 4 | module('mock.firebase'); 5 | module('firebase.auth'); 6 | }); 7 | 8 | it('should return $firebaseAuth instance', function() { 9 | inject(function (Auth, $firebaseAuth) { 10 | var ref = new MockFirebase(); 11 | var testInst = $firebaseAuth(ref); 12 | expect(Auth.prototype === testInst.prototype).toBe(true); 13 | }); 14 | }); 15 | }); -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | - 0.12 5 | - stable 6 | 7 | sudo: false 8 | 9 | before_script: 10 | - export DISPLAY=:99.0 11 | - sh -e /etc/init.d/xvfb start 12 | - npm start > /dev/null & 13 | - npm run update-webdriver 14 | - sleep 1 # give server time to start 15 | 16 | script: 17 | - node_modules/.bin/karma start karma.conf.js --no-auto-watch --single-run --reporters=dots --browsers=Firefox 18 | - node_modules/.bin/protractor e2e-tests/protractor-conf.js --browser=firefox 19 | -------------------------------------------------------------------------------- /app/home/home.html: -------------------------------------------------------------------------------- 1 |

Home

2 | 3 |

Hello! You are logged in with id {{user.uid}}.

4 |

Welcome. You are not logged in

5 | 6 | {{syncedValue.$value}} 7 | 8 |

Open Forge or a new window to see this input update in real time.

9 | 10 | -------------------------------------------------------------------------------- /app/chat/chat_test.js: -------------------------------------------------------------------------------- 1 | 2 | describe('myApp.chat', function() { 3 | beforeEach(module('myApp.chat')); 4 | 5 | describe('ChatCtrl', function() { 6 | var chatCtrl, $scope; 7 | beforeEach(function() { 8 | inject(function($controller) { 9 | $scope = {}; 10 | chatCtrl = $controller('ChatCtrl', {$scope: $scope}); 11 | }); 12 | }); 13 | 14 | it('creates messages array in scope', function() { 15 | expect(Object.prototype.toString.call($scope.messages)).toBe('[object Array]'); 16 | }); 17 | }); 18 | }); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularfire-seed", 3 | "description": "A starter project for Angular + Firebase with AngularFire", 4 | "version": "1.0.0", 5 | "homepage": "https://github.com/firebase/angularfire-seed", 6 | "license": "MIT", 7 | "private": true, 8 | "dependencies": { 9 | "angular": "~1.3.8", 10 | "angular-route": "~1.3.8", 11 | "angular-loader": "~1.3.8", 12 | "angular-mocks": "~1.3.8", 13 | "html5-boilerplate": "~4.3.0", 14 | "firebase": "2.2.2", 15 | "angularfire": "~1.0.0", 16 | "mockfirebase": "~0.10.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /app/components/appversion/appversion-directive_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for directives go here */ 4 | 5 | describe('app-version directive', function() { 6 | beforeEach(function() { 7 | module('mock.firebase'); 8 | module('myApp'); 9 | }); 10 | 11 | it('should print current version', function() { 12 | module(function($provide) { 13 | $provide.constant('version', 'TEST_VER'); 14 | }); 15 | inject(function($compile, $rootScope) { 16 | var element = $compile('')($rootScope); 17 | expect(element.text()).toEqual('TEST_VER'); 18 | }); 19 | }); 20 | 21 | }); -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Declare app level module which depends on filters, and services 4 | angular.module('myApp', [ 5 | 'myApp.config', 6 | 'myApp.security', 7 | 'myApp.home', 8 | 'myApp.account', 9 | 'myApp.chat', 10 | 'myApp.login' 11 | ]) 12 | 13 | .config(['$routeProvider', function ($routeProvider) { 14 | $routeProvider.otherwise({ 15 | redirectTo: '/home' 16 | }); 17 | }]) 18 | 19 | .run(['$rootScope', 'Auth', function($rootScope, Auth) { 20 | // track status of authentication 21 | Auth.$onAuth(function(user) { 22 | $rootScope.loggedIn = !!user; 23 | }); 24 | }]); 25 | -------------------------------------------------------------------------------- /app/home/home_test.js: -------------------------------------------------------------------------------- 1 | 2 | describe('myApp.home', function() { 3 | beforeEach(module('myApp.home')); 4 | 5 | describe('HomeCtrl', function() { 6 | var homeCtrl, $scope; 7 | beforeEach(function() { 8 | module(function($provide) { 9 | // comes from routes.js in the resolve: {} attribute 10 | $provide.value('user', {uid: 'test123'}); 11 | }); 12 | inject(function($controller) { 13 | $scope = {}; 14 | homeCtrl = $controller('HomeCtrl', {$scope: $scope}); 15 | }); 16 | }); 17 | 18 | it('assigns user in scope', function() { 19 | expect(typeof $scope.user).toBe('object'); 20 | expect($scope.user.uid).toBe('test123'); 21 | }); 22 | }); 23 | }); -------------------------------------------------------------------------------- /app/login/login_test.js: -------------------------------------------------------------------------------- 1 | 2 | describe('myApp.login', function() { 3 | beforeEach(function() { 4 | module('myApp'); 5 | module('myApp.login'); 6 | }); 7 | 8 | describe('LoginCtrl', function() { 9 | var loginCtrl, $scope; 10 | beforeEach(function() { 11 | inject(function($controller) { 12 | $scope = {}; 13 | loginCtrl = $controller('LoginCtrl', {$scope: $scope}); 14 | }); 15 | }); 16 | 17 | it('should define login function', function() { 18 | expect(typeof $scope.login).toBe('function'); 19 | }); 20 | 21 | it('should define createAccount function', function() { 22 | expect(typeof $scope.createAccount).toBe('function'); 23 | }); 24 | }); 25 | }); -------------------------------------------------------------------------------- /test/lib/mock.firebase.js: -------------------------------------------------------------------------------- 1 | 2 | //todo-mock 3 | MockFirebase.prototype.orderByKey = function() { return this; }; 4 | MockFirebase.prototype.orderByPriority = function() { return this; }; 5 | MockFirebase.prototype.orderByValue = function() { return this; }; 6 | MockFirebase.prototype.orderByChild = function() { return this; }; 7 | MockFirebase.prototype.limitToLast = function() { return this; }; 8 | MockFirebase.prototype.limitToFirst = function() { return this; }; 9 | MockFirebase.prototype.startAt = function() { return this; }; 10 | MockFirebase.prototype.endAt = function() { return this; }; 11 | 12 | angular.module('mock.firebase', []) 13 | .run(function($window) { 14 | $window.Firebase = $window.MockFirebase; 15 | }) 16 | .factory('Firebase', function($window) { 17 | return $window.MockFirebase; 18 | }); -------------------------------------------------------------------------------- /app/login/login.html: -------------------------------------------------------------------------------- 1 | 2 |

Login Page

3 | 4 |
5 | 9 | 13 | 17 | 18 | 19 | 20 | 21 | 22 |

{{err}}

23 |
24 | 25 | -------------------------------------------------------------------------------- /app/chat/chat.js: -------------------------------------------------------------------------------- 1 | (function (angular) { 2 | "use strict"; 3 | 4 | var app = angular.module('myApp.chat', ['ngRoute', 'firebase.utils', 'firebase']); 5 | 6 | app.controller('ChatCtrl', ['$scope', 'messageList', function($scope, messageList) { 7 | $scope.messages = messageList; 8 | $scope.addMessage = function(newMessage) { 9 | if( newMessage ) { 10 | $scope.messages.$add({text: newMessage}); 11 | } 12 | }; 13 | }]); 14 | 15 | app.factory('messageList', ['fbutil', '$firebaseArray', function(fbutil, $firebaseArray) { 16 | var ref = fbutil.ref('messages').limitToLast(10); 17 | return $firebaseArray(ref); 18 | }]); 19 | 20 | app.config(['$routeProvider', function($routeProvider) { 21 | $routeProvider.when('/chat', { 22 | templateUrl: 'chat/chat.html', 23 | controller: 'ChatCtrl' 24 | }); 25 | }]); 26 | 27 | })(angular); -------------------------------------------------------------------------------- /app/components/ngcloak/ngcloak-decorator.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Wraps ng-cloak so that, instead of simply waiting for Angular to compile, it waits until 4 | * Auth resolves with the remote Firebase services. 5 | * 6 | * 7 | *
Authentication has resolved.
8 | *
9 | */ 10 | angular.module('myApp') 11 | .config(['$provide', function($provide) { 12 | // adapt ng-cloak to wait for auth before it does its magic 13 | $provide.decorator('ngCloakDirective', ['$delegate', 'Auth', 14 | function($delegate, Auth) { 15 | var directive = $delegate[0]; 16 | // make a copy of the old directive 17 | var _compile = directive.compile; 18 | directive.compile = function(element, attr) { 19 | Auth.$waitForAuth().then(function() { 20 | // after auth, run the original ng-cloak directive 21 | _compile.call(directive, element, attr); 22 | }); 23 | }; 24 | // return the modified directive 25 | return $delegate; 26 | }]); 27 | }]); -------------------------------------------------------------------------------- /app/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Declare app level module which depends on filters, and services 4 | angular.module('myApp.config', []) 5 | 6 | // version of this seed app is compatible with angularFire 1.0.0 7 | // see tags for other versions: https://github.com/firebase/angularFire-seed/tags 8 | .constant('version', '1.0.0') 9 | 10 | // where to redirect users if they need to authenticate (see security.js) 11 | .constant('loginRedirectPath', '/login') 12 | 13 | // your Firebase data URL goes here, no trailing slash 14 | .constant('FBURL', 'https://angularfire-seed-dev.firebaseio.com') 15 | 16 | // double check that the app has been configured before running it and blowing up space and time 17 | .run(['FBURL', '$timeout', function(FBURL, $timeout) { 18 | if( FBURL.match('//INSTANCE.firebaseio.com') ) { 19 | angular.element(document.body).html('

Please configure app/config.js before running!

'); 20 | $timeout(function() { 21 | angular.element(document.body).removeClass('hide'); 22 | }, 250); 23 | } 24 | }]); 25 | 26 | -------------------------------------------------------------------------------- /app/app.css: -------------------------------------------------------------------------------- 1 | /* app css stylesheet */ 2 | 3 | body { 4 | margin: 10px; 5 | } 6 | 7 | .menu { 8 | list-style: none; 9 | border-bottom: 0.1em solid black; 10 | margin-bottom: 2em; 11 | padding: 0 0 0.5em; 12 | } 13 | 14 | .menu:before { 15 | content: "["; 16 | } 17 | 18 | .menu:after { 19 | content: "]"; 20 | } 21 | 22 | .menu > li { 23 | display: inline; 24 | } 25 | 26 | .menu > li.hide { 27 | display: none; 28 | } 29 | 30 | .menu > li:before { 31 | content: "|"; 32 | padding-right: 0.3em; 33 | } 34 | 35 | .menu > li:nth-child(1):before { 36 | content: ""; 37 | padding: 0; 38 | } 39 | 40 | p.status { 41 | color: red; 42 | } 43 | 44 | p.status.good, p.good { 45 | color: green; 46 | } 47 | 48 | form label { 49 | display: block; 50 | margin: 15px 0; 51 | } 52 | 53 | form label span { 54 | display: block; 55 | font-weight: bold; 56 | } 57 | 58 | .error { 59 | color: red; 60 | } 61 | 62 | fieldset { 63 | float: left; 64 | margin-right: 25px; 65 | padding: 25px; 66 | } 67 | 68 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 69 | display: none !important; 70 | } 71 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config){ 2 | config.set({ 3 | 4 | basePath : './', 5 | 6 | files : [ 7 | 'app/bower_components/angular/angular.js', 8 | 'app/bower_components/angular-route/angular-route.js', 9 | 'app/bower_components/angular-mocks/angular-mocks.js', 10 | 'app/bower_components/mockfirebase/browser/mockfirebase.js', 11 | 'app/bower_components/angularfire/dist/angularfire.js', 12 | 'test/lib/**/*.js', 13 | 'app/app.js', 14 | 'app/config.js', 15 | 'app/components/**/*.js', 16 | 'app/account/**/*.js', 17 | 'app/chat/**/*.js', 18 | 'app/home/**/*.js', 19 | 'app/login/**/*.js', 20 | 'app/config_test.js' 21 | ], 22 | 23 | autoWatch : true, 24 | 25 | frameworks: ['jasmine'], 26 | 27 | browsers : ['Chrome'], 28 | 29 | plugins : [ 30 | 'karma-chrome-launcher', 31 | 'karma-firefox-launcher', 32 | 'karma-jasmine', 33 | 'karma-junit-reporter' 34 | ], 35 | 36 | junitReporter : { 37 | outputFile: 'test_out/unit.xml', 38 | suite: 'unit' 39 | } 40 | 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2014 Google, Inc. http://angularjs.org 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 | 23 | -------------------------------------------------------------------------------- /app/account/account_test.js: -------------------------------------------------------------------------------- 1 | 2 | describe('myApp.account', function() { 3 | beforeEach(function() { 4 | module('myApp'); 5 | module('myApp.account'); 6 | }); 7 | 8 | describe('AccountCtrl', function() { 9 | var accountCtrl, $scope; 10 | beforeEach(function() { 11 | module(function($provide) { 12 | // comes from routes.js in the resolve: {} attribute 13 | $provide.value('user', {uid: 'test123'}); 14 | }); 15 | 16 | inject(function($controller) { 17 | $scope = {}; 18 | accountCtrl = $controller('AccountCtrl', {$scope: $scope}); 19 | }); 20 | }); 21 | 22 | it('should define logout method', function() { 23 | expect(typeof $scope.logout).toBe('function'); 24 | }); 25 | 26 | it('should define changePassword method', function() { 27 | expect(typeof $scope.changePassword).toBe('function'); 28 | }); 29 | 30 | it('should define changeEmail method', function() { 31 | expect(typeof $scope.changeEmail).toBe('function'); 32 | }); 33 | 34 | it('should define clear method', function() { 35 | expect(typeof $scope.clear).toBe('function'); 36 | }); 37 | }); 38 | }); -------------------------------------------------------------------------------- /app/home/home.js: -------------------------------------------------------------------------------- 1 | (function(angular) { 2 | "use strict"; 3 | 4 | var app = angular.module('myApp.home', ['firebase.auth', 'firebase', 'firebase.utils', 'ngRoute']); 5 | 6 | app.controller('HomeCtrl', ['$scope', 'fbutil', 'user', '$firebaseObject', 'FBURL', function ($scope, fbutil, user, $firebaseObject, FBURL) { 7 | $scope.syncedValue = $firebaseObject(fbutil.ref('syncedValue')); 8 | $scope.user = user; 9 | $scope.FBURL = FBURL; 10 | }]); 11 | 12 | app.config(['$routeProvider', function ($routeProvider) { 13 | $routeProvider.when('/home', { 14 | templateUrl: 'home/home.html', 15 | controller: 'HomeCtrl', 16 | resolve: { 17 | // forces the page to wait for this promise to resolve before controller is loaded 18 | // the controller can then inject `user` as a dependency. This could also be done 19 | // in the controller, but this makes things cleaner (controller doesn't need to worry 20 | // about auth status or timing of accessing data or displaying elements) 21 | user: ['Auth', function (Auth) { 22 | return Auth.$waitForAuth(); 23 | }] 24 | } 25 | }); 26 | }]); 27 | 28 | })(angular); 29 | 30 | -------------------------------------------------------------------------------- /security-rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | ".read": false, 4 | ".write": false, 5 | "syncedValue": { 6 | ".read": true, 7 | ".write": true, 8 | ".validate": "newData.isString() && newData.val().length <= 100" 9 | }, 10 | "messages": { 11 | ".read": true, 12 | "$message": { 13 | ".write": true, 14 | ".validate": "newData.hasChildren(['text'])", 15 | "text": { 16 | ".validate": "newData.isString() && newData.val().length <= 1000" 17 | }, 18 | "$other": { 19 | ".validate": false 20 | } 21 | } 22 | }, 23 | "users": { 24 | "$user": { 25 | ".read": "auth.uid === $user", 26 | ".write": "auth.uid === $user && (!newData.exists() || newData.hasChildren())", 27 | "name": { 28 | ".validate": "newData.isString() && newData.val().length <= 2000" 29 | }, 30 | "email": { 31 | ".validate": "newData.isString() && newData.val().length <= 2000" 32 | }, 33 | "$other": { 34 | ".validate": false 35 | } 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularfire-seed", 3 | "description": "A starter project for Angular + Firebase with AngularFire", 4 | "version": "1.0.0", 5 | "homepage": "https://github.com/firebase/angularfire-seed", 6 | "repository": "https://github.com/angular/angular-seed", 7 | "private": true, 8 | "license": "MIT", 9 | "devDependencies": { 10 | "karma": "~0.10", 11 | "protractor": "~0.20.1", 12 | "http-server": "^0.6.1", 13 | "bower": "^1.3.1", 14 | "shelljs": "^0.2.6", 15 | "karma-junit-reporter": "^0.2.2" 16 | }, 17 | "scripts": { 18 | "postinstall": "bower install", 19 | 20 | "prestart": "npm install", 21 | "start": "http-server -a localhost -p 8000", 22 | 23 | "pretest": "npm install", 24 | "test": "karma start karma.conf.js", 25 | "test-single-run": "karma start karma.conf.js --single-run", 26 | 27 | "preupdate-webdriver": "npm install", 28 | "update-webdriver": "webdriver-manager update", 29 | 30 | "preprotractor": "npm run update-webdriver", 31 | "protractor": "protractor e2e-tests/protractor-conf.js", 32 | 33 | "update-index-async": "node -e \"require('shelljs/global'); sed('-i', /\\/\\/@@NG_LOADER_START@@[\\s\\S]*\\/\\/@@NG_LOADER_END@@/, '//@@NG_LOADER_START@@\\n' + cat('app/bower_components/angular-loader/angular-loader.min.js') + '\\n//@@NG_LOADER_END@@', 'app/index-async.html');\"" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/account/account.html: -------------------------------------------------------------------------------- 1 | 2 |

Account

3 | 4 |
5 |
6 | Profile 7 | 8 | 12 | 13 | 17 |
18 |
19 | 20 |
21 |
22 | Change Password 23 | 24 | 28 | 29 | 33 | 34 | 38 | 39 | 40 | 41 |

{{err}}

42 |

{{msg}}

43 |
44 |
45 | 46 |
47 |
48 | Change Email 49 | 50 | 54 | 55 | 59 | 60 | 61 | 62 |

{{emailerr}}

63 |

{{emailmsg}}

64 |
65 |
66 | 67 | -------------------------------------------------------------------------------- /app/components/firebase.utils/firebase.utils.js: -------------------------------------------------------------------------------- 1 | 2 | // a simple wrapper on Firebase and AngularFire to simplify deps and keep things DRY 3 | angular.module('firebase.utils', ['firebase', 'myApp.config']) 4 | .factory('fbutil', ['$window', 'FBURL', '$q', function($window, FBURL, $q) { 5 | "use strict"; 6 | 7 | var utils = { 8 | // convert a node or Firebase style callback to a future 9 | handler: function(fn, context) { 10 | return utils.defer(function(def) { 11 | fn.call(context, function(err, result) { 12 | if( err !== null ) { def.reject(err); } 13 | else { def.resolve(result); } 14 | }); 15 | }); 16 | }, 17 | 18 | // abstract the process of creating a future/promise 19 | defer: function(fn, context) { 20 | var def = $q.defer(); 21 | fn.call(context, def); 22 | return def.promise; 23 | }, 24 | 25 | ref: firebaseRef 26 | }; 27 | 28 | return utils; 29 | 30 | function pathRef(args) { 31 | for (var i = 0; i < args.length; i++) { 32 | if (angular.isArray(args[i])) { 33 | args[i] = pathRef(args[i]); 34 | } 35 | else if( typeof args[i] !== 'string' ) { 36 | throw new Error('Argument '+i+' to firebaseRef is not a string: '+args[i]); 37 | } 38 | } 39 | return args.join('/'); 40 | } 41 | 42 | /** 43 | * Example: 44 | * 45 | * function(firebaseRef) { 46 | * var ref = firebaseRef('path/to/data'); 47 | * } 48 | * 49 | * 50 | * @function 51 | * @name firebaseRef 52 | * @param {String|Array...} path relative path to the root folder in Firebase instance 53 | * @return a Firebase instance 54 | */ 55 | function firebaseRef(path) { 56 | var ref = new $window.Firebase(FBURL); 57 | var args = Array.prototype.slice.call(arguments); 58 | if( args.length ) { 59 | ref = ref.child(pathRef(args)); 60 | } 61 | return ref; 62 | } 63 | }]); 64 | 65 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | My AngularJS App 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 22 | 23 |
24 | 25 |
AngularFire-seed v
26 | 27 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/account/account.js: -------------------------------------------------------------------------------- 1 | (function (angular) { 2 | "use strict"; 3 | 4 | var app = angular.module('myApp.account', ['firebase', 'firebase.utils', 'firebase.auth', 'ngRoute']); 5 | 6 | app.controller('AccountCtrl', ['$scope', 'Auth', 'fbutil', 'user', '$location', '$firebaseObject', 7 | function($scope, Auth, fbutil, user, $location, $firebaseObject) { 8 | var unbind; 9 | // create a 3-way binding with the user profile object in Firebase 10 | var profile = $firebaseObject(fbutil.ref('users', user.uid)); 11 | profile.$bindTo($scope, 'profile').then(function(ub) { unbind = ub; }); 12 | 13 | // expose logout function to scope 14 | $scope.logout = function() { 15 | if( unbind ) { unbind(); } 16 | profile.$destroy(); 17 | Auth.$unauth(); 18 | $location.path('/login'); 19 | }; 20 | 21 | $scope.changePassword = function(pass, confirm, newPass) { 22 | resetMessages(); 23 | if( !pass || !confirm || !newPass ) { 24 | $scope.err = 'Please fill in all password fields'; 25 | } 26 | else if( newPass !== confirm ) { 27 | $scope.err = 'New pass and confirm do not match'; 28 | } 29 | else { 30 | Auth.$changePassword({email: profile.email, oldPassword: pass, newPassword: newPass}) 31 | .then(function() { 32 | $scope.msg = 'Password changed'; 33 | }, function(err) { 34 | $scope.err = err; 35 | }) 36 | } 37 | }; 38 | 39 | $scope.clear = resetMessages; 40 | 41 | $scope.changeEmail = function(pass, newEmail) { 42 | resetMessages(); 43 | var oldEmail = profile.email; 44 | Auth.$changeEmail({oldEmail: oldEmail, newEmail: newEmail, password: pass}) 45 | .then(function() { 46 | // store the new email address in the user's profile 47 | return fbutil.handler(function(done) { 48 | fbutil.ref('users', user.uid, 'email').set(newEmail, done); 49 | }); 50 | }) 51 | .then(function() { 52 | $scope.emailmsg = 'Email changed'; 53 | }, function(err) { 54 | $scope.emailerr = err; 55 | }); 56 | }; 57 | 58 | function resetMessages() { 59 | $scope.err = null; 60 | $scope.msg = null; 61 | $scope.emailerr = null; 62 | $scope.emailmsg = null; 63 | } 64 | } 65 | ]); 66 | 67 | app.config(['$routeProvider', function($routeProvider) { 68 | // require user to be authenticated before they can access this page 69 | // this is handled by the .whenAuthenticated method declared in 70 | // components/router/router.js 71 | $routeProvider.whenAuthenticated('/account', { 72 | templateUrl: 'account/account.html', 73 | controller: 'AccountCtrl' 74 | }) 75 | }]); 76 | 77 | })(angular); -------------------------------------------------------------------------------- /app/login/login.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | angular.module('myApp.login', ['firebase.utils', 'firebase.auth', 'ngRoute']) 3 | 4 | .config(['$routeProvider', function($routeProvider) { 5 | $routeProvider.when('/login', { 6 | controller: 'LoginCtrl', 7 | templateUrl: 'login/login.html' 8 | }); 9 | }]) 10 | 11 | .controller('LoginCtrl', ['$scope', 'Auth', '$location', 'fbutil', function($scope, Auth, $location, fbutil) { 12 | $scope.email = null; 13 | $scope.pass = null; 14 | $scope.confirm = null; 15 | $scope.createMode = false; 16 | 17 | $scope.login = function(email, pass) { 18 | $scope.err = null; 19 | Auth.$authWithPassword({ email: email, password: pass }, {rememberMe: true}) 20 | .then(function(/* user */) { 21 | $location.path('/account'); 22 | }, function(err) { 23 | $scope.err = errMessage(err); 24 | }); 25 | }; 26 | 27 | $scope.createAccount = function() { 28 | $scope.err = null; 29 | if( assertValidAccountProps() ) { 30 | var email = $scope.email; 31 | var pass = $scope.pass; 32 | // create user credentials in Firebase auth system 33 | Auth.$createUser({email: email, password: pass}) 34 | .then(function() { 35 | // authenticate so we have permission to write to Firebase 36 | return Auth.$authWithPassword({ email: email, password: pass }); 37 | }) 38 | .then(function(user) { 39 | // create a user profile in our data store 40 | var ref = fbutil.ref('users', user.uid); 41 | return fbutil.handler(function(cb) { 42 | ref.set({email: email, name: name||firstPartOfEmail(email)}, cb); 43 | }); 44 | }) 45 | .then(function(/* user */) { 46 | // redirect to the account page 47 | $location.path('/account'); 48 | }, function(err) { 49 | $scope.err = errMessage(err); 50 | }); 51 | } 52 | }; 53 | 54 | function assertValidAccountProps() { 55 | if( !$scope.email ) { 56 | $scope.err = 'Please enter an email address'; 57 | } 58 | else if( !$scope.pass || !$scope.confirm ) { 59 | $scope.err = 'Please enter a password'; 60 | } 61 | else if( $scope.createMode && $scope.pass !== $scope.confirm ) { 62 | $scope.err = 'Passwords do not match'; 63 | } 64 | return !$scope.err; 65 | } 66 | 67 | function errMessage(err) { 68 | return angular.isObject(err) && err.code? err.code : err + ''; 69 | } 70 | 71 | function firstPartOfEmail(email) { 72 | return ucfirst(email.substr(0, email.indexOf('@'))||''); 73 | } 74 | 75 | function ucfirst (str) { 76 | // inspired by: http://kevin.vanzonneveld.net 77 | str += ''; 78 | var f = str.charAt(0).toUpperCase(); 79 | return f + str.substr(1); 80 | } 81 | }]); -------------------------------------------------------------------------------- /e2e-tests/scenarios.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ 4 | describe('my app', function() { 5 | 6 | browser.get('index.html'); 7 | 8 | it('should automatically redirect to /home when location hash/fragment is empty', function() { 9 | expect(browser.getLocationAbsUrl()).toMatch("/home"); 10 | }); 11 | 12 | describe('home', function() { 13 | 14 | beforeEach(function() { 15 | browser.get('index.html#/home'); 16 | }); 17 | 18 | 19 | it('should render home when user navigates to /home', function() { 20 | expect(element.all(by.css('[ng-view] h2')).first().getText()). 21 | toMatch(/Home/); 22 | }); 23 | 24 | }); 25 | 26 | 27 | describe('chat', function() { 28 | beforeEach(function() { 29 | browser.get('index.html#/chat'); 30 | }); 31 | 32 | it('should render chat when user navigates to /chat', function() { 33 | expect(element.all(by.css('[ng-view] h2')).first().getText()). 34 | toMatch(/Chat/); 35 | }); 36 | }); 37 | 38 | describe('account', function() { 39 | it('should redirect to /login if not logged in', function() { 40 | browser.get('index.html#/account'); 41 | expect(browser.getLocationAbsUrl()).toMatch('/login'); 42 | }); 43 | 44 | //todo https://github.com/firebase/angularFire-seed/issues/41 45 | }); 46 | 47 | describe('login', function() { 48 | beforeEach(function() { 49 | browser.get('index.html#/login'); 50 | }); 51 | 52 | it('should render login when user navigates to /login', function() { 53 | expect(element.all(by.css('[ng-view] h2')).first().getText()).toMatch(/Login Page/); 54 | }); 55 | 56 | // 57 | // afterEach(function() { 58 | // angularFireLogout(); 59 | // }); 60 | // 61 | 62 | //todo https://github.com/firebase/angularFire-seed/issues/41 63 | // 64 | // it('should show error if no email', function() { 65 | // expect(element('p.error').text()).toEqual(''); 66 | // input('email').enter(''); 67 | // input('pass').enter('test123'); 68 | // element('button[ng-click="login()"]').click(); 69 | // expect(element('p.error').text()).not().toEqual(''); 70 | // }); 71 | // 72 | // it('should show error if no password', function() { 73 | // expect(element('p.error').text()).toEqual(''); 74 | // input('email').enter('test@test.com'); 75 | // input('pass').enter(''); 76 | // element('button[ng-click="login()"]').click(); 77 | // expect(element('p.error').text()).not().toEqual('') 78 | // }); 79 | // 80 | // it('should log in with valid fields', function() { 81 | // input('email').enter('test@test.com'); 82 | // input('pass').enter('test123'); 83 | // element('button[ng-click="login()"]').click(); 84 | // expect(element('p.error').text()).toEqual(''); 85 | // }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /app/components/security/security.js: -------------------------------------------------------------------------------- 1 | (function (angular) { 2 | "use strict"; 3 | 4 | // when $routeProvider.whenAuthenticated() is called, the path is stored in this list 5 | // to be used by authRequired() in the services below 6 | var securedRoutes = []; 7 | 8 | angular.module('myApp.security', ['ngRoute', 'firebase.auth', 'myApp.config']) 9 | 10 | .config(['$routeProvider', function ($routeProvider) { 11 | // routes which are not in our map are redirected to /home 12 | //$routeProvider.otherwise({redirectTo: '/home'}); 13 | }]) 14 | 15 | /** 16 | * Adds a special `whenAuthenticated` method onto $routeProvider. This special method, 17 | * when called, waits for auth status to be resolved asynchronously, and then fails/redirects 18 | * if the user is not properly authenticated. 19 | * 20 | * The promise either resolves to the authenticated user object and makes it available to 21 | * dependency injection (see AuthCtrl), or rejects the promise if user is not logged in, 22 | * forcing a redirect to the /login page 23 | */ 24 | .config(['$routeProvider', function ($routeProvider) { 25 | // credits for this idea: https://groups.google.com/forum/#!msg/angular/dPr9BpIZID0/MgWVluo_Tg8J 26 | // unfortunately, a decorator cannot be use here because they are not applied until after 27 | // the .config calls resolve, so they can't be used during route configuration, so we have 28 | // to hack it directly onto the $routeProvider object 29 | $routeProvider.whenAuthenticated = function (path, route) { 30 | securedRoutes.push(path); // store all secured routes for use with authRequired() below 31 | route.resolve = route.resolve || {}; 32 | route.resolve.user = ['Auth', function (Auth) { 33 | return Auth.$requireAuth(); 34 | }]; 35 | $routeProvider.when(path, route); 36 | return this; 37 | } 38 | }]) 39 | 40 | /** 41 | * Apply some route security. Any route's resolve method can reject the promise with 42 | * { authRequired: true } to force a redirect. This method enforces that and also watches 43 | * for changes in auth status which might require us to navigate away from a path 44 | * that we can no longer view. 45 | */ 46 | .run(['$rootScope', '$location', 'Auth', 'loginRedirectPath', 47 | function ($rootScope, $location, Auth, loginRedirectPath) { 48 | // watch for login status changes and redirect if appropriate 49 | Auth.$onAuth(check); 50 | 51 | // some of our routes may reject resolve promises with the special {authRequired: true} error 52 | // this redirects to the login page whenever that is encountered 53 | $rootScope.$on("$routeChangeError", function (e, next, prev, err) { 54 | if (err === "AUTH_REQUIRED") { 55 | $location.path(loginRedirectPath); 56 | } 57 | }); 58 | 59 | function check(user) { 60 | if (!user && authRequired($location.path())) { 61 | console.log('check failed', user, $location.path()); //debug 62 | $location.path(loginRedirectPath); 63 | } 64 | } 65 | 66 | function authRequired(path) { 67 | console.log('authRequired?', path, securedRoutes.indexOf(path)); //debug 68 | return securedRoutes.indexOf(path) !== -1; 69 | } 70 | } 71 | ]); 72 | 73 | })(angular); 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpathgi 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | /.idea 217 | 218 | #Test result ignore 219 | test_out/unit.xml 220 | test_out/e2e.xml 221 | 222 | 223 | ############# 224 | ## Node/Npm/Bower 225 | ############# 226 | 227 | node_modules/ 228 | app/bower_components/ -------------------------------------------------------------------------------- /app/index-async.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 65 | My AngularJS App 66 | 67 | 68 | 69 | 70 | 76 | 77 |
78 | 79 |
Angular seed app: v
80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Status: Archived 2 | This repository has been archived and is no longer maintained. 3 | 4 | ![status: inactive](https://img.shields.io/badge/status-inactive-red.svg) 5 | 6 | # angularfire-seed — the seed for Angular+Firebase apps 7 | 8 | ## Disclaimer: This project is for legacy Firebase apps (version < 2.0). If you created an app using console.firebase.google.com this project will not work for you. Please see the [main AngularFire repo](https://github.com/firebase/angularfire/) for documentation on setting up an application with the new setup. 9 | 10 | [![Build Status](https://travis-ci.org/firebase/angularfire-seed.svg)](https://travis-ci.org/firebase/angularfire-seed) 11 | 12 | This derivative of [angular-seed](https://github.com/angular/angular-seed) is an application 13 | skeleton for a typical [AngularFire](http://angularfire.com/) web app. You can use it to quickly 14 | bootstrap your Angular + Firebase projects. 15 | 16 | The seed is preconfigured to install the Angular framework, Firebase, AngularFire, and a bundle of 17 | development and testing tools. 18 | 19 | The seed app doesn't do much, but does demonstrate the basics of Angular + Firebase development, 20 | including: 21 | * binding synchronized objects 22 | * binding synchronized arrays 23 | * authentication 24 | * route security 25 | * basic account management 26 | 27 | ## How to use angularfire-seed 28 | 29 | Other than one additional configuration step (specifying your Firebase database URL), this setup is nearly 30 | identical to angular-seed. 31 | 32 | ### Prerequisites 33 | 34 | You need git to clone the angularfire-seed repository. You can get it from 35 | [http://git-scm.com/](http://git-scm.com/). 36 | 37 | We also use a number of node.js tools to initialize and test angularfire-seed. You must have node.js and 38 | its package manager (npm) installed. You can get them from [http://nodejs.org/](http://nodejs.org/). 39 | 40 | ### Clone angularfire-seed 41 | 42 | Clone the angularfire-seed repository using [git][git]: 43 | 44 | ``` 45 | git clone https://github.com/firebase/angularfire-seed.git 46 | cd angularfire-seed 47 | ``` 48 | 49 | ### Install Dependencies 50 | 51 | We have two kinds of dependencies in this project: tools and angular framework code. The tools help 52 | us manage and test the application. 53 | 54 | * We get the tools we depend upon via `npm`, the [node package manager][npm]. 55 | * We get the angular code via `bower`, a [client-side code package manager][bower]. 56 | 57 | We have preconfigured `npm` to automatically run `bower` so we can simply do: 58 | 59 | ``` 60 | npm install 61 | ``` 62 | 63 | Behind the scenes this will also call `bower install`. You should find that you have two new 64 | folders in your project. 65 | 66 | * `node_modules` - contains the npm packages for the tools we need 67 | * `app/bower_components` - contains the angular framework files 68 | 69 | *Note that the `bower_components` folder would normally be installed in the root folder but 70 | angularfire-seed changes this location through the `.bowerrc` file. Putting it in the app folder makes 71 | it easier to serve the files by a webserver.* 72 | 73 | ### Configure the Application 74 | 75 | 1. Open `app/config.js` and set the value of FBURL constant to your Firebase database URL 76 | 1. Go to your Firebase dashboard and enable email/password authentication under the Auth tab 77 | 1. Copy/paste the contents of `security-rules.json` into your Security tab, which is also under your Firebase dashboard. 78 | 79 | ### Run the Application 80 | 81 | We have preconfigured the project with a simple development web server. The simplest way to start 82 | this server is: 83 | 84 | ``` 85 | npm start 86 | ``` 87 | 88 | Now browse to the app at `http://localhost:8000/app/index.html`. 89 | 90 | ## Directory Layout 91 | 92 | app/ --> all of the files to be used in production 93 | app.js --> application 94 | config.js --> where you configure Firebase and auth options 95 | app.css --> default stylesheet 96 | index.html --> app layout file (the main html template file of the app) 97 | index-async.html --> just like index.html, but loads js files asynchronously 98 | components/ --> javascript files 99 | appversion/ --> The app-version directive 100 | auth/ --> A wrapper on the `$firebaseAuth` service 101 | firebase.utils/ --> Some convenience methods for dealing with Firebase event callbacks and refs 102 | ngcloak/ --> A decorator on the ngCloak directive so that it works with auth 103 | reverse/ --> A filter to reverse order of arrays 104 | security/ --> route-based security tools (adds the $routeProvider.whenAuthenticated() method and redirects) 105 | account/ --> the account view 106 | chat/ --> the chat view 107 | home/ --> the default view 108 | login/ --> login screen 109 | e2e-tests/ --> protractor end-to-end tests 110 | test/lib/ --> utilities and mocks for test units 111 | 112 | ## Testing 113 | 114 | There are two kinds of tests in the angularfire-seed application: Unit tests and End to End tests. 115 | 116 | ### Running Unit Tests 117 | 118 | The angularfire-seed app comes preconfigured with unit tests. These are written in 119 | [Jasmine][jasmine], which we run with the [Karma Test Runner][karma]. We provide a Karma 120 | configuration file to run them. 121 | 122 | * the configuration is found at `test/karma.conf.js` 123 | * the unit tests are found in `test/unit/` 124 | 125 | The easiest way to run the unit tests is to use the supplied npm script: 126 | 127 | ``` 128 | npm test 129 | ``` 130 | 131 | This script will start the Karma test runner to execute the unit tests. Moreover, Karma will sit and 132 | watch the source and test files for changes and then re-run the tests whenever any of them change. 133 | This is the recommended strategy; if your unit tests are being run every time you save a file then 134 | you receive instant feedback on any changes that break the expected code functionality. 135 | 136 | You can also ask Karma to do a single run of the tests and then exit. This is useful if you want to 137 | check that a particular version of the code is operating as expected. The project contains a 138 | predefined script to do this: 139 | 140 | ``` 141 | npm run test-single-run 142 | ``` 143 | 144 | 145 | ### End to end testing 146 | 147 | The angularfire-seed app comes with end-to-end tests, again written in [Jasmine][jasmine]. These tests 148 | are run with the [Protractor][protractor] End-to-End test runner. It uses native events and has 149 | special features for Angular applications. 150 | 151 | * the configuration is found at `e2e-tests/protractor-conf.js` 152 | * the end-to-end tests are found in `e2e-tests/scenarios.js` 153 | 154 | Protractor simulates interaction with our web app and verifies that the application responds 155 | correctly. Therefore, our web server needs to be serving up the application, so that Protractor 156 | can interact with it. 157 | 158 | ``` 159 | npm start 160 | ``` 161 | 162 | In addition, since Protractor is built upon WebDriver we need to install this. The angularfire-seed 163 | project comes with a predefined script to do this: 164 | 165 | ``` 166 | npm run update-webdriver 167 | ``` 168 | 169 | This will download and install the latest version of the stand-alone WebDriver tool. 170 | 171 | Once you have ensured that the development web server hosting our application is up and running 172 | and WebDriver is updated, you can run the end-to-end tests using the supplied npm script: 173 | 174 | ``` 175 | npm run protractor 176 | ``` 177 | 178 | This script will execute the end-to-end tests against the application being hosted on the 179 | development server. 180 | 181 | 182 | ## Updating Dependencies 183 | 184 | Previously we recommended that you merge in changes to angularfire-seed into your own fork of the project. 185 | Now that the angular framework library code and tools are acquired through package managers (npm and 186 | bower) you can use these tools instead to update the dependencies. 187 | 188 | You can update the tool dependencies by running: 189 | 190 | ``` 191 | npm update 192 | ``` 193 | 194 | This will find the latest versions that match the version ranges specified in the `package.json` file. 195 | 196 | You can update the Angular, Firebase, and AngularFire dependencies by running: 197 | 198 | ``` 199 | bower update 200 | ``` 201 | 202 | This will find the latest versions that match the version ranges specified in the `bower.json` file. 203 | 204 | 205 | ## Loading AngularFire Asynchronously 206 | 207 | The angularfire-seed project supports loading the framework and application scripts asynchronously. The 208 | special `index-async.html` is designed to support this style of loading. For it to work you must 209 | inject a piece of Angular JavaScript into the HTML page. The project has a predefined script to help 210 | do this. 211 | 212 | ``` 213 | npm run update-index-async 214 | ``` 215 | 216 | This will copy the contents of the `angular-loader.js` library file into the `index-async.html` page. 217 | You can run this every time you update the version of Angular that you are using. 218 | 219 | 220 | ## Serving the Application Files 221 | 222 | While Angular is client-side-only technology and it's possible to create Angular webapps that 223 | don't require a backend server at all, we recommend serving the project files using a local 224 | webserver during development to avoid issues with security restrictions (sandbox) in browsers. The 225 | sandbox implementation varies between browsers, but quite often prevents things like cookies, xhr, 226 | etc to function properly when an html page is opened via `file://` scheme instead of `http://`. 227 | 228 | 229 | ### Running the App during Development 230 | 231 | The angularfire-seed project comes preconfigured with a local development webserver. It is a node.js 232 | tool called [http-server][http-server]. You can start this webserver with `npm start` but you may choose to 233 | install the tool globally: 234 | 235 | ``` 236 | sudo npm install -g http-server 237 | ``` 238 | 239 | Then you can start your own development web server to serve static files from a folder by 240 | running: 241 | 242 | ``` 243 | http-server 244 | ``` 245 | 246 | Alternatively, you can choose to configure your own webserver, such as apache or nginx. Just 247 | configure your server to serve the files under the `app/` directory. 248 | 249 | 250 | ### Running the App in Production 251 | 252 | This really depends on how complex your app is and the overall infrastructure of your system, but 253 | the general rule is that all you need in production are all the files under the `app/` directory. 254 | Everything else should be omitted. 255 | 256 | Angular/Firebase apps are really just a bunch of static html, css and js files that just need to be hosted 257 | somewhere they can be accessed by browsers. 258 | 259 | ## Continuous Integration 260 | 261 | ### Travis CI 262 | 263 | [Travis CI][travis] is a continuous integration service, which can monitor GitHub for new commits 264 | to your repository and execute scripts such as building the app or running tests. The angularfire-seed 265 | project contains a Travis configuration file, `.travis.yml`, which will cause Travis to run your 266 | tests when you push to GitHub. 267 | 268 | You will need to enable the integration between Travis and GitHub. See the Travis website for more 269 | instruction on how to do this. 270 | 271 | ### CloudBees 272 | 273 | CloudBees have provided a CI/deployment setup: 274 | 275 | 276 | 277 | 278 | If you run this, you will get a cloned version of this repo to start working on in a private git repo, 279 | along with a CI service (in Jenkins) hosted that will run unit and end to end tests in both Firefox and Chrome. 280 | 281 | 282 | ## Contact 283 | 284 | For more information on Firebase and AngularFire, 285 | check out https://firebase.com/docs/web/bindings/angular 286 | 287 | For more information on AngularJS please check out http://angularjs.org/ 288 | 289 | [git]: http://git-scm.com/ 290 | [bower]: http://bower.io 291 | [npm]: https://www.npmjs.org/ 292 | [node]: http://nodejs.org 293 | [protractor]: https://github.com/angular/protractor 294 | [jasmine]: http://jasmine.github.io/1.3/introduction.html 295 | [karma]: http://karma-runner.github.io 296 | [travis]: https://travis-ci.org/ 297 | [http-server]: https://github.com/nodeapps/http-server 298 | --------------------------------------------------------------------------------