Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"build": "esbuild src/index.ts --bundle --format=iife --outfile=surface_tag.js --target=es2020 && pnpm copyTagToEmbed",
"copyTagToEmbed": "cp surface_tag.js surface_embed_v1.js",
"dev": "esbuild src/index.ts --bundle --format=iife --outfile=surface_tag.js --target=es2020 --watch --sourcemap",
"test:unit": "node --test test/unit/*.test.js",
"typecheck": "tsc --noEmit",
"postbuild": "pnpm copyTagToEmbed"
},
Expand Down
76 changes: 43 additions & 33 deletions src/store/user-journey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,42 @@ import {
refreshJourneyCookie,
getExistingJourneyId,
} from "./journey-cookies";
import type { Logger, JourneyTrackEvent } from "../types";
import type { LeadData, Logger, JourneyTrackEvent } from "../types";

function getBrowserReferrer(): string {
return typeof document === "undefined" ? "" : document.referrer || "";
}

function getSurfaceLeadDataSafely(): LeadData | null {
try {
if (typeof localStorage === "undefined") return null;

return getLeadDataWithTTL();
} catch {
return null;
}
}

function createPageViewEvent(
url: string,
environmentId: string | null
): JourneyTrackEvent {
return {
data: {
type: "page_view",
payload: {
url,
timestamp: new Date().toISOString(),
// Native referrer stays fixed to the hard-navigation source across SPA routes.
referrer: getBrowserReferrer(),
},
},
metadata: {
...(environmentId ? { environmentId } : {}),
...(getSurfaceLeadDataSafely() ?? {}),
},
};
}

