Skip to content

Commit bcc8983

Browse files
fix(auth): Fixes broken universallinks.subscribe in Android when multiple listeners are set. (#478)
1 parent fcc77c2 commit bcc8983

File tree

5 files changed

+227
-11
lines changed

5 files changed

+227
-11
lines changed

packages/auth/src/cordovahandler.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ goog.require('fireauth.AuthEvent');
3535
goog.require('fireauth.AuthProvider');
3636
goog.require('fireauth.DynamicLink');
3737
goog.require('fireauth.OAuthSignInHandler');
38+
goog.require('fireauth.UniversalLinkSubscriber');
3839
goog.require('fireauth.authenum.Error');
3940
goog.require('fireauth.iframeclient.IfcHandler');
4041
goog.require('fireauth.storage.AuthEventManager');
@@ -724,16 +725,6 @@ fireauth.CordovaHandler.prototype.extractAuthEventFromUrl_ =
724725
fireauth.CordovaHandler.prototype.setAuthEventListener_ = function() {
725726
// https://github.com/nordnet/cordova-universal-links-plugin
726727
var self = this;
727-
// Get universal link subscriber.
728-
var subscribe = fireauth.util.getObjectRef(
729-
'universalLinks.subscribe', goog.global);
730-
// Should not occur as initializeAndWait will ensure that.
731-
if (typeof subscribe !== 'function') {
732-
// Universal link plugin not installed.
733-
throw new fireauth.AuthError(
734-
fireauth.authenum.Error.INVALID_CORDOVA_CONFIGURATION);
735-
}
736-
// Universal link plugin installed.
737728
// Default no redirect event result.
738729
var noEvent = new fireauth.AuthEvent(
739730
fireauth.AuthEvent.Type.UNKNOWN,
@@ -805,7 +796,7 @@ fireauth.CordovaHandler.prototype.setAuthEventListener_ = function() {
805796
}
806797
}
807798
};
808-
subscribe(null, universalLinkCb);
799+
fireauth.UniversalLinkSubscriber.getInstance().subscribe(universalLinkCb);
809800
};
810801

811802

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
* Copyright 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* @fileoverview Provides the universal link subscriber utility to allow
19+
* multiple subscriptions for incoming universal link detection.
20+
*/
21+
goog.provide('fireauth.UniversalLinkSubscriber');
22+
23+
goog.require('fireauth.util');
24+
goog.require('goog.array');
25+
26+
/**
27+
* Defines the universal link subscriber class used to allow multiple universal
28+
* link subscriptions since the underlying plugin only works with one.
29+
* This utility is needed since the universal link cordova plugin can only allow
30+
* one subscriber and multiple app instances can subscribe to this.
31+
* @constructor @final @struct
32+
*/
33+
fireauth.UniversalLinkSubscriber = function() {
34+
/**
35+
* @private {?function(?Object)} The master callback that subscribes directly
36+
* to universalLinks.
37+
*/
38+
this.masterCb_ = null;
39+
/**
40+
* @private {!Array<function(?Object)>} The list of external subscribers that
41+
* are triggered every time the master callback is triggered.
42+
*/
43+
this.cb_ = [];
44+
};
45+
46+
47+
/**
48+
* @return {!fireauth.UniversalLinkSubscriber} The default universal link
49+
* subscriber instance.
50+
*/
51+
fireauth.UniversalLinkSubscriber.getInstance = function() {
52+
if (!fireauth.UniversalLinkSubscriber.instance_) {
53+
fireauth.UniversalLinkSubscriber.instance_ =
54+
new fireauth.UniversalLinkSubscriber();
55+
}
56+
return fireauth.UniversalLinkSubscriber.instance_;
57+
};
58+
59+
60+
/** Clears singleton instance. Useful for testing. */
61+
fireauth.UniversalLinkSubscriber.clear = function() {
62+
fireauth.UniversalLinkSubscriber.instance_ = null;
63+
};
64+
65+
66+
/**
67+
* @private {?fireauth.UniversalLinkSubscriber} The singleton universal
68+
* link subscriber instance.
69+
*/
70+
fireauth.UniversalLinkSubscriber.instance_ = null;
71+
72+
73+
/**
74+
* Subscribes a callback to the universal link plugin listener.
75+
* @param {function(?Object)} cb The callback to subscribe to the universal
76+
* link plugin.
77+
*/
78+
fireauth.UniversalLinkSubscriber.prototype.subscribe = function(cb) {
79+
var self = this;
80+
this.cb_.push(cb);
81+
if (!this.masterCb_) {
82+
this.masterCb_ = function(event) {
83+
for (var i = 0; i < self.cb_.length; i++) {
84+
self.cb_[i](event);
85+
}
86+
};
87+
var subscribe = fireauth.util.getObjectRef(
88+
'universalLinks.subscribe', goog.global);
89+
// For iOS environments, this plugin is not used, therefore this is a no-op
90+
// and no error needs to be thrown.
91+
if (typeof subscribe === 'function') {
92+
subscribe(null, this.masterCb_);
93+
}
94+
}
95+
};
96+
97+
98+
/**
99+
* Unsubscribes a callback from the universal link plugin listener.
100+
* @param {function(?Object)} cb The callback to unsubscribe from the universal
101+
* link plugin.
102+
*/
103+
fireauth.UniversalLinkSubscriber.prototype.unsubscribe = function(cb) {
104+
goog.array.removeAllIf(this.cb_, function(ele) {
105+
return ele == cb;
106+
});
107+
};
108+

