From e5f5ce02f59bdfaa0d54b99fdf938f83218800cb Mon Sep 17 00:00:00 2001 From: Blossom Date: Sun, 17 May 2026 12:02:07 +0800 Subject: [PATCH] fix(app): trim URLs before openUrl http fallback (#1954) Co-authored-by: Steven Enamakel --- app/src/utils/openUrl.test.ts | 22 ++++++++++++++++++++++ app/src/utils/openUrl.ts | 12 +++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/app/src/utils/openUrl.test.ts b/app/src/utils/openUrl.test.ts index a071b7a2b..cee04e07c 100644 --- a/app/src/utils/openUrl.test.ts +++ b/app/src/utils/openUrl.test.ts @@ -122,4 +122,26 @@ describe('openUrl', () => { expect(call?.data?.url).not.toContain('secret-redact-me'); expect(call?.data?.url).not.toContain('/dashboard'); }); + + it('trims surrounding whitespace before classifying an http URL for fallback', async () => { + isTauriMock.mockReturnValue(true); + tauriOpenUrlMock.mockRejectedValue( + new TypeError("Cannot read properties of undefined (reading 'postMessage')") + ); + + const { openUrl } = await import('./openUrl'); + await openUrl(' https://tinyhumans.ai/dashboard?token=secret-redact-me '); + + expect(tauriOpenUrlMock).toHaveBeenCalledWith( + 'https://tinyhumans.ai/dashboard?token=secret-redact-me' + ); + expect(windowOpenMock).toHaveBeenCalledWith( + 'https://tinyhumans.ai/dashboard?token=secret-redact-me', + '_blank', + 'noopener,noreferrer' + ); + expect(addBreadcrumbMock).toHaveBeenCalledWith( + expect.objectContaining({ data: expect.objectContaining({ url: 'https://tinyhumans.ai' }) }) + ); + }); }); diff --git a/app/src/utils/openUrl.ts b/app/src/utils/openUrl.ts index 04541f082..9c91c0fc1 100644 --- a/app/src/utils/openUrl.ts +++ b/app/src/utils/openUrl.ts @@ -3,7 +3,7 @@ import { openUrl as tauriOpenUrl } from '@tauri-apps/plugin-opener'; import { isTauri } from './tauriCommands/common'; -const isHttpUrl = (url: string): boolean => /^https?:\/\//i.test(url); +const isHttpUrl = (url: string): boolean => /^https?:\/\//i.test(url.trim()); /** * Returns a low-PII representation of `url` for telemetry breadcrumbs. @@ -48,22 +48,24 @@ const getTelemetryUrl = (url: string): string => { * `https://` / `mailto:` links still work for dev/preview builds. */ export const openUrl = async (url: string): Promise => { + const normalizedUrl = url.trim(); + if (isTauri()) { try { - await tauriOpenUrl(url); + await tauriOpenUrl(normalizedUrl); return; } catch (err) { Sentry.addBreadcrumb({ category: 'ipc', level: 'warning', message: 'tauriOpenUrl failed; evaluating fallback', - data: { url: getTelemetryUrl(url), error: String(err) }, + data: { url: getTelemetryUrl(normalizedUrl), error: String(err) }, }); - if (!isHttpUrl(url)) { + if (!isHttpUrl(normalizedUrl)) { throw err; } // http(s) URL — safe to fall back to window.open. } } - window.open(url, '_blank', 'noopener,noreferrer'); + window.open(normalizedUrl, '_blank', 'noopener,noreferrer'); };