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
5 changes: 5 additions & 0 deletions .changeset/few-starfishes-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@segment/analytics-next': patch
---

Fix query string API not respecting source middleware
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
import { JSDOM } from 'jsdom'
import { Analytics } from '../../core/analytics'
// @ts-ignore loadCDNSettings mocked dependency is accused as unused
import { AnalyticsBrowser } from '..'
import { setGlobalCDNUrl } from '../../lib/parse-cdn'
import { TEST_WRITEKEY } from '../../test-helpers/test-writekeys'
import { createMockFetchImplementation } from '../../test-helpers/fixtures/create-fetch-method'
import { parseFetchCall } from '../../test-helpers/fetch-parse'
import { cdnSettingsKitchenSink } from '../../test-helpers/fixtures/cdn-settings'

const fetchCalls: ReturnType<typeof parseFetchCall>[] = []
jest.mock('unfetch', () => {
return {
__esModule: true,
default: (url: RequestInfo, body?: RequestInit) => {
const call = parseFetchCall([url, body])
fetchCalls.push(call)
return createMockFetchImplementation(cdnSettingsKitchenSink)(url, body)
},
}
})

const writeKey = TEST_WRITEKEY

describe('queryString', () => {
let jsd: JSDOM

beforeEach(async () => {
jest.restoreAllMocks()
jest.resetAllMocks()

const html = `
<!DOCTYPE html>
<head>
Expand All @@ -37,33 +48,41 @@ describe('queryString', () => {
setGlobalCDNUrl(undefined as any)
})

it('applies query string logic before analytics is finished initializing', async () => {
let analyticsInitializedBeforeQs: boolean | undefined
const originalQueryString = Analytics.prototype.queryString
const mockQueryString = jest
.fn()
.mockImplementation(async function (this: Analytics, ...args) {
// simulate network latency when retrieving the bundle
await new Promise((r) => setTimeout(r, 500))
return originalQueryString.apply(this, args).then((result) => {
// ensure analytics has not finished initializing before querystring completes
analyticsInitializedBeforeQs = this.initialized
return result
})
})
Analytics.prototype.queryString = mockQueryString
it('querystring events that update anonymousId have priority over other buffered events', async () => {
const queryStringSpy = jest.spyOn(Analytics.prototype, 'queryString')

jsd.reconfigure({
url: 'https://localhost/?ajs_aid=123',
})

const [analytics] = await AnalyticsBrowser.load({ writeKey })
expect(mockQueryString).toHaveBeenCalledWith('?ajs_aid=123')
expect(analyticsInitializedBeforeQs).toBe(false)
// check that calls made immediately after analytics is loaded use correct anonymousId
const pageContext = await analytics.page()
const analytics = new AnalyticsBrowser()
const pagePromise = analytics.page()
await analytics.load({ writeKey })
expect(queryStringSpy).toHaveBeenCalledWith('?ajs_aid=123')
const pageContext = await pagePromise
expect(pageContext.event.anonymousId).toBe('123')
expect(analytics.user().anonymousId()).toBe('123')
const user = await analytics.user()
expect(user.anonymousId()).toBe('123')
})

it('querystring events have middleware applied like any other event', async () => {
jsd.reconfigure({
url: 'https://localhost/?ajs_event=Clicked',
})

const analytics = new AnalyticsBrowser()
void analytics.addSourceMiddleware(({ next, payload }) => {
payload.obj.event = payload.obj.event + ' Middleware Applied'
return next(payload)
})
await analytics.load({ writeKey })
const trackCalls = fetchCalls.filter(
(call) => call.url === 'https://api.segment.io/v1/t'
)
expect(trackCalls.length).toBe(1)
expect(trackCalls[0].body.event).toMatchInlineSnapshot(
`"Clicked Middleware Applied"`
)
})

it('applies query string logic if window.location.search is present', async () => {
Expand Down
39 changes: 25 additions & 14 deletions packages/browser/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,14 +208,29 @@
*/
async function flushFinalBuffer(
analytics: Analytics,
queryString: string,
buffer: PreInitMethodCallBuffer
): Promise<void> {
// Call popSnippetWindowBuffer before each flush task since there may be
// analytics calls during async function calls.
await flushAddSourceMiddleware(analytics, buffer)
await flushQueryString(analytics, queryString)

Check warning on line 214 in packages/browser/src/browser/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/browser/src/browser/index.ts#L214

Added line #L214 was not covered by tests
flushAnalyticsCallsInNewTask(analytics, buffer)
}

const getQueryString = (): string => {
const hash = window.location.hash ?? ''
const search = window.location.search ?? ''
const term = search.length ? search : hash.replace(/(?=#).*(?=\?)/, '')
return term

Check warning on line 222 in packages/browser/src/browser/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/browser/src/browser/index.ts#L218-L222

Added lines #L218 - L222 were not covered by tests
}

const flushQueryString = async (

Check warning on line 225 in packages/browser/src/browser/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/browser/src/browser/index.ts#L225

Added line #L225 was not covered by tests
analytics: Analytics,
queryString: string
): Promise<void> => {
if (queryString.includes('ajs_')) {
await analytics.queryString(queryString).catch(console.error)

Check warning on line 230 in packages/browser/src/browser/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/browser/src/browser/index.ts#L227-L230

Added lines #L227 - L230 were not covered by tests
}
}

async function registerPlugins(
writeKey: string,
cdnSettings: CDNSettings,
Expand Down Expand Up @@ -337,6 +352,9 @@
})
}

// register any user-defined plugins added via analytics.addSourceMiddleware()
await flushAddSourceMiddleware(analytics, preInitBuffer)

Check warning on line 356 in packages/browser/src/browser/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/browser/src/browser/index.ts#L356

Added line #L356 was not covered by tests

return ctx
}

Expand All @@ -360,6 +378,9 @@
preInitBuffer.add(new PreInitMethodCall('page', []))
}

// reading the query string as early as possible in case the URL changes
const queryString = getQueryString()

Check warning on line 382 in packages/browser/src/browser/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/browser/src/browser/index.ts#L382

Added line #L382 was not covered by tests

const cdnURL = settings.cdnURL ?? getCDN()
let cdnSettings =
settings.cdnSettings ?? (await loadCDNSettings(settings.writeKey, cdnURL))
Expand Down Expand Up @@ -412,19 +433,9 @@
preInitBuffer
)

const search = window.location.search ?? ''
const hash = window.location.hash ?? ''

const term = search.length ? search : hash.replace(/(?=#).*(?=\?)/, '')

if (term.includes('ajs_')) {
await analytics.queryString(term).catch(console.error)
}

analytics.initialized = true
analytics.emit('initialize', settings, options)

await flushFinalBuffer(analytics, preInitBuffer)
await flushFinalBuffer(analytics, queryString, preInitBuffer)

Check warning on line 438 in packages/browser/src/browser/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/browser/src/browser/index.ts#L438

Added line #L438 was not covered by tests

return [analytics, ctx]
}
Expand Down
2 changes: 1 addition & 1 deletion turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
},
"lint": {
"dependsOn": ["build"],
"inputs": ["**/tsconfig*.json", "**/*.ts", "**/*.tsx", "**/*.js"],
"inputs": ["**/tsconfig*.json", "**/*.ts", "**/*.tsx"],
"outputs": []
},
"test:cloudflare-workers": {
Expand Down
Loading