-
Notifications
You must be signed in to change notification settings - Fork 190
Expand file tree
/
Copy pathcookieAccess.ts
More file actions
150 lines (133 loc) · 5.23 KB
/
Copy pathcookieAccess.ts
File metadata and controls
150 lines (133 loc) · 5.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { ONE_MINUTE, ONE_SECOND, dateNow } from '@datadog/js-core/time'
import { globalObject } from '@datadog/js-core/util'
import { setInterval, clearInterval } from '../tools/timer'
import { Observable } from '../tools/observable'
import { mockable } from '../tools/mockable'
import { display } from '../tools/display'
import { generateUUID } from '../tools/utils/stringUtils'
import { addTelemetryDebug } from '../domain/telemetry'
import { addEventListener, DOM_EVENT, isEventSupported } from './addEventListener'
import { getCookies, setCookie } from './cookie'
import type { CookieOptions } from './cookie'
export interface CookieAccess {
getAll(): Promise<string[]>
getAllAndSet(cb: (value: string[]) => { value: string; expireDelay: number }): Promise<void>
observable: Observable<void>
}
export type CookieAccessFactory = (cookieName: string, cookieOptions: CookieOptions) => CookieAccess
export async function areCookiesAuthorized(
createAccess: CookieAccessFactory,
cookieOptions: CookieOptions
): Promise<boolean> {
// Use a unique cookie name to avoid issues when the SDK is initialized multiple times during
// the test cookie lifetime
const testCookieName = `dd_cookie_test_${generateUUID()}`
const testCookieValue = 'test'
const access = createAccess(testCookieName, cookieOptions)
try {
await access.getAllAndSet(() => ({ value: testCookieValue, expireDelay: ONE_MINUTE }))
const values = await access.getAll()
return values.includes(testCookieValue)
} catch (error) {
display.error(error)
return false
} finally {
try {
await access.getAllAndSet(() => ({ value: '', expireDelay: 0 }))
} catch {
// Best-effort cleanup
}
}
}
export function createCookieStoreAccess(cookieName: string, cookieOptions: CookieOptions): CookieAccess {
const cookieStore = mockable(globalObject.cookieStore)!
const observable = new Observable<void>(() => {
const listener = addEventListener(cookieStore, DOM_EVENT.CHANGE, (event) => {
// Based on our experimentation, we're assuming that entries for the same cookie cannot be in both the 'changed' and 'deleted' arrays.
// However, due to ambiguity in the specification, we asked for clarification: https://github.com/WICG/cookie-store/issues/226
const changeEvent =
event.changed.some((event) => event.name === cookieName) ||
event.deleted.some((event) => event.name === cookieName)
if (changeEvent) {
observable.notify()
}
})
return listener.stop
})
return {
async getAll() {
const items = await cookieStore.getAll(cookieName)
return items.map((item) => item.value)
},
async getAllAndSet(cb: (value: string[]) => { value: string; expireDelay: number }) {
const items = await cookieStore.getAll(cookieName)
const currentValues = items.map((item) => item.value)
const { value, expireDelay } = cb(currentValues)
try {
await cookieStore.set({
name: cookieName,
value,
expires: dateNow() + expireDelay,
path: '/',
sameSite: cookieOptions.crossSite ? 'none' : 'strict',
domain: cookieOptions.domain,
secure: cookieOptions.secure,
partitioned: cookieOptions.partitioned,
})
} catch (error) {
const documentCookies = getCookies(cookieName)
// monitor-until: 2026-07-01
addTelemetryDebug('Failed to set cookie using Cookie Store API', {
'error.message': (error as Error).message,
newValue: value,
cookieOptions: {
...cookieOptions,
},
cookies: items.map((item) => ({
...item,
})),
cookieCount: items.length,
documentCookies,
})
}
},
observable,
}
}
export const WATCH_COOKIE_INTERVAL_DELAY = ONE_SECOND
export function createDocumentCookieAccess(cookieName: string, cookieOptions: CookieOptions): CookieAccess {
let previousCookieValues = getCookies(cookieName)
const observable = new Observable<void>(() => {
const watchCookieIntervalId = setInterval(() => {
const cookieValues = getCookies(cookieName)
notifyCookieValueIfChanged(cookieValues)
}, WATCH_COOKIE_INTERVAL_DELAY)
return () => {
clearInterval(watchCookieIntervalId)
}
})
function notifyCookieValueIfChanged(cookieValues: string[]) {
if (String(cookieValues) !== String(previousCookieValues)) {
previousCookieValues = cookieValues
observable.notify()
}
}
return {
getAll() {
return Promise.resolve(getCookies(cookieName))
},
async getAllAndSet(cb: (value: string[]) => { value: string; expireDelay: number }) {
const currentValue = getCookies(cookieName)
const { value, expireDelay } = cb(currentValue)
setCookie(cookieName, value, expireDelay, cookieOptions)
await Promise.resolve()
notifyCookieValueIfChanged([value])
},
observable,
}
}
// Salesforce LWS does not support the change event of CookieStore objects. https://developer.salesforce.com/tools/lws-distortion-viewer
export function isCookieStoreSupported(): boolean {
const cookieStore = mockable(globalObject.cookieStore)
return Boolean(cookieStore && isEventSupported(cookieStore, DOM_EVENT.CHANGE))
}