From 264172adc5a03b58aab2803a43094aad35abf9fe Mon Sep 17 00:00:00 2001 From: Phillip Carter Date: Thu, 4 Jun 2026 19:45:45 -0700 Subject: [PATCH] Fix Zoom meeting links from Google Calendar invites When Google Calendar wraps meeting URLs (especially Zoom links) in redirects, the app now automatically unwraps them so users can click directly to join meetings instead of getting broken redirect links. The fix: - Added unwrapGoogleCalendarUrl() to extract the real URL from Google's redirect wrapper pattern (q parameter in google.com/url) - Added unwrapGoogleUrlsInHtml() to process all href attributes in email HTML - Applied unwrapping before linkifying so users get direct meeting links - Added comprehensive tests for Zoom URLs, regular URLs, and HTML-escaped chars This fixes the issue where Zoom meeting links from calendar invites would redirect through google.com/url and fail to activate the meeting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/lib/__tests__/emailHtml.test.ts | 38 +++++++++++++++++++++++++++++ src/lib/emailHtml.ts | 37 ++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/lib/__tests__/emailHtml.test.ts b/src/lib/__tests__/emailHtml.test.ts index 0f9b9b6..e56ebeb 100644 --- a/src/lib/__tests__/emailHtml.test.ts +++ b/src/lib/__tests__/emailHtml.test.ts @@ -392,4 +392,42 @@ describe("linkifyHtmlText (via prepareHtml)", () => { const result = prepareHtml(wrap("

hello

")); assert.ok(result.includes("a{cursor:pointer}"), "cursor:pointer must be in the injected style"); }); + + it("unwraps Google Calendar redirect URLs in href attributes", () => { + const zoomUrl = "https://coreweave.zoom.us/j/86296412870"; + const googleRedirectUrl = `https://www.google.com/url?q=${encodeURIComponent(zoomUrl)}&sa=D&source=calendar&ust=1780956240000000&usg=AOvVaw2hpQdEyAOVox_UyoWKRPB1`; + const email = wrap(`Join Zoom Meeting`); + const result = prepareHtml(email); + assert.ok( + result.includes(`href="${zoomUrl}"`), + "Google Calendar redirect should be unwrapped to the direct Zoom URL" + ); + assert.ok( + !result.includes("google.com/url"), + "Google redirect wrapper should be removed" + ); + }); + + it("preserves non-Google URLs unchanged", () => { + const regularUrl = "https://example.com/meeting"; + const email = wrap(`Click here`); + const result = prepareHtml(email); + assert.ok( + result.includes(`href="${regularUrl}"`), + "regular URLs should not be modified" + ); + }); + + it("handles HTML-escaped characters in Google redirect URLs", () => { + const zoomUrl = "https://zoom.us/j/123456?pwd=xyz"; + const googleRedirectUrl = `https://www.google.com/url?q=${encodeURIComponent(zoomUrl)}&sa=D`; + // HTML-escaped version + const escapedUrl = googleRedirectUrl.replace(/&/g, "&"); + const email = wrap(`Join`); + const result = prepareHtml(email); + assert.ok( + result.includes("zoom.us/j/123456"), + "Google redirect should be unwrapped even with HTML-escaped ampersands" + ); + }); }); diff --git a/src/lib/emailHtml.ts b/src/lib/emailHtml.ts index b963411..ed889ac 100644 --- a/src/lib/emailHtml.ts +++ b/src/lib/emailHtml.ts @@ -25,6 +25,40 @@ */ import { defaultEmailRenderTheme, type EmailRenderTheme } from "./emailRenderTheme"; +// Unwrap Google Calendar redirect URLs to their actual destination. +// Google Calendar wraps meeting links (especially Zoom) like: +// https://www.google.com/url?q=&sa=D&source=calendar&... +// This function extracts the real URL so users can click directly to join meetings. +function unwrapGoogleCalendarUrl(href: string): string { + try { + // Match Google's URL wrapper pattern - the q parameter contains the actual URL + const googleUrlMatch = href.match(/[?&]q=([^&]+)/); + if (googleUrlMatch && googleUrlMatch[1]) { + const decoded = decodeURIComponent(googleUrlMatch[1]); + // Only unwrap if it's a valid http(s) URL (avoid security issues) + // and it came from a Google domain + if (/^https?:\/\//.test(decoded) && /(?:www\.)?google\.com\/url/.test(href)) { + return decoded; + } + } + } catch { + // If URL parsing fails, return original + } + return href; +} + +// Unwrap Google redirect URLs in href attributes +function unwrapGoogleUrlsInHtml(html: string): string { + // Match href="..." and href='...' attributes and unwrap their URLs + return html.replace(/\bhref=(['"])(.*?)\1/gi, (_match, quote, href) => { + const unescaped = href.replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&"); + const unwrapped = unwrapGoogleCalendarUrl(unescaped); + // Re-escape for HTML attribute + const reescaped = unwrapped.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'"); + return `href=${quote}${reescaped}${quote}`; + }); +} + // Linkify bare URLs in HTML text content at the string level (server-side), // so no browser DOM manipulation is needed. Skips content inside ,