Skip to content

Commit 96bcb98

Browse files
authored
Add the outward-bound part of the Cordova redirect flow to auth@exp (#4413)
* Further Cordova redirect support * Fix * Add tests for new cordova stuff * Formatting * Formatting * PR feedback * Formatting
1 parent 3da2039 commit 96bcb98

17 files changed

+837
-369
lines changed

packages-exp/auth-exp/index.cordova.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ export { indexedDBLocalPersistence } from './src/platform_browser/persistence/in
3838
export { browserLocalPersistence } from './src/platform_browser/persistence/local_storage';
3939
export { browserSessionPersistence } from './src/platform_browser/persistence/session_storage';
4040

41-
export { cordovaPopupRedirectResolver } from './src/platform_cordova/popup_redirect';
41+
export { cordovaPopupRedirectResolver } from './src/platform_cordova/popup_redirect/popup_redirect';
4242
export { signInWithRedirect } from './src/platform_cordova/strategies/redirect';
4343

44-
import { cordovaPopupRedirectResolver } from './src/platform_cordova/popup_redirect';
44+
import { cordovaPopupRedirectResolver } from './src/platform_cordova/popup_redirect/popup_redirect';
4545

4646
export function getAuth(app: FirebaseApp): Auth {
4747
return initializeAuth(app, {

packages-exp/auth-exp/karma.conf.js

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ function getTestFiles(argv) {
3535
return ['src/**/*.test.ts', 'test/helpers/**/*.test.ts'];
3636
} else if (argv.integration) {
3737
return ['test/integration/**/*.test.ts'];
38+
} else if (argv.cordova) {
39+
return ['src/platform_cordova/**/*.test.ts'];
3840
} else {
3941
// For the catch-all yarn:test, ignore the phone integration test
4042
return [

packages-exp/auth-exp/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
"test:browser:integration": "karma start --single-run --integration",
2828
"test:browser:debug": "karma start --auto-watch",
2929
"test:browser:unit:debug": "karma start --auto-watch --unit",
30+
"test:cordova": "karma start --single-run --cordova",
31+
"test:cordova:debug": "karma start --auto-watch --cordova",
3032
"test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'src/!(platform_browser|platform_react_native|platform_cordova)/**/*.test.ts' --file index.node.ts --config ../../config/mocharc.node.js",
3133
"api-report": "api-extractor run --local --verbose",
3234
"predoc": "node ../../scripts/exp/remove-exp.js temp",

packages-exp/auth-exp/rollup.config.shared.js

+13
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ export function getConfig({ isReleaseBuild }) {
9292
plugins: es5BuildPlugins,
9393
external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`))
9494
},
95+
/**
96+
* Cordova Builds
97+
*/
98+
{
99+
input: {
100+
index: 'index.cordova.ts',
101+
internal: 'internal/index.ts'
102+
},
103+
output: [{ dir: 'dist/cordova', format: 'es', sourcemap: true }],
104+
plugins: es5BuildPlugins,
105+
external: id =>
106+
[...deps, 'cordova'].some(dep => id === dep || id.startsWith(`${dep}/`))
107+
},
95108
/**
96109
* React Native Builds
97110
*/

packages-exp/auth-exp/src/core/util/browser.ts

+15-8
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export function _getBrowserName(userAgent: string): BrowserName | string {
8888
return BrowserName.OTHER;
8989
}
9090

91-
export function _isFirefox(ua: string): boolean {
91+
export function _isFirefox(ua = getUA()): boolean {
9292
return /firefox\//i.test(ua);
9393
}
9494

@@ -102,31 +102,38 @@ export function _isSafari(userAgent: string): boolean {
102102
);
103103
}
104104

105-
export function _isChromeIOS(ua: string): boolean {
105+
export function _isChromeIOS(ua = getUA()): boolean {
106106
return /crios\//i.test(ua);
107107
}
108108

109-
export function _isIEMobile(ua: string): boolean {
109+
export function _isIEMobile(ua = getUA()): boolean {
110110
return /iemobile/i.test(ua);
111111
}
112112

113-
export function _isAndroid(ua: string): boolean {
113+
export function _isAndroid(ua = getUA()): boolean {
114114
return /android/i.test(ua);
115115
}
116116

117-
export function _isBlackBerry(ua: string): boolean {
117+
export function _isBlackBerry(ua = getUA()): boolean {
118118
return /blackberry/i.test(ua);
119119
}
120120

121-
export function _isWebOS(ua: string): boolean {
121+
export function _isWebOS(ua = getUA()): boolean {
122122
return /webos/i.test(ua);
123123
}
124124

125-
export function _isIOS(ua: string): boolean {
125+
export function _isIOS(ua = getUA()): boolean {
126126
return /iphone|ipad|ipod/i.test(ua);
127127
}
128128

129-
export function _isIOSStandalone(ua: string): boolean {
129+
export function _isIOS7Or8(ua = getUA()): boolean {
130+
return (
131+
/(iPad|iPhone|iPod).*OS 7_\d/i.test(ua) ||
132+
/(iPad|iPhone|iPod).*OS 8_\d/i.test(ua)
133+
);
134+
}
135+
136+
export function _isIOSStandalone(ua = getUA()): boolean {
130137
return _isIOS(ua) && !!(window.navigator as NavigatorStandalone)?.standalone;
131138
}
132139

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { SDK_VERSION } from '@firebase/app-exp';
19+
import * as externs from '@firebase/auth-types-exp';
20+
import { ApiKey, AppName, Auth } from '../../model/auth';
21+
import { AuthEventType } from '../../model/popup_redirect';
22+
import { AuthErrorCode } from '../errors';
23+
import { OAuthProvider } from '../providers/oauth';
24+
import { _assert } from './assert';
25+
import { isEmpty, querystring } from '@firebase/util';
26+
import { _emulatorUrl } from './emulator';
27+
28+
/**
29+
* URL for Authentication widget which will initiate the OAuth handshake
30+
*
31+
* @internal
32+
*/
33+
const WIDGET_PATH = '__/auth/handler';
34+
35+
/**
36+
* URL for emulated environment
37+
*
38+
* @internal
39+
*/
40+
const EMULATOR_WIDGET_PATH = 'emulator/auth/handler';
41+
42+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
43+
type WidgetParams = {
44+
apiKey: ApiKey;
45+
appName: AppName;
46+
authType: AuthEventType;
47+
redirectUrl?: string;
48+
v: string;
49+
providerId?: string;
50+
scopes?: string;
51+
customParameters?: string;
52+
eventId?: string;
53+
tid?: string;
54+
} & { [key: string]: string | undefined };
55+
56+
export function _getRedirectUrl(
57+
auth: Auth,
58+
provider: externs.AuthProvider,
59+
authType: AuthEventType,
60+
redirectUrl?: string,
61+
eventId?: string,
62+
additionalParams?: Record<string, string>
63+
): string {
64+
_assert(auth.config.authDomain, auth, AuthErrorCode.MISSING_AUTH_DOMAIN);
65+
_assert(auth.config.apiKey, auth, AuthErrorCode.INVALID_API_KEY);
66+
67+
const params: WidgetParams = {
68+
apiKey: auth.config.apiKey,
69+
appName: auth.name,
70+
authType,
71+
redirectUrl,
72+
v: SDK_VERSION,
73+
eventId
74+
};
75+
76+
if (provider instanceof OAuthProvider) {
77+
provider.setDefaultLanguage(auth.languageCode);
78+
params.providerId = provider.providerId || '';
79+
if (!isEmpty(provider.getCustomParameters())) {
80+
params.customParameters = JSON.stringify(provider.getCustomParameters());
81+
}
82+
const scopes = provider.getScopes().filter(scope => scope !== '');
83+
if (scopes.length > 0) {
84+
params.scopes = scopes.join(',');
85+
}
86+
87+
// TODO set additionalParams from the provider as well?
88+
for (const [key, value] of Object.entries(additionalParams || {})) {
89+
params[key] = value;
90+
}
91+
}
92+
93+
if (auth.tenantId) {
94+
params.tid = auth.tenantId;
95+
}
96+
97+
for (const key of Object.keys(params)) {
98+
if ((params as Record<string, unknown>)[key] === undefined) {
99+
delete (params as Record<string, unknown>)[key];
100+
}
101+
}
102+
103+
// TODO: maybe set eid as endipointId
104+
// TODO: maybe set fw as Frameworks.join(",")
105+
106+
const url = new URL(
107+
`${getHandlerBase(auth)}?${querystring(
108+
params as Record<string, string | number>
109+
).slice(1)}`
110+
);
111+
112+
return url.toString();
113+
}
114+
115+
function getHandlerBase({ config }: Auth): string {
116+
if (!config.emulator) {
117+
return `https://${config.authDomain}/${WIDGET_PATH}`;
118+
}
119+
120+
return _emulatorUrl(config, EMULATOR_WIDGET_PATH);
121+
}

packages-exp/auth-exp/src/model/popup_redirect.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export interface PopupRedirectResolver extends externs.PopupRedirectResolver {
8686
provider: externs.AuthProvider,
8787
authType: AuthEventType,
8888
eventId?: string
89-
): Promise<never>;
89+
): Promise<unknown>;
9090
_isIframeWebStorageSupported(
9191
auth: Auth,
9292
cb: (support: boolean) => unknown

packages-exp/auth-exp/src/platform_browser/popup_redirect.ts

+12-100
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,15 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { SDK_VERSION } from '@firebase/app-exp';
1918
import * as externs from '@firebase/auth-types-exp';
20-
import { isEmpty, querystring } from '@firebase/util';
2119

2220
import { AuthEventManager } from '../core/auth/auth_event_manager';
2321
import { AuthErrorCode } from '../core/errors';
24-
import { OAuthProvider } from '../core/providers/oauth';
2522
import { _assert, debugAssert, _fail } from '../core/util/assert';
26-
import { _emulatorUrl } from '../core/util/emulator';
2723
import { _generateEventId } from '../core/util/event_id';
2824
import { _getCurrentUrl } from '../core/util/location';
2925
import { _validateOrigin } from '../core/util/validate_origin';
30-
import { ApiKey, AppName, Auth } from '../model/auth';
26+
import { Auth } from '../model/auth';
3127
import {
3228
AuthEventType,
3329
EventManager,
@@ -40,18 +36,7 @@ import { _openIframe } from './iframe/iframe';
4036
import { browserSessionPersistence } from './persistence/session_storage';
4137
import { _open, AuthPopup } from './util/popup';
4238
import { _getRedirectResult } from './strategies/redirect';
43-
44-
/**
45-
* URL for Authentication widget which will initiate the OAuth handshake
46-
*
47-
*/
48-
const WIDGET_PATH = '__/auth/handler';
49-
50-
/**
51-
* URL for emulated environment
52-
*
53-
*/
54-
const EMULATOR_WIDGET_PATH = 'emulator/auth/handler';
39+
import { _getRedirectUrl } from '../core/util/handler';
5540

5641
/**
5742
* The special web storage event
@@ -89,7 +74,13 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolver {
8974
);
9075

9176
await this.originValidation(auth);
92-
const url = getRedirectUrl(auth, provider, authType, eventId);
77+
const url = _getRedirectUrl(
78+
auth,
79+
provider,
80+
authType,
81+
_getCurrentUrl(),
82+
eventId
83+
);
9384
return _open(auth, url, _generateEventId());
9485
}
9586

@@ -100,7 +91,9 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolver {
10091
eventId?: string
10192
): Promise<never> {
10293
await this.originValidation(auth);
103-
_setWindowLocation(getRedirectUrl(auth, provider, authType, eventId));
94+
_setWindowLocation(
95+
_getRedirectUrl(auth, provider, authType, _getCurrentUrl(), eventId)
96+
);
10497
return new Promise(() => {});
10598
}
10699

@@ -180,84 +173,3 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolver {
180173
* @public
181174
*/
182175
export const browserPopupRedirectResolver: externs.PopupRedirectResolver = BrowserPopupRedirectResolver;
183-
184-
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
185-
type WidgetParams = {
186-
apiKey: ApiKey;
187-
appName: AppName;
188-
authType: AuthEventType;
189-
redirectUrl: string;
190-
v: string;
191-
providerId?: string;
192-
scopes?: string;
193-
customParameters?: string;
194-
eventId?: string;
195-
tid?: string;
196-
};
197-
198-
function getRedirectUrl(
199-
auth: Auth,
200-
provider: externs.AuthProvider,
201-
authType: AuthEventType,
202-
eventId?: string
203-
): string {
204-
_assert(auth.config.authDomain, auth, AuthErrorCode.MISSING_AUTH_DOMAIN);
205-
_assert(auth.config.apiKey, auth, AuthErrorCode.INVALID_API_KEY);
206-
207-
const params: WidgetParams = {
208-
apiKey: auth.config.apiKey,
209-
appName: auth.name,
210-
authType,
211-
redirectUrl: _getCurrentUrl(),
212-
v: SDK_VERSION,
213-
eventId
214-
};
215-
216-
if (provider instanceof OAuthProvider) {
217-
provider.setDefaultLanguage(auth.languageCode);
218-
params.providerId = provider.providerId || '';
219-
if (!isEmpty(provider.getCustomParameters())) {
220-
params.customParameters = JSON.stringify(provider.getCustomParameters());
221-
}
222-
const scopes = provider.getScopes().filter(scope => scope !== '');
223-
if (scopes.length > 0) {
224-
params.scopes = scopes.join(',');
225-
}
226-
// TODO set additionalParams?
227-
// let additionalParams = provider.getAdditionalParams();
228-
// for (let key in additionalParams) {
229-
// if (!params.hasOwnProperty(key)) {
230-
// params[key] = additionalParams[key]
231-
// }
232-
// }
233-
}
234-
235-
if (auth.tenantId) {
236-
params.tid = auth.tenantId;
237-
}
238-
239-
for (const key of Object.keys(params)) {
240-
if ((params as Record<string, unknown>)[key] === undefined) {
241-
delete (params as Record<string, unknown>)[key];
242-
}
243-
}
244-
245-
// TODO: maybe set eid as endipointId
246-
// TODO: maybe set fw as Frameworks.join(",")
247-
248-
const url = new URL(
249-
`${getHandlerBase(auth)}?${querystring(
250-
params as Record<string, string | number>
251-
).slice(1)}`
252-
);
253-
254-
return url.toString();
255-
}
256-
257-
function getHandlerBase({ config }: Auth): string {
258-
if (!config.emulator) {
259-
return `https://${config.authDomain}/${WIDGET_PATH}`;
260-
}
261-
262-
return _emulatorUrl(config, EMULATOR_WIDGET_PATH);
263-
}

0 commit comments

Comments
 (0)