Skip to content

Commit fd9d539

Browse files
committed
Add hooks into emulator for auth-next
1 parent e22885d commit fd9d539

File tree

8 files changed

+132
-18
lines changed

8 files changed

+132
-18
lines changed

packages-exp/auth-exp/src/api/authentication/token.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919

2020
import { querystring } from '@firebase/util';
2121

22-
import { _performFetchWithErrorHandling, HttpMethod } from '../';
23-
import { AuthCore } from '../../model/auth';
22+
import { _getFinalTarget, _performFetchWithErrorHandling, HttpMethod } from '../';
2423
import { FetchProvider } from '../../core/util/fetch_provider';
24+
import { AuthCore } from '../../model/auth';
2525

2626
export const _ENDPOINT = 'v1/token';
2727
const GRANT_TYPE = 'refresh_token';
@@ -51,7 +51,7 @@ export async function requestStsToken(
5151
'refresh_token': refreshToken
5252
}).slice(1);
5353
const { apiScheme, tokenApiHost, apiKey, sdkClientVersion } = auth.config;
54-
const url = `${apiScheme}://${tokenApiHost}/${_ENDPOINT}`;
54+
const url = _getFinalTarget(auth, `${apiScheme}://${tokenApiHost}/${_ENDPOINT}`);
5555

5656
return FetchProvider.fetch()(`${url}?key=${apiKey}`, {
5757
method: HttpMethod.POST,

packages-exp/auth-exp/src/api/index.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import { FirebaseError, querystring } from '@firebase/util';
1919

20+
<<<<<<< HEAD
2021
import {
2122
AUTH_ERROR_FACTORY,
2223
AuthErrorCode,
@@ -26,6 +27,13 @@ import { fail } from '../core/util/assert';
2627
import { Delay } from '../core/util/delay';
2728
import { FetchProvider } from '../core/util/fetch_provider';
2829
import { AuthCore } from '../model/auth';
30+
=======
31+
import { AUTH_ERROR_FACTORY, AuthErrorCode, NamedErrorParams } from '../core/errors';
32+
import { fail } from '../core/util/assert';
33+
import { Delay } from '../core/util/delay';
34+
import { FetchProvider } from '../core/util/fetch_provider';
35+
import { Auth, AuthCore } from '../model/auth';
36+
>>>>>>> be103af54 (Add hooks into emulator for auth-next)
2937
import { IdTokenResponse, TaggedWithTokenResponse } from '../model/id_token';
3038
import { IdTokenMfaResponse } from './authentication/mfa';
3139
import { SERVER_ERROR_MAP, ServerError, ServerErrorMap } from './errors';
@@ -99,7 +107,7 @@ export async function _performApiRequest<T, V>(
99107
}
100108

101109
return FetchProvider.fetch()(
102-
`${auth.config.apiScheme}://${auth.config.apiHost}${path}?${query}`,
110+
_getFinalTarget(auth, `${auth.config.apiScheme}://${auth.config.apiHost}${path}?${query}`),
103111
{
104112
method,
105113
headers,
@@ -115,6 +123,7 @@ export async function _performFetchWithErrorHandling<V>(
115123
customErrorMap: Partial<ServerErrorMap<ServerError>>,
116124
fetchFn: () => Promise<Response>
117125
): Promise<V> {
126+
(auth as Auth)._canInitEmulator = false;
118127
const errorMap = { ...SERVER_ERROR_MAP, ...customErrorMap };
119128
try {
120129
const response: Response = await Promise.race<Promise<Response>>([
@@ -183,6 +192,16 @@ export async function _performSignInRequest<T, V extends IdTokenResponse>(
183192
return serverResponse;
184193
}
185194

195+
export function _getFinalTarget(auth: AuthCore, url: string): string {
196+
const {emulator} = auth.config;
197+
if (!emulator) {
198+
return url;
199+
}
200+
201+
const urlWithoutScheme = url.replace(/^.*:\/\//, '');
202+
return `http://${emulator.hostname}:${emulator.port}/${urlWithoutScheme}`;
203+
}
204+
186205
function makeNetworkTimeout<T>(appName: string): Promise<T> {
187206
return new Promise((_, reject) =>
188207
setTimeout(() => {

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,7 @@
1818
import { _FirebaseService, FirebaseApp } from '@firebase/app-types-exp';
1919
import * as externs from '@firebase/auth-types-exp';
2020
import {
21-
CompleteFn,
22-
createSubscribe,
23-
ErrorFn,
24-
NextFn,
25-
Observer,
26-
Subscribe,
27-
Unsubscribe
21+
CompleteFn, createSubscribe, ErrorFn, NextFn, Observer, Subscribe, Unsubscribe
2822
} from '@firebase/util';
2923

3024
import { Auth, AuthCore } from '../../model/auth';
@@ -33,8 +27,7 @@ import { User, UserParameters } from '../../model/user';
3327
import { AuthErrorCode } from '../errors';
3428
import { Persistence } from '../persistence';
3529
import {
36-
_REDIRECT_USER_KEY_NAME,
37-
PersistenceUserManager
30+
_REDIRECT_USER_KEY_NAME, PersistenceUserManager
3831
} from '../persistence/persistence_user_manager';
3932
import { _reloadWithoutSaving } from '../user/reload';
4033
import { UserImpl } from '../user/user_impl';
@@ -63,6 +56,10 @@ export class AuthImplCompat<T extends User> implements Auth, _FirebaseService {
6356
private idTokenSubscription = new Subscription<T>(this);
6457
private redirectUser: T | null = null;
6558
private isProactiveRefreshEnabled = false;
59+
60+
// Any network calls will set this to true and prevent subsequent emulator
61+
// initialization
62+
_canInitEmulator = true;
6663
_isInitialized = false;
6764
_initializationPromise: Promise<void> | null = null;
6865
_popupRedirectResolver: PopupRedirectResolver | null = null;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* @license
3+
* Copyright 2020 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 { expect } from 'chai';
19+
20+
import { Auth, User } from '@firebase/auth-types-exp';
21+
import { FirebaseError } from '@firebase/util';
22+
23+
import { endpointUrl, mockEndpoint } from '../../../test/helpers/api/helper';
24+
import { testAuth, testUser } from '../../../test/helpers/mock_auth';
25+
import * as fetch from '../../../test/helpers/mock_fetch';
26+
import { _getFinalTarget, Endpoint } from '../../api';
27+
import { _castAuth } from './auth_impl';
28+
import { useEmulator } from './initialize';
29+
30+
describe('core/auth/initialize', () => {
31+
let auth: Auth;
32+
let user: User;
33+
let normalEndpoint: fetch.Route;
34+
let emulatorEndpoint: fetch.Route;
35+
36+
beforeEach(async () => {
37+
auth = await testAuth();
38+
user = testUser(_castAuth(auth), 'uid', 'email', true);
39+
fetch.setUp();
40+
normalEndpoint = mockEndpoint(Endpoint.DELETE_ACCOUNT, {});
41+
emulatorEndpoint = fetch.mock(`http://localhost:2020/${endpointUrl(Endpoint.DELETE_ACCOUNT).replace(/^.*:\/\//, '')}`, {});
42+
});
43+
44+
afterEach(() => {
45+
fetch.tearDown();
46+
});
47+
48+
context('useEmulator', () => {
49+
it('fails if a network request has already been made', async () => {
50+
await user.delete();
51+
expect(() => useEmulator(auth, 'localhost', 2020)).to.throw(FirebaseError, 'auth/emulator-config-failed');
52+
});
53+
54+
it('updates the endpoint appropriately', async () => {
55+
useEmulator(auth, 'localhost', 2020);
56+
await user.delete();
57+
expect(normalEndpoint.calls.length).to.eq(0);
58+
expect(emulatorEndpoint.calls.length).to.eq(1);
59+
});
60+
});
61+
});

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ import { FirebaseApp } from '@firebase/app-types-exp';
2020
import * as externs from '@firebase/auth-types-exp';
2121

2222
import { Dependencies } from '../../model/auth';
23+
import { AuthErrorCode } from '../errors';
2324
import { Persistence } from '../persistence';
25+
import { fail } from '../util/assert';
2426
import { _getInstance } from '../util/instantiator';
25-
import { AuthImpl } from './auth_impl';
27+
import { _castAuth, AuthImpl } from './auth_impl';
2628

2729
export function initializeAuth(
2830
app: FirebaseApp = getApp(),
@@ -34,6 +36,24 @@ export function initializeAuth(
3436
return auth;
3537
}
3638

39+
export function useEmulator(
40+
authExtern: externs.Auth,
41+
hostname: string,
42+
port: number,
43+
): void {
44+
const auth = _castAuth(authExtern);
45+
if (!auth._canInitEmulator) {
46+
fail(AuthErrorCode.EMULATOR_CONFIG_FAILED, {
47+
appName: auth.name
48+
});
49+
}
50+
51+
auth.config.emulator = {
52+
hostname,
53+
port
54+
};
55+
}
56+
3757
export function _initializeAuthInstance(
3858
auth: AuthImpl,
3959
deps?: Dependencies

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
import * as externs from '@firebase/auth-types-exp';
2020
import { ErrorFactory, ErrorMap } from '@firebase/util';
2121

22-
import { AppName } from '../model/auth';
2322
import { IdTokenMfaResponse } from '../api/authentication/mfa';
23+
import { AppName } from '../model/auth';
2424

2525
/*
2626
* Developer facing Firebase Auth error codes.
@@ -40,6 +40,7 @@ export const enum AuthErrorCode {
4040
DYNAMIC_LINK_NOT_ACTIVATED = 'dynamic-link-not-activated',
4141
EMAIL_CHANGE_NEEDS_VERIFICATION = 'email-change-needs-verification',
4242
EMAIL_EXISTS = 'email-already-in-use',
43+
EMULATOR_CONFIG_FAILED = 'emulator-config-failed',
4344
EXPIRED_OOB_CODE = 'expired-action-code',
4445
EXPIRED_POPUP_REQUEST = 'cancelled-popup-request',
4546
INTERNAL_ERROR = 'internal-error',
@@ -116,7 +117,7 @@ export const enum AuthErrorCode {
116117
USER_MISMATCH = 'user-mismatch',
117118
USER_SIGNED_OUT = 'user-signed-out',
118119
WEAK_PASSWORD = 'weak-password',
119-
WEB_STORAGE_UNSUPPORTED = 'web-storage-unsupported'
120+
WEB_STORAGE_UNSUPPORTED = 'web-storage-unsupported',
120121
}
121122

122123
const ERRORS: ErrorMap<AuthErrorCode> = {
@@ -154,6 +155,10 @@ const ERRORS: ErrorMap<AuthErrorCode> = {
154155
'Multi-factor users must always have a verified email.',
155156
[AuthErrorCode.EMAIL_EXISTS]:
156157
'The email address is already in use by another account.',
158+
[AuthErrorCode.EMULATOR_CONFIG_FAILED]:
159+
'Auth instance has already been used to make a network call. Auth can ' +
160+
'no longer be configured to use the emulator. Try calling ' +
161+
'"useEmulator()" sooner.',
157162
[AuthErrorCode.EXPIRED_OOB_CODE]: 'The action code has expired.',
158163
[AuthErrorCode.EXPIRED_POPUP_REQUEST]:
159164
'This operation has been cancelled due to another conflicting popup being opened.',

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,20 @@ export type AppName = string;
2525
export type ApiKey = string;
2626
export type AuthDomain = string;
2727

28+
interface ConfigInternal extends externs.Config {
29+
emulator?: {
30+
hostname: string;
31+
port: number;
32+
}
33+
}
34+
2835
/**
2936
* Core implementation of the Auth object, the signatures here should match across both legacy
3037
* and modern implementations
3138
*/
3239
export interface AuthCore {
3340
readonly name: AppName;
34-
readonly config: externs.Config;
41+
readonly config: ConfigInternal;
3542
languageCode: string | null;
3643
tenantId: string | null;
3744
readonly settings: externs.AuthSettings;
@@ -42,6 +49,7 @@ export interface AuthCore {
4249

4350
export interface Auth extends AuthCore {
4451
currentUser: User | null;
52+
_canInitEmulator: boolean;
4553
_isInitialized: boolean;
4654
_initializationPromise: Promise<void> | null;
4755
updateCurrentUser(user: User | null): Promise<void>;

packages-exp/auth-exp/test/helpers/api/helper.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@ import { Endpoint } from '../../../src/api';
1919
import { TEST_HOST, TEST_KEY, TEST_SCHEME } from '../mock_auth';
2020
import { mock, Route } from '../mock_fetch';
2121

22+
export function endpointUrl(endpoint: Endpoint):string {
23+
return `${TEST_SCHEME}://${TEST_HOST}${endpoint}?key=${TEST_KEY}`;
24+
}
25+
2226
export function mockEndpoint(
2327
endpoint: Endpoint,
2428
response: object,
2529
status = 200
2630
): Route {
2731
return mock(
28-
`${TEST_SCHEME}://${TEST_HOST}${endpoint}?key=${TEST_KEY}`,
32+
endpointUrl(endpoint),
2933
response,
3034
status
3135
);

0 commit comments

Comments
 (0)