/**
* Resolve a server-relative attachment URL against the configured API base.
*
* Background: when the backend has no CloudFront signer configured (e.g.
* the self-hosted RustFS / private-S3 case in MUL-2976), `attachment.url`
* and `attachment.download_url` come back as server-relative paths like
* `/api/attachments/{id}/download`. Web is happy with that — same-origin
* `
` resolves against the document base — but RN
* needs an absolute http(s) URL for both `Linking.openURL` (`Cannot open
* URL` otherwise) and `` (no document origin to
* resolve against; the request is silently dropped).
*
* Mirrors `packages/core/workspace/avatar-url.ts:resolvePublicFileUrl`
* exactly. We don't import the core helper because its `getBaseUrl()`
* pulls from a singleton ApiClient that lives in `@multica/core/api` —
* not on the mobile sharing whitelist (apps/mobile/CLAUDE.md "mirror,
* don't import"). Mobile reads its own `EXPO_PUBLIC_API_URL` from the
* Expo env, the same value the rest of `data/api.ts` uses.
*
* Contract:
* - null / undefined / "" → null (caller should treat as "no URL").
* - already-absolute URL → returned unchanged.
* - server-relative path → API base + path, with a single boundary
* slash (we trim trailing slashes from the
* base before joining).
*/
const API_URL = process.env.EXPO_PUBLIC_API_URL ?? "";
export function resolveAttachmentUrlWithBase(
rawUrl: string | null | undefined,
baseUrl: string,
): string | null {
if (!rawUrl) return null;
if (!rawUrl.startsWith("/")) return rawUrl;
const trimmedBaseUrl = baseUrl.replace(/\/+$/, "");
return `${trimmedBaseUrl}${rawUrl}`;
}
export function resolveAttachmentUrl(
rawUrl: string | null | undefined,
): string | null {
return resolveAttachmentUrlWithBase(rawUrl, API_URL);
}