Skip to content

fix(auth): Cordova OAuth fix for Android with multiple App instances #478

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 1 commit into from
Feb 1, 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
13 changes: 2 additions & 11 deletions packages/auth/src/cordovahandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ goog.require('fireauth.AuthEvent');
goog.require('fireauth.AuthProvider');
goog.require('fireauth.DynamicLink');
goog.require('fireauth.OAuthSignInHandler');
goog.require('fireauth.UniversalLinkSubscriber');
goog.require('fireauth.authenum.Error');
goog.require('fireauth.iframeclient.IfcHandler');
goog.require('fireauth.storage.AuthEventManager');
Expand Down Expand Up @@ -724,16 +725,6 @@ fireauth.CordovaHandler.prototype.extractAuthEventFromUrl_ =
fireauth.CordovaHandler.prototype.setAuthEventListener_ = function() {
// https://github.com/nordnet/cordova-universal-links-plugin
var self = this;
// Get universal link subscriber.
var subscribe = fireauth.util.getObjectRef(
'universalLinks.subscribe', goog.global);
// Should not occur as initializeAndWait will ensure that.
if (typeof subscribe !== 'function') {
// Universal link plugin not installed.
throw new fireauth.AuthError(
fireauth.authenum.Error.INVALID_CORDOVA_CONFIGURATION);
}
// Universal link plugin installed.
// Default no redirect event result.
var noEvent = new fireauth.AuthEvent(
fireauth.AuthEvent.Type.UNKNOWN,
Expand Down Expand Up @@ -805,7 +796,7 @@ fireauth.CordovaHandler.prototype.setAuthEventListener_ = function() {
}
}
};
subscribe(null, universalLinkCb);
fireauth.UniversalLinkSubscriber.getInstance().subscribe(universalLinkCb);
};


Expand Down
108 changes: 108 additions & 0 deletions packages/auth/src/universallinksubscriber.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @fileoverview Provides the universal link subscriber utility to allow
* multiple subscriptions for incoming universal link detection.
*/
goog.provide('fireauth.UniversalLinkSubscriber');

goog.require('fireauth.util');
goog.require('goog.array');

/**
* Defines the universal link subscriber class used to allow multiple universal
* link subscriptions since the underlying plugin only works with one.
* This utility is needed since the universal link cordova plugin can only allow
* one subscriber and multiple app instances can subscribe to this.
* @constructor @final @struct
*/
fireauth.UniversalLinkSubscriber = function() {
/**
* @private {?function(?Object)} The master callback that subscribes directly
* to universalLinks.
*/
this.masterCb_ = null;
/**
* @private {!Array<function(?Object)>} The list of external subscribers that
* are triggered every time the master callback is triggered.
*/
this.cb_ = [];
};


/**
* @return {!fireauth.UniversalLinkSubscriber} The default universal link
* subscriber instance.
*/
fireauth.UniversalLinkSubscriber.getInstance = function() {
if (!fireauth.UniversalLinkSubscriber.instance_) {
fireauth.UniversalLinkSubscriber.instance_ =
new fireauth.UniversalLinkSubscriber();
}
return fireauth.UniversalLinkSubscriber.instance_;
};


/** Clears singleton instance. Useful for testing. */
fireauth.UniversalLinkSubscriber.clear = function() {
fireauth.UniversalLinkSubscriber.instance_ = null;
};


/**
* @private {?fireauth.UniversalLinkSubscriber} The singleton universal
* link subscriber instance.
*/
fireauth.UniversalLinkSubscriber.instance_ = null;


/**
* Subscribes a callback to the universal link plugin listener.
* @param {function(?Object)} cb The callback to subscribe to the universal
* link plugin.
*/
fireauth.UniversalLinkSubscriber.prototype.subscribe = function(cb) {
var self = this;
this.cb_.push(cb);
if (!this.masterCb_) {
this.masterCb_ = function(event) {
for (var i = 0; i < self.cb_.length; i++) {
self.cb_[i](event);
}
};
var subscribe = fireauth.util.getObjectRef(
'universalLinks.subscribe', goog.global);
// For iOS environments, this plugin is not used, therefore this is a no-op
// and no error needs to be thrown.
if (typeof subscribe === 'function') {
subscribe(null, this.masterCb_);
}
}
};


/**
* Unsubscribes a callback from the universal link plugin listener.
* @param {function(?Object)} cb The callback to unsubscribe from the universal
* link plugin.
*/
fireauth.UniversalLinkSubscriber.prototype.unsubscribe = function(cb) {
goog.array.removeAllIf(this.cb_, function(ele) {
return ele == cb;
});
};

