Skip to content

Commit fc48310

Browse files
authored
Merge aa563ba into 4bdc9f3
2 parents 4bdc9f3 + aa563ba commit fc48310

File tree

9 files changed

+260
-48
lines changed

9 files changed

+260
-48
lines changed

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

+32
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ export interface AnalyticsCallOptions {
1616
global: boolean;
1717
}
1818

19+
// @public
20+
export interface AnalyticsOptions {
21+
config?: GtagConfigParams | EventParams;
22+
}
23+
1924
// @public
2025
export interface ControlParams {
2126
// (undocumented)
@@ -45,6 +50,8 @@ export type EventNameString = 'add_payment_info' | 'add_shipping_info' | 'add_to
4550

4651
// @public
4752
export interface EventParams {
53+
// (undocumented)
54+
[key: string]: unknown;
4855
// (undocumented)
4956
affiliation?: string;
5057
// (undocumented)
@@ -110,6 +117,31 @@ export interface EventParams {
110117
// @public
111118
export function getAnalytics(app?: FirebaseApp): Analytics;
112119

120+
// @public
121+
export interface GtagConfigParams {
122+
'allow_google_signals?': boolean;
123+
// (undocumented)
124+
[key: string]: unknown;
125+
'allow_ad_personalization_signals'?: boolean;
126+
'anonymize_ip'?: boolean;
127+
'cookie_domain'?: string;
128+
'cookie_expires'?: number;
129+
'cookie_flags'?: string;
130+
'cookie_prefix'?: string;
131+
'cookie_update'?: boolean;
132+
'custom_map'?: {
133+
[key: string]: unknown;
134+
};
135+
'link_attribution'?: boolean;
136+
'page_location'?: string;
137+
'page_path'?: string;
138+
'page_title'?: string;
139+
'send_page_view'?: boolean;
140+
}
141+
142+
// @public
143+
export function initializeAnalytics(app: FirebaseApp, options?: AnalyticsOptions): Analytics;
144+
113145
// @public
114146
export function isSupported(): Promise<boolean>;
115147

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

+30-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { _getProvider, FirebaseApp, getApp } from '@firebase/app-exp';
2121
import {
2222
Analytics,
2323
AnalyticsCallOptions,
24+
AnalyticsOptions,
2425
CustomParams,
2526
EventNameString,
2627
EventParams
@@ -47,6 +48,7 @@ import {
4748
setUserProperties as internalSetUserProperties,
4849
setAnalyticsCollectionEnabled as internalSetAnalyticsCollectionEnabled
4950
} from './functions';
51+
import { ERROR_FACTORY, AnalyticsError } from './errors';
5052

5153
export { settings } from './factory';
5254

@@ -70,7 +72,34 @@ export function getAnalytics(app: FirebaseApp = getApp()): Analytics {
7072
app,
7173
ANALYTICS_TYPE
7274
);
73-
const analyticsInstance = analyticsProvider.getImmediate();
75+
76+
if (analyticsProvider.isInitialized()) {
77+
return analyticsProvider.getImmediate();
78+
}
79+
80+
return initializeAnalytics(app);
81+
}
82+
83+
/**
84+
* Returns a Firebase Analytics instance for the given app.
85+
*
86+
* @public
87+
*
88+
* @param app - The FirebaseApp to use.
89+
*/
90+
export function initializeAnalytics(
91+
app: FirebaseApp,
92+
options: AnalyticsOptions = {}
93+
): Analytics {
94+
// Dependencies
95+
const analyticsProvider: Provider<'analytics-exp'> = _getProvider(
96+
app,
97+
ANALYTICS_TYPE
98+
);
99+
if (analyticsProvider.isInitialized()) {
100+
throw ERROR_FACTORY.create(AnalyticsError.ALREADY_INITIALIZED);
101+
}
102+
const analyticsInstance = analyticsProvider.initialize({ options });
74103
return analyticsInstance;
75104
}
76105

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

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { ErrorFactory, ErrorMap } from '@firebase/util';
2020
export const enum AnalyticsError {
2121
ALREADY_EXISTS = 'already-exists',
2222
ALREADY_INITIALIZED = 'already-initialized',
23+
ALREADY_INITIALIZED_SETTINGS = 'already-initialized-settings',
2324
INTEROP_COMPONENT_REG_FAILED = 'interop-component-reg-failed',
2425
INVALID_ANALYTICS_CONTEXT = 'invalid-analytics-context',
2526
INDEXEDDB_UNAVAILABLE = 'indexeddb-unavailable',
@@ -35,6 +36,10 @@ const ERRORS: ErrorMap<AnalyticsError> = {
3536
' already exists. ' +
3637
'Only one Firebase Analytics instance can be created for each appId.',
3738
[AnalyticsError.ALREADY_INITIALIZED]:
39+
'Firebase Analytics has already been initialized. ' +
40+
'initializeAnalytics() must only be called once. getAnalytics() can be used ' +
41+
'to get a reference to the already-intialized instance.',
42+
[AnalyticsError.ALREADY_INITIALIZED_SETTINGS]:
3843
'Firebase Analytics has already been initialized.' +
3944
'settings() must be called before initializing any Analytics instance' +
4045
'or it will have no effect.',

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
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';
@@ -176,7 +176,8 @@ function warnOnBrowserContextMismatch(): void {
176176
*/
177177
export function factory(
178178
app: FirebaseApp,
179-
installations: _FirebaseInstallationsInternal
179+
installations: _FirebaseInstallationsInternal,
180+
options?: AnalyticsOptions
180181
): AnalyticsService {
181182
warnOnBrowserContextMismatch();
182183
const appId = app.options.appId;
@@ -226,7 +227,8 @@ export function factory(
226227
measurementIdToAppId,
227228
installations,
228229
gtagCoreFunction,
229-
dataLayerName
230+
dataLayerName,
231+
options
230232
);
231233

232234
const analyticsInstance: AnalyticsService = new AnalyticsService(app);

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import { ANALYTICS_TYPE } from './constants';
2828
import {
2929
Component,
3030
ComponentType,
31-
ComponentContainer
31+
ComponentContainer,
32+
InstanceFactoryOptions
3233
} from '@firebase/component';
3334
import { ERROR_FACTORY, AnalyticsError } from './errors';
3435
import { logEvent } from './api';
@@ -46,14 +47,14 @@ function registerAnalytics(): void {
4647
_registerComponent(
4748
new Component(
4849
ANALYTICS_TYPE,
49-
container => {
50+
(container, { options: analyticsOptions }: InstanceFactoryOptions) => {
5051
// getImmediate for FirebaseApp will always succeed
5152
const app = container.getProvider('app-exp').getImmediate();
5253
const installations = container
5354
.getProvider('installations-exp-internal')
5455
.getImmediate();
5556

56-
return factory(app, installations);
57+
return factory(app, installations, analyticsOptions);
5758
},
5859
ComponentType.PUBLIC
5960
)

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

+19-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function stubFetch(): void {
4848
fetchStub.returns(Promise.resolve(mockResponse));
4949
}
5050

51-
describe('initializeIds()', () => {
51+
describe('initializeAnalytics()', () => {
5252
const gtagStub: SinonStub = stub();
5353
const dynamicPromisesList: Array<Promise<DynamicConfig>> = [];
5454
const measurementIdToAppId: { [key: string]: string } = {};
@@ -79,6 +79,24 @@ describe('initializeIds()', () => {
7979
update: true
8080
});
8181
});
82+
it('calls gtag config with options if provided', async () => {
83+
stubFetch();
84+
await initializeAnalytics(
85+
app,
86+
dynamicPromisesList,
87+
measurementIdToAppId,
88+
fakeInstallations,
89+
gtagStub,
90+
'dataLayer',
91+
{ config: { 'send_page_view': false } }
92+
);
93+
expect(gtagStub).to.be.calledWith(GtagCommand.CONFIG, fakeMeasurementId, {
94+
'firebase_id': fakeFid,
95+
'origin': 'firebase',
96+
update: true,
97+
'send_page_view': false
98+
});
99+
});
82100
it('puts dynamic fetch promise into dynamic promises list', async () => {
83101
stubFetch();
84102
await initializeAnalytics(

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

+9-6
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()) {
@@ -72,7 +73,8 @@ export async function initializeAnalytics(
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.
@@ -121,12 +123,13 @@ export async function initializeAnalytics(
121123
// We keep it together with other initialization logic for better code structure.
122124
// eslint-disable-next-line @typescript-eslint/no-explicit-any
123125
(gtagCore as any)('js', new Date());
126+
// User config added first. We don't want users to accidentally overwrite
127+
// base Firebase config properties.
128+
const configProperties: Record<string, unknown> = options?.config ?? {};
124129

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-
};
130+
// guard against developers accidentally setting properties with prefix `firebase_`
131+
configProperties[ORIGIN_KEY] = 'firebase';
132+
configProperties.update = true;
130133

131134
if (fid != null) {
132135
configProperties[GA_FID_KEY] = fid;

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

+98-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,98 @@
1717

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

20+
/**
21+
* A set of common Analytics config settings recognized by
22+
* gtag.
23+
* @public
24+
*/
25+
export interface GtagConfigParams {
26+
/**
27+
* Whether or not a page view should be sent.
28+
* If set to true (default), a page view is automatically sent upon initialization
29+
* of analytics.
30+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/pages
31+
*/
32+
'send_page_view'?: boolean;
33+
/**
34+
* The title of the page.
35+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/pages
36+
*/
37+
'page_title'?: string;
38+
/**
39+
* The path to the page. If overridden, this value must start with a / character.
40+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/pages
41+
*/
42+
'page_path'?: string;
43+
/**
44+
* The URL of the page.
45+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/pages
46+
*/
47+
'page_location'?: string;
48+
/**
49+
* Defaults to `auto`.
50+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/cookies-user-id
51+
*/
52+
'cookie_domain'?: string;
53+
/**
54+
* Defaults to 63072000 (two years, in seconds).
55+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/cookies-user-id
56+
*/
57+
'cookie_expires'?: number;
58+
/**
59+
* Defaults to `_ga`.
60+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/cookies-user-id
61+
*/
62+
'cookie_prefix'?: string;
63+
/**
64+
* If set to true, will update cookies on each page load.
65+
* Defaults to true.
66+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/cookies-user-id
67+
*/
68+
'cookie_update'?: boolean;
69+
/**
70+
* Appends additional flags to the cookie when set.
71+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/cookies-user-id
72+
*/
73+
'cookie_flags'?: string;
74+
/**
75+
* If set to false, disables all advertising features with gtag.js.
76+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/display-features
77+
*/
78+
'allow_google_signals?': boolean;
79+
/**
80+
* If set to false, disables all advertising personalization with gtag.js.
81+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/display-features
82+
*/
83+
'allow_ad_personalization_signals'?: boolean;
84+
/**
85+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-link-attribution
86+
*/
87+
'link_attribution'?: boolean;
88+
/**
89+
* If set to true, anonymizes IP addresses for all events.
90+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/ip-anonymization
91+
*/
92+
'anonymize_ip'?: boolean;
93+
/**
94+
* Custom dimensions and metrics.
95+
* See https://developers.google.com/analytics/devguides/collection/gtagjs/custom-dims-mets
96+
*/
97+
'custom_map'?: { [key: string]: unknown };
98+
[key: string]: unknown;
99+
}
100+
101+
/**
102+
* Analytics initialization options.
103+
* @public
104+
*/
105+
export interface AnalyticsOptions {
106+
/**
107+
* Params to be passed in the initial gtag config call during analytics initialization.
108+
*/
109+
config?: GtagConfigParams | EventParams;
110+
}
111+
20112
/**
21113
* Additional options that can be passed to Firebase Analytics method
22114
* calls such as `logEvent`, `setCurrentScreen`, etc.
@@ -31,13 +123,12 @@ export interface AnalyticsCallOptions {
31123
}
32124

33125
/**
34-
* The Firebase Analytics service interface.
35-
*
126+
* An instance of Firebase Analytics.
36127
* @public
37128
*/
38129
export interface Analytics {
39130
/**
40-
* The FirebaseApp this Functions instance is associated with.
131+
* The FirebaseApp this Analytics instance is associated with.
41132
*/
42133
app: FirebaseApp;
43134
}
@@ -61,6 +152,7 @@ export interface SettingsOptions {
61152
export interface CustomParams {
62153
[key: string]: unknown;
63154
}
155+
64156
/**
65157
* Type for standard gtag.js event names. `logEvent` also accepts any
66158
* custom string and interprets it as a custom event name.
@@ -96,14 +188,14 @@ export type EventNameString =
96188
| 'view_search_results';
97189

98190
/**
99-
* Currency field used by some Analytics events.
191+
* Standard analytics currency type.
100192
* @public
101193
*/
102194
export type Currency = string | number;
103195

104196
/* eslint-disable camelcase */
105197
/**
106-
* Item field used by some Analytics events.
198+
* Standard analytics `Item` type.
107199
* @public
108200
*/
109201
export interface Item {
@@ -204,5 +296,6 @@ export interface EventParams {
204296
page_title?: string;
205297
page_location?: string;
206298
page_path?: string;
299+
[key: string]: unknown;
207300
}
208301
/* eslint-enable camelcase */

0 commit comments

Comments
 (0)