Skip to content

Refactor Auth storage test #530

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -299,6 +299,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 @@ -392,7 +422,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 @@ -430,13 +460,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