export function initializeUserJourneyTracking(
environmentId: string | null,
Expand All @@ -20,6 +55,8 @@ export function initializeUserJourneyTracking(
setJourneyId: (id: string | null) => void
): void {
try {
if (typeof window === "undefined") return;

const existingId = getExistingJourneyId();
setJourneyId(existingId);
log.info({ message: "Existing journey ID", response: { id: existingId || "none" } });
Expand All @@ -32,23 +69,8 @@ export function initializeUserJourneyTracking(
return;
}

const surfaceLeadData = getLeadDataWithTTL();

trackToRedis(
{
data: {
type: "page_view",
payload: {
url: currentUrl,
timestamp: new Date().toISOString(),
referrer: document.referrer || "",
},
},
metadata: {
...(environmentId ? { environmentId } : {}),
...(surfaceLeadData ?? {}),
},
},
createPageViewEvent(currentUrl, environmentId),
log,
getJourneyId,
setJourneyId
Expand Down Expand Up @@ -79,7 +101,7 @@ export async function trackToRedis(

log.info({ message: "Tracking to Redis", response: payload });

if (journeyId && navigator.sendBeacon) {
if (journeyId && typeof navigator !== "undefined" && navigator.sendBeacon) {
const blob = new Blob([JSON.stringify(payload)], {
type: "application/json",
});
Expand Down Expand Up @@ -126,6 +148,8 @@ export function updateUserJourneyOnRouteChange(
setJourneyId: (id: string | null) => void
): void {
try {
if (typeof window === "undefined") return;

const currentUrl = newUrl || window.location.href;
const recentVisit = getCookie(SURFACE_USER_JOURNEY_RECENT_VISIT_COOKIE_NAME);

Expand All @@ -134,22 +158,8 @@ export function updateUserJourneyOnRouteChange(
return;
}

const surfaceLeadData = getLeadDataWithTTL();

trackToRedis(
{
data: {
type: "page_view",
payload: {
url: currentUrl,
timestamp: new Date().toISOString(),
},
},
metadata: {
...(environmentId ? { environmentId } : {}),
...(surfaceLeadData ?? {}),
},
},
createPageViewEvent(currentUrl, environmentId),
log,
getJourneyId,
setJourneyId
Expand Down
6 changes: 6 additions & 0 deletions src/utils/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { CookieOptions } from "../types";
export function parseCookies(): Record<string, string> {
const cookies: Record<string, string> = {};

if (typeof document === "undefined") return cookies;

document.cookie.split(";").forEach((cookie) => {
const trimmed = cookie.trim();
const eqIndex = trimmed.indexOf("=");
Expand All @@ -27,6 +29,8 @@ export function setCookie(
value: string,
options: CookieOptions = {}
): void {
if (typeof document === "undefined") return;

const encoded = encodeURIComponent(value);
const path = options.path || "/";
const maxAge = options.maxAge ?? 604800;
Expand All @@ -44,6 +48,8 @@ export function deleteCookie(
name: string,
options: Pick<CookieOptions, "domain"> = {}
): void {
if (typeof document === "undefined") return;

const domainAttr = options.domain ? `; domain=${options.domain}` : "";
document.cookie = `${name}=; path=/; max-age=0; samesite=lax${domainAttr}`;
}
66 changes: 36 additions & 30 deletions surface_embed_v1.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@
// src/utils/cookies.ts
function parseCookies() {
const cookies = {};
if (typeof document === "undefined") return cookies;
document.cookie.split(";").forEach((cookie) => {
const trimmed = cookie.trim();
const eqIndex = trimmed.indexOf("=");
Expand All @@ -218,6 +219,7 @@
return cookies;
}
function setCookie(name, value, options = {}) {
if (typeof document === "undefined") return;
const encoded = encodeURIComponent(value);
const path = options.path || "/";
const maxAge = options.maxAge ?? 604800;
Expand All @@ -230,6 +232,7 @@
return cookies[name] || null;
}
function deleteCookie(name, options = {}) {
if (typeof document === "undefined") return;
const domainAttr = options.domain ? `; domain=${options.domain}` : "";
document.cookie = `${name}=; path=/; max-age=0; samesite=lax${domainAttr}`;
}
Expand Down Expand Up @@ -326,8 +329,37 @@
}

// src/store/user-journey.ts
function getBrowserReferrer() {
return typeof document === "undefined" ? "" : document.referrer || "";
}
function getSurfaceLeadDataSafely() {
try {
if (typeof localStorage === "undefined") return null;
return getLeadDataWithTTL();
} catch {
return null;
}
}
function createPageViewEvent(url, environmentId3) {
return {
data: {
type: "page_view",
payload: {
url,
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
// Native referrer stays fixed to the hard-navigation source across SPA routes.
referrer: getBrowserReferrer()
}
},
metadata: {
...environmentId3 ? { environmentId: environmentId3 } : {},
...getSurfaceLeadDataSafely() ?? {}
}
};
}
function initializeUserJourneyTracking(environmentId3, log2, getJourneyId, setJourneyId) {
try {
if (typeof window === "undefined") return;
const existingId = getExistingJourneyId();
setJourneyId(existingId);
log2.info({ message: "Existing journey ID", response: { id: existingId || "none" } });
Expand All @@ -337,22 +369,8 @@
log2.info({ message: "Skipping duplicate page view (same as recent visit)" });
return;
}
const surfaceLeadData = getLeadDataWithTTL();
trackToRedis(
{
data: {
type: "page_view",
payload: {
url: currentUrl2,
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
referrer: document.referrer || ""
}
},
metadata: {
...environmentId3 ? { environmentId: environmentId3 } : {},
...surfaceLeadData ?? {}
}
},
createPageViewEvent(currentUrl2, environmentId3),
log2,
getJourneyId,
setJourneyId
Expand All @@ -373,7 +391,7 @@
const payload = { ...event };
if (journeyId) payload.id = journeyId;
log2.info({ message: "Tracking to Redis", response: payload });
if (journeyId && navigator.sendBeacon) {
if (journeyId && typeof navigator !== "undefined" && navigator.sendBeacon) {
const blob = new Blob([JSON.stringify(payload)], {
type: "application/json"
});
Expand Down Expand Up @@ -408,27 +426,15 @@
}
function updateUserJourneyOnRouteChange(environmentId3, newUrl, log2, getJourneyId, setJourneyId) {
try {
if (typeof window === "undefined") return;
const currentUrl2 = newUrl || window.location.href;
const recentVisit = getCookie(SURFACE_USER_JOURNEY_RECENT_VISIT_COOKIE_NAME);
if (recentVisit === currentUrl2) {
log2.info({ message: "Skipping duplicate page view on route change" });
return;
}
const surfaceLeadData = getLeadDataWithTTL();
trackToRedis(
{
data: {
type: "page_view",
payload: {
url: currentUrl2,
timestamp: (/* @__PURE__ */ new Date()).toISOString()
}
},
metadata: {
...environmentId3 ? { environmentId: environmentId3 } : {},
...surfaceLeadData ?? {}
}
},
createPageViewEvent(currentUrl2, environmentId3),
log2,
getJourneyId,
setJourneyId
Expand Down
Loading