diff --git a/packages/core/src/browser/cookie.ts b/packages/core/src/browser/cookie.ts index 1ec3a5cff7..50e0212023 100644 --- a/packages/core/src/browser/cookie.ts +++ b/packages/core/src/browser/cookie.ts @@ -1,5 +1,4 @@ -import { display } from '../tools/display' -import { ONE_MINUTE, ONE_SECOND } from '../tools/utils/timeUtils' +import { ONE_SECOND } from '../tools/utils/timeUtils' import { findAllCommaSeparatedValues, findCommaSeparatedValue, @@ -70,25 +69,6 @@ export function deleteCookie(name: string, options?: CookieOptions) { setCookie(name, '', 0, options) } -export function areCookiesAuthorized(options: CookieOptions): boolean { - if (document.cookie === undefined || document.cookie === null) { - return false - } - try { - // 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' - setCookie(testCookieName, testCookieValue, ONE_MINUTE, options) - const isCookieCorrectlySet = getCookie(testCookieName) === testCookieValue - deleteCookie(testCookieName, options) - return isCookieCorrectlySet - } catch (error) { - display.error(error) - return false - } -} - /** * No API to retrieve it, number of levels for subdomain and suffix are unknown * strategy: find the minimal domain on which cookies are allowed to be set diff --git a/packages/core/src/browser/cookieAccess.spec.ts b/packages/core/src/browser/cookieAccess.spec.ts index 3cba33546e..50b45b766e 100644 --- a/packages/core/src/browser/cookieAccess.spec.ts +++ b/packages/core/src/browser/cookieAccess.spec.ts @@ -1,12 +1,19 @@ import type { Clock } from '../../test' import { collectAsyncCalls, mockClock, registerCleanupTask, replaceMockable } from '../../test' import type { Configuration } from '../domain/configuration' +import { display } from '../tools/display' import { detectVersion, isChromium } from '../tools/utils/browserDetection' import { dateNow } from '../tools/utils/timeUtils' import type { CookieOptions } from './cookie' import { deleteCookie, getCookie, setCookie } from './cookie' import type { CookieStoreWindow } from './browser.types' -import { createCookieAccess, WATCH_COOKIE_INTERVAL_DELAY } from './cookieAccess' +import type { CookieAccess } from './cookieAccess' +import { + areCookiesAuthorized, + createCookieStoreAccess, + createDocumentCookieAccess, + WATCH_COOKIE_INTERVAL_DELAY, +} from './cookieAccess' const COOKIE_NAME = 'test_cookie' const COOKIE_OPTIONS = { secure: false, crossSite: false, partitioned: false } @@ -18,6 +25,7 @@ function disableCookieStore() { interface setupResult { clock: Clock + createCookieAccess: (name: string, options: CookieOptions) => CookieAccess flushObservable: (spy: jasmine.Spy) => Promise setCookieWithCleanup: ( this: void, @@ -38,6 +46,7 @@ describe('cookieAccess', () => { return { clock, + createCookieAccess: (name: string, options: CookieOptions) => createDocumentCookieAccess(name, options), flushObservable(this: void, _spy: jasmine.Spy) { clock.tick(WATCH_COOKIE_INTERVAL_DELAY) return Promise.resolve() @@ -68,6 +77,8 @@ describe('cookieAccess', () => { return { clock, + createCookieAccess: (name: string, options: CookieOptions) => + createCookieStoreAccess(name, options, MOCK_CONFIGURATION), async flushObservable(this: void, spy: jasmine.Spy) { await collectAsyncCalls(spy, 1) // Reset the spy calls to avoid throwing on unexpected calls during teardown @@ -107,10 +118,10 @@ describe('cookieAccess', () => { describe(title, () => { describe('getAllAndSet', () => { it('should pass current cookie values to callback', async () => { - const { setCookieWithCleanup } = setup() + const { createCookieAccess, setCookieWithCleanup } = setup() await setCookieWithCleanup(COOKIE_NAME, 'value1', 1000) - const cookieAccess = createCookieAccess(COOKIE_NAME, MOCK_CONFIGURATION, COOKIE_OPTIONS) + const cookieAccess = createCookieAccess(COOKIE_NAME, COOKIE_OPTIONS) let capturedValues: string[] | undefined await cookieAccess.getAllAndSet((values) => { @@ -122,8 +133,8 @@ describe('cookieAccess', () => { }) it('should pass empty array when cookie does not exist', async () => { - setup() - const cookieAccess = createCookieAccess(COOKIE_NAME, MOCK_CONFIGURATION, COOKIE_OPTIONS) + const { createCookieAccess } = setup() + const cookieAccess = createCookieAccess(COOKIE_NAME, COOKIE_OPTIONS) let capturedValues: string[] | undefined await cookieAccess.getAllAndSet((values) => { @@ -135,8 +146,8 @@ describe('cookieAccess', () => { }) it('should write the value returned by the callback', async () => { - setup() - const cookieAccess = createCookieAccess(COOKIE_NAME, MOCK_CONFIGURATION, COOKIE_OPTIONS) + const { createCookieAccess } = setup() + const cookieAccess = createCookieAccess(COOKIE_NAME, COOKIE_OPTIONS) await cookieAccess.getAllAndSet(() => ({ value: 'hello', expireDelay: 1000 })) @@ -149,11 +160,11 @@ describe('cookieAccess', () => { pending('Only Recent Chromium supports multiple cookies with the same name with different options') } - const { setCookieWithCleanup } = setup() + const { createCookieAccess, setCookieWithCleanup } = setup() await setCookieWithCleanup(COOKIE_NAME, 'value1', 1000) await setCookieWithCleanup(COOKIE_NAME, 'value2', 1000, { secure: true, partitioned: true }) - const cookieAccess = createCookieAccess(COOKIE_NAME, MOCK_CONFIGURATION, COOKIE_OPTIONS) + const cookieAccess = createCookieAccess(COOKIE_NAME, COOKIE_OPTIONS) let capturedValues: string[] | undefined await cookieAccess.getAllAndSet((values) => { @@ -167,8 +178,8 @@ describe('cookieAccess', () => { describe('observable', () => { it('should notify when cookie is changed externally', async () => { - const { flushObservable, setCookieWithCleanup } = setup() - const cookieAccess = createCookieAccess(COOKIE_NAME, MOCK_CONFIGURATION, COOKIE_OPTIONS) + const { createCookieAccess, flushObservable, setCookieWithCleanup } = setup() + const cookieAccess = createCookieAccess(COOKIE_NAME, COOKIE_OPTIONS) const spy = jasmine.createSpy('observer') const subscription = cookieAccess.observable.subscribe(spy) registerCleanupTask(() => subscription.unsubscribe()) @@ -180,10 +191,10 @@ describe('cookieAccess', () => { }) it('should notify when cookie is deleted externally', async () => { - const { flushObservable, setCookieWithCleanup } = setup() + const { createCookieAccess, flushObservable, setCookieWithCleanup } = setup() await setCookieWithCleanup(COOKIE_NAME, 'existing', 1000) - const cookieAccess = createCookieAccess(COOKIE_NAME, MOCK_CONFIGURATION, COOKIE_OPTIONS) + const cookieAccess = createCookieAccess(COOKIE_NAME, COOKIE_OPTIONS) const spy = jasmine.createSpy('observer') const subscription = cookieAccess.observable.subscribe(spy) registerCleanupTask(() => subscription.unsubscribe()) @@ -195,10 +206,10 @@ describe('cookieAccess', () => { }) it('should not notify when cookie value is unchanged', async () => { - const { clock, setCookieWithCleanup } = setup() + const { createCookieAccess, clock, setCookieWithCleanup } = setup() await setCookieWithCleanup(COOKIE_NAME, 'stable', 1000) - const cookieAccess = createCookieAccess(COOKIE_NAME, MOCK_CONFIGURATION, COOKIE_OPTIONS) + const cookieAccess = createCookieAccess(COOKIE_NAME, COOKIE_OPTIONS) const spy = jasmine.createSpy('observer') const subscription = cookieAccess.observable.subscribe(spy) registerCleanupTask(() => subscription.unsubscribe()) @@ -209,8 +220,8 @@ describe('cookieAccess', () => { }) it('should notify the observable after writing', async () => { - const { flushObservable } = setup() - const cookieAccess = createCookieAccess(COOKIE_NAME, MOCK_CONFIGURATION, COOKIE_OPTIONS) + const { createCookieAccess, flushObservable } = setup() + const cookieAccess = createCookieAccess(COOKIE_NAME, COOKIE_OPTIONS) const spy = jasmine.createSpy('observer') const subscription = cookieAccess.observable.subscribe(spy) registerCleanupTask(() => subscription.unsubscribe()) @@ -223,4 +234,85 @@ describe('cookieAccess', () => { }) }) } + + describe('areCookiesAuthorized', () => { + it('returns true when the access can write and read back the test cookie', async () => { + const access: CookieAccess = { + getAll: jasmine.createSpy('getAll').and.returnValue(Promise.resolve(['test'])), + getAllAndSet: jasmine.createSpy('getAllAndSet').and.returnValue(Promise.resolve()), + observable: null as any, + } + const factory = jasmine.createSpy('factory').and.returnValue(access) + + const result = await areCookiesAuthorized(factory, COOKIE_OPTIONS, MOCK_CONFIGURATION) + + expect(result).toBe(true) + expect(factory).toHaveBeenCalledWith(jasmine.any(String), COOKIE_OPTIONS, MOCK_CONFIGURATION) + }) + + it('returns false when the access cannot read back the test cookie', async () => { + const access: CookieAccess = { + getAll: () => Promise.resolve([]), + getAllAndSet: () => Promise.resolve(), + observable: null as any, + } + + const result = await areCookiesAuthorized(() => access, COOKIE_OPTIONS, MOCK_CONFIGURATION) + + expect(result).toBe(false) + }) + + it('returns false and logs when the access throws', async () => { + const displayErrorSpy = spyOn(display, 'error') + const access: CookieAccess = { + getAll: () => Promise.resolve([]), + getAllAndSet: () => Promise.reject(new Error('boom')), + observable: null as any, + } + + const result = await areCookiesAuthorized(() => access, COOKIE_OPTIONS, MOCK_CONFIGURATION) + + expect(result).toBe(false) + expect(displayErrorSpy).toHaveBeenCalled() + }) + + it('cleans up the test cookie after the check', async () => { + const calls: Array<{ value: string; expireDelay: number }> = [] + const access: CookieAccess = { + getAll: () => Promise.resolve(['test']), + getAllAndSet: (cb) => { + calls.push(cb([])) + return Promise.resolve() + }, + observable: null as any, + } + + await areCookiesAuthorized(() => access, COOKIE_OPTIONS, MOCK_CONFIGURATION) + + expect(calls).toEqual([ + { value: 'test', expireDelay: jasmine.any(Number) as unknown as number }, + { value: '', expireDelay: 0 }, + ]) + }) + + it('works with the real createDocumentCookieAccess', async () => { + disableCookieStore() + const result = await areCookiesAuthorized(createDocumentCookieAccess, COOKIE_OPTIONS, MOCK_CONFIGURATION) + expect(result).toBe(true) + }) + + it('works with the real createCookieStoreAccess', async () => { + if (!(window as CookieStoreWindow).cookieStore) { + pending('CookieStore API not available') + } + const result = await areCookiesAuthorized(createCookieStoreAccess, COOKIE_OPTIONS, MOCK_CONFIGURATION) + expect(result).toBe(true) + }) + + it('returns false when document.cookie is empty', async () => { + spyOnProperty(document, 'cookie', 'get').and.returnValue('') + const result = await areCookiesAuthorized(createDocumentCookieAccess, COOKIE_OPTIONS, MOCK_CONFIGURATION) + expect(result).toBe(false) + }) + }) }) diff --git a/packages/core/src/browser/cookieAccess.ts b/packages/core/src/browser/cookieAccess.ts index adb6e59f11..03b2425153 100644 --- a/packages/core/src/browser/cookieAccess.ts +++ b/packages/core/src/browser/cookieAccess.ts @@ -1,7 +1,9 @@ import { setInterval, clearInterval } from '../tools/timer' -import { dateNow, ONE_SECOND } from '../tools/utils/timeUtils' +import { dateNow, ONE_MINUTE, ONE_SECOND } from '../tools/utils/timeUtils' import { Observable } from '../tools/observable' import { mockable } from '../tools/mockable' +import { display } from '../tools/display' +import { generateUUID } from '../tools/utils/stringUtils' import type { Configuration } from '../domain/configuration' import { addTelemetryDebug } from '../domain/telemetry' import { addEventListener, DOM_EVENT } from './addEventListener' @@ -21,24 +23,44 @@ export interface CookieAccess { observable: Observable } -export function createCookieAccess( +export type CookieAccessFactory = ( cookieName: string, - configuration: Configuration, - cookieOptions: CookieOptions -): CookieAccess { - const cookieStore = mockable((window as CookieStoreWindow).cookieStore) - if (cookieStore) { - return createCookieStoreAccess(cookieName, configuration, cookieOptions, cookieStore) + cookieOptions: CookieOptions, + configuration: Configuration +) => CookieAccess + +export async function areCookiesAuthorized( + createAccess: CookieAccessFactory, + cookieOptions: CookieOptions, + configuration: Configuration +): Promise { + // 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, configuration) + 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 + } } - return createDocumentCookieAccess(cookieName, cookieOptions) } -function createCookieStoreAccess( +export function createCookieStoreAccess( cookieName: string, - configuration: Configuration, cookieOptions: CookieOptions, - cookieStore: NonNullable + configuration: Configuration ): CookieAccess { + const cookieStore = mockable((window as CookieStoreWindow).cookieStore)! const observable = new Observable(() => { const listener = addEventListener(configuration, 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. @@ -99,7 +121,11 @@ function createCookieStoreAccess( } export const WATCH_COOKIE_INTERVAL_DELAY = ONE_SECOND -function createDocumentCookieAccess(cookieName: string, cookieOptions: CookieOptions): CookieAccess { +export function createDocumentCookieAccess( + cookieName: string, + cookieOptions: CookieOptions, + _configuration?: Configuration +): CookieAccess { let previousCookieValues = getCookies(cookieName) const observable = new Observable(() => { diff --git a/packages/core/src/domain/configuration/configuration.spec.ts b/packages/core/src/domain/configuration/configuration.spec.ts index f352f73400..ac7d76ae8c 100644 --- a/packages/core/src/domain/configuration/configuration.spec.ts +++ b/packages/core/src/domain/configuration/configuration.spec.ts @@ -3,7 +3,6 @@ import { EXHAUSTIVE_INIT_CONFIGURATION, SERIALIZED_EXHAUSTIVE_INIT_CONFIGURATION import type { ExtractTelemetryConfiguration, MapInitConfigurationKey } from '../../../test' import { DOCS_ORIGIN, MORE_DETAILS, display } from '../../tools/display' import { ExperimentalFeature, isExperimentalFeatureEnabled } from '../../tools/experimentalFeatures' -import { SessionPersistence } from '../session/sessionConstants' import { TrackingConsent } from '../trackingConsent' import type { InitConfiguration } from './configuration' import { serializeConfiguration, validateAndBuildConfiguration } from './configuration' @@ -87,53 +86,6 @@ describe('validateAndBuildConfiguration', () => { }) }) - describe('sessionStoreStrategyType', () => { - it('should contain cookie strategy in the configuration by default', () => { - const configuration = validateAndBuildConfiguration({ clientToken }) - expect(configuration?.sessionStoreStrategyType).toEqual({ - type: SessionPersistence.COOKIE, - cookieOptions: { secure: false, crossSite: false, partitioned: false }, - }) - }) - - it('should not contain any strategy if cookies are unavailable and no session persistence is configured', () => { - spyOnProperty(document, 'cookie', 'get').and.returnValue('') - spyOn(Storage.prototype, 'getItem').and.throwError('unavailable') - const configuration = validateAndBuildConfiguration({ clientToken }) - expect(configuration?.sessionStoreStrategyType).toBeUndefined() - }) - - it('should use the first available strategy when multiple session persistence strategies are provided', () => { - const configuration = validateAndBuildConfiguration({ - clientToken, - sessionPersistence: [SessionPersistence.COOKIE, SessionPersistence.LOCAL_STORAGE], - }) - expect(configuration?.sessionStoreStrategyType).toEqual({ - type: SessionPersistence.COOKIE, - cookieOptions: { secure: false, crossSite: false, partitioned: false }, - }) - }) - - it('should contain local-storage strategy if cookies are unavailable and session persistence contained cookies and local-storage', () => { - spyOnProperty(document, 'cookie', 'get').and.returnValue('') - const configuration = validateAndBuildConfiguration({ - clientToken, - sessionPersistence: [SessionPersistence.COOKIE, SessionPersistence.LOCAL_STORAGE], - }) - expect(configuration?.sessionStoreStrategyType).toEqual({ type: SessionPersistence.LOCAL_STORAGE }) - }) - - it('should contain memory strategy if cookies and local-storage are unavailable and session persistence contained all three', () => { - spyOnProperty(document, 'cookie', 'get').and.returnValue('') - spyOn(Storage.prototype, 'getItem').and.throwError('unavailable') - const configuration = validateAndBuildConfiguration({ - clientToken, - sessionPersistence: [SessionPersistence.COOKIE, SessionPersistence.LOCAL_STORAGE, SessionPersistence.MEMORY], - }) - expect(configuration?.sessionStoreStrategyType).toEqual({ type: SessionPersistence.MEMORY }) - }) - }) - describe('beforeSend', () => { it('should be undefined when beforeSend is missing on user configuration', () => { const configuration = validateAndBuildConfiguration({ clientToken })! diff --git a/packages/core/src/domain/configuration/configuration.ts b/packages/core/src/domain/configuration/configuration.ts index 01b2891a9a..b64240a0e6 100644 --- a/packages/core/src/domain/configuration/configuration.ts +++ b/packages/core/src/domain/configuration/configuration.ts @@ -3,8 +3,8 @@ import { DOCS_ORIGIN, MORE_DETAILS, display } from '../../tools/display' import type { RawTelemetryConfiguration } from '../telemetry' import { isPercentage } from '../../tools/utils/numberUtils' import { objectHasValue } from '../../tools/utils/objectUtils' -import { selectSessionStoreStrategyType } from '../session/sessionStore' -import type { SessionStoreStrategyType } from '../session/storeStrategies/sessionStoreStrategy' +import type { CookieOptions } from '../../browser/cookie' +import { buildCookieOptions } from '../session/storeStrategies/sessionInCookie' import { TrackingConsent } from '../trackingConsent' import type { SessionPersistence } from '../session/sessionConstants' import type { MatchOption } from '../../tools/matchOption' @@ -287,7 +287,8 @@ export type SdkSource = 'browser' | 'flutter' | 'unity' export interface Configuration extends TransportConfiguration { // Built from init configuration beforeSend: GenericBeforeSendCallback | undefined - sessionStoreStrategyType: SessionStoreStrategyType | undefined + cookieOptions: CookieOptions | undefined + sessionPersistence: SessionPersistence | SessionPersistence[] | undefined sessionSampleRate: number telemetrySampleRate: number telemetryConfigurationSampleRate: number @@ -372,7 +373,8 @@ export function validateAndBuildConfiguration( return { beforeSend: initConfiguration.beforeSend && catchUserErrors(initConfiguration.beforeSend, 'beforeSend threw an error:'), - sessionStoreStrategyType: selectSessionStoreStrategyType(initConfiguration), + cookieOptions: buildCookieOptions(initConfiguration), + sessionPersistence: initConfiguration.sessionPersistence, sessionSampleRate: initConfiguration.sessionSampleRate ?? 100, telemetrySampleRate: initConfiguration.telemetrySampleRate ?? 20, telemetryConfigurationSampleRate: initConfiguration.telemetryConfigurationSampleRate ?? 5, diff --git a/packages/core/src/domain/session/sessionManager.spec.ts b/packages/core/src/domain/session/sessionManager.spec.ts index 29c181c42c..102b32686a 100644 --- a/packages/core/src/domain/session/sessionManager.spec.ts +++ b/packages/core/src/domain/session/sessionManager.spec.ts @@ -9,6 +9,7 @@ import { replaceMockable, restorePageVisibility, setPageVisibility, + waitNextMicrotask, } from '../../../test' import type { Clock } from '../../../test' import { DOM_EVENT } from '../../browser/addEventListener' @@ -25,14 +26,19 @@ import { TRACKED_SESSION_MAX_AGE, VISIBILITY_CHECK_DELAY, } from './sessionManager' -import { getSessionStoreStrategy } from './sessionStore' +import { getSessionStoreStrategy, selectSessionStoreStrategyType } from './sessionStore' import { SESSION_EXPIRATION_DELAY, SESSION_TIME_OUT_DELAY, SessionPersistence } from './sessionConstants' import type { SessionStoreStrategyType } from './storeStrategies/sessionStoreStrategy' +import { CookieApi } from './storeStrategies/sessionStoreStrategy' import type { SessionState } from './sessionState' import { EXPIRED } from './sessionState' describe('startSessionManager', () => { - const STORE_TYPE: SessionStoreStrategyType = { type: SessionPersistence.COOKIE, cookieOptions: {} } + const STORE_TYPE: SessionStoreStrategyType = { + type: SessionPersistence.COOKIE, + cookieOptions: {}, + cookieApi: CookieApi.DOCUMENT_COOKIE, + } let fakeStrategy: ReturnType let clock: Clock let sessionObservableSpy!: jasmine.Spy @@ -46,13 +52,17 @@ describe('startSessionManager', () => { fakeStrategy = createFakeSessionStoreStrategy(options) } + let currentStoreType: SessionStoreStrategyType | undefined + beforeEach(() => { sessionObservableSpy = jasmine.createSpy('sessionObservable') clock = mockClock() fakeStrategy = createFakeSessionStoreStrategy() fakeStrategy.sessionObservable.subscribe(sessionObservableSpy) + currentStoreType = STORE_TYPE // Register the mockable once, pointing to a function that always returns the current fakeStrategy replaceMockable(getSessionStoreStrategy, () => fakeStrategy) + replaceMockable(selectSessionStoreStrategyType, () => Promise.resolve(currentStoreType)) registerCleanupTask(() => { stopSessionManager() @@ -69,7 +79,6 @@ describe('startSessionManager', () => { } = {}): Promise { const sessionManager = await startSessionManager( { - sessionStoreStrategyType: STORE_TYPE, sessionSampleRate: 100, ...configuration, } as Configuration, @@ -81,9 +90,10 @@ describe('startSessionManager', () => { describe('initialization', () => { it('should not start if no session store strategy type is configured', async () => { const displayWarnSpy = spyOn(display, 'warn') + currentStoreType = undefined const sessionManager = await startSessionManager( - { sessionStoreStrategyType: undefined } as Configuration, + {} as Configuration, createTrackingConsentState(TrackingConsent.GRANTED) ) @@ -101,7 +111,7 @@ describe('startSessionManager', () => { fakeStrategy.setSessionState.and.returnValue(Promise.reject(new Error('storage failure'))) const sessionManager = await startSessionManager( - { sessionStoreStrategyType: STORE_TYPE, sessionSampleRate: 100, trackAnonymousUser: false } as Configuration, + { sessionSampleRate: 100, trackAnonymousUser: false } as Configuration, createTrackingConsentState(TrackingConsent.GRANTED) ) @@ -110,7 +120,7 @@ describe('startSessionManager', () => { it('should resolve after initialization', async () => { const sessionManager = await startSessionManager( - { sessionStoreStrategyType: STORE_TYPE, sessionSampleRate: 100, trackAnonymousUser: false } as Configuration, + { sessionSampleRate: 100, trackAnonymousUser: false } as Configuration, createTrackingConsentState(TrackingConsent.GRANTED) ) @@ -386,7 +396,7 @@ describe('startSessionManager', () => { clock.tick(SESSION_TIME_OUT_DELAY) // Drain the pending setSessionState microtasks so that scheduleExpirationTimeout runs // and registers the 0ms expiry timeout. - await Promise.resolve() + await waitNextMicrotask() // Fire the 0ms expiry timeout. clock.tick(0) @@ -537,13 +547,15 @@ describe('startSessionManager', () => { const sessionManagerPromise = startSessionManager( { - sessionStoreStrategyType: STORE_TYPE, sessionSampleRate: 100, trackAnonymousUser: false, } as Configuration, trackingConsentState ) + // Allow startSessionManager to await selectSessionStoreStrategyType and call setSessionState + await waitNextMicrotask() + // Consent revoked while initialization promise is pending trackingConsentState.update(TrackingConsent.NOT_GRANTED) diff --git a/packages/core/src/domain/session/sessionManager.ts b/packages/core/src/domain/session/sessionManager.ts index 1f164230d5..15ce7133f0 100644 --- a/packages/core/src/domain/session/sessionManager.ts +++ b/packages/core/src/domain/session/sessionManager.ts @@ -37,7 +37,7 @@ import { initializeSession, isSessionInExpiredState, } from './sessionState' -import { getSessionStoreStrategy } from './sessionStore' +import { getSessionStoreStrategy, selectSessionStoreStrategyType } from './sessionStore' export interface SessionManager { findSession: (startTime?: RelativeTime, options?: { returnInactive: boolean }) => SessionContext | undefined @@ -93,12 +93,13 @@ export async function startSessionManager( const expireObservable = new Observable() let expireContext: SessionDebugContext | undefined - if (!configuration.sessionStoreStrategyType) { + const sessionStoreStrategyType = await mockable(selectSessionStoreStrategyType)(configuration) + if (!sessionStoreStrategyType) { display.warn('No storage available for session. We will not send any data.') return } - const strategy = mockable(getSessionStoreStrategy)(configuration.sessionStoreStrategyType, configuration) + const strategy = mockable(getSessionStoreStrategy)(sessionStoreStrategyType, configuration) const sessionContextHistory = createValueHistory({ expireDelay: SESSION_CONTEXT_TIMEOUT_DELAY, diff --git a/packages/core/src/domain/session/sessionStore.spec.ts b/packages/core/src/domain/session/sessionStore.spec.ts index bc5b403f3d..a8bd170701 100644 --- a/packages/core/src/domain/session/sessionStore.spec.ts +++ b/packages/core/src/domain/session/sessionStore.spec.ts @@ -1,142 +1,165 @@ -import type { InitConfiguration } from '../configuration' +import { replaceMockable } from '../../../test' +import type { CookieStoreWindow } from '../../browser/browser.types' +import type { Configuration, InitConfiguration } from '../configuration' import { display } from '../../tools/display' import { selectSessionStoreStrategyType } from './sessionStore' import { SessionPersistence } from './sessionConstants' +import { buildCookieOptions } from './storeStrategies/sessionInCookie' const DEFAULT_INIT_CONFIGURATION: InitConfiguration = { clientToken: 'abc' } +function makeConfiguration(initConfiguration: InitConfiguration): Configuration { + return { + cookieOptions: buildCookieOptions(initConfiguration), + sessionPersistence: initConfiguration.sessionPersistence, + } as Configuration +} + describe('session store', () => { describe('selectSessionStoreStrategyType', () => { describe('sessionPersistence: cookie (default)', () => { - it('returns cookie strategy when cookies are available', () => { - const sessionStoreStrategyType = selectSessionStoreStrategyType(DEFAULT_INIT_CONFIGURATION) + it('returns cookie strategy when cookies are available', async () => { + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration(DEFAULT_INIT_CONFIGURATION) + ) expect(sessionStoreStrategyType).toEqual(jasmine.objectContaining({ type: SessionPersistence.COOKIE })) }) - it('returns undefined when cookies are not available', () => { + it('returns undefined when cookies are not available', async () => { disableCookies() - const sessionStoreStrategyType = selectSessionStoreStrategyType(DEFAULT_INIT_CONFIGURATION) + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration(DEFAULT_INIT_CONFIGURATION) + ) expect(sessionStoreStrategyType).toBeUndefined() }) - it('returns cookie strategy when sessionPersistence is cookie', () => { - const sessionStoreStrategyType = selectSessionStoreStrategyType({ - ...DEFAULT_INIT_CONFIGURATION, - sessionPersistence: SessionPersistence.COOKIE, - }) + it('returns cookie strategy when sessionPersistence is cookie', async () => { + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration({ ...DEFAULT_INIT_CONFIGURATION, sessionPersistence: SessionPersistence.COOKIE }) + ) expect(sessionStoreStrategyType).toEqual(jasmine.objectContaining({ type: SessionPersistence.COOKIE })) }) }) describe('sessionPersistence: local-storage', () => { - it('returns local storage strategy when sessionPersistence is local storage', () => { - const sessionStoreStrategyType = selectSessionStoreStrategyType({ - ...DEFAULT_INIT_CONFIGURATION, - sessionPersistence: SessionPersistence.LOCAL_STORAGE, - }) + it('returns local storage strategy when sessionPersistence is local storage', async () => { + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration({ ...DEFAULT_INIT_CONFIGURATION, sessionPersistence: SessionPersistence.LOCAL_STORAGE }) + ) expect(sessionStoreStrategyType).toEqual(jasmine.objectContaining({ type: SessionPersistence.LOCAL_STORAGE })) }) - it('returns undefined when local storage is not available', () => { + it('returns undefined when local storage is not available', async () => { disableLocalStorage() - const sessionStoreStrategyType = selectSessionStoreStrategyType({ - ...DEFAULT_INIT_CONFIGURATION, - sessionPersistence: SessionPersistence.LOCAL_STORAGE, - }) + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration({ ...DEFAULT_INIT_CONFIGURATION, sessionPersistence: SessionPersistence.LOCAL_STORAGE }) + ) expect(sessionStoreStrategyType).toBeUndefined() }) }) describe('sessionPersistence: memory', () => { - it('returns memory strategy when sessionPersistence is memory', () => { - const sessionStoreStrategyType = selectSessionStoreStrategyType({ - ...DEFAULT_INIT_CONFIGURATION, - sessionPersistence: SessionPersistence.MEMORY, - }) + it('returns memory strategy when sessionPersistence is memory', async () => { + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration({ ...DEFAULT_INIT_CONFIGURATION, sessionPersistence: SessionPersistence.MEMORY }) + ) expect(sessionStoreStrategyType).toEqual(jasmine.objectContaining({ type: SessionPersistence.MEMORY })) }) }) - it('returns undefined when sessionPersistence is invalid', () => { + it('returns undefined when sessionPersistence is invalid', async () => { const displayErrorSpy = spyOn(display, 'error') - const sessionStoreStrategyType = selectSessionStoreStrategyType({ - ...DEFAULT_INIT_CONFIGURATION, - sessionPersistence: 'invalid' as SessionPersistence, - }) + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration({ ...DEFAULT_INIT_CONFIGURATION, sessionPersistence: 'invalid' as SessionPersistence }) + ) expect(sessionStoreStrategyType).toBeUndefined() expect(displayErrorSpy).toHaveBeenCalledOnceWith("Invalid session persistence 'invalid'") }) describe('sessionPersistence as array', () => { - it('returns the first available strategy from the array', () => { - const sessionStoreStrategyType = selectSessionStoreStrategyType({ - ...DEFAULT_INIT_CONFIGURATION, - sessionPersistence: [SessionPersistence.COOKIE, SessionPersistence.LOCAL_STORAGE], - }) + it('returns the first available strategy from the array', async () => { + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration({ + ...DEFAULT_INIT_CONFIGURATION, + sessionPersistence: [SessionPersistence.COOKIE, SessionPersistence.LOCAL_STORAGE], + }) + ) expect(sessionStoreStrategyType).toEqual(jasmine.objectContaining({ type: SessionPersistence.COOKIE })) }) - it('falls back to next strategy when first is unavailable', () => { + it('falls back to next strategy when first is unavailable', async () => { disableCookies() - const sessionStoreStrategyType = selectSessionStoreStrategyType({ - ...DEFAULT_INIT_CONFIGURATION, - sessionPersistence: [SessionPersistence.COOKIE, SessionPersistence.LOCAL_STORAGE], - }) + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration({ + ...DEFAULT_INIT_CONFIGURATION, + sessionPersistence: [SessionPersistence.COOKIE, SessionPersistence.LOCAL_STORAGE], + }) + ) expect(sessionStoreStrategyType).toEqual(jasmine.objectContaining({ type: SessionPersistence.LOCAL_STORAGE })) }) - it('falls back to memory when cookie and local storage are unavailable', () => { + it('falls back to memory when cookie and local storage are unavailable', async () => { disableCookies() disableLocalStorage() - const sessionStoreStrategyType = selectSessionStoreStrategyType({ - ...DEFAULT_INIT_CONFIGURATION, - sessionPersistence: [SessionPersistence.COOKIE, SessionPersistence.LOCAL_STORAGE, SessionPersistence.MEMORY], - }) + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration({ + ...DEFAULT_INIT_CONFIGURATION, + sessionPersistence: [ + SessionPersistence.COOKIE, + SessionPersistence.LOCAL_STORAGE, + SessionPersistence.MEMORY, + ], + }) + ) expect(sessionStoreStrategyType).toEqual(jasmine.objectContaining({ type: SessionPersistence.MEMORY })) }) - it('returns undefined when no strategy in array is available', () => { + it('returns undefined when no strategy in array is available', async () => { disableCookies() disableLocalStorage() - const sessionStoreStrategyType = selectSessionStoreStrategyType({ - ...DEFAULT_INIT_CONFIGURATION, - sessionPersistence: [SessionPersistence.COOKIE, SessionPersistence.LOCAL_STORAGE], - }) + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration({ + ...DEFAULT_INIT_CONFIGURATION, + sessionPersistence: [SessionPersistence.COOKIE, SessionPersistence.LOCAL_STORAGE], + }) + ) expect(sessionStoreStrategyType).toBeUndefined() }) - it('handles empty array', () => { - const sessionStoreStrategyType = selectSessionStoreStrategyType({ - ...DEFAULT_INIT_CONFIGURATION, - sessionPersistence: [], - }) + it('handles empty array', async () => { + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration({ ...DEFAULT_INIT_CONFIGURATION, sessionPersistence: [] }) + ) expect(sessionStoreStrategyType).toBeUndefined() }) - it('handles array with single element', () => { - const sessionStoreStrategyType = selectSessionStoreStrategyType({ - ...DEFAULT_INIT_CONFIGURATION, - sessionPersistence: [SessionPersistence.LOCAL_STORAGE], - }) + it('handles array with single element', async () => { + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration({ ...DEFAULT_INIT_CONFIGURATION, sessionPersistence: [SessionPersistence.LOCAL_STORAGE] }) + ) expect(sessionStoreStrategyType).toEqual(jasmine.objectContaining({ type: SessionPersistence.LOCAL_STORAGE })) }) - it('stops at first available strategy and does not try subsequent ones', () => { - const sessionStoreStrategyType = selectSessionStoreStrategyType({ - ...DEFAULT_INIT_CONFIGURATION, - sessionPersistence: [SessionPersistence.LOCAL_STORAGE, SessionPersistence.COOKIE], - }) + it('stops at first available strategy and does not try subsequent ones', async () => { + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration({ + ...DEFAULT_INIT_CONFIGURATION, + sessionPersistence: [SessionPersistence.LOCAL_STORAGE, SessionPersistence.COOKIE], + }) + ) // Should return local storage (first available), not cookie expect(sessionStoreStrategyType).toEqual(jasmine.objectContaining({ type: SessionPersistence.LOCAL_STORAGE })) }) - it('returns undefined and logs error if array contains invalid persistence type', () => { + it('returns undefined and logs error if array contains invalid persistence type', async () => { const displayErrorSpy = spyOn(display, 'error') - const sessionStoreStrategyType = selectSessionStoreStrategyType({ - ...DEFAULT_INIT_CONFIGURATION, - sessionPersistence: ['invalid'] as unknown as SessionPersistence[], - }) + const sessionStoreStrategyType = await selectSessionStoreStrategyType( + makeConfiguration({ + ...DEFAULT_INIT_CONFIGURATION, + sessionPersistence: ['invalid'] as unknown as SessionPersistence[], + }) + ) expect(sessionStoreStrategyType).toBeUndefined() expect(displayErrorSpy).toHaveBeenCalledOnceWith("Invalid session persistence 'invalid'") }) @@ -144,6 +167,7 @@ describe('session store', () => { function disableCookies() { spyOnProperty(document, 'cookie', 'get').and.returnValue('') + replaceMockable((window as CookieStoreWindow).cookieStore, undefined) } function disableLocalStorage() { spyOn(Storage.prototype, 'getItem').and.throwError('unavailable') diff --git a/packages/core/src/domain/session/sessionStore.ts b/packages/core/src/domain/session/sessionStore.ts index 44651f0c59..a289e2d1b9 100644 --- a/packages/core/src/domain/session/sessionStore.ts +++ b/packages/core/src/domain/session/sessionStore.ts @@ -1,4 +1,4 @@ -import type { Configuration, InitConfiguration } from '../configuration' +import type { Configuration } from '../configuration' import { isWorkerEnvironment } from '../../tools/globalObject' import { display } from '../../tools/display' import { SessionPersistence } from './sessionConstants' @@ -12,15 +12,13 @@ import { selectMemorySessionStoreStrategy, initMemorySessionStoreStrategy } from * availability. When an array is provided, tries each persistence type in order until one * successfully initializes. */ -export function selectSessionStoreStrategyType( - initConfiguration: InitConfiguration -): SessionStoreStrategyType | undefined { - const { sessionPersistence } = initConfiguration - - const persistenceList = normalizePersistenceList(sessionPersistence) +export async function selectSessionStoreStrategyType( + configuration: Configuration +): Promise { + const persistenceList = normalizePersistenceList(configuration.sessionPersistence) for (const persistence of persistenceList) { - const strategyType = selectStrategyForPersistence(persistence, initConfiguration) + const strategyType = await selectStrategyForPersistence(persistence, configuration) if (strategyType !== undefined) { return strategyType } @@ -51,11 +49,11 @@ function normalizePersistenceList( function selectStrategyForPersistence( persistence: SessionPersistence, - initConfiguration: InitConfiguration -): SessionStoreStrategyType | undefined { + configuration: Configuration +): Promise | SessionStoreStrategyType | undefined { switch (persistence) { case SessionPersistence.COOKIE: - return selectCookieStrategy(initConfiguration) + return selectCookieStrategy(configuration) case SessionPersistence.LOCAL_STORAGE: return selectLocalStorageStrategy() @@ -75,7 +73,7 @@ export function getSessionStoreStrategy( ): SessionStoreStrategy { switch (sessionStoreStrategyType.type) { case SessionPersistence.COOKIE: - return initCookieStrategy(sessionStoreStrategyType.cookieOptions, configuration) + return initCookieStrategy(sessionStoreStrategyType, configuration) case SessionPersistence.LOCAL_STORAGE: return initLocalStorageStrategy(configuration) case SessionPersistence.MEMORY: diff --git a/packages/core/src/domain/session/storeStrategies/sessionInCookie.spec.ts b/packages/core/src/domain/session/storeStrategies/sessionInCookie.spec.ts index f4218600f7..eec1901a4a 100644 --- a/packages/core/src/domain/session/storeStrategies/sessionInCookie.spec.ts +++ b/packages/core/src/domain/session/storeStrategies/sessionInCookie.spec.ts @@ -1,11 +1,11 @@ import { registerCleanupTask, replaceMockable, mockCookies, collectAsyncCalls } from '../../../../test' -import { createCookieAccess } from '../../../browser/cookieAccess' +import type { CookieStoreWindow } from '../../../browser/browser.types' import { Observable } from '../../../tools/observable' import type { SessionState } from '../sessionState' import type { Configuration, InitConfiguration } from '../../configuration' -import { SESSION_COOKIE_EXPIRATION_DELAY, SESSION_TIME_OUT_DELAY } from '../sessionConstants' -import { LEGACY_SESSION_STORE_KEY } from './sessionStoreStrategy' -import { buildCookieOptions, selectCookieStrategy, initCookieStrategy } from './sessionInCookie' +import { SESSION_COOKIE_EXPIRATION_DELAY, SESSION_TIME_OUT_DELAY, SessionPersistence } from '../sessionConstants' +import { CookieApi, LEGACY_SESSION_STORE_KEY } from './sessionStoreStrategy' +import { buildCookieOptions, createCookieAccess, selectCookieStrategy, initCookieStrategy } from './sessionInCookie' const DEFAULT_INIT_CONFIGURATION = { clientToken: 'abc', trackAnonymousUser: true } @@ -55,7 +55,10 @@ function setupCookieStrategy(partialInitConfiguration: Partial mockCookieAccess) return { - strategy: initCookieStrategy(cookieOptions, configuration), + strategy: initCookieStrategy( + { type: SessionPersistence.COOKIE, cookieOptions, cookieApi: CookieApi.DOCUMENT_COOKIE }, + configuration + ), cookieOptions, mockCookie, } @@ -257,10 +260,45 @@ describe('session in cookie strategy', () => { }) describe('selectCookieStrategy', () => { - it('should return defined when cookies are authorized', () => { + function makeConfiguration(): Configuration { + const cookieOptions = buildCookieOptions({ clientToken: 'abc' }) + return { cookieOptions } as Configuration + } + + function disableCookieStore() { + replaceMockable((window as CookieStoreWindow).cookieStore, undefined) + } + + function disableDocumentCookie() { + spyOnProperty(document, 'cookie', 'get').and.returnValue('') + } + + it('returns cookieStore strategy when both APIs are available', async () => { + if (!(window as CookieStoreWindow).cookieStore) { + pending('CookieStore API not available') + } mockCookies() - const strategy = selectCookieStrategy({ clientToken: 'abc' }) - expect(strategy).toBeDefined() + const strategy = await selectCookieStrategy(makeConfiguration()) + expect(strategy).toEqual(jasmine.objectContaining({ cookieApi: 'cookieStore' })) + }) + + it('falls back to document.cookie when CookieStore is unavailable', async () => { + disableCookieStore() + mockCookies() + const strategy = await selectCookieStrategy(makeConfiguration()) + expect(strategy).toEqual(jasmine.objectContaining({ cookieApi: 'documentCookie' })) + }) + + it('returns undefined when both APIs are unavailable', async () => { + disableCookieStore() + disableDocumentCookie() + const strategy = await selectCookieStrategy(makeConfiguration()) + expect(strategy).toBeUndefined() + }) + + it('returns undefined when cookieOptions is undefined', async () => { + const strategy = await selectCookieStrategy({ cookieOptions: undefined } as unknown as Configuration) + expect(strategy).toBeUndefined() }) }) diff --git a/packages/core/src/domain/session/storeStrategies/sessionInCookie.ts b/packages/core/src/domain/session/storeStrategies/sessionInCookie.ts index 60e53d25cf..990a347515 100644 --- a/packages/core/src/domain/session/storeStrategies/sessionInCookie.ts +++ b/packages/core/src/domain/session/storeStrategies/sessionInCookie.ts @@ -1,6 +1,6 @@ import { isEmptyObject } from '../../../tools/utils/objectUtils' import type { CookieOptions } from '../../../browser/cookie' -import { getCurrentSite, areCookiesAuthorized, getCookies } from '../../../browser/cookie' +import { getCurrentSite, getCookies } from '../../../browser/cookie' import type { Configuration, InitConfiguration } from '../../configuration' import { SESSION_COOKIE_EXPIRATION_DELAY, SESSION_TIME_OUT_DELAY, SessionPersistence } from '../sessionConstants' import type { SessionState } from '../sessionState' @@ -8,27 +8,57 @@ import { toSessionString, toSessionState } from '../sessionState' import { Observable } from '../../../tools/observable' import { mockable } from '../../../tools/mockable' import { monitorError } from '../../../tools/monitor' -import { createCookieAccess } from '../../../browser/cookieAccess' -import type { SessionStoreStrategy, SessionStoreStrategyType, SessionObservableEvent } from './sessionStoreStrategy' -import { SESSION_STORE_KEY, LEGACY_SESSION_STORE_KEY } from './sessionStoreStrategy' +import type { CookieAccess } from '../../../browser/cookieAccess' +import { + areCookiesAuthorized, + createCookieStoreAccess, + createDocumentCookieAccess, +} from '../../../browser/cookieAccess' +import type { CookieStoreWindow } from '../../../browser/browser.types' +import type { + CookieSessionStoreStrategyType, + SessionStoreStrategy, + SessionStoreStrategyType, + SessionObservableEvent, +} from './sessionStoreStrategy' +import { CookieApi, SESSION_STORE_KEY, LEGACY_SESSION_STORE_KEY } from './sessionStoreStrategy' const SESSION_COOKIE_VERSION = 0 -export function selectCookieStrategy(initConfiguration: InitConfiguration): SessionStoreStrategyType | undefined { - const cookieOptions = buildCookieOptions(initConfiguration) - return cookieOptions && areCookiesAuthorized(cookieOptions) - ? { type: SessionPersistence.COOKIE, cookieOptions } - : undefined +export async function selectCookieStrategy( + configuration: Configuration +): Promise { + const { cookieOptions } = configuration + if (!cookieOptions) { + return undefined + } + + if ( + mockable((window as CookieStoreWindow).cookieStore) && + (await areCookiesAuthorized(createCookieStoreAccess, cookieOptions, configuration)) + ) { + return { type: SessionPersistence.COOKIE, cookieOptions, cookieApi: CookieApi.COOKIE_STORE } + } + + if (await areCookiesAuthorized(createDocumentCookieAccess, cookieOptions, configuration)) { + return { type: SessionPersistence.COOKIE, cookieOptions, cookieApi: CookieApi.DOCUMENT_COOKIE } + } + + return undefined } // Promise chain serializes calls when Web Locks are unavailable let pendingChain: Promise | undefined -export function initCookieStrategy(cookieOptions: CookieOptions, configuration: Configuration): SessionStoreStrategy { +export function initCookieStrategy( + sessionStoreStrategyType: CookieSessionStoreStrategyType, + configuration: Configuration +): SessionStoreStrategy { + const { cookieOptions, cookieApi } = sessionStoreStrategyType const sessionObservable = new Observable() const trackAnonymousUser = !!configuration.trackAnonymousUser const opts = encodeCookieOptions(cookieOptions) - const cookieAccess = mockable(createCookieAccess)(SESSION_STORE_KEY, configuration, cookieOptions) + const cookieAccess = mockable(createCookieAccess)(cookieApi, configuration, cookieOptions) let isFirstCall = true cookieAccess.observable.subscribe(() => { @@ -70,6 +100,16 @@ export function initCookieStrategy(cookieOptions: CookieOptions, configuration: } } +export function createCookieAccess( + cookieApi: CookieApi, + configuration: Configuration, + cookieOptions: CookieOptions +): CookieAccess { + return cookieApi === CookieApi.COOKIE_STORE + ? createCookieStoreAccess(SESSION_STORE_KEY, cookieOptions, configuration) + : createDocumentCookieAccess(SESSION_STORE_KEY, cookieOptions) +} + function findMatchingSessionState(items: string[], opts: string): SessionState { let sessionState: SessionState | undefined diff --git a/packages/core/src/domain/session/storeStrategies/sessionStoreStrategy.ts b/packages/core/src/domain/session/storeStrategies/sessionStoreStrategy.ts index 8095b265a3..f20821027c 100644 --- a/packages/core/src/domain/session/storeStrategies/sessionStoreStrategy.ts +++ b/packages/core/src/domain/session/storeStrategies/sessionStoreStrategy.ts @@ -6,8 +6,20 @@ import type { Observable } from '../../../tools/observable' export const SESSION_STORE_KEY = '_dd_s_v2' export const LEGACY_SESSION_STORE_KEY = '_dd_s' +export const CookieApi = { + COOKIE_STORE: 'cookieStore', + DOCUMENT_COOKIE: 'documentCookie', +} as const +export type CookieApi = (typeof CookieApi)[keyof typeof CookieApi] + +export interface CookieSessionStoreStrategyType { + type: typeof SessionPersistence.COOKIE + cookieOptions: CookieOptions + cookieApi: CookieApi +} + export type SessionStoreStrategyType = - | { type: typeof SessionPersistence.COOKIE; cookieOptions: CookieOptions } + | CookieSessionStoreStrategyType | { type: typeof SessionPersistence.LOCAL_STORAGE } | { type: typeof SessionPersistence.MEMORY } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d5695e8e45..07a90c4f9c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -102,14 +102,7 @@ export { } from './domain/error/error' export { NonErrorPrefix } from './domain/error/error.types' export type { Context, ContextArray, ContextValue } from './tools/serialisation/context' -export { - areCookiesAuthorized, - getCookie, - getInitCookie, - setCookie, - deleteCookie, - resetInitCookies, -} from './browser/cookie' +export { getCookie, getInitCookie, setCookie, deleteCookie, resetInitCookies } from './browser/cookie' export type { CookieStore, WeakRef, WeakRefConstructor } from './browser/browser.types' export type { XhrCompleteContext, XhrStartContext, XhrContext } from './browser/xhrObservable' export { initXhrObservable } from './browser/xhrObservable'