From ec22b709b5c2c372bae2598d7210053ca7073594 Mon Sep 17 00:00:00 2001 From: richjohnrambo Date: Wed, 14 Oct 2026 13:58:33 +0800 Subject: [PATCH] fix legacy attachment URLs in Vite web --- web/src/lib/api/file-url.ts | 27 +++++++++++++++++++++++++++ web/src/lib/api/upload.ts | 7 +++++-- web/tests/file-url.test.mjs | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 web/src/lib/api/file-url.ts create mode 100644 web/tests/file-url.test.mjs diff --git a/web/src/lib/api/file-url.ts b/web/src/lib/api/file-url.ts new file mode 100644 index 00000000..092330d1 --- /dev/null +++ b/web/src/lib/api/file-url.ts @@ -0,0 +1,27 @@ +/** + * Older clients persisted attachment refs on the API host, for example + * `https://api.example.com/v0/file/s/...`. Fetching those URLs with the file + * auth headers makes the browser forward a CORS preflight through the OSS + * redirect. Route them through the tenant's configured file endpoint instead, + * matching the relative refs emitted by current clients. + */ +export function normalizeFileDownloadUrl( + refUrl: string, + apiEndpoint: string, + fileEndpoint: string, + pageOrigin: string, +): string { + try { + const source = new URL(refUrl, pageOrigin); + const api = new URL(apiEndpoint, pageOrigin); + if (source.origin !== api.origin || !source.pathname.startsWith('/v0/file/')) return refUrl; + + const target = new URL(fileEndpoint, pageOrigin); + target.pathname = target.pathname.replace(/\/$/, '') + source.pathname.slice('/v0/file'.length); + target.search = source.search; + target.hash = source.hash; + return target.toString(); + } catch { + return refUrl; + } +} diff --git a/web/src/lib/api/upload.ts b/web/src/lib/api/upload.ts index 6ec60731..ee235f16 100644 --- a/web/src/lib/api/upload.ts +++ b/web/src/lib/api/upload.ts @@ -9,7 +9,8 @@ // at all -- so this hand-built fetch, not the SDK helper, is the only path that actually works here). import { API_KEY } from '@/lib/config'; -import { authToken, fileBase } from './client'; +import { apiBase, authToken, fileBase } from './client'; +import { normalizeFileDownloadUrl } from './file-url'; import { getTinode } from '../tinode/connection'; /** Confirmed live: the real `ctrl.params` shape is `{file_id, url, expires}` -- no `name`/`size`/ @@ -98,7 +99,9 @@ export function upgradeToHttps(url: string): string { * fileBase() down to just its origin before appending, rather than concatenating the two paths. */ export function fileDownloadUrl(refUrl: string): string { - if (refUrl.startsWith('http')) return upgradeToHttps(refUrl); + if (refUrl.startsWith('http')) { + return upgradeToHttps(normalizeFileDownloadUrl(refUrl, apiBase(), fileBase(), window.location.origin)); + } const origin = fileBase().replace(/\/v0\/file.*$/, ''); return `${origin}${refUrl}`; } diff --git a/web/tests/file-url.test.mjs b/web/tests/file-url.test.mjs new file mode 100644 index 00000000..2219ebe9 --- /dev/null +++ b/web/tests/file-url.test.mjs @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { createJiti } from 'jiti'; + +const jiti = createJiti(import.meta.url); +const { normalizeFileDownloadUrl } = await jiti.import('../src/lib/api/file-url.ts'); + +const pageOrigin = 'https://web.118im.com'; +const apiEndpoint = 'https://api.118im.com/v1/im'; +const fileEndpoint = 'https://web.118im.com/v0/file'; + +test('routes historical API-host attachment refs through the configured file endpoint', () => { + assert.equal( + normalizeFileDownloadUrl( + 'https://api.118im.com/v0/file/s/example.jpg?size=large#preview', + apiEndpoint, + fileEndpoint, + pageOrigin, + ), + 'https://web.118im.com/v0/file/s/example.jpg?size=large#preview', + ); +}); + +test('leaves current and unrelated attachment refs unchanged', () => { + assert.equal( + normalizeFileDownloadUrl('/v0/file/s/current.jpg', apiEndpoint, fileEndpoint, pageOrigin), + '/v0/file/s/current.jpg', + ); + assert.equal( + normalizeFileDownloadUrl('https://cdn.example.com/image.jpg', apiEndpoint, fileEndpoint, pageOrigin), + 'https://cdn.example.com/image.jpg', + ); +});