diff --git a/.gitignore b/.gitignore index 2be39e4..7be7aea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,21 @@ -bower_components* -bower-*.json +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Dependency directories +node_modules/ + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity diff --git a/demo/firebase-auth.html b/demo/firebase-auth.html index 031bfb1..1f534cb 100644 --- a/demo/firebase-auth.html +++ b/demo/firebase-auth.html @@ -12,9 +12,9 @@ firebase-auth demo - - - + + + - + app.$.messaging.activate(sw); + }); +} +

firebase-messaging demo

This demo will allow you to request notification permissions and obtain an instance token. To actually test receipt of push messages, you will need to clone the demo and modify the @@ -101,25 +101,27 @@

Message Log

- +app.logit = function(e) { + this.$.log.innerHTML = JSON.stringify(e.detail.message) + "\n" + app.$.log.innerHTML; +} + diff --git a/demo/firebase-storage-auto.html b/demo/firebase-storage-auto.html index 92e069b..d5b4bf9 100644 --- a/demo/firebase-storage-auto.html +++ b/demo/firebase-storage-auto.html @@ -11,9 +11,9 @@ firebase-storage-multiupload demo - - - + + + diff --git a/demo/firebase-storage-multiupload-auto.html b/demo/firebase-storage-multiupload-auto.html deleted file mode 100644 index 46e4573..0000000 --- a/demo/firebase-storage-multiupload-auto.html +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - - - - diff --git a/demo/firebase-storage-multiupload-auto.js b/demo/firebase-storage-multiupload-auto.js new file mode 100644 index 0000000..e9871ae --- /dev/null +++ b/demo/firebase-storage-multiupload-auto.js @@ -0,0 +1,148 @@ +import '../../../@polymer/polymer/polymer.js'; +import '../../../@polymer/paper-progress/paper-progress.js'; +import '../../../@polymer/paper-toast/paper-toast.js'; +import '../polymerfire.js'; +import { Polymer } from '../../../@polymer/polymer/lib/legacy/polymer-fn.js'; + +Polymer({ + _template: ` + + + + + + +
+ + + + +`, + + is: 'firebase-storage-multiupload-auto', + + properties: { + user: Object, + + files: { + type: Array, + notify: true, + value: [], + }, + + uploadTasks: { + type: Array, + observer: '_uploadTasksChanged' + }, + + uploadedFiles: { + type: Array, + } + }, + + catchError(e) { + this.$$('#toaster').show({ + text: e.detail.message + }); + }, + + cancel(e) { + this.$$('#task-' + e.target.value).cancel(); + }, + + resume(e) { + this.$$('#task-' + e.target.value).resume(); + }, + + pause(e) { + this.$$('#task-' + e.target.value).pause(); + }, + + download(e) { + this.$$('#ref-' + e.target.value).getDownloadURL().then(function(d) { + console.log(d) + window.open(d, '_blank') + }) + }, + + deleteFile(e) { + this.$$('#ref-' + e.target.value).delete().then(function(d) { + console.log(d) + }) + }, + + _uploadTasksChanged(uploadTasks) { + console.log(uploadTasks); + }, + + _uploadedFilesChanged(uploadedFiles) { + console.log(uploadedFiles); + }, + + onFileUpload(e) { + this.files = e.target.files; + console.log(this.files) + }, + + isEqual(a, b) { + return a === b; + }, + + signIn: function() { + this.$.auth.signInAnonymously(); + } +}); diff --git a/demo/firebase-storage-multiupload-demo.html b/demo/firebase-storage-multiupload-demo.html deleted file mode 100644 index 7a50c35..0000000 --- a/demo/firebase-storage-multiupload-demo.html +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - - - - - - diff --git a/demo/firebase-storage-multiupload-demo.js b/demo/firebase-storage-multiupload-demo.js new file mode 100644 index 0000000..e87f87a --- /dev/null +++ b/demo/firebase-storage-multiupload-demo.js @@ -0,0 +1,142 @@ +import '../../../@polymer/polymer/polymer.js'; +import '../../../@polymer/paper-progress/paper-progress.js'; +import '../../../@polymer/paper-toast/paper-toast.js'; +import '../polymerfire.js'; +import { Polymer } from '../../../@polymer/polymer/lib/legacy/polymer-fn.js'; + +Polymer({ + _template: ` + + + + + + +
+ + + + +`, + + is: 'firebase-storage-multiupload-demo', + + properties: { + user: Object, + + uploadTasks: { + type: Array, + observer: '_uploadTasksChanged' + }, + + uploadedFiles: { + type: Array, + } + }, + + catchError(e) { + this.$$('#toaster').show({ + text: e.detail.message + }); + }, + + cancel(e) { + this.$$('#task-' + e.target.value).cancel(); + }, + + resume(e) { + this.$$('#task-' + e.target.value).resume(); + }, + + pause(e) { + this.$$('#task-' + e.target.value).pause(); + }, + + download(e) { + this.$$('#ref-' + e.target.value).getDownloadURL().then(function(d) { + console.log(d) + window.open(d, '_blank') + }) + }, + + deleteFile(e) { + this.$$('#ref-' + e.target.value).delete().then(function(d) { + console.log(d) + }) + }, + + _uploadTasksChanged(uploadTasks) { + console.log(uploadTasks); + }, + + _uploadedFilesChanged(uploadedFiles) { + console.log(uploadedFiles); + }, + + isEqual(a, b) { + return a === b; + }, + + upload() { + this.$$('#fs').upload(this.$$('#file-uploader').files) + }, + + signIn: function() { + this.$.auth.signInAnonymously(); + } +}); diff --git a/demo/firebase-storage.html b/demo/firebase-storage.html index f476ba6..0fe4f03 100644 --- a/demo/firebase-storage.html +++ b/demo/firebase-storage.html @@ -11,9 +11,9 @@ firebase-storage-multiupload demo - - - + + + diff --git a/firebase-app-script.html b/firebase-app-script.html deleted file mode 100644 index 21f4f5c..0000000 --- a/firebase-app-script.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/firebase-app.html b/firebase-app.html deleted file mode 100644 index f4ba9d2..0000000 --- a/firebase-app.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - - - - - - diff --git a/firebase-app.js b/firebase-app.js new file mode 100644 index 0000000..d26f63a --- /dev/null +++ b/firebase-app.js @@ -0,0 +1,132 @@ +import '../../@polymer/polymer/polymer.js'; +import 'firebase/app'; +import 'firebase/database'; +import 'firebase/auth'; +import 'firebase/storage'; +import 'firebase/messaging'; +import 'firebase/firestore'; +import { Polymer } from '../../@polymer/polymer/lib/legacy/polymer-fn.js'; + +/** + * The firebase-app element is used for initializing and configuring your + * connection to firebase. It is permanently initialized once attached and + * should not be dynamically bound. + */ +Polymer({ + is: 'firebase-app', + + properties: { + /** + * The name of your app. Optional. + * + * You can use this with the `appName` property of other Polymerfire elements + * in order to use multiple firebase configurations on a page at once. + * In that case the name is used as a key to lookup the configuration. + */ + name: { + type: String, + value: '' + }, + + /** + * Your API key. + * + * Get this from the Auth > Web Setup panel of the new + * Firebase Console at https://console.firebase.google.com + * + * It looks like this: 'AIzaSyDTP-eiQezleFsV2WddFBAhF_WEzx_8v_g' + */ + apiKey: { + type: String + }, + + /** + * The domain name to authenticate with. + * + * The same as your Firebase Hosting subdomain or custom domain. + * Available on the Firebase Console. + * + * For example: 'polymerfire-test.firebaseapp.com' + */ + authDomain: { + type: String + }, + + /** + * The URL of your Firebase Realtime Database. You can find this + * URL in the Database panel of the Firebase Console. + * Available on the Firebase Console. + * + * For example: 'https://polymerfire-test.firebaseio.com/' + */ + databaseUrl: { + type: String + }, + + /** + * The Firebase Storage bucket for your project. You can find this + * in the Firebase Console under "Web Setup". + * + * For example: `polymerfire-test.appspot.com` + */ + storageBucket: { + type: String, + value: null + }, + + /** + * The Firebase Cloud Messaging Sender ID for your project. You can find + * this in the Firebase Console under "Web Setup". + */ + messagingSenderId: { + type: String, + value: null + }, + + /** + * The Google Cloud Project ID for your project. You can find this + * in the Firebase Console under "Web Setup". + * + * For example: `polymerfire-test` + */ + projectId: { + type: String, + value: null + }, + + /** + * The Firebase app object constructed from the other fields of + * this element. + * @type {firebase.app.App} + */ + app: { + type: Object, + notify: true, + computed: '__computeApp(name, apiKey, authDomain, databaseUrl, storageBucket, messagingSenderId, projectId)' + } + }, + + __computeApp: function(name, apiKey, authDomain, databaseUrl, storageBucket, messagingSenderId, projectId) { + if (apiKey && authDomain && databaseUrl) { + var init = [{ + apiKey: apiKey, + authDomain: authDomain, + databaseURL: databaseUrl, + projectId: projectId, + storageBucket: storageBucket, + messagingSenderId: messagingSenderId + }]; + + if (name) { + init.push(name); + } + + firebase.initializeApp.apply(firebase, init); + this.fire('firebase-app-initialized'); + } else { + return null; + } + + return firebase.app(name); + } +}); diff --git a/firebase-auth-script.html b/firebase-auth-script.html deleted file mode 100644 index 283742c..0000000 --- a/firebase-auth-script.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/firebase-auth.html b/firebase-auth.html deleted file mode 100644 index 3eb8d25..0000000 --- a/firebase-auth.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - - - - - - diff --git a/firebase-auth.js b/firebase-auth.js new file mode 100644 index 0000000..cf1d0f7 --- /dev/null +++ b/firebase-auth.js @@ -0,0 +1,251 @@ +import '../../@polymer/polymer/polymer.js'; +import { FirebaseCommonBehavior } from './firebase-common-behavior.js'; +import 'firebase/auth'; +import { Polymer } from '../../@polymer/polymer/lib/legacy/polymer-fn.js'; + +Polymer({ + + is: 'firebase-auth', + + behaviors: [ + FirebaseCommonBehavior + ], + + properties: { + /** + * [`firebase.Auth`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth) service interface. + */ + auth: { + type: Object, + computed: '_computeAuth(app)', + observer: '__authChanged' + }, + + /** + * Default auth provider OAuth flow to use when attempting provider + * sign in. This property can remain undefined when attempting to sign + * in anonymously, using email and password, or when specifying a + * provider in the provider sign-in function calls (i.e. + * `signInWithPopup` and `signInWithRedirect`). + * + * Current accepted providers are: + * + * ``` + * 'facebook' + * 'github' + * 'google' + * 'twitter' + * ``` + */ + provider: { + type: String, + notify: true + }, + + /** + * True if the client is authenticated, and false if the client is not + * authenticated. + */ + signedIn: { + type: Boolean, + computed: '_computeSignedIn(user)', + notify: true + }, + + /** + * The currently-authenticated user with user-related metadata. See + * the [`firebase.User`](https://firebase.google.com/docs/reference/js/firebase.User) + * documentation for the spec. + */ + user: { + type: Object, + readOnly: true, + value: null, + notify: true + }, + + /** + * When true, login status can be determined by checking `user` property. + */ + statusKnown: { + type: Boolean, + value: false, + notify: true, + readOnly: true, + reflectToAttribute: true + } + + }, + + /** + * Authenticates a Firebase client using a new, temporary guest account. + * + * @return {Promise} Promise that handles success and failure. + */ + signInAnonymously: function() { + if (!this.auth) { + return Promise.reject('No app configured for firebase-auth!'); + } + + return this._handleSignIn(this.auth.signInAnonymously()); + }, + + /** + * Authenticates a Firebase client using a custom JSON Web Token. + * + * @return {Promise} Promise that handles success and failure. + */ + signInWithCustomToken: function(token) { + if (!this.auth) { + return Promise.reject('No app configured for firebase-auth!'); + } + return this._handleSignIn(this.auth.signInWithCustomToken(token)); + }, + + /** + * Authenticates a Firebase client using an oauth id_token. + * + * @return {Promise} Promise that handles success and failure. + */ + signInWithCredential: function(credential) { + if (!this.auth) { + return Promise.reject('No app configured for firebase-auth!'); + } + return this._handleSignIn(this.auth.signInWithCredential(credential)); + }, + + /** + * Authenticates a Firebase client using a popup-based OAuth flow. + * + * @param {?String} provider Provider OAuth flow to follow. If no + * provider is specified, it will default to the element's `provider` + * property's OAuth flow (See the `provider` property's documentation + * for supported providers). + * @return {Promise} Promise that handles success and failure. + */ + signInWithPopup: function(provider) { + return this._attemptProviderSignIn(this._normalizeProvider(provider), this.auth.signInWithPopup); + }, + + /** + * Authenticates a firebase client using a redirect-based OAuth flow. + * + * @param {?String} provider Provider OAuth flow to follow. If no + * provider is specified, it will default to the element's `provider` + * property's OAuth flow (See the `provider` property's documentation + * for supported providers). + * @return {Promise} Promise that handles failure. (NOTE: The Promise + * will not get resolved on success due to the inherent page redirect + * of the auth flow, but it can be used to handle errors that happen + * before the redirect). + */ + signInWithRedirect: function(provider) { + return this._attemptProviderSignIn(this._normalizeProvider(provider), this.auth.signInWithRedirect); + }, + + /** + * Authenticates a Firebase client using an email / password combination. + * + * @param {!String} email Email address corresponding to the user account. + * @param {!String} password Password corresponding to the user account. + * @return {Promise} Promise that handles success and failure. + */ + signInWithEmailAndPassword: function(email, password) { + return this._handleSignIn(this.auth.signInWithEmailAndPassword(email, password)); + }, + + /** + * Creates a new user account using an email / password combination. + * + * @param {!String} email Email address corresponding to the user account. + * @param {!String} password Password corresponding to the user account. + * @return {Promise} Promise that handles success and failure. + */ + createUserWithEmailAndPassword: function(email, password) { + return this._handleSignIn(this.auth.createUserWithEmailAndPassword(email, password)); + }, + + /** + * Sends a password reset email to the given email address. + * + * @param {!String} email Email address corresponding to the user account. + * @return {Promise} Promise that handles success and failure. + */ + sendPasswordResetEmail: function(email) { + return this._handleSignIn(this.auth.sendPasswordResetEmail(email)); + }, + + /** + * Unauthenticates a Firebase client. + * + * @return {Promise} Promise that handles success and failure. + */ + signOut: function() { + if (!this.auth) { + return Promise.reject('No app configured for auth!'); + } + + return this.auth.signOut(); + }, + + _attemptProviderSignIn: function(provider, method) { + provider = provider || this._providerFromName(this.provider); + if (!provider) { + return Promise.reject('Must supply a provider for popup sign in.'); + } + if (!this.auth) { + return Promise.reject('No app configured for firebase-auth!'); + } + + return this._handleSignIn(method.call(this.auth, provider)); + }, + + _providerFromName: function(name) { + switch (name) { + case 'facebook': return new firebase.auth.FacebookAuthProvider(); + case 'github': return new firebase.auth.GithubAuthProvider(); + case 'google': return new firebase.auth.GoogleAuthProvider(); + case 'twitter': return new firebase.auth.TwitterAuthProvider(); + default: this.fire('error', 'Unrecognized firebase-auth provider "' + name + '"'); + } + }, + + _normalizeProvider: function(provider) { + if (typeof provider === 'string') { + return this._providerFromName(provider); + } + return provider; + }, + + _handleSignIn: function(promise) { + return promise.catch(function(err) { + this.fire('error', err); + throw err; + }.bind(this)); + }, + + _computeSignedIn: function(user) { + return !!user; + }, + + _computeAuth: function(app) { + return this.app.auth(); + }, + + __authChanged: function(auth, oldAuth) { + this._setStatusKnown(false); + if (oldAuth !== auth && this._unsubscribe) { + this._unsubscribe(); + this._unsubscribe = null; + } + + if (this.auth) { + this._unsubscribe = this.auth.onAuthStateChanged(function(user) { + this._setUser(user); + this._setStatusKnown(true); + }.bind(this), function(err) { + this.fire('error', err); + }.bind(this)); + } + } +}); diff --git a/firebase-common-behavior.html b/firebase-common-behavior.html deleted file mode 100644 index 79036f4..0000000 --- a/firebase-common-behavior.html +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - diff --git a/firebase-common-behavior.js b/firebase-common-behavior.js new file mode 100644 index 0000000..72103fa --- /dev/null +++ b/firebase-common-behavior.js @@ -0,0 +1,65 @@ +import '../../@polymer/polymer/polymer.js'; +import { AppNetworkStatusBehavior } from '../../@polymer/app-storage/app-network-status-behavior.js'; +import 'firebase/app'; + +export const FirebaseCommonBehaviorImpl = { + properties: { + + + /** + * @type {!firebase.app.App|undefined} + */ + app: { + type: Object, + notify: true, + observer: '__appChanged' + }, + + appName: { + type: String, + notify: true, + value: '', + observer: '__appNameChanged' + } + }, + + __appNameChanged: function(appName) { + if (this.app && this.app.name === appName) { + return; + } + + try { + if (appName == null) { + this.app = firebase.app(); + } else { + this.app = firebase.app(appName); + } + } catch (e) { + // appropriate app hasn't been initialized yet + var self = this; + window.addEventListener('firebase-app-initialized', + function onFirebaseAppInitialized(event) { + window.removeEventListener( + 'firebase-app-initialized', onFirebaseAppInitialized); + self.__appNameChanged(self.appName); + }); + } + }, + + __appChanged: function(app) { + if (app && app.name === this.appName) { + return; + } + + this.appName = app ? app.name : ''; + }, + + __onError: function(err) { + this.fire('error', err); + } +}; + +export const FirebaseCommonBehavior = [ + AppNetworkStatusBehavior, + FirebaseCommonBehaviorImpl +]; diff --git a/firebase-database-behavior.html b/firebase-database-behavior.html deleted file mode 100644 index 0cf7108..0000000 --- a/firebase-database-behavior.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - - diff --git a/firebase-database-behavior.js b/firebase-database-behavior.js new file mode 100644 index 0000000..200283b --- /dev/null +++ b/firebase-database-behavior.js @@ -0,0 +1,115 @@ +import '../../@polymer/polymer/polymer.js'; +import { AppStorageBehavior } from '../../@polymer/app-storage/app-storage-behavior.js'; +import { FirebaseCommonBehavior } from './firebase-common-behavior.js'; +import 'firebase/database'; + +export const FirebaseDatabaseBehaviorImpl = { + properties: { + db: { + type: Object, + computed: '__computeDb(app)' + }, + + ref: { + type: Object, + computed: '__computeRef(db, path, disabled)', + observer: '__refChanged' + }, + + /** + * Path to a Firebase root or endpoint. N.B. `path` is case sensitive. + * @type {string|null} + */ + path: { + type: String, + value: null, + observer: '__pathChanged' + }, + + /** + * When true, Firebase listeners won't be activated. This can be useful + * in situations where elements are loaded into the DOM before they're + * ready to be activated (e.g. navigation, initialization scenarios). + */ + disabled: { + type: Boolean, + value: false + } + }, + + observers: [ + '__onlineChanged(online)' + ], + + /** + * Set the firebase value. + * @return {!firebase.Promise} + */ + _setFirebaseValue: function(path, value) { + this._log('Setting Firebase value at', path, 'to', value) + var key = value && value.$key; + var leaf = value && value.hasOwnProperty('$val'); + if (key) { + value.$key = null; + } + var result = this.db.ref(path).set(leaf ? value.$val : value); + if (key) { + value.$key = key; + } + return result; + }, + + __computeDb: function(app) { + return app ? app.database() : null; + }, + + __computeRef: function(db, path) { + if (db == null || + path == null || + !this.__pathReady(path) || + this.disabled) { + return null; + } + + return db.ref(path); + }, + + /** + * Override this method if needed. + * e.g. to detach or attach listeners. + */ + __refChanged: function(ref, oldRef){ + return; + }, + + __pathChanged: function(path, oldPath) { + if (!this.disabled && !this.valueIsEmpty(this.data)) { + this.syncToMemory(function() { + this.data = this.zeroValue; + this.__needSetData = true; + }); + } + }, + + __pathReady: function(path) { + return path && path.split('/').slice(1).indexOf('') < 0; + }, + + __onlineChanged: function(online) { + if (!this.ref) { + return; + } + + if (online) { + this.db.goOnline(); + } else { + this.db.goOffline(); + } + } +}; + +export const FirebaseDatabaseBehavior = [ + AppStorageBehavior, + FirebaseCommonBehavior, + FirebaseDatabaseBehaviorImpl +]; diff --git a/firebase-database-script.html b/firebase-database-script.html deleted file mode 100644 index 3913f64..0000000 --- a/firebase-database-script.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/firebase-document.html b/firebase-document.html deleted file mode 100644 index d0538b1..0000000 --- a/firebase-document.html +++ /dev/null @@ -1,195 +0,0 @@ - - - - - diff --git a/firebase-document.js b/firebase-document.js new file mode 100644 index 0000000..f50711f --- /dev/null +++ b/firebase-document.js @@ -0,0 +1,193 @@ +import '../../@polymer/polymer/polymer.js'; +import { FirebaseDatabaseBehavior } from './firebase-database-behavior.js'; +import { Polymer } from '../../@polymer/polymer/lib/legacy/polymer-fn.js'; +/** +@license +Copyright 2016 Google Inc. All Rights Reserved. +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file or at +https://github.com/firebase/polymerfire/blob/master/LICENSE +*/ +'use strict'; + +/** + * The firebase-document element is an easy way to interact with a firebase + * location as an object and expose it to the Polymer databinding system. + * + * For example: + * + * + * + * + * This fetches the `noteData` object from the firebase location at + * `/users/${userId}/notes/${noteId}` and exposes it to the Polymer + * databinding system. Changes to `noteData` will likewise be, sent back up + * and stored. + * + * `` needs some information about how to talk to Firebase. + * Set this configuration by adding a `` element anywhere in your + * app. + */ +Polymer({ + is: 'firebase-document', + + behaviors: [ + FirebaseDatabaseBehavior + ], + + attached: function() { + this.__needSetData = true; + this.__refChanged(this.ref, this.ref); + }, + + detached: function() { + if (this.ref) { + this.ref.off('value', this.__onFirebaseValue, this); + } + }, + + + get isNew() { + return this.disabled || !this.__pathReady(this.path); + }, + + + get zeroValue() { + return {}; + }, + + /** + * Update the path and write this.data to that new location. + * + * Important note: `this.path` is updated asynchronously. + * + * @param {string} parentPath The new firebase location to write to. + * @param {string=} key The key within the parentPath to write `data` to. If + * not given, a random key will be generated and used. + * @return {Promise} A promise that resolves once this.data has been + * written to the new path. + * + */ + saveValue: function(parentPath, key) { + return new Promise(function(resolve, reject) { + var path = null; + + if (!this.app) { + reject(new Error('No app configured!')); + } + + if (key) { + path = parentPath + '/' + key; + resolve(this._setFirebaseValue(path, this.data)); + } else { + path = firebase.database(this.app).ref(parentPath) + .push(this.data, function(error) { + if (error) { + reject(error); + return; + } + + resolve(); + }).path.toString(); + } + + this.path = path; + }.bind(this)); + }, + + reset: function() { + this.path = null; + return Promise.resolve(); + }, + + destroy: function() { + return this._setFirebaseValue(this.path, null).then(function() { + return this.reset(); + }.bind(this)); + }, + + memoryPathToStoragePath: function(path) { + var storagePath = this.path; + + if (path !== 'data') { + storagePath += path.replace(/^data\.?/, '/').split('.').join('/'); + } + + return storagePath; + }, + + storagePathToMemoryPath: function(storagePath) { + var path = 'data'; + + storagePath = + storagePath.replace(this.path, '').split('/').join('.'); + + if (storagePath) { + path += '.' + storagePath; + } + + return path; + }, + + getStoredValue: function(path) { + return new Promise(function(resolve, reject) { + this.db.ref(path).once('value', function(snapshot) { + var value = snapshot.val(); + if (value == null) { + resolve(this.zeroValue); + } + resolve(value); + }, this.__onError, this); + }.bind(this)); + }, + + setStoredValue: function(path, value) { + return this._setFirebaseValue(path, value); + }, + + __refChanged: function(ref, oldRef) { + if (oldRef) { + oldRef.off('value', this.__onFirebaseValue, this); + } + + if (ref) { + ref.on('value', this.__onFirebaseValue, this.__onError, this); + } + }, + + __onFirebaseValue: function(snapshot) { + var value = snapshot.val(); + + if (value == null) { + value = this.zeroValue; + this.__needSetData = true; + } + + if (!this.isNew) { + this.async(function() { + this.syncToMemory(function() { + this._log('Updating data from Firebase value:', value); + + // set the value if: + // it is the first time we run this (or the path has changed and we are back with zeroValue) + // or if this.data does not exist + // or value is primitive + // or if firebase value obj contain less keys than this.data (https://github.com/Polymer/polymer/issues/2565) + if (this.__needSetData || !this.data || typeof value !== 'object' || ( Object.keys(value).length < Object.keys(this.data).length)) { + this.__needSetData = false; + return this.set('data', value); + } + + // now, we loop over keys + for (var prop in value) { + if(value[prop] !== this.data[prop]) { + this.set(['data', prop], value[prop]); + } + } + }); + }); + } + } +}); diff --git a/firebase-firestore-mixin.html b/firebase-firestore-mixin.js similarity index 86% rename from firebase-firestore-mixin.html rename to firebase-firestore-mixin.js index c7b42b3..0b91fa7 100644 --- a/firebase-firestore-mixin.html +++ b/firebase-firestore-mixin.js @@ -1,6 +1,6 @@ - - diff --git a/firebase-firestore-script.html b/firebase-firestore-script.html deleted file mode 100644 index db57bd6..0000000 --- a/firebase-firestore-script.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/firebase-messaging-script.html b/firebase-messaging-script.html deleted file mode 100644 index 071bf91..0000000 --- a/firebase-messaging-script.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/firebase-messaging.html b/firebase-messaging.html deleted file mode 100644 index fa2bf28..0000000 --- a/firebase-messaging.html +++ /dev/null @@ -1,266 +0,0 @@ - - - - - - - - - - diff --git a/firebase-messaging.js b/firebase-messaging.js new file mode 100644 index 0000000..fded581 --- /dev/null +++ b/firebase-messaging.js @@ -0,0 +1,198 @@ +import '../../@polymer/polymer/polymer.js'; +import { FirebaseCommonBehavior } from './firebase-common-behavior.js'; +import 'firebase/messaging'; +import { Polymer } from '../../@polymer/polymer/lib/legacy/polymer-fn.js'; +var stateMap = {}; + +/** + * + * @param {Object} app + * @param {string} method + * @param {...*} var_args +*/ +function applyAll(app, method, var_args) { + var args = Array.prototype.slice.call(arguments, 2); + stateMap[app.name].instances.forEach(function(el) { + el[method].apply(el, args); + }); +} + +function refreshToken(app) { + var state = stateMap[app.name]; + + app.messaging().getToken().then(function(token) { + applyAll(app, '_setToken', token); + applyAll(app, '_setStatusKnown', true); + return token; + }, function(err) { + applyAll(app, '_setToken', null); + applyAll(app, '_setStatusKnown', true); + applyAll(app, 'fire', 'error', err); + throw err; + }); +} + +function activateMessaging(el, app) { + var name = app.name; + + stateMap[name] = stateMap[name] || {messaging: app.messaging()}; + var state = stateMap[name]; + + state.instances = state.instances || []; + if (state.instances.indexOf(el) < 0) { + state.instances.push(el); + } + + if (!state.listener) { + state.listener = app.messaging().onMessage(function(message) { + state.instances.forEach(function(el) { + el._setLastMessage(message); + el.fire('message', {message: message}); + }); + }); + } + + if (!state.tokenListener) { + state.tokenListener = app.messaging().onTokenRefresh(function() { + refreshToken(app); + }); + } + + return refreshToken(app); +} + +Polymer({ + is: 'firebase-messaging', + + behaviors: [ + FirebaseCommonBehavior, + ], + + properties: { + /** + * The current registration token for this session. Save this + * somewhere server-accessible so that you can target push messages + * to this device. + * @type {string|null} + */ + token: { + type: String, + value: null, + notify: true, + readOnly: true, + }, + /** + * True when Firebase Cloud Messaging is successfully + * registered and actively listening for messages. + */ + active: { + type: Boolean, + notify: true, + computed: '_computeActive(statusKnown, token)', + }, + /** + * True after an attempt has been made to fetch a push + * registration token, regardless of whether one was available. + */ + statusKnown: { + type: Boolean, + value: false, + notify: true, + readOnly: true, + }, + /** + * The most recent push message received. Generally in the format: + * + * { + * "from": "", + * "category": "", + * "collapse_key": "do_not_collapse", + * "data": { + * "...": "..." + * }, + * "notification": { + * "...": "..." + * } + * } + */ + lastMessage: { + type: Object, + value: null, + notify: true, + readOnly: true, + }, + /** + * When true, Firebase Messaging will not initialize until `activate()` + * is called explicitly. This allows for custom service worker registration. + */ + customSw: { + type: Boolean, + value: false + }, + /** + * True if the Push API is supported in the user's browser. + */ + pushSupported: { + type: Boolean, + value: function() { + return ('serviceWorker' in navigator && 'PushManager' in window); + }, + notify: true, + readOnly: true + } + }, + + observers: [ + '_bootstrapApp(app, customSw)' + ], + + /** + * Requests Notifications permission and returns a `Promise` that + * resolves if it is granted. Resolves immediately if already granted. + */ + requestPermission: function() { + if (!this.messaging) { + throw new Error('firebase-messaging: No app configured!'); + } + + return this.messaging.requestPermission().then(function() { + return refreshToken(this.app); + }.bind(this)); + }, + + /** + * When the `custom-sw` is added to `firebase-messaging`, this method + * must be called after initialization to start listening for push + * messages. + * + * @param {ServiceWorkerRegistration=} swreg the custom service worker registration with which to activate + */ + activate: function(swreg) { + this.statusKnown = false; + this.active = false; + this.token = null; + if (this.app) { + this.messaging = this.app.messaging(); + if (swreg) { + this.messaging.useServiceWorker(swreg); + } + activateMessaging(this, this.app); + } else { + this.messaging = null; + this.statusKnown = false; + this.active = false; + this.token = null; + return; + } + }, + + _computeActive: function(statusKnown, token) { + return !!(statusKnown && token); + }, + + _bootstrapApp: function(app, customSw) { + if (app && !customSw) { + this.activate(); + } + }, +}); diff --git a/firebase-query.html b/firebase-query.html deleted file mode 100644 index d503f83..0000000 --- a/firebase-query.html +++ /dev/null @@ -1,473 +0,0 @@ - - - - - - - - - - diff --git a/firebase-query.js b/firebase-query.js new file mode 100644 index 0000000..bed9e0a --- /dev/null +++ b/firebase-query.js @@ -0,0 +1,417 @@ +import '../../@polymer/polymer/polymer.js'; +import { FirebaseDatabaseBehavior } from './firebase-database-behavior.js'; +import { Polymer } from '../../@polymer/polymer/lib/legacy/polymer-fn.js'; + +Polymer({ + is: 'firebase-query', + + behaviors: [ + FirebaseDatabaseBehavior + ], + + properties: { + /** + * [`firebase.database.Query`](https://firebase.google.com/docs/reference/js/firebase.database.Query#property) + * object computed by the following parameters. + */ + query: { + type: Object, + computed: '__computeQuery(ref, orderByChild, orderByValue, limitToFirst, limitToLast, startAt, endAt, equalTo)', + observer: '__queryChanged' + }, + + /** + * The child key of each query result to order the query by. + * + * Changing this value generates a new `query` ordered by the + * specified child key. + */ + orderByChild: { + type: String, + value: '' + }, + + /** + * Order this query by values. This is only applicable to leaf node queries + * against data structures such as `{a: 1, b: 2, c: 3}`. + */ + orderByValue: { + type: Boolean, + value: false + }, + + /** + * The value to start at in the query. + * + * Changing this value generates a new `query` with the specified + * starting point. The generated `query` includes children which match + * the specified starting point. + */ + startAt: { + type: String, + value: '' + }, + + /** + * The value to end at in the query. + * + * Changing this value generates a new `query` with the specified + * ending point. The generated `query` includes children which match + * the specified ending point. + */ + endAt: { + type: String, + value: '' + }, + + /** + * Specifies a child-key value that must be matched for each candidate result. + * + * Changing this value generates a new `query` which includes children + * which match the specified value. + */ + equalTo: { + type: Object, + value: null + }, + + /** + * The maximum number of nodes to include in the query. + * + * Changing this value generates a new `query` limited to the first + * number of children. + */ + limitToFirst: { + type: Number, + value: 0 + }, + + /** + * The maximum number of nodes to include in the query. + * + * Changing this value generates a new `query` limited to the last + * number of children. + */ + limitToLast: { + type: Number, + value: 0 + } + }, + + created: function() { + this.__map = {}; + }, + + attached: function() { + this.__queryChanged(this.query, this.query); + }, + + detached: function() { + if (this.query == null) { + return; + } + + this.__queryChanged(null, this.query); + }, + + child: function(key) { + return this.__map[key]; + }, + + get isNew() { + return this.disabled || !this.__pathReady(this.path); + }, + + get zeroValue() { + return []; + }, + + memoryPathToStoragePath: function(path) { + var storagePath = this.path; + + if (path !== 'data') { + var parts = path.split('.'); + var index = window.parseInt(parts[1], 10); + + if (index != null && !isNaN(index)) { + parts[1] = this.data[index] != null && this.data[index].$key; + } + + storagePath += parts.join('/').replace(/^data\.?/, ''); + } + + return storagePath; + }, + + storagePathToMemoryPath: function(storagePath) { + var path = 'data'; + + if (storagePath !== this.path) { + var parts = storagePath.replace(this.path + '/', '').split('/'); + var key = parts[0]; + var datum = this.__map[key]; + + if (datum) { + parts[0] = this.__indexFromKey(key); + } + + path += '.' + parts.join('.'); + } + + return path; + }, + + setStoredValue: function(storagePath, value) { + if (storagePath === this.path || /\$key$/.test(storagePath)) { + return Promise.resolve(); + } else if (/\/\$val$/.test(storagePath)) { + return this._setFirebaseValue(storagePath.replace(/\/\$val$/, ''), value); + } else { + return this._setFirebaseValue(storagePath, value); + } + }, + + _propertyToKey: function(property) { + var index = window.parseInt(property, 10); + if (index != null && !isNaN(index)) { + return this.data[index].$key; + } + }, + + __computeQuery: function(ref, orderByChild, orderByValue, limitToFirst, limitToLast, startAt, endAt, equalTo) { + if (ref == null) { + return null; + } + + var query; + + if (orderByChild) { + query = ref.orderByChild(orderByChild); + } else if (orderByValue) { + query = ref.orderByValue(); + } else { + query = ref.orderByKey(); + } + + if (limitToFirst) { + query = query.limitToFirst(limitToFirst); + } else if (limitToLast) { + query = query.limitToLast(limitToLast); + } + + if (startAt) { + query = query.startAt(startAt); + } + + if (endAt) { + query = query.endAt(endAt); + } + + if (equalTo !== null) { + query = query.equalTo(equalTo); + } + + return query; + }, + + __pathChanged: function(path, oldPath) { + // we only need to reset the data if the path is null (will also trigged when this element initiates) + // When path changes and is not null, it triggers a ref change (via __computeRef(db,path)), which then triggers a __queryChanged setting data to zeroValue + + if (path == null) { + this.syncToMemory(function() { + this.data = this.zeroValue; + }); + } + }, + + __queryChanged: function(query, oldQuery) { + if (oldQuery) { + oldQuery.off('value', this.__onFirebaseValue, this); + oldQuery.off('child_added', this.__onFirebaseChildAdded, this); + oldQuery.off('child_removed', this.__onFirebaseChildRemoved, this); + oldQuery.off('child_changed', this.__onFirebaseChildChanged, this); + oldQuery.off('child_moved', this.__onFirebaseChildMoved, this); + + this.syncToMemory(function() { + this.__map = {}; + this.set('data', this.zeroValue); + }); + } + + // this allows us to just call the addition of event listeners only once. + // __queryChanged is being called thrice when firebase-query is created + // 1 - 2. query property computed (null, undefined) + // 3. when attached is called (this.query, this.query) + // need help to fix this so that this function is only called once + + if (query) { + if(this._onOnce){ // remove handlers before adding again. Otherwise we get data multiplying + query.off('child_added', this.__onFirebaseChildAdded, this); + query.off('child_removed', this.__onFirebaseChildRemoved, this); + query.off('child_changed', this.__onFirebaseChildChanged, this); + query.off('child_moved', this.__onFirebaseChildMoved, this); + } + + this._onOnce = true; + this._query = query + + // does the on-value first + query.off('value', this.__onFirebaseValue, this) + query.on('value', this.__onFirebaseValue, this.__onError, this) + } + }, + + __indexFromKey: function(key) { + if (key != null) { + for (var i = 0; i < this.data.length; i++) { + if (this.data[i].$key === key) { + return i; + } + } + } + return -1; + }, + + __onFirebaseValue: function(snapshot) { + if (snapshot.hasChildren()) { + var data = []; + snapshot.forEach(function(childSnapshot) { + var key = childSnapshot.key; + var value = this.__valueWithKey(key, childSnapshot.val()) + + this.__map[key] = value; + data.push(value) + }.bind(this)) + + this.set('data', data); + } + + const query = this.query + + query.off('value', this.__onFirebaseValue, this) + + // ensures that all events are called once + query.off('child_added', this.__onFirebaseChildAdded, this); + query.off('child_removed', this.__onFirebaseChildRemoved, this); + query.off('child_changed', this.__onFirebaseChildChanged, this); + query.off('child_moved', this.__onFirebaseChildMoved, this); + + query.on('child_added', this.__onFirebaseChildAdded, this.__onError, this); + query.on('child_removed', this.__onFirebaseChildRemoved, this.__onError, this); + query.on('child_changed', this.__onFirebaseChildChanged, this.__onError, this); + query.on('child_moved', this.__onFirebaseChildMoved, this.__onError, this); + }, + + __onFirebaseChildAdded: function(snapshot, previousChildKey) { + var key = snapshot.key; + + // check if the key-value pair already exists + if (this.__indexFromKey(key) >= 0) return + + var value = snapshot.val(); + var previousChildIndex = this.__indexFromKey(previousChildKey); + + this._log('Firebase child_added:', key, value); + + value = this.__snapshotToValue(snapshot); + + this.__map[key] = value; + this.splice('data', previousChildIndex + 1, 0, value); + }, + + __onFirebaseChildRemoved: function(snapshot) { + + var key = snapshot.key; + var value = this.__map[key]; + + this._log('Firebase child_removed:', key, value); + + if (value) { + this.__map[key] = null; + this.async(function() { + this.syncToMemory(function() { + // this only catches already deleted keys (which will return -1) + // at least it will not delete the last element from the array (this.splice('data', -1, 1)) + if (this.__indexFromKey(key) >= 0) { + this.splice('data', this.__indexFromKey(key), 1); + } + }); + }); + } + }, + + __onFirebaseChildChanged: function(snapshot) { + var key = snapshot.key; + var prev = this.__map[key]; + + this._log('Firebase child_changed:', key, prev); + + if (prev) { + this.async(function() { + var index = this.__indexFromKey(key); + var value = this.__snapshotToValue(snapshot); + + this.__map[key] = value; + + this.syncToMemory(function() { + // TODO(cdata): Update this as appropriate when dom-repeat + // supports custom object key indices. + if (value instanceof Object) { + for (var property in value) { + this.set(['data', index, property], value[property]); + } + for (var property in prev) { + if(!value.hasOwnProperty(property)) { + this.set(['data', index, property], null); + } + } + } else { + this.set(['data', index], value); + } + }); + }); + } + }, + + __onFirebaseChildMoved: function(snapshot, previousChildKey) { + var key = snapshot.key; + var value = this.__map[key]; + var targetIndex = previousChildKey ? this.__indexFromKey(previousChildKey) + 1 : 0; + + this._log('Firebase child_moved:', key, value, + 'to index', targetIndex); + + if (value) { + var index = this.__indexFromKey(key); + value = this.__snapshotToValue(snapshot); + + this.__map[key] = value; + + this.async(function() { + this.syncToMemory(function() { + this.splice('data', index, 1); + this.splice('data', targetIndex, 0, value); + }); + }); + } + }, + + __valueWithKey: function(key, value) { + var leaf = typeof value !== 'object'; + + if (leaf) { + value = {$key: key, $val: value}; + } else { + value.$key = key; + } + return value; + }, + + __snapshotToValue: function(snapshot) { + var key = snapshot.key; + var value = snapshot.val(); + + return this.__valueWithKey(key, value); + } +}); diff --git a/firebase-storage-behavior.html b/firebase-storage-behavior.html deleted file mode 100644 index 9fa8b3a..0000000 --- a/firebase-storage-behavior.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - diff --git a/firebase-storage-behavior.js b/firebase-storage-behavior.js new file mode 100644 index 0000000..dcf87ed --- /dev/null +++ b/firebase-storage-behavior.js @@ -0,0 +1,103 @@ +import '../../@polymer/polymer/polymer.js'; +import { FirebaseCommonBehavior } from './firebase-common-behavior.js'; +import 'firebase/storage'; + +export const FirebaseStorageBehaviorImpl = { + properties: { + /** + * Firebase storage instance + * + */ + storage : { + type: Object, + computed: '__computeStorage(app)' + }, + + /** + * Firebase storage ref instance + * + */ + ref: { + type: Object, + computed: '__computeRef(storage, path)' + }, + + /** + * Path to a Firebase storage root or endpoint. N.B. `path` is case sensitive. + */ + path: { + type: String, + observer: '__pathChanged', + value: null + }, + + /** + * Forces every upload to be a unique file by adding a date of upload at the start of the file. + * + */ + forceUnique: { + type: Boolean, + value: false + }, + + /** + * When true, will perform detailed logging. + */ + log: { + type: Boolean, + value: false + } + }, + + get zeroValue() { + return []; + }, + + __put: function(path, file, metadata) { + this._log('Putting Firebase file at', path ? this.path + '/' + path : this.path); + if (file) { + var newFilename = this.forceUnique ? Date.now().toString() + '-' + file.name : file.name; + return path ? this.ref.root.child(path + '/' + newFilename).put(file, metadata) : this.ref.child(newFilename).put(file, metadata); + } + return path ? this.ref.child(path).delete() : this.ref.delete(); + }, + + __putString: function(path, data, format, metadata) { + this._log('Putting Firebase file at', path ? this.path + '/' + path : this.path); + if (data) { + var ref = path ? this.storage.ref().child(path) : this.ref; + return ref.putString(data, format, metadata); + } + return path ? this.ref.child(path).delete() : this.ref.delete(); + }, + + __computeStorage: function(app) { + return app ? app.storage() : null; + }, + + __computeRef: function(storage, path) { + if (storage == null || + path == null || + path.split('/').slice(1).indexOf('') >= 0) { + return null; + } + return storage.ref(path); + }, + + __pathChanged: function(path) {}, + + /** + * A wrapper around `console.log`. + */ + _log: function() { + if (this.log) { + console.log.apply(console, arguments); + } + } + +}; + +export const FirebaseStorageBehavior = [ + FirebaseCommonBehavior, + FirebaseStorageBehaviorImpl +]; diff --git a/firebase-storage-multiupload.html b/firebase-storage-multiupload.html deleted file mode 100644 index d1790b5..0000000 --- a/firebase-storage-multiupload.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - diff --git a/firebase-storage-multiupload.js b/firebase-storage-multiupload.js new file mode 100644 index 0000000..cc0efac --- /dev/null +++ b/firebase-storage-multiupload.js @@ -0,0 +1,198 @@ +import '../../@polymer/polymer/polymer.js'; +import { FirebaseStorageBehavior } from './firebase-storage-behavior.js'; +import { Polymer } from '../../@polymer/polymer/lib/legacy/polymer-fn.js'; + +/** +* The firebase-storage-multiupload element is an easy way to upload files by +* expose the firebase storage api to the Polymer databinding system. +* +* For example: +* +* +* +* +* This fetches the `fileArray` object, which is usually an array of Files, +* or a FileList, which are then automatically uploaded to +* `/users/${userId}/files/${filepath}` and then creates an array of upload +* tasks that are exposed through the Polymer databinding system via the +* `uploadTasks`. Changes to `fileArray` will likewise create a new set of +* uploads, which creates a new set of tasks, which are appended to the +* `uploadTasks`. +* +* You can then use `` to cancel, pause or resume the upload. +* There are two ways to do this. You can encapsulate `firebase-storage-upload-task` in another +* element to have a local scope of the upload task's state: +* +* ``` +* file-uploader +* +* +* +* +* +* +* +* file-task +* +* +* +* ``` +* +* or you can just add the states in the uploadTasks list +* +* ``` +* +* +* +* +* ``` +* +* `` needs some information about how to talk to Firebase. +* Set this configuration by adding a `` element anywhere in your +* app. +*/ +Polymer({ + is: 'firebase-storage-multiupload', + + properties: { + /** + * The files to be uploaded. + */ + files: { + type: Array + }, + + /** + * The upload tasks after invoking the Firebase storage put method + * + */ + uploadTasks: { + type: Array, + notify: true, + value: [] + }, + + /** + * Uploads the files automatically when the file list has been changed/updated + * + */ + auto: { + type: Boolean, + value: false + } + }, + + behaviors: [ + FirebaseStorageBehavior + ], + + /** + * @override + */ + get isNew() { + return !this.path; + }, + + /** + * @override + */ + get zeroValue() { + return []; + }, + + observers: [ + '__filesChanged(files, auto)' + ], + + /** + * Upload files, update the path and write this.files to that new location. + * + * Important note: `this.path` is updated asynchronously. + * + * @param {Array} Array of Files to be uploaded + * @param {string} path of the new firebase location to write to. + * @return {Promise} A promise that resolves once this.files has been + * written to the new path. + * @override + */ + upload: function(files, path) { + if (!this.app) { + this.fire('error', new Error('No app configured!')) + } + this._putMultipleFirebaseFiles(path || this.path, files && files.length ? files : this.files); + if (path) { + this.path = path; + } + }, + + /** + * Resets the firebase-storage-multiupload instance + * + */ + reset: function() { + this.path = null; + this.clearTasks(); + return Promise.resolve(); + }, + + /** + * Resets the upload tasks + * + */ + clearTasks: function() { + this.uploadTasks = []; + }, + + _putMultipleFirebaseFiles: function(path, files) { + this._log('Putting Multiple Firebase files at', path ? this.path + '/' + path : this.path); + files = files && typeof files === 'object' && files.name ? [files] : files; + if (files && files.length > 0) { + for (var i = 0; i < files.length; i++) { + var uploadTask = this.__put(path, files[i], this.metadata ? this.metadata : files[i].metadata); + uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, function(snapshot) {}, function(error) { + this.fire('error', error, { bubble: false}); + }.bind(this)); + this.push('uploadTasks', uploadTask); + } + } + }, + + __filesChanged: function(files, auto) { + if (auto && files && files.length) { + this.upload(files) + } + }, + + __pathChanged: function(path, oldPath) { + if (oldPath != null) { + this.clearTasks(); + } + }, +}); diff --git a/firebase-storage-ref.html b/firebase-storage-ref.html deleted file mode 100644 index 8b9952a..0000000 --- a/firebase-storage-ref.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - diff --git a/firebase-storage-ref.js b/firebase-storage-ref.js new file mode 100644 index 0000000..73c460b --- /dev/null +++ b/firebase-storage-ref.js @@ -0,0 +1,204 @@ +import '../../@polymer/polymer/polymer.js'; +import { FirebaseStorageBehavior } from './firebase-storage-behavior.js'; +import { Polymer } from '../../@polymer/polymer/lib/legacy/polymer-fn.js'; + +/** +* The firebase-storage-ref element is an easy way to interact with a firebase +* storage as an object and expose it to the Polymer databinding system. +* +* For example: +* +* +* +* +* This fetches file associated within the `path` attribute from the firebase storage +* and produces the metadata and the download url associated with it. +* It also exposes several firebase storage methods to manipulate and get +* additional data from it. +* +* `` needs some information about how to talk to Firebase. +* Set this configuration by adding a `` element anywhere in your +* app. +*/ +Polymer({ + is: 'firebase-storage-ref', + + properties: { + /** + * The url of the file for download + */ + downloadUrl: { + type: String, + notify: true + }, + + /** + * The metadata of the file + */ + metadata: { + type: Object, + notify: true + }, + + /** + * The Cloud Storage URI string of this object in the form `gs://///` + */ + storageUri: { + type: String, + notify: true + }, + + /** + * The upload task of the file when you use the put method. + */ + uploadTask: { + type: Object, + notify: true + } + }, + + behaviors: [ + FirebaseStorageBehavior + ], + + + observers: [ + '__pathChanged(path, storage)' + ], + + /** + * @override + */ + get isNew() { + return !this.path; + }, + + /** + * @override + */ + get zeroValue() { + return []; + }, + + __pathChanged: function(path) { + if (this.storage) { + this.getDownloadURL(path).then(function(downloadUrl) { + this.downloadUrl = downloadUrl; + }.bind(this)).catch(function(error) { + this.fire('error', error, { bubble: false}); + }.bind(this)); + + this.getMetadata(path).then(function(metadata) { + this.metadata = metadata; + }.bind(this)).catch(function(error) { + this.fire('error', error, { bubble: false}); + }.bind(this)); + + this.storageUri = this.toGsString(path); + } + }, + + + /** + * Resets this element's path + */ + reset: function() { + this.path = null; + return Promise.resolve(); + }, + + /** + * Sets the path from url + */ + setPathFromUrl: function(url) { + if (url) { + this.path = this.getPathFromUrl(url); + return new Promise.resolve(); + } + return new Promise.resolve(); + }, + + /** + * Get's the path from url + */ + getPathFromUrl: function(url) { + return url ? this.storage.refFromURL(url) : null; + }, + + /** + * Deletes the file associated in the firebase storage path + */ + delete: function() { + return this.__put().then(function() { + return this.reset(); + }.bind(this)); + }, + + /** + * Stores a new single file inside this path + */ + put: function(file, metadata) { + this.uploadTask = this.__put(null, file, metadata); + return this.uploadTask; + }, + + /** + * Stores a string in a given format + */ + putString: function(data, format, metadata) { + this.uploadTask = this.__putString(null, data, format, metadata); + return this.uploadTask; + }, + + /** + * Get the download url of the file + */ + getDownloadURL: function(path) { + if (path) { + return this.storage.ref(path).getDownloadURL(); + } else if (this.ref) { + return this.ref.getDownloadURL(); + } + return new Promise(function(resolve, reject) {resolve();}); + }, + + /** + * Get the metadata of the file + */ + getMetadata: function(path) { + if (path) { + return this.storage.ref(path).getMetadata(); + } else if (this.ref) { + return this.ref.getMetadata(); + } + return new Promise(function(resolve, reject) {resolve();}); + }, + + /** + * Returns a gs:// URL for this object in the form gs:///// + */ + + toGsString: function(path) { + if (path) { + return this.storage.ref(path).toString(); + } else if (this.ref) { + return this.ref.toString(); + } + }, + + /** + * Sets the metadata of the file + */ + setMetadata: function(metadata, path) { + if (path) { + return this.storage.ref(path).updateMetadata(metadata); + } else if (this.ref) { + return this.ref.updateMetadata(metadata); + } + return new Promise(function(resolve, reject) {resolve();}); + } +}); diff --git a/firebase-storage-script.html b/firebase-storage-script.html deleted file mode 100644 index 4f7fcff..0000000 --- a/firebase-storage-script.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/firebase-storage-upload-task.html b/firebase-storage-upload-task.html deleted file mode 100644 index 2dfdf90..0000000 --- a/firebase-storage-upload-task.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - - diff --git a/firebase-storage-upload-task.js b/firebase-storage-upload-task.js new file mode 100644 index 0000000..c3a2f69 --- /dev/null +++ b/firebase-storage-upload-task.js @@ -0,0 +1,158 @@ +import '../../@polymer/polymer/polymer.js'; +import { FirebaseStorageBehavior } from './firebase-storage-behavior.js'; +import { Polymer } from '../../@polymer/polymer/lib/legacy/polymer-fn.js'; + +/** +* The firebase-storage-upload-task element is an easy way to track upload tasks made +* by the firebase-storage-multiupload or the firebase-storage-ref +* +* For example: +* +* +* +* It has to get the upload task from either the firebase-storage-multiupload or firebase-storage-ref +* and produces data like bytes-transferred, total-bytes, states, etc... +* +* You can also use this element to cancel, pause, and resume a task. +* +* +* `` needs some information about how to talk to Firebase. +* Set this configuration by adding a `` element anywhere in your +* app. +*/ +Polymer({ + is: 'firebase-storage-upload-task', + + properties: { + /** + * The upload task that is being tracked + */ + task: { + type: Object, + value: null, + observer: '_taskChanged' + }, + + /** + * Number of bytes transferred + */ + bytesTransferred: { + type: Number, + notify: true + }, + + /** + * Total number of bytes of the file + */ + totalBytes: { + type: Number, + notify: true + }, + + /** + * The upload task's state + */ + state: { + type: Object, + notify: true + }, + + /** + * The download url of the file + */ + downloadUrl: { + type: String, + notify: true + }, + + /** + * The metadata of the file + */ + metadata: { + type: Object, + notify: true + }, + + /** + * The firebase storage path of the file + */ + path: { + type: String, + notify: true + }, + + /** + * The current snapshot of the task + */ + snapshot: { + type: Object, + notify: true + } + }, + + behaviors: [ + FirebaseStorageBehavior + ], + + /** + * @override + */ + get zeroValue() { + return []; + }, + + _updateProperties: function(snapshot) { + this.state = snapshot.state; + this.totalBytes = snapshot.totalBytes; + this.bytesTransferred = snapshot.bytesTransferred; + this.downloadUrl = snapshot.downloadURL; + this.metadata = snapshot.metadata; + this.path = snapshot.ref.fullPath; + this.snapshot = snapshot; + }, + + _taskChanged: function(task) { + this._updateProperties(task.snapshot); + task.on(firebase.storage.TaskEvent.STATE_CHANGED, + this._updateProperties.bind(this), function(error) { + this._updateProperties(task.snapshot) + this.fire('error', error, { bubble: false}); + }.bind(this), function() { + this._updateProperties(task.snapshot) + }.bind(this)); + }, + + /** + * Cancels the upload + */ + cancel: function() { + return this.task ? this.task.cancel() : new Promise(function(resolve, reject) { + reject('No task included') + }); + }, + + /** + * Resumes a paused upload + */ + resume: function() { + return this.task ? this.task.resume() : new Promise(function(resolve, reject) { + reject('No task included') + }); + }, + + /** + * Pauses the upload + */ + pause: function() { + return this.task ? this.task.pause() : new Promise(function(resolve, reject) { + reject('No task included') + }); + } +}); diff --git a/firebase.html b/firebase.html index 464ed67..cc3228c 100644 --- a/firebase.html +++ b/firebase.html @@ -6,8 +6,8 @@ https://github.com/firebase/polymerfire/blob/master/LICENSE --> - - - - - + + + + + diff --git a/index.html b/index.html index f292f8d..ba48a35 100644 --- a/index.html +++ b/index.html @@ -12,8 +12,8 @@ - - + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..b668fd4 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "@polymer/polymerfire", + "flat": true, + "version": "3.0.0", + "description": "Polymer Web Components for Firebase", + "contributors": [ + "Firebase" + ], + "keywords": [ + "web-component", + "polymer", + "firebase" + ], + "main": "polymerfire.html", + "license": "http://polymer.github.io/LICENSE.txt", + "homepage": "https://github.com/firebase/polymerfire", + "dependencies": { + "@polymer/app-storage": "^3.0.0-pre.1", + "@polymer/polymer": "^3.0.0-pre.1", + "firebase": "^5.0.1" + }, + "devDependencies": {} +} diff --git a/polymerfire.html b/polymerfire.html deleted file mode 100644 index 50dab49..0000000 --- a/polymerfire.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - diff --git a/polymerfire.js b/polymerfire.js new file mode 100644 index 0000000..bdf2711 --- /dev/null +++ b/polymerfire.js @@ -0,0 +1,18 @@ +import './firebase-app.js'; +import './firebase-auth.js'; +import './firebase-document.js'; +import './firebase-query.js'; +import './firebase-messaging.js'; +import './firebase-storage-multiupload.js'; +import './firebase-storage-upload-task.js'; +import './firebase-storage-ref.js'; +import './firebase-firestore-mixin.js'; + +/** +@license +Copyright 2016 Google Inc. All Rights Reserved. +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file or at +https://github.com/firebase/polymerfire/blob/master/LICENSE +*/ +; diff --git a/test/firebase-auth.html b/test/firebase-auth.html index 5e4f5f1..5febc49 100644 --- a/test/firebase-auth.html +++ b/test/firebase-auth.html @@ -13,11 +13,11 @@ firebase-auth tests - - + + - - + + @@ -60,77 +60,79 @@ - diff --git a/test/firebase-common-behavior.html b/test/firebase-common-behavior.html index 8eba2b9..d4ccb0d 100644 --- a/test/firebase-common-behavior.html +++ b/test/firebase-common-behavior.html @@ -13,12 +13,12 @@ FirebaseCommonBehavior tests - - + + - - - + + + @@ -54,58 +54,60 @@ - + }); +}); + diff --git a/test/firebase-database-behavior.html b/test/firebase-database-behavior.html index f3efa47..5dca770 100644 --- a/test/firebase-database-behavior.html +++ b/test/firebase-database-behavior.html @@ -13,12 +13,12 @@ FirebaseDatabaseBehavior tests - - + + - - - + + + @@ -41,43 +41,45 @@ - + }); +}); + diff --git a/test/firebase-document.html b/test/firebase-document.html index b97adcf..bf22728 100644 --- a/test/firebase-document.html +++ b/test/firebase-document.html @@ -13,13 +13,13 @@ firebase-document tests - - + + - - - - + + + + @@ -43,29 +43,32 @@ - + }, + fetchStoredValue: window.foo = function(storagePath) { + return new Promise(function(resolve) { + var ref = firebase.app('test').database().ref(storagePath); + ref.on('value', function(snapshot) { + ref.off('value'); + resolve(snapshot.val()); + }); + }); + } +}); + diff --git a/test/firebase-query.html b/test/firebase-query.html index 777e7b5..1a4f0fa 100644 --- a/test/firebase-query.html +++ b/test/firebase-query.html @@ -13,11 +13,11 @@ firebase-query tests - - + + - - + + - + @@ -61,309 +64,311 @@ - + }); +}); + diff --git a/test/index.html b/test/index.html index 16edb4f..fb0bdbd 100644 --- a/test/index.html +++ b/test/index.html @@ -10,8 +10,8 @@ - - + +