packages/auth/test/autheventmanager_test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ goog.require('fireauth.InvalidOriginError');
3030
goog.require('fireauth.PopupAuthEventProcessor');
3131
goog.require('fireauth.RedirectAuthEventProcessor');
3232
goog.require('fireauth.RpcHandler');
33+
goog.require('fireauth.UniversalLinkSubscriber');
3334
goog.require('fireauth.authenum.Error');
3435
goog.require('fireauth.constants');
3536
goog.require('fireauth.iframeclient.IfcHandler');
@@ -108,6 +109,7 @@ function tearDown() {
108109
} finally {
109110
mockControl.$tearDown();
110111
}
112+
fireauth.UniversalLinkSubscriber.clear();
111113
}
112114

113115

packages/auth/test/cordovahandler_test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ goog.require('fireauth.AuthEvent');
2525
goog.require('fireauth.CordovaHandler');
2626
goog.require('fireauth.EmailAuthProvider');
2727
goog.require('fireauth.GoogleAuthProvider');
28+
goog.require('fireauth.UniversalLinkSubscriber');
2829
goog.require('fireauth.authenum.Error');
2930
goog.require('fireauth.constants');
3031
goog.require('fireauth.iframeclient.IfcHandler');
@@ -183,6 +184,7 @@ function tearDown() {
183184
if (goog.global['handleOpenURL']) {
184185
delete goog.global['handleOpenURL'];
185186
}
187+
fireauth.UniversalLinkSubscriber.clear();
186188
}
187189

188190

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* Copyright 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* @fileoverview Tests for universallinksubscriber.js
19+
*/
20+
21+
goog.provide('fireauth.UniversalLinkSubscriberTest');
22+
23+
goog.require('fireauth.UniversalLinkSubscriber');
24+
goog.require('goog.testing.jsunit');
25+
goog.require('goog.testing.recordFunction');
26+
27+
goog.setTestOnly('fireauth.UniversalLinkSubscriberTest');
28+
29+
30+
var universalLinks = null;
31+
var eventData1 = {url: 'URL1'};
32+
var eventData2 = {url: 'URLK2'};
33+
var eventData3 = {url: 'URLK2'};
34+
35+
36+
function setUp() {
37+
// Initialize mock universalLinks plugin.
38+
universalLinks = {};
39+
universalLinks.subscribe = function(name, cb) {
40+
// Only one subscriber allowed.
41+
assertUndefined(universalLinks.cb);
42+
// No name should be supplied.
43+
assertNull(name);
44+
// Save callback.
45+
universalLinks.cb = cb;
46+
};
47+
48+
}
49+
50+
51+
function testUniversalLinkSubscriber() {
52+
var listener1 = goog.testing.recordFunction();
53+
var listener2 = goog.testing.recordFunction();
54+
var universalLinkSubscriber = new fireauth.UniversalLinkSubscriber();
55+
universalLinkSubscriber.subscribe(listener1);
56+
universalLinkSubscriber.subscribe(listener2);
57+
// Trigger first event.
58+
universalLinks.cb(eventData1);
59+
// Both listeners triggered.
60+
assertEquals(1, listener1.getCallCount());
61+
assertEquals(eventData1, listener1.getLastCall().getArgument(0));
62+
assertEquals(1, listener2.getCallCount());
63+
assertEquals(eventData1, listener2.getLastCall().getArgument(0));
64+
65+
// Remove listener 2.
66+
universalLinkSubscriber.unsubscribe(listener2);
67+
// Trigger second event.
68+
universalLinks.cb(eventData2);
69+
// Only first listener triggered.
70+
assertEquals(2, listener1.getCallCount());
71+
assertEquals(eventData2, listener1.getLastCall().getArgument(0));
72+
assertEquals(1, listener2.getCallCount());
73+
74+
// Remove remaining listener.
75+
universalLinkSubscriber.unsubscribe(listener1);
76+
// Trigger third event.
77+
universalLinks.cb(eventData3);
78+
// No listener triggered
79+
assertEquals(2, listener1.getCallCount());
80+
assertEquals(1, listener2.getCallCount());
81+
82+
// Re-subscribe first listener.
83+
universalLinkSubscriber.subscribe(listener1);
84+
// Trigger first event again.
85+
universalLinks.cb(eventData1);
86+
// Only first listener triggered.
87+
assertEquals(3, listener1.getCallCount());
88+
assertEquals(eventData1, listener1.getLastCall().getArgument(0));
89+
assertEquals(1, listener2.getCallCount());
90+
}
91+
92+
93+
function testUniversalLinkSubscriber_unavailable() {
94+
// Test when universal link plugin not available, no error is thrown.
95+
universalLinks = null;
96+
var listener1 = goog.testing.recordFunction();
97+
var universalLinkSubscriber = new fireauth.UniversalLinkSubscriber();
98+
assertNotThrows(function() {
99+
universalLinkSubscriber.subscribe(listener1);
100+
universalLinkSubscriber.unsubscribe(listener1);
101+
});
102+
}
103+
104+
105+
function testUniversalLinkSubscriber_clear() {
106+
var instance = fireauth.UniversalLinkSubscriber.getInstance();
107+
// Same instance should be returned.
108+
assertEquals(instance, fireauth.UniversalLinkSubscriber.getInstance());
109+
fireauth.UniversalLinkSubscriber.clear();
110+
// After clearing, new instance should be returned.
111+
assertNotEquals(instance, fireauth.UniversalLinkSubscriber.getInstance());
112+
}
113+

0 commit comments

Comments
 (0)