2 changes: 2 additions & 0 deletions packages/auth/test/autheventmanager_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ goog.require('fireauth.InvalidOriginError');
goog.require('fireauth.PopupAuthEventProcessor');
goog.require('fireauth.RedirectAuthEventProcessor');
goog.require('fireauth.RpcHandler');
goog.require('fireauth.UniversalLinkSubscriber');
goog.require('fireauth.authenum.Error');
goog.require('fireauth.constants');
goog.require('fireauth.iframeclient.IfcHandler');
Expand Down Expand Up @@ -108,6 +109,7 @@ function tearDown() {
} finally {
mockControl.$tearDown();
}
fireauth.UniversalLinkSubscriber.clear();
}


Expand Down
2 changes: 2 additions & 0 deletions packages/auth/test/cordovahandler_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ goog.require('fireauth.AuthEvent');
goog.require('fireauth.CordovaHandler');
goog.require('fireauth.EmailAuthProvider');
goog.require('fireauth.GoogleAuthProvider');
goog.require('fireauth.UniversalLinkSubscriber');
goog.require('fireauth.authenum.Error');
goog.require('fireauth.constants');
goog.require('fireauth.iframeclient.IfcHandler');
Expand Down Expand Up @@ -183,6 +184,7 @@ function tearDown() {
if (goog.global['handleOpenURL']) {
delete goog.global['handleOpenURL'];
}
fireauth.UniversalLinkSubscriber.clear();
}


Expand Down
113 changes: 113 additions & 0 deletions packages/auth/test/universallinksubscriber_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @fileoverview Tests for universallinksubscriber.js
*/

goog.provide('fireauth.UniversalLinkSubscriberTest');

goog.require('fireauth.UniversalLinkSubscriber');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.recordFunction');

goog.setTestOnly('fireauth.UniversalLinkSubscriberTest');


var universalLinks = null;
var eventData1 = {url: 'URL1'};
var eventData2 = {url: 'URLK2'};
var eventData3 = {url: 'URLK2'};


function setUp() {
// Initialize mock universalLinks plugin.
universalLinks = {};
universalLinks.subscribe = function(name, cb) {
// Only one subscriber allowed.
assertUndefined(universalLinks.cb);
// No name should be supplied.
assertNull(name);
// Save callback.
universalLinks.cb = cb;
};

}


function testUniversalLinkSubscriber() {
var listener1 = goog.testing.recordFunction();
var listener2 = goog.testing.recordFunction();
var universalLinkSubscriber = new fireauth.UniversalLinkSubscriber();
universalLinkSubscriber.subscribe(listener1);
universalLinkSubscriber.subscribe(listener2);
// Trigger first event.
universalLinks.cb(eventData1);
// Both listeners triggered.
assertEquals(1, listener1.getCallCount());
assertEquals(eventData1, listener1.getLastCall().getArgument(0));
assertEquals(1, listener2.getCallCount());
assertEquals(eventData1, listener2.getLastCall().getArgument(0));

// Remove listener 2.
universalLinkSubscriber.unsubscribe(listener2);
// Trigger second event.
universalLinks.cb(eventData2);
// Only first listener triggered.
assertEquals(2, listener1.getCallCount());
assertEquals(eventData2, listener1.getLastCall().getArgument(0));
assertEquals(1, listener2.getCallCount());

// Remove remaining listener.
universalLinkSubscriber.unsubscribe(listener1);
// Trigger third event.
universalLinks.cb(eventData3);
// No listener triggered
assertEquals(2, listener1.getCallCount());
assertEquals(1, listener2.getCallCount());

// Re-subscribe first listener.
universalLinkSubscriber.subscribe(listener1);
// Trigger first event again.
universalLinks.cb(eventData1);
// Only first listener triggered.
assertEquals(3, listener1.getCallCount());
assertEquals(eventData1, listener1.getLastCall().getArgument(0));
assertEquals(1, listener2.getCallCount());
}


function testUniversalLinkSubscriber_unavailable() {
// Test when universal link plugin not available, no error is thrown.
universalLinks = null;
var listener1 = goog.testing.recordFunction();
var universalLinkSubscriber = new fireauth.UniversalLinkSubscriber();
assertNotThrows(function() {
universalLinkSubscriber.subscribe(listener1);
universalLinkSubscriber.unsubscribe(listener1);
});
}


function testUniversalLinkSubscriber_clear() {
var instance = fireauth.UniversalLinkSubscriber.getInstance();
// Same instance should be returned.
assertEquals(instance, fireauth.UniversalLinkSubscriber.getInstance());
fireauth.UniversalLinkSubscriber.clear();
// After clearing, new instance should be returned.
assertNotEquals(instance, fireauth.UniversalLinkSubscriber.getInstance());
}