Skip to content

fix: make getTopLevelDomain and isEnabled synchronous to avoid re-entrancy#1564

Merged
daniel-graham-amplitude merged 76 commits intomainfrom
AMP-149016-is-enabled-v2
Mar 5, 2026
Merged

fix: make getTopLevelDomain and isEnabled synchronous to avoid re-entrancy#1564
daniel-graham-amplitude merged 76 commits intomainfrom
AMP-149016-is-enabled-v2

Conversation

@daniel-graham-amplitude
Copy link
Collaborator

@daniel-graham-amplitude daniel-graham-amplitude commented Feb 28, 2026

Summary

(follow-up PR after #1569)

"isEnabled" and "getTopLevelDomain" both operate on 1. write cookie; 2. read cookie; 3. delete cookie

These operations are problematic if done concurrently. Our current solution we add a UUID to the cookie name to avoid collisions, but this results in more cookies being created than necessary, and seemingly, these cookies sometimes are rejected by the browser.

What this does is does away with the UUID scoping, and back to the hardcoded names. But now it wraps the operation in a "transaction" (as introduced by #1569) so that when this transaction is run, all other instances (including across tabs) are locked from reading and writing the cookie until the transaction is done.

Checklist

  • Does your PR title have the correct title format?
  • Does your PR have a breaking change?: No

Note

Medium Risk
Changes core cookie write/read/cleanup paths and domain-scoping detection; failures could impact cookie persistence and session identity, especially in browsers without navigator.locks or with strict cookie policies.

Overview
Updates browser TLD detection to stop creating per-call UUID-namespaced test cookies and instead rely on CookieStorage.isDomainWritable() for checking which parent domain can accept cookies.

Refactors CookieStorage.isEnabled() to run the write/read/cleanup test inside the new cookie transaction lock with a stable AMP_TEST key, and adjusts tests and the test-server stress page (RUNS 10→1000) to validate the new concurrency-safe behavior.

Written by Cursor Bugbot for commit 214f0e1. This will update automatically on new commits. Configure here.

@daniel-graham-amplitude daniel-graham-amplitude marked this pull request as draft February 28, 2026 02:18
@daniel-graham-amplitude daniel-graham-amplitude changed the title fix: experimental lockfiles fix: (experimental) apply navigator.locks when running cookie functions Feb 28, 2026
@daniel-graham-amplitude daniel-graham-amplitude changed the title fix: (experimental) apply navigator.locks when running cookie functions fix: (experimental) make getTopLevelDomain and isEnabled synchronous to avoid re-entrancy Mar 1, 2026
@daniel-graham-amplitude
Copy link
Collaborator Author

bugbot run

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Wrong option name in test file is silently ignored
    • Replaced the invalid _useExperimentalMutex option with _enableNextFeatures in the test server init config so the flag is actually applied.
  • ✅ Fixed: Static _enableNextFeatures flag creates race condition between instances
    • Removed per-instance mutation of the static flag in useBrowserConfig and threaded an instance-local enableNextFeatures value through getTopLevelDomain and CookieStorage.isEnabled to avoid cross-instance races.

Create PR

Or push these changes by commenting:

@cursor push b53a95ee88
Preview (b53a95ee88)
diff --git a/packages/analytics-browser/src/config.ts b/packages/analytics-browser/src/config.ts
--- a/packages/analytics-browser/src/config.ts
+++ b/packages/analytics-browser/src/config.ts
@@ -295,8 +295,7 @@
   diagnosticsClient?: IDiagnosticsClient,
   earlyConfig?: EarlyConfig,
 ): Promise<IBrowserConfig> => {
-  // set the experimental mutex flag to enable locking in CookieStorage
-  CookieStorage._enableNextFeatures = options._enableNextFeatures || false;
+  const enableNextFeatures = options._enableNextFeatures || false;
 
   // Step 1: Create identity storage instance
   const identityStorage = options.identityStorage || DEFAULT_IDENTITY_STORAGE;
@@ -305,7 +304,7 @@
   // use the getTopLevelDomain function to find the TLD only if identity storage
   // is cookie (because getTopLevelDomain() uses cookies)
   if (identityStorage === DEFAULT_IDENTITY_STORAGE) {
-    defaultCookieDomain = await getTopLevelDomain(undefined, diagnosticsClient);
+    defaultCookieDomain = await getTopLevelDomain(undefined, diagnosticsClient, enableNextFeatures);
   }
   const cookieOptions = {
     domain: options.cookieOptions?.domain ?? defaultCookieDomain,
@@ -516,14 +515,18 @@
   return '';
 };
 
-export const getTopLevelDomain = async (url?: string, diagnosticsClient?: IDiagnosticsClient) => {
+export const getTopLevelDomain = async (
+  url?: string,
+  diagnosticsClient?: IDiagnosticsClient,
+  enableNextFeatures = CookieStorage._enableNextFeatures,
+) => {
   if (
-    !(await new CookieStorage<number>(undefined, { diagnosticsClient }).isEnabled()) ||
+    !(await new CookieStorage<number>(undefined, { diagnosticsClient }).isEnabled(enableNextFeatures)) ||
     (!url && (typeof location === 'undefined' || !location.hostname))
   ) {
     return '';
   }
-  if (CookieStorage._enableNextFeatures) {
+  if (enableNextFeatures) {
     return getTopLevelDomainSync(url, diagnosticsClient);
   }
 

diff --git a/packages/analytics-core/src/storage/cookie.ts b/packages/analytics-core/src/storage/cookie.ts
--- a/packages/analytics-core/src/storage/cookie.ts
+++ b/packages/analytics-core/src/storage/cookie.ts
@@ -67,7 +67,7 @@
     }
   }
 
-  async isEnabled(): Promise<boolean> {
+  async isEnabled(enableNextFeatures = CookieStorage._enableNextFeatures): Promise<boolean> {
     const globalScope = getGlobalScope();
     /* istanbul ignore if */
     if (!globalScope || !globalScope.document) {
@@ -75,7 +75,7 @@
     }
 
     // experimental feature for now that uses navigator.locks
-    if (CookieStorage._enableNextFeatures) {
+    if (enableNextFeatures) {
       return this.isEnabledSync();
     }
 

diff --git a/test-server/autocapture/element-interactions.html b/test-server/autocapture/element-interactions.html
--- a/test-server/autocapture/element-interactions.html
+++ b/test-server/autocapture/element-interactions.html
@@ -273,7 +273,7 @@
         import.meta.env.VITE_AMPLITUDE_API_KEY,
         import.meta.env.VITE_AMPLITUDE_USER_ID || 'amplitude-typescript test user',
         {
-          _useExperimentalMutex: true,
+          _enableNextFeatures: true,
           fetchRemoteConfig: false,
           logLevel: 'debug',
           autocapture: {
This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

@daniel-graham-amplitude daniel-graham-amplitude changed the base branch from main to AMP-149016-cookie-transaction-helper-method March 2, 2026 22:01
@daniel-graham-amplitude daniel-graham-amplitude changed the title fix: (experimental) make getTopLevelDomain and isEnabled synchronous to avoid re-entrancy fix: make getTopLevelDomain and isEnabled synchronous to avoid re-entrancy Mar 4, 2026
@daniel-graham-amplitude daniel-graham-amplitude marked this pull request as ready for review March 4, 2026 19:20
Base automatically changed from AMP-149016-cookie-transaction-helper-method to main March 5, 2026 18:03
Copy link
Contributor

@Mercy811 Mercy811 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Dan, LGTM!

Co-authored-by: Xinyi Ye <xinyi.ye@amplitude.com>
@daniel-graham-amplitude daniel-graham-amplitude merged commit ab9b09f into main Mar 5, 2026
10 checks passed
@daniel-graham-amplitude daniel-graham-amplitude deleted the AMP-149016-is-enabled-v2 branch March 5, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants