Skip to content

Commit

Permalink
Update browser-plugin-ga-cookies to include options for Google Analyt…
Browse files Browse the repository at this point in the history
…ics 4 cookies
  • Loading branch information
igneel64 committed Aug 18, 2023
1 parent f717666 commit 735f4e3
Show file tree
Hide file tree
Showing 12 changed files with 494 additions and 91 deletions.
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@snowplow/browser-plugin-ga-cookies",
"comment": "Update to include options for Google Analytics 4 cookies",
"type": "none"
}
],
"packageName": "@snowplow/browser-plugin-ga-cookies"
}
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@snowplow/javascript-tracker",
"comment": "Add further options for the gaCookies context",
"type": "none"
}
],
"packageName": "@snowplow/javascript-tracker"
}
18 changes: 15 additions & 3 deletions plugins/browser-plugin-ga-cookies/README.md
Expand Up @@ -5,7 +5,7 @@

Browser Plugin to be used with `@snowplow/browser-tracker`.

Adds GA Cookies to your Snowplow tracking.
Adds Universal Analytics and Google Analytics 4 cookies to your Snowplow tracking.

## Maintainer quick start

Expand Down Expand Up @@ -36,14 +36,26 @@ Initialize your tracker with the GaCookiesPlugin:
import { newTracker } from '@snowplow/browser-tracker';
import { GaCookiesPlugin } from '@snowplow/browser-plugin-ga-cookies';

newTracker('sp1', '{{collector}}', { plugins: [ GaCookiesPlugin() ] }); // Also stores reference at module level
newTracker('sp1', '{{collector}}', { plugins: [ GaCookiesPlugin(
/* pluginOptions */
) ] });

