Skip to content

Commit ddab235

Browse files
Integrate benoit/restore-cookie-store-usage (#3561) into staging-22
Integrated commit sha: fab7030 Co-authored-by: BenoitZugmeyer <[email protected]>
2 parents df8f583 + fab7030 commit ddab235

File tree

7 files changed

+93
-28
lines changed

7 files changed

+93
-28
lines changed

packages/core/src/browser/addEventListener.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe('addEventListener', () => {
7676
const customEventTarget = {
7777
addEventListener: jasmine.createSpy(),
7878
removeEventListener: jasmine.createSpy(),
79-
} as unknown as EventTarget
79+
} as unknown as HTMLElement
8080

8181
const { stop } = addEventListener({ allowUntrustedEvents: false }, customEventTarget, 'change', listener)
8282
stop()

packages/core/src/browser/browser.types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ export interface WeakRefConstructor {
5353

5454
// Those are native API types that are not official supported by TypeScript yet
5555

56-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
57-
export interface CookieStore extends EventTarget {}
56+
export interface CookieStore extends EventTarget {
57+
get(name: string): Promise<unknown>
58+
}
5859

5960
export interface CookieStoreEventMap {
6061
change: CookieChangeEvent

packages/rum-core/src/boot/startRum.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ export function startRumEventCollection(
266266
)
267267

268268
const displayContext = startDisplayContext(hooks, configuration)
269-
const ciVisibilityContext = startCiVisibilityContext(hooks)
269+
const ciVisibilityContext = startCiVisibilityContext(configuration, hooks)
270270
startSyntheticsContext(hooks)
271271

272272
startRumAssembly(configuration, lifeCycle, hooks, reportError)

packages/rum-core/src/browser/cookieObservable.spec.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type { Subscription } from '@datadog/browser-core'
22
import { ONE_MINUTE, deleteCookie, setCookie } from '@datadog/browser-core'
33
import type { Clock } from '@datadog/browser-core/test'
44
import { mockClock } from '@datadog/browser-core/test'
5+
import { mockRumConfiguration } from '../../test'
6+
import type { CookieStoreWindow } from './cookieObservable'
57
import { WATCH_COOKIE_INTERVAL_DELAY, createCookieObservable } from './cookieObservable'
68

79
const COOKIE_NAME = 'cookie_name'
@@ -25,20 +27,47 @@ describe('cookieObservable', () => {
2527
clock.cleanup()
2628
})
2729

28-
it('should notify observers on cookie change', (done) => {
29-
const observable = createCookieObservable(COOKIE_NAME)
30+
it('should notify observers on cookie change', async () => {
31+
const observable = createCookieObservable(mockRumConfiguration(), COOKIE_NAME)
3032

31-
subscription = observable.subscribe((cookieChange) => {
32-
expect(cookieChange).toEqual('foo')
33-
34-
done()
33+
const cookieChangePromise = new Promise((resolve) => {
34+
subscription = observable.subscribe(resolve)
3535
})
36+
37+
// When writing a cookie just after subscribing to the cookieStore 'change' event, the 'change'
38+
// event is sometimes not triggered, making this test case flaky.
39+
// To work around this, we get some random cookie from the cookieStore. This adds enough delay
40+
// to ensure that the 'change' event is triggered when we write our cookie.
41+
// This was reported here: https://issues.chromium.org/issues/420405275
42+
const cookieStore = (window as CookieStoreWindow).cookieStore
43+
if (cookieStore) {
44+
// Wait for the cookieStore to be ready
45+
await cookieStore.get('some_cookie_name')
46+
}
47+
48+
setCookie(COOKIE_NAME, 'foo', COOKIE_DURATION)
49+
clock.tick(WATCH_COOKIE_INTERVAL_DELAY)
50+
51+
const cookieChange = await cookieChangePromise
52+
expect(cookieChange).toEqual('foo')
53+
})
54+
55+
it('should notify observers on cookie change when cookieStore is not supported', () => {
56+
Object.defineProperty(window, 'cookieStore', { get: () => undefined, configurable: true })
57+
const observable = createCookieObservable(mockRumConfiguration(), COOKIE_NAME)
58+
59+
let cookieChange: string | undefined
60+
subscription = observable.subscribe((change) => (cookieChange = change))
61+
3662
setCookie(COOKIE_NAME, 'foo', COOKIE_DURATION)
3763
clock.tick(WATCH_COOKIE_INTERVAL_DELAY)
64+
65+
expect(cookieChange).toEqual('foo')
3866
})
3967

40-
it('should not notify observers on cookie change when the cookie value has not changed', () => {
41-
const observable = createCookieObservable(COOKIE_NAME)
68+
it('should not notify observers on cookie change when the cookie value as not changed when cookieStore is not supported', () => {
69+
Object.defineProperty(window, 'cookieStore', { get: () => undefined, configurable: true })
70+
const observable = createCookieObservable(mockRumConfiguration(), COOKIE_NAME)
4271

4372
setCookie(COOKIE_NAME, 'foo', COOKIE_DURATION)
4473

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,54 @@
1-
import type { CookieStore } from '@datadog/browser-core'
2-
import { setInterval, clearInterval, Observable, ONE_SECOND, findCommaSeparatedValue } from '@datadog/browser-core'
1+
import type { Configuration, CookieStore } from '@datadog/browser-core'
2+
import {
3+
setInterval,
4+
clearInterval,
5+
Observable,
6+
addEventListener,
7+
ONE_SECOND,
8+
findCommaSeparatedValue,
9+
DOM_EVENT,
10+
} from '@datadog/browser-core'
311

412
export interface CookieStoreWindow extends Window {
513
cookieStore?: CookieStore
614
}
715

816
export type CookieObservable = ReturnType<typeof createCookieObservable>
917

10-
export const WATCH_COOKIE_INTERVAL_DELAY = ONE_SECOND
18+
export function createCookieObservable(configuration: Configuration, cookieName: string) {
19+
const detectCookieChangeStrategy = (window as CookieStoreWindow).cookieStore
20+
? listenToCookieStoreChange(configuration)
21+
: watchCookieFallback
1122

12-
export function createCookieObservable(cookieName: string) {
1323
return new Observable<string | undefined>((observable) =>
14-
// NOTE: we don't use cookiestore.addEventListner('change', handler) as it seems to me more prone to bugs
15-
// and cause our tests to be flaky (and probably in production as well)
16-
watchCookieStrategy(cookieName, (event) => observable.notify(event))
24+
detectCookieChangeStrategy(cookieName, (event) => observable.notify(event))
1725
)
1826
}
1927

20-
function watchCookieStrategy(cookieName: string, callback: (event: string | undefined) => void) {
28+
function listenToCookieStoreChange(configuration: Configuration) {
29+
return (cookieName: string, callback: (event: string | undefined) => void) => {
30+
const listener = addEventListener(
31+
configuration,
32+
(window as CookieStoreWindow).cookieStore!,
33+
DOM_EVENT.CHANGE,
34+
(event) => {
35+
// Based on our experimentation, we're assuming that entries for the same cookie cannot be in both the 'changed' and 'deleted' arrays.
36+
// However, due to ambiguity in the specification, we asked for clarification: https://github.com/WICG/cookie-store/issues/226
37+
const changeEvent =
38+
event.changed.find((event) => event.name === cookieName) ||
39+
event.deleted.find((event) => event.name === cookieName)
40+
if (changeEvent) {
41+
callback(changeEvent.value)
42+
}
43+
}
44+
)
45+
return listener.stop
46+
}
47+
}
48+
49+
export const WATCH_COOKIE_INTERVAL_DELAY = ONE_SECOND
50+
51+
function watchCookieFallback(cookieName: string, callback: (event: string | undefined) => void) {
2152
const previousCookieValue = findCommaSeparatedValue(document.cookie, cookieName)
2253
const watchCookieIntervalId = setInterval(() => {
2354
const cookieValue = findCommaSeparatedValue(document.cookie, cookieName)
@@ -26,5 +57,7 @@ function watchCookieStrategy(cookieName: string, callback: (event: string | unde
2657
}
2758
}, WATCH_COOKIE_INTERVAL_DELAY)
2859

29-
return () => clearInterval(watchCookieIntervalId)
60+
return () => {
61+
clearInterval(watchCookieIntervalId)
62+
}
3063
}

packages/rum-core/src/domain/contexts/ciVisibilityContext.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { RelativeTime } from '@datadog/browser-core'
1+
import type { Configuration, RelativeTime } from '@datadog/browser-core'
22
import { HookNames, Observable } from '@datadog/browser-core'
33
import { mockCiVisibilityValues } from '../../../test'
44
import type { CookieObservable } from '../../browser/cookieObservable'
@@ -24,7 +24,7 @@ describe('startCiVisibilityContext', () => {
2424
describe('assemble hook', () => {
2525
it('should set ci visibility context defined by Cypress global variables', () => {
2626
mockCiVisibilityValues('trace_id_value')
27-
;({ stop: stopCiVisibility } = startCiVisibilityContext(hooks, cookieObservable))
27+
;({ stop: stopCiVisibility } = startCiVisibilityContext({} as Configuration, hooks, cookieObservable))
2828

2929
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
3030
eventType: 'view',
@@ -44,7 +44,7 @@ describe('startCiVisibilityContext', () => {
4444

4545
it('should add the ci visibility context defined by global cookie', () => {
4646
mockCiVisibilityValues('trace_id_value', 'cookies')
47-
;({ stop: stopCiVisibility } = startCiVisibilityContext(hooks, cookieObservable))
47+
;({ stop: stopCiVisibility } = startCiVisibilityContext({} as Configuration, hooks, cookieObservable))
4848

4949
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
5050
eventType: 'view',
@@ -64,7 +64,7 @@ describe('startCiVisibilityContext', () => {
6464

6565
it('should update the ci visibility context when global cookie is updated', () => {
6666
mockCiVisibilityValues('trace_id_value', 'cookies')
67-
;({ stop: stopCiVisibility } = startCiVisibilityContext(hooks, cookieObservable))
67+
;({ stop: stopCiVisibility } = startCiVisibilityContext({} as Configuration, hooks, cookieObservable))
6868
cookieObservable.notify('trace_id_value_updated')
6969

7070
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
@@ -85,7 +85,7 @@ describe('startCiVisibilityContext', () => {
8585

8686
it('should not set ci visibility context if the Cypress global variable is undefined', () => {
8787
mockCiVisibilityValues(undefined)
88-
;({ stop: stopCiVisibility } = startCiVisibilityContext(hooks, cookieObservable))
88+
;({ stop: stopCiVisibility } = startCiVisibilityContext({} as Configuration, hooks, cookieObservable))
8989

9090
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
9191
eventType: 'view',
@@ -97,7 +97,7 @@ describe('startCiVisibilityContext', () => {
9797

9898
it('should not set ci visibility context if it is not a string', () => {
9999
mockCiVisibilityValues({ key: 'value' })
100-
;({ stop: stopCiVisibility } = startCiVisibilityContext(hooks, cookieObservable))
100+
;({ stop: stopCiVisibility } = startCiVisibilityContext({} as Configuration, hooks, cookieObservable))
101101

102102
const defaultRumEventAttributes = hooks.triggerHook(HookNames.Assemble, {
103103
eventType: 'view',

packages/rum-core/src/domain/contexts/ciVisibilityContext.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getInitCookie, HookNames, SKIPPED } from '@datadog/browser-core'
2+
import type { Configuration } from '@datadog/browser-core'
23
import { createCookieObservable } from '../../browser/cookieObservable'
34
import { SessionType } from '../rumSessionManager'
45
import type { DefaultRumEventAttributes, Hooks } from '../hooks'
@@ -14,8 +15,9 @@ export interface CiTestWindow extends Window {
1415
export type CiVisibilityContext = ReturnType<typeof startCiVisibilityContext>
1516

1617
export function startCiVisibilityContext(
18+
configuration: Configuration,
1719
hooks: Hooks,
18-
cookieObservable = createCookieObservable(CI_VISIBILITY_TEST_ID_COOKIE_NAME)
20+
cookieObservable = createCookieObservable(configuration, CI_VISIBILITY_TEST_ID_COOKIE_NAME)
1921
) {
2022
let testExecutionId =
2123
getInitCookie(CI_VISIBILITY_TEST_ID_COOKIE_NAME) || (window as CiTestWindow).Cypress?.env('traceId')

0 commit comments

Comments
 (0)