diff --git a/apps/nextjs/app/layout.tsx b/apps/nextjs/app/layout.tsx index 6e390ed..258d9a9 100644 --- a/apps/nextjs/app/layout.tsx +++ b/apps/nextjs/app/layout.tsx @@ -25,7 +25,7 @@ export default function RootLayout({ domainsConfig={{ refer: 'getacme.link', site: 'getacme.link', - outbound: 'example.com,other.com,sub.example.com', + outbound: 'example.com,other.com,sub.example.com,*.wildcard.com', }} scriptProps={{ src: DUB_ANALYTICS_SCRIPT_URL, diff --git a/apps/nextjs/app/outbound/page.tsx b/apps/nextjs/app/outbound/page.tsx index bcda020..00071a6 100644 --- a/apps/nextjs/app/outbound/page.tsx +++ b/apps/nextjs/app/outbound/page.tsx @@ -15,6 +15,15 @@ export default function Outbound() { Subdomain Link Other Subdomain Link WWW Subdomain Link + + {/* Wildcard domain test links */} + Wildcard API Link + Wildcard Admin Link + Wildcard Nested Link + Wildcard Root Link + Non-Wildcard Link + WWW Wildcard API Link + WWW Wildcard Admin Link ); } diff --git a/apps/nextjs/tests/outbound-domains.spec.ts b/apps/nextjs/tests/outbound-domains.spec.ts index 23de133..286f869 100644 --- a/apps/nextjs/tests/outbound-domains.spec.ts +++ b/apps/nextjs/tests/outbound-domains.spec.ts @@ -1,4 +1,3 @@ -import { DUB_ANALYTICS_SCRIPT_URL } from '@/app/constants'; import { test, expect } from '@playwright/test'; declare global { @@ -27,6 +26,11 @@ test.describe('Outbound domains tracking', () => { expect(exampleHref).toContain('dub_id=test-click-id'); expect(otherHref).toContain('dub_id=test-click-id'); expect(unrelatedHref).not.toContain('dub_id=test-click-id'); + + // 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'); }); test('should handle iframe src attributes', async ({ page }) => { @@ -149,4 +153,59 @@ test.describe('Outbound domains tracking', () => { const iframeSrc = await iframe?.getAttribute('src'); expect(iframeSrc).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); + + 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 with www prefix 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 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'); + }); }); diff --git a/packages/script/src/extensions/outbound-domains.js b/packages/script/src/extensions/outbound-domains.js index 2b59412..b0119f1 100644 --- a/packages/script/src/extensions/outbound-domains.js +++ b/packages/script/src/extensions/outbound-domains.js @@ -18,7 +18,13 @@ const initOutboundDomains = () => { const normalizedUrlHostname = normalizeDomain(urlHostname); const normalizedDomain = normalizeDomain(domain); - // Exact match after removing www. + // if wildcard domain, check if the url hostname ends with the apex domain + if (normalizedDomain.startsWith('*.')) { + const apexDomain = normalizedDomain.slice(2); + return normalizedUrlHostname.endsWith(`.${apexDomain}`); + } + + // check for exact match after removing www. return normalizedUrlHostname === normalizedDomain; } catch (e) { return false;