diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..1033e2d
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
+
+# 2 space indentation
+[**.*]
+indent_style = space
+indent_size = 2
\ No newline at end of file
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..f674032
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,3 @@
+{
+ "extends": "./node_modules/aurelia-tools/.eslintrc"
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8aa7bc6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+node_modules
+jspm_packages
+bower_components
+.idea
+.DS_STORE
+build/reports
+*.bat
\ No newline at end of file
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..a5aaaed
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,3 @@
+{
+ "esnext": true
+}
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..cafe9b6
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,3 @@
+jspm_packages
+bower_components
+.idea
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..b9a7eae
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Alain Mereaux
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ff65c74
--- /dev/null
+++ b/README.md
@@ -0,0 +1,136 @@
+# aurelia-firebase
+
+[![Join the chat at https://gitter.im/PulsarBlow/aurelia-firebase](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/PulsarBlow/aurelia-firebase?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Circle CI](https://circleci.com/gh/PulsarBlow/aurelia-firebase/tree/master.svg?style=svg)](https://circleci.com/gh/PulsarBlow/aurelia-firebase/tree/master)
+
+A Firebase plugin for [Aurelia](http://aurelia.io/) that supports Promises.
+Developed from scratch following aurelia's spirit.
+
+This is an early version which comes with :
+
+- A complete firebase password authentication support
+- A reactive collection providing auto-sync with Firebase server.
+
+This version is a work in progress, and lacks some Firebase features :
+
+- Full Query support (order, startAt, endAt etc..)
+- Priorities
+- Transactions
+
+Play with the demo : https://aureliaonfire.azurewebsites.net
+
+# Installation
+
+
+#### Install via JSPM
+Go into your project and verify it's already `npm install`'ed and `jspm install`'ed. Now execute following command to install the plugin via JSPM:
+
+```
+jspm install aurelia-firebase
+```
+
+this will add the plugin into your `jspm_packages` folder as well as an mapping-line into your `config.js` as:
+
+```
+"aurelia-firebase": "github:aurelia-firebase@X.X.X",
+```
+
+If you're feeling experimental or cannot wait for the next release, you could also install the latest version by executing:
+```
+jspm install aurelia-firebase=github:pulsarblow/aurelia-firebase@master
+```
+
+
+#### Migrate from aurelia-app to aurelia-app="main"
+You'll need to register the plugin when your aurelia app is bootstrapping. If you have an aurelia app because you cloned a sample, there's a good chance that the app is bootstrapping based on default conventions. In that case, open your **index.html** file and look at the *body* tag.
+``` html
+
+```
+Change the *aurelia-app* attribute to *aurelia-app="main"*.
+``` html
+
+```
+The aurelia framework will now bootstrap the application by looking for your **main.js** file and executing the exported *configure* method. Go ahead and add a new **main.js** file with these contents:
+``` javascript
+export function configure(aurelia) {
+ aurelia.use
+ .standardConfiguration()
+ .developmentLogging();
+
+ aurelia.start().then(a => a.setRoot('app', document.body));
+}
+
+```
+
+#### Load the plugin
+During bootstrapping phase, you can now include the validation plugin:
+
+``` javascript
+export function configure(aurelia) {
+ aurelia.use
+ .standardConfiguration()
+ .developmentLogging()
+ .plugin('aurelia-firebase'); //Add this line to load the plugin
+
+ aurelia.start().then(a => a.setRoot('app', document.body));
+}
+```
+
+# Getting started
+
+TBD...
+
+#Configuration
+##One config to rule them all
+The firebase plugin has one global configuration instance, which is passed to an optional callback function when you first install the plugin:
+``` javascript
+export function configure(aurelia) {
+ aurelia.use
+ .standardConfiguration()
+ .developmentLogging()
+ .plugin('aurelia-firebase', (config) => { config.setFirebaseUrl('https://myapp.firebaseio.com/'); });
+
+ aurelia.start().then(a => a.setRoot('app', document.body));
+}
+```
+
+>Note: if you want to access the global configuration instance at a later point in time, you can inject it:
+``` javascript
+import {Configuration} from 'aurelia-firebase';
+import {inject} from 'aurelia-framework';
+
+>@inject(Configuration)
+export class MyVM{
+ constructor(config)
+ {
+
+> }
+}
+```
+
+##Possible configuration
+>Note: all these can be chained:
+``` javascript
+(config) => { config.setFirebaseUrl('https://myapp.firebaseio.com/').setMonitorAuthChange(true); }
+```
+
+###config.setFirebaseUrl(firebaseUrl: string)
+``` javascript
+(config) => { config.setFirebaseUrl('https://myapp.firebaseio.com/'); }
+```
+Sets the Firebase URL where your app answers.
+This is required and the plugin will not start if not provided.
+
+###config.setMonitorAuthChange(monitorAuthChange: boolean)
+``` javascript
+(config) => { config.setMonitorAuthChange(true); }
+```
+When set to true, the authentication manager will monitor authentication changes for the current user
+The default value is false.
+
+#AuthenticationManager
+
+The authentication manager handles authentication aspects in the plugin.
+
+#ReactiveCollection
+
+The ReactiveCollection class handles firebase data synchronization.
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..f9caaad
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,19 @@
+{
+ "name": "aurelia-firebase",
+ "version": "0.1.0-beta",
+ "description": "A Firebase plugin for Aurelia.",
+ "keywords": [
+ "aurelia",
+ "firebase",
+ "plugin"
+ ],
+ "homepage": "https://github.com/PulsarBlow/aurelia-firebase",
+ "license": "MIT",
+ "authors": [
+ "Alain Méreaux "
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/PulsarBlow/aurelia-firebase"
+ }
+}
diff --git a/build/args.js b/build/args.js
new file mode 100644
index 0000000..db342fc
--- /dev/null
+++ b/build/args.js
@@ -0,0 +1,13 @@
+var yargs = require('yargs');
+
+var argv = yargs.argv,
+ validBumpTypes = "major|minor|patch|prerelease".split("|"),
+ bump = (argv.bump || 'patch').toLowerCase();
+
+if(validBumpTypes.indexOf(bump) === -1) {
+ throw new Error('Unrecognized bump "' + bump + '".');
+}
+
+module.exports = {
+ bump: bump
+};
diff --git a/build/babel-options.js b/build/babel-options.js
new file mode 100644
index 0000000..9504abb
--- /dev/null
+++ b/build/babel-options.js
@@ -0,0 +1,11 @@
+module.exports = {
+ modules: 'system',
+ moduleIds: false,
+ comments: false,
+ compact: false,
+ stage:2,
+ optional: [
+ "es7.decorators",
+ "es7.classProperties"
+ ]
+};
diff --git a/build/paths.js b/build/paths.js
new file mode 100644
index 0000000..0ca8b7a
--- /dev/null
+++ b/build/paths.js
@@ -0,0 +1,16 @@
+var path = require('path');
+
+var appRoot = 'src/';
+var outputRoot = 'dist/';
+
+module.exports = {
+ root: appRoot,
+ source: appRoot + '**/*.js',
+ html: appRoot + '**/*.html',
+ css: appRoot + '**/*.css',
+ style: 'styles/**/*.css',
+ output: outputRoot,
+ doc:'./doc',
+ e2eSpecsSrc: 'test/e2e/src/*.js',
+ e2eSpecsDist: 'test/e2e/dist/'
+};
diff --git a/build/tasks/build.js b/build/tasks/build.js
new file mode 100644
index 0000000..30b6ad9
--- /dev/null
+++ b/build/tasks/build.js
@@ -0,0 +1,57 @@
+var gulp = require('gulp');
+var runSequence = require('run-sequence');
+var to5 = require('gulp-babel');
+var paths = require('../paths');
+var compilerOptions = require('../babel-options');
+var assign = Object.assign || require('object.assign');
+
+gulp.task('build-html-es6', function () {
+ return gulp.src(paths.html)
+ .pipe(gulp.dest(paths.output + 'es6'));
+});
+
+gulp.task('build-es6', ['build-html-es6'], function () {
+ return gulp.src(paths.source)
+ .pipe(gulp.dest(paths.output + 'es6'));
+});
+
+gulp.task('build-html-commonjs', function () {
+ return gulp.src(paths.html)
+ .pipe(gulp.dest(paths.output + 'commonjs'));
+});
+
+gulp.task('build-commonjs', ['build-html-commonjs'], function () {
+ return gulp.src(paths.source)
+ .pipe(to5(assign({}, compilerOptions, {modules:'common'})))
+ .pipe(gulp.dest(paths.output + 'commonjs'));
+});
+
+gulp.task('build-html-amd', function () {
+ return gulp.src(paths.html)
+ .pipe(gulp.dest(paths.output + 'amd'));
+});
+
+gulp.task('build-amd', ['build-html-amd'], function () {
+ return gulp.src(paths.source)
+ .pipe(to5(assign({}, compilerOptions, {modules:'amd'})))
+ .pipe(gulp.dest(paths.output + 'amd'));
+});
+
+gulp.task('build-html-system', function () {
+ return gulp.src(paths.html)
+ .pipe(gulp.dest(paths.output + 'system'));
+});
+
+gulp.task('build-system', ['build-html-system'], function () {
+ return gulp.src(paths.source)
+ .pipe(to5(assign({}, compilerOptions, {modules:'system'})))
+ .pipe(gulp.dest(paths.output + 'system'));
+});
+
+gulp.task('build', function(callback) {
+ return runSequence(
+ 'clean',
+ ['build-es6', 'build-commonjs', 'build-amd', 'build-system'],
+ callback
+ );
+});
diff --git a/build/tasks/clean.js b/build/tasks/clean.js
new file mode 100644
index 0000000..897eed3
--- /dev/null
+++ b/build/tasks/clean.js
@@ -0,0 +1,10 @@
+var gulp = require('gulp');
+var paths = require('../paths');
+var del = require('del');
+var vinylPaths = require('vinyl-paths');
+
+// deletes all files in the output path
+gulp.task('clean', function() {
+ return gulp.src([paths.output])
+ .pipe(vinylPaths(del));
+});
diff --git a/build/tasks/dev.js b/build/tasks/dev.js
new file mode 100644
index 0000000..860b46f
--- /dev/null
+++ b/build/tasks/dev.js
@@ -0,0 +1,20 @@
+var gulp = require('gulp');
+var tools = require('aurelia-tools');
+
+// source code for the tasks called in this file
+// is located at: https://github.com/aurelia/tools/blob/master/src/dev.js
+
+// updates dependencies in this folder
+// from folders in the parent directory
+gulp.task('update-own-deps', function() {
+ tools.updateOwnDependenciesFromLocalRepositories();
+});
+
+// quickly pulls in all of the aurelia
+// github repos, placing them up one directory
+// from where the command is executed,
+// then runs `npm install`
+// and `gulp build` for each repo
+gulp.task('build-dev-env', function () {
+ tools.buildDevEnv();
+});
diff --git a/build/tasks/doc.js b/build/tasks/doc.js
new file mode 100644
index 0000000..2a5c936
--- /dev/null
+++ b/build/tasks/doc.js
@@ -0,0 +1,33 @@
+var gulp = require('gulp');
+var paths = require('../paths');
+var typedoc = require('gulp-typedoc');
+var typedocExtractor = require('gulp-typedoc-extractor');
+var runSequence = require('run-sequence');
+
+gulp.task('doc-generate', function(){
+ return gulp.src([paths.output + 'amd/*.d.ts', paths.doc + '/core-js.d.ts', './jspm_packages/github/aurelia/*/*.d.ts'])
+ .pipe(typedoc({
+ target: 'es6',
+ includeDeclarations: true,
+ json: paths.doc + '/api.json',
+ name: paths.packageName + '-docs',
+ mode: 'modules',
+ excludeExternals: true,
+ ignoreCompilerErrors: false,
+ version: true
+ }));
+});
+
+gulp.task('doc-extract', function(){
+ return gulp.src([paths.doc + '/api.json'])
+ .pipe(typedocExtractor(paths.output + 'amd/' + paths.packageName))
+ .pipe(gulp.dest(paths.doc));
+});
+
+gulp.task('doc', function(callback){
+ return runSequence(
+ 'doc-generate',
+ 'doc-extract',
+ callback
+ );
+});
diff --git a/build/tasks/lint.js b/build/tasks/lint.js
new file mode 100644
index 0000000..e758acc
--- /dev/null
+++ b/build/tasks/lint.js
@@ -0,0 +1,11 @@
+var gulp = require('gulp');
+var paths = require('../paths');
+var eslint = require('gulp-eslint');
+
+// runs eslint on all .js files
+gulp.task('lint', function() {
+ return gulp.src(paths.source)
+ .pipe(eslint())
+ .pipe(eslint.format())
+ .pipe(eslint.failOnError());
+});
diff --git a/build/tasks/prepare-release.js b/build/tasks/prepare-release.js
new file mode 100644
index 0000000..fb7a249
--- /dev/null
+++ b/build/tasks/prepare-release.js
@@ -0,0 +1,41 @@
+var gulp = require('gulp');
+var runSequence = require('run-sequence');
+var paths = require('../paths');
+var changelog = require('conventional-changelog');
+var fs = require('fs');
+var bump = require('gulp-bump');
+var args = require('../args');
+
+// utilizes the bump plugin to bump the
+// semver for the repo
+gulp.task('bump-version', function() {
+ return gulp.src(['./package.json'])
+ .pipe(bump({type:args.bump})) //major|minor|patch|prerelease
+ .pipe(gulp.dest('./'));
+});
+
+// generates the CHANGELOG.md file based on commit
+// from git commit messages
+gulp.task('changelog', function(callback) {
+ var pkg = JSON.parse(fs.readFileSync('./package.json', 'utf-8'));
+
+ return changelog({
+ repository: pkg.repository.url,
+ version: pkg.version,
+ file: paths.doc + '/CHANGELOG.md'
+ }, function(err, log) {
+ fs.writeFileSync(paths.doc + '/CHANGELOG.md', log);
+ });
+});
+
+// calls the listed sequence of tasks in order
+gulp.task('prepare-release', function(callback){
+ return runSequence(
+ 'build',
+ 'lint',
+ 'bump-version',
+ 'doc',
+ 'changelog',
+ callback
+ );
+});
diff --git a/build/tasks/test.js b/build/tasks/test.js
new file mode 100644
index 0000000..fe8631b
--- /dev/null
+++ b/build/tasks/test.js
@@ -0,0 +1,46 @@
+var gulp = require('gulp');
+var karma = require('karma').server;
+
+/**
+ * Run test once and exit
+ */
+gulp.task('test', function (done) {
+ karma.start({
+ configFile: __dirname + '/../../karma.conf.js',
+ singleRun: true
+ }, function(e) {
+ done();
+ });
+});
+
+/**
+ * Watch for file changes and re-run tests on each change
+ */
+gulp.task('tdd', function (done) {
+ karma.start({
+ configFile: __dirname + '/../../karma.conf.js'
+ }, function(e) {
+ done();
+ });
+});
+
+/**
+ * Run test once with code coverage and exit
+ */
+gulp.task('cover', function (done) {
+ karma.start({
+ configFile: __dirname + '/../../karma.conf.js',
+ singleRun: true,
+ reporters: ['coverage'],
+ preprocessors: {
+ 'test/**/*.js': ['babel'],
+ 'src/**/*.js': ['babel', 'coverage']
+ },
+ coverageReporter: {
+ type: 'html',
+ dir: 'build/reports/coverage'
+ }
+ }, function (e) {
+ done();
+ });
+});
diff --git a/config.js b/config.js
new file mode 100644
index 0000000..693a062
--- /dev/null
+++ b/config.js
@@ -0,0 +1,86 @@
+System.config({
+ defaultJSExtensions: true,
+ transpiler: "babel",
+ babelOptions: {
+ "optional": [
+ "es7.decorators",
+ "es7.classProperties",
+ "runtime"
+ ]
+ },
+ paths: {
+ "*": "dist/*",
+ "github:*": "jspm_packages/github/*",
+ "npm:*": "jspm_packages/npm/*"
+ },
+
+ map: {
+ "aurelia-dependency-injection": "github:aurelia/dependency-injection@0.11.0",
+ "aurelia-event-aggregator": "github:aurelia/event-aggregator@0.9.0",
+ "aurelia-logging": "github:aurelia/logging@0.8.0",
+ "babel": "npm:babel-core@5.8.25",
+ "babel-runtime": "npm:babel-runtime@5.8.25",
+ "bluebird": "npm:bluebird@2.10.2",
+ "core-js": "npm:core-js@0.9.18",
+ "faker": "npm:faker@3.0.1",
+ "firebase": "github:firebase/firebase-bower@2.3.1",
+ "text": "github:systemjs/plugin-text@0.0.2",
+ "github:aurelia/dependency-injection@0.11.0": {
+ "aurelia-logging": "github:aurelia/logging@0.8.0",
+ "aurelia-metadata": "github:aurelia/metadata@0.9.0",
+ "aurelia-pal": "github:aurelia/pal@0.2.0",
+ "core-js": "npm:core-js@0.9.18"
+ },
+ "github:aurelia/event-aggregator@0.9.0": {
+ "aurelia-logging": "github:aurelia/logging@0.8.0"
+ },
+ "github:aurelia/metadata@0.9.0": {
+ "aurelia-pal": "github:aurelia/pal@0.2.0",
+ "core-js": "npm:core-js@0.9.18"
+ },
+ "github:jspm/nodelibs-assert@0.1.0": {
+ "assert": "npm:assert@1.3.0"
+ },
+ "github:jspm/nodelibs-path@0.1.0": {
+ "path-browserify": "npm:path-browserify@0.0.0"
+ },
+ "github:jspm/nodelibs-process@0.1.2": {
+ "process": "npm:process@0.11.2"
+ },
+ "github:jspm/nodelibs-util@0.1.0": {
+ "util": "npm:util@0.10.3"
+ },
+ "npm:assert@1.3.0": {
+ "util": "npm:util@0.10.3"
+ },
+ "npm:babel-runtime@5.8.25": {
+ "process": "github:jspm/nodelibs-process@0.1.2"
+ },
+ "npm:bluebird@2.10.2": {
+ "process": "github:jspm/nodelibs-process@0.1.2"
+ },
+ "npm:core-js@0.9.18": {
+ "fs": "github:jspm/nodelibs-fs@0.1.2",
+ "process": "github:jspm/nodelibs-process@0.1.2",
+ "systemjs-json": "github:systemjs/plugin-json@0.1.0"
+ },
+ "npm:faker@3.0.1": {
+ "fs": "github:jspm/nodelibs-fs@0.1.2",
+ "path": "github:jspm/nodelibs-path@0.1.0",
+ "process": "github:jspm/nodelibs-process@0.1.2"
+ },
+ "npm:inherits@2.0.1": {
+ "util": "github:jspm/nodelibs-util@0.1.0"
+ },
+ "npm:path-browserify@0.0.0": {
+ "process": "github:jspm/nodelibs-process@0.1.2"
+ },
+ "npm:process@0.11.2": {
+ "assert": "github:jspm/nodelibs-assert@0.1.0"
+ },
+ "npm:util@0.10.3": {
+ "inherits": "npm:inherits@2.0.1",
+ "process": "github:jspm/nodelibs-process@0.1.2"
+ }
+ }
+});
diff --git a/dist/amd/authentication.js b/dist/amd/authentication.js
new file mode 100644
index 0000000..40deed8
--- /dev/null
+++ b/dist/amd/authentication.js
@@ -0,0 +1,166 @@
+define(['exports', 'bluebird', 'firebase', 'aurelia-dependency-injection', './events', './user', './configuration'], function (exports, _bluebird, _firebase, _aureliaDependencyInjection, _events, _user, _configuration) {
+ 'use strict';
+
+ Object.defineProperty(exports, '__esModule', {
+ value: true
+ });
+
+ var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+ var _Promise = _interopRequireDefault(_bluebird);
+
+ var _Firebase = _interopRequireDefault(_firebase);
+
+ var AuthenticationManager = (function () {
+ function AuthenticationManager(configuration, publisher) {
+ var _this = this;
+
+ _classCallCheck(this, _AuthenticationManager);
+
+ this._firebase = null;
+ this._publisher = null;
+ this.currentUser = null;
+
+ this._firebase = new _Firebase['default'](configuration.getFirebaseUrl());
+ this._publisher = publisher;
+ this.currentUser = new _user.User();
+
+ if (configuration.getMonitorAuthChange() === true) {
+ this._firebase.onAuth(function (result) {
+ _this._onUserAuthStateChanged(result);
+ }, this);
+ }
+ }
+
+ _createClass(AuthenticationManager, [{
+ key: 'createUser',
+ value: function createUser(email, password) {
+ var _this2 = this;
+
+ return new _Promise['default'](function (resolve, reject) {
+ _this2._firebase.createUser({ email: email, password: password }, function (error, result) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ var user = new _user.User(result);
+ user.email = user.email || email;
+ _this2._publisher.publish(new _events.UserCreatedEvent(user));
+ resolve(user);
+ });
+ });
+ }
+ }, {
+ key: 'signIn',
+ value: function signIn(email, password) {
+ var _this3 = this;
+
+ return new _Promise['default'](function (resolve, reject) {
+ _this3._firebase.authWithPassword({ email: email, password: password }, function (error, result) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ var user = new _user.User(result);
+ _this3._publisher.publish(new _events.UserSignedInEvent(user));
+ resolve(user);
+ });
+ });
+ }
+ }, {
+ key: 'createUserAndSignIn',
+ value: function createUserAndSignIn(email, password) {
+ var _this4 = this;
+
+ return this.createUser(email, password).then(function () {
+ return _this4.signIn(email, password);
+ });
+ }
+ }, {
+ key: 'signOut',
+ value: function signOut() {
+ var _this5 = this;
+
+ return new _Promise['default'](function (resolve) {
+ _this5._firebase.unauth();
+ _this5.currentUser.reset();
+ resolve();
+ });
+ }
+ }, {
+ key: 'changeEmail',
+ value: function changeEmail(oldEmail, password, newEmail) {
+ var _this6 = this;
+
+ return new _Promise['default'](function (resolve, reject) {
+ _this6._firebase.changeEmail({ oldEmail: oldEmail, password: password, newEmail: newEmail }, function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ _this6.currentUser.email = newEmail;
+ var result = { oldEmail: oldEmail, newEmail: newEmail };
+ _this6._publisher.publish(new _events.UserEmailChangedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+ }, {
+ key: 'changePassword',
+ value: function changePassword(email, oldPassword, newPassword) {
+ var _this7 = this;
+
+ return new _Promise['default'](function (resolve, reject) {
+ _this7._firebase.changePassword({ email: email, oldPassword: oldPassword, newPassword: newPassword }, function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ var result = { email: email };
+ _this7._publisher.publish(new _events.UserPasswordChangedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+ }, {
+ key: 'deleteUser',
+ value: function deleteUser(email, password) {
+ var _this8 = this;
+
+ return new _Promise['default'](function (resolve, reject) {
+ _this8._firebase.removeUser({ email: email, password: password }, function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ _this8.currentUser.reset();
+ var result = { email: email };
+ _this8._publisher.publish(new _events.UserDeletedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+ }, {
+ key: '_onUserAuthStateChanged',
+ value: function _onUserAuthStateChanged(authData) {
+ this.currentUser.update(authData);
+ this._publisher.publish(new _events.UserAuthStateChangedEvent(authData));
+ }
+ }]);
+
+ var _AuthenticationManager = AuthenticationManager;
+ AuthenticationManager = (0, _aureliaDependencyInjection.inject)(_configuration.Configuration, _events.Publisher)(AuthenticationManager) || AuthenticationManager;
+ return AuthenticationManager;
+ })();
+
+ exports.AuthenticationManager = AuthenticationManager;
+});
\ No newline at end of file
diff --git a/dist/amd/collection.js b/dist/amd/collection.js
new file mode 100644
index 0000000..529060d
--- /dev/null
+++ b/dist/amd/collection.js
@@ -0,0 +1,201 @@
+define(['exports', 'bluebird', 'firebase', 'aurelia-dependency-injection', './configuration'], function (exports, _bluebird, _firebase, _aureliaDependencyInjection, _configuration) {
+ 'use strict';
+
+ Object.defineProperty(exports, '__esModule', {
+ value: true
+ });
+
+ var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+ var _Promise = _interopRequireDefault(_bluebird);
+
+ var _Firebase = _interopRequireDefault(_firebase);
+
+ var ReactiveCollection = (function () {
+ function ReactiveCollection(path) {
+ _classCallCheck(this, ReactiveCollection);
+
+ this._query = null;
+ this._valueMap = new Map();
+ this.items = [];
+
+ if (!_aureliaDependencyInjection.Container || !_aureliaDependencyInjection.Container.instance) throw Error('Container has not been made global');
+ var config = _aureliaDependencyInjection.Container.instance.get(_configuration.Configuration);
+ if (!config) throw Error('Configuration has not been set');
+
+ this._query = new _Firebase['default'](ReactiveCollection._getChildLocation(config.getFirebaseUrl(), path));
+ this._listenToQuery(this._query);
+ }
+
+ _createClass(ReactiveCollection, [{
+ key: 'add',
+ value: function add(item) {
+ var _this = this;
+
+ return new _Promise['default'](function (resolve, reject) {
+ var query = _this._query.ref().push();
+ query.set(item, function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(item);
+ });
+ });
+ }
+ }, {
+ key: 'remove',
+ value: function remove(item) {
+ if (item === null || item.__firebaseKey__ === null) {
+ return _Promise['default'].reject({ message: 'Unknown item' });
+ }
+ return this.removeByKey(item.__firebaseKey__);
+ }
+ }, {
+ key: 'getByKey',
+ value: function getByKey(key) {
+ return this._valueMap.get(key);
+ }
+ }, {
+ key: 'removeByKey',
+ value: function removeByKey(key) {
+ var _this2 = this;
+
+ return new _Promise['default'](function (resolve, reject) {
+ _this2._query.ref().child(key).remove(function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(key);
+ });
+ });
+ }
+ }, {
+ key: 'clear',
+ value: function clear() {
+ var _this3 = this;
+
+ return new _Promise['default'](function (resolve, reject) {
+ var query = _this3._query.ref();
+ query.remove(function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve();
+ });
+ });
+ }
+ }, {
+ key: '_listenToQuery',
+ value: function _listenToQuery(query) {
+ var _this4 = this;
+
+ query.on('child_added', function (snapshot, previousKey) {
+ _this4._onItemAdded(snapshot, previousKey);
+ });
+ query.on('child_removed', function (snapshot) {
+ _this4._onItemRemoved(snapshot);
+ });
+ query.on('child_changed', function (snapshot, previousKey) {
+ _this4._onItemChanded(snapshot, previousKey);
+ });
+ query.on('child_moved', function (snapshot, previousKey) {
+ _this4._onItemMoved(snapshot, previousKey);
+ });
+ }
+ }, {
+ key: '_stopListeningToQuery',
+ value: function _stopListeningToQuery(query) {
+ query.off();
+ }
+ }, {
+ key: '_onItemAdded',
+ value: function _onItemAdded(snapshot, previousKey) {
+ var value = this._valueFromSnapshot(snapshot);
+ var index = previousKey !== null ? this.items.indexOf(this._valueMap.get(previousKey)) + 1 : 0;
+ this._valueMap.set(value.__firebaseKey__, value);
+ this.items.splice(index, 0, value);
+ }
+ }, {
+ key: '_onItemRemoved',
+ value: function _onItemRemoved(oldSnapshot) {
+ var key = oldSnapshot.key();
+ var value = this._valueMap.get(key);
+
+ if (!value) {
+ return;
+ }
+
+ var index = this.items.indexOf(value);
+ this._valueMap['delete'](key);
+ if (index !== -1) {
+ this.items.splice(index, 1);
+ }
+ }
+ }, {
+ key: '_onItemChanged',
+ value: function _onItemChanged(snapshot, previousKey) {
+ var value = this._valueFromSnapshot(snapshot);
+ var oldValue = this._valueMap.get(value.__firebaseKey__);
+
+ if (!oldValue) {
+ return;
+ }
+
+ this._valueMap['delete'](oldValue.__firebaseKey__);
+ this._valueMap.set(value.__firebaseKey__, value);
+ this.items.splice(this.items.indexOf(oldValue), 1, value);
+ }
+ }, {
+ key: '_onItemMoved',
+ value: function _onItemMoved(snapshot, previousKey) {
+ var key = snapshot.key();
+ var value = this._valueMap.get(key);
+
+ if (!value) {
+ return;
+ }
+
+ var previousValue = this._valueMap.get(previousKey);
+ var newIndex = previousValue !== null ? this.items.indexOf(previousValue) + 1 : 0;
+ this.items.splice(this.items.indexOf(value), 1);
+ this.items.splice(newIndex, 0, value);
+ }
+ }, {
+ key: '_valueFromSnapshot',
+ value: function _valueFromSnapshot(snapshot) {
+ var value = snapshot.val();
+ if (!(value instanceof Object)) {
+ value = {
+ value: value,
+ __firebasePrimitive__: true
+ };
+ }
+ value.__firebaseKey__ = snapshot.key();
+ return value;
+ }
+ }], [{
+ key: '_getChildLocation',
+ value: function _getChildLocation(root, path) {
+ if (!path) {
+ return root;
+ }
+ if (!root.endsWith('/')) {
+ root = root + '/';
+ }
+
+ return root + (Array.isArray(path) ? path.join('/') : path);
+ }
+ }]);
+
+ return ReactiveCollection;
+ })();
+
+ exports.ReactiveCollection = ReactiveCollection;
+});
\ No newline at end of file
diff --git a/dist/amd/configuration.js b/dist/amd/configuration.js
new file mode 100644
index 0000000..eebc92b
--- /dev/null
+++ b/dist/amd/configuration.js
@@ -0,0 +1,82 @@
+define(['exports'], function (exports) {
+ 'use strict';
+
+ Object.defineProperty(exports, '__esModule', {
+ value: true
+ });
+
+ var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+ var ConfigurationDefaults = function ConfigurationDefaults() {
+ _classCallCheck(this, ConfigurationDefaults);
+ };
+
+ exports.ConfigurationDefaults = ConfigurationDefaults;
+
+ ConfigurationDefaults._defaults = {
+ firebaseUrl: null,
+ monitorAuthChange: false
+ };
+
+ ConfigurationDefaults.defaults = function () {
+ var defaults = {};
+ Object.assign(defaults, ConfigurationDefaults._defaults);
+ return defaults;
+ };
+
+ var Configuration = (function () {
+ function Configuration(innerConfig) {
+ _classCallCheck(this, Configuration);
+
+ this.innerConfig = innerConfig;
+ this.values = this.innerConfig ? {} : ConfigurationDefaults.defaults();
+ }
+
+ _createClass(Configuration, [{
+ key: 'getValue',
+ value: function getValue(identifier) {
+ if (this.values.hasOwnProperty(identifier) !== null && this.values[identifier] !== undefined) {
+ return this.values[identifier];
+ }
+ if (this.innerConfig !== null) {
+ return this.innerConfig.getValue(identifier);
+ }
+ throw new Error('Config not found: ' + identifier);
+ }
+ }, {
+ key: 'setValue',
+ value: function setValue(identifier, value) {
+ this.values[identifier] = value;
+ return this;
+ }
+ }, {
+ key: 'getFirebaseUrl',
+ value: function getFirebaseUrl() {
+ return this.getValue('firebaseUrl');
+ }
+ }, {
+ key: 'setFirebaseUrl',
+ value: function setFirebaseUrl(firebaseUrl) {
+ return this.setValue('firebaseUrl', firebaseUrl);
+ }
+ }, {
+ key: 'getMonitorAuthChange',
+ value: function getMonitorAuthChange() {
+ return this.getValue('monitorAuthChange');
+ }
+ }, {
+ key: 'setMonitorAuthChange',
+ value: function setMonitorAuthChange() {
+ var monitorAuthChange = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
+
+ return this.setValue('monitorAuthChange', monitorAuthChange === true);
+ }
+ }]);
+
+ return Configuration;
+ })();
+
+ exports.Configuration = Configuration;
+});
\ No newline at end of file
diff --git a/dist/amd/events.js b/dist/amd/events.js
new file mode 100644
index 0000000..0c745ef
--- /dev/null
+++ b/dist/amd/events.js
@@ -0,0 +1,170 @@
+define(['exports', 'aurelia-dependency-injection', 'aurelia-event-aggregator'], function (exports, _aureliaDependencyInjection, _aureliaEventAggregator) {
+ 'use strict';
+
+ Object.defineProperty(exports, '__esModule', {
+ value: true
+ });
+
+ var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+ var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+ function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+ var FirebaseEvent = function FirebaseEvent() {
+ _classCallCheck(this, FirebaseEvent);
+
+ this.handled = false;
+ };
+
+ var UserEvent = (function (_FirebaseEvent) {
+ _inherits(UserEvent, _FirebaseEvent);
+
+ function UserEvent() {
+ var uid = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
+
+ _classCallCheck(this, UserEvent);
+
+ _get(Object.getPrototypeOf(UserEvent.prototype), 'constructor', this).call(this);
+ this.uid = uid;
+ }
+
+ return UserEvent;
+ })(FirebaseEvent);
+
+ var UserCreatedEvent = (function (_UserEvent) {
+ _inherits(UserCreatedEvent, _UserEvent);
+
+ function UserCreatedEvent(data) {
+ _classCallCheck(this, UserCreatedEvent);
+
+ _get(Object.getPrototypeOf(UserCreatedEvent.prototype), 'constructor', this).call(this, data.uid);
+ this.email = data.email;
+ }
+
+ return UserCreatedEvent;
+ })(UserEvent);
+
+ exports.UserCreatedEvent = UserCreatedEvent;
+
+ var UserSignedInEvent = (function (_UserEvent2) {
+ _inherits(UserSignedInEvent, _UserEvent2);
+
+ function UserSignedInEvent(data) {
+ _classCallCheck(this, UserSignedInEvent);
+
+ _get(Object.getPrototypeOf(UserSignedInEvent.prototype), 'constructor', this).call(this, data.uid);
+ this.provider = data.provider;
+ this.email = data.email;
+ }
+
+ return UserSignedInEvent;
+ })(UserEvent);
+
+ exports.UserSignedInEvent = UserSignedInEvent;
+
+ var UserSignedOutEvent = (function (_UserEvent3) {
+ _inherits(UserSignedOutEvent, _UserEvent3);
+
+ function UserSignedOutEvent(data) {
+ _classCallCheck(this, UserSignedOutEvent);
+
+ _get(Object.getPrototypeOf(UserSignedOutEvent.prototype), 'constructor', this).call(this);
+ this.email = data.email;
+ }
+
+ return UserSignedOutEvent;
+ })(UserEvent);
+
+ exports.UserSignedOutEvent = UserSignedOutEvent;
+
+ var UserEmailChangedEvent = (function (_UserEvent4) {
+ _inherits(UserEmailChangedEvent, _UserEvent4);
+
+ function UserEmailChangedEvent(data) {
+ _classCallCheck(this, UserEmailChangedEvent);
+
+ _get(Object.getPrototypeOf(UserEmailChangedEvent.prototype), 'constructor', this).call(this);
+ this.oldEmail = data.oldEmail;
+ this.newEmail = data.newEmail;
+ }
+
+ return UserEmailChangedEvent;
+ })(UserEvent);
+
+ exports.UserEmailChangedEvent = UserEmailChangedEvent;
+
+ var UserPasswordChangedEvent = (function (_UserEvent5) {
+ _inherits(UserPasswordChangedEvent, _UserEvent5);
+
+ function UserPasswordChangedEvent(data) {
+ _classCallCheck(this, UserPasswordChangedEvent);
+
+ _get(Object.getPrototypeOf(UserPasswordChangedEvent.prototype), 'constructor', this).call(this);
+ this.email = data.email;
+ }
+
+ return UserPasswordChangedEvent;
+ })(UserEvent);
+
+ exports.UserPasswordChangedEvent = UserPasswordChangedEvent;
+
+ var UserDeletedEvent = (function (_UserEvent6) {
+ _inherits(UserDeletedEvent, _UserEvent6);
+
+ function UserDeletedEvent(data) {
+ _classCallCheck(this, UserDeletedEvent);
+
+ _get(Object.getPrototypeOf(UserDeletedEvent.prototype), 'constructor', this).call(this);
+ this.email = data.email;
+ }
+
+ return UserDeletedEvent;
+ })(UserEvent);
+
+ exports.UserDeletedEvent = UserDeletedEvent;
+
+ var UserAuthStateChangedEvent = (function (_UserEvent7) {
+ _inherits(UserAuthStateChangedEvent, _UserEvent7);
+
+ function UserAuthStateChangedEvent(data) {
+ _classCallCheck(this, UserAuthStateChangedEvent);
+
+ data = data || {};
+ _get(Object.getPrototypeOf(UserAuthStateChangedEvent.prototype), 'constructor', this).call(this, data.uid);
+ this.provider = data.provider || null;
+ this.auth = data.auth || null;
+ this.expires = data.expires || 0;
+ }
+
+ return UserAuthStateChangedEvent;
+ })(UserEvent);
+
+ exports.UserAuthStateChangedEvent = UserAuthStateChangedEvent;
+
+ var Publisher = (function () {
+ function Publisher(eventAggregator) {
+ _classCallCheck(this, _Publisher);
+
+ this._eventAggregator = eventAggregator;
+ }
+
+ _createClass(Publisher, [{
+ key: 'publish',
+ value: function publish(event) {
+ if (event.handled) {
+ return;
+ }
+ this._eventAggregator.publish(event);
+ }
+ }]);
+
+ var _Publisher = Publisher;
+ Publisher = (0, _aureliaDependencyInjection.inject)(_aureliaEventAggregator.EventAggregator)(Publisher) || Publisher;
+ return Publisher;
+ })();
+
+ exports.Publisher = Publisher;
+});
\ No newline at end of file
diff --git a/dist/amd/index.js b/dist/amd/index.js
new file mode 100644
index 0000000..811728e
--- /dev/null
+++ b/dist/amd/index.js
@@ -0,0 +1,48 @@
+define(['exports', './configuration', './user', './authentication', './collection', './events'], function (exports, _configuration, _user, _authentication, _collection, _events) {
+ 'use strict';
+
+ Object.defineProperty(exports, '__esModule', {
+ value: true
+ });
+ exports.configure = configure;
+
+ function _interopExportWildcard(obj, defaults) { var newObj = defaults({}, obj); delete newObj['default']; return newObj; }
+
+ function _defaults(obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; }
+
+ Object.defineProperty(exports, 'Configuration', {
+ enumerable: true,
+ get: function get() {
+ return _configuration.Configuration;
+ }
+ });
+ Object.defineProperty(exports, 'User', {
+ enumerable: true,
+ get: function get() {
+ return _user.User;
+ }
+ });
+ Object.defineProperty(exports, 'AuthenticationManager', {
+ enumerable: true,
+ get: function get() {
+ return _authentication.AuthenticationManager;
+ }
+ });
+ Object.defineProperty(exports, 'ReactiveCollection', {
+ enumerable: true,
+ get: function get() {
+ return _collection.ReactiveCollection;
+ }
+ });
+
+ _defaults(exports, _interopExportWildcard(_events, _defaults));
+
+ function configure(aurelia, configCallback) {
+ var config = new _configuration.Configuration(_configuration.Configuration.defaults);
+
+ if (configCallback !== undefined && typeof configCallback === 'function') {
+ configCallback(config);
+ }
+ aurelia.instance(_configuration.Configuration, config);
+ }
+});
\ No newline at end of file
diff --git a/dist/amd/user.js b/dist/amd/user.js
new file mode 100644
index 0000000..754b4d8
--- /dev/null
+++ b/dist/amd/user.js
@@ -0,0 +1,63 @@
+define(["exports"], function (exports) {
+ "use strict";
+
+ Object.defineProperty(exports, "__esModule", {
+ value: true
+ });
+
+ var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+ var User = (function () {
+ _createClass(User, [{
+ key: "isAuthenticated",
+ get: function get() {
+ return this.token && this.auth && this.expires > 0 || false;
+ }
+ }]);
+
+ function User() {
+ var userData = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
+
+ _classCallCheck(this, User);
+
+ this.uid = null;
+ this.provider = null;
+ this.token = null;
+ this.auth = null;
+ this.expires = 0;
+ this.email = null;
+ this.isTemporaryPassword = null;
+ this.profileImageUrl = null;
+
+ this.update(userData);
+ }
+
+ _createClass(User, [{
+ key: "update",
+ value: function update(userData) {
+ userData = userData || {};
+ this.uid = userData.uid || null;
+ this.provider = userData.provider || null;
+ this.token = userData.token || null;
+ this.auth = userData.auth || null;
+ this.expires = userData.expires || 0;
+
+ userData.password = userData.password || {};
+ this.isTemporaryPassword = userData.password.isTemporaryPassword || false;
+ this.profileImageUrl = userData.password.profileImageURL || null;
+ this.email = userData.password.email || null;
+ }
+ }, {
+ key: "reset",
+ value: function reset() {
+ this.update({});
+ }
+ }]);
+
+ return User;
+ })();
+
+ exports.User = User;
+});
\ No newline at end of file
diff --git a/dist/commonjs/authentication.js b/dist/commonjs/authentication.js
new file mode 100644
index 0000000..d53efbb
--- /dev/null
+++ b/dist/commonjs/authentication.js
@@ -0,0 +1,180 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var _bluebird = require('bluebird');
+
+var _bluebird2 = _interopRequireDefault(_bluebird);
+
+var _firebase = require('firebase');
+
+var _firebase2 = _interopRequireDefault(_firebase);
+
+var _aureliaDependencyInjection = require('aurelia-dependency-injection');
+
+var _events = require('./events');
+
+var events = _interopRequireWildcard(_events);
+
+var _user = require('./user');
+
+var _configuration = require('./configuration');
+
+var AuthenticationManager = (function () {
+ function AuthenticationManager(configuration, publisher) {
+ var _this = this;
+
+ _classCallCheck(this, _AuthenticationManager);
+
+ this._firebase = null;
+ this._publisher = null;
+ this.currentUser = null;
+
+ this._firebase = new _firebase2['default'](configuration.getFirebaseUrl());
+ this._publisher = publisher;
+ this.currentUser = new _user.User();
+
+ if (configuration.getMonitorAuthChange() === true) {
+ this._firebase.onAuth(function (result) {
+ _this._onUserAuthStateChanged(result);
+ }, this);
+ }
+ }
+
+ _createClass(AuthenticationManager, [{
+ key: 'createUser',
+ value: function createUser(email, password) {
+ var _this2 = this;
+
+ return new _bluebird2['default'](function (resolve, reject) {
+ _this2._firebase.createUser({ email: email, password: password }, function (error, result) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ var user = new _user.User(result);
+ user.email = user.email || email;
+ _this2._publisher.publish(new events.UserCreatedEvent(user));
+ resolve(user);
+ });
+ });
+ }
+ }, {
+ key: 'signIn',
+ value: function signIn(email, password) {
+ var _this3 = this;
+
+ return new _bluebird2['default'](function (resolve, reject) {
+ _this3._firebase.authWithPassword({ email: email, password: password }, function (error, result) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ var user = new _user.User(result);
+ _this3._publisher.publish(new events.UserSignedInEvent(user));
+ resolve(user);
+ });
+ });
+ }
+ }, {
+ key: 'createUserAndSignIn',
+ value: function createUserAndSignIn(email, password) {
+ var _this4 = this;
+
+ return this.createUser(email, password).then(function () {
+ return _this4.signIn(email, password);
+ });
+ }
+ }, {
+ key: 'signOut',
+ value: function signOut() {
+ var _this5 = this;
+
+ return new _bluebird2['default'](function (resolve) {
+ _this5._firebase.unauth();
+ _this5.currentUser.reset();
+ resolve();
+ });
+ }
+ }, {
+ key: 'changeEmail',
+ value: function changeEmail(oldEmail, password, newEmail) {
+ var _this6 = this;
+
+ return new _bluebird2['default'](function (resolve, reject) {
+ _this6._firebase.changeEmail({ oldEmail: oldEmail, password: password, newEmail: newEmail }, function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ _this6.currentUser.email = newEmail;
+ var result = { oldEmail: oldEmail, newEmail: newEmail };
+ _this6._publisher.publish(new events.UserEmailChangedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+ }, {
+ key: 'changePassword',
+ value: function changePassword(email, oldPassword, newPassword) {
+ var _this7 = this;
+
+ return new _bluebird2['default'](function (resolve, reject) {
+ _this7._firebase.changePassword({ email: email, oldPassword: oldPassword, newPassword: newPassword }, function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ var result = { email: email };
+ _this7._publisher.publish(new events.UserPasswordChangedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+ }, {
+ key: 'deleteUser',
+ value: function deleteUser(email, password) {
+ var _this8 = this;
+
+ return new _bluebird2['default'](function (resolve, reject) {
+ _this8._firebase.removeUser({ email: email, password: password }, function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ _this8.currentUser.reset();
+ var result = { email: email };
+ _this8._publisher.publish(new events.UserDeletedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+ }, {
+ key: '_onUserAuthStateChanged',
+ value: function _onUserAuthStateChanged(authData) {
+ this.currentUser.update(authData);
+ this._publisher.publish(new events.UserAuthStateChangedEvent(authData));
+ }
+ }]);
+
+ var _AuthenticationManager = AuthenticationManager;
+ AuthenticationManager = (0, _aureliaDependencyInjection.inject)(_configuration.Configuration, events.Publisher)(AuthenticationManager) || AuthenticationManager;
+ return AuthenticationManager;
+})();
+
+exports.AuthenticationManager = AuthenticationManager;
\ No newline at end of file
diff --git a/dist/commonjs/collection.js b/dist/commonjs/collection.js
new file mode 100644
index 0000000..47b21e1
--- /dev/null
+++ b/dist/commonjs/collection.js
@@ -0,0 +1,207 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var _bluebird = require('bluebird');
+
+var _bluebird2 = _interopRequireDefault(_bluebird);
+
+var _firebase = require('firebase');
+
+var _firebase2 = _interopRequireDefault(_firebase);
+
+var _aureliaDependencyInjection = require('aurelia-dependency-injection');
+
+var _configuration = require('./configuration');
+
+var ReactiveCollection = (function () {
+ function ReactiveCollection(path) {
+ _classCallCheck(this, ReactiveCollection);
+
+ this._query = null;
+ this._valueMap = new Map();
+ this.items = [];
+
+ if (!_aureliaDependencyInjection.Container || !_aureliaDependencyInjection.Container.instance) throw Error('Container has not been made global');
+ var config = _aureliaDependencyInjection.Container.instance.get(_configuration.Configuration);
+ if (!config) throw Error('Configuration has not been set');
+
+ this._query = new _firebase2['default'](ReactiveCollection._getChildLocation(config.getFirebaseUrl(), path));
+ this._listenToQuery(this._query);
+ }
+
+ _createClass(ReactiveCollection, [{
+ key: 'add',
+ value: function add(item) {
+ var _this = this;
+
+ return new _bluebird2['default'](function (resolve, reject) {
+ var query = _this._query.ref().push();
+ query.set(item, function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(item);
+ });
+ });
+ }
+ }, {
+ key: 'remove',
+ value: function remove(item) {
+ if (item === null || item.__firebaseKey__ === null) {
+ return _bluebird2['default'].reject({ message: 'Unknown item' });
+ }
+ return this.removeByKey(item.__firebaseKey__);
+ }
+ }, {
+ key: 'getByKey',
+ value: function getByKey(key) {
+ return this._valueMap.get(key);
+ }
+ }, {
+ key: 'removeByKey',
+ value: function removeByKey(key) {
+ var _this2 = this;
+
+ return new _bluebird2['default'](function (resolve, reject) {
+ _this2._query.ref().child(key).remove(function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(key);
+ });
+ });
+ }
+ }, {
+ key: 'clear',
+ value: function clear() {
+ var _this3 = this;
+
+ return new _bluebird2['default'](function (resolve, reject) {
+ var query = _this3._query.ref();
+ query.remove(function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve();
+ });
+ });
+ }
+ }, {
+ key: '_listenToQuery',
+ value: function _listenToQuery(query) {
+ var _this4 = this;
+
+ query.on('child_added', function (snapshot, previousKey) {
+ _this4._onItemAdded(snapshot, previousKey);
+ });
+ query.on('child_removed', function (snapshot) {
+ _this4._onItemRemoved(snapshot);
+ });
+ query.on('child_changed', function (snapshot, previousKey) {
+ _this4._onItemChanded(snapshot, previousKey);
+ });
+ query.on('child_moved', function (snapshot, previousKey) {
+ _this4._onItemMoved(snapshot, previousKey);
+ });
+ }
+ }, {
+ key: '_stopListeningToQuery',
+ value: function _stopListeningToQuery(query) {
+ query.off();
+ }
+ }, {
+ key: '_onItemAdded',
+ value: function _onItemAdded(snapshot, previousKey) {
+ var value = this._valueFromSnapshot(snapshot);
+ var index = previousKey !== null ? this.items.indexOf(this._valueMap.get(previousKey)) + 1 : 0;
+ this._valueMap.set(value.__firebaseKey__, value);
+ this.items.splice(index, 0, value);
+ }
+ }, {
+ key: '_onItemRemoved',
+ value: function _onItemRemoved(oldSnapshot) {
+ var key = oldSnapshot.key();
+ var value = this._valueMap.get(key);
+
+ if (!value) {
+ return;
+ }
+
+ var index = this.items.indexOf(value);
+ this._valueMap['delete'](key);
+ if (index !== -1) {
+ this.items.splice(index, 1);
+ }
+ }
+ }, {
+ key: '_onItemChanged',
+ value: function _onItemChanged(snapshot, previousKey) {
+ var value = this._valueFromSnapshot(snapshot);
+ var oldValue = this._valueMap.get(value.__firebaseKey__);
+
+ if (!oldValue) {
+ return;
+ }
+
+ this._valueMap['delete'](oldValue.__firebaseKey__);
+ this._valueMap.set(value.__firebaseKey__, value);
+ this.items.splice(this.items.indexOf(oldValue), 1, value);
+ }
+ }, {
+ key: '_onItemMoved',
+ value: function _onItemMoved(snapshot, previousKey) {
+ var key = snapshot.key();
+ var value = this._valueMap.get(key);
+
+ if (!value) {
+ return;
+ }
+
+ var previousValue = this._valueMap.get(previousKey);
+ var newIndex = previousValue !== null ? this.items.indexOf(previousValue) + 1 : 0;
+ this.items.splice(this.items.indexOf(value), 1);
+ this.items.splice(newIndex, 0, value);
+ }
+ }, {
+ key: '_valueFromSnapshot',
+ value: function _valueFromSnapshot(snapshot) {
+ var value = snapshot.val();
+ if (!(value instanceof Object)) {
+ value = {
+ value: value,
+ __firebasePrimitive__: true
+ };
+ }
+ value.__firebaseKey__ = snapshot.key();
+ return value;
+ }
+ }], [{
+ key: '_getChildLocation',
+ value: function _getChildLocation(root, path) {
+ if (!path) {
+ return root;
+ }
+ if (!root.endsWith('/')) {
+ root = root + '/';
+ }
+
+ return root + (Array.isArray(path) ? path.join('/') : path);
+ }
+ }]);
+
+ return ReactiveCollection;
+})();
+
+exports.ReactiveCollection = ReactiveCollection;
\ No newline at end of file
diff --git a/dist/commonjs/configuration.js b/dist/commonjs/configuration.js
new file mode 100644
index 0000000..90cf687
--- /dev/null
+++ b/dist/commonjs/configuration.js
@@ -0,0 +1,80 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var ConfigurationDefaults = function ConfigurationDefaults() {
+ _classCallCheck(this, ConfigurationDefaults);
+};
+
+exports.ConfigurationDefaults = ConfigurationDefaults;
+
+ConfigurationDefaults._defaults = {
+ firebaseUrl: null,
+ monitorAuthChange: false
+};
+
+ConfigurationDefaults.defaults = function () {
+ var defaults = {};
+ Object.assign(defaults, ConfigurationDefaults._defaults);
+ return defaults;
+};
+
+var Configuration = (function () {
+ function Configuration(innerConfig) {
+ _classCallCheck(this, Configuration);
+
+ this.innerConfig = innerConfig;
+ this.values = this.innerConfig ? {} : ConfigurationDefaults.defaults();
+ }
+
+ _createClass(Configuration, [{
+ key: 'getValue',
+ value: function getValue(identifier) {
+ if (this.values.hasOwnProperty(identifier) !== null && this.values[identifier] !== undefined) {
+ return this.values[identifier];
+ }
+ if (this.innerConfig !== null) {
+ return this.innerConfig.getValue(identifier);
+ }
+ throw new Error('Config not found: ' + identifier);
+ }
+ }, {
+ key: 'setValue',
+ value: function setValue(identifier, value) {
+ this.values[identifier] = value;
+ return this;
+ }
+ }, {
+ key: 'getFirebaseUrl',
+ value: function getFirebaseUrl() {
+ return this.getValue('firebaseUrl');
+ }
+ }, {
+ key: 'setFirebaseUrl',
+ value: function setFirebaseUrl(firebaseUrl) {
+ return this.setValue('firebaseUrl', firebaseUrl);
+ }
+ }, {
+ key: 'getMonitorAuthChange',
+ value: function getMonitorAuthChange() {
+ return this.getValue('monitorAuthChange');
+ }
+ }, {
+ key: 'setMonitorAuthChange',
+ value: function setMonitorAuthChange() {
+ var monitorAuthChange = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
+
+ return this.setValue('monitorAuthChange', monitorAuthChange === true);
+ }
+ }]);
+
+ return Configuration;
+})();
+
+exports.Configuration = Configuration;
\ No newline at end of file
diff --git a/dist/commonjs/events.js b/dist/commonjs/events.js
new file mode 100644
index 0000000..e1e2b60
--- /dev/null
+++ b/dist/commonjs/events.js
@@ -0,0 +1,172 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var _aureliaDependencyInjection = require('aurelia-dependency-injection');
+
+var _aureliaEventAggregator = require('aurelia-event-aggregator');
+
+var FirebaseEvent = function FirebaseEvent() {
+ _classCallCheck(this, FirebaseEvent);
+
+ this.handled = false;
+};
+
+var UserEvent = (function (_FirebaseEvent) {
+ _inherits(UserEvent, _FirebaseEvent);
+
+ function UserEvent() {
+ var uid = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
+
+ _classCallCheck(this, UserEvent);
+
+ _get(Object.getPrototypeOf(UserEvent.prototype), 'constructor', this).call(this);
+ this.uid = uid;
+ }
+
+ return UserEvent;
+})(FirebaseEvent);
+
+var UserCreatedEvent = (function (_UserEvent) {
+ _inherits(UserCreatedEvent, _UserEvent);
+
+ function UserCreatedEvent(data) {
+ _classCallCheck(this, UserCreatedEvent);
+
+ _get(Object.getPrototypeOf(UserCreatedEvent.prototype), 'constructor', this).call(this, data.uid);
+ this.email = data.email;
+ }
+
+ return UserCreatedEvent;
+})(UserEvent);
+
+exports.UserCreatedEvent = UserCreatedEvent;
+
+var UserSignedInEvent = (function (_UserEvent2) {
+ _inherits(UserSignedInEvent, _UserEvent2);
+
+ function UserSignedInEvent(data) {
+ _classCallCheck(this, UserSignedInEvent);
+
+ _get(Object.getPrototypeOf(UserSignedInEvent.prototype), 'constructor', this).call(this, data.uid);
+ this.provider = data.provider;
+ this.email = data.email;
+ }
+
+ return UserSignedInEvent;
+})(UserEvent);
+
+exports.UserSignedInEvent = UserSignedInEvent;
+
+var UserSignedOutEvent = (function (_UserEvent3) {
+ _inherits(UserSignedOutEvent, _UserEvent3);
+
+ function UserSignedOutEvent(data) {
+ _classCallCheck(this, UserSignedOutEvent);
+
+ _get(Object.getPrototypeOf(UserSignedOutEvent.prototype), 'constructor', this).call(this);
+ this.email = data.email;
+ }
+
+ return UserSignedOutEvent;
+})(UserEvent);
+
+exports.UserSignedOutEvent = UserSignedOutEvent;
+
+var UserEmailChangedEvent = (function (_UserEvent4) {
+ _inherits(UserEmailChangedEvent, _UserEvent4);
+
+ function UserEmailChangedEvent(data) {
+ _classCallCheck(this, UserEmailChangedEvent);
+
+ _get(Object.getPrototypeOf(UserEmailChangedEvent.prototype), 'constructor', this).call(this);
+ this.oldEmail = data.oldEmail;
+ this.newEmail = data.newEmail;
+ }
+
+ return UserEmailChangedEvent;
+})(UserEvent);
+
+exports.UserEmailChangedEvent = UserEmailChangedEvent;
+
+var UserPasswordChangedEvent = (function (_UserEvent5) {
+ _inherits(UserPasswordChangedEvent, _UserEvent5);
+
+ function UserPasswordChangedEvent(data) {
+ _classCallCheck(this, UserPasswordChangedEvent);
+
+ _get(Object.getPrototypeOf(UserPasswordChangedEvent.prototype), 'constructor', this).call(this);
+ this.email = data.email;
+ }
+
+ return UserPasswordChangedEvent;
+})(UserEvent);
+
+exports.UserPasswordChangedEvent = UserPasswordChangedEvent;
+
+var UserDeletedEvent = (function (_UserEvent6) {
+ _inherits(UserDeletedEvent, _UserEvent6);
+
+ function UserDeletedEvent(data) {
+ _classCallCheck(this, UserDeletedEvent);
+
+ _get(Object.getPrototypeOf(UserDeletedEvent.prototype), 'constructor', this).call(this);
+ this.email = data.email;
+ }
+
+ return UserDeletedEvent;
+})(UserEvent);
+
+exports.UserDeletedEvent = UserDeletedEvent;
+
+var UserAuthStateChangedEvent = (function (_UserEvent7) {
+ _inherits(UserAuthStateChangedEvent, _UserEvent7);
+
+ function UserAuthStateChangedEvent(data) {
+ _classCallCheck(this, UserAuthStateChangedEvent);
+
+ data = data || {};
+ _get(Object.getPrototypeOf(UserAuthStateChangedEvent.prototype), 'constructor', this).call(this, data.uid);
+ this.provider = data.provider || null;
+ this.auth = data.auth || null;
+ this.expires = data.expires || 0;
+ }
+
+ return UserAuthStateChangedEvent;
+})(UserEvent);
+
+exports.UserAuthStateChangedEvent = UserAuthStateChangedEvent;
+
+var Publisher = (function () {
+ function Publisher(eventAggregator) {
+ _classCallCheck(this, _Publisher);
+
+ this._eventAggregator = eventAggregator;
+ }
+
+ _createClass(Publisher, [{
+ key: 'publish',
+ value: function publish(event) {
+ if (event.handled) {
+ return;
+ }
+ this._eventAggregator.publish(event);
+ }
+ }]);
+
+ var _Publisher = Publisher;
+ Publisher = (0, _aureliaDependencyInjection.inject)(_aureliaEventAggregator.EventAggregator)(Publisher) || Publisher;
+ return Publisher;
+})();
+
+exports.Publisher = Publisher;
\ No newline at end of file
diff --git a/dist/commonjs/index.js b/dist/commonjs/index.js
new file mode 100644
index 0000000..64ac3c4
--- /dev/null
+++ b/dist/commonjs/index.js
@@ -0,0 +1,59 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+exports.configure = configure;
+
+function _interopExportWildcard(obj, defaults) { var newObj = defaults({}, obj); delete newObj['default']; return newObj; }
+
+function _defaults(obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; }
+
+var _configuration = require('./configuration');
+
+Object.defineProperty(exports, 'Configuration', {
+ enumerable: true,
+ get: function get() {
+ return _configuration.Configuration;
+ }
+});
+
+var _user = require('./user');
+
+Object.defineProperty(exports, 'User', {
+ enumerable: true,
+ get: function get() {
+ return _user.User;
+ }
+});
+
+var _authentication = require('./authentication');
+
+Object.defineProperty(exports, 'AuthenticationManager', {
+ enumerable: true,
+ get: function get() {
+ return _authentication.AuthenticationManager;
+ }
+});
+
+var _collection = require('./collection');
+
+Object.defineProperty(exports, 'ReactiveCollection', {
+ enumerable: true,
+ get: function get() {
+ return _collection.ReactiveCollection;
+ }
+});
+
+var _events = require('./events');
+
+_defaults(exports, _interopExportWildcard(_events, _defaults));
+
+function configure(aurelia, configCallback) {
+ var config = new _configuration.Configuration(_configuration.Configuration.defaults);
+
+ if (configCallback !== undefined && typeof configCallback === 'function') {
+ configCallback(config);
+ }
+ aurelia.instance(_configuration.Configuration, config);
+}
\ No newline at end of file
diff --git a/dist/commonjs/user.js b/dist/commonjs/user.js
new file mode 100644
index 0000000..35fb719
--- /dev/null
+++ b/dist/commonjs/user.js
@@ -0,0 +1,61 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var User = (function () {
+ _createClass(User, [{
+ key: "isAuthenticated",
+ get: function get() {
+ return this.token && this.auth && this.expires > 0 || false;
+ }
+ }]);
+
+ function User() {
+ var userData = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
+
+ _classCallCheck(this, User);
+
+ this.uid = null;
+ this.provider = null;
+ this.token = null;
+ this.auth = null;
+ this.expires = 0;
+ this.email = null;
+ this.isTemporaryPassword = null;
+ this.profileImageUrl = null;
+
+ this.update(userData);
+ }
+
+ _createClass(User, [{
+ key: "update",
+ value: function update(userData) {
+ userData = userData || {};
+ this.uid = userData.uid || null;
+ this.provider = userData.provider || null;
+ this.token = userData.token || null;
+ this.auth = userData.auth || null;
+ this.expires = userData.expires || 0;
+
+ userData.password = userData.password || {};
+ this.isTemporaryPassword = userData.password.isTemporaryPassword || false;
+ this.profileImageUrl = userData.password.profileImageURL || null;
+ this.email = userData.password.email || null;
+ }
+ }, {
+ key: "reset",
+ value: function reset() {
+ this.update({});
+ }
+ }]);
+
+ return User;
+})();
+
+exports.User = User;
\ No newline at end of file
diff --git a/dist/es6/authentication.js b/dist/es6/authentication.js
new file mode 100644
index 0000000..69bd028
--- /dev/null
+++ b/dist/es6/authentication.js
@@ -0,0 +1,177 @@
+import Promise from 'bluebird';
+import Firebase from 'firebase';
+import {inject} from 'aurelia-dependency-injection';
+
+import * as events from './events';
+import {User} from './user';
+import {Configuration} from './configuration';
+
+/**
+ * Handles Firebase authentication features
+ */
+@inject(Configuration, events.Publisher)
+export class AuthenticationManager {
+
+ _firebase = null;
+ _publisher = null;
+ currentUser = null;
+
+ /**
+ * Initializes a new instance of the AuthenticationManager
+ * @param {Configuration} configuration - The configuration to use
+ * @param {Publisher} publisher - The publisher used to broadcast system wide user events
+ */
+ constructor(
+ configuration: Configuration,
+ publisher: Publisher) {
+ this._firebase = new Firebase(configuration.getFirebaseUrl());
+ this._publisher = publisher;
+ this.currentUser = new User();
+
+ // Register auth state changed event
+ // This will handle user data update now and in the future.
+ if (configuration.getMonitorAuthChange() === true) {
+ this._firebase.onAuth((result) => {
+ this._onUserAuthStateChanged(result);
+ }, this);
+ }
+ }
+
+ /**
+ * Creates a new user but does not authenticate him.
+ * @param {string} email - The user email
+ * @param {string} password - The user password
+ * @returns {Promise} - Returns a promise which on completion will return the user infos
+ */
+ createUser(email, password) : Promise {
+ return new Promise((resolve, reject) => {
+ this._firebase.createUser({email: email, password: password}, (error, result) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ let user = new User(result);
+ user.email = user.email || email; // Because firebase result doesn't provide the email
+ this._publisher.publish(new events.UserCreatedEvent(user));
+ resolve(user);
+ });
+ });
+ }
+
+ /**
+ * Sign in a user with a password.
+ * @param {string} email - The user email
+ * @param {string} password - The user password
+ * @returns {Promise} Returns a promise which on completion will return user infos
+ */
+ signIn(email, password) : Promise {
+ return new Promise((resolve, reject) => {
+ this._firebase.authWithPassword({email: email, password: password}, (error, result) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ let user = new User(result);
+ this._publisher.publish(new events.UserSignedInEvent(user));
+ resolve(user);
+ });
+ });
+ }
+
+ /**
+ * Creates a user and automatically sign in if creation succeed
+ * @param {string} email - The user email
+ * @param {string} password - The user password
+ * @returns {Promise} - Returns a promise which on completion will return user infos
+ */
+ createUserAndSignIn(email, password) : Promise {
+ return this.createUser(email, password).then(() => {
+ return this.signIn(email, password);
+ });
+ }
+
+ /**
+ * Sign out any authenticated user
+ * @returns {Promise} - Returns a promise
+ */
+ signOut() : Promise {
+ return new Promise((resolve) => {
+ this._firebase.unauth();
+ this.currentUser.reset();
+ resolve();
+ });
+ }
+
+ /**
+ * Changes the user email.
+ * User will be disconnected upon email change.
+ * @param {string} oldEmail - The current user email (email to be changed)
+ * @param {string} password - The current user password
+ * @param {string} newEmail - The new email
+ * @returns {Promise} - Returns a promise which on completion will return an object containing the old and new email
+ */
+ changeEmail(oldEmail, password, newEmail) : Promise {
+ return new Promise((resolve, reject) => {
+ this._firebase.changeEmail({oldEmail: oldEmail, password: password, newEmail: newEmail}, (error) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ this.currentUser.email = newEmail;
+ let result = {oldEmail: oldEmail, newEmail: newEmail};
+ this._publisher.publish(new events.UserEmailChangedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+
+ /**
+ * Changes the user password
+ * @param {string} email - The email of the user to change the password
+ * @param {string} oldPassword - The current password
+ * @param {string} newPassword - The new password
+ */
+ changePassword(email, oldPassword, newPassword) : Promise {
+ return new Promise((resolve, reject) => {
+ this._firebase.changePassword({email: email, oldPassword: oldPassword, newPassword: newPassword}, (error) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ let result = {email: email};
+ this._publisher.publish(new events.UserPasswordChangedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+
+ /**
+ * Deletes a user account
+ * @param {string} email - The users's email
+ * @param {string} password - The user's password
+ */
+ deleteUser(email: string, password: string) : Promise {
+ return new Promise((resolve, reject) => {
+ this._firebase.removeUser({email: email, password: password}, (error) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ this.currentUser.reset();
+ let result = {email: email};
+ this._publisher.publish(new events.UserDeletedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+
+ _onUserAuthStateChanged(authData) {
+ this.currentUser.update(authData);
+ this._publisher.publish(new events.UserAuthStateChangedEvent(authData));
+ }
+}
diff --git a/dist/es6/collection.js b/dist/es6/collection.js
new file mode 100644
index 0000000..9f9675c
--- /dev/null
+++ b/dist/es6/collection.js
@@ -0,0 +1,164 @@
+import Promise from 'bluebird';
+import Firebase from 'firebase';
+import {Container} from 'aurelia-dependency-injection';
+import {Configuration} from './configuration';
+
+export class ReactiveCollection {
+
+ _query = null;
+ _valueMap = new Map();
+ items = [];
+
+ constructor(path: Array) {
+ if (!Container || !Container.instance) throw Error('Container has not been made global');
+ let config = Container.instance.get(Configuration);
+ if (!config) throw Error('Configuration has not been set');
+
+ this._query = new Firebase(ReactiveCollection._getChildLocation(
+ config.getFirebaseUrl(),
+ path));
+ this._listenToQuery(this._query);
+ }
+
+ add(item:any) : Promise {
+ return new Promise((resolve, reject) => {
+ let query = this._query.ref().push();
+ query.set(item, (error) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(item);
+ });
+ });
+ }
+
+ remove(item: any): Promise {
+ if (item === null || item.__firebaseKey__ === null) {
+ return Promise.reject({message: 'Unknown item'});
+ }
+ return this.removeByKey(item.__firebaseKey__);
+ }
+
+ getByKey(key): any {
+ return this._valueMap.get(key);
+ }
+
+ removeByKey(key) {
+ return new Promise((resolve, reject) => {
+ this._query.ref().child(key).remove((error) =>{
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(key);
+ });
+ });
+ }
+
+ clear() {
+ //this._stopListeningToQuery(this._query);
+ return new Promise((resolve, reject) => {
+ let query = this._query.ref();
+ query.remove((error) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve();
+ });
+ });
+ }
+
+ _listenToQuery(query) {
+ query.on('child_added', (snapshot, previousKey) => {
+ this._onItemAdded(snapshot, previousKey);
+ });
+ query.on('child_removed', (snapshot) => {
+ this._onItemRemoved(snapshot);
+ });
+ query.on('child_changed', (snapshot, previousKey) => {
+ this._onItemChanded(snapshot, previousKey);
+ });
+ query.on('child_moved', (snapshot, previousKey) => {
+ this._onItemMoved(snapshot, previousKey);
+ });
+ }
+
+ _stopListeningToQuery(query) {
+ query.off();
+ }
+
+ _onItemAdded(snapshot, previousKey) {
+ let value = this._valueFromSnapshot(snapshot);
+ let index = previousKey !== null ?
+ this.items.indexOf(this._valueMap.get(previousKey)) + 1 : 0;
+ this._valueMap.set(value.__firebaseKey__, value);
+ this.items.splice(index, 0, value);
+ }
+
+ _onItemRemoved(oldSnapshot) {
+ let key = oldSnapshot.key();
+ let value = this._valueMap.get(key);
+
+ if (!value) {
+ return;
+ }
+
+ let index = this.items.indexOf(value);
+ this._valueMap.delete(key);
+ if (index !== -1) {
+ this.items.splice(index, 1);
+ }
+ }
+
+ _onItemChanged(snapshot, previousKey) {
+ let value = this._valueFromSnapshot(snapshot);
+ let oldValue = this._valueMap.get(value.__firebaseKey__);
+
+ if (!oldValue) {
+ return;
+ }
+
+ this._valueMap.delete(oldValue.__firebaseKey__);
+ this._valueMap.set(value.__firebaseKey__, value);
+ this.items.splice(this.items.indexOf(oldValue), 1, value);
+ }
+
+ _onItemMoved(snapshot, previousKey) {
+ let key = snapshot.key();
+ let value = this._valueMap.get(key);
+
+ if (!value) {
+ return;
+ }
+
+ let previousValue = this._valueMap.get(previousKey);
+ let newIndex = previousValue !== null ? this.items.indexOf(previousValue) + 1 : 0;
+ this.items.splice(this.items.indexOf(value), 1);
+ this.items.splice(newIndex, 0, value);
+ }
+
+ _valueFromSnapshot(snapshot) {
+ let value = snapshot.val();
+ if (!(value instanceof Object)) {
+ value = {
+ value: value,
+ __firebasePrimitive__: true
+ };
+ }
+ value.__firebaseKey__ = snapshot.key();
+ return value;
+ }
+
+ static _getChildLocation(root: string, path: Array) {
+ if (!path) {
+ return root;
+ }
+ if (!root.endsWith('/')) {
+ root = root + '/';
+ }
+
+ return root + (Array.isArray(path) ? path.join('/') : path);
+ }
+}
diff --git a/dist/es6/configuration.js b/dist/es6/configuration.js
new file mode 100644
index 0000000..a7e5931
--- /dev/null
+++ b/dist/es6/configuration.js
@@ -0,0 +1,93 @@
+/**
+ * The default configuration
+ */
+export class ConfigurationDefaults {
+
+}
+
+ConfigurationDefaults._defaults = {
+ firebaseUrl: null,
+ monitorAuthChange: false
+};
+
+ConfigurationDefaults.defaults = function() {
+ let defaults = {};
+ Object.assign(defaults, ConfigurationDefaults._defaults);
+ return defaults;
+};
+
+/**
+ * Configuration class used by the plugin
+ */
+export class Configuration {
+
+ /**
+ * Initializes a new instance of the Configuration class
+ * @param {Object} innerConfig - The optional initial configuration values. If not provided will initialize using the defaults.
+ */
+ constructor(innerConfig) {
+ this.innerConfig = innerConfig;
+ this.values = this.innerConfig ? {} : ConfigurationDefaults.defaults();
+ }
+
+ /**
+ * Gets the value of a configuration option by its identifier
+ * @param {string} identifier - The configuration option identifier
+ * @returns {any} - The value of the configuration option
+ * @throws {Error} - When configuration option is not found
+ */
+ getValue(identifier) {
+ if (this.values.hasOwnProperty(identifier) !== null && this.values[identifier] !== undefined) {
+ return this.values[identifier];
+ }
+ if (this.innerConfig !== null) {
+ return this.innerConfig.getValue(identifier);
+ }
+ throw new Error('Config not found: ' + identifier);
+ }
+
+ /**
+ * Sets the value of a configuration option
+ * @param {string} identifier - The key used to store the configuration option value
+ * @param {any} value - The value of the configuration option
+ * @returns {Configuration} - The current configuration instance (Fluent API)
+ */
+ setValue(identifier, value) {
+ this.values[identifier] = value;
+ return this; // fluent API
+ }
+
+ /**
+ * Gets the value of the firebaseUrl configuration option
+ * @returns {string} - The value of the firebaseUrl configuration option
+ */
+ getFirebaseUrl() {
+ return this.getValue('firebaseUrl');
+ }
+
+ /**
+ * Sets the value of the firebaseUrl configuration option
+ * @param {string} firebaseUrl - An URL to a valid Firebase location
+ * @returns {Configuration} - Returns the configuration instance (fluent API)
+ */
+ setFirebaseUrl(firebaseUrl) {
+ return this.setValue('firebaseUrl', firebaseUrl);
+ }
+
+ /**
+ * Gets the value of the monitorAuthChange configuration option
+ * @returns {boolean} - The value of the monitorAuthChange configuration option
+ */
+ getMonitorAuthChange() {
+ return this.getValue('monitorAuthChange');
+ }
+
+ /**
+ * Sets the value of the monitorAuthChange configuration option
+ * @param monitorAuthChange
+ * @returns {Configuration} - Returns the configuration instance (fluent API)
+ */
+ setMonitorAuthChange(monitorAuthChange: boolean = true) {
+ return this.setValue('monitorAuthChange', monitorAuthChange === true);
+ }
+}
diff --git a/dist/es6/events.js b/dist/es6/events.js
new file mode 100644
index 0000000..8ef4e87
--- /dev/null
+++ b/dist/es6/events.js
@@ -0,0 +1,130 @@
+import {inject} from 'aurelia-dependency-injection';
+import {EventAggregator} from 'aurelia-event-aggregator';
+
+class FirebaseEvent {
+ handled = false;
+
+ constructor() {
+ }
+}
+
+class UserEvent extends FirebaseEvent {
+ uid: string;
+
+ constructor(uid: string = null) {
+ super();
+ this.uid = uid;
+ }
+}
+
+/**
+ * An event triggered when a user is created
+ */
+export class UserCreatedEvent extends UserEvent {
+ email:string;
+ constructor(data: Object) {
+ super(data.uid);
+ this.email = data.email;
+ }
+}
+
+/**
+ * An event triggered when a user signed in
+ */
+export class UserSignedInEvent extends UserEvent {
+ provider: string;
+ email: string;
+ profileImageUrl:string;
+
+ constructor(data: Object) {
+ super(data.uid);
+ this.provider = data.provider;
+ this.email = data.email;
+ }
+}
+
+/**
+ * An event triggered when a user signed out
+ */
+export class UserSignedOutEvent extends UserEvent {
+ email: string;
+ constructor(data: Object) {
+ super();
+ this.email = data.email;
+ }
+}
+
+/**
+ * An event triggered when a user's email has changed
+ */
+export class UserEmailChangedEvent extends UserEvent {
+ oldEmail: string;
+ newEmail: string;
+ constructor(data: Object) {
+ super();
+ this.oldEmail = data.oldEmail;
+ this.newEmail = data.newEmail;
+ }
+}
+
+/**
+ * An event triggered when a user's password has changed
+ */
+export class UserPasswordChangedEvent extends UserEvent {
+ email:string;
+ constructor(data: Object) {
+ super();
+ this.email = data.email;
+ }
+}
+
+/**
+ * An event triggered when a user has been deleted
+ */
+export class UserDeletedEvent extends UserEvent {
+ email: string;
+ constructor(data: Object) {
+ super();
+ this.email = data.email;
+ }
+}
+
+/**
+ * An event triggered when a user authentication state has changed
+ */
+export class UserAuthStateChangedEvent extends UserEvent {
+ provider: string;
+ auth: any;
+ expires: number;
+
+ constructor(data: Object) {
+ data = data || {};
+ super(data.uid);
+ this.provider = data.provider || null;
+ this.auth = data.auth || null;
+ this.expires = data.expires || 0;
+ }
+}
+
+/**
+ * Handles publishing events in the system
+ */
+@inject(EventAggregator)
+export class Publisher {
+ _eventAggregator;
+
+ constructor(eventAggregator: EventAggregator) {
+ this._eventAggregator = eventAggregator;
+ }
+
+ /**
+ * Publish an event
+ * @param {FirebaseEvent} event - The event to publish
+ */
+ publish(event: FirebaseEvent) {
+ if (event.handled) {
+ return;
+ }
+ this._eventAggregator.publish(event);
+ }
+}
diff --git a/dist/es6/index.js b/dist/es6/index.js
new file mode 100644
index 0000000..62e93a1
--- /dev/null
+++ b/dist/es6/index.js
@@ -0,0 +1,16 @@
+import {Configuration} from './configuration';
+
+export {Configuration} from './configuration';
+export {User} from './user';
+export {AuthenticationManager} from './authentication';
+export {ReactiveCollection} from './collection';
+export * from './events';
+
+export function configure(aurelia: Object, configCallback: Function) {
+ let config = new Configuration(Configuration.defaults);
+
+ if (configCallback !== undefined && typeof configCallback === 'function') {
+ configCallback(config);
+ }
+ aurelia.instance(Configuration, config);
+}
diff --git a/dist/es6/user.js b/dist/es6/user.js
new file mode 100644
index 0000000..507cb6e
--- /dev/null
+++ b/dist/es6/user.js
@@ -0,0 +1,96 @@
+/**
+ * Represents a Firebase User
+ */
+export class User {
+
+ /**
+ * A unique user ID, intented as the user's unique key accross all providers
+ * @type {string}
+ */
+ uid = null;
+
+ /**
+ * The authentication method used
+ * @type {string}
+ */
+ provider = null;
+
+ /**
+ * The Firebase authentication token for this session
+ * @type {string}
+ */
+ token = null;
+
+ /**
+ * The contents of the authentication token
+ * @type {Object}
+ */
+ auth = null;
+
+ /**
+ * A timestamp, in seconds since UNIX epoch, indicated when the authentication token expires
+ * @type {number}
+ */
+ expires = 0;
+
+ /**
+ * The user's email address
+ * @type {string}
+ */
+ email = null;
+
+ /**
+ * Whether or not the user authenticated using a temporary password,
+ * as used in password reset flows.
+ * @type {boolean}
+ */
+ isTemporaryPassword = null;
+
+ /**
+ * The URL to the user's Gravatar profile image
+ * @type {string}
+ */
+ profileImageUrl = null;
+
+ /**
+ * Whether or not the user is authenticated
+ * @type {boolean} True is the user is authenticated, false otherwise.
+ */
+ get isAuthenticated() {
+ return (this.token && this.auth && this.expires > 0) || false;
+ }
+
+ /**
+ * Initializes a new instance of user
+ * @param userData {Object} Optional object containing data
+ * to initialize this user with.
+ */
+ constructor(userData: Object = null) {
+ this.update(userData);
+ }
+
+ /**
+ * Update the current user instance with the provided data
+ * @param userData {Object} An object containing the data
+ */
+ update(userData: Object) {
+ userData = userData || {};
+ this.uid = userData.uid || null;
+ this.provider = userData.provider || null;
+ this.token = userData.token || null;
+ this.auth = userData.auth || null;
+ this.expires = userData.expires || 0;
+
+ userData.password = userData.password || {};
+ this.isTemporaryPassword = userData.password.isTemporaryPassword || false;
+ this.profileImageUrl = userData.password.profileImageURL || null;
+ this.email = userData.password.email || null;
+ }
+
+ /**
+ * Reinitializes the current user instance.
+ */
+ reset() {
+ this.update({});
+ }
+}
diff --git a/dist/system/authentication.js b/dist/system/authentication.js
new file mode 100644
index 0000000..95ebc37
--- /dev/null
+++ b/dist/system/authentication.js
@@ -0,0 +1,175 @@
+System.register(['bluebird', 'firebase', 'aurelia-dependency-injection', './events', './user', './configuration'], function (_export) {
+ 'use strict';
+
+ var Promise, Firebase, inject, events, User, Configuration, AuthenticationManager;
+
+ var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+ return {
+ setters: [function (_bluebird) {
+ Promise = _bluebird['default'];
+ }, function (_firebase) {
+ Firebase = _firebase['default'];
+ }, function (_aureliaDependencyInjection) {
+ inject = _aureliaDependencyInjection.inject;
+ }, function (_events) {
+ events = _events;
+ }, function (_user) {
+ User = _user.User;
+ }, function (_configuration) {
+ Configuration = _configuration.Configuration;
+ }],
+ execute: function () {
+ AuthenticationManager = (function () {
+ function AuthenticationManager(configuration, publisher) {
+ var _this = this;
+
+ _classCallCheck(this, _AuthenticationManager);
+
+ this._firebase = null;
+ this._publisher = null;
+ this.currentUser = null;
+
+ this._firebase = new Firebase(configuration.getFirebaseUrl());
+ this._publisher = publisher;
+ this.currentUser = new User();
+
+ if (configuration.getMonitorAuthChange() === true) {
+ this._firebase.onAuth(function (result) {
+ _this._onUserAuthStateChanged(result);
+ }, this);
+ }
+ }
+
+ _createClass(AuthenticationManager, [{
+ key: 'createUser',
+ value: function createUser(email, password) {
+ var _this2 = this;
+
+ return new Promise(function (resolve, reject) {
+ _this2._firebase.createUser({ email: email, password: password }, function (error, result) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ var user = new User(result);
+ user.email = user.email || email;
+ _this2._publisher.publish(new events.UserCreatedEvent(user));
+ resolve(user);
+ });
+ });
+ }
+ }, {
+ key: 'signIn',
+ value: function signIn(email, password) {
+ var _this3 = this;
+
+ return new Promise(function (resolve, reject) {
+ _this3._firebase.authWithPassword({ email: email, password: password }, function (error, result) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ var user = new User(result);
+ _this3._publisher.publish(new events.UserSignedInEvent(user));
+ resolve(user);
+ });
+ });
+ }
+ }, {
+ key: 'createUserAndSignIn',
+ value: function createUserAndSignIn(email, password) {
+ var _this4 = this;
+
+ return this.createUser(email, password).then(function () {
+ return _this4.signIn(email, password);
+ });
+ }
+ }, {
+ key: 'signOut',
+ value: function signOut() {
+ var _this5 = this;
+
+ return new Promise(function (resolve) {
+ _this5._firebase.unauth();
+ _this5.currentUser.reset();
+ resolve();
+ });
+ }
+ }, {
+ key: 'changeEmail',
+ value: function changeEmail(oldEmail, password, newEmail) {
+ var _this6 = this;
+
+ return new Promise(function (resolve, reject) {
+ _this6._firebase.changeEmail({ oldEmail: oldEmail, password: password, newEmail: newEmail }, function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ _this6.currentUser.email = newEmail;
+ var result = { oldEmail: oldEmail, newEmail: newEmail };
+ _this6._publisher.publish(new events.UserEmailChangedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+ }, {
+ key: 'changePassword',
+ value: function changePassword(email, oldPassword, newPassword) {
+ var _this7 = this;
+
+ return new Promise(function (resolve, reject) {
+ _this7._firebase.changePassword({ email: email, oldPassword: oldPassword, newPassword: newPassword }, function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ var result = { email: email };
+ _this7._publisher.publish(new events.UserPasswordChangedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+ }, {
+ key: 'deleteUser',
+ value: function deleteUser(email, password) {
+ var _this8 = this;
+
+ return new Promise(function (resolve, reject) {
+ _this8._firebase.removeUser({ email: email, password: password }, function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ _this8.currentUser.reset();
+ var result = { email: email };
+ _this8._publisher.publish(new events.UserDeletedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+ }, {
+ key: '_onUserAuthStateChanged',
+ value: function _onUserAuthStateChanged(authData) {
+ this.currentUser.update(authData);
+ this._publisher.publish(new events.UserAuthStateChangedEvent(authData));
+ }
+ }]);
+
+ var _AuthenticationManager = AuthenticationManager;
+ AuthenticationManager = inject(Configuration, events.Publisher)(AuthenticationManager) || AuthenticationManager;
+ return AuthenticationManager;
+ })();
+
+ _export('AuthenticationManager', AuthenticationManager);
+ }
+ };
+});
\ No newline at end of file
diff --git a/dist/system/collection.js b/dist/system/collection.js
new file mode 100644
index 0000000..a9d9f61
--- /dev/null
+++ b/dist/system/collection.js
@@ -0,0 +1,206 @@
+System.register(['bluebird', 'firebase', 'aurelia-dependency-injection', './configuration'], function (_export) {
+ 'use strict';
+
+ var Promise, Firebase, Container, Configuration, ReactiveCollection;
+
+ var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+ return {
+ setters: [function (_bluebird) {
+ Promise = _bluebird['default'];
+ }, function (_firebase) {
+ Firebase = _firebase['default'];
+ }, function (_aureliaDependencyInjection) {
+ Container = _aureliaDependencyInjection.Container;
+ }, function (_configuration) {
+ Configuration = _configuration.Configuration;
+ }],
+ execute: function () {
+ ReactiveCollection = (function () {
+ function ReactiveCollection(path) {
+ _classCallCheck(this, ReactiveCollection);
+
+ this._query = null;
+ this._valueMap = new Map();
+ this.items = [];
+
+ if (!Container || !Container.instance) throw Error('Container has not been made global');
+ var config = Container.instance.get(Configuration);
+ if (!config) throw Error('Configuration has not been set');
+
+ this._query = new Firebase(ReactiveCollection._getChildLocation(config.getFirebaseUrl(), path));
+ this._listenToQuery(this._query);
+ }
+
+ _createClass(ReactiveCollection, [{
+ key: 'add',
+ value: function add(item) {
+ var _this = this;
+
+ return new Promise(function (resolve, reject) {
+ var query = _this._query.ref().push();
+ query.set(item, function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(item);
+ });
+ });
+ }
+ }, {
+ key: 'remove',
+ value: function remove(item) {
+ if (item === null || item.__firebaseKey__ === null) {
+ return Promise.reject({ message: 'Unknown item' });
+ }
+ return this.removeByKey(item.__firebaseKey__);
+ }
+ }, {
+ key: 'getByKey',
+ value: function getByKey(key) {
+ return this._valueMap.get(key);
+ }
+ }, {
+ key: 'removeByKey',
+ value: function removeByKey(key) {
+ var _this2 = this;
+
+ return new Promise(function (resolve, reject) {
+ _this2._query.ref().child(key).remove(function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(key);
+ });
+ });
+ }
+ }, {
+ key: 'clear',
+ value: function clear() {
+ var _this3 = this;
+
+ return new Promise(function (resolve, reject) {
+ var query = _this3._query.ref();
+ query.remove(function (error) {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve();
+ });
+ });
+ }
+ }, {
+ key: '_listenToQuery',
+ value: function _listenToQuery(query) {
+ var _this4 = this;
+
+ query.on('child_added', function (snapshot, previousKey) {
+ _this4._onItemAdded(snapshot, previousKey);
+ });
+ query.on('child_removed', function (snapshot) {
+ _this4._onItemRemoved(snapshot);
+ });
+ query.on('child_changed', function (snapshot, previousKey) {
+ _this4._onItemChanded(snapshot, previousKey);
+ });
+ query.on('child_moved', function (snapshot, previousKey) {
+ _this4._onItemMoved(snapshot, previousKey);
+ });
+ }
+ }, {
+ key: '_stopListeningToQuery',
+ value: function _stopListeningToQuery(query) {
+ query.off();
+ }
+ }, {
+ key: '_onItemAdded',
+ value: function _onItemAdded(snapshot, previousKey) {
+ var value = this._valueFromSnapshot(snapshot);
+ var index = previousKey !== null ? this.items.indexOf(this._valueMap.get(previousKey)) + 1 : 0;
+ this._valueMap.set(value.__firebaseKey__, value);
+ this.items.splice(index, 0, value);
+ }
+ }, {
+ key: '_onItemRemoved',
+ value: function _onItemRemoved(oldSnapshot) {
+ var key = oldSnapshot.key();
+ var value = this._valueMap.get(key);
+
+ if (!value) {
+ return;
+ }
+
+ var index = this.items.indexOf(value);
+ this._valueMap['delete'](key);
+ if (index !== -1) {
+ this.items.splice(index, 1);
+ }
+ }
+ }, {
+ key: '_onItemChanged',
+ value: function _onItemChanged(snapshot, previousKey) {
+ var value = this._valueFromSnapshot(snapshot);
+ var oldValue = this._valueMap.get(value.__firebaseKey__);
+
+ if (!oldValue) {
+ return;
+ }
+
+ this._valueMap['delete'](oldValue.__firebaseKey__);
+ this._valueMap.set(value.__firebaseKey__, value);
+ this.items.splice(this.items.indexOf(oldValue), 1, value);
+ }
+ }, {
+ key: '_onItemMoved',
+ value: function _onItemMoved(snapshot, previousKey) {
+ var key = snapshot.key();
+ var value = this._valueMap.get(key);
+
+ if (!value) {
+ return;
+ }
+
+ var previousValue = this._valueMap.get(previousKey);
+ var newIndex = previousValue !== null ? this.items.indexOf(previousValue) + 1 : 0;
+ this.items.splice(this.items.indexOf(value), 1);
+ this.items.splice(newIndex, 0, value);
+ }
+ }, {
+ key: '_valueFromSnapshot',
+ value: function _valueFromSnapshot(snapshot) {
+ var value = snapshot.val();
+ if (!(value instanceof Object)) {
+ value = {
+ value: value,
+ __firebasePrimitive__: true
+ };
+ }
+ value.__firebaseKey__ = snapshot.key();
+ return value;
+ }
+ }], [{
+ key: '_getChildLocation',
+ value: function _getChildLocation(root, path) {
+ if (!path) {
+ return root;
+ }
+ if (!root.endsWith('/')) {
+ root = root + '/';
+ }
+
+ return root + (Array.isArray(path) ? path.join('/') : path);
+ }
+ }]);
+
+ return ReactiveCollection;
+ })();
+
+ _export('ReactiveCollection', ReactiveCollection);
+ }
+ };
+});
\ No newline at end of file
diff --git a/dist/system/configuration.js b/dist/system/configuration.js
new file mode 100644
index 0000000..35818e6
--- /dev/null
+++ b/dist/system/configuration.js
@@ -0,0 +1,85 @@
+System.register([], function (_export) {
+ 'use strict';
+
+ var ConfigurationDefaults, Configuration;
+
+ var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+ return {
+ setters: [],
+ execute: function () {
+ ConfigurationDefaults = function ConfigurationDefaults() {
+ _classCallCheck(this, ConfigurationDefaults);
+ };
+
+ _export('ConfigurationDefaults', ConfigurationDefaults);
+
+ ConfigurationDefaults._defaults = {
+ firebaseUrl: null,
+ monitorAuthChange: false
+ };
+
+ ConfigurationDefaults.defaults = function () {
+ var defaults = {};
+ Object.assign(defaults, ConfigurationDefaults._defaults);
+ return defaults;
+ };
+
+ Configuration = (function () {
+ function Configuration(innerConfig) {
+ _classCallCheck(this, Configuration);
+
+ this.innerConfig = innerConfig;
+ this.values = this.innerConfig ? {} : ConfigurationDefaults.defaults();
+ }
+
+ _createClass(Configuration, [{
+ key: 'getValue',
+ value: function getValue(identifier) {
+ if (this.values.hasOwnProperty(identifier) !== null && this.values[identifier] !== undefined) {
+ return this.values[identifier];
+ }
+ if (this.innerConfig !== null) {
+ return this.innerConfig.getValue(identifier);
+ }
+ throw new Error('Config not found: ' + identifier);
+ }
+ }, {
+ key: 'setValue',
+ value: function setValue(identifier, value) {
+ this.values[identifier] = value;
+ return this;
+ }
+ }, {
+ key: 'getFirebaseUrl',
+ value: function getFirebaseUrl() {
+ return this.getValue('firebaseUrl');
+ }
+ }, {
+ key: 'setFirebaseUrl',
+ value: function setFirebaseUrl(firebaseUrl) {
+ return this.setValue('firebaseUrl', firebaseUrl);
+ }
+ }, {
+ key: 'getMonitorAuthChange',
+ value: function getMonitorAuthChange() {
+ return this.getValue('monitorAuthChange');
+ }
+ }, {
+ key: 'setMonitorAuthChange',
+ value: function setMonitorAuthChange() {
+ var monitorAuthChange = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
+
+ return this.setValue('monitorAuthChange', monitorAuthChange === true);
+ }
+ }]);
+
+ return Configuration;
+ })();
+
+ _export('Configuration', Configuration);
+ }
+ };
+});
\ No newline at end of file
diff --git a/dist/system/events.js b/dist/system/events.js
new file mode 100644
index 0000000..7810715
--- /dev/null
+++ b/dist/system/events.js
@@ -0,0 +1,177 @@
+System.register(['aurelia-dependency-injection', 'aurelia-event-aggregator'], function (_export) {
+ 'use strict';
+
+ var inject, EventAggregator, FirebaseEvent, UserEvent, UserCreatedEvent, UserSignedInEvent, UserSignedOutEvent, UserEmailChangedEvent, UserPasswordChangedEvent, UserDeletedEvent, UserAuthStateChangedEvent, Publisher;
+
+ var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+ var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+ function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+ return {
+ setters: [function (_aureliaDependencyInjection) {
+ inject = _aureliaDependencyInjection.inject;
+ }, function (_aureliaEventAggregator) {
+ EventAggregator = _aureliaEventAggregator.EventAggregator;
+ }],
+ execute: function () {
+ FirebaseEvent = function FirebaseEvent() {
+ _classCallCheck(this, FirebaseEvent);
+
+ this.handled = false;
+ };
+
+ UserEvent = (function (_FirebaseEvent) {
+ _inherits(UserEvent, _FirebaseEvent);
+
+ function UserEvent() {
+ var uid = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
+
+ _classCallCheck(this, UserEvent);
+
+ _get(Object.getPrototypeOf(UserEvent.prototype), 'constructor', this).call(this);
+ this.uid = uid;
+ }
+
+ return UserEvent;
+ })(FirebaseEvent);
+
+ UserCreatedEvent = (function (_UserEvent) {
+ _inherits(UserCreatedEvent, _UserEvent);
+
+ function UserCreatedEvent(data) {
+ _classCallCheck(this, UserCreatedEvent);
+
+ _get(Object.getPrototypeOf(UserCreatedEvent.prototype), 'constructor', this).call(this, data.uid);
+ this.email = data.email;
+ }
+
+ return UserCreatedEvent;
+ })(UserEvent);
+
+ _export('UserCreatedEvent', UserCreatedEvent);
+
+ UserSignedInEvent = (function (_UserEvent2) {
+ _inherits(UserSignedInEvent, _UserEvent2);
+
+ function UserSignedInEvent(data) {
+ _classCallCheck(this, UserSignedInEvent);
+
+ _get(Object.getPrototypeOf(UserSignedInEvent.prototype), 'constructor', this).call(this, data.uid);
+ this.provider = data.provider;
+ this.email = data.email;
+ }
+
+ return UserSignedInEvent;
+ })(UserEvent);
+
+ _export('UserSignedInEvent', UserSignedInEvent);
+
+ UserSignedOutEvent = (function (_UserEvent3) {
+ _inherits(UserSignedOutEvent, _UserEvent3);
+
+ function UserSignedOutEvent(data) {
+ _classCallCheck(this, UserSignedOutEvent);
+
+ _get(Object.getPrototypeOf(UserSignedOutEvent.prototype), 'constructor', this).call(this);
+ this.email = data.email;
+ }
+
+ return UserSignedOutEvent;
+ })(UserEvent);
+
+ _export('UserSignedOutEvent', UserSignedOutEvent);
+
+ UserEmailChangedEvent = (function (_UserEvent4) {
+ _inherits(UserEmailChangedEvent, _UserEvent4);
+
+ function UserEmailChangedEvent(data) {
+ _classCallCheck(this, UserEmailChangedEvent);
+
+ _get(Object.getPrototypeOf(UserEmailChangedEvent.prototype), 'constructor', this).call(this);
+ this.oldEmail = data.oldEmail;
+ this.newEmail = data.newEmail;
+ }
+
+ return UserEmailChangedEvent;
+ })(UserEvent);
+
+ _export('UserEmailChangedEvent', UserEmailChangedEvent);
+
+ UserPasswordChangedEvent = (function (_UserEvent5) {
+ _inherits(UserPasswordChangedEvent, _UserEvent5);
+
+ function UserPasswordChangedEvent(data) {
+ _classCallCheck(this, UserPasswordChangedEvent);
+
+ _get(Object.getPrototypeOf(UserPasswordChangedEvent.prototype), 'constructor', this).call(this);
+ this.email = data.email;
+ }
+
+ return UserPasswordChangedEvent;
+ })(UserEvent);
+
+ _export('UserPasswordChangedEvent', UserPasswordChangedEvent);
+
+ UserDeletedEvent = (function (_UserEvent6) {
+ _inherits(UserDeletedEvent, _UserEvent6);
+
+ function UserDeletedEvent(data) {
+ _classCallCheck(this, UserDeletedEvent);
+
+ _get(Object.getPrototypeOf(UserDeletedEvent.prototype), 'constructor', this).call(this);
+ this.email = data.email;
+ }
+
+ return UserDeletedEvent;
+ })(UserEvent);
+
+ _export('UserDeletedEvent', UserDeletedEvent);
+
+ UserAuthStateChangedEvent = (function (_UserEvent7) {
+ _inherits(UserAuthStateChangedEvent, _UserEvent7);
+
+ function UserAuthStateChangedEvent(data) {
+ _classCallCheck(this, UserAuthStateChangedEvent);
+
+ data = data || {};
+ _get(Object.getPrototypeOf(UserAuthStateChangedEvent.prototype), 'constructor', this).call(this, data.uid);
+ this.provider = data.provider || null;
+ this.auth = data.auth || null;
+ this.expires = data.expires || 0;
+ }
+
+ return UserAuthStateChangedEvent;
+ })(UserEvent);
+
+ _export('UserAuthStateChangedEvent', UserAuthStateChangedEvent);
+
+ Publisher = (function () {
+ function Publisher(eventAggregator) {
+ _classCallCheck(this, _Publisher);
+
+ this._eventAggregator = eventAggregator;
+ }
+
+ _createClass(Publisher, [{
+ key: 'publish',
+ value: function publish(event) {
+ if (event.handled) {
+ return;
+ }
+ this._eventAggregator.publish(event);
+ }
+ }]);
+
+ var _Publisher = Publisher;
+ Publisher = inject(EventAggregator)(Publisher) || Publisher;
+ return Publisher;
+ })();
+
+ _export('Publisher', Publisher);
+ }
+ };
+});
\ No newline at end of file
diff --git a/dist/system/index.js b/dist/system/index.js
new file mode 100644
index 0000000..cd9b762
--- /dev/null
+++ b/dist/system/index.js
@@ -0,0 +1,35 @@
+System.register(['./configuration', './user', './authentication', './collection', './events'], function (_export) {
+ 'use strict';
+
+ var Configuration;
+
+ _export('configure', configure);
+
+ function configure(aurelia, configCallback) {
+ var config = new Configuration(Configuration.defaults);
+
+ if (configCallback !== undefined && typeof configCallback === 'function') {
+ configCallback(config);
+ }
+ aurelia.instance(Configuration, config);
+ }
+
+ return {
+ setters: [function (_configuration) {
+ Configuration = _configuration.Configuration;
+
+ _export('Configuration', _configuration.Configuration);
+ }, function (_user) {
+ _export('User', _user.User);
+ }, function (_authentication) {
+ _export('AuthenticationManager', _authentication.AuthenticationManager);
+ }, function (_collection) {
+ _export('ReactiveCollection', _collection.ReactiveCollection);
+ }, function (_events) {
+ for (var _key in _events) {
+ if (_key !== 'default') _export(_key, _events[_key]);
+ }
+ }],
+ execute: function () {}
+ };
+});
\ No newline at end of file
diff --git a/dist/system/user.js b/dist/system/user.js
new file mode 100644
index 0000000..f3f56e2
--- /dev/null
+++ b/dist/system/user.js
@@ -0,0 +1,66 @@
+System.register([], function (_export) {
+ "use strict";
+
+ var User;
+
+ var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+ return {
+ setters: [],
+ execute: function () {
+ User = (function () {
+ _createClass(User, [{
+ key: "isAuthenticated",
+ get: function get() {
+ return this.token && this.auth && this.expires > 0 || false;
+ }
+ }]);
+
+ function User() {
+ var userData = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
+
+ _classCallCheck(this, User);
+
+ this.uid = null;
+ this.provider = null;
+ this.token = null;
+ this.auth = null;
+ this.expires = 0;
+ this.email = null;
+ this.isTemporaryPassword = null;
+ this.profileImageUrl = null;
+
+ this.update(userData);
+ }
+
+ _createClass(User, [{
+ key: "update",
+ value: function update(userData) {
+ userData = userData || {};
+ this.uid = userData.uid || null;
+ this.provider = userData.provider || null;
+ this.token = userData.token || null;
+ this.auth = userData.auth || null;
+ this.expires = userData.expires || 0;
+
+ userData.password = userData.password || {};
+ this.isTemporaryPassword = userData.password.isTemporaryPassword || false;
+ this.profileImageUrl = userData.password.profileImageURL || null;
+ this.email = userData.password.email || null;
+ }
+ }, {
+ key: "reset",
+ value: function reset() {
+ this.update({});
+ }
+ }]);
+
+ return User;
+ })();
+
+ _export("User", User);
+ }
+ };
+});
\ No newline at end of file
diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md
new file mode 100644
index 0000000..d39987d
--- /dev/null
+++ b/doc/CHANGELOG.md
@@ -0,0 +1,6 @@
+### 0.1.0 (2015-10-17)
+
+#### Features
+
+* **all:** initial commit of aurelia-firebase ([5c444129](http://github.com/pulsarblow/aurelia-firebase/commit/5c44412992bb4c4da4d614fdc115e735903a67a7))
+
diff --git a/doc/api.json b/doc/api.json
new file mode 100644
index 0000000..6de3c0a
--- /dev/null
+++ b/doc/api.json
@@ -0,0 +1 @@
+{"classes":[],"methods":[],"properties":[],"events":[]}
\ No newline at end of file
diff --git a/doc/intro.md b/doc/intro.md
new file mode 100644
index 0000000..1cdc464
--- /dev/null
+++ b/doc/intro.md
@@ -0,0 +1,118 @@
+[![Join the chat at https://gitter.im/aurelia/discuss](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/aurelia/discuss?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+# Installation
+
+
+#### Install via JSPM
+Go into your project and verify it's already `npm install`'ed and `jspm install`'ed. Now execute following command to install the plugin via JSPM:
+
+```
+jspm install aurelia-firebase
+```
+
+this will add the plugin into your `jspm_packages` folder as well as an mapping-line into your `config.js` as:
+
+```
+"aurelia-firebase": "github:aurelia-firebase@X.X.X",
+```
+
+If you're feeling experimental or cannot wait for the next release, you could also install the latest version by executing:
+```
+jspm install aurelia-firebase=github:pulsarblow/aurelia-firebase@master
+```
+
+
+#### Migrate from aurelia-app to aurelia-app="main"
+You'll need to register the plugin when your aurelia app is bootstrapping. If you have an aurelia app because you cloned a sample, there's a good chance that the app is bootstrapping based on default conventions. In that case, open your **index.html** file and look at the *body* tag.
+``` html
+
+```
+Change the *aurelia-app* attribute to *aurelia-app="main"*.
+``` html
+
+```
+The aurelia framework will now bootstrap the application by looking for your **main.js** file and executing the exported *configure* method. Go ahead and add a new **main.js** file with these contents:
+``` javascript
+export function configure(aurelia) {
+ aurelia.use
+ .standardConfiguration()
+ .developmentLogging();
+
+ aurelia.start().then(a => a.setRoot('app', document.body));
+}
+
+```
+
+#### Load the plugin
+During bootstrapping phase, you can now include the validation plugin:
+
+``` javascript
+export function configure(aurelia) {
+ aurelia.use
+ .standardConfiguration()
+ .developmentLogging()
+ .plugin('aurelia-firebase'); //Add this line to load the plugin
+
+ aurelia.start().then(a => a.setRoot('app', document.body));
+}
+```
+
+# Getting started
+
+TBD...
+
+#Configuration
+##One config to rule them all
+The firebase plugin has one global configuration instance, which is passed to an optional callback function when you first install the plugin:
+``` javascript
+export function configure(aurelia) {
+ aurelia.use
+ .standardConfiguration()
+ .developmentLogging()
+ .plugin('aurelia-firebase', (config) => { config.setFirebaseUrl('https://myapp.firebaseio.com/'); });
+
+ aurelia.start().then(a => a.setRoot('app', document.body));
+}
+```
+
+>Note: if you want to access the global configuration instance at a later point in time, you can inject it:
+``` javascript
+import {Configuration} from 'aurelia-firebase';
+import {inject} from 'aurelia-framework';
+
+>@inject(Configuration)
+export class MyVM{
+ constructor(config)
+ {
+
+> }
+}
+```
+
+##Possible configuration
+>Note: all these can be chained:
+``` javascript
+(config) => { config.setFirebaseUrl('https://myapp.firebaseio.com/').setMonitorAuthChange(true); }
+```
+
+###config.setFirebaseUrl(firebaseUrl: string)
+``` javascript
+(config) => { config.setFirebaseUrl('https://myapp.firebaseio.com/'); }
+```
+Sets the Firebase URL where your app answers.
+This is required and the plugin will not start if not provided.
+
+###config.setMonitorAuthChange(monitorAuthChange: boolean)
+``` javascript
+(config) => { config.setMonitorAuthChange(true); }
+```
+When set to true, the authentication manager will monitor authentication changes for the current user
+The default value is false.
+
+#AuthenticationManager
+
+The authentication manager handles authentication aspects in the plugin.
+
+#ReactiveCollection
+
+The ReactiveCollection class handles firebase data synchronization.
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000..4b28ec1
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,3 @@
+// all gulp tasks are located in the ./build/tasks directory
+// gulp configuration is in files in ./build directory
+require('require-dir')('build/tasks');
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 0000000..75fec1b
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,87 @@
+// Karma configuration
+
+var babelOptions = {
+ sourceMap: 'inline',
+ modules: 'system',
+ moduleIds: false,
+ comments: false,
+ loose: "all",
+ optional: [
+ "es7.decorators",
+ "es7.classProperties"
+ ]
+};
+
+module.exports = function(config) {
+ config.set({
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['jspm', 'jasmine-ajax', 'jasmine'],
+
+ jspm: {
+ // Edit this to your needs
+ loadFiles: ['src/**/*.js', 'test/**/*.js'],
+ paths: {
+ '*': '*.js'
+ }
+ },
+
+
+ // list of files / patterns to load in the browser
+ files: [],
+
+
+ // list of files to exclude
+ exclude: [
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ 'test/**/*.js': ['babel'],
+ 'src/**/*.js': ['babel']
+ },
+
+ 'babelPreprocessor': {
+ options: babelOptions
+ },
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['progress'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['Chrome'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false
+ });
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..35cf71b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,92 @@
+{
+ "name": "aurelia-firebase",
+ "version": "0.1.0-beta",
+ "description": "A Firebase plugin for Aurelia.",
+ "keywords": [
+ "aurelia",
+ "firebase",
+ "plugin"
+ ],
+ "homepage": "https://github.com/pulsarblow/aurelia-firebase",
+ "license": "MIT",
+ "author": "Alain Mereaux ",
+ "main": "dist/commonjs/index.js",
+ "scripts": {
+ "test": "gulp test"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/pulsarblow/aurelia-firebase#readme"
+ },
+ "bugs": {
+ "url": "https://github.com/pulsarblow/aurelia-firebase/issues"
+ },
+ "devDependencies": {
+ "aurelia-bundler": "^0.1.6",
+ "aurelia-tools": "^0.1.3",
+ "babel": "^5.8.23",
+ "babel-eslint": "^4.1.3",
+ "conventional-changelog": "0.0.11",
+ "del": "^1.1.0",
+ "faker": "^3.0.1",
+ "gulp": "^3.8.10",
+ "gulp-babel": "^5.1.0",
+ "gulp-bump": "^0.3.1",
+ "gulp-changed": "^1.1.0",
+ "gulp-eslint": "^1.0.0",
+ "gulp-notify": "^2.2.0",
+ "gulp-plumber": "^0.6.6",
+ "gulp-protractor": "^0.0.12",
+ "gulp-sourcemaps": "^1.3.0",
+ "gulp-typedoc": "^1.2.1",
+ "gulp-typedoc-extractor": "0.0.8",
+ "gulp-yuidoc": "^0.1.2",
+ "jasmine-core": "^2.1.3",
+ "jspm": "^0.16.12",
+ "karma": "^0.13.9",
+ "karma-babel-preprocessor": "^5.2.2",
+ "karma-chrome-launcher": "^0.2.0",
+ "karma-coverage": "^0.3.1",
+ "karma-jasmine": "^0.3.6",
+ "karma-jasmine-ajax": "^0.1.13",
+ "karma-jspm": "^2.0.1",
+ "object.assign": "^1.0.3",
+ "phantomjs": "^1.9.18",
+ "require-dir": "^0.1.0",
+ "run-sequence": "^1.0.2",
+ "vinyl": "^1.0.0",
+ "vinyl-paths": "^1.0.0",
+ "yargs": "^2.1.1"
+ },
+ "directories": {
+ "doc": "doc",
+ "packages": "jspm_packages"
+ },
+ "jspm": {
+ "main": "index",
+ "format": "amd",
+ "directories": {
+ "lib": "dist/amd"
+ },
+ "dependencies": {
+ "aurelia-dependency-injection": "github:aurelia/dependency-injection@^0.11.0",
+ "aurelia-event-aggregator": "github:aurelia/event-aggregator@^0.9.0",
+ "aurelia-logging": "github:aurelia/logging@^0.8.0",
+ "bluebird": "npm:bluebird@^2.10.2",
+ "core-js": "npm:core-js@^0.9.4",
+ "firebase": "github:firebase/firebase-bower@^2.3.1",
+ "text": "github:systemjs/plugin-text@^0.0.2"
+ },
+ "devDependencies": {
+ "babel": "npm:babel-core@^5.8.24",
+ "babel-runtime": "npm:babel-runtime@^5.8.24",
+ "core-js": "npm:core-js@^0.9.4",
+ "faker": "npm:faker@^3.0.1"
+ },
+ "overrides": {
+ "npm:core-js@0.9.18": {
+ "main": "client/shim.min"
+ }
+ }
+ }
+}
diff --git a/src/authentication.js b/src/authentication.js
new file mode 100644
index 0000000..69bd028
--- /dev/null
+++ b/src/authentication.js
@@ -0,0 +1,177 @@
+import Promise from 'bluebird';
+import Firebase from 'firebase';
+import {inject} from 'aurelia-dependency-injection';
+
+import * as events from './events';
+import {User} from './user';
+import {Configuration} from './configuration';
+
+/**
+ * Handles Firebase authentication features
+ */
+@inject(Configuration, events.Publisher)
+export class AuthenticationManager {
+
+ _firebase = null;
+ _publisher = null;
+ currentUser = null;
+
+ /**
+ * Initializes a new instance of the AuthenticationManager
+ * @param {Configuration} configuration - The configuration to use
+ * @param {Publisher} publisher - The publisher used to broadcast system wide user events
+ */
+ constructor(
+ configuration: Configuration,
+ publisher: Publisher) {
+ this._firebase = new Firebase(configuration.getFirebaseUrl());
+ this._publisher = publisher;
+ this.currentUser = new User();
+
+ // Register auth state changed event
+ // This will handle user data update now and in the future.
+ if (configuration.getMonitorAuthChange() === true) {
+ this._firebase.onAuth((result) => {
+ this._onUserAuthStateChanged(result);
+ }, this);
+ }
+ }
+
+ /**
+ * Creates a new user but does not authenticate him.
+ * @param {string} email - The user email
+ * @param {string} password - The user password
+ * @returns {Promise} - Returns a promise which on completion will return the user infos
+ */
+ createUser(email, password) : Promise {
+ return new Promise((resolve, reject) => {
+ this._firebase.createUser({email: email, password: password}, (error, result) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ let user = new User(result);
+ user.email = user.email || email; // Because firebase result doesn't provide the email
+ this._publisher.publish(new events.UserCreatedEvent(user));
+ resolve(user);
+ });
+ });
+ }
+
+ /**
+ * Sign in a user with a password.
+ * @param {string} email - The user email
+ * @param {string} password - The user password
+ * @returns {Promise} Returns a promise which on completion will return user infos
+ */
+ signIn(email, password) : Promise {
+ return new Promise((resolve, reject) => {
+ this._firebase.authWithPassword({email: email, password: password}, (error, result) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ let user = new User(result);
+ this._publisher.publish(new events.UserSignedInEvent(user));
+ resolve(user);
+ });
+ });
+ }
+
+ /**
+ * Creates a user and automatically sign in if creation succeed
+ * @param {string} email - The user email
+ * @param {string} password - The user password
+ * @returns {Promise} - Returns a promise which on completion will return user infos
+ */
+ createUserAndSignIn(email, password) : Promise {
+ return this.createUser(email, password).then(() => {
+ return this.signIn(email, password);
+ });
+ }
+
+ /**
+ * Sign out any authenticated user
+ * @returns {Promise} - Returns a promise
+ */
+ signOut() : Promise {
+ return new Promise((resolve) => {
+ this._firebase.unauth();
+ this.currentUser.reset();
+ resolve();
+ });
+ }
+
+ /**
+ * Changes the user email.
+ * User will be disconnected upon email change.
+ * @param {string} oldEmail - The current user email (email to be changed)
+ * @param {string} password - The current user password
+ * @param {string} newEmail - The new email
+ * @returns {Promise} - Returns a promise which on completion will return an object containing the old and new email
+ */
+ changeEmail(oldEmail, password, newEmail) : Promise {
+ return new Promise((resolve, reject) => {
+ this._firebase.changeEmail({oldEmail: oldEmail, password: password, newEmail: newEmail}, (error) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ this.currentUser.email = newEmail;
+ let result = {oldEmail: oldEmail, newEmail: newEmail};
+ this._publisher.publish(new events.UserEmailChangedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+
+ /**
+ * Changes the user password
+ * @param {string} email - The email of the user to change the password
+ * @param {string} oldPassword - The current password
+ * @param {string} newPassword - The new password
+ */
+ changePassword(email, oldPassword, newPassword) : Promise {
+ return new Promise((resolve, reject) => {
+ this._firebase.changePassword({email: email, oldPassword: oldPassword, newPassword: newPassword}, (error) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ let result = {email: email};
+ this._publisher.publish(new events.UserPasswordChangedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+
+ /**
+ * Deletes a user account
+ * @param {string} email - The users's email
+ * @param {string} password - The user's password
+ */
+ deleteUser(email: string, password: string) : Promise {
+ return new Promise((resolve, reject) => {
+ this._firebase.removeUser({email: email, password: password}, (error) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ this.currentUser.reset();
+ let result = {email: email};
+ this._publisher.publish(new events.UserDeletedEvent(result));
+ resolve(result);
+ });
+ });
+ }
+
+ _onUserAuthStateChanged(authData) {
+ this.currentUser.update(authData);
+ this._publisher.publish(new events.UserAuthStateChangedEvent(authData));
+ }
+}
diff --git a/src/collection.js b/src/collection.js
new file mode 100644
index 0000000..9f9675c
--- /dev/null
+++ b/src/collection.js
@@ -0,0 +1,164 @@
+import Promise from 'bluebird';
+import Firebase from 'firebase';
+import {Container} from 'aurelia-dependency-injection';
+import {Configuration} from './configuration';
+
+export class ReactiveCollection {
+
+ _query = null;
+ _valueMap = new Map();
+ items = [];
+
+ constructor(path: Array) {
+ if (!Container || !Container.instance) throw Error('Container has not been made global');
+ let config = Container.instance.get(Configuration);
+ if (!config) throw Error('Configuration has not been set');
+
+ this._query = new Firebase(ReactiveCollection._getChildLocation(
+ config.getFirebaseUrl(),
+ path));
+ this._listenToQuery(this._query);
+ }
+
+ add(item:any) : Promise {
+ return new Promise((resolve, reject) => {
+ let query = this._query.ref().push();
+ query.set(item, (error) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(item);
+ });
+ });
+ }
+
+ remove(item: any): Promise {
+ if (item === null || item.__firebaseKey__ === null) {
+ return Promise.reject({message: 'Unknown item'});
+ }
+ return this.removeByKey(item.__firebaseKey__);
+ }
+
+ getByKey(key): any {
+ return this._valueMap.get(key);
+ }
+
+ removeByKey(key) {
+ return new Promise((resolve, reject) => {
+ this._query.ref().child(key).remove((error) =>{
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(key);
+ });
+ });
+ }
+
+ clear() {
+ //this._stopListeningToQuery(this._query);
+ return new Promise((resolve, reject) => {
+ let query = this._query.ref();
+ query.remove((error) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve();
+ });
+ });
+ }
+
+ _listenToQuery(query) {
+ query.on('child_added', (snapshot, previousKey) => {
+ this._onItemAdded(snapshot, previousKey);
+ });
+ query.on('child_removed', (snapshot) => {
+ this._onItemRemoved(snapshot);
+ });
+ query.on('child_changed', (snapshot, previousKey) => {
+ this._onItemChanded(snapshot, previousKey);
+ });
+ query.on('child_moved', (snapshot, previousKey) => {
+ this._onItemMoved(snapshot, previousKey);
+ });
+ }
+
+ _stopListeningToQuery(query) {
+ query.off();
+ }
+
+ _onItemAdded(snapshot, previousKey) {
+ let value = this._valueFromSnapshot(snapshot);
+ let index = previousKey !== null ?
+ this.items.indexOf(this._valueMap.get(previousKey)) + 1 : 0;
+ this._valueMap.set(value.__firebaseKey__, value);
+ this.items.splice(index, 0, value);
+ }
+
+ _onItemRemoved(oldSnapshot) {
+ let key = oldSnapshot.key();
+ let value = this._valueMap.get(key);
+
+ if (!value) {
+ return;
+ }
+
+ let index = this.items.indexOf(value);
+ this._valueMap.delete(key);
+ if (index !== -1) {
+ this.items.splice(index, 1);
+ }
+ }
+
+ _onItemChanged(snapshot, previousKey) {
+ let value = this._valueFromSnapshot(snapshot);
+ let oldValue = this._valueMap.get(value.__firebaseKey__);
+
+ if (!oldValue) {
+ return;
+ }
+
+ this._valueMap.delete(oldValue.__firebaseKey__);
+ this._valueMap.set(value.__firebaseKey__, value);
+ this.items.splice(this.items.indexOf(oldValue), 1, value);
+ }
+
+ _onItemMoved(snapshot, previousKey) {
+ let key = snapshot.key();
+ let value = this._valueMap.get(key);
+
+ if (!value) {
+ return;
+ }
+
+ let previousValue = this._valueMap.get(previousKey);
+ let newIndex = previousValue !== null ? this.items.indexOf(previousValue) + 1 : 0;
+ this.items.splice(this.items.indexOf(value), 1);
+ this.items.splice(newIndex, 0, value);
+ }
+
+ _valueFromSnapshot(snapshot) {
+ let value = snapshot.val();
+ if (!(value instanceof Object)) {
+ value = {
+ value: value,
+ __firebasePrimitive__: true
+ };
+ }
+ value.__firebaseKey__ = snapshot.key();
+ return value;
+ }
+
+ static _getChildLocation(root: string, path: Array) {
+ if (!path) {
+ return root;
+ }
+ if (!root.endsWith('/')) {
+ root = root + '/';
+ }
+
+ return root + (Array.isArray(path) ? path.join('/') : path);
+ }
+}
diff --git a/src/configuration.js b/src/configuration.js
new file mode 100644
index 0000000..a7e5931
--- /dev/null
+++ b/src/configuration.js
@@ -0,0 +1,93 @@
+/**
+ * The default configuration
+ */
+export class ConfigurationDefaults {
+
+}
+
+ConfigurationDefaults._defaults = {
+ firebaseUrl: null,
+ monitorAuthChange: false
+};
+
+ConfigurationDefaults.defaults = function() {
+ let defaults = {};
+ Object.assign(defaults, ConfigurationDefaults._defaults);
+ return defaults;
+};
+
+/**
+ * Configuration class used by the plugin
+ */
+export class Configuration {
+
+ /**
+ * Initializes a new instance of the Configuration class
+ * @param {Object} innerConfig - The optional initial configuration values. If not provided will initialize using the defaults.
+ */
+ constructor(innerConfig) {
+ this.innerConfig = innerConfig;
+ this.values = this.innerConfig ? {} : ConfigurationDefaults.defaults();
+ }
+
+ /**
+ * Gets the value of a configuration option by its identifier
+ * @param {string} identifier - The configuration option identifier
+ * @returns {any} - The value of the configuration option
+ * @throws {Error} - When configuration option is not found
+ */
+ getValue(identifier) {
+ if (this.values.hasOwnProperty(identifier) !== null && this.values[identifier] !== undefined) {
+ return this.values[identifier];
+ }
+ if (this.innerConfig !== null) {
+ return this.innerConfig.getValue(identifier);
+ }
+ throw new Error('Config not found: ' + identifier);
+ }
+
+ /**
+ * Sets the value of a configuration option
+ * @param {string} identifier - The key used to store the configuration option value
+ * @param {any} value - The value of the configuration option
+ * @returns {Configuration} - The current configuration instance (Fluent API)
+ */
+ setValue(identifier, value) {
+ this.values[identifier] = value;
+ return this; // fluent API
+ }
+
+ /**
+ * Gets the value of the firebaseUrl configuration option
+ * @returns {string} - The value of the firebaseUrl configuration option
+ */
+ getFirebaseUrl() {
+ return this.getValue('firebaseUrl');
+ }
+
+ /**
+ * Sets the value of the firebaseUrl configuration option
+ * @param {string} firebaseUrl - An URL to a valid Firebase location
+ * @returns {Configuration} - Returns the configuration instance (fluent API)
+ */
+ setFirebaseUrl(firebaseUrl) {
+ return this.setValue('firebaseUrl', firebaseUrl);
+ }
+
+ /**
+ * Gets the value of the monitorAuthChange configuration option
+ * @returns {boolean} - The value of the monitorAuthChange configuration option
+ */
+ getMonitorAuthChange() {
+ return this.getValue('monitorAuthChange');
+ }
+
+ /**
+ * Sets the value of the monitorAuthChange configuration option
+ * @param monitorAuthChange
+ * @returns {Configuration} - Returns the configuration instance (fluent API)
+ */
+ setMonitorAuthChange(monitorAuthChange: boolean = true) {
+ return this.setValue('monitorAuthChange', monitorAuthChange === true);
+ }
+}
diff --git a/src/events.js b/src/events.js
new file mode 100644
index 0000000..8ef4e87
--- /dev/null
+++ b/src/events.js
@@ -0,0 +1,130 @@
+import {inject} from 'aurelia-dependency-injection';
+import {EventAggregator} from 'aurelia-event-aggregator';
+
+class FirebaseEvent {
+ handled = false;
+
+ constructor() {
+ }
+}
+
+class UserEvent extends FirebaseEvent {
+ uid: string;
+
+ constructor(uid: string = null) {
+ super();
+ this.uid = uid;
+ }
+}
+
+/**
+ * An event triggered when a user is created
+ */
+export class UserCreatedEvent extends UserEvent {
+ email:string;
+ constructor(data: Object) {
+ super(data.uid);
+ this.email = data.email;
+ }
+}
+
+/**
+ * An event triggered when a user signed in
+ */
+export class UserSignedInEvent extends UserEvent {
+ provider: string;
+ email: string;
+ profileImageUrl:string;
+
+ constructor(data: Object) {
+ super(data.uid);
+ this.provider = data.provider;
+ this.email = data.email;
+ }
+}
+
+/**
+ * An event triggered when a user signed out
+ */
+export class UserSignedOutEvent extends UserEvent {
+ email: string;
+ constructor(data: Object) {
+ super();
+ this.email = data.email;
+ }
+}
+
+/**
+ * An event triggered when a user's email has changed
+ */
+export class UserEmailChangedEvent extends UserEvent {
+ oldEmail: string;
+ newEmail: string;
+ constructor(data: Object) {
+ super();
+ this.oldEmail = data.oldEmail;
+ this.newEmail = data.newEmail;
+ }
+}
+
+/**
+ * An event triggered when a user's password has changed
+ */
+export class UserPasswordChangedEvent extends UserEvent {
+ email:string;
+ constructor(data: Object) {
+ super();
+ this.email = data.email;
+ }
+}
+
+/**
+ * An event triggered when a user has been deleted
+ */
+export class UserDeletedEvent extends UserEvent {
+ email: string;
+ constructor(data: Object) {
+ super();
+ this.email = data.email;
+ }
+}
+
+/**
+ * An event triggered when a user authentication state has changed
+ */
+export class UserAuthStateChangedEvent extends UserEvent {
+ provider: string;
+ auth: any;
+ expires: number;
+
+ constructor(data: Object) {
+ data = data || {};
+ super(data.uid);
+ this.provider = data.provider || null;
+ this.auth = data.auth || null;
+ this.expires = data.expires || 0;
+ }
+}
+
+/**
+ * Handles publishing events in the system
+ */
+@inject(EventAggregator)
+export class Publisher {
+ _eventAggregator;
+
+ constructor(eventAggregator: EventAggregator) {
+ this._eventAggregator = eventAggregator;
+ }
+
+ /**
+ * Publish an event
+ * @param {FirebaseEvent} event - The event to publish
+ */
+ publish(event: FirebaseEvent) {
+ if (event.handled) {
+ return;
+ }
+ this._eventAggregator.publish(event);
+ }
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..62e93a1
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,16 @@
+import {Configuration} from './configuration';
+
+export {Configuration} from './configuration';
+export {User} from './user';
+export {AuthenticationManager} from './authentication';
+export {ReactiveCollection} from './collection';
+export * from './events';
+
+export function configure(aurelia: Object, configCallback: Function) {
+ let config = new Configuration(Configuration.defaults);
+
+ if (configCallback !== undefined && typeof configCallback === 'function') {
+ configCallback(config);
+ }
+ aurelia.instance(Configuration, config);
+}
diff --git a/src/user.js b/src/user.js
new file mode 100644
index 0000000..507cb6e
--- /dev/null
+++ b/src/user.js
@@ -0,0 +1,96 @@
+/**
+ * Represents a Firebase User
+ */
+export class User {
+
+ /**
+ * A unique user ID, intented as the user's unique key accross all providers
+ * @type {string}
+ */
+ uid = null;
+
+ /**
+ * The authentication method used
+ * @type {string}
+ */
+ provider = null;
+
+ /**
+ * The Firebase authentication token for this session
+ * @type {string}
+ */
+ token = null;
+
+ /**
+ * The contents of the authentication token
+ * @type {Object}
+ */
+ auth = null;
+
+ /**
+ * A timestamp, in seconds since UNIX epoch, indicated when the authentication token expires
+ * @type {number}
+ */
+ expires = 0;
+
+ /**
+ * The user's email address
+ * @type {string}
+ */
+ email = null;
+
+ /**
+ * Whether or not the user authenticated using a temporary password,
+ * as used in password reset flows.
+ * @type {boolean}
+ */
+ isTemporaryPassword = null;
+
+ /**
+ * The URL to the user's Gravatar profile image
+ * @type {string}
+ */
+ profileImageUrl = null;
+
+ /**
+ * Whether or not the user is authenticated
+ * @type {boolean} True is the user is authenticated, false otherwise.
+ */
+ get isAuthenticated() {
+ return (this.token && this.auth && this.expires > 0) || false;
+ }
+
+ /**
+ * Initializes a new instance of user
+ * @param userData {Object} Optional object containing data
+ * to initialize this user with.
+ */
+ constructor(userData: Object = null) {
+ this.update(userData);
+ }
+
+ /**
+ * Update the current user instance with the provided data
+ * @param userData {Object} An object containing the data
+ */
+ update(userData: Object) {
+ userData = userData || {};
+ this.uid = userData.uid || null;
+ this.provider = userData.provider || null;
+ this.token = userData.token || null;
+ this.auth = userData.auth || null;
+ this.expires = userData.expires || 0;
+
+ userData.password = userData.password || {};
+ this.isTemporaryPassword = userData.password.isTemporaryPassword || false;
+ this.profileImageUrl = userData.password.profileImageURL || null;
+ this.email = userData.password.email || null;
+ }
+
+ /**
+ * Reinitializes the current user instance.
+ */
+ reset() {
+ this.update({});
+ }
+}
diff --git a/test/authentication.spec.js b/test/authentication.spec.js
new file mode 100644
index 0000000..9126cfb
--- /dev/null
+++ b/test/authentication.spec.js
@@ -0,0 +1,211 @@
+import faker from 'faker';
+import {EventAggregator} from 'aurelia-event-aggregator';
+import {Configuration} from '../src/configuration';
+import {Publisher} from '../src/events';
+import {AuthenticationManager} from '../src/authentication';
+import {User} from '../src/user';
+import * as helpers from './testHelpers';
+
+describe('AuthenticationManager', () => {
+
+ describe('without MonitorAuthStateChange', () => {
+ let configuration = new Configuration(Configuration.defaults)
+ .setFirebaseUrl('https://aurelia-firebase.firebaseio.com')
+ .setMonitorAuthChange(false);
+ let authManager = createAuthManager(configuration),
+ email = createRandomTestEmail(),
+ password = createRandomTestPassword();
+
+ it('and should initialize properly', () => {
+ expect(typeof authManager).toBe('object');
+ });
+
+ it('should create a new user', (done) => {
+ authManager.createUser(email, password)
+ .then((user)=> {
+ expect(typeof user).toBe('object');
+ expect(user.uid).toBeTruthy();
+ expect(user.email).toBe(email);
+ done();
+ })
+ .catch((error) => {
+ helpers.logError('createUser test error', error);
+ done.fail();
+ });
+ });
+
+ it('should sign in the user', (done) => {
+ authManager.signIn(email, password)
+ .then((user) => {
+ expect(typeof user).toBe('object');
+ expect(user.provider).toBe('password');
+ expect(user.uid).toBeTruthy();
+ expect(user.token).toBeTruthy();
+ expect(typeof user.auth).toBe('object');
+ expect(user.auth.uid).toBe(user.uid);
+ expect(user.auth.provider).toBe(user.provider);
+ expect(user.expires).toBeGreaterThan(0);
+ expect(user.email).toBe(email);
+ expect(user.isTemporaryPassword).toBe(false);
+ expect(user.profileImageUrl).toBeTruthy();
+ expect(user.isAuthenticated).toBe(true);
+ done();
+ })
+ .catch((error) => {
+ helpers.logError('signIn test error', error);
+ done.fail();
+ });
+ });
+
+ it('should change the user\'s email', (done) => {
+ let newEmail = createRandomTestEmail();
+ authManager.changeEmail(email, password, newEmail)
+ .then((result) => {
+ expect(typeof result).toBe('object');
+ expect(result.oldEmail).toBe(email);
+ expect(result.newEmail).toBe(newEmail);
+ email = newEmail; // used in the next test
+ done();
+ })
+ .catch((error) => {
+ helpers.logError('change email test error', error);
+ done.fail()
+ });
+ });
+
+ it('should change the user\'s password', (done) => {
+ let newPassword = createRandomTestPassword();
+ authManager.changePassword(email, password, newPassword)
+ .then((result) => {
+ expect(typeof result).toBe('object');
+ expect(result.email).toBe(email);
+ password = newPassword;
+ done();
+ })
+ .catch((error) => {
+ helpers.logError('change password test error', error);
+ done.fail();
+ });
+ });
+
+ it('should sign out a user', (done) => {
+ authManager.signOut()
+ .then(() => {
+ done();
+ })
+ .catch((error) => {
+ helpers.logError('signout test error', error);
+ done.fail();
+ });
+ });
+
+ it('should be able to relog after email and password changed', (done) => {
+ authManager.signIn(email, password)
+ .then(()=> {
+ authManager.signOut();
+ done();
+ })
+ .catch((error) => {
+ helpers.logError('signIn test error', error);
+ done.fail();
+ });
+ });
+
+ it('should delete a user', (done) => {
+ authManager.deleteUser(email, password)
+ .then((result) => {
+ expect(typeof result).toBe('object');
+ expect(result.email).toBe(email);
+ done();
+ })
+ .catch((error) => {
+ helpers.logError('delete user test error', error);
+ done.fail();
+ });
+ });
+
+ it('should not be able to sign in after being deleted', (done) => {
+ authManager.signIn(email, password)
+ .then(() => {
+ helpers.logError('signIn test error : user shouldn\'t be signed in');
+ done.fail();
+ })
+ .catch(() => {
+ done();
+ });
+ });
+ });
+
+ describe('createUserAndSignin', () => {
+
+ it('should create and sign in a user', (done) => {
+
+ let configuration = new Configuration(Configuration.defaults)
+ .setFirebaseUrl('https://aurelia-firebase.firebaseio.com')
+ .setMonitorAuthChange(false);
+ let authManager = createAuthManager(configuration),
+ email = createRandomTestEmail(),
+ password = createRandomTestPassword();
+
+ authManager.createUserAndSignIn(email, password)
+ .then((user) => {
+ expect(typeof user).toBe('object');
+ expect(user.isAuthenticated).toBe(true);
+ return authManager.signOut().then(() => {
+ return authManager.deleteUser(email, password).then(() => {
+ done();
+ });
+ });
+ })
+ .catch((error) => {
+ helpers.logError('create user and signIn test error', error);
+ done.fail();
+ });
+ });
+ });
+
+ describe('with monitorAuthStateChange', () => {
+
+ it('should monitor auth state changes', (done) => {
+
+ let configuration = new Configuration(Configuration.defaults)
+ .setFirebaseUrl('https://aurelia-firebase.firebaseio.com')
+ .setMonitorAuthChange(true);
+ let authManager = createAuthManager(configuration),
+ email = createRandomTestEmail(),
+ password = createRandomTestPassword();
+
+ authManager.createUserAndSignIn(email, password)
+ .then((user) => {
+ expect(typeof user).toBe('object');
+ expect(user.isAuthenticated).toBe(true);
+ expect(authManager.currentUser).toEqual(user);
+ return authManager.signOut().then(() => {
+ return authManager.deleteUser(email, password).then(() => {
+ expect(authManager.currentUser.isAuthenticated).toBe(false);
+ done();
+ });
+ });
+ })
+ .catch((error) => {
+ helpers.logError('create user and signIn test error', error);
+ done.fail();
+ });
+ });
+ });
+
+ function createRandomTestEmail() {
+ setTimeout(function() {
+ // Timeout to force timestamp increment
+ }, 100);
+ return 'xunittests_' + new Date().getTime() + '_' + faker.internet.email().toLowerCase();
+ }
+
+ function createRandomTestPassword() {
+ return faker.internet.password();
+ }
+
+ function createAuthManager(configuration: Configuration) {
+ return new AuthenticationManager(configuration, new Publisher(new EventAggregator()));
+ }
+});
diff --git a/test/collection.spec.js b/test/collection.spec.js
new file mode 100644
index 0000000..9a4aba3
--- /dev/null
+++ b/test/collection.spec.js
@@ -0,0 +1,102 @@
+import faker from 'faker';
+import {Container} from 'aurelia-dependency-injection';
+import {EventAggregator} from 'aurelia-event-aggregator';
+import {Configuration} from '../src/configuration';
+import {ReactiveCollection} from '../src/collection';
+import {testHelpers} from './testHelpers';
+
+describe('A ReactiveCollection', () => {
+
+ let config = new Configuration(Configuration.defaults)
+ .setFirebaseUrl('https://aurelia-firebase.firebaseio.com')
+ .setMonitorAuthChange(false);
+ let container = new Container();
+ container.makeGlobal();
+ container.registerInstance(Configuration, config);
+
+ let id = faker.random.uuid(),
+ value = faker.lorem.sentence(),
+ collection = new ReactiveCollection(['unit_tests', faker.random.uuid()]);
+
+ it('should initialize properly', () => {
+ expect(typeof collection).toBe('object');
+ });
+
+ it('should add an item properly', (done) => {
+ collection.add({id: id, value: value}).then((data) => {
+ expect(data).not.toBeNull();
+ expect(data.id).toBe(id);
+ expect(data.value).toBe(value);
+
+ let item = collection.items[0];
+ expect(item).toBeDefined();
+ expect(item.id).toBe(data.id);
+ expect(item.value).toBe(data.value);
+ expect(item.__firebaseKey__).toBeDefined();
+
+ done();
+ }).catch((error) => {
+ testHelpers.logWarn('add item test failed', error);
+ done.fail();
+ });
+ });
+
+ it('should remove an item properly', (done) => {
+ let item = collection.items[0];
+ collection.remove(item)
+ .then((itemKey) => {
+ expect(itemKey).toBeTruthy();
+ item = collection.items[0];
+ expect(item).not.toBeDefined();
+ done();
+ })
+ .catch(() => {
+ done.fail();
+ });
+ });
+
+ it('should find an item by key', (done) => {
+ collection.add({id: id, value: value}).then((data) => {
+ let actual = collection.items[0];
+ let expected = collection.getByKey(actual.__firebaseKey__);
+ expect(actual).toBe(expected);
+ done();
+ })
+ .catch(() => {
+ done.fail();
+ });
+ });
+
+ it('should clear the collection accordingly', (done) => {
+ collection.clear().then(() => {
+ expect(collection.items.length).toBe(0);
+ done();
+ });
+ });
+
+ it('should handle a path location provided as a string', () => {
+ expect(ReactiveCollection._getChildLocation('https://aurelia-firebase.firebaseio.com'))
+ .toBe('https://aurelia-firebase.firebaseio.com');
+ expect(ReactiveCollection._getChildLocation('https://aurelia-firebase.firebaseio.com/'))
+ .toBe('https://aurelia-firebase.firebaseio.com/');
+ expect(ReactiveCollection._getChildLocation('https://aurelia-firebase.firebaseio.com', null))
+ .toBe('https://aurelia-firebase.firebaseio.com');
+ expect(ReactiveCollection._getChildLocation('https://aurelia-firebase.firebaseio.com', 'apath'))
+ .toBe('https://aurelia-firebase.firebaseio.com/apath');
+ expect(ReactiveCollection._getChildLocation('https://aurelia-firebase.firebaseio.com/', 'apath'))
+ .toBe('https://aurelia-firebase.firebaseio.com/apath');
+ });
+
+ it('should handle a path location provided as an array', () => {
+ expect(ReactiveCollection._getChildLocation('https://aurelia-firebase.firebaseio.com'))
+ .toBe('https://aurelia-firebase.firebaseio.com');
+ expect(ReactiveCollection._getChildLocation('https://aurelia-firebase.firebaseio.com/'))
+ .toBe('https://aurelia-firebase.firebaseio.com/');
+ expect(ReactiveCollection._getChildLocation('https://aurelia-firebase.firebaseio.com', null))
+ .toBe('https://aurelia-firebase.firebaseio.com');
+ expect(ReactiveCollection._getChildLocation('https://aurelia-firebase.firebaseio.com', ['apath']))
+ .toBe('https://aurelia-firebase.firebaseio.com/apath');
+ expect(ReactiveCollection._getChildLocation('https://aurelia-firebase.firebaseio.com/', ['one', 'another']))
+ .toBe('https://aurelia-firebase.firebaseio.com/one/another');
+ });
+});
diff --git a/test/configuration.spec.js b/test/configuration.spec.js
new file mode 100644
index 0000000..340e438
--- /dev/null
+++ b/test/configuration.spec.js
@@ -0,0 +1,57 @@
+import {Configuration} from '../src/configuration';
+
+describe('Configuration', () => {
+ it('should have default values', () => {
+ let config = new Configuration();
+ expect(config.getFirebaseUrl()).toBeNull();
+ expect(config.getMonitorAuthChange()).toBe(false);
+ });
+
+ it('should be configurable', () => {
+ let config = new Configuration();
+ expect(config.setFirebaseUrl('https://google.com')).toBe(config); // fluent api check
+ expect(config.getFirebaseUrl()).toBe('https://google.com');
+
+ config = new Configuration();
+ expect(config.setMonitorAuthChange(true)).toBe(config); // fluent api check
+ expect(config.getMonitorAuthChange()).toBe(true);
+ });
+
+ it('should never change the defaults', () => {
+ let config1 = new Configuration();
+ let config2 = new Configuration();
+
+ expect(config2.getFirebaseUrl()).toBeNull();
+ config1.setFirebaseUrl('https://google.com');
+ expect(config2.getFirebaseUrl()).toBeNull();
+ });
+
+ it('should get values on the parent when it does not have a value itself', () => {
+ let parentConfig = new Configuration();
+ parentConfig.setValue('test', 'a');
+ let config = new Configuration(parentConfig);
+ expect(config.getValue('test')).toBe('a');
+ });
+
+ it('should not copy the values when using get but instead delegate to the parent', () => {
+ let parentConfig = new Configuration();
+ parentConfig.setValue('test', 'a');
+ let config = new Configuration(parentConfig);
+ expect(config.getValue('test')).toBe('a');
+
+ parentConfig.setValue('test', 'b');
+ expect(config.getValue('test')).toBe('b');
+ });
+
+ it('should allow to override parents with own values', () => {
+ let parentConfig = new Configuration();
+ parentConfig.setValue('test', 'a');
+ let config = new Configuration(parentConfig);
+ expect(config.getValue('test')).toBe('a');
+
+ config.setValue('test', 'c');
+ parentConfig.setValue('test', 'a');
+ expect(config.getValue('test')).toBe('c');
+
+ });
+});
diff --git a/test/events.spec.js b/test/events.spec.js
new file mode 100644
index 0000000..77624a6
--- /dev/null
+++ b/test/events.spec.js
@@ -0,0 +1,155 @@
+import faker from 'faker';
+import {EventAggregator} from 'aurelia-event-aggregator';
+import * as events from '../src/events';
+import * as testHelpers from './testHelpers';
+
+describe('A Publisher', () => {
+
+ it('should initialize properly', () => {
+ let publisher = new events.Publisher(new EventAggregator());
+ expect(typeof publisher).toBe('object');
+ });
+
+ describe('publishing events', () => {
+ let eventAggregator = new EventAggregator(),
+ publisher = new events.Publisher(eventAggregator);
+
+ it('should publish a UserCreatedEvent', (done) => {
+ let data = createEventData();
+
+ eventAggregator.subscribeOnce(events.UserCreatedEvent, (eventData) => {
+ expect(eventData).toBeDefined();
+ expect(eventData).not.toBeNull();
+ expect(eventData.uid).toBe(data.uid);
+ expect(eventData.email).toBe(data.email);
+ done();
+ });
+
+ publisher.publish(new events.UserCreatedEvent(data));
+ });
+
+ it('should publish a UserSignedInEvent', (done) => {
+ let data = createEventData();
+
+ eventAggregator.subscribeOnce(events.UserSignedInEvent, (eventData) => {
+ expect(eventData).toBeDefined();
+ expect(eventData).not.toBeNull();
+ expect(eventData.uid).toBe(data.uid);
+ expect(eventData.email).toBe(data.email);
+ expect(eventData.provider).toBe(data.provider);
+ done();
+ });
+
+ publisher.publish(new events.UserSignedInEvent(data));
+ });
+
+ it('should publish a UserSignedOutEvent', (done) => {
+ let data = {email: faker.internet.email()};
+
+ eventAggregator.subscribeOnce(events.UserSignedOutEvent, (eventData) => {
+ expect(eventData).toBeDefined();
+ expect(eventData).not.toBeNull();
+ expect(eventData.uid).toBeNull();
+ expect(eventData.email).toBe(data.email);
+ done();
+ });
+
+ publisher.publish(new events.UserSignedOutEvent(data));
+ });
+
+ it('should publish a UserEmailChangedEvent', (done) => {
+ let data = {
+ oldEmail: faker.internet.email(),
+ newEmail: faker.internet.email()
+ };
+
+ eventAggregator.subscribeOnce(events.UserEmailChangedEvent, (eventData) => {
+ expect(eventData).toBeDefined();
+ expect(eventData).not.toBeNull();
+ expect(eventData.uid).toBeNull();
+ expect(eventData.oldEmail).toBe(data.oldEmail);
+ expect(eventData.newEmail).toBe(data.newEmail);
+ done();
+ });
+
+ publisher.publish(new events.UserEmailChangedEvent(data));
+ });
+
+ it('should publish a UserPasswordChangedEvent', (done) => {
+ let data = {email: faker.internet.email()};
+
+ eventAggregator.subscribeOnce(events.UserPasswordChangedEvent, (eventData) => {
+ expect(eventData).toBeDefined();
+ expect(eventData).not.toBeNull();
+ expect(eventData.email).toBe(data.email);
+ done();
+ });
+
+ publisher.publish(new events.UserPasswordChangedEvent(data));
+ });
+
+ it('should publish a UserDeletedEvent', (done) => {
+ let data = { email: faker.internet.email() };
+
+ eventAggregator.subscribeOnce(events.UserDeletedEvent, (eventData) => {
+ expect(eventData).toBeDefined();
+ expect(eventData).not.toBeNull();
+ expect(eventData.email).toBe(data.email);
+ done();
+ });
+
+ publisher.publish(new events.UserDeletedEvent(data));
+ });
+
+ it('should publish a UserAuthStateChangedEvent with data', (done) => {
+ let uid = faker.random.uuid(),
+ data = { uid: uid, provider: 'password', auth: {uid:uid, provider:'password'}, expires: 111111 };
+
+ eventAggregator.subscribeOnce(events.UserAuthStateChangedEvent, (eventData) => {
+ expect(eventData).toBeDefined();
+ expect(eventData).not.toBeNull();
+ expect(eventData.uid).toBe(data.uid);
+ expect(eventData.provider).toBe(data.provider);
+ expect(eventData.auth).toBe(data.auth);
+ expect(eventData.expires).toBe(data.expires);
+ done();
+ });
+
+ publisher.publish(new events.UserAuthStateChangedEvent(data));
+ });
+
+ it('should publish a UserAuthStateChangedEvent with no data', (done) => {
+ let data = { };
+
+ eventAggregator.subscribeOnce(events.UserAuthStateChangedEvent, (eventData) => {
+ expect(eventData).toBeDefined();
+ expect(eventData).not.toBeNull();
+ expect(eventData.uid).toBeNull();
+ expect(eventData.provider).toBeNull();
+ expect(eventData.auth).toBeNull();
+ expect(eventData.expires).toBe(0);
+ done();
+ });
+
+ publisher.publish(new events.UserAuthStateChangedEvent(data));
+ });
+
+ it('should not publish handled event', (done) => {
+ let event = new events.UserCreatedEvent({});
+ event.handled = true;
+ eventAggregator.subscribeOnce(events.UserCreatedEvent, () => {
+ done.fail();
+ });
+ publisher.publish(event);
+ done();
+ });
+ });
+});
+
+function createEventData() {
+ return {
+ uid: faker.random.uuid(),
+ email: faker.internet.email(),
+ provider: faker.hacker.noun()
+ };
+}
diff --git a/test/plugin.spec.js b/test/plugin.spec.js
new file mode 100644
index 0000000..f6624eb
--- /dev/null
+++ b/test/plugin.spec.js
@@ -0,0 +1,37 @@
+import {Container} from 'aurelia-dependency-injection';
+import {configure} from '../src/index';
+
+describe('aurelia-firebase', () => {
+
+ let container = new Container();
+
+ describe('plugin initialization', () => {
+ let aurelia = {
+ globalizeResources: () => {
+
+ },
+ singleton: (type: any, implementation: Function) => {
+ this.container.registerSingleton(type,implementation);
+ return aurelia;
+ },
+ container: container
+ };
+
+ it('should export configure function', () => {
+ expect(typeof configure).toBe('function');
+ });
+
+ it('should accept a setup callback passing back the configuration instance', (done) => {
+ let firebaseUrl = 'https://aurelia-firebase.firebaseio.com',
+ loginRoute = 'a/login/route',
+ cb = (instance) => {
+ expect(typeof instance).toBe('object');
+ instance
+ .setFirebaseUrl(firebaseUrl)
+ .setMonitorAuthChange(true);
+ done();
+ };
+ configure(aurelia, cb);
+ });
+ });
+});
diff --git a/test/testHelpers.js b/test/testHelpers.js
new file mode 100644
index 0000000..593f08f
--- /dev/null
+++ b/test/testHelpers.js
@@ -0,0 +1,19 @@
+export function logInfo(message, args) {
+ log('log', message, args);
+}
+
+export function logWarn(message, args) {
+ log('warn', message, args);
+}
+
+export function logError(message, args) {
+ log('error', message, args);
+}
+
+function log(method, message, args) {
+ if(args) {
+ console[method](message, JSON.stringify(args));
+ } else {
+ console[method](message);
+ }
+}
diff --git a/test/user.spec.js b/test/user.spec.js
new file mode 100644
index 0000000..5d258b5
--- /dev/null
+++ b/test/user.spec.js
@@ -0,0 +1,64 @@
+import {User} from '../src/user';
+
+describe('User', () => {
+ it('should initialize properly without initializer', () => {
+ let user = new User();
+ expect(user.uid).toBeNull();
+ expect(user.provider).toBeNull();
+ expect(user.token).toBeNull();
+ expect(user.auth).toBeNull();
+ expect(user.expires).toBe(0);
+ expect(user.isTemporaryPassword).toBe(false);
+ expect(user.profileImageUrl).toBeNull();
+ expect(user.email).toBeNull();
+ expect(user.isAuthenticated).toBe(false);
+ });
+
+ it('should initialize properly with uncomplete initializer', () => {
+ let user = new User({uid:'uid'});
+ expect(user.uid).toBe('uid');
+ expect(user.provider).toBeNull();
+ expect(user.token).toBeNull();
+ expect(user.auth).toBeNull();
+ expect(user.expires).toBe(0);
+ expect(user.isTemporaryPassword).toBe(false);
+ expect(user.profileImageUrl).toBeNull();
+ expect(user.email).toBeNull();
+ expect(user.isAuthenticated).toBe(false);
+ });
+
+ it('should returns truthy authenticated state', () => {
+ let user = new User({auth: {}, token:'token', expires: 1000});
+ expect(user.isAuthenticated).toBe(true);
+ });
+
+ it('should returns false authenticated state', () => {
+ let user = new User({auth: {}, token:'token', expires: 0});
+ expect(user.isAuthenticated).toBe(false);
+ });
+
+ it('should returns false authenticated state', () => {
+ let user = new User({auth: {}, token:'token', expires: '45'});
+ expect(user.isAuthenticated).toBe(true);
+ });
+
+ it('should returns false authenticated state', () => {
+ let user = new User({auth: {}, token:'token', expires: 'abs'});
+ expect(user.isAuthenticated).toBe(false);
+ });
+
+ it('should returns false authenticated state', () => {
+ let user = new User({auth: {}, token:null, expires: 100});
+ expect(user.isAuthenticated).toBe(false);
+ });
+
+ it('should returns false authenticated state', () => {
+ let user = new User({auth: null, token:'token', expires: 1000});
+ expect(user.isAuthenticated).toBe(false);
+ });
+
+ it('should returns false authenticated state', () => {
+ let user = new User({auth: undefined, token:''});
+ expect(user.isAuthenticated).toBe(false);
+ });
+});