From 5a7bbaea6cb6830076afda7b108f54fa656daf20 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Sat, 28 Mar 2026 20:28:42 -0500 Subject: [PATCH] fix(browser): re-read document.referrer when buffered value is empty On iOS Safari, document.referrer is not yet populated when the snippet captures BufferedPageContext. This causes context.page.referrer to be empty in all Segment calls for the session. Re-read document.referrer in popPageContext() when the buffered value is empty. Co-Authored-By: Claude Opus 4.6 --- .../src/core/buffer/__tests__/index.test.ts | 43 +++++++++++++++++++ packages/browser/src/core/buffer/index.ts | 11 ++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/browser/src/core/buffer/__tests__/index.test.ts b/packages/browser/src/core/buffer/__tests__/index.test.ts index 4255e02da..9fa49fa71 100644 --- a/packages/browser/src/core/buffer/__tests__/index.test.ts +++ b/packages/browser/src/core/buffer/__tests__/index.test.ts @@ -5,6 +5,7 @@ import { flushAnalyticsCallsInNewTask, PreInitMethodCallBuffer, PreInitMethodName, + popPageContext, } from '..' import { Analytics } from '../../analytics' import { Context } from '../../context' @@ -13,6 +14,7 @@ import { User } from '../../user' import { getBufferedPageCtxFixture } from '../../../test-helpers/fixtures' import * as GlobalAnalytics from '../../../lib/global-analytics-helper' import { setVersionType } from '../../../lib/version-type' +import { BufferedPageContextDiscriminant } from '../../page' describe(PreInitMethodCallBuffer, () => { beforeEach(() => { @@ -359,6 +361,47 @@ describe(AnalyticsBuffered, () => { }) }) +describe(popPageContext, () => { + const makeBPC = (referrer: string) => ({ + __t: BufferedPageContextDiscriminant, + c: undefined, + p: '/', + u: 'https://example.com/', + s: '', + t: 'Test', + r: referrer, + }) + + function withDocumentReferrer(value: string, fn: () => void) { + const original = document.referrer + Object.defineProperty(document, 'referrer', { value, configurable: true }) + try { + fn() + } finally { + Object.defineProperty(document, 'referrer', { + value: original, + configurable: true, + }) + } + } + + it('should use fresh document.referrer when buffered referrer is empty', () => { + withDocumentReferrer('https://www.google.com/', () => { + const args: unknown[] = [makeBPC('')] + const result = popPageContext(args) + expect(result!.referrer).toBe('https://www.google.com/') + }) + }) + + it('should not override when both buffered and document.referrer are empty', () => { + withDocumentReferrer('', () => { + const args: unknown[] = [makeBPC('')] + const result = popPageContext(args) + expect(result!.referrer).toBe('') + }) + }) +}) + describe(callAnalyticsMethod, () => { let ajs!: Analytics let resolveSpy!: jest.Mock diff --git a/packages/browser/src/core/buffer/index.ts b/packages/browser/src/core/buffer/index.ts index 0fe60e2ce..993ee4e27 100644 --- a/packages/browser/src/core/buffer/index.ts +++ b/packages/browser/src/core/buffer/index.ts @@ -107,7 +107,16 @@ export const flushAnalyticsCallsInNewTask = ( export const popPageContext = (args: unknown[]): PageContext | undefined => { if (hasBufferedPageContextAsLastArg(args)) { const ctx = args.pop() as BufferedPageContext - return createPageContext(ctx) + const pageCtx = createPageContext(ctx) + // Re-read referrer if the buffered value is empty (iOS Safari timing issue) + if ( + !pageCtx.referrer && + typeof document !== 'undefined' && + document.referrer + ) { + return { ...pageCtx, referrer: document.referrer } + } + return pageCtx } }