fix(desktop): invalidate stale media lookups

Co-authored-by: npub1qvn3cujt28pg06ehlstrxyz6ayzp06t4uc7r566vxwwgrv24hglq9zju0n <03271c724b51c287eb37fc1633105ae90417e975e63c3a6b4c339c81b155ba3e@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1qvn3cujt28pg06ehlstrxyz6ayzp06t4uc7r566vxwwgrv24hglq9zju0n <03271c724b51c287eb37fc1633105ae90417e975e63c3a6b4c339c81b155ba3e@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
npub1qvn3cujt28pg06ehlstrxyz6ayzp06t4uc7r566vxwwgrv24hglq9zju0n
2026-07-16 21:12:59 -07:00
parent 43bd104de5
commit c6345f79bf
2 changed files with 68 additions and 2 deletions

View File

@@ -5,9 +5,62 @@ import { mediaProxyUrl } from "./mediaUrl.ts";
const HASH = "a".repeat(64);
function deferred() {
let resolve;
const promise = new Promise((resolvePromise) => {
resolve = resolvePromise;
});
return { promise, resolve };
}
test("mediaProxyUrl: uses the IPv4 loopback literal for the localhost proxy", () => {
assert.equal(
mediaProxyUrl(54321, `${HASH}.png`),
`http://127.0.0.1:54321/media/${HASH}.png`,
);
});
test("resetMediaCaches: ignores relay origin lookups from the previous generation", async () => {
const previousWindow = globalThis.window;
const staleOrigin = deferred();
let relayOriginCalls = 0;
globalThis.window = {
__TAURI_INTERNALS__: {
invoke(command) {
if (command === "get_media_proxy_port") return Promise.resolve(54321);
if (command === "get_relay_http_url") {
relayOriginCalls += 1;
return relayOriginCalls === 1
? staleOrigin.promise
: Promise.resolve("https://active.example");
}
return Promise.reject(new Error(`Unexpected command: ${command}`));
},
},
};
try {
// A unique URL triggers module-load fetching with the stale relay lookup
// still unresolved, matching a cold launch before applyCommunity finishes.
const mediaUrl = await import(`./mediaUrl.ts?race=${Date.now()}`);
await new Promise((resolve) => setTimeout(resolve, 0));
mediaUrl.resetMediaCaches();
const activeUrl = `https://active.example/media/${HASH}.png`;
mediaUrl.rewriteRelayUrl(activeUrl);
await new Promise((resolve) => setTimeout(resolve, 0));
// Complete the old lookup after reset. It must not overwrite the active
// community origin fetched by the new generation.
staleOrigin.resolve("https://stale.example");
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(
mediaUrl.rewriteRelayUrl(activeUrl),
`http://127.0.0.1:54321/media/${HASH}.png`,
);
} finally {
globalThis.window = previousWindow;
}
});

View File

@@ -29,6 +29,13 @@ let portPromise: Promise<number | null> | null = null;
/** Cached relay origin (e.g. "https://buzz-oss.stage.blox.sqprod.co"). */
let cachedRelayOrigin: string | null = null;
/**
* Monotonic cache generation. Async lookups capture the current generation and
* may only publish results while it is still current. This prevents a lookup
* started for the previous community from repopulating caches after reset.
*/
let cacheGeneration = 0;
const POLL_INTERVAL_MS = 100;
const POLL_TIMEOUT_MS = 5000;
@@ -38,20 +45,25 @@ const POLL_TIMEOUT_MS = 5000;
* Returns the port, or null if the proxy never came up.
*/
async function fetchProxyPort(): Promise<number | null> {
const generation = cacheGeneration;
// Fetch relay origin in parallel — fire-and-forget, no retry needed.
if (!cachedRelayOrigin) {
invoke<string>("get_relay_http_url")
.then((url) => {
cachedRelayOrigin = url.replace(/\/+$/, "");
if (generation === cacheGeneration) {
cachedRelayOrigin = url.replace(/\/+$/, "");
}
})
.catch(() => {});
}
const deadline = Date.now() + POLL_TIMEOUT_MS;
while (Date.now() < deadline) {
while (Date.now() < deadline && generation === cacheGeneration) {
try {
const port = await invoke<number>("get_media_proxy_port");
if (port > 0) {
if (generation !== cacheGeneration) return null;
cachedPort = port;
return port;
}
@@ -75,6 +87,7 @@ if (typeof window !== "undefined") {
* and relay origin for the new community.
*/
export function resetMediaCaches(): void {
cacheGeneration += 1;
cachedPort = null;
portPromise = null;
cachedRelayOrigin = null;