Skip to content

Used Trust Types When API is available for gtag URL creation #7052

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4cf1388
Initial fix for Trusted Types failures
dwyfrequency Feb 22, 2023
9222f5e
Add Trusted-Types
dwyfrequency Feb 22, 2023
ac387ac
Sanitize input
dwyfrequency Feb 22, 2023
53781ab
Add check for gtag url and log error if not found
dwyfrequency Feb 27, 2023
6c30bd6
Add test and exported function
dwyfrequency Mar 8, 2023
39a6958
Add changeset
dwyfrequency Mar 9, 2023
86c0f2c
Update comment
dwyfrequency Mar 9, 2023
516a70d
Merge branch 'master' into jd-trusted-types-7048
dwyfrequency Mar 12, 2023
dc7b7f1
Created function to create policy
dwyfrequency Mar 21, 2023
80ae05e
Stub logger instead of console.log
dwyfrequency Mar 21, 2023
ff564c7
Remove returns keyword in function docstring
dwyfrequency Mar 22, 2023
5316071
Updated the changeset for OR queries to be more descriptive (#7121)
MarkDuckworth Mar 14, 2023
cc82816
New way to config Firestore SDK Cache. (#7015)
wu-hui Mar 15, 2023
f8437f5
Enable bundle spec tests for ios (#7096)
wu-hui Mar 15, 2023
715ee9b
Add a TODO (#7103)
wu-hui Mar 15, 2023
8bad12d
Change _fail to use NETWORK_REQUEST_FAILED (#7125)
NhienLam Mar 16, 2023
1794678
firestore: verify DOMException exists before using it in when base64 …
dconeybe Mar 16, 2023
c3f3f61
Version Packages (#7123)
google-oss-bot Mar 16, 2023
54035e6
remove gapi.auth fall back option for getAuthToken() (#7100)
milaGGL Mar 17, 2023
e755eae
Update dependency simple-git to v3.16.0 [SECURITY] (#6986)
renovate[bot] Mar 17, 2023
f8334ae
Update dependency webpack to v5.76.0 [SECURITY] (#7127)
renovate[bot] Mar 17, 2023
be8b265
chore(deps): update endbug/add-and-commit action to v9 (#6425)
renovate[bot] Mar 17, 2023
cc3c93a
Add changeset (#7137)
hsubox76 Mar 20, 2023
b26c072
Firestore: query.test.ts: add a test that resumes a query with existe…
dconeybe Mar 20, 2023
8a1f7a4
Unmangle a switch variable for Closure (#7136)
hsubox76 Mar 21, 2023
ee6cc36
Remove dependency merge conflicts.
dwyfrequency Feb 22, 2023
62f0a42
Remove duplicate types package
dwyfrequency Mar 23, 2023
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
5 changes: 5 additions & 0 deletions .changeset/tasty-cooks-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@firebase/analytics': patch
---

Use the Trusted Types API when composing the gtag URL.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"@types/sinon": "9.0.11",
"@types/sinon-chai": "3.2.9",
"@types/tmp": "0.2.3",
"@types/trusted-types": "2.0.3",
"@types/yargs": "17.0.22",
"@typescript-eslint/eslint-plugin": "5.43.0",
"@typescript-eslint/eslint-plugin-tslint": "5.43.0",
Expand Down
8 changes: 6 additions & 2 deletions packages/analytics/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export const enum AnalyticsError {
FETCH_THROTTLE = 'fetch-throttle',
CONFIG_FETCH_FAILED = 'config-fetch-failed',
NO_API_KEY = 'no-api-key',
NO_APP_ID = 'no-app-id'
NO_APP_ID = 'no-app-id',
INVALID_GTAG_RESOURCE = 'invalid-gtag-resource'
}

const ERRORS: ErrorMap<AnalyticsError> = {
Expand Down Expand Up @@ -64,7 +65,9 @@ const ERRORS: ErrorMap<AnalyticsError> = {
'contain a valid API key.',
[AnalyticsError.NO_APP_ID]:
'The "appId" field is empty in the local Firebase config. Firebase Analytics requires this field to' +
'contain a valid app ID.'
'contain a valid app ID.',
[AnalyticsError.INVALID_GTAG_RESOURCE]:
'Trusted Types detected an invalid gtag resource: {$gtagURL}.'
};

interface ErrorParams {
Expand All @@ -77,6 +80,7 @@ interface ErrorParams {
};
[AnalyticsError.INVALID_ANALYTICS_CONTEXT]: { errorInfo: string };
[AnalyticsError.INDEXEDDB_UNAVAILABLE]: { errorInfo: string };
[AnalyticsError.INVALID_GTAG_RESOURCE]: { gtagURL: string };
}

export const ERROR_FACTORY = new ErrorFactory<AnalyticsError, ErrorParams>(
Expand Down
72 changes: 70 additions & 2 deletions packages/analytics/src/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ import {
insertScriptTag,
wrapOrCreateGtag,
findGtagScriptOnPage,
promiseAllSettled
promiseAllSettled,
createGtagTrustedTypesScriptURL,
createTrustedTypesPolicy
} from './helpers';
import { GtagCommand } from './constants';
import { GtagCommand, GTAG_URL } from './constants';
import { Deferred } from '@firebase/util';
import { ConsentSettings } from './public-types';
import { removeGtagScripts } from '../testing/gtag-script-util';
import { logger } from './logger';
import { AnalyticsError, ERROR_FACTORY } from './errors';

const fakeMeasurementId = 'abcd-efgh-ijkl';
const fakeAppId = 'my-test-app-1234';
Expand All @@ -46,6 +50,70 @@ const fakeDynamicConfig: DynamicConfig = {
};
const fakeDynamicConfigPromises = [Promise.resolve(fakeDynamicConfig)];

describe('Trusted Types policies and functions', () => {
describe('Trusted types exists', () => {
let ttStub: SinonStub;

beforeEach(() => {
ttStub = stub(
window.trustedTypes as TrustedTypePolicyFactory,
'createPolicy'
).returns({
createScriptURL: (s: string) => s
} as any);
});

afterEach(() => {
removeGtagScripts();
ttStub.restore();
});

it('Verify trustedTypes is called if the API is available', () => {
const trustedTypesPolicy = createTrustedTypesPolicy(
'firebase-js-sdk-policy',
{
createScriptURL: createGtagTrustedTypesScriptURL
}
);

expect(ttStub).to.be.called;
expect(trustedTypesPolicy).not.to.be.undefined;
});

it('createGtagTrustedTypesScriptURL verifies gtag URL base exists when a URL is provided', () => {
expect(createGtagTrustedTypesScriptURL(GTAG_URL)).to.equal(GTAG_URL);
});

it('createGtagTrustedTypesScriptURL rejects URLs with non-gtag base', () => {
const NON_GTAG_URL = 'http://iamnotgtag.com';
const loggerWarnStub = stub(logger, 'warn');
const errorMessage = ERROR_FACTORY.create(
AnalyticsError.INVALID_GTAG_RESOURCE,
{
gtagURL: NON_GTAG_URL
}
).message;

expect(createGtagTrustedTypesScriptURL(NON_GTAG_URL)).to.equal('');
expect(loggerWarnStub).to.be.calledWith(errorMessage);
});
});

describe('Trusted types does not exist', () => {
it('Verify trustedTypes functions are not called if the API is not available', () => {
delete window.trustedTypes;
const trustedTypesPolicy = createTrustedTypesPolicy(
'firebase-js-sdk-policy',
{
createScriptURL: createGtagTrustedTypesScriptURL
}
);

expect(trustedTypesPolicy).to.be.undefined;
});
});
});

describe('Gtag wrapping functions', () => {
afterEach(() => {
removeGtagScripts();
Expand Down
53 changes: 52 additions & 1 deletion packages/analytics/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,25 @@ import {
import { DynamicConfig, DataLayer, Gtag, MinimalDynamicConfig } from './types';
import { GtagCommand, GTAG_URL } from './constants';
import { logger } from './logger';
import { AnalyticsError, ERROR_FACTORY } from './errors';

// Possible parameter types for gtag 'event' and 'config' commands
type GtagConfigOrEventParams = ControlParams & EventParams & CustomParams;

/**
* Verifies and creates a TrustedScriptURL.
*/
export function createGtagTrustedTypesScriptURL(url: string): string {
if (!url.startsWith(GTAG_URL)) {
const err = ERROR_FACTORY.create(AnalyticsError.INVALID_GTAG_RESOURCE, {
gtagURL: url
});
logger.warn(err.message);
return '';
}
return url;
}

/**
* Makeshift polyfill for Promise.allSettled(). Resolves when all promises
* have either resolved or rejected.
Expand All @@ -40,6 +55,30 @@ export function promiseAllSettled<T>(
return Promise.all(promises.map(promise => promise.catch(e => e)));
}

/**
* Creates a TrustedTypePolicy object that implements the rules passed as policyOptions.
*
* @param policyName A string containing the name of the policy
* @param policyOptions Object containing implementations of instance methods for TrustedTypesPolicy, see {@link https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicy#instance_methods
* | the TrustedTypePolicy reference documentation}.
* @returns
*/
export function createTrustedTypesPolicy(
policyName: string,
policyOptions: Partial<TrustedTypePolicyOptions>
): Partial<TrustedTypePolicy> | undefined {
// Create a TrustedTypes policy that we can use for updating src
// properties
let trustedTypesPolicy: Partial<TrustedTypePolicy> | undefined;
if (window.trustedTypes) {
trustedTypesPolicy = window.trustedTypes.createPolicy(
policyName,
policyOptions
);
}
return trustedTypesPolicy;
}

/**
* Inserts gtag script tag into the page to asynchronously download gtag.
* @param dataLayerName Name of datalayer (most often the default, "_dataLayer").
Expand All @@ -48,10 +87,22 @@ export function insertScriptTag(
dataLayerName: string,
measurementId: string
): void {
const trustedTypesPolicy = createTrustedTypesPolicy(
'firebase-js-sdk-policy',
{
createScriptURL: createGtagTrustedTypesScriptURL
}
);

const script = document.createElement('script');
// We are not providing an analyticsId in the URL because it would trigger a `page_view`
// without fid. We will initialize ga-id using gtag (config) command together with fid.
script.src = `${GTAG_URL}?l=${dataLayerName}&id=${measurementId}`;

const gtagScriptURL = `${GTAG_URL}?l=${dataLayerName}&id=${measurementId}`;
(script.src as string | TrustedScriptURL) = trustedTypesPolicy
? (trustedTypesPolicy as TrustedTypePolicy)?.createScriptURL(gtagScriptURL)
: gtagScriptURL;

script.async = true;
document.head.appendChild(script);
}
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3693,6 +3693,11 @@
resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz#8f80dd965ad81f3e1bc26d6f5c727e132721ff40"
integrity sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==

"@types/[email protected]":
version "2.0.3"
resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz#a136f83b0758698df454e328759dbd3d44555311"
integrity sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==

"@types/vinyl@^2.0.4":
version "2.0.6"
resolved "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.6.tgz#b2d134603557a7c3d2b5d3dc23863ea2b5eb29b0"
Expand Down