Skip to content

Commit 7405e7d

Browse files
authored
[Auth] heartbeat implementation (#6033)
* Auth heartbeat implementation * Undo worker commenting out * Changeset
1 parent e987dac commit 7405e7d

File tree

10 files changed

+84
-17
lines changed

10 files changed

+84
-17
lines changed

.changeset/neat-olives-punch.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@firebase/auth-compat": patch
3+
"@firebase/auth": patch
4+
---
5+
6+
Heartbeat

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import sinonChai from 'sinon-chai';
2424
import { Auth } from './auth';
2525
import { CompatPopupRedirectResolver } from './popup_redirect';
2626
import * as platform from './platform';
27+
import { FAKE_HEARTBEAT_CONTROLLER_PROVIDER } from '../test/helpers/helpers';
2728

2829
use(sinonChai);
2930

@@ -41,9 +42,13 @@ describe('auth compat', () => {
4142

4243
beforeEach(() => {
4344
app = { options: { apiKey: 'api-key' } } as FirebaseApp;
44-
underlyingAuth = new exp.AuthImpl(app, {
45-
apiKey: 'api-key'
46-
} as exp.ConfigInternal);
45+
underlyingAuth = new exp.AuthImpl(
46+
app,
47+
FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
48+
{
49+
apiKey: 'api-key'
50+
} as exp.ConfigInternal
51+
);
4752
sinon.stub(underlyingAuth, '_initializeWithPersistence');
4853

4954
providerStub = sinon.createStubInstance(Provider);

packages/auth-compat/src/popup_redirect.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import * as exp from '@firebase/auth/internal';
2222
import * as platform from './platform';
2323
import { CompatPopupRedirectResolver } from './popup_redirect';
2424
import { FirebaseApp } from '@firebase/app-compat';
25+
import { FAKE_HEARTBEAT_CONTROLLER_PROVIDER } from '../test/helpers/helpers';
2526

2627
use(sinonChai);
2728

@@ -41,7 +42,7 @@ describe('popup_redirect/CompatPopupRedirectResolver', () => {
4142
beforeEach(() => {
4243
compatResolver = new CompatPopupRedirectResolver();
4344
const app = { options: { apiKey: 'api-key' } } as FirebaseApp;
44-
auth = new exp.AuthImpl(app, {
45+
auth = new exp.AuthImpl(app, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
4546
apiKey: 'api-key'
4647
} as exp.ConfigInternal);
4748
});

packages/auth-compat/test/helpers/helpers.ts

+8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import * as sinon from 'sinon';
1919
import firebase from '@firebase/app-compat';
20+
import { Provider } from '@firebase/component';
2021
import '../..';
2122

2223
import * as exp from '@firebase/auth/internal';
@@ -26,6 +27,13 @@ import {
2627
} from '../../../auth/test/helpers/integration/settings';
2728
import { resetEmulator } from '../../../auth/test/helpers/integration/emulator_rest_helpers';
2829

30+
// Heartbeat is fully tested in core auth impl
31+
export const FAKE_HEARTBEAT_CONTROLLER_PROVIDER = {
32+
getImmediate(): undefined {
33+
return undefined;
34+
}
35+
} as unknown as Provider<'heartbeat'>;
36+
2937
export function initializeTestInstance(): void {
3038
firebase.initializeApp(getAppConfig());
3139
const stub = stubConsoleToSilenceEmulatorWarnings();

packages/auth/src/api/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ export const enum HttpHeader {
3737
CONTENT_TYPE = 'Content-Type',
3838
X_FIREBASE_LOCALE = 'X-Firebase-Locale',
3939
X_CLIENT_VERSION = 'X-Client-Version',
40-
X_FIREBASE_GMPID = 'X-Firebase-gmpid'
40+
X_FIREBASE_GMPID = 'X-Firebase-gmpid',
41+
X_FIREBASE_CLIENT = 'X-Firebase-Client',
4142
}
4243

4344
export const enum Endpoint {

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

+26-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import sinonChai from 'sinon-chai';
2323
import { FirebaseApp } from '@firebase/app';
2424
import { FirebaseError } from '@firebase/util';
2525

26-
import { testAuth, testUser } from '../../../test/helpers/mock_auth';
26+
import { FAKE_HEARTBEAT_CONTROLLER, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, testAuth, testUser } from '../../../test/helpers/mock_auth';
2727
import { AuthInternal } from '../../model/auth';
2828
import { UserInternal } from '../../model/user';
2929
import { PersistenceInternal } from '../persistence';
@@ -53,7 +53,7 @@ describe('core/auth/auth_impl', () => {
5353

5454
beforeEach(async () => {
5555
persistenceStub = sinon.stub(_getInstance(inMemoryPersistence));
56-
const authImpl = new AuthImpl(FAKE_APP, {
56+
const authImpl = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
5757
apiKey: FAKE_APP.options.apiKey!,
5858
apiHost: DefaultConfig.API_HOST,
5959
apiScheme: DefaultConfig.API_SCHEME,
@@ -431,7 +431,7 @@ describe('core/auth/auth_impl', () => {
431431
});
432432

433433
it('prevents initialization from completing', async () => {
434-
const authImpl = new AuthImpl(FAKE_APP, {
434+
const authImpl = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
435435
apiKey: FAKE_APP.options.apiKey!,
436436
apiHost: DefaultConfig.API_HOST,
437437
apiScheme: DefaultConfig.API_SCHEME,
@@ -474,6 +474,29 @@ describe('core/auth/auth_impl', () => {
474474
'X-Client-Version': 'v',
475475
'X-Firebase-gmpid': 'app-id',
476476
});
477+
delete auth.app.options.appId;
478+
});
479+
480+
it('adds the heartbeat if available', async () => {
481+
sinon.stub(FAKE_HEARTBEAT_CONTROLLER, 'getHeartbeatsHeader').returns(Promise.resolve('heartbeat'));
482+
expect(await auth._getAdditionalHeaders()).to.eql({
483+
'X-Client-Version': 'v',
484+
'X-Firebase-Client': 'heartbeat',
485+
});
486+
});
487+
488+
it('does not add heartbeat if none returned', async () => {
489+
sinon.stub(FAKE_HEARTBEAT_CONTROLLER, 'getHeartbeatsHeader').returns(Promise.resolve(''));
490+
expect(await auth._getAdditionalHeaders()).to.eql({
491+
'X-Client-Version': 'v',
492+
});
493+
});
494+
495+
it('does not add heartbeat if controller unavailable', async () => {
496+
sinon.stub(FAKE_HEARTBEAT_CONTROLLER_PROVIDER, 'getImmediate').returns(undefined as any);
497+
expect(await auth._getAdditionalHeaders()).to.eql({
498+
'X-Client-Version': 'v',
499+
});
477500
});
478501
});
479502
});

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

+11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
import { _FirebaseService, FirebaseApp } from '@firebase/app';
19+
import { Provider } from '@firebase/component';
1920
import {
2021
Auth,
2122
AuthErrorMap,
@@ -103,6 +104,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
103104

104105
constructor(
105106
public readonly app: FirebaseApp,
107+
private readonly heartbeatServiceProvider: Provider<'heartbeat'>,
106108
public readonly config: ConfigInternal
107109
) {
108110
this.name = app.name;
@@ -583,9 +585,18 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
583585
const headers: Record<string, string> = {
584586
[HttpHeader.X_CLIENT_VERSION]: this.clientVersion,
585587
};
588+
586589
if (this.app.options.appId) {
587590
headers[HttpHeader.X_FIREBASE_GMPID] = this.app.options.appId;
588591
}
592+
593+
// If the heartbeat service exists, add the heartbeat string
594+
const heartbeatsHeader = await this.heartbeatServiceProvider.getImmediate({
595+
optional: true,
596+
})?.getHeartbeatsHeader();
597+
if (heartbeatsHeader) {
598+
headers[HttpHeader.X_FIREBASE_CLIENT] = heartbeatsHeader;
599+
}
589600
return headers;
590601
}
591602
}

packages/auth/src/core/auth/register.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { _registerComponent, registerVersion } from '@firebase/app';
1919
import {
2020
Component,
2121
ComponentType,
22-
InstantiationMode
22+
InstantiationMode,
2323
} from '@firebase/component';
2424

2525
import { name, version } from '../../../package.json';
@@ -61,8 +61,9 @@ export function registerAuth(clientPlatform: ClientPlatform): void {
6161
_ComponentName.AUTH,
6262
(container, { options: deps }: { options?: Dependencies }) => {
6363
const app = container.getProvider('app').getImmediate()!;
64+
const heartbeatServiceProvider = container.getProvider<'heartbeat'>('heartbeat');
6465
const { apiKey, authDomain } = app.options;
65-
return (app => {
66+
return ((app, heartbeatServiceProvider) => {
6667
_assert(
6768
apiKey && !apiKey.includes(':'),
6869
AuthErrorCode.INVALID_API_KEY,
@@ -82,11 +83,11 @@ export function registerAuth(clientPlatform: ClientPlatform): void {
8283
sdkClientVersion: _getClientVersion(clientPlatform)
8384
};
8485

85-
const authInstance = new AuthImpl(app, config);
86+
const authInstance = new AuthImpl(app, heartbeatServiceProvider, config);
8687
_initializeAuthInstance(authInstance, deps);
8788

8889
return authInstance;
89-
})(app);
90+
})(app, heartbeatServiceProvider);
9091
},
9192
ComponentType.PUBLIC
9293
)

packages/auth/src/platform_browser/auth.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
} from '../model/public_types';
2929
import { OperationType } from '../model/enums';
3030

31-
import { testAuth, testUser } from '../../test/helpers/mock_auth';
31+
import { FAKE_HEARTBEAT_CONTROLLER_PROVIDER, testAuth, testUser } from '../../test/helpers/mock_auth';
3232
import { AuthImpl, DefaultConfig } from '../core/auth/auth_impl';
3333
import { _initializeAuthInstance } from '../core/auth/initialize';
3434
import { AuthErrorCode } from '../core/errors';
@@ -66,7 +66,7 @@ describe('core/auth/auth_impl', () => {
6666

6767
beforeEach(async () => {
6868
persistenceStub = sinon.stub(_getInstance(inMemoryPersistence));
69-
const authImpl = new AuthImpl(FAKE_APP, {
69+
const authImpl = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
7070
apiKey: FAKE_APP.options.apiKey!,
7171
apiHost: DefaultConfig.API_HOST,
7272
apiScheme: DefaultConfig.API_SCHEME,
@@ -132,7 +132,7 @@ describe('core/auth/initializeAuth', () => {
132132
popupRedirectResolver?: PopupRedirectResolver,
133133
authDomain = FAKE_APP.options.authDomain
134134
): Promise<Auth> {
135-
const auth = new AuthImpl(FAKE_APP, {
135+
const auth = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
136136
apiKey: FAKE_APP.options.apiKey!,
137137
apiHost: DefaultConfig.API_HOST,
138138
apiScheme: DefaultConfig.API_SCHEME,
@@ -359,7 +359,7 @@ describe('core/auth/initializeAuth', () => {
359359

360360
// Manually initialize auth to make sure no error is thrown,
361361
// since the _initializeAuthInstance function floats
362-
const auth = new AuthImpl(FAKE_APP, {
362+
const auth = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
363363
apiKey: FAKE_APP.options.apiKey!,
364364
apiHost: DefaultConfig.API_HOST,
365365
apiScheme: DefaultConfig.API_SCHEME,

packages/auth/test/helpers/mock_auth.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
import { FirebaseApp } from '@firebase/app';
19+
import { Provider } from '@firebase/component';
1920
import { PopupRedirectResolver } from '../../src/model/public_types';
2021
import { debugErrorMap } from '../../src';
2122

@@ -44,6 +45,16 @@ const FAKE_APP: FirebaseApp = {
4445
automaticDataCollectionEnabled: false
4546
};
4647

48+
export const FAKE_HEARTBEAT_CONTROLLER = {
49+
getHeartbeatsHeader: async () => '',
50+
};
51+
52+
export const FAKE_HEARTBEAT_CONTROLLER_PROVIDER: Provider<'heartbeat'> = {
53+
getImmediate(): typeof FAKE_HEARTBEAT_CONTROLLER {
54+
return FAKE_HEARTBEAT_CONTROLLER;
55+
}
56+
} as unknown as Provider<'heartbeat'>;
57+
4758
export class MockPersistenceLayer extends InMemoryPersistence {
4859
lastObjectSet: PersistedBlob | null = null;
4960

@@ -62,7 +73,7 @@ export async function testAuth(
6273
popupRedirectResolver?: PopupRedirectResolver,
6374
persistence = new MockPersistenceLayer()
6475
): Promise<TestAuth> {
65-
const auth: TestAuth = new AuthImpl(FAKE_APP, {
76+
const auth: TestAuth = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
6677
apiKey: TEST_KEY,
6778
authDomain: TEST_AUTH_DOMAIN,
6879
apiHost: TEST_HOST,

0 commit comments

Comments
 (0)