Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"dev": "vite build --watch",
"fix": "biome check --write",
"lint": "biome lint",
"build": "tsc --noEmit && vite build",
"build": "vite build",
"typecheck": "tsc --noEmit",
"test": "vitest",
"test:cov": "vitest run --coverage",
Expand Down
24 changes: 24 additions & 0 deletions src/api/pageview/pageview.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,30 @@ describe("pageview", () => {
);
});

it("should send utm parameters manually outside the page_view event", () => {
pageview({
page_path: "/",
page_location:
"http://localhost:3000/?foo=1&utm_source=google&utm_medium=cpc&utm_campaign=summer_sale&bar=2",
});

expect(query).toHaveBeenNthCalledWith(1, "set", "campaign", {
source: "google",
medium: "cpc",
campaign: "summer_sale",
});

expect(query).toHaveBeenNthCalledWith(
2,
"event",
"page_view",
expect.objectContaining({
page_path: "/",
page_location: "http://localhost:3000/?foo=1&bar=2",
}),
);
});

describe("pageTracker enabled", () => {
beforeEach(() => {
updateSettings({
Expand Down
18 changes: 10 additions & 8 deletions src/api/pageview/pageview.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import { query } from "@/api/query";
import { set } from "@/api/set";
import { type Route, getSettings } from "@/core/settings";
import type { GtagConfigParams } from "@/types/gtag";
import { getPathWithBase, hasUtmParams, useUtmParams } from "@/utils";

export type Pageview = GtagConfigParams;

export type PageviewParams = string | Route | Pageview;

function getPathWithBase(path: string, base: string): string {
const normalizedBase = base.endsWith("/") ? base : `${base}/`;
const normalizedPath = path.startsWith("/") ? path.substring(1) : path;

return `${normalizedBase}${normalizedPath}`;
}

export function pageview(params: PageviewParams) {
const { pageTracker } = getSettings();

Expand Down Expand Up @@ -48,5 +42,13 @@ export function pageview(params: PageviewParams) {
template.page_path = template.page_path.slice(0, -1);
}

if (hasUtmParams(template.page_location)) {
const { utmParams, cleanUrl } = useUtmParams(template.page_location);

template.page_location = cleanUrl;

set("campaign", utmParams);
}

query("event", "page_view", template);
}
41 changes: 41 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,44 @@ export function deepMerge<T extends DeepMergeable, U extends DeepMergeable>(

return output as T & U;
}

const UTM_PREFIX = "utm_";

type UseUtmParams = {
utmParams: Record<string, string>;
cleanUrl: string;
};

export function useUtmParams(url: string): UseUtmParams {
const urlObject = new URL(url);
const utmParams: Record<string, string> = {};
const params: string[] = [];

urlObject.searchParams.forEach((value, key) => {
if (key.includes(UTM_PREFIX)) {
utmParams[key.replace(UTM_PREFIX, "")] = value;
params.push(key);
}
});

for (const utmParam of params) {
urlObject.searchParams.delete(utmParam);
}

return {
utmParams,
cleanUrl: urlObject.toString(),
};
}

export function hasUtmParams(url: string): boolean {
const utmRegex = new RegExp(`[?&]${UTM_PREFIX}`);
return !!url.match(utmRegex);
}

export function getPathWithBase(path: string, base: string): string {
const normalizedBase = base.endsWith("/") ? base : `${base}/`;
const normalizedPath = path.startsWith("/") ? path.substring(1) : path;

return `${normalizedBase}${normalizedPath}`;
}