Skip to content

Commit 8d545cd

Browse files
authored
Merge 03aaa51 into e558d3e
2 parents e558d3e + 03aaa51 commit 8d545cd

File tree

5 files changed

+91
-38
lines changed

5 files changed

+91
-38
lines changed

packages-exp/auth-exp/src/core/action_code_url.ts

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

18+
import { extractQuerystring, querystringDecode } from '@firebase/util';
1819
import { ActionCodeOperation } from '../model/public_types';
1920
import { AuthErrorCode } from './errors';
2021
import { _assert } from './util/assert';
@@ -63,14 +64,18 @@ function parseMode(mode: string | null): ActionCodeOperation | null {
6364
* @param url
6465
*/
6566
function parseDeepLink(url: string): string {
66-
const uri = new URL(url);
67-
const link = uri.searchParams.get('link');
67+
const link = querystringDecode(extractQuerystring(url))['link'];
68+
6869
// Double link case (automatic redirect).
69-
const doubleDeepLink = link ? new URL(link).searchParams.get('link') : null;
70+
const doubleDeepLink = link
71+
? querystringDecode(extractQuerystring(link))['deep_link_id']
72+
: null;
7073
// iOS custom scheme links.
71-
const iOSDeepLink = uri.searchParams.get('deep_link_id');
74+
const iOSDeepLink = querystringDecode(extractQuerystring(url))[
75+
'deep_link_id'
76+
];
7277
const iOSDoubleDeepLink = iOSDeepLink
73-
? new URL(iOSDeepLink).searchParams.get('link')
78+
? querystringDecode(extractQuerystring(iOSDeepLink))['link']
7479
: null;
7580
return iOSDoubleDeepLink || iOSDeepLink || doubleDeepLink || link || url;
7681
}
@@ -115,18 +120,18 @@ export class ActionCodeURL {
115120
* @internal
116121
*/
117122
constructor(actionLink: string) {
118-
const uri = new URL(actionLink);
119-
const apiKey = uri.searchParams.get(QueryField.API_KEY);
120-
const code = uri.searchParams.get(QueryField.CODE);
121-
const operation = parseMode(uri.searchParams.get(QueryField.MODE));
123+
const searchParams = querystringDecode(extractQuerystring(actionLink));
124+
const apiKey = searchParams[QueryField.API_KEY] ?? null;
125+
const code = searchParams[QueryField.CODE] ?? null;
126+
const operation = parseMode(searchParams[QueryField.MODE] ?? null);
122127
// Validate API key, code and mode.
123128
_assert(apiKey && code && operation, AuthErrorCode.ARGUMENT_ERROR);
124129
this.apiKey = apiKey;
125130
this.operation = operation;
126131
this.code = code;
127-
this.continueUrl = uri.searchParams.get(QueryField.CONTINUE_URL);
128-
this.languageCode = uri.searchParams.get(QueryField.LANGUAGE_CODE);
129-
this.tenantId = uri.searchParams.get(QueryField.TENANT_ID);
132+
this.continueUrl = searchParams[QueryField.CONTINUE_URL] ?? null;
133+
this.languageCode = searchParams[QueryField.LANGUAGE_CODE] ?? null;
134+
this.tenantId = searchParams[QueryField.TENANT_ID] ?? null;
130135
}
131136

132137
/**

packages-exp/auth-exp/src/core/auth/emulator.ts

+44-6
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,60 @@ export function useAuthEmulator(
5858
AuthErrorCode.INVALID_EMULATOR_SCHEME
5959
);
6060

61-
const parsedUrl = new URL(url);
6261
const disableWarnings = !!options?.disableWarnings;
6362

64-
// Store the normalized URL whose path is always nonempty (i.e. containing at least a single '/').
65-
authInternal.config.emulator = { url: parsedUrl.toString() };
63+
const protocol = extractProtocol(url);
64+
const { host, port } = extractHostAndPort(url);
65+
const portStr = port === null ? '' : `:${port}`;
66+
67+
// Always replace path with "/" (even if input url had no path at all, or had a different one).
68+
authInternal.config.emulator = { url: `${protocol}//${host}${portStr}/` };
6669
authInternal.settings.appVerificationDisabledForTesting = true;
6770
authInternal.emulatorConfig = Object.freeze({
68-
host: parsedUrl.hostname,
69-
port: parsedUrl.port ? Number(parsedUrl.port) : null,
70-
protocol: parsedUrl.protocol.replace(':', ''),
71+
host,
72+
port,
73+
protocol: protocol.replace(':', ''),
7174
options: Object.freeze({ disableWarnings })
7275
});
7376

7477
emitEmulatorWarning(disableWarnings);
7578
}
7679

80+
function extractProtocol(url: string): string {
81+
const protocolEnd = url.indexOf(':');
82+
return protocolEnd < 0 ? '' : url.substr(0, protocolEnd + 1);
83+
}
84+
85+
function extractHostAndPort(
86+
url: string
87+
): { host: string; port: number | null } {
88+
const protocol = extractProtocol(url);
89+
const authority = /(\/\/)?([^?#/]+)/.exec(url.substr(protocol.length)); // Between // and /, ? or #.
90+
if (!authority) {
91+
return { host: '', port: null };
92+
}
93+
const hostAndPort = authority[2].split('@').pop() || ''; // Strip out "username:password@".
94+
const bracketedIPv6 = /^(\[[^\]]+\])(:|$)/.exec(hostAndPort);
95+
if (bracketedIPv6) {
96+
const host = bracketedIPv6[1];
97+
return { host, port: parsePort(hostAndPort.substr(host.length + 1)) };
98+
} else {
99+
const [host, port] = hostAndPort.split(':');
100+
return { host, port: parsePort(port) };
101+
}
102+
}
103+
104+
function parsePort(portStr: string): number | null {
105+
if (!portStr) {
106+
return null;
107+
}
108+
const port = Number(portStr);
109+
if (isNaN(port)) {
110+
return null;
111+
}
112+
return port;
113+
}
114+
77115
function emitEmulatorWarning(disableBanner: boolean): void {
78116
function attachBanner(): void {
79117
const el = document.createElement('p');

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

+7-13
Original file line numberDiff line numberDiff line change
@@ -94,22 +94,16 @@ export function _getRedirectUrl(
9494
params.tid = auth.tenantId;
9595
}
9696

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-
10397
// TODO: maybe set eid as endipointId
10498
// TODO: maybe set fw as Frameworks.join(",")
10599

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();
100+
const paramsDict = params as Record<string, string | number>;
101+
for (const key of Object.keys(paramsDict)) {
102+
if (paramsDict[key] === undefined) {
103+
delete paramsDict[key];
104+
}
105+
}
106+
return `${getHandlerBase(auth)}?${querystring(paramsDict).slice(1)}`;
113107
}
114108

115109
function getHandlerBase({ config }: AuthInternal): string {

packages-exp/auth-exp/src/platform_react_native/persistence/react_native.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
STORAGE_AVAILABLE_KEY,
2525
StorageEventListener
2626
} from '../../core/persistence';
27-
import { debugFail } from '../../core/util/assert';
2827

2928
/**
3029
* Returns a persistence class that wraps AsyncStorage imported from
@@ -74,11 +73,13 @@ export function getReactNativePersistence(
7473
}
7574

7675
_addListener(_key: string, _listener: StorageEventListener): void {
77-
debugFail('not implemented');
76+
// Listeners are not supported for React Native storage.
77+
return;
7878
}
7979

8080
_removeListener(_key: string, _listener: StorageEventListener): void {
81-
debugFail('not implemented');
81+
// Listeners are not supported for React Native storage.
82+
return;
8283
}
8384
};
8485
}

packages/util/src/query.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,30 @@ export function querystring(querystringParams: {
4242
* Decodes a querystring (e.g. ?arg=val&arg2=val2) into a params object
4343
* (e.g. {arg: 'val', arg2: 'val2'})
4444
*/
45-
export function querystringDecode(querystring: string): object {
46-
const obj: { [key: string]: unknown } = {};
45+
export function querystringDecode(querystring: string): Record<string, string> {
46+
const obj: Record<string, string> = {};
4747
const tokens = querystring.replace(/^\?/, '').split('&');
4848

4949
tokens.forEach(token => {
5050
if (token) {
51-
const key = token.split('=');
52-
obj[key[0]] = key[1];
51+
const [key, value] = token.split('=');
52+
obj[decodeURIComponent(key)] = decodeURIComponent(value);
5353
}
5454
});
5555
return obj;
5656
}
57+
58+
/**
59+
* Extract the query string part of a URL, including the leading question mark (if present).
60+
*/
61+
export function extractQuerystring(url: string): string {
62+
const queryStart = url.indexOf('?');
63+
if (!queryStart) {
64+
return '';
65+
}
66+
const fragmentStart = url.indexOf('#', queryStart);
67+
return url.substring(
68+
queryStart,
69+
fragmentStart > 0 ? fragmentStart : undefined
70+
);
71+
}

0 commit comments

Comments
 (0)