Skip to content

Commit 1b3ba41

Browse files
authored
Refactor Auth storage test (#530)
* 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
1 parent 2cb9ef5 commit 1b3ba41

39 files changed

+1625
-383
lines changed

packages/auth/demo/public/script.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,15 @@ function refreshUserData() {
153153
$('input.profile-name').val(user.displayName);
154154
$('input.photo-url').val(user.photoURL);
155155
if (user.photoURL != null) {
156-
$('img.profile-image').attr('src', user.photoURL).show();
156+
var photoURL = user.photoURL;
157+
// Append size to the photo URL for Google hosted images to avoid requesting
158+
// the image with its original resolution (using more bandwidth than needed)
159+
// when it is going to be presented in smaller size.
160+
if ((photoURL.indexOf('googleusercontent.com') != -1) ||
161+
(photoURL.indexOf('ggpht.com') != -1)) {
162+
photoURL = photoURL + '?sz=' + $('img.profile-image').height();
163+
}
164+
$('img.profile-image').attr('src', photoURL).show();
157165
} else {
158166
$('img.profile-image').hide();
159167
}

packages/auth/src/authstorage.js

+59-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ goog.provide('fireauth.authStorage.Persistence');
2626
goog.require('fireauth.AuthError');
2727
goog.require('fireauth.authenum.Error');
2828
goog.require('fireauth.storage.Factory');
29+
goog.require('fireauth.storage.Storage');
2930
goog.require('fireauth.util');
3031
goog.require('goog.Promise');
3132
goog.require('goog.array');
@@ -235,6 +236,12 @@ fireauth.authStorage.Manager.getInstance = function() {
235236
};
236237

237238

239+
/** Clears storage manager instances. This is used for testing. */
240+
fireauth.authStorage.Manager.clear = function() {
241+
fireauth.authStorage.Manager.instance_ = null;
242+
};
243+
244+
238245
/**
239246
* Returns the storage corresponding to the specified persistence.
240247
* @param {!fireauth.authStorage.Persistence} persistent The type of storage
@@ -270,6 +277,55 @@ fireauth.authStorage.Manager.prototype.getKeyName_ = function(dataKey, opt_id) {
270277
};
271278

272279

280+
/**
281+
* Migrates window.localStorage to the provided persistent storage.
282+
* @param {fireauth.authStorage.Key} dataKey The key under which the persistent
283+
* value is supposed to be stored.
284+
* @param {?string=} opt_id When operating in multiple app mode, this ID
285+
* associates storage values with specific apps.
286+
* @return {!goog.Promise<void>} A promise that resolves when the data stored
287+
* in window.localStorage is migrated to the provided persistent storage
288+
* identified by the provided data key.
289+
*/
290+
fireauth.authStorage.Manager.prototype.migrateFromLocalStorage =
291+
function(dataKey, opt_id) {
292+
var self = this;
293+
var key = this.getKeyName_(dataKey, opt_id);
294+
var storage = this.getStorage_(dataKey.persistent);
295+
// Get data stored in the default persistent storage identified by dataKey.
296+
return this.get(dataKey, opt_id).then(function(response) {
297+
// Get the stored value in window.localStorage if available.
298+
var oldStorageValue = null;
299+
try {
300+
oldStorageValue = fireauth.util.parseJSON(
301+
goog.global['localStorage']['getItem'](key));
302+
} catch (e) {
303+
// Set value as null. This will resolve the promise immediately.
304+
}
305+
// If data is stored in window.localStorage but no data is available in
306+
// default persistent storage, migrate data from window.localStorage to
307+
// default persistent storage.
308+
if (oldStorageValue && !response) {
309+
// This condition may fail in situations where a user opens a tab with
310+
// an old version while using a tab with a new version, or when a
311+
// developer switches back and forth between and old and new version of
312+
// the library.
313+
goog.global['localStorage']['removeItem'](key);
314+
// Migrate the value to new default persistent storage.
315+
return self.set(dataKey, oldStorageValue, opt_id);
316+
} else if (oldStorageValue &&
317+
response &&
318+
storage.type != fireauth.storage.Storage.Type.LOCAL_STORAGE) {
319+
// Data stored in both localStorage and new persistent storage (eg.
320+
// indexedDB) for some reason.
321+
// This could happen if the developer is migrating back and forth.
322+
// The new default persistent storage (eg. indexedDB) takes precedence.
323+
goog.global['localStorage']['removeItem'](key);
324+
}
325+
});
326+
};
327+
328+
273329
/**
274330
* Gets the stored value from the corresponding storage.
275331
* @param {fireauth.authStorage.Key} dataKey The key under which the value is
@@ -404,9 +460,9 @@ fireauth.authStorage.Manager.prototype.startListeners_ = function() {
404460
// TODO: refactor this implementation to be handled by the underlying
405461
// storage mechanism.
406462
if (!this.runsInBackground_ &&
407-
// Add an exception for IE11 and Edge browsers, we should stick to
408-
// indexedDB in that case.
409-
!fireauth.util.isLocalStorageNotSynchronized() &&
463+
// Add an exception for browsers that persist storage with indexedDB, we
464+
// should stick with indexedDB listener implementation in that case.
465+
!fireauth.util.persistsStorageWithIndexedDB() &&
410466
// Confirm browser web storage is supported as polling relies on it.
411467
this.webStorageSupported_) {
412468
this.startManualListeners_();

packages/auth/src/error_auth.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,9 @@ fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_APP_CREDENTIAL] =
240240
fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_APP_ID] =
241241
'The mobile app identifier is not registed for the current project.';
242242
fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_AUTH] =
243-
'The user\'s credential is no longer valid. The user must sign in again.';
243+
'This user\'s credential isn\'t valid for this project. This can happen ' +
244+
'if the user\'s token has been tampered with, or if the user isn\'t for ' +
245+
'the project associated with this API key.';
244246
fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_AUTH_EVENT] =
245247
'An internal error has occurred.';
246248
fireauth.AuthError.MESSAGES_[fireauth.authenum.Error.INVALID_CODE] =

packages/auth/src/exports_auth.js

+3
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,9 @@ fireauth.exportlib.exportPrototypeMethods(
320320

321321
fireauth.exportlib.exportPrototypeMethods(
322322
goog.Promise.prototype, {
323+
thenAlways: {
324+
name: 'finally'
325+
},
323326
thenCatch: {
324327
name: 'catch'
325328
},

packages/auth/src/iframeclient/iframewrapper.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,12 @@ fireauth.iframeclient.IframeWrapper.prototype.registerEvent =
174174
this.onIframeOpen_.then(function() {
175175
self.iframe_.register(
176176
eventName,
177-
handler,
177+
/** @type {function(this:gapi.iframes.Iframe,
178+
* *, gapi.iframes.Iframe): *}
179+
*/ (handler),
178180
/** @type {!gapi.iframes.IframesFilter} */ (
179-
fireauth.util.getObjectRef(
180-
'gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER')));
181+
fireauth.util.getObjectRef(
182+
'gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER')));
181183
});
182184
};
183185

