Skip to content

Commit c191029

Browse files
committed
Add initializeAnalytics()
1 parent 5e7c013 commit c191029

File tree

7 files changed

+127
-44
lines changed

7 files changed

+127
-44
lines changed

packages-exp/analytics-exp/src/api.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { _getProvider, FirebaseApp } from '@firebase/app-exp';
2121
import {
2222
Analytics,
2323
AnalyticsCallOptions,
24+
AnalyticsOptions,
2425
CustomParams,
2526
EventNameString,
2627
EventParams
@@ -36,6 +37,7 @@ import { ANALYTICS_TYPE } from './constants';
3637
import {
3738
AnalyticsService,
3839
initializationPromisesMap,
40+
_initializeAnalyticsForApp,
3941
wrappedGtagFunction
4042
} from './factory';
4143
import { logger } from './logger';
@@ -68,7 +70,42 @@ export function getAnalytics(app: FirebaseApp): Analytics {
6870
app,
6971
ANALYTICS_TYPE
7072
);
73+
74+
if (analyticsProvider.isInitialized()) {
75+
return analyticsProvider.getImmediate();
76+
}
77+
78+
return initializeAnalytics(app);
79+
}
80+
81+
/**
82+
* Returns a Firebase Analytics instance for the given app.
83+
*
84+
* @public
85+
*
86+
* @param app - The FirebaseApp to use.
87+
*/
88+
export function initializeAnalytics(
89+
app: FirebaseApp,
90+
options?: AnalyticsOptions
91+
): Analytics {
92+
// Dependencies
93+
const analyticsProvider: Provider<'analytics-exp'> = _getProvider(
94+
app,
95+
ANALYTICS_TYPE
96+
);
97+
if (analyticsProvider.isInitialized()) {
98+
return analyticsProvider.getImmediate();
99+
// TODO: Throw an analytics specific error.
100+
// _fail(analyticsInstance, AuthErrorCode.ALREADY_INITIALIZED);
101+
}
71102
const analyticsInstance = analyticsProvider.getImmediate();
103+
// do init settings stuff here
104+
const installations = _getProvider(
105+
app,
106+
'installations-exp-internal'
107+
).getImmediate();
108+
_initializeAnalyticsForApp(app, installations, options);
72109
return analyticsInstance;
73110
}
74111

packages-exp/analytics-exp/src/factory.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { SettingsOptions, Analytics } from './public-types';
18+
import { SettingsOptions, Analytics, AnalyticsOptions } from './public-types';
1919
import { Gtag, DynamicConfig, MinimalDynamicConfig } from './types';
2020
import { getOrCreateDataLayer, wrapOrCreateGtag } from './helpers';
2121
import { AnalyticsError, ERROR_FACTORY } from './errors';
2222
import { _FirebaseInstallationsInternal } from '@firebase/installations-types-exp';
2323
import { areCookiesEnabled, isBrowserExtension } from '@firebase/util';
24-
import { initializeAnalytics } from './initialize-analytics';
24+
import { _initializeAnalytics } from './initialize-analytics';
2525
import { logger } from './logger';
2626
import { FirebaseApp, _FirebaseService } from '@firebase/app-exp';
2727

