-
Notifications
You must be signed in to change notification settings - Fork 60
Amp 149016 global is enabled locked #1562
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
55f0c4b
ba83071
4ee809d
967a29e
043dfda
6371d99
03066b7
8ca68ac
dbe7c7d
7391d3d
f9ea605
b296944
c252f3b
3e642db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { test, expect } from '@playwright/test'; | ||
|
|
||
| const RUNS = 10; | ||
|
|
||
| test.describe('CookieStorage', () => { | ||
| // Regression Test to cover re-entrancy issues fixed by https://github.com/amplitude/Amplitude-TypeScript/pull/1539 | ||
| test('.isEnabled works when called multiple times concurrently', async ({ page }) => { | ||
| const consoleErrors: string[] = []; | ||
| page.on('console', (msg) => { | ||
| if (msg.type() === 'error') { | ||
| consoleErrors.push(msg.text()); | ||
| } | ||
| }); | ||
|
|
||
| const pageErrors: string[] = []; | ||
| page.on('pageerror', (err) => { | ||
| pageErrors.push(err.message); | ||
| }); | ||
|
|
||
| for (let i = 0; i < RUNS; i++) { | ||
| await page.goto('/cookies/is-enabled.html'); | ||
| expect(pageErrors, `Expected no page errors, but got: ${pageErrors.join('; ')}`).toHaveLength(0); | ||
| expect(consoleErrors, `Expected no console.error calls, but got: ${consoleErrors.join('; ')}`).toHaveLength(0); | ||
| } | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ import { | |
| isDomainEqual, | ||
| CookieStorageConfig, | ||
| decodeCookieValue, | ||
| getGlobalScope, | ||
| } from '@amplitude/analytics-core'; | ||
|
|
||
| import { LocalStorage } from './storage/local-storage'; | ||
|
|
@@ -295,7 +296,9 @@ export const useBrowserConfig = async ( | |
| const identityStorage = options.identityStorage || DEFAULT_IDENTITY_STORAGE; | ||
| const cookieOptions = { | ||
| domain: | ||
| identityStorage !== DEFAULT_IDENTITY_STORAGE ? '' : options.cookieOptions?.domain ?? (await getTopLevelDomain()), | ||
| identityStorage !== DEFAULT_IDENTITY_STORAGE | ||
| ? '' | ||
| : options.cookieOptions?.domain ?? (await getTopLevelDomain(undefined, diagnosticsClient)), | ||
| expiration: 365, | ||
| sameSite: 'Lax' as const, | ||
| secure: false, | ||
|
|
@@ -468,9 +471,51 @@ export const createTransport = (transport?: TransportTypeOrOptions) => { | |
| return new FetchTransport(headers); | ||
| }; | ||
|
|
||
| export const getTopLevelDomain = async (url?: string) => { | ||
| export const getTopLevelDomain = async (url?: string, diagnosticsClient?: IDiagnosticsClient) => { | ||
| if ( | ||
| !(await new CookieStorage<number>().isEnabled()) || | ||
| !(await new CookieStorage<number>(undefined, { diagnosticsClient }).isEnabled()) || | ||
| (!url && (typeof location === 'undefined' || !location.hostname)) | ||
| ) { | ||
| return ''; | ||
| } | ||
|
|
||
| const global = getGlobalScope(); | ||
| if (!global?.navigator?.locks) { | ||
| return legacyGetTopLevelDomain(url, diagnosticsClient); | ||
| } | ||
|
|
||
| return await global.navigator.locks.request('com/amplitude/get-top-level-domain', async () => { | ||
| const host = url ?? location.hostname; | ||
| const parts = host.split('.'); | ||
| const levels = []; | ||
| const storageKey = 'AMP_TLDTEST'; | ||
|
|
||
| for (let i = parts.length - 2; i >= 0; --i) { | ||
| levels.push(parts.slice(i).join('.')); | ||
| } | ||
| for (let i = 0; i < levels.length; i++) { | ||
| const domain = levels[i]; | ||
| const options = { domain: '.' + domain }; | ||
| const storage = new CookieStorage<number>(options); | ||
| try { | ||
| await storage.set(storageKey, 1); | ||
| const value = await storage.get(storageKey); | ||
| if (value) { | ||
| console.log('TLD -- exiting with domain:', '.' + domain); | ||
| return '.' + domain; | ||
| } | ||
| } finally { | ||
| await storage.remove(storageKey); | ||
| } | ||
| } | ||
|
|
||
| return ''; | ||
| }); | ||
| }; | ||
|
|
||
| const legacyGetTopLevelDomain = async (url?: string, diagnosticsClient?: IDiagnosticsClient) => { | ||
| if ( | ||
| !(await new CookieStorage<number>(undefined, { diagnosticsClient }).isEnabled()) || | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant isEnabled check on legacy fallback pathLow Severity
|
||
| (!url && (typeof location === 'undefined' || !location.hostname)) | ||
| ) { | ||
| return ''; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,59 @@ export class CookieStorage<T> implements Storage<T> { | |
| if (!globalScope || !globalScope.document) { | ||
| return false; | ||
| } | ||
| const { navigator } = globalScope; | ||
| if (!navigator?.locks) { | ||
| return await this.legacyIsEnabled(); | ||
| } | ||
|
|
||
| return await navigator.locks.request('com/amplitude/cookie-storage-is-enabled', async () => { | ||
| const testValue = String(Date.now()); | ||
| const testCookieOptions = { ...this.options }; | ||
| const testStorage = new CookieStorage<string>(testCookieOptions); | ||
| const testKey = 'AMP_TEST'; | ||
| try { | ||
| await testStorage.set(testKey, testValue); | ||
| const value = await testStorage.get(testKey); | ||
|
|
||
| /* istanbul ignore next */ | ||
| if (value !== testValue && this.config.diagnosticsClient) { | ||
| this.config.diagnosticsClient?.recordEvent('cookies.isEnabled.failure', { | ||
| reason: 'Test Value mismatch', | ||
| testKey, | ||
| testValue, | ||
| }); | ||
| } | ||
| console.log('isEnabled -- exiting with result:', value === testValue); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Debug console.log statements left in production codeHigh Severity Two Additional Locations (1) |
||
| return value === testValue; | ||
| } catch (e){ | ||
| /* istanbul ignore next */ | ||
| if (this.config.diagnosticsClient) { | ||
| const errMessage = e instanceof Error ? e.message : String(e); | ||
| this.config.diagnosticsClient?.recordEvent('cookies.isEnabled.failure', { | ||
| reason: 'Cookie getter/setter failed', | ||
| testKey, | ||
| testValue, | ||
| error: errMessage, | ||
| }); | ||
| } | ||
| return false; | ||
| } finally { | ||
| await testStorage.remove(testKey); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Detects if cookies are enabled by setting a test cookie and checking if it is set. | ||
| * Legacy version that doesn't use navigator.locks | ||
| * @returns boolean | ||
| */ | ||
| async legacyIsEnabled(): Promise<boolean> { | ||
| const globalScope = getGlobalScope(); | ||
| /* istanbul ignore if */ | ||
| if (!globalScope || !globalScope.document) { | ||
| return false; | ||
| } | ||
|
|
||
| const testValue = String(Date.now()); | ||
| const testCookieOptions = { | ||
|
|
@@ -46,9 +99,26 @@ export class CookieStorage<T> implements Storage<T> { | |
| try { | ||
| await testStorage.set(testKey, testValue); | ||
| const value = await testStorage.get(testKey); | ||
| /* istanbul ignore next */ | ||
| if (value !== testValue && this.config.diagnosticsClient) { | ||
| this.config.diagnosticsClient?.recordEvent('cookies.isEnabled.failure', { | ||
| reason: 'Test Value mismatch', | ||
| testKey, | ||
| testValue, | ||
| }); | ||
| } | ||
| return value === testValue; | ||
| } catch { | ||
| } catch (e) { | ||
| /* istanbul ignore next */ | ||
| if (this.config.diagnosticsClient) { | ||
| const errMessage = e instanceof Error ? e.message : String(e); | ||
| this.config.diagnosticsClient?.recordEvent('cookies.isEnabled.failure', { | ||
| reason: 'Cookie getter/setter failed', | ||
| testKey, | ||
| testValue, | ||
| error: errMessage, | ||
| }); | ||
| } | ||
| return false; | ||
| } finally { | ||
| await testStorage.remove(testKey); | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing diagnostics event that test expects to find
Medium Severity
The new test at line 468 expects
diagnosticsClient.recordEventto be called with'cookies.tld.failure'when TLD detection fails, but neithergetTopLevelDomainnorlegacyGetTopLevelDomainever records this event. Thereturn ''at the end of both functions silently discards the failure without any diagnostics reporting, causing the test to fail.Additional Locations (1)
packages/analytics-browser/test/config.test.ts#L467-L481