/*
* Available plugin options `GACookiesPluginOptions`:
* {
* ua: Send Universal Analytics specific cookie values. Defaults to true.
* ga4: Send Google Analytics 4 specific cookie values. Defaults to false.
* ga4MeasurementId: Measurement id/ids to search the Google Analytics 4 session cookie. Can be a single measurement id as a string or an array of measurement id strings. The cookie has the form of <cookie_prefix>_ga_<container-id> where <container-id> is the data stream container id and <cookie_prefix> is the optional cookie_prefix option of the gtag.js tracker.
* cookiePrefix: Cookie prefix set on the Google Analytics 4 cookies using the cookie_prefix option of the gtag.js tracker.
* }
*/
```

## Copyright and license

Licensed and distributed under the [BSD 3-Clause License](LICENSE) ([An OSI Approved License][osi]).

Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang.
Copyright (c) 2023 Snowplow Analytics Ltd.

All rights reserved.

Expand Down
5 changes: 5 additions & 0 deletions plugins/browser-plugin-ga-cookies/jest.config.js
@@ -0,0 +1,5 @@
module.exports = {
preset: 'ts-jest',
reporters: ['jest-standard-reporter'],
testEnvironment: 'jest-environment-jsdom-global',
};
2 changes: 1 addition & 1 deletion plugins/browser-plugin-ga-cookies/package.json
Expand Up @@ -19,7 +19,7 @@
],
"scripts": {
"build": "rollup -c --silent --failAfterWarnings",
"test": ""
"test": "jest"
},
"dependencies": {
"@snowplow/browser-tracker-core": "workspace:*",
Expand Down
42 changes: 0 additions & 42 deletions plugins/browser-plugin-ga-cookies/src/contexts.ts

This file was deleted.

126 changes: 83 additions & 43 deletions plugins/browser-plugin-ga-cookies/src/index.ts
@@ -1,54 +1,94 @@
/*
* Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import { SelfDescribingJson } from '@snowplow/tracker-core';
import { BrowserPlugin, cookie } from '@snowplow/browser-tracker-core';
import { Cookies } from './contexts';
import { GA4Cookies, GA4SessionCookies, UACookies } from './types';
import { GOOGLE_ANALYTICS_4_COOKIES_SCHEMA, UNIVERSAL_ANALYTICS_COOKIES_SCHEMA } from './schemata';

interface GACookiesPluginOptions {
ua?: boolean;
ga4?: boolean;
ga4MeasurementId?: string | string[];
cookiePrefix?: string | string[];
}

const defaultPluginOptions: GACookiesPluginOptions = {
ua: true,
ga4: false,
ga4MeasurementId: '',
cookiePrefix: [],
};

/**
* Captures the GA cookies on a page and sends as context on each event
* @param pluginOptions.ua - Send Universal Analytics specific cookie values.
* @param pluginOptions.ga4 - Send Google Analytics 4 specific cookie values.
* @param pluginOptions.ga4MeasurementId - Measurement id/ids to search the Google Analytics 4 session cookie. Can be a single measurement id as a string or an array of measurement id strings. The cookie has the form of _ga_<container-id> where <container-id> is the data stream container id.
* @param pluginOptions.cookiePrefix - Cookie prefix set on the Google Analytics 4 cookies using the cookie_prefix option of the gtag.js tracker.
*/
export function GaCookiesPlugin(): BrowserPlugin {
export function GaCookiesPlugin(pluginOptions: GACookiesPluginOptions = defaultPluginOptions): BrowserPlugin {
return {
contexts: () => {
const gaCookieData: SelfDescribingJson<Cookies> = {
schema: 'iglu:com.google.analytics/cookies/jsonschema/1-0-0',
data: {},
};
['__utma', '__utmb', '__utmc', '__utmv', '__utmz', '_ga'].forEach(function (cookieType) {
var value = cookie(cookieType);
if (value) {
gaCookieData.data[cookieType] = value;
}
});
return [gaCookieData];
const contexts: SelfDescribingJson<Record<string, unknown>>[] = [];
const { ga4, ga4MeasurementId, ua, cookiePrefix } = { ...defaultPluginOptions, ...pluginOptions };
const GA_USER_COOKIE = '_ga';
const GA4_MEASUREMENT_ID_PREFIX = 'G-';
const GA4_COOKIE_PREFIX = '_ga_';

if (ua) {
const uaCookiesContext: SelfDescribingJson<UACookies> = {
schema: UNIVERSAL_ANALYTICS_COOKIES_SCHEMA,
data: {},
};
['__utma', '__utmb', '__utmc', '__utmv', '__utmz', GA_USER_COOKIE].forEach(function (cookieType) {
var value = cookie(cookieType);
if (value) {
uaCookiesContext.data[cookieType] = value;
}
});
contexts.push(uaCookiesContext);
}

if (ga4) {
const cookiePrefixes = Array.isArray(cookiePrefix) ? [...cookiePrefix] : [cookiePrefix];

/* We also will search for the default (no prefix) in every case. */
cookiePrefixes.unshift('');
cookiePrefixes.forEach((prefix) => {
const userCookie = cookie(prefix + GA_USER_COOKIE);
const sessionCookies: GA4SessionCookies[] = [];
if (ga4MeasurementId) {
const measurementIdentifiers = Array.isArray(ga4MeasurementId) ? [...ga4MeasurementId] : [ga4MeasurementId];

measurementIdentifiers.forEach(function (cookieIdentifier) {
const sessionCookieValue = cookie(
prefix + cookieIdentifier.replace(GA4_MEASUREMENT_ID_PREFIX, GA4_COOKIE_PREFIX)
);
if (sessionCookieValue) {
sessionCookies.push({
measurement_id: cookieIdentifier,
session_cookie: sessionCookieValue,
});
}
});
}

if (!userCookie && !sessionCookies.length) {
return;
}

const ga4CookiesContext: SelfDescribingJson<GA4Cookies> = {
schema: GOOGLE_ANALYTICS_4_COOKIES_SCHEMA,
data: {
_ga: userCookie,
session_cookies: sessionCookies.length ? sessionCookies : undefined,
cookie_prefix: prefix || undefined,
},
};

contexts.push(ga4CookiesContext);
});
}

return contexts;
},
};
}
2 changes: 2 additions & 0 deletions plugins/browser-plugin-ga-cookies/src/schemata.ts
@@ -0,0 +1,2 @@
export const UNIVERSAL_ANALYTICS_COOKIES_SCHEMA = 'iglu:com.google.analytics/cookies/jsonschema/1-0-0';
export const GOOGLE_ANALYTICS_4_COOKIES_SCHEMA = 'iglu:com.google.ga4/cookies/jsonschema/1-0-0';
20 changes: 20 additions & 0 deletions plugins/browser-plugin-ga-cookies/src/types.ts
@@ -0,0 +1,20 @@
export interface UACookies {
__utma?: string;
__utmb?: string;
__utmc?: string;
__utmv?: string;
__utmz?: string;
_ga?: string;
[key: string]: string | undefined;
}

export interface GA4Cookies {
_ga?: string;
session_cookies?: GA4SessionCookies[];
[key: string]: unknown;
}

export interface GA4SessionCookies {
measurement_id: string;
session_cookie?: string;
}

0 comments on commit 735f4e3

Please sign in to comment.