diff --git a/packages/auth/src/cordovahandler.js b/packages/auth/src/cordovahandler.js index 31ba90c1e30..5411ec0d4be 100644 --- a/packages/auth/src/cordovahandler.js +++ b/packages/auth/src/cordovahandler.js @@ -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'); @@ -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, @@ -805,7 +796,7 @@ fireauth.CordovaHandler.prototype.setAuthEventListener_ = function() { } } }; - subscribe(null, universalLinkCb); + fireauth.UniversalLinkSubscriber.getInstance().subscribe(universalLinkCb); }; diff --git a/packages/auth/src/universallinksubscriber.js b/packages/auth/src/universallinksubscriber.js new file mode 100644 index 00000000000..f2f7a0ca220 --- /dev/null +++ b/packages/auth/src/universallinksubscriber.js @@ -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} 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; + }); +}; + diff --git a/packages/auth/test/autheventmanager_test.js b/packages/auth/test/autheventmanager_test.js index d4f1b5e2243..d68299608d0 100644 --- a/packages/auth/test/autheventmanager_test.js +++ b/packages/auth/test/autheventmanager_test.js @@ -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'); @@ -108,6 +109,7 @@ function tearDown() { } finally { mockControl.$tearDown(); } + fireauth.UniversalLinkSubscriber.clear(); } diff --git a/packages/auth/test/cordovahandler_test.js b/packages/auth/test/cordovahandler_test.js index 6ebc1515347..a2b17f0c79c 100644 --- a/packages/auth/test/cordovahandler_test.js +++ b/packages/auth/test/cordovahandler_test.js @@ -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'); @@ -183,6 +184,7 @@ function tearDown() { if (goog.global['handleOpenURL']) { delete goog.global['handleOpenURL']; } + fireauth.UniversalLinkSubscriber.clear(); } diff --git a/packages/auth/test/universallinksubscriber_test.js b/packages/auth/test/universallinksubscriber_test.js new file mode 100644 index 00000000000..5c908ace5a2 --- /dev/null +++ b/packages/auth/test/universallinksubscriber_test.js @@ -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()); +} +