Skip to content

Commit a238a1d

Browse files
tyiuhccursoragent
andcommitted
fix(experiment-tag): address Bugbot relay flush and sync issues
Keep pending writes until RPC confirms, include event id in merge dedup key, use writeEvent retry queue on FIFO trim, and retain relay client on sync failure. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 98bd4e1 commit a238a1d

4 files changed

Lines changed: 48 additions & 39 deletions

File tree

packages/experiment-tag/src/behavioral-targeting/behavioral-targeting-manager.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ export class BehavioralTargetingManager {
8989
}
9090
const synced = await this.syncFromRelay();
9191
if (!synced) {
92-
this.setRelayClient(null);
9392
return false;
9493
}
9594
return behaviorsBefore !== this.serializeMatchedBehaviors();

packages/experiment-tag/src/behavioral-targeting/event-storage.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import { RelayEventStorage } from './relay-protocol';
33
import { SessionManager } from './session-manager';
44

55
/**
6-
* Dedup key for cross-subdomain merge (matches relay.js MIGRATE_EVENTS).
6+
* Dedup key for cross-subdomain merge (matches relay.js MIGRATE_EVENTS for
7+
* migration; includes id so same-millisecond events do not collapse on merge).
78
*/
89
export function eventDedupKey(event: {
910
event_type: string;
1011
timestamp: number;
12+
id: number;
1113
}): string {
12-
return `${event.event_type}:${event.timestamp}`;
14+
return `${event.event_type}:${event.timestamp}:${event.id}`;
1315
}
1416

1517
/**
@@ -315,14 +317,9 @@ export class EventStorageManager {
315317
if (!relay?.relayAvailable) {
316318
return;
317319
}
318-
void relay
319-
.migrateEvents(window.location.origin, {
320-
events: [...this.memoryCache.events],
321-
nextId: this.memoryCache.nextId,
322-
})
323-
.catch(() => {
324-
// fire-and-forget
325-
});
320+
for (const event of this.memoryCache.events) {
321+
relay.writeEvent(event);
322+
}
326323
}
327324

328325
private scheduleDebouncedWrite(): void {

packages/experiment-tag/src/behavioral-targeting/relay-client.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -234,37 +234,38 @@ export class RelayClient {
234234

235235
// Queue until async write confirms — flush() can resend in-flight events on unload.
236236
this.pendingWrites.push(event);
237+
this.sendPendingWrite(event);
238+
}
239+
240+
private removeConfirmedWrite(event: RelayEventRecord): void {
241+
const idx = this.pendingWrites.indexOf(event);
242+
if (idx !== -1) {
243+
this.pendingWrites.splice(idx, 1);
244+
}
245+
}
237246

247+
private sendPendingWrite(event: RelayEventRecord): void {
238248
if (!this.available || !this.iframeWindow) {
239249
return;
240250
}
241251

242252
void this.sendRequest(this.createRelayRequest('WRITE_EVENT', { event }))
243253
.then((response) => {
244-
if (!response.ok) {
245-
return;
246-
}
247-
const idx = this.pendingWrites.indexOf(event);
248-
if (idx !== -1) {
249-
this.pendingWrites.splice(idx, 1);
254+
if (response.ok) {
255+
this.removeConfirmedWrite(event);
250256
}
251257
})
252258
.catch(() => {
253-
// Keep in pendingWrites for flush()
259+
// Keep in pendingWrites for a later flush()
254260
});
255261
}
256262

257263
flush(): void {
258264
if (!this.available || !this.iframeWindow) {
259265
return;
260266
}
261-
const writes = [...this.pendingWrites];
262-
this.pendingWrites = [];
263-
for (const event of writes) {
264-
this.iframeWindow.postMessage(
265-
this.createRelayRequest('WRITE_EVENT', { event }),
266-
this.relayOrigin,
267-
);
267+
for (const event of [...this.pendingWrites]) {
268+
this.sendPendingWrite(event);
268269
}
269270
}
270271

packages/experiment-tag/test/behavioral-targeting/event-storage.test.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -417,10 +417,10 @@ describe('EventStorageManager', () => {
417417
});
418418

419419
describe('eventDedupKey', () => {
420-
test('uses event_type and timestamp', () => {
421-
expect(eventDedupKey({ event_type: 'click', timestamp: 1000 })).toBe(
422-
'click:1000',
423-
);
420+
test('uses event_type, timestamp, and id', () => {
421+
expect(
422+
eventDedupKey({ event_type: 'click', timestamp: 1000, id: 1 }),
423+
).toBe('click:1000:1');
424424
});
425425
});
426426

@@ -461,24 +461,22 @@ describe('EventStorageManager', () => {
461461
expect(relay.flush).toHaveBeenCalled();
462462
});
463463

464-
test('re-migrates relay cache when FIFO trim evicts local events', () => {
464+
test('re-writes relay cache via writeEvent when FIFO trim evicts local events', () => {
465465
for (let i = 0; i < 500; i++) {
466466
eventStorage.addEvent('test', { index: i });
467467
}
468468

469469
const relay = createMockRelay();
470470
eventStorage.setRelayClient(relay as unknown as RelayClient);
471+
relay.writeEvent.mockClear();
471472
eventStorage.addEvent('test', { index: 500 });
472473

473-
expect(relay.migrateEvents).toHaveBeenCalledWith(
474-
window.location.origin,
475-
expect.objectContaining({
476-
events: expect.arrayContaining([
477-
expect.objectContaining({ properties: { index: 500 } }),
478-
]),
479-
}),
480-
);
481-
expect(relay.writeEvent).not.toHaveBeenCalled();
474+
expect(relay.writeEvent).toHaveBeenCalled();
475+
expect(
476+
relay.writeEvent.mock.calls.some(
477+
([event]) => event.properties?.index === 500,
478+
),
479+
).toBe(true);
482480
});
483481
});
484482

@@ -509,6 +507,20 @@ describe('EventStorageManager', () => {
509507
expect(events[0].properties).toEqual({ source: 'relay' });
510508
expect(events[1].event_type).toBe('view');
511509
});
510+
511+
test('keeps same-millisecond events with different ids', () => {
512+
eventStorage.addEvent('click', {}, 1000);
513+
eventStorage.addEvent('click', {}, 1000);
514+
515+
expect(eventStorage.getAllEvents()).toHaveLength(2);
516+
517+
eventStorage.mergeFromRelay({
518+
events: [],
519+
nextId: 1,
520+
});
521+
522+
expect(eventStorage.getAllEvents()).toHaveLength(2);
523+
});
512524
});
513525

514526
describe('syncFromRelay', () => {

0 commit comments

Comments
 (0)