Skip to content

Add wildcard support to outbound-domains#63

Merged
steven-tey merged 2 commits intomainfrom
wildcard-outbound-domains
Sep 4, 2025
Merged

Add wildcard support to outbound-domains#63
steven-tey merged 2 commits intomainfrom
wildcard-outbound-domains

Conversation

@steven-tey
Copy link
Contributor

@steven-tey steven-tey commented Sep 4, 2025

Summary by CodeRabbit

  • New Features

    • Outbound link tracking now supports wildcard domains (e.g., *.wildcard.com), including handling of www-prefixed subdomains.
    • Outbound demo page includes sample links to validate wildcard and non-wildcard behavior.
  • Tests

    • Added tests verifying tracking parameters are applied to wildcard domains and ignored for non-matching domains.
    • Added tests confirming correct handling of www-prefixed wildcard subdomains.
    • Removed an unused import in the test suite.

@vercel
Copy link

vercel bot commented Sep 4, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
analytics-nextjs-geolocation-script Ready Ready Preview Comment Sep 4, 2025 8:25pm

@coderabbitai
Copy link

coderabbitai bot commented Sep 4, 2025

Walkthrough

Added wildcard domain support for outbound link tracking by matching patterns like *.wildcard.com; updated Next.js layout config and outbound page test links; and expanded tests and script logic to ensure wildcard and www‑prefixed subdomains are matched.

Changes

Cohort / File(s) Summary of changes
Config: Outbound domains list
apps/nextjs/app/layout.tsx
Appended wildcard domain *.wildcard.com to the outbound domains configuration passed to DubAnalytics.
UI: Test links for wildcard coverage
apps/nextjs/app/outbound/page.tsx
Added a “Wildcard domain test links” block with anchors for api.wildcard.com, admin.wildcard.com, deep.nested.wildcard.com, wildcard.com, notwildcard.com, www.api.wildcard.com, and www.admin.wildcard.com.
Logic: Wildcard domain matching
packages/script/src/extensions/outbound-domains.js
Updated isMatchingDomain to treat domains starting with *. as suffix matches (checks hostname endsWith apex domain) while preserving existing normalization and exact-match behavior for non-wildcards.
Tests: Wildcard behavior
apps/nextjs/tests/outbound-domains.spec.ts
Removed an unused import. Extended an existing test to include api.wildcard.com and added tests verifying wildcard matching for nested subdomains and www.-prefixed hosts.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant P as Outbound Page
  participant S as Outbound Script
  participant M as isMatchingDomain

  U->>P: GET /outbound?dub_id=...
  P->>S: Init analytics with domains (incl. "*.wildcard.com")
  S->>P: Scan anchors

  loop per anchor
    S->>M: isMatchingDomain(configDomain, anchor.href)
    alt configDomain starts with "*."
      Note right of M #eef3ff: New path — match by suffix\nhostname.endsWith(apexDomain)
      M-->>S: true/false
    else
      Note right of M #fff7e6: Existing path — normalize and exact match
      M-->>S: true/false
    end
    alt match == true
      S->>P: Append dub_id to href
    else
      S-->>P: Leave href unchanged
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I twitch my ears at dots and stars,
A wildcard hops through subdomain bars—
From api to deep we bound,
With dub_id carrots neatly found.
www? A wiggle, still we see—
Links align and tests agree. 🐇


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 36d3990 and 2c638d9.

📒 Files selected for processing (1)
  • packages/script/src/extensions/outbound-domains.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/script/src/extensions/outbound-domains.js
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch wildcard-outbound-domains

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/script/src/extensions/outbound-domains.js (1)

26-27: Exact-match path is fine; consider normalizing case to be robust.

URL.hostnames are lowercased, but config values may not be. Consider lowercasing in normalizeDomain for consistency.

Outside-range helper suggestion:

function normalizeDomain(domain) {
  return domain.toLowerCase().replace(/^www\./, '').replace(/\.$/, '').trim();
}
apps/nextjs/app/layout.tsx (1)

28-29: Confirm requirement for apex matching with wildcard.

If you pick Option B (exclude apex) in the script, add "wildcard.com" explicitly to outbound domains. If you pick Option A (include apex), current config is sufficient but implicitly relies on that behavior. Please confirm product intent.

apps/nextjs/tests/outbound-domains.spec.ts (2)

