diff --git a/apps/nextjs/app/outbound/page.tsx b/apps/nextjs/app/outbound/page.tsx index 00071a6..f2e5aa9 100644 --- a/apps/nextjs/app/outbound/page.tsx +++ b/apps/nextjs/app/outbound/page.tsx @@ -8,6 +8,10 @@ export default function Outbound() { + {/* Cal.com style iframe with srcdoc containing nested iframes */} + + + Internal Link
diff --git a/apps/nextjs/tests/outbound-domains.spec.ts b/apps/nextjs/tests/outbound-domains.spec.ts index 286f869..06b12d2 100644 --- a/apps/nextjs/tests/outbound-domains.spec.ts +++ b/apps/nextjs/tests/outbound-domains.spec.ts @@ -45,6 +45,67 @@ test.describe('Outbound domains tracking', () => { expect(iframeSrc).toContain('dub_id=test-click-id'); }); + test('should handle nested iframes inside srcdoc (Cal.com style)', async ({ + page, + }) => { + await page.goto('/outbound?dub_id=test-click-id'); + + await page.waitForFunction(() => window._dubAnalytics !== undefined); + + await page.waitForTimeout(2500); + + // Check that nested iframes inside srcdoc get tracking parameters + // This tests the contentDocument access functionality + const nestedIframeCheck = await page.evaluate(() => { + const srcdocIframes = document.querySelectorAll('iframe[srcdoc]'); + const results = []; + + srcdocIframes.forEach((srcdocIframe, index) => { + try { + const contentDoc = srcdocIframe.contentDocument; + if (contentDoc) { + const nestedIframes = contentDoc.querySelectorAll('iframe[src]'); + nestedIframes.forEach((nestedIframe) => { + results.push({ + index, + src: nestedIframe.src, + hasTracking: nestedIframe.src.includes('dub_id=test-click-id'), + }); + }); + } + } catch (e) { + results.push({ index, error: e.message }); + } + }); + + return results; + }); + + // Verify that nested iframes were found and have tracking parameters + expect(nestedIframeCheck.length).toBeGreaterThan(0); + + // Check that at least some nested iframes have tracking + const trackedIframes = nestedIframeCheck.filter( + (result) => result.hasTracking, + ); + expect(trackedIframes.length).toBeGreaterThan(0); + + // Verify specific URLs got tracking + const exampleTracked = nestedIframeCheck.some( + (result) => + result.src && + result.src.includes('example.com/booking-widget?dub_id=test-click-id'), + ); + const otherTracked = nestedIframeCheck.some( + (result) => + result.src && + result.src.includes('other.com/calendar?dub_id=test-click-id'), + ); + + expect(exampleTracked).toBe(true); + expect(otherTracked).toBe(true); + }); + test('should not add tracking to links on the same domain', async ({ page, }) => { diff --git a/packages/script/src/extensions/outbound-domains.js b/packages/script/src/extensions/outbound-domains.js index b0119f1..8b3bd9b 100644 --- a/packages/script/src/extensions/outbound-domains.js +++ b/packages/script/src/extensions/outbound-domains.js @@ -49,9 +49,33 @@ const initOutboundDomains = () => { // Get all links and iframes const elements = document.querySelectorAll('a[href], iframe[src]'); - if (!elements || elements.length === 0) return; - elements.forEach((element) => { + // Also get nested iframes inside srcdoc iframes + const srcdocIframes = document.querySelectorAll('iframe[srcdoc]'); + const nestedElements = []; + + srcdocIframes.forEach((srcdocIframe) => { + try { + // Access the content document of the srcdoc iframe + const contentDoc = srcdocIframe.contentDocument; + if (contentDoc) { + // Find iframes and links inside the srcdoc content + const nestedIframes = contentDoc.querySelectorAll('iframe[src]'); + const nestedLinks = contentDoc.querySelectorAll('a[href]'); + + nestedElements.push(...nestedIframes, ...nestedLinks); + } + } catch (e) { + // contentDocument access might fail due to CORS or other security restrictions + console.warn('Could not access contentDocument of srcdoc iframe:', e); + } + }); + + // Combine all elements + const allElements = [...elements, ...nestedElements]; + if (!allElements || allElements.length === 0) return; + + allElements.forEach((element) => { // Skip already processed elements if (outboundLinksUpdated.has(element)) return;