Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions packages/auth/src/core/auth/auth_impl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,28 +553,62 @@ describe('core/auth/auth_impl', () => {
expect(callbackCalled).to.be.false;
});

it('immediately calls firebaseTokenChange if initialization finished', done => {
const token: FirebaseToken = {
token: 'test-token',
expirationTime: 123456
};
(auth as any).firebaseToken = token;
auth._isInitialized = true;
auth.onFirebaseTokenChanged(t => {
expect(t).to.eq(token);
done();
});
});

it('waits for initialization for onFirebaseTokenChanged', done => {
const token: FirebaseToken = {
token: 'test-token',
expirationTime: 123456
};
(auth as any).firebaseToken = token;
auth._isInitialized = false;
auth.onFirebaseTokenChanged(t => {
expect(t).to.eq(token);
done();
});
});

describe('user logs in/out, tokens refresh', () => {
let user: UserInternal;
let authStateCallback: sinon.SinonSpy;
let idTokenCallback: sinon.SinonSpy;
let beforeAuthCallback: sinon.SinonSpy;
let firebaseTokenCallback: sinon.SinonSpy;
const testFirebaseToken: FirebaseToken = {
token: 'test-fb-token',
expirationTime: 123456789
};

beforeEach(() => {
user = testUser(auth, 'uid');
authStateCallback = sinon.spy();
idTokenCallback = sinon.spy();
beforeAuthCallback = sinon.spy();
firebaseTokenCallback = sinon.spy();
});

context('initially currentUser is null', () => {
beforeEach(async () => {
auth.onAuthStateChanged(authStateCallback);
auth.onIdTokenChanged(idTokenCallback);
auth.beforeAuthStateChanged(beforeAuthCallback);
auth.onFirebaseTokenChanged(firebaseTokenCallback);
await auth._updateCurrentUser(null);
authStateCallback.resetHistory();
idTokenCallback.resetHistory();
beforeAuthCallback.resetHistory();
firebaseTokenCallback.resetHistory();
});

it('onAuthStateChange triggers on log in', async () => {
Expand All @@ -591,17 +625,26 @@ describe('core/auth/auth_impl', () => {
await auth._updateCurrentUser(user);
expect(beforeAuthCallback).to.have.been.calledWith(user);
});

it('onFirebaseTokenChanged triggers on token set', async () => {
await auth._updateFirebaseToken(testFirebaseToken);
expect(firebaseTokenCallback).to.have.been.calledWith(
testFirebaseToken
);
});
});

context('initially currentUser is user', () => {
beforeEach(async () => {
auth.onAuthStateChanged(authStateCallback);
auth.onIdTokenChanged(idTokenCallback);
auth.beforeAuthStateChanged(beforeAuthCallback);
auth.onFirebaseTokenChanged(firebaseTokenCallback);
await auth._updateCurrentUser(user);
authStateCallback.resetHistory();
idTokenCallback.resetHistory();
beforeAuthCallback.resetHistory();
firebaseTokenCallback.resetHistory();
});

it('onAuthStateChange triggers on log out', async () => {
Expand Down Expand Up @@ -638,6 +681,43 @@ describe('core/auth/auth_impl', () => {
});
});

context('initially firebaseToken is null', () => {
beforeEach(async () => {
auth.onFirebaseTokenChanged(firebaseTokenCallback);
await auth._updateFirebaseToken(null);
firebaseTokenCallback.resetHistory();
});

it('onFirebaseTokenChanged triggers on token set', async () => {
await auth._updateFirebaseToken(testFirebaseToken);
expect(firebaseTokenCallback).to.have.been.calledWith(
testFirebaseToken
);
});
});

context('initially firebaseToken is token', () => {
beforeEach(async () => {
auth.onFirebaseTokenChanged(firebaseTokenCallback);
await auth._updateFirebaseToken(testFirebaseToken);
firebaseTokenCallback.resetHistory();
});

it('onFirebaseTokenChanged triggers on token set to null', async () => {
await auth._updateFirebaseToken(null);
expect(firebaseTokenCallback).to.have.been.calledWith(null);
});

it('onFirebaseTokenChanged triggers for token props change', async () => {
const newToken: FirebaseToken = {
...testFirebaseToken,
token: 'new-fb-token'
};
await auth._updateFirebaseToken(newToken);
expect(firebaseTokenCallback).to.have.been.calledWith(newToken);
});
});

context('with Proactive Refresh', () => {
let oldUser: UserInternal;

Expand Down Expand Up @@ -739,6 +819,20 @@ describe('core/auth/auth_impl', () => {
expect(cb2).to.have.been.calledWith(user);
});

it('onFirebaseTokenChanged works for multiple listeners', async () => {
const cb1 = sinon.spy();
const cb2 = sinon.spy();
auth.onFirebaseTokenChanged(cb1);
auth.onFirebaseTokenChanged(cb2);
await auth._updateFirebaseToken(null);
cb1.resetHistory();
cb2.resetHistory();

await auth._updateFirebaseToken(testFirebaseToken);
expect(cb1).to.have.been.calledWith(testFirebaseToken);
expect(cb2).to.have.been.calledWith(testFirebaseToken);
});

it('_updateCurrentUser throws if a beforeAuthStateChange callback throws', async () => {
await auth._updateCurrentUser(null);
const cb1 = sinon.stub().throws();
Expand Down
25 changes: 21 additions & 4 deletions packages/auth/src/core/auth/auth_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
return this.registerStateListener(
this.authStateSubscription,
nextOrObserver,
this.currentUser,
error,
completed
);
Expand All @@ -667,6 +668,21 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
return this.registerStateListener(
this.idTokenSubscription,
nextOrObserver,
this.currentUser,
error,
completed
);
}

onFirebaseTokenChanged(
nextOrObserver: NextOrObserver<FirebaseToken>,
error?: ErrorFn,
completed?: CompleteFn
): Unsubscribe | undefined {
return this.registerStateListener(
this.firebaseTokenSubscription,
nextOrObserver,
this.firebaseToken,
error,
completed
);
Expand Down Expand Up @@ -814,9 +830,10 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
}
}

private registerStateListener(
subscription: Subscription<User>,
nextOrObserver: NextOrObserver<User>,
private registerStateListener<T>(
subscription: Subscription<T>,
nextOrObserver: NextOrObserver<T>,
currentValue: T | null,
error?: ErrorFn,
completed?: CompleteFn
): Unsubscribe {
Expand All @@ -841,7 +858,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
if (isUnsubscribed) {
return;
}
cb(this.currentUser);
cb(currentValue);
});

if (typeof nextOrObserver === 'function') {
Expand Down
9 changes: 9 additions & 0 deletions packages/auth/src/model/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@
import {
Auth,
AuthSettings,
CompleteFn,
Config,
EmulatorConfig,
ErrorFn,
NextOrObserver,
PasswordPolicy,
PasswordValidationStatus,
PopupRedirectResolver,
TenantConfig,
Unsubscribe,
User
} from './public_types';
import { ErrorFactory } from '@firebase/util';
Expand Down Expand Up @@ -77,6 +81,11 @@ export interface AuthInternal extends Auth {
currentUser: User | null;
emulatorConfig: EmulatorConfig | null;
getFirebaseAccessToken(forceRefresh?: boolean): Promise<string | null>;
onFirebaseTokenChanged(
nextOrObserver: NextOrObserver<FirebaseToken>,
error?: ErrorFn,
completed?: CompleteFn
): Unsubscribe | undefined;
_agentRecaptchaConfig: RecaptchaConfig | null;
_tenantRecaptchaConfigs: Record<string, RecaptchaConfig>;
_projectPasswordPolicy: PasswordPolicy | null;
Expand Down
Loading