30-34: Prefer locators + attribute assertions to remove flakiness and extra waits

Use Playwright’s auto-waiting instead of manual element handles. This also adds a regex boundary to avoid false positives or duplicates.

-    // Also verify wildcard domain functionality
-    const wildcardLink = await page.$('a[href*="api.wildcard.com"]');
-    const wildcardHref = await wildcardLink?.getAttribute('href');
-    expect(wildcardHref).toContain('dub_id=test-click-id');
+    // Also verify wildcard domain functionality
+    await expect(
+      page.locator('a[href*="://api.wildcard.com"]'),
+    ).toHaveAttribute('href', /[?&]dub_id=test-click-id(&|$)/);

191-210: Simplify with locators and drop arbitrary timeout

Same benefits: auto-wait, clearer failures, and boundary-safe regex.

-    await page.waitForTimeout(1000);
-
-    // Test wildcard domain with www prefix
-    const wwwWildcardLink = await page.$('a[href*="www.api.wildcard.com"]');
-    const wwwWildcardSubdomainLink = await page.$(
-      'a[href*="www.admin.wildcard.com"]',
-    );
-
-    const wwwWildcardHref = await wwwWildcardLink?.getAttribute('href');
-    const wwwWildcardSubdomainHref =
-      await wwwWildcardSubdomainLink?.getAttribute('href');
-
-    // www. prefix should be ignored, so these should still match wildcard pattern
-    expect(wwwWildcardHref).toContain('dub_id=test-click-id');
-    expect(wwwWildcardSubdomainHref).toContain('dub_id=test-click-id');
+    // Test wildcard domain with www prefix
+    const wwwApi = page.locator('a[href*="://www.api.wildcard.com"]');
+    const wwwAdmin = page.locator('a[href*="://www.admin.wildcard.com"]');
+    const hasDubId = /[?&]dub_id=test-click-id(&|$)/;
+
+    // www. prefix should be ignored, so these should still match wildcard pattern
+    await expect(wwwApi).toHaveAttribute('href', hasDubId);
+    await expect(wwwAdmin).toHaveAttribute('href', hasDubId);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 031bded and 36d3990.