@@ -174,10 +174,7 @@ function warnOnBrowserContextMismatch(): void {
174174
* Analytics instance factory.
175175
* @internal
176176
*/
177-
export function factory(
178-
app: FirebaseApp,
179-
installations: _FirebaseInstallationsInternal
180-
): AnalyticsService {
177+
export function factory(app: FirebaseApp): AnalyticsService {
181178
warnOnBrowserContextMismatch();
182179
const appId = app.options.appId;
183180
if (!appId) {
@@ -218,18 +215,29 @@ export function factory(
218215

219216
globalInitDone = true;
220217
}
218+
219+
const analyticsInstance: AnalyticsService = new AnalyticsService(app);
220+
return analyticsInstance;
221+
}
222+
223+
/**
224+
* Starts async initializations for this app.
225+
*/
226+
export function _initializeAnalyticsForApp(
227+
app: FirebaseApp,
228+
installations: _FirebaseInstallationsInternal,
229+
options?: AnalyticsOptions
230+
): void {
221231
// Async but non-blocking.
222232
// This map reflects the completion state of all promises for each appId.
223-
initializationPromisesMap[appId] = initializeAnalytics(
233+
// Factory will have thrown if options has no appId.
234+
initializationPromisesMap[app.options.appId!] = _initializeAnalytics(
224235
app,
225236
dynamicConfigPromisesList,
226237
measurementIdToAppId,
227238
installations,
228239
gtagCoreFunction,
229-
dataLayerName
240+
dataLayerName,
241+
options
230242
);
231-
232-
const analyticsInstance: AnalyticsService = new AnalyticsService(app);
233-
234-
return analyticsInstance;
235243
}

packages-exp/analytics-exp/src/index.test.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ import {
3434
AnalyticsService,
3535
getGlobalVars,
3636
resetGlobalVars,
37-
factory as analyticsFactory
37+
factory as analyticsFactory,
38+
_initializeAnalyticsForApp
3839
} from './factory';
3940
import { _FirebaseInstallationsInternal } from '@firebase/installations-types-exp';
4041

@@ -89,15 +90,11 @@ describe('FirebaseAnalytics instance tests', () => {
8990

9091
it('Throws if no appId in config', () => {
9192
const app = getFakeApp({ apiKey: fakeAppParams.apiKey });
92-
expect(() => analyticsFactory(app, fakeInstallations)).to.throw(
93-
AnalyticsError.NO_APP_ID
94-
);
93+
expect(() => analyticsFactory(app)).to.throw(AnalyticsError.NO_APP_ID);
9594
});
9695
it('Throws if no apiKey or measurementId in config', () => {
9796
const app = getFakeApp({ appId: fakeAppParams.appId });
98-
expect(() => analyticsFactory(app, fakeInstallations)).to.throw(
99-
AnalyticsError.NO_API_KEY
100-
);
97+
expect(() => analyticsFactory(app)).to.throw(AnalyticsError.NO_API_KEY);
10198
});
10299
it('Warns if config has no apiKey but does have a measurementId', async () => {
103100
// Since this is a warning and doesn't block the rest of initialization
@@ -110,7 +107,8 @@ describe('FirebaseAnalytics instance tests', () => {
110107
measurementId: fakeMeasurementId
111108
});
112109
stubIdbOpen();
113-
analyticsFactory(app, fakeInstallations);
110+
analyticsFactory(app);
111+
_initializeAnalyticsForApp(app, fakeInstallations);
114112
// Successfully resolves fake IDB open request.
115113
fakeRequest.onsuccess();
116114
// Lets async IDB validation process complete.
@@ -128,7 +126,7 @@ describe('FirebaseAnalytics instance tests', () => {
128126
it('Throws if creating an instance with already-used appId', () => {
129127
const app = getFakeApp(fakeAppParams);
130128
resetGlobalVars(false, { [fakeAppParams.appId]: Promise.resolve() });
131-
expect(() => analyticsFactory(app, fakeInstallations)).to.throw(
129+
expect(() => analyticsFactory(app)).to.throw(
132130
AnalyticsError.ALREADY_EXISTS
133131
);
134132
});
@@ -149,7 +147,8 @@ describe('FirebaseAnalytics instance tests', () => {
149147
window['dataLayer'] = [];
150148
stubFetch(200, { measurementId: fakeMeasurementId });
151149
stubIdbOpen();
152-
analyticsInstance = analyticsFactory(app, fakeInstallations);
150+
analyticsInstance = analyticsFactory(app);
151+
_initializeAnalyticsForApp(app, fakeInstallations);
153152
// Successfully resolves fake IDB open request.
154153
fakeRequest.onsuccess();
155154
});
@@ -223,7 +222,8 @@ describe('FirebaseAnalytics instance tests', () => {
223222
});
224223
it('Warns on initialization if cookies not available', async () => {
225224
cookieStub = stub(navigator, 'cookieEnabled').value(false);
226-
analyticsInstance = analyticsFactory(app, fakeInstallations);
225+
analyticsInstance = analyticsFactory(app);
226+
_initializeAnalyticsForApp(app, fakeInstallations);
227227
// Successfully resolves fake IDB open request.
228228
fakeRequest.onsuccess();
229229
expect(warnStub.args[0][1]).to.include(
@@ -234,7 +234,8 @@ describe('FirebaseAnalytics instance tests', () => {
234234
});
235235
it('Warns on initialization if in browser extension', async () => {
236236
window.chrome = { runtime: { id: 'blah' } };
237-
analyticsInstance = analyticsFactory(app, fakeInstallations);
237+
analyticsInstance = analyticsFactory(app);
238+
_initializeAnalyticsForApp(app, fakeInstallations);
238239
// Successfully resolves fake IDB open request.
239240
fakeRequest.onsuccess();
240241
expect(warnStub.args[0][1]).to.include(
@@ -245,7 +246,8 @@ describe('FirebaseAnalytics instance tests', () => {
245246
});
246247
it('Warns on logEvent if indexedDB API not available', async () => {
247248
const idbStub = stub(window, 'indexedDB').value(undefined);
248-
analyticsInstance = analyticsFactory(app, fakeInstallations);
249+
analyticsInstance = analyticsFactory(app);
250+
_initializeAnalyticsForApp(app, fakeInstallations);
249251
logEvent(analyticsInstance, 'add_payment_info', {
250252
currency: 'USD'
251253
});
@@ -265,7 +267,8 @@ describe('FirebaseAnalytics instance tests', () => {
265267
it('Warns on logEvent if indexedDB.open() not allowed', async () => {
266268
idbOpenStub.restore();
267269
idbOpenStub = stub(indexedDB, 'open').throws('idb open error test');
268-
analyticsInstance = analyticsFactory(app, fakeInstallations);
270+
analyticsInstance = analyticsFactory(app);
271+
_initializeAnalyticsForApp(app, fakeInstallations);
269272
logEvent(analyticsInstance, 'add_payment_info', {
270273
currency: 'USD'
271274
});
@@ -303,7 +306,8 @@ describe('FirebaseAnalytics instance tests', () => {
303306
});
304307
stubIdbOpen();
305308
stubFetch(200, { measurementId: fakeMeasurementId });
306-
analyticsInstance = analyticsFactory(app, fakeInstallations);
309+
analyticsInstance = analyticsFactory(app);
310+
_initializeAnalyticsForApp(app, fakeInstallations);
307311
// Successfully resolves fake IDB open request.
308312
fakeRequest.onsuccess();
309313
});
@@ -349,7 +353,8 @@ describe('FirebaseAnalytics instance tests', () => {
349353
fakeInstallations = getFakeInstallations();
350354
stubFetch(200, {});
351355
stubIdbOpen();
352-
analyticsInstance = analyticsFactory(app, fakeInstallations);
356+
analyticsInstance = analyticsFactory(app);
357+
_initializeAnalyticsForApp(app, fakeInstallations);
353358

354359
const { initializationPromisesMap } = getGlobalVars();
355360
// Successfully resolves fake IDB open request.

packages-exp/analytics-exp/src/index.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,8 @@ function registerAnalytics(): void {
4343
container => {
4444
// getImmediate for FirebaseApp will always succeed
4545
const app = container.getProvider('app-exp').getImmediate();
46-
const installations = container
47-
.getProvider('installations-exp-internal')
48-
.getImmediate();
4946

50-
return factory(app, installations);
47+
return factory(app);
5148
},
5249
ComponentType.PUBLIC
5350
)
@@ -62,6 +59,8 @@ function registerAnalytics(): void {
6259
function internalFactory(
6360
container: ComponentContainer
6461
): FirebaseAnalyticsInternal {
62+
//TODO: initialization fetches aren't in factory anymore so it might need to be
63+
// called here.
6564
try {
6665
const analytics = container.getProvider(ANALYTICS_TYPE).getImmediate();
6766
return {

packages-exp/analytics-exp/src/initialize-analytics.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import { expect } from 'chai';
1919
import { SinonStub, stub } from 'sinon';
2020
import '../testing/setup';
21-
import { initializeAnalytics } from './initialize-analytics';
21+
import { _initializeAnalytics } from './initialize-analytics';
2222
import {
2323
getFakeApp,
2424
getFakeInstallations
@@ -65,7 +65,7 @@ describe('initializeIds()', () => {
6565
});
6666
it('gets FID and measurement ID and calls gtag config with them', async () => {
6767
stubFetch();
68-
await initializeAnalytics(
68+
await _initializeAnalytics(
6969
app,
7070
dynamicPromisesList,
7171
measurementIdToAppId,
@@ -81,7 +81,7 @@ describe('initializeIds()', () => {
8181
});
8282
it('puts dynamic fetch promise into dynamic promises list', async () => {
8383
stubFetch();
84-
await initializeAnalytics(
84+
await _initializeAnalytics(
8585
app,
8686
dynamicPromisesList,
8787
measurementIdToAppId,
@@ -95,7 +95,7 @@ describe('initializeIds()', () => {
9595
});
9696
it('puts dynamically fetched measurementId into lookup table', async () => {
9797
stubFetch();
98-
await initializeAnalytics(
98+
await _initializeAnalytics(
9999
app,
100100
dynamicPromisesList,
101101
measurementIdToAppId,
@@ -108,7 +108,7 @@ describe('initializeIds()', () => {
108108
it('warns on local/fetched measurement ID mismatch', async () => {
109109
stubFetch();
110110
const consoleStub = stub(console, 'warn');
111-
await initializeAnalytics(
111+
await _initializeAnalytics(
112112
getFakeApp({ ...fakeAppParams, measurementId: 'old-measurement-id' }),
113113
dynamicPromisesList,
114114
measurementIdToAppId,

packages-exp/analytics-exp/src/initialize-analytics.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
} from '@firebase/util';
2828
import { ERROR_FACTORY, AnalyticsError } from './errors';
2929
import { findGtagScriptOnPage, insertScriptTag } from './helpers';
30+
import { AnalyticsOptions } from './public-types';
3031

3132
async function validateIndexedDB(): Promise<boolean> {
3233
if (!isIndexedDBAvailable()) {
@@ -64,15 +65,16 @@ async function validateIndexedDB(): Promise<boolean> {
6465
*
6566
* @returns Measurement ID.
6667
*/
67-
export async function initializeAnalytics(
68+
export async function _initializeAnalytics(
6869
app: FirebaseApp,
6970
dynamicConfigPromisesList: Array<
7071
Promise<DynamicConfig | MinimalDynamicConfig>
7172
>,
7273
measurementIdToAppId: { [key: string]: string },
7374
installations: _FirebaseInstallationsInternal,
7475
gtagCore: Gtag,
75-
dataLayerName: string
76+
dataLayerName: string,
77+
options?: AnalyticsOptions
7678
): Promise<string> {
7779
const dynamicConfigPromise = fetchDynamicConfigWithRetry(app);
7880
// Once fetched, map measurementIds to appId, for ease of lookup in wrapped gtag function.
@@ -122,11 +124,13 @@ export async function initializeAnalytics(
122124
// eslint-disable-next-line @typescript-eslint/no-explicit-any
123125
(gtagCore as any)('js', new Date());
124126

125-
const configProperties: { [key: string]: string | boolean } = {
126-
// guard against developers accidentally setting properties with prefix `firebase_`
127-
[ORIGIN_KEY]: 'firebase',
128-
update: true
129-
};
127+
// User config added first. We don't want users to accidentally overwrite
128+
// base Firebase config properties.
129+
const configProperties: Record<string, unknown> = options?.config ?? {};
130+
131+
// guard against developers accidentally setting properties with prefix `firebase_`
132+
configProperties[ORIGIN_KEY] = 'firebase';
133+
configProperties.update = true;
130134

131135
if (fid != null) {
132136
configProperties[GA_FID_KEY] = fid;

packages-exp/analytics-exp/src/public-types.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,35 @@
1717

1818
import { FirebaseApp } from '@firebase/app-exp';
1919

20+
/**
21+
* A set of common Analytics config settings recognized by
22+
* gtag.
23+
*/
24+
export interface GtagConfigParams {
25+
'send_page_view'?: boolean;
26+
'page_title'?: string;
27+
'page_path'?: string;
28+
'page_location'?: string;
29+
'cookie_domain'?: string;
30+
'cookie_expires'?: number;
31+
'cookie_prefix'?: string;
32+
'cookie_update'?: boolean;
33+
'cookie_flags'?: string;
34+
'allow_google_signals?': boolean;
35+
'allow_ad_personalization_signals'?: boolean;
36+
'link_attribution'?: boolean;
37+
'anonymize_ip'?: boolean;
38+
'custom_map'?: { [key: string]: unknown };
39+
[key: string]: unknown;
40+
}
41+
42+
/**
43+
* Analytics initialization options.
44+
*/
45+
export interface AnalyticsOptions {
46+
config: GtagConfigParams | EventParams;
47+
}
48+
2049
/**
2150
* Additional options that can be passed to Firebase Analytics method
2251
* calls such as `logEvent`, `setCurrentScreen`, etc.
@@ -181,5 +210,6 @@ export interface EventParams {
181210
page_title?: string;
182211
page_location?: string;
183212
page_path?: string;
213+
[key: string]: unknown;
184214
}
185215
/* eslint-enable camelcase */

0 commit comments

Comments
 (0)