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;