📒 Files selected for processing (4)
  • apps/nextjs/app/layout.tsx (1 hunks)
  • apps/nextjs/app/outbound/page.tsx (1 hunks)
  • apps/nextjs/tests/outbound-domains.spec.ts (2 hunks)
  • packages/script/src/extensions/outbound-domains.js (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Vade Review
🔇 Additional comments (2)
packages/script/src/extensions/outbound-domains.js (1)

21-25: Enforce dot-boundary in wildcard matching
The current endsWith(normalizedDomain.slice(2)) will match “notwildcard.com” or “evilwildcard.com.” Replace it with one of the following:

Option A – include apex match:

-      if (normalizedDomain.startsWith('*.')) {
-        return normalizedUrlHostname.endsWith(normalizedDomain.slice(2));
-      }
+      if (normalizedDomain.startsWith('*.')) {
+        const suffix = normalizedDomain.slice(2);
+        return (
+          normalizedUrlHostname === suffix ||
+          normalizedUrlHostname.endsWith(`.${suffix}`)
+        );
+      }

Option B – strict “*.domain.com” semantics (exclude apex):

-      if (normalizedDomain.startsWith('*.')) {
-        return normalizedUrlHostname.endsWith(normalizedDomain.slice(2));
-      }
+      if (normalizedDomain.startsWith('*.')) {
+        const suffix = normalizedDomain.slice(2);
+        return normalizedUrlHostname.endsWith(`.${suffix}`);
+      }

Which behavior is intended for *.wildcard.com: should it match the apex “wildcard.com” (Option A) or only subdomains (Option B)?

apps/nextjs/app/outbound/page.tsx (1)

19-26: Enforce dot-boundary matching for ‘*.wildcard.com’
Ensure the outbound-domain matcher treats ‘*.wildcard.com’ as matching only true subdomains (e.g. api.wildcard.com) and not notwildcard.com. Verify and update the analytics wildcard logic to include the leading “.” boundary.

Comment on lines +156 to +189

test('should handle wildcard domains correctly', async ({ page }) => {
await page.goto('/outbound?dub_id=test-click-id');
await page.waitForFunction(() => window._dubAnalytics !== undefined);

await page.waitForTimeout(1000);

// Test wildcard domain matching - *.wildcard.com should match all subdomains
const wildcardLink = await page.$('a[href*="api.wildcard.com"]');
const wildcardSubdomainLink = await page.$('a[href*="admin.wildcard.com"]');
const wildcardNestedLink = await page.$(
'a[href*="deep.nested.wildcard.com"]',
);
const wildcardRootLink = await page.$('a[href*="wildcard.com"]');

// Test non-wildcard domain that shouldn't match wildcard pattern
const nonWildcardLink = await page.$('a[href*="notwildcard.com"]');

const wildcardHref = await wildcardLink?.getAttribute('href');
const wildcardSubdomainHref =
await wildcardSubdomainLink?.getAttribute('href');
const wildcardNestedHref = await wildcardNestedLink?.getAttribute('href');
const wildcardRootHref = await wildcardRootLink?.getAttribute('href');
const nonWildcardHref = await nonWildcardLink?.getAttribute('href');

// All wildcard.com subdomains should have tracking
expect(wildcardHref).toContain('dub_id=test-click-id');
expect(wildcardSubdomainHref).toContain('dub_id=test-click-id');
expect(wildcardNestedHref).toContain('dub_id=test-click-id');
expect(wildcardRootHref).toContain('dub_id=test-click-id');

// Non-wildcard domain should not have tracking
expect(nonWildcardHref).not.toContain('dub_id=test-click-id');
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Apex-domain selector is ambiguous; may match subdomains and cause flaky failures

'a[href*="wildcard.com"]' can resolve to any subdomain and even collide with non-targets depending on order. Also replace manual timeouts with auto-waits and assert using regex boundaries.

-    await page.waitForTimeout(1000);
+    // Rely on auto-waiting expectations instead of arbitrary sleeps.

-    // Test wildcard domain matching - *.wildcard.com should match all subdomains
-    const wildcardLink = await page.$('a[href*="api.wildcard.com"]');
-    const wildcardSubdomainLink = await page.$('a[href*="admin.wildcard.com"]');
-    const wildcardNestedLink = await page.$(
-      'a[href*="deep.nested.wildcard.com"]',
-    );
-    const wildcardRootLink = await page.$('a[href*="wildcard.com"]');
+    // Test wildcard domain matching - *.wildcard.com should match all subdomains
+    const api = page.locator('a[href*="://api.wildcard.com"]');
+    const admin = page.locator('a[href*="://admin.wildcard.com"]');
+    const nested = page.locator('a[href*="://deep.nested.wildcard.com"]');
+    // Apex only; avoid matching subdomains
+    const apex = page
+      .locator('a[href="https://wildcard.com"], a[href="https://wildcard.com/"]')
+      .first();

-    // Test non-wildcard domain that shouldn't match wildcard pattern
-    const nonWildcardLink = await page.$('a[href*="notwildcard.com"]');
+    // Test non-wildcard domain that shouldn't match wildcard pattern
+    const nonWildcard = page.locator('a[href*="://notwildcard.com"]');

-    const wildcardHref = await wildcardLink?.getAttribute('href');
-    const wildcardSubdomainHref =
-      await wildcardSubdomainLink?.getAttribute('href');
-    const wildcardNestedHref = await wildcardNestedLink?.getAttribute('href');
-    const wildcardRootHref = await wildcardRootLink?.getAttribute('href');
-    const nonWildcardHref = await nonWildcardLink?.getAttribute('href');
+    const hasDubId = /[?&]dub_id=test-click-id(&|$)/;

-    // All wildcard.com subdomains should have tracking
-    expect(wildcardHref).toContain('dub_id=test-click-id');
-    expect(wildcardSubdomainHref).toContain('dub_id=test-click-id');
-    expect(wildcardNestedHref).toContain('dub_id=test-click-id');
-    expect(wildcardRootHref).toContain('dub_id=test-click-id');
+    // All wildcard.com subdomains should have tracking
+    await expect(api).toHaveAttribute('href', hasDubId);
+    await expect(admin).toHaveAttribute('href', hasDubId);
+    await expect(nested).toHaveAttribute('href', hasDubId);
+    await expect(apex).toHaveAttribute('href', hasDubId);

-    // Non-wildcard domain should not have tracking
-    expect(nonWildcardHref).not.toContain('dub_id=test-click-id');
+    // Non-wildcard domain should not have tracking
+    await expect(nonWildcard).not.toHaveAttribute('href', hasDubId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('should handle wildcard domains correctly', async ({ page }) => {
await page.goto('/outbound?dub_id=test-click-id');
await page.waitForFunction(() => window._dubAnalytics !== undefined);
await page.waitForTimeout(1000);
// Test wildcard domain matching - *.wildcard.com should match all subdomains
const wildcardLink = await page.$('a[href*="api.wildcard.com"]');
const wildcardSubdomainLink = await page.$('a[href*="admin.wildcard.com"]');
const wildcardNestedLink = await page.$(
'a[href*="deep.nested.wildcard.com"]',
);
const wildcardRootLink = await page.$('a[href*="wildcard.com"]');
// Test non-wildcard domain that shouldn't match wildcard pattern
const nonWildcardLink = await page.$('a[href*="notwildcard.com"]');
const wildcardHref = await wildcardLink?.getAttribute('href');
const wildcardSubdomainHref =
await wildcardSubdomainLink?.getAttribute('href');
const wildcardNestedHref = await wildcardNestedLink?.getAttribute('href');
const wildcardRootHref = await wildcardRootLink?.getAttribute('href');
const nonWildcardHref = await nonWildcardLink?.getAttribute('href');
// All wildcard.com subdomains should have tracking
expect(wildcardHref).toContain('dub_id=test-click-id');
expect(wildcardSubdomainHref).toContain('dub_id=test-click-id');
expect(wildcardNestedHref).toContain('dub_id=test-click-id');
expect(wildcardRootHref).toContain('dub_id=test-click-id');
// Non-wildcard domain should not have tracking
expect(nonWildcardHref).not.toContain('dub_id=test-click-id');
});
test('should handle wildcard domains correctly', async ({ page }) => {
await page.goto('/outbound?dub_id=test-click-id');
await page.waitForFunction(() => window._dubAnalytics !== undefined);
// Rely on auto-waiting expectations instead of arbitrary sleeps.
// Test wildcard domain matching - *.wildcard.com should match all subdomains
const api = page.locator('a[href*="://api.wildcard.com"]');
const admin = page.locator('a[href*="://admin.wildcard.com"]');
const nested = page.locator('a[href*="://deep.nested.wildcard.com"]');
// Apex only; avoid matching subdomains
const apex = page
.locator('a[href="https://wildcard.com"], a[href="https://wildcard.com/"]')
.first();
// Test non-wildcard domain that shouldn't match wildcard pattern
const nonWildcard = page.locator('a[href*="://notwildcard.com"]');
const hasDubId = /[?&]dub_id=test-click-id(&|$)/;
// All wildcard.com subdomains should have tracking
await expect(api).toHaveAttribute('href', hasDubId);
await expect(admin).toHaveAttribute('href', hasDubId);
await expect(nested).toHaveAttribute('href', hasDubId);
await expect(apex).toHaveAttribute('href', hasDubId);
// Non-wildcard domain should not have tracking
await expect(nonWildcard).not.toHaveAttribute('href', hasDubId);
});
🤖 Prompt for AI Agents
In apps/nextjs/tests/outbound-domains.spec.ts around lines 156 to 189, the
selector 'a[href*="wildcard.com"]' is ambiguous and can match subdomains or
unintended links, and the test uses a manual waitForTimeout; change selectors to
verify link hostnames explicitly (parse each anchor href with new URL(...) and
assert hostname === 'wildcard.com' or hostname.endsWith('.wildcard.com') for
wildcard cases), replace the generic 'a[href*="wildcard.com"]' with precise
queries (e.g., locate anchors then filter by parsed hostname), ensure the
non-wildcard case checks hostname does not match '*.wildcard.com', replace
waitForTimeout with deterministic waits like waitForSelector or waitForFunction
that _dubAnalytics and the specific anchors are present, and change the
assertions to use regex boundary checks (e.g., match dub_id=test-click-id as a
whole token) to avoid false positives.

@steven-tey steven-tey merged commit 0b6f8ae into main Sep 4, 2025
5 checks passed
@steven-tey steven-tey deleted the wildcard-outbound-domains branch September 4, 2025 21:33
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.

1 participant