Skip to content

Commit 706a315

Browse files
committed
Implement useAuthEmulator and remove auth.useEmulator.
1 parent 0eeefe2 commit 706a315

File tree

10 files changed

+213
-138
lines changed

10 files changed

+213
-138
lines changed

common/api-review/auth-exp.api.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,11 @@ export function updatePhoneNumber(user: externs.User, credential: externs.PhoneA
524524
// @public
525525
export function updateProfile(user: externs.User, { displayName, photoURL: photoUrl }: Profile): Promise<void>;
526526

527+
// @public
528+
export function useAuthEmulator(auth: externs.Auth, url: string, options?: {
529+
disableWarnings: boolean;
530+
}): void;
531+
527532
// @public
528533
export function useDeviceLanguage(auth: externs.Auth): void;
529534

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export class Auth
9898
return this.auth.signOut();
9999
}
100100
useEmulator(url: string, options?: { disableWarnings: boolean }): void {
101-
this.auth.useEmulator(url, options);
101+
impl.useAuthEmulator(this.auth, url, options);
102102
}
103103
applyActionCode(code: string): Promise<void> {
104104
return impl.applyActionCode(this.auth, code);

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

Lines changed: 0 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -461,126 +461,3 @@ describe('core/auth/auth_impl', () => {
461461
});
462462
});
463463
});
464-
465-
// These tests are separate because they are using a different auth with
466-
// separate setup and config
467-
describe('core/auth/auth_impl useEmulator', () => {
468-
let auth: TestAuth;
469-
let user: User;
470-
let normalEndpoint: fetch.Route;
471-
let emulatorEndpoint: fetch.Route;
472-
473-
beforeEach(async () => {
474-
auth = await testAuth();
475-
user = testUser(_castAuth(auth), 'uid', 'email', true);
476-
fetch.setUp();
477-
normalEndpoint = mockEndpoint(Endpoint.DELETE_ACCOUNT, {});
478-
emulatorEndpoint = fetch.mock(
479-
`http://localhost:2020/${endpointUrl(Endpoint.DELETE_ACCOUNT).replace(
480-
/^.*:\/\//,
481-
''
482-
)}`,
483-
{}
484-
);
485-
});
486-
487-
afterEach(() => {
488-
fetch.tearDown();
489-
sinon.restore();
490-
491-
// The DOM persists through tests; remove the banner if it is attached
492-
const banner =
493-
typeof document !== 'undefined'
494-
? document.querySelector('.firebase-emulator-warning')
495-
: null;
496-
if (banner) {
497-
banner.parentElement?.removeChild(banner);
498-
}
499-
});
500-
501-
context('useEmulator', () => {
502-
it('fails if a network request has already been made', async () => {
503-
await user.delete();
504-
expect(() => auth.useEmulator('http://localhost:2020')).to.throw(
505-
FirebaseError,
506-
'auth/emulator-config-failed'
507-
);
508-
});
509-
510-
it('updates the endpoint appropriately', async () => {
511-
auth.useEmulator('http://localhost:2020');
512-
await user.delete();
513-
expect(normalEndpoint.calls.length).to.eq(0);
514-
expect(emulatorEndpoint.calls.length).to.eq(1);
515-
});
516-
517-
it('checks the scheme properly', () => {
518-
expect(() => auth.useEmulator('http://localhost:2020')).not.to.throw;
519-
delete auth.config.emulator;
520-
expect(() => auth.useEmulator('https://localhost:2020')).not.to.throw;
521-
delete auth.config.emulator;
522-
expect(() => auth.useEmulator('ssh://localhost:2020')).to.throw(
523-
FirebaseError,
524-
'auth/invalid-emulator-scheme'
525-
);
526-
delete auth.config.emulator;
527-
expect(() => auth.useEmulator('localhost:2020')).to.throw(
528-
FirebaseError,
529-
'auth/invalid-emulator-scheme'
530-
);
531-
});
532-
533-
it('attaches a banner to the DOM', () => {
534-
auth.useEmulator('http://localhost:2020');
535-
if (typeof document !== 'undefined') {
536-
const el = document.querySelector('.firebase-emulator-warning')!;
537-
expect(el).not.to.be.null;
538-
expect(el.textContent).to.eq(
539-
'Running in emulator mode. ' +
540-
'Do not use with production credentials.'
541-
);
542-
}
543-
});
544-
545-
it('logs out a warning to the console', () => {
546-
sinon.stub(console, 'info');
547-
auth.useEmulator('http://localhost:2020');
548-
expect(console.info).to.have.been.calledWith(
549-
'WARNING: You are using the Auth Emulator,' +
550-
' which is intended for local testing only. Do not use with' +
551-
' production credentials.'
552-
);
553-
});
554-
555-
it('logs out the warning but has no banner if disableBanner true', () => {
556-
sinon.stub(console, 'info');
557-
auth.useEmulator('http://localhost:2020', { disableWarnings: true });
558-
expect(console.info).to.have.been.calledWith(
559-
'WARNING: You are using the Auth Emulator,' +
560-
' which is intended for local testing only. Do not use with' +
561-
' production credentials.'
562-
);
563-
if (typeof document !== 'undefined') {
564-
expect(document.querySelector('.firebase-emulator-warning')).to.be.null;
565-
}
566-
});
567-
});
568-
569-
context('toJSON', () => {
570-
it('works when theres no current user', () => {
571-
expect(JSON.stringify(auth)).to.eq(
572-
'{"apiKey":"test-api-key","authDomain":"localhost","appName":"test-app"}'
573-
);
574-
});
575-
576-
it('also stringifies the current user', () => {
577-
auth.currentUser = ({
578-
toJSON: (): object => ({ foo: 'bar' })
579-
} as unknown) as User;
580-
expect(JSON.stringify(auth)).to.eq(
581-
'{"apiKey":"test-api-key","authDomain":"localhost",' +
582-
'"appName":"test-app","currentUser":{"foo":"bar"}}'
583-
);
584-
});
585-
});
586-
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ export class AuthImpl implements Auth, _FirebaseService {
272272
this.languageCode = _getUserLanguage();
273273
}
274274

275-
useEmulator(url: string, options?: { disableWarnings: boolean }): void {
275+
_useEmulator(url: string, options?: { disableWarnings: boolean }): void {
276276
_assert(this._canInitEmulator, this, AuthErrorCode.EMULATOR_CONFIG_FAILED);
277277

278278
_assert(
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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 { expect, use } from 'chai';
19+
import * as chaiAsPromised from 'chai-as-promised';
20+
import * as sinon from 'sinon';
21+
import * as sinonChai from 'sinon-chai';
22+
23+
import { FirebaseError } from '@firebase/util';
24+
25+
import { endpointUrl, mockEndpoint } from '../../../test/helpers/api/helper';
26+
import { testAuth, TestAuth, testUser } from '../../../test/helpers/mock_auth';
27+
import * as fetch from '../../../test/helpers/mock_fetch';
28+
import { Endpoint } from '../../api';
29+
import { User } from '../../model/user';
30+
import { _castAuth } from './auth_impl';
31+
import { useAuthEmulator } from './emulator';
32+
33+
use(sinonChai);
34+
use(chaiAsPromised);
35+
36+
describe('core/auth/emulator', () => {
37+
let auth: TestAuth;
38+
let user: User;
39+
let normalEndpoint: fetch.Route;
40+
let emulatorEndpoint: fetch.Route;
41+
42+
beforeEach(async () => {
43+
auth = await testAuth();
44+
user = testUser(_castAuth(auth), 'uid', 'email', true);
45+
fetch.setUp();
46+
normalEndpoint = mockEndpoint(Endpoint.DELETE_ACCOUNT, {});
47+
emulatorEndpoint = fetch.mock(
48+
`http://localhost:2020/${endpointUrl(Endpoint.DELETE_ACCOUNT).replace(
49+
/^.*:\/\//,
50+
''
51+
)}`,
52+
{}
53+
);
54+
});
55+
56+
afterEach(() => {
57+
fetch.tearDown();
58+
sinon.restore();
59+
60+
// The DOM persists through tests; remove the banner if it is attached
61+
const banner =
62+
typeof document !== 'undefined'
63+
? document.querySelector('.firebase-emulator-warning')
64+
: null;
65+
if (banner) {
66+
banner.parentElement?.removeChild(banner);
67+
}
68+
});
69+
70+
context('useAuthEmulator', () => {
71+
it('fails if a network request has already been made', async () => {
72+
await user.delete();
73+
expect(() => useAuthEmulator(auth, 'http://localhost:2020')).to.throw(
74+
FirebaseError,
75+
'auth/emulator-config-failed'
76+
);
77+
});
78+
79+
it('updates the endpoint appropriately', async () => {
80+
useAuthEmulator(auth, 'http://localhost:2020');
81+
await user.delete();
82+
expect(normalEndpoint.calls.length).to.eq(0);
83+
expect(emulatorEndpoint.calls.length).to.eq(1);
84+
});
85+
86+
it('checks the scheme properly', () => {
87+
expect(() => useAuthEmulator(auth, 'http://localhost:2020')).not.to.throw;
88+
delete auth.config.emulator;
89+
expect(() => useAuthEmulator(auth, 'https://localhost:2020')).not.to
90+
.throw;
91+
delete auth.config.emulator;
92+
expect(() => useAuthEmulator(auth, 'ssh://localhost:2020')).to.throw(
93+
FirebaseError,
94+
'auth/invalid-emulator-scheme'
95+
);
96+
delete auth.config.emulator;
97+
expect(() => useAuthEmulator(auth, 'localhost:2020')).to.throw(
98+
FirebaseError,
99+
'auth/invalid-emulator-scheme'
100+
);
101+
});
102+
103+
it('attaches a banner to the DOM', () => {
104+
useAuthEmulator(auth, 'http://localhost:2020');
105+
if (typeof document !== 'undefined') {
106+
const el = document.querySelector('.firebase-emulator-warning')!;
107+
expect(el).not.to.be.null;
108+
expect(el.textContent).to.eq(
109+
'Running in emulator mode. ' +
110+
'Do not use with production credentials.'
111+
);
112+
}
113+
});
114+
115+
it('logs out a warning to the console', () => {
116+
sinon.stub(console, 'info');
117+
useAuthEmulator(auth, 'http://localhost:2020');
118+
expect(console.info).to.have.been.calledWith(
119+
'WARNING: You are using the Auth Emulator,' +
120+
' which is intended for local testing only. Do not use with' +
121+
' production credentials.'
122+
);
123+
});
124+
125+
it('logs out the warning but has no banner if disableBanner true', () => {
126+
sinon.stub(console, 'info');
127+
useAuthEmulator(auth, 'http://localhost:2020', { disableWarnings: true });
128+
expect(console.info).to.have.been.calledWith(
129+
'WARNING: You are using the Auth Emulator,' +
130+
' which is intended for local testing only. Do not use with' +
131+
' production credentials.'
132+
);
133+
if (typeof document !== 'undefined') {
134+
expect(document.querySelector('.firebase-emulator-warning')).to.be.null;
135+
}
136+
});
137+
});
138+
139+
context('toJSON', () => {
140+
it('works when theres no current user', () => {
141+
expect(JSON.stringify(auth)).to.eq(
142+
'{"apiKey":"test-api-key","authDomain":"localhost","appName":"test-app"}'
143+
);
144+
});
145+
146+
it('also stringifies the current user', () => {
147+
auth.currentUser = ({
148+
toJSON: (): object => ({ foo: 'bar' })
149+
} as unknown) as User;
150+
expect(JSON.stringify(auth)).to.eq(
151+
'{"apiKey":"test-api-key","authDomain":"localhost",' +
152+
'"appName":"test-app","currentUser":{"foo":"bar"}}'
153+
);
154+
});
155+
});
156+
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
import * as externs from '@firebase/auth-types-exp';
18+
import { _castAuth } from './auth_impl';
19+
20+
/**
21+
* Changes the Auth instance to communicate with the Firebase Auth Emulator, instead of production
22+
* Firebase Auth services.
23+
*
24+
* @remarks
25+
* This must be called synchronously immediately following the first call to
26+
* {@link @firebase/auth#initializeAuth}. Do not use with production credentials as emulator
27+
* traffic is not encrypted.
28+
*
29+
*
30+
* @example
31+
* ```javascript
32+
* useAuthEmulator(auth, 'http://127.0.0.1:9099');
33+
* ```
34+
*
35+
* @param auth - The Auth instance.
36+
* @param url - The URL at which the emulator is running (eg, 'http://localhost:9099').
37+
* @param options.disableWarnings - (Optional: default false) Disable the warning banner attached to the DOM
38+
*
39+
* @public
40+
*/
41+
export function useAuthEmulator(
42+
auth: externs.Auth,
43+
url: string,
44+
options?: { disableWarnings: boolean }
45+
): void {
46+
_castAuth(auth)._useEmulator(url, options);
47+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ function _debugErrorMap(): ErrorMap<AuthErrorCode> {
162162
[AuthErrorCode.EMULATOR_CONFIG_FAILED]:
163163
'Auth instance has already been used to make a network call. Auth can ' +
164164
'no longer be configured to use the emulator. Try calling ' +
165-
'"useEmulator()" sooner.',
165+
'"useAuthEmulator()" sooner.',
166166
[AuthErrorCode.EXPIRED_OOB_CODE]: 'The action code has expired.',
167167
[AuthErrorCode.EXPIRED_POPUP_REQUEST]:
168168
'This operation has been cancelled due to another conflicting popup being opened.',

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ export function signOut(auth: externs.Auth): Promise<void> {
134134
}
135135

136136
export { initializeAuth } from './auth/initialize';
137+
export { useAuthEmulator } from './auth/emulator';
137138

138139
// credentials
139140
export { AuthCredential } from './credentials';

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export interface Auth extends externs.Auth {
5656
_startProactiveRefresh(): void;
5757
_stopProactiveRefresh(): void;
5858
_getPersistence(): string;
59+
_useEmulator(url: string, options?: { disableWarnings: boolean }): void;
5960

6061
readonly name: AppName;
6162
readonly config: ConfigInternal;

0 commit comments

Comments
 (0)