Skip to content

Commit

Permalink
[Auth] heartbeat implementation (#6033)
Browse files Browse the repository at this point in the history
* Auth heartbeat implementation

* Undo worker commenting out

* Changeset
  • Loading branch information
sam-gc authored Mar 16, 2022
1 parent e987dac commit 7405e7d
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 17 deletions.
6 changes: 6 additions & 0 deletions .changeset/neat-olives-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@firebase/auth-compat": patch
"@firebase/auth": patch
---

Heartbeat
11 changes: 8 additions & 3 deletions packages/auth-compat/src/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import sinonChai from 'sinon-chai';
import { Auth } from './auth';
import { CompatPopupRedirectResolver } from './popup_redirect';
import * as platform from './platform';
import { FAKE_HEARTBEAT_CONTROLLER_PROVIDER } from '../test/helpers/helpers';

use(sinonChai);

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

beforeEach(() => {
app = { options: { apiKey: 'api-key' } } as FirebaseApp;
underlyingAuth = new exp.AuthImpl(app, {
apiKey: 'api-key'
} as exp.ConfigInternal);
underlyingAuth = new exp.AuthImpl(
app,
FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
{
apiKey: 'api-key'
} as exp.ConfigInternal
);
sinon.stub(underlyingAuth, '_initializeWithPersistence');

providerStub = sinon.createStubInstance(Provider);
Expand Down
3 changes: 2 additions & 1 deletion packages/auth-compat/src/popup_redirect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import * as exp from '@firebase/auth/internal';
import * as platform from './platform';
import { CompatPopupRedirectResolver } from './popup_redirect';
import { FirebaseApp } from '@firebase/app-compat';
import { FAKE_HEARTBEAT_CONTROLLER_PROVIDER } from '../test/helpers/helpers';

use(sinonChai);

Expand All @@ -41,7 +42,7 @@ describe('popup_redirect/CompatPopupRedirectResolver', () => {
beforeEach(() => {
compatResolver = new CompatPopupRedirectResolver();
const app = { options: { apiKey: 'api-key' } } as FirebaseApp;
auth = new exp.AuthImpl(app, {
auth = new exp.AuthImpl(app, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
apiKey: 'api-key'
} as exp.ConfigInternal);
});
Expand Down
8 changes: 8 additions & 0 deletions packages/auth-compat/test/helpers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import * as sinon from 'sinon';
import firebase from '@firebase/app-compat';
import { Provider } from '@firebase/component';
import '../..';

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

// Heartbeat is fully tested in core auth impl
export const FAKE_HEARTBEAT_CONTROLLER_PROVIDER = {
getImmediate(): undefined {
return undefined;
}
} as unknown as Provider<'heartbeat'>;

export function initializeTestInstance(): void {
firebase.initializeApp(getAppConfig());
const stub = stubConsoleToSilenceEmulatorWarnings();
Expand Down
3 changes: 2 additions & 1 deletion packages/auth/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export const enum HttpHeader {
CONTENT_TYPE = 'Content-Type',
X_FIREBASE_LOCALE = 'X-Firebase-Locale',
X_CLIENT_VERSION = 'X-Client-Version',
X_FIREBASE_GMPID = 'X-Firebase-gmpid'
X_FIREBASE_GMPID = 'X-Firebase-gmpid',
X_FIREBASE_CLIENT = 'X-Firebase-Client',
}

export const enum Endpoint {
Expand Down
29 changes: 26 additions & 3 deletions packages/auth/src/core/auth/auth_impl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import sinonChai from 'sinon-chai';
import { FirebaseApp } from '@firebase/app';
import { FirebaseError } from '@firebase/util';

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

beforeEach(async () => {
persistenceStub = sinon.stub(_getInstance(inMemoryPersistence));
const authImpl = new AuthImpl(FAKE_APP, {
const authImpl = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
apiKey: FAKE_APP.options.apiKey!,
apiHost: DefaultConfig.API_HOST,
apiScheme: DefaultConfig.API_SCHEME,
Expand Down Expand Up @@ -431,7 +431,7 @@ describe('core/auth/auth_impl', () => {
});

it('prevents initialization from completing', async () => {
const authImpl = new AuthImpl(FAKE_APP, {
const authImpl = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
apiKey: FAKE_APP.options.apiKey!,
apiHost: DefaultConfig.API_HOST,
apiScheme: DefaultConfig.API_SCHEME,
Expand Down Expand Up @@ -474,6 +474,29 @@ describe('core/auth/auth_impl', () => {
'X-Client-Version': 'v',
'X-Firebase-gmpid': 'app-id',
});
delete auth.app.options.appId;
});

it('adds the heartbeat if available', async () => {
sinon.stub(FAKE_HEARTBEAT_CONTROLLER, 'getHeartbeatsHeader').returns(Promise.resolve('heartbeat'));
expect(await auth._getAdditionalHeaders()).to.eql({
'X-Client-Version': 'v',
'X-Firebase-Client': 'heartbeat',
});
});

it('does not add heartbeat if none returned', async () => {
sinon.stub(FAKE_HEARTBEAT_CONTROLLER, 'getHeartbeatsHeader').returns(Promise.resolve(''));
expect(await auth._getAdditionalHeaders()).to.eql({
'X-Client-Version': 'v',
});
});

it('does not add heartbeat if controller unavailable', async () => {
sinon.stub(FAKE_HEARTBEAT_CONTROLLER_PROVIDER, 'getImmediate').returns(undefined as any);
expect(await auth._getAdditionalHeaders()).to.eql({
'X-Client-Version': 'v',
});
});
});
});
11 changes: 11 additions & 0 deletions packages/auth/src/core/auth/auth_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

import { _FirebaseService, FirebaseApp } from '@firebase/app';
import { Provider } from '@firebase/component';
import {
Auth,
AuthErrorMap,
Expand Down Expand Up @@ -103,6 +104,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService {

constructor(
public readonly app: FirebaseApp,
private readonly heartbeatServiceProvider: Provider<'heartbeat'>,
public readonly config: ConfigInternal
) {
this.name = app.name;
Expand Down Expand Up @@ -583,9 +585,18 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
const headers: Record<string, string> = {
[HttpHeader.X_CLIENT_VERSION]: this.clientVersion,
};

if (this.app.options.appId) {
headers[HttpHeader.X_FIREBASE_GMPID] = this.app.options.appId;
}

// If the heartbeat service exists, add the heartbeat string
const heartbeatsHeader = await this.heartbeatServiceProvider.getImmediate({
optional: true,
})?.getHeartbeatsHeader();
if (heartbeatsHeader) {
headers[HttpHeader.X_FIREBASE_CLIENT] = heartbeatsHeader;
}
return headers;
}
}
Expand Down
9 changes: 5 additions & 4 deletions packages/auth/src/core/auth/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { _registerComponent, registerVersion } from '@firebase/app';
import {
Component,
ComponentType,
InstantiationMode
InstantiationMode,
} from '@firebase/component';

import { name, version } from '../../../package.json';
Expand Down Expand Up @@ -61,8 +61,9 @@ export function registerAuth(clientPlatform: ClientPlatform): void {
_ComponentName.AUTH,
(container, { options: deps }: { options?: Dependencies }) => {
const app = container.getProvider('app').getImmediate()!;
const heartbeatServiceProvider = container.getProvider<'heartbeat'>('heartbeat');
const { apiKey, authDomain } = app.options;
return (app => {
return ((app, heartbeatServiceProvider) => {
_assert(
apiKey && !apiKey.includes(':'),
AuthErrorCode.INVALID_API_KEY,
Expand All @@ -82,11 +83,11 @@ export function registerAuth(clientPlatform: ClientPlatform): void {
sdkClientVersion: _getClientVersion(clientPlatform)
};

const authInstance = new AuthImpl(app, config);
const authInstance = new AuthImpl(app, heartbeatServiceProvider, config);
_initializeAuthInstance(authInstance, deps);

return authInstance;
})(app);
})(app, heartbeatServiceProvider);
},
ComponentType.PUBLIC
)
Expand Down
8 changes: 4 additions & 4 deletions packages/auth/src/platform_browser/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
} from '../model/public_types';
import { OperationType } from '../model/enums';

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

beforeEach(async () => {
persistenceStub = sinon.stub(_getInstance(inMemoryPersistence));
const authImpl = new AuthImpl(FAKE_APP, {
const authImpl = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
apiKey: FAKE_APP.options.apiKey!,
apiHost: DefaultConfig.API_HOST,
apiScheme: DefaultConfig.API_SCHEME,
Expand Down Expand Up @@ -132,7 +132,7 @@ describe('core/auth/initializeAuth', () => {
popupRedirectResolver?: PopupRedirectResolver,
authDomain = FAKE_APP.options.authDomain
): Promise<Auth> {
const auth = new AuthImpl(FAKE_APP, {
const auth = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
apiKey: FAKE_APP.options.apiKey!,
apiHost: DefaultConfig.API_HOST,
apiScheme: DefaultConfig.API_SCHEME,
Expand Down Expand Up @@ -359,7 +359,7 @@ describe('core/auth/initializeAuth', () => {

// Manually initialize auth to make sure no error is thrown,
// since the _initializeAuthInstance function floats
const auth = new AuthImpl(FAKE_APP, {
const auth = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
apiKey: FAKE_APP.options.apiKey!,
apiHost: DefaultConfig.API_HOST,
apiScheme: DefaultConfig.API_SCHEME,
Expand Down
13 changes: 12 additions & 1 deletion packages/auth/test/helpers/mock_auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

import { FirebaseApp } from '@firebase/app';
import { Provider } from '@firebase/component';
import { PopupRedirectResolver } from '../../src/model/public_types';
import { debugErrorMap } from '../../src';

Expand Down Expand Up @@ -44,6 +45,16 @@ const FAKE_APP: FirebaseApp = {
automaticDataCollectionEnabled: false
};

export const FAKE_HEARTBEAT_CONTROLLER = {
getHeartbeatsHeader: async () => '',
};

export const FAKE_HEARTBEAT_CONTROLLER_PROVIDER: Provider<'heartbeat'> = {
getImmediate(): typeof FAKE_HEARTBEAT_CONTROLLER {
return FAKE_HEARTBEAT_CONTROLLER;
}
} as unknown as Provider<'heartbeat'>;

export class MockPersistenceLayer extends InMemoryPersistence {
lastObjectSet: PersistedBlob | null = null;

Expand All @@ -62,7 +73,7 @@ export async function testAuth(
popupRedirectResolver?: PopupRedirectResolver,
persistence = new MockPersistenceLayer()
): Promise<TestAuth> {
const auth: TestAuth = new AuthImpl(FAKE_APP, {
const auth: TestAuth = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
apiKey: TEST_KEY,
authDomain: TEST_AUTH_DOMAIN,
apiHost: TEST_HOST,
Expand Down

0 comments on commit 7405e7d

Please sign in to comment.