Skip to content

Commit

Permalink
Refactor Auth storage test (#530)
Browse files Browse the repository at this point in the history
* refactored storage test

* [AUTOMATED]: Prettier Code Styling

* [AUTOMATED]: Prettier Code Styling

* migrate storage from localstorage to indexedDb

* added worker compatibility, exposed finally, updated error message

* appended photo size for google hosted image
  • Loading branch information
wti806 authored Mar 15, 2018
1 parent 2cb9ef5 commit 1b3ba41
Show file tree
Hide file tree
Showing 39 changed files with 1,625 additions and 383 deletions.
10 changes: 9 additions & 1 deletion packages/auth/demo/public/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,15 @@ function refreshUserData() {
$('input.profile-name').val(user.displayName);
$('input.photo-url').val(user.photoURL);
if (user.photoURL != null) {
$('img.profile-image').attr('src', user.photoURL).show();
var photoURL = user.photoURL;
// Append size to the photo URL for Google hosted images to avoid requesting
// the image with its original resolution (using more bandwidth than needed)
// when it is going to be presented in smaller size.
if ((photoURL.indexOf('googleusercontent.com') != -1) ||
(photoURL.indexOf('ggpht.com') != -1)) {
photoURL = photoURL + '?sz=' + $('img.profile-image').height();
}
$('img.profile-image').attr('src', photoURL).show();
} else {
$('img.profile-image').hide();
}
Expand Down
62 changes: 59 additions & 3 deletions packages/auth/src/authstorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ goog.provide('fireauth.authStorage.Persistence');
goog.require('fireauth.AuthError');
goog.require('fireauth.authenum.Error');
goog.require('fireauth.storage.Factory');
goog.require('fireauth.storage.Storage');
goog.require('fireauth.util');
goog.require('goog.Promise');
goog.require('goog.array');
Expand Down Expand Up @@ -235,6 +236,12 @@ fireauth.authStorage.Manager.getInstance = function() {
};


/** Clears storage manager instances. This is used for testing. */
fireauth.authStorage.Manager.clear = function() {
fireauth.authStorage.Manager.instance_ = null;
};


/**
* Returns the storage corresponding to the specified persistence.
* @param {!fireauth.authStorage.Persistence} persistent The type of storage
Expand Down Expand Up @@ -270,6 +277,55 @@ fireauth.authStorage.Manager.prototype.getKeyName_ = function(dataKey, opt_id) {
};


/**
* Migrates window.localStorage to the provided persistent storage.
* @param {fireauth.authStorage.Key} dataKey The key under which the persistent
* value is supposed to be stored.
* @param {?string=} opt_id When operating in multiple app mode, this ID
* associates storage values with specific apps.
* @return {!goog.Promise<void>} A promise that resolves when the data stored
* in window.localStorage is migrated to the provided persistent storage
* identified by the provided data key.
*/
fireauth.authStorage.Manager.prototype.migrateFromLocalStorage =
function(dataKey, opt_id) {
var self = this;
var key = this.getKeyName_(dataKey, opt_id);
var storage = this.getStorage_(dataKey.persistent);
// Get data stored in the default persistent storage identified by dataKey.
return this.get(dataKey, opt_id).then(function(response) {
// Get the stored value in window.localStorage if available.
var oldStorageValue = null;
try {
oldStorageValue = fireauth.util.parseJSON(
goog.global['localStorage']['getItem'](key));
} catch (e) {
// Set value as null. This will resolve the promise immediately.
}
// If data is stored in window.localStorage but no data is available in
// default persistent storage, migrate data from window.localStorage to
// default persistent storage.
if (oldStorageValue && !response) {
// This condition may fail in situations where a user opens a tab with
// an old version while using a tab with a new version, or when a
// developer switches back and forth between and old and new version of
// the library.
goog.global['localStorage']['removeItem'](key);
// Migrate the value to new default persistent storage.
return self.set(dataKey, oldStorageValue, opt_id);
} else if (oldStorageValue &&
response &&
storage.type != fireauth.storage.Storage.Type.LOCAL_STORAGE) {
// Data stored in both localStorage and new persistent storage (eg.
// indexedDB) for some reason.
// This could happen if the developer is migrating back and forth.
// The new default persistent storage (eg. indexedDB) takes precedence.
goog.global['localStorage']['removeItem'](key);
}
});
};


/**
* Gets the stored value from the corresponding storage.
* @param {fireauth.authStorage.Key} dataKey The key under which the value is
Expand Down Expand Up @@ -404,9 +460,9 @@ fireauth.authStorage.Manager.prototype.startListeners_ = function() {
// TODO: refactor this implementation to be handled by the underlying
// storage mechanism.
if (!this.runsInBackground_ &&
// Add an exception for IE11 and Edge browsers, we should stick to
// indexedDB in that case.
!fireauth.util.isLocalStorageNotSynchronized() &&
// Add an exception for browsers that persist storage with indexedDB, we
// should stick with indexedDB listener implementation in that case.
!fireauth.util.persistsStorageWithIndexedDB() &&
// Confirm browser web storage is supported as polling relies on it.
this.webStorageSupported_) {
this.startManualListeners_();
Expand Down
4 changes: 3 additions & 1 deletion packages/auth/src/error_auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,9 @@ fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_APP_CREDENTIAL] =
fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_APP_ID] =
'The mobile app identifier is not registed for the current project.';
fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_AUTH] =
'The user\'s credential is no longer valid. The user must sign in again.';
'This user\'s credential isn\'t valid for this project. This can happen ' +
'if the user\'s token has been tampered with, or if the user isn\'t for ' +
'the project associated with this API key.';
fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_AUTH_EVENT] =
'An internal error has occurred.';
fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_CODE] =
Expand Down
3 changes: 3 additions & 0 deletions packages/auth/src/exports_auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,9 @@ fireauth.exportlib.exportPrototypeMethods(

fireauth.exportlib.exportPrototypeMethods(
goog.Promise.prototype, {
thenAlways: {
name: 'finally'
},
thenCatch: {
name: 'catch'
},
Expand Down
15 changes: 10 additions & 5 deletions packages/auth/src/iframeclient/iframewrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,12 @@ fireauth.iframeclient.IframeWrapper.prototype.registerEvent =
this.onIframeOpen_.then(function() {
self.iframe_.register(
eventName,
handler,
/** @type {function(this:gapi.iframes.Iframe,
* *, gapi.iframes.Iframe): *}
*/ (handler),
/** @type {!gapi.iframes.IframesFilter} */ (
fireauth.util.getObjectRef(
'gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER')));
fireauth.util.getObjectRef(
'gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER')));
});
};

