fix(desktop): only proxy relay-origin Blossom URLs in media rewriter (#392)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wes
2026-04-23 12:11:07 -06:00
committed by GitHub
parent 8599a4d2a0
commit 1863411b1e

View File

@@ -8,11 +8,11 @@
* For video, the proxy streams via axum — no buffering the entire file.
* Images and other media also benefit from this path.
*
* Detection is path-based: /media/{64-hex-chars}.{ext} is a Blossom BUD-01
* content-addressed URL. The 64-char lowercase hex SHA-256 hash makes this
* pattern unique to Blossom relays — false positives from other origins are
* practically impossible. This avoids needing async relay-URL initialization,
* eliminating race conditions with first render.
* Only URLs hosted on the Sprout relay are rewritten. External Blossom URLs
* (e.g. nostr.build, void.cat) are returned unchanged — they aren't behind
* Cloudflare Access and can be loaded directly by WKWebView. Without this
* origin check, external Blossom URLs would be proxied to the wrong server
* (the Sprout relay), resulting in 404s.
*/
import { invoke } from "@tauri-apps/api/core";
@@ -26,14 +26,27 @@ const RELAY_MEDIA_RE =
let cachedPort: number | null = null;
let portPromise: Promise<number | null> | null = null;
/** Cached relay origin (e.g. "https://sprout-oss.stage.blox.sqprod.co"). */
let cachedRelayOrigin: string | null = null;
const POLL_INTERVAL_MS = 100;
const POLL_TIMEOUT_MS = 5000;
/**
* Poll `get_media_proxy_port` until we get a non-zero port or timeout.
* Also fetches the relay HTTP base URL for origin-checking.
* Returns the port, or null if the proxy never came up.
*/
async function fetchProxyPort(): Promise<number | null> {
// Fetch relay origin in parallel — fire-and-forget, no retry needed.
if (!cachedRelayOrigin) {
invoke<string>("get_relay_http_url")
.then((url) => {
cachedRelayOrigin = url.replace(/\/+$/, "");
})
.catch(() => {});
}
const deadline = Date.now() + POLL_TIMEOUT_MS;
while (Date.now() < deadline) {
try {
@@ -58,14 +71,24 @@ if (typeof window !== "undefined") {
}
/**
* If `url` looks like a Blossom relay media URL, rewrite it to go through
* the localhost streaming proxy. Falls back to sprout-media:// if the proxy
* port isn't available yet.
* If `url` is a Blossom media URL hosted on the Sprout relay, rewrite it
* to go through the localhost streaming proxy. External Blossom URLs and
* non-Blossom URLs are returned unchanged.
*
* Falls back to sprout-media:// if the proxy port isn't available yet.
*/
export function rewriteRelayUrl(url: string): string {
const m = RELAY_MEDIA_RE.exec(url);
if (!m) return url;
// Only proxy URLs that belong to our relay. External Blossom URLs
// (different origin) pass through unchanged — they work fine via WKWebView.
// If the relay origin isn't cached yet, fall through to the rewrite path
// as a safe default (relay URLs need the proxy to avoid Cloudflare 403s).
if (cachedRelayOrigin && !url.startsWith(`${cachedRelayOrigin}/`)) {
return url;
}
if (cachedPort && cachedPort > 0) {
return `http://localhost:${cachedPort}/media/${m[1]}`;
}