diff --git a/.changeset/violet-rockets-tie.md b/.changeset/violet-rockets-tie.md new file mode 100644 index 000000000..f9e8f541a --- /dev/null +++ b/.changeset/violet-rockets-tie.md @@ -0,0 +1,5 @@ +--- +'@segment/analytics-next': patch +--- + +Fixes issue related to how retried events are stored in localStorage to prevent analytics.js from reading events for a different writeKey when that writeKey is used on the same domain as the current analytics.js. diff --git a/packages/browser-integration-tests/src/fixtures/settings.ts b/packages/browser-integration-tests/src/fixtures/settings.ts index e6e27a86e..b31478442 100644 --- a/packages/browser-integration-tests/src/fixtures/settings.ts +++ b/packages/browser-integration-tests/src/fixtures/settings.ts @@ -4,11 +4,11 @@ type RemotePlugin = NonNullable[number] export class SettingsBuilder { private settings: Record - constructor(baseSettings?: Record) { + constructor(writeKey: string, baseSettings?: Record) { this.settings = baseSettings || { integrations: { 'Segment.io': { - apiKey: 'writeKey', + apiKey: writeKey, unbundledIntegrations: [], addBundledMetadata: true, maybeBundledConfigIds: {}, diff --git a/packages/browser-integration-tests/src/helpers/extract-writekey.ts b/packages/browser-integration-tests/src/helpers/extract-writekey.ts new file mode 100644 index 000000000..888e61c67 --- /dev/null +++ b/packages/browser-integration-tests/src/helpers/extract-writekey.ts @@ -0,0 +1,9 @@ +export function extractWriteKeyFromUrl(url: string): string | undefined { + const matches = url.match( + /https:\/\/cdn.segment.com\/v1\/projects\/(.+)\/settings/ + ) + + if (matches) { + return matches[1] + } +} diff --git a/packages/browser-integration-tests/src/helpers/get-persisted-items.ts b/packages/browser-integration-tests/src/helpers/get-persisted-items.ts new file mode 100644 index 000000000..7249dfd81 --- /dev/null +++ b/packages/browser-integration-tests/src/helpers/get-persisted-items.ts @@ -0,0 +1,42 @@ +export interface PersistedQueueResult { + key: string + name: string + messageIds: string[] + writeKey?: string +} + +export function getPersistedItems(): PersistedQueueResult[] { + const results: PersistedQueueResult[] = [] + + for (let i = 0; i < window.localStorage.length; i++) { + const key = window.localStorage.key(i) + if ( + key && + key.startsWith('persisted-queue:v1:') && + key.endsWith(':items') + ) { + const value = window.localStorage.getItem(key) + const messageIds = value + ? JSON.parse(value).map((i: any) => i.event.messageId) + : [] + + // Key looks like either: + // new keys - persisted-queue:v1:writeKey:dest-Segment.io:items + // old keys - persisted-queue:v1:dest-Segment.io:items + const components = key.split(':') + let writeKey: string | undefined + let name: string + + if (components.length === 5) { + ;[, , writeKey, name] = components + } else if (components.length === 4) { + ;[, , name] = components + } else { + throw new Error('Unrecognized persisted queue key.') + } + results.push({ key, messageIds, name, writeKey }) + } + } + + return results +} diff --git a/packages/browser-integration-tests/src/index.test.ts b/packages/browser-integration-tests/src/index.test.ts index d5fe69a2d..aaa9d1758 100644 --- a/packages/browser-integration-tests/src/index.test.ts +++ b/packages/browser-integration-tests/src/index.test.ts @@ -1,6 +1,7 @@ import { test, expect } from '@playwright/test' import { SettingsBuilder } from './fixtures/settings' import { standaloneMock } from './helpers/standalone-mock' +import { extractWriteKeyFromUrl } from './helpers/extract-writekey' test.describe('Standalone tests', () => { test.beforeEach(standaloneMock) @@ -14,13 +15,14 @@ test.describe('Standalone tests', () => { return route.continue() } + const writeKey = extractWriteKeyFromUrl(request.url()) || 'writeKey' return route.fulfill({ status: 200, headers: { 'Content-Type': 'application/json', }, body: JSON.stringify( - new SettingsBuilder() + new SettingsBuilder(writeKey) .addActionDestinationSettings({ name: 'Amplitude (Actions)', creationName: 'Actions Amplitude', @@ -74,13 +76,14 @@ test.describe('Standalone tests', () => { return route.continue() } + const writeKey = extractWriteKeyFromUrl(request.url()) || 'writeKey' return route.fulfill({ status: 200, headers: { 'Content-Type': 'application/json', }, body: JSON.stringify( - new SettingsBuilder() + new SettingsBuilder(writeKey) .addActionDestinationSettings({ name: 'Braze Cloud Mode (Actions)', creationName: 'Braze Cloud Mode (Actions)', diff --git a/packages/browser-integration-tests/src/segment-retries.test.ts b/packages/browser-integration-tests/src/segment-retries.test.ts index b066d046f..f10442b2a 100644 --- a/packages/browser-integration-tests/src/segment-retries.test.ts +++ b/packages/browser-integration-tests/src/segment-retries.test.ts @@ -1,115 +1,201 @@ import { Request, test, expect } from '@playwright/test' import { SettingsBuilder } from './fixtures/settings' import { standaloneMock } from './helpers/standalone-mock' +import { extractWriteKeyFromUrl } from './helpers/extract-writekey' +import { + PersistedQueueResult, + getPersistedItems, +} from './helpers/get-persisted-items' -test.describe('Standalone tests', () => { +test.describe('Segment.io Retries', () => { test.beforeEach(standaloneMock) - - test.describe('Segment.io Retries', () => { - test.beforeEach(async ({ context }) => { - await context.route( - 'https://cdn.segment.com/v1/projects/*/settings', - (route, request) => { - if (request.method().toLowerCase() !== 'get') { - return route.continue() - } - - return route.fulfill({ - status: 200, - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(new SettingsBuilder().build()), - }) + test.beforeEach(async ({ context }) => { + await context.route( + 'https://cdn.segment.com/v1/projects/*/settings', + (route, request) => { + if (request.method().toLowerCase() !== 'get') { + return route.continue() } - ) + + const writeKey = extractWriteKeyFromUrl(request.url()) || 'writeKey' + return route.fulfill({ + status: 200, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(new SettingsBuilder(writeKey).build()), + }) + } + ) + }) + test.beforeEach(async ({ context }) => { + await context.setOffline(false) + }) + + test('supports retrying failed requests on page navigation', async ({ + page, + }) => { + // Load analytics.js + await page.goto('/standalone.html') + await page.evaluate(() => window.analytics.load('fake-key')) + + // fail the 1st request + await page.route( + 'https://api.segment.io/v1/t', + (route) => { + return route.abort('blockedbyclient') + }, + { + times: 1, + } + ) + const requestFailure = new Promise>((resolve) => { + page.once('requestfailed', (request) => resolve(request.postDataJSON())) }) - test('supports retrying failed requests on page navigation', async ({ - page, - }) => { - // Load analytics.js - await page.goto('/standalone.html') - await page.evaluate(() => window.analytics.load('fake-key')) - - // fail the 1st request - await page.route( - 'https://api.segment.io/v1/t', - (route) => { - return route.abort('blockedbyclient') - }, - { - times: 1, - } - ) - const requestFailure = new Promise>((resolve) => { - page.once('requestfailed', (request) => resolve(request.postDataJSON())) - }) - - // trigger an event - await page.evaluate(() => { - void window.analytics.track('test event') - }) - - const { messageId } = await requestFailure - await page.reload() - - // load analytics.js again and wait for a new request. - const [request] = await Promise.all([ - page.waitForRequest('https://api.segment.io/v1/t'), - page.evaluate(() => window.analytics.load('fake-key')), - ]) - - expect(request.method()).toBe('POST') - expect(request.postDataJSON().messageId).toBe(messageId) + // trigger an event + await page.evaluate(() => { + void window.analytics.track('test event') }) - test('supports retrying in-flight requests on page navigation', async ({ - page, - }) => { - // Load analytics.js - await page.goto('/standalone.html') - await page.evaluate(() => window.analytics.load('fake-key')) - - // blackhole the request so that it stays in-flight when we reload the page - await page.route( - 'https://api.segment.io/v1/t', - async () => { - // do nothing - }, - { - times: 1, - } - ) - - // Detect when we've seen a track request initiated by the browser - const requestSent = new Promise>((resolve) => { - const onRequest: (req: Request) => void = (req) => { - if (req.url() === 'https://api.segment.io/v1/t') { - page.off('request', onRequest) - resolve(req.postDataJSON()) - } + const { messageId } = await requestFailure + await page.reload() + + // load analytics.js again and wait for a new request. + const [request] = await Promise.all([ + page.waitForRequest('https://api.segment.io/v1/t'), + page.evaluate(() => window.analytics.load('fake-key')), + ]) + + expect(request.method()).toBe('POST') + expect(request.postDataJSON().messageId).toBe(messageId) + }) + + test('supports retrying in-flight requests on page navigation', async ({ + page, + }) => { + // Load analytics.js + await page.goto('/standalone.html') + await page.evaluate(() => window.analytics.load('fake-key')) + + // blackhole the request so that it stays in-flight when we reload the page + await page.route( + 'https://api.segment.io/v1/t', + async () => { + // do nothing + }, + { + times: 1, + } + ) + + // Detect when we've seen a track request initiated by the browser + const requestSent = new Promise>((resolve) => { + const onRequest: (req: Request) => void = (req) => { + if (req.url() === 'https://api.segment.io/v1/t') { + page.off('request', onRequest) + resolve(req.postDataJSON()) } + } + + page.on('request', onRequest) + }) + + // trigger an event + await page.evaluate(() => { + void window.analytics.track('test event') + }) + + const { messageId } = await requestSent + await page.reload() - page.on('request', onRequest) - }) + // load analytics.js again and wait for a new request. + const [request] = await Promise.all([ + page.waitForRequest('https://api.segment.io/v1/t'), + page.evaluate(() => window.analytics.load('fake-key')), + ]) - // trigger an event - await page.evaluate(() => { - void window.analytics.track('test event') - }) + expect(request.method()).toBe('POST') + expect(request.postDataJSON().messageId).toBe(messageId) + }) + + test('events persisted to localStorage do not leak across write keys', async ({ + page, + }) => { + // Load analytics.js + await page.goto('/standalone.html') + await page.evaluate(() => window.analytics.load('key1')) + + // fail the initial track request on first 2 page loads (2 different write keys) + await page.route( + 'https://api.segment.io/v1/t', + (route) => { + return route.abort('blockedbyclient') + }, + { + times: 2, + } + ) + let requestFailure = new Promise>((resolve) => { + page.once('requestfailed', (request) => resolve(request.postDataJSON())) + }) + + // trigger an event that wil fail + await page.evaluate(() => { + void window.analytics.track('test event') + }) + + const { messageId: messageId1 } = await requestFailure + await page.reload() + + // check localstorage for event data from previous analytics (key1) + let persistedItems = await page.evaluate(getPersistedItems) + + expect(persistedItems).toHaveLength(1) + expect(persistedItems[0].writeKey).toBe('key1') + expect(persistedItems[0].messageIds).toStrictEqual([messageId1]) - const { messageId } = await requestSent - await page.reload() + requestFailure = new Promise>((resolve) => { + page.once('requestfailed', (request) => resolve(request.postDataJSON())) + }) - // load analytics.js again and wait for a new request. - const [request] = await Promise.all([ - page.waitForRequest('https://api.segment.io/v1/t'), - page.evaluate(() => window.analytics.load('fake-key')), - ]) + // Load analytics with a different write key (key2) + await page.evaluate(() => window.analytics.load('key2')) - expect(request.method()).toBe('POST') - expect(request.postDataJSON().messageId).toBe(messageId) + // trigger an event that will fail + await page.evaluate(() => { + void window.analytics.track('test event') }) + + const { messageId: messageId2 } = await requestFailure + await page.reload() + + // check localstorage for data from both write keys + persistedItems = await page.evaluate(getPersistedItems) + + expect(persistedItems).toHaveLength(2) + const persistedByWriteKey = persistedItems.reduce((prev, cur) => { + prev[cur.writeKey || '_'] = cur + return prev + }, {} as { [writeKey: string]: PersistedQueueResult }) + expect(persistedByWriteKey['key1'].messageIds).toStrictEqual([messageId1]) + expect(persistedByWriteKey['key2'].messageIds).toStrictEqual([messageId2]) + + // Now load analytics with original write key (key1) to validate message is sent + const [request] = await Promise.all([ + page.waitForRequest('https://api.segment.io/v1/t'), + page.evaluate(() => window.analytics.load('key1')), + ]) + + expect(request.method()).toBe('POST') + expect(request.postDataJSON().messageId).toBe(messageId1) + expect(request.postDataJSON().writeKey).toBe('key1') + + // Make sure localStorage only has data for the 2nd writeKey - which wasn't reloaded. + persistedItems = await page.evaluate(getPersistedItems) + + expect(persistedItems).toHaveLength(1) + expect(persistedItems[0].writeKey).toBe('key2') + expect(persistedItems[0].messageIds).toStrictEqual([messageId2]) }) }) diff --git a/packages/browser/package.json b/packages/browser/package.json index 96ae6ff70..e0323fd82 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -43,7 +43,7 @@ "size-limit": [ { "path": "dist/umd/index.js", - "limit": "28.0 KB" + "limit": "28.1 KB" } ], "dependencies": { diff --git a/packages/browser/src/browser/__tests__/integration.test.ts b/packages/browser/src/browser/__tests__/integration.test.ts index d4f84e92e..49a36cb10 100644 --- a/packages/browser/src/browser/__tests__/integration.test.ts +++ b/packages/browser/src/browser/__tests__/integration.test.ts @@ -677,6 +677,7 @@ describe('addDestinationMiddleware', () => { const amplitude = new LegacyDestination( 'amplitude', 'latest', + writeKey, { apiKey: amplitudeWriteKey, }, @@ -820,6 +821,7 @@ describe('deregister', () => { const amplitude = new LegacyDestination( 'amplitude', 'latest', + writeKey, { apiKey: amplitudeWriteKey, }, @@ -1015,6 +1017,7 @@ describe('Options', () => { const amplitude = new LegacyDestination( 'amplitude', 'latest', + writeKey, { apiKey: amplitudeWriteKey, }, @@ -1051,6 +1054,7 @@ describe('Options', () => { const amplitude = new LegacyDestination( 'amplitude', 'latest', + writeKey, { apiKey: amplitudeWriteKey, }, @@ -1087,6 +1091,7 @@ describe('Options', () => { const amplitude = new LegacyDestination( 'amplitude', 'latest', + writeKey, { apiKey: amplitudeWriteKey, }, diff --git a/packages/browser/src/browser/__tests__/integrations.integration.test.ts b/packages/browser/src/browser/__tests__/integrations.integration.test.ts index 3f80931b2..a70f12095 100644 --- a/packages/browser/src/browser/__tests__/integrations.integration.test.ts +++ b/packages/browser/src/browser/__tests__/integrations.integration.test.ts @@ -59,6 +59,7 @@ describe('Integrations', () => { const amplitude = new LegacyDestination( 'amplitude', 'latest', + writeKey, { apiKey: amplitudeWriteKey, }, @@ -129,13 +130,20 @@ describe('Integrations', () => { const amplitude = new LegacyDestination( 'Amplitude', 'latest', + writeKey, { apiKey: amplitudeWriteKey, }, {} ) - const ga = new LegacyDestination('Google-Analytics', 'latest', {}, {}) + const ga = new LegacyDestination( + 'Google-Analytics', + 'latest', + writeKey, + {}, + {} + ) const [analytics] = await AnalyticsBrowser.load({ writeKey, @@ -156,14 +164,27 @@ describe('Integrations', () => { const amplitude = new LegacyDestination( 'Amplitude', 'latest', + writeKey, { apiKey: amplitudeWriteKey, }, {} ) - const ga = new LegacyDestination('Google-Analytics', 'latest', {}, {}) - const customerIO = new LegacyDestination('Customer.io', 'latest', {}, {}) + const ga = new LegacyDestination( + 'Google-Analytics', + 'latest', + writeKey, + {}, + {} + ) + const customerIO = new LegacyDestination( + 'Customer.io', + 'latest', + writeKey, + {}, + {} + ) const [analytics] = await AnalyticsBrowser.load({ writeKey, diff --git a/packages/browser/src/browser/__tests__/standalone-analytics.test.ts b/packages/browser/src/browser/__tests__/standalone-analytics.test.ts index eec550fce..ede23fc4b 100644 --- a/packages/browser/src/browser/__tests__/standalone-analytics.test.ts +++ b/packages/browser/src/browser/__tests__/standalone-analytics.test.ts @@ -27,7 +27,9 @@ jest.mock('../../core/analytics', () => ({ register, emit: jest.fn(), on, - queue: new EventQueue(new PersistedPriorityQueue(1, 'event-queue') as any), + queue: new EventQueue( + new PersistedPriorityQueue(1, 'foo:event-queue') as any + ), options, }), })) diff --git a/packages/browser/src/browser/index.ts b/packages/browser/src/browser/index.ts index bfbf92f76..aa896f054 100644 --- a/packages/browser/src/browser/index.ts +++ b/packages/browser/src/browser/index.ts @@ -150,6 +150,7 @@ async function flushFinalBuffer( } async function registerPlugins( + writeKey: string, legacySettings: LegacySettings, analytics: Analytics, opts: InitOptions, @@ -173,6 +174,7 @@ async function registerPlugins( /* webpackChunkName: "ajs-destination" */ '../plugins/ajs-destination' ).then((mod) => { return mod.ajsDestinations( + writeKey, legacySettings, analytics.integrations, opts, @@ -286,6 +288,7 @@ async function loadAnalytics( flushPreBuffer(analytics, preInitBuffer) const ctx = await registerPlugins( + settings.writeKey, legacySettings, analytics, opts, diff --git a/packages/browser/src/core/analytics/index.ts b/packages/browser/src/core/analytics/index.ts index f597303e8..52fbadfab 100644 --- a/packages/browser/src/core/analytics/index.ts +++ b/packages/browser/src/core/analytics/index.ts @@ -56,11 +56,15 @@ const deprecationWarning = const global: any = getGlobal() const _analytics = global?.analytics -function createDefaultQueue(retryQueue = false, disablePersistance = false) { +function createDefaultQueue( + name: string, + retryQueue = false, + disablePersistance = false +) { const maxAttempts = retryQueue ? 4 : 1 const priorityQueue = disablePersistance ? new PriorityQueue(maxAttempts, []) - : new PersistedPriorityQueue(maxAttempts, 'event-queue') + : new PersistedPriorityQueue(maxAttempts, name) return new EventQueue(priorityQueue) } @@ -140,7 +144,12 @@ export class Analytics this.settings = settings this.settings.timeout = this.settings.timeout ?? 300 this.queue = - queue ?? createDefaultQueue(options?.retryQueue, disablePersistance) + queue ?? + createDefaultQueue( + `${settings.writeKey}:event-queue`, + options?.retryQueue, + disablePersistance + ) this._universalStorage = new UniversalStorage( disablePersistance ? ['memory'] : ['localStorage', 'cookie', 'memory'], diff --git a/packages/browser/src/core/queue/__tests__/event-queue.test.ts b/packages/browser/src/core/queue/__tests__/event-queue.test.ts index 06d5e0ef5..860c5bc36 100644 --- a/packages/browser/src/core/queue/__tests__/event-queue.test.ts +++ b/packages/browser/src/core/queue/__tests__/event-queue.test.ts @@ -47,7 +47,7 @@ const segmentio = { describe('alternative names', () => { test('delivers to action destinations using alternative names', async () => { - const eq = new EventQueue() + const eq = new EventQueue(`writeKey:event-queue`) const fullstory = new ActionDestination('fullstory', testPlugin) // TODO: This should be re-written as higher level integration test. fullstory.alternativeNames.push('fullstory trackEvent') fullstory.type = 'destination' diff --git a/packages/browser/src/core/queue/event-queue.ts b/packages/browser/src/core/queue/event-queue.ts index d4c9989fb..299508fac 100644 --- a/packages/browser/src/core/queue/event-queue.ts +++ b/packages/browser/src/core/queue/event-queue.ts @@ -6,8 +6,14 @@ import { CoreEventQueue } from '@segment/analytics-core' import { isOffline } from '../connection' export class EventQueue extends CoreEventQueue { - constructor(priorityQueue?: PriorityQueue) { - super(priorityQueue ?? new PersistedPriorityQueue(4, 'event-queue')) + constructor(name: string) + constructor(priorityQueue: PriorityQueue) + constructor(nameOrQueue: string | PriorityQueue) { + super( + typeof nameOrQueue === 'string' + ? new PersistedPriorityQueue(4, nameOrQueue) + : nameOrQueue + ) } async flush(): Promise { if (isOffline()) return [] diff --git a/packages/browser/src/plugins/ajs-destination/__tests__/index.test.ts b/packages/browser/src/plugins/ajs-destination/__tests__/index.test.ts index d4dfcf9a1..5a12d8249 100644 --- a/packages/browser/src/plugins/ajs-destination/__tests__/index.test.ts +++ b/packages/browser/src/plugins/ajs-destination/__tests__/index.test.ts @@ -74,6 +74,7 @@ jest.mock('unfetch', () => { }) describe('loading ajsDestinations', () => { + const writeKey = 'foo' beforeEach(async () => { jest.resetAllMocks() @@ -84,7 +85,7 @@ describe('loading ajsDestinations', () => { }) it('loads version overrides', () => { - const destinations = ajsDestinations(cdnResponse, {}, {}) + const destinations = ajsDestinations(writeKey, cdnResponse, {}, {}) const withVersionSettings = destinations.find( (d) => d.name === 'WithVersionSettings' @@ -103,7 +104,7 @@ describe('loading ajsDestinations', () => { // This test should temporary. It must be deleted once we fix the Iterable metadata it('ignores Iterable', () => { - const destinations = ajsDestinations(cdnResponse, {}, {}) + const destinations = ajsDestinations(writeKey, cdnResponse, {}, {}) const iterable = destinations.find((d) => d.name === 'Iterable') expect(iterable).toBeUndefined() }) @@ -111,6 +112,7 @@ describe('loading ajsDestinations', () => { describe('versionSettings.components', () => { it('ignores [componentType:browser] when bundlingStatus is unbundled', () => { const destinations = ajsDestinations( + writeKey, { integrations: { 'Some server destination': { @@ -141,6 +143,7 @@ describe('loading ajsDestinations', () => { it('loads [componentType:browser] when bundlingStatus is not defined', () => { const destinations = ajsDestinations( + writeKey, { integrations: { 'Some server destination': { @@ -170,13 +173,14 @@ describe('loading ajsDestinations', () => { }) it('loads type:browser legacy ajs destinations from cdn', () => { - const destinations = ajsDestinations(cdnResponse, {}, {}) + const destinations = ajsDestinations(writeKey, cdnResponse, {}, {}) // ignores segment.io expect(destinations.length).toBe(5) }) it('ignores type:browser when bundlingStatus is unbundled', () => { const destinations = ajsDestinations( + writeKey, { integrations: { 'Some server destination': { @@ -201,6 +205,7 @@ describe('loading ajsDestinations', () => { it('loads type:browser when bundlingStatus is not defined', () => { const destinations = ajsDestinations( + writeKey, { integrations: { 'Some server destination': { @@ -223,12 +228,13 @@ describe('loading ajsDestinations', () => { }) it('ignores destinations of type:server', () => { - const destinations = ajsDestinations(cdnResponse, {}, {}) + const destinations = ajsDestinations(writeKey, cdnResponse, {}, {}) expect(destinations.find((d) => d.name === 'Zapier')).toBe(undefined) }) it('does not load integrations when All:false', () => { const destinations = ajsDestinations( + writeKey, cdnResponse, { All: false, @@ -240,6 +246,7 @@ describe('loading ajsDestinations', () => { it('loads integrations when All:false, : true', () => { const destinations = ajsDestinations( + writeKey, cdnResponse, { All: false, @@ -256,7 +263,13 @@ describe('loading ajsDestinations', () => { const middleware = tsubMiddleware( cdnResponse.middlewareSettings!.routingRules ) - const destinations = ajsDestinations(cdnResponse, {}, {}, middleware) + const destinations = ajsDestinations( + writeKey, + cdnResponse, + {}, + {}, + middleware + ) const amplitude = destinations.find((d) => d.name === 'Amplitude') expect(amplitude?.middleware.length).toBe(1) }) @@ -267,6 +280,7 @@ describe('settings', () => { const dest = new LegacyDestination( 'Yandex', 'latest', + 'writeKey', { type: 'custom', }, @@ -279,6 +293,7 @@ describe('settings', () => { const dest = new LegacyDestination( 'Amplitude', 'latest', + 'writeKey', { type: 'browser', }, @@ -294,18 +309,21 @@ describe('options', () => { const defaultDestWithPersistance = new LegacyDestination( 'LocalStorageUser', 'latest', + 'writeKey', {}, {} ) const destWithPersistance = new LegacyDestination( 'LocalStorageUserToo', 'latest', + 'writeKey', {}, { disableClientPersistence: false } ) const destWithoutPersistance = new LegacyDestination( 'MemoryUser', 'latest', + 'writeKey', {}, { disableClientPersistence: true } ) @@ -326,13 +344,15 @@ describe('remote loading', () => { const loadAmplitude = async ( obfuscate = false ): Promise => { + const writeKey = 'abc' const ajs = new Analytics({ - writeKey: 'abc', + writeKey, }) const dest = new LegacyDestination( 'Amplitude', 'latest', + writeKey, { apiKey: AMPLITUDE_WRITEKEY, }, @@ -499,13 +519,15 @@ describe('plan', () => { }) const loadAmplitude = async (plan: Plan): Promise => { + const writeKey = 'abc' const ajs = new Analytics({ - writeKey: 'abc', + writeKey, }) const dest = new LegacyDestination( 'amplitude', 'latest', + writeKey, { apiKey: AMPLITUDE_WRITEKEY, }, @@ -777,7 +799,12 @@ describe('option overrides', () => { }, } - const destinations = ajsDestinations(cdnSettings, {}, initOptions) + const destinations = ajsDestinations( + 'writeKey', + cdnSettings, + {}, + initOptions + ) const amplitude = destinations[0] await amplitude.load(Context.system(), {} as Analytics) diff --git a/packages/browser/src/plugins/ajs-destination/index.ts b/packages/browser/src/plugins/ajs-destination/index.ts index 9c7450079..ac2a938cf 100644 --- a/packages/browser/src/plugins/ajs-destination/index.ts +++ b/packages/browser/src/plugins/ajs-destination/index.ts @@ -86,6 +86,7 @@ export class LegacyDestination implements DestinationPlugin { constructor( name: string, version: string, + writeKey: string, settings: JSONObject = {}, options: InitOptions, integrationSource?: ClassicIntegrationSource @@ -105,7 +106,7 @@ export class LegacyDestination implements DestinationPlugin { this.options = options this.buffer = options.disableClientPersistence ? new PriorityQueue(4, []) - : new PersistedPriorityQueue(4, `dest-${name}`) + : new PersistedPriorityQueue(4, `${writeKey}:dest-${name}`) this.scheduleFlush() } @@ -318,6 +319,7 @@ export class LegacyDestination implements DestinationPlugin { } export function ajsDestinations( + writeKey: string, settings: LegacySettings, globalIntegrations: Integrations = {}, options: InitOptions = {}, @@ -372,6 +374,7 @@ export function ajsDestinations( const destination = new LegacyDestination( name, version, + writeKey, integrationOptions[name], options, adhocIntegrationSources?.[name] diff --git a/packages/browser/src/plugins/middleware/__tests__/index.test.ts b/packages/browser/src/plugins/middleware/__tests__/index.test.ts index 9f01a7232..5c44d8a5a 100644 --- a/packages/browser/src/plugins/middleware/__tests__/index.test.ts +++ b/packages/browser/src/plugins/middleware/__tests__/index.test.ts @@ -118,7 +118,13 @@ describe(sourceMiddlewarePlugin, () => { next(payload) } - const dest = new LegacyDestination('Google Analytics', 'latest', {}, {}) + const dest = new LegacyDestination( + 'Google Analytics', + 'latest', + 'writeKey', + {}, + {} + ) const ctx = new Context({ type: 'track', diff --git a/packages/browser/src/plugins/segmentio/__tests__/retries.test.ts b/packages/browser/src/plugins/segmentio/__tests__/retries.test.ts index 4bb1b601f..39b2c6486 100644 --- a/packages/browser/src/plugins/segmentio/__tests__/retries.test.ts +++ b/packages/browser/src/plugins/segmentio/__tests__/retries.test.ts @@ -46,7 +46,10 @@ describe('Segment.io retries', () => { value: jest.fn().mockImplementation(() => queue), }) } else { - queue = new PPQ.PersistedPriorityQueue(3, `test-Segment.io`) + queue = new PPQ.PersistedPriorityQueue( + 3, + `${options.apiKey}:test-Segment.io` + ) queue['__type'] = 'persisted' Object.defineProperty(PPQ, 'PersistedPriorityQueue', { writable: true, diff --git a/packages/browser/src/plugins/segmentio/index.ts b/packages/browser/src/plugins/segmentio/index.ts index 16b01ef18..bf24da376 100644 --- a/packages/browser/src/plugins/segmentio/index.ts +++ b/packages/browser/src/plugins/segmentio/index.ts @@ -62,11 +62,13 @@ export function segmentio( inflightEvents.clear() }) + const writeKey = settings?.apiKey ?? '' + const buffer = analytics.options.disableClientPersistence ? new PriorityQueue(analytics.queue.queue.maxAttempts, []) : new PersistedPriorityQueue( analytics.queue.queue.maxAttempts, - `dest-Segment.io` + `${writeKey}:dest-Segment.io` ) const inflightEvents = new Set()