Expand All @@ -191,12 +193,15 @@ fireauth.iframeclient.IframeWrapper.prototype.unregisterEvent =
function(eventName, handler) {
var self = this;
this.onIframeOpen_.then(function() {
self.iframe_.unregister(eventName, handler);
self.iframe_.unregister(
eventName,
/** @type {(function(this:gapi.iframes.Iframe,
* *, gapi.iframes.Iframe): *|undefined)}
*/ (handler));
});
};



/** @private @const {!goog.string.Const} The GApi loader URL. */
fireauth.iframeclient.IframeWrapper.GAPI_LOADER_SRC_ = goog.string.Const.from(
'https://apis.google.com/js/api.js?onload=%{onload}');
Expand Down
12 changes: 9 additions & 3 deletions packages/auth/src/recaptchaverifier/recaptchaverifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ fireauth.BaseRecaptchaVerifier = function(apiKey, container, opt_parameters,
this.isInvisible_ =
this.parameters_[fireauth.BaseRecaptchaVerifier.ParamName.SIZE] ===
'invisible';
// Check if DOM is supported.
if (!fireauth.util.isDOMSupported()) {
throw new fireauth.AuthError(
fireauth.authenum.Error.OPERATION_NOT_SUPPORTED,
'RecaptchaVerifier is only supported in a browser HTTP/HTTPS ' +
'environment with DOM support.');
}
// reCAPTCHA container must be valid and if visible, not empty.
// An invisible reCAPTCHA will not render in its container. That container
// will execute the reCAPTCHA when it is clicked.
Expand Down Expand Up @@ -257,9 +264,8 @@ fireauth.BaseRecaptchaVerifier.prototype.isReady_ = function() {
this.cachedReadyPromise_ = this.registerPendingPromise_(goog.Promise.resolve()
.then(function() {
// Verify environment first.
// This is actually not enough as this could be triggered from a worker
// environment, but DOM ready should theoretically not resolve.
if (fireauth.util.isHttpOrHttps()) {
// Fail quickly from a worker environment or non-HTTP/HTTPS environment.
if (fireauth.util.isHttpOrHttps() && !fireauth.util.isWorker()) {
// Wait for DOM to be ready as this feature depends on that.
return fireauth.util.onDomReady();
} else {
Expand Down
62 changes: 43 additions & 19 deletions packages/auth/src/rpchandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ goog.require('goog.html.TrustedResourceUrl');
goog.require('goog.json');
goog.require('goog.net.CorsXmlHttpFactory');
goog.require('goog.net.EventType');
goog.require('goog.net.FetchXmlHttpFactory');
goog.require('goog.net.XhrIo');
goog.require('goog.net.XmlHttpFactory');
goog.require('goog.net.jsloader');
Expand Down Expand Up @@ -93,13 +94,6 @@ fireauth.XmlHttpFactory.prototype.internalGetOptions = function() {
* @constructor
*/
fireauth.RpcHandler = function(apiKey, opt_config, opt_firebaseClientVersion) {
// Get XMLHttpRequest reference.
var XMLHttpRequest = fireauth.util.getXMLHttpRequest();
if (!XMLHttpRequest) {
// In a Node.js environment, xmlhttprequest module needs to be required.
throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR,
'The XMLHttpRequest compatibility library was not found.');
}
this.apiKey_ = apiKey;
var config = opt_config || {};
this.secureTokenEndpoint_ = config['secureTokenEndpoint'] ||
Expand Down Expand Up @@ -131,10 +125,46 @@ fireauth.RpcHandler = function(apiKey, opt_config, opt_firebaseClientVersion) {
// Log client version for securetoken server.
this.secureTokenHeaders_['X-Client-Version'] = opt_firebaseClientVersion;
}
/** @const @private {!goog.net.CorsXmlHttpFactory} The CORS XHR factory. */
this.corsXhrFactory_ = new goog.net.CorsXmlHttpFactory();
/** @const @private {!goog.net.XmlHttpFactory} The XHR factory. */
this.xhrFactory_ = new fireauth.XmlHttpFactory(XMLHttpRequest);

// Get XMLHttpRequest reference.
var XMLHttpRequest = fireauth.RpcHandler.getXMLHttpRequest();
if (!XMLHttpRequest && !fireauth.util.isWorker()) {
// In a Node.js environment, xmlhttprequest module needs to be required.
throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR,
'The XMLHttpRequest compatibility library was not found.');
}
/** @private {!goog.net.XmlHttpFactory|undefined} The XHR factory. */
this.rpcHandlerXhrFactory_ = undefined;
// Initialize XHR factory. CORS does not apply in native environments or
// workers so don't use CorsXmlHttpFactory in those cases.
if (fireauth.util.isWorker()) {
// For worker environment use FetchXmlHttpFactory.
this.rpcHandlerXhrFactory_ = new goog.net.FetchXmlHttpFactory(
/** @type {!WorkerGlobalScope} */ (self));
} else if (fireauth.util.isNativeEnvironment()) {
// For Node.js, this is the polyfill library. For other environments,
// this is the native global XMLHttpRequest.
this.rpcHandlerXhrFactory_ = new fireauth.XmlHttpFactory(
/** @type {function(new:XMLHttpRequest)} */ (XMLHttpRequest));
} else {
// CORS Browser environment.
this.rpcHandlerXhrFactory_ = new goog.net.CorsXmlHttpFactory();
}
};


/**
* @return {?function(new:XMLHttpRequest)|undefined} The current environment
* XMLHttpRequest. This is undefined for worker environment.
*/
fireauth.RpcHandler.getXMLHttpRequest = function() {
// In Node.js XMLHttpRequest is polyfilled.
var isNode = fireauth.util.getEnvironment() == fireauth.util.Env.NODE;
var XMLHttpRequest = goog.global['XMLHttpRequest'] ||
(isNode &&
firebase.INTERNAL['node'] &&
firebase.INTERNAL['node']['XMLHttpRequest']);
return XMLHttpRequest;
};


Expand Down Expand Up @@ -394,7 +424,7 @@ fireauth.RpcHandler.prototype.sendXhr_ = function(
return;
}
var sendXhr;
if (fireauth.util.supportsCors()) {
if (fireauth.util.supportsCors() || fireauth.util.isWorker()) {
// If supports CORS use goog.net.XhrIo.
sendXhr = goog.bind(this.sendXhrUsingXhrIo_, this);
} else {
Expand Down Expand Up @@ -432,13 +462,7 @@ fireauth.RpcHandler.prototype.sendXhrUsingXhrIo_ = function(
opt_data,
opt_headers,
opt_timeout) {
// Send XHR request. CORS does not apply in native environments so don't use
// CorsXmlHttpFactory in those cases.
// For a Node.js environment use the fireauth.XmlHttpFactory instance.
var isNode = fireauth.util.getEnvironment() == fireauth.util.Env.NODE;
var xhrIo = fireauth.util.isNativeEnvironment() ?
(isNode ? new goog.net.XhrIo(this.xhrFactory_) : new goog.net.XhrIo()) :
new goog.net.XhrIo(this.corsXhrFactory_);
var xhrIo = new goog.net.XhrIo(this.rpcHandlerXhrFactory_);

// xhrIo.setTimeoutInterval not working in IE10 and IE11, handle manually.
var requestTimeout;
Expand Down
2 changes: 2 additions & 0 deletions packages/auth/src/storage/asyncstorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ fireauth.storage.AsyncStorage = function(opt_asyncStorage) {
throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR,
'The React Native compatibility library was not found.');
}
/** @protected {string} The storage type identifier. */
this.type = fireauth.storage.Storage.Type.ASYNC_STORAGE;
};


Expand Down
11 changes: 8 additions & 3 deletions packages/auth/src/storage/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ fireauth.storage.Factory.EnvConfig = {
REACT_NATIVE: {
persistent: fireauth.storage.AsyncStorage,
temporary: fireauth.storage.NullStorage
},
WORKER: {
persistent: fireauth.storage.LocalStorage,
temporary: fireauth.storage.NullStorage
}
};

Expand All @@ -95,6 +99,8 @@ fireauth.storage.Factory.getEnvConfig = function() {
fireauth.storage.Factory.EnvConfig.NODE;
envMap[fireauth.util.Env.REACT_NATIVE] =
fireauth.storage.Factory.EnvConfig.REACT_NATIVE;
envMap[fireauth.util.Env.WORKER] =
fireauth.storage.Factory.EnvConfig.WORKER;
return envMap[fireauth.util.getEnvironment()];
};

Expand All @@ -103,9 +109,8 @@ fireauth.storage.Factory.getEnvConfig = function() {
* @return {!fireauth.storage.Storage} The persistent storage instance.
*/
fireauth.storage.Factory.prototype.makePersistentStorage = function() {
if (fireauth.util.isLocalStorageNotSynchronized()) {
// In a browser environment, when an iframe and a popup web storage are not
// synchronized, use the indexedDB fireauth.storage.Storage implementation.
if (fireauth.util.persistsStorageWithIndexedDB()) {
// If persistent storage is implemented using indexedDB, use indexedDB.
return fireauth.storage.IndexedDB.getFireauthManager();
}
return new this.env_.persistent();
Expand Down
2 changes: 2 additions & 0 deletions packages/auth/src/storage/indexeddb.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ fireauth.storage.IndexedDB = function(
/** @private {!IDBFactory} The indexedDB factory object. */
this.indexedDB_ = /** @type {!IDBFactory} */ (
opt_indexedDB || goog.global.indexedDB);
/** @protected {string} The storage type identifier. */
this.type = fireauth.storage.Storage.Type.INDEXEDDB;
};


Expand Down
20 changes: 11 additions & 9 deletions packages/auth/src/storage/inmemorystorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ goog.require('goog.Promise');
* @implements {fireauth.storage.Storage}
*/
fireauth.storage.InMemoryStorage = function() {
/** @private {!Object} The object where we store values. */
this.storage_ = {};
/** @protected {!Object} The object where we store values. */
this.storage = {};
/** @protected {string} The storage type identifier. */
this.type = fireauth.storage.Storage.Type.IN_MEMORY;
};


Expand All @@ -42,7 +44,7 @@ fireauth.storage.InMemoryStorage = function() {
* @override
*/
fireauth.storage.InMemoryStorage.prototype.get = function(key) {
return goog.Promise.resolve(/** @type {*} */ (this.storage_[key]));
return goog.Promise.resolve(/** @type {*} */ (this.storage[key]));
};


Expand All @@ -53,7 +55,7 @@ fireauth.storage.InMemoryStorage.prototype.get = function(key) {
* @override
*/
fireauth.storage.InMemoryStorage.prototype.set = function(key, value) {
this.storage_[key] = value;
this.storage[key] = value;
return goog.Promise.resolve();
};

Expand All @@ -64,14 +66,14 @@ fireauth.storage.InMemoryStorage.prototype.set = function(key, value) {
* @override
*/
fireauth.storage.InMemoryStorage.prototype.remove = function(key) {
delete this.storage_[key];
delete this.storage[key];
return goog.Promise.resolve();
};


/**
* @param {function(!goog.events.BrowserEvent)} listener The storage event
* listener.
* @param {function((!goog.events.BrowserEvent|!Array<string>))} listener The
* storage event listener.
* @override
*/
fireauth.storage.InMemoryStorage.prototype.addStorageListener =
Expand All @@ -80,8 +82,8 @@ fireauth.storage.InMemoryStorage.prototype.addStorageListener =


/**
* @param {function(!goog.events.BrowserEvent)} listener The storage event
* listener.
* @param {function((!goog.events.BrowserEvent|!Array<string>))} listener The
* storage event listener.
* @override
*/
fireauth.storage.InMemoryStorage.prototype.removeStorageListener = function(
Expand Down
Loading

0 comments on commit 1b3ba41

Please sign in to comment.