@@ -191,12 +193,15 @@ fireauth.iframeclient.IframeWrapper.prototype.unregisterEvent =
191193
function(eventName, handler) {
192194
var self = this;
193195
this.onIframeOpen_.then(function() {
194-
self.iframe_.unregister(eventName, handler);
196+
self.iframe_.unregister(
197+
eventName,
198+
/** @type {(function(this:gapi.iframes.Iframe,
199+
* *, gapi.iframes.Iframe): *|undefined)}
200+
*/ (handler));
195201
});
196202
};
197203

198204

199-
200205
/** @private @const {!goog.string.Const} The GApi loader URL. */
201206
fireauth.iframeclient.IframeWrapper.GAPI_LOADER_SRC_ = goog.string.Const.from(
202207
'https://apis.google.com/js/api.js?onload=%{onload}');

packages/auth/src/recaptchaverifier/recaptchaverifier.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ fireauth.BaseRecaptchaVerifier = function(apiKey, container, opt_parameters,
9494
this.isInvisible_ =
9595
this.parameters_[fireauth.BaseRecaptchaVerifier.ParamName.SIZE] ===
9696
'invisible';
97+
// Check if DOM is supported.
98+
if (!fireauth.util.isDOMSupported()) {
99+
throw new fireauth.AuthError(
100+
fireauth.authenum.Error.OPERATION_NOT_SUPPORTED,
101+
'RecaptchaVerifier is only supported in a browser HTTP/HTTPS ' +
102+
'environment with DOM support.');
103+
}
97104
// reCAPTCHA container must be valid and if visible, not empty.
98105
// An invisible reCAPTCHA will not render in its container. That container
99106
// will execute the reCAPTCHA when it is clicked.
@@ -257,9 +264,8 @@ fireauth.BaseRecaptchaVerifier.prototype.isReady_ = function() {
257264
this.cachedReadyPromise_ = this.registerPendingPromise_(goog.Promise.resolve()
258265
.then(function() {
259266
// Verify environment first.
260-
// This is actually not enough as this could be triggered from a worker
261-
// environment, but DOM ready should theoretically not resolve.
262-
if (fireauth.util.isHttpOrHttps()) {
267+
// Fail quickly from a worker environment or non-HTTP/HTTPS environment.
268+
if (fireauth.util.isHttpOrHttps() && !fireauth.util.isWorker()) {
263269
// Wait for DOM to be ready as this feature depends on that.
264270
return fireauth.util.onDomReady();
265271
} else {

packages/auth/src/rpchandler.js

+43-19
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ goog.require('goog.html.TrustedResourceUrl');
3535
goog.require('goog.json');
3636
goog.require('goog.net.CorsXmlHttpFactory');
3737
goog.require('goog.net.EventType');
38+
goog.require('goog.net.FetchXmlHttpFactory');
3839
goog.require('goog.net.XhrIo');
3940
goog.require('goog.net.XmlHttpFactory');
4041
goog.require('goog.net.jsloader');
@@ -93,13 +94,6 @@ fireauth.XmlHttpFactory.prototype.internalGetOptions = function() {
9394
* @constructor
9495
*/
9596
fireauth.RpcHandler = function(apiKey, opt_config, opt_firebaseClientVersion) {
96-
// Get XMLHttpRequest reference.
97-
var XMLHttpRequest = fireauth.util.getXMLHttpRequest();
98-
if (!XMLHttpRequest) {
99-
// In a Node.js environment, xmlhttprequest module needs to be required.
100-
throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR,
101-
'The XMLHttpRequest compatibility library was not found.');
102-
}
10397
this.apiKey_ = apiKey;
10498
var config = opt_config || {};
10599
this.secureTokenEndpoint_ = config['secureTokenEndpoint'] ||
@@ -131,10 +125,46 @@ fireauth.RpcHandler = function(apiKey, opt_config, opt_firebaseClientVersion) {
131125
// Log client version for securetoken server.
132126
this.secureTokenHeaders_['X-Client-Version'] = opt_firebaseClientVersion;
133127
}
134-
/** @const @private {!goog.net.CorsXmlHttpFactory} The CORS XHR factory. */
135-
this.corsXhrFactory_ = new goog.net.CorsXmlHttpFactory();
136-
/** @const @private {!goog.net.XmlHttpFactory} The XHR factory. */
137-
this.xhrFactory_ = new fireauth.XmlHttpFactory(XMLHttpRequest);
128+
129+
// Get XMLHttpRequest reference.
130+
var XMLHttpRequest = fireauth.RpcHandler.getXMLHttpRequest();
131+
if (!XMLHttpRequest && !fireauth.util.isWorker()) {
132+
// In a Node.js environment, xmlhttprequest module needs to be required.
133+
throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR,
134+
'The XMLHttpRequest compatibility library was not found.');
135+
}
136+
/** @private {!goog.net.XmlHttpFactory|undefined} The XHR factory. */
137+
this.rpcHandlerXhrFactory_ = undefined;
138+
// Initialize XHR factory. CORS does not apply in native environments or
139+
// workers so don't use CorsXmlHttpFactory in those cases.
140+
if (fireauth.util.isWorker()) {
141+
// For worker environment use FetchXmlHttpFactory.
142+
this.rpcHandlerXhrFactory_ = new goog.net.FetchXmlHttpFactory(
143+
/** @type {!WorkerGlobalScope} */ (self));
144+
} else if (fireauth.util.isNativeEnvironment()) {
145+
// For Node.js, this is the polyfill library. For other environments,
146+
// this is the native global XMLHttpRequest.
147+
this.rpcHandlerXhrFactory_ = new fireauth.XmlHttpFactory(
148+
/** @type {function(new:XMLHttpRequest)} */ (XMLHttpRequest));
149+
} else {
150+
// CORS Browser environment.
151+
this.rpcHandlerXhrFactory_ = new goog.net.CorsXmlHttpFactory();
152+
}
153+
};
154+
155+
156+
/**
157+
* @return {?function(new:XMLHttpRequest)|undefined} The current environment
158+
* XMLHttpRequest. This is undefined for worker environment.
159+
*/
160+
fireauth.RpcHandler.getXMLHttpRequest = function() {
161+
// In Node.js XMLHttpRequest is polyfilled.
162+
var isNode = fireauth.util.getEnvironment() == fireauth.util.Env.NODE;
163+
var XMLHttpRequest = goog.global['XMLHttpRequest'] ||
164+
(isNode &&
165+
firebase.INTERNAL['node'] &&
166+
firebase.INTERNAL['node']['XMLHttpRequest']);
167+
return XMLHttpRequest;
138168
};
139169

140170

@@ -394,7 +424,7 @@ fireauth.RpcHandler.prototype.sendXhr_ = function(
394424
return;
395425
}
396426
var sendXhr;
397-
if (fireauth.util.supportsCors()) {
427+
if (fireauth.util.supportsCors() || fireauth.util.isWorker()) {
398428
// If supports CORS use goog.net.XhrIo.
399429
sendXhr = goog.bind(this.sendXhrUsingXhrIo_, this);
400430
} else {
@@ -432,13 +462,7 @@ fireauth.RpcHandler.prototype.sendXhrUsingXhrIo_ = function(
432462
opt_data,
433463
opt_headers,
434464
opt_timeout) {
435-
// Send XHR request. CORS does not apply in native environments so don't use
436-
// CorsXmlHttpFactory in those cases.
437-
// For a Node.js environment use the fireauth.XmlHttpFactory instance.
438-
var isNode = fireauth.util.getEnvironment() == fireauth.util.Env.NODE;
439-
var xhrIo = fireauth.util.isNativeEnvironment() ?
440-
(isNode ? new goog.net.XhrIo(this.xhrFactory_) : new goog.net.XhrIo()) :
441-
new goog.net.XhrIo(this.corsXhrFactory_);
465+
var xhrIo = new goog.net.XhrIo(this.rpcHandlerXhrFactory_);
442466

443467
// xhrIo.setTimeoutInterval not working in IE10 and IE11, handle manually.
444468
var requestTimeout;

packages/auth/src/storage/asyncstorage.js

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ fireauth.storage.AsyncStorage = function(opt_asyncStorage) {
4545
throw new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR,
4646
'The React Native compatibility library was not found.');
4747
}
48+
/** @protected {string} The storage type identifier. */
49+
this.type = fireauth.storage.Storage.Type.ASYNC_STORAGE;
4850
};
4951

5052

packages/auth/src/storage/factory.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ fireauth.storage.Factory.EnvConfig = {
7878
REACT_NATIVE: {
7979
persistent: fireauth.storage.AsyncStorage,
8080
temporary: fireauth.storage.NullStorage
81+
},
82+
WORKER: {
83+
persistent: fireauth.storage.LocalStorage,
84+
temporary: fireauth.storage.NullStorage
8185
}
8286
};
8387

@@ -95,6 +99,8 @@ fireauth.storage.Factory.getEnvConfig = function() {
9599
fireauth.storage.Factory.EnvConfig.NODE;
96100
envMap[fireauth.util.Env.REACT_NATIVE] =
97101
fireauth.storage.Factory.EnvConfig.REACT_NATIVE;
102+
envMap[fireauth.util.Env.WORKER] =
103+
fireauth.storage.Factory.EnvConfig.WORKER;
98104
return envMap[fireauth.util.getEnvironment()];
99105
};
100106

@@ -103,9 +109,8 @@ fireauth.storage.Factory.getEnvConfig = function() {
103109
* @return {!fireauth.storage.Storage} The persistent storage instance.
104110
*/
105111
fireauth.storage.Factory.prototype.makePersistentStorage = function() {
106-
if (fireauth.util.isLocalStorageNotSynchronized()) {
107-
// In a browser environment, when an iframe and a popup web storage are not
108-
// synchronized, use the indexedDB fireauth.storage.Storage implementation.
112+
if (fireauth.util.persistsStorageWithIndexedDB()) {
113+
// If persistent storage is implemented using indexedDB, use indexedDB.
109114
return fireauth.storage.IndexedDB.getFireauthManager();
110115
}
111116
return new this.env_.persistent();

packages/auth/src/storage/indexeddb.js

+2
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ fireauth.storage.IndexedDB = function(
9494
/** @private {!IDBFactory} The indexedDB factory object. */
9595
this.indexedDB_ = /** @type {!IDBFactory} */ (
9696
opt_indexedDB || goog.global.indexedDB);
97+
/** @protected {string} The storage type identifier. */
98+
this.type = fireauth.storage.Storage.Type.INDEXEDDB;
9799
};
98100

99101

packages/auth/src/storage/inmemorystorage.js

+11-9
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ goog.require('goog.Promise');
3131
* @implements {fireauth.storage.Storage}
3232
*/
3333
fireauth.storage.InMemoryStorage = function() {
34-
/** @private {!Object} The object where we store values. */
35-
this.storage_ = {};
34+
/** @protected {!Object} The object where we store values. */
35+
this.storage = {};
36+
/** @protected {string} The storage type identifier. */
37+
this.type = fireauth.storage.Storage.Type.IN_MEMORY;
3638
};
3739

3840

@@ -42,7 +44,7 @@ fireauth.storage.InMemoryStorage = function() {
4244
* @override
4345
*/
4446
fireauth.storage.InMemoryStorage.prototype.get = function(key) {
45-
return goog.Promise.resolve(/** @type {*} */ (this.storage_[key]));
47+
return goog.Promise.resolve(/** @type {*} */ (this.storage[key]));
4648
};
4749

4850

@@ -53,7 +55,7 @@ fireauth.storage.InMemoryStorage.prototype.get = function(key) {
5355
* @override
5456
*/
5557
fireauth.storage.InMemoryStorage.prototype.set = function(key, value) {
56-
this.storage_[key] = value;
58+
this.storage[key] = value;
5759
return goog.Promise.resolve();
5860
};
5961

@@ -64,14 +66,14 @@ fireauth.storage.InMemoryStorage.prototype.set = function(key, value) {
6466
* @override
6567
*/
6668
fireauth.storage.InMemoryStorage.prototype.remove = function(key) {
67-
delete this.storage_[key];
69+
delete this.storage[key];
6870
return goog.Promise.resolve();
6971
};
7072

7173

7274
/**
73-
* @param {function(!goog.events.BrowserEvent)} listener The storage event
74-
* listener.
75+
* @param {function((!goog.events.BrowserEvent|!Array<string>))} listener The
76+
* storage event listener.
7577
* @override
7678
*/
7779
fireauth.storage.InMemoryStorage.prototype.addStorageListener =
@@ -80,8 +82,8 @@ fireauth.storage.InMemoryStorage.prototype.addStorageListener =
8082

8183

8284
/**
83-
* @param {function(!goog.events.BrowserEvent)} listener The storage event
84-
* listener.
85+
* @param {function((!goog.events.BrowserEvent|!Array<string>))} listener The
86+
* storage event listener.
8587
* @override
8688
*/
8789
fireauth.storage.InMemoryStorage.prototype.removeStorageListener = function(

0 commit comments

Comments
 (0)