Skip to content

Recover dead media links from the author's published Blossom servers - #93

Open
dadofsambonzuki wants to merge 1 commit into
lnbits:mainfrom
dadofsambonzuki:feat/blossom-media-recovery
Open

dadofsambonzuki wants to merge 1 commit into
lnbits:mainfrom
dadofsambonzuki:feat/blossom-media-recovery

Conversation

@dadofsambonzuki

Copy link
Copy Markdown

What happens today

When a message's media link stops working — the server shut down, the file moved, a subdomain changed — the bubble shows a broken image and there is nothing else to try, even when the same file is still served by one of the author's other Blossom servers, and even when the author publishes exactly that list.

The change — NIP-B7's other half, in four pieces

  1. src/utils/blossomMedia.ts reads the hash out of a media URL (/<64-hex>[.ext]), keeps the extension, and builds the serving URL for another server. Only a plain HTTPS origin plus a 64-character hex hash is accepted.
  2. src/services/blossomMediaRecoveryService.ts runs the lookup: on a failed load it tries the author's servers in their published order, skipping the server that just failed, downloads each candidate and compares its SHA-256 against the hash in the link. Only matching bytes are handed back, as a blob, so what ends up on screen is what was checked. Downloading to check costs memory, so media above 20 MiB is skipped (the size in the message metadata, and content-length when the server declares it). One attempt per link per session, two callers asking at once share a single download, and up to 12 verified files / 48 MiB are kept so a second render does not download again.
  3. src/stores/nostr/blobMediaRecoveryRuntime.ts finds the author's kind 10063 list: their reading relays when the app knows them, then ours, then the public metadata indexers — which stay out of the way when the app is configured with local relays only, so tests stay on their own relay. Cached per author for ten minutes, including "this author published nothing", and dropped on logout.
  4. MessageBody.svelte falls back to the recovered blob when an img, video or audio fails to load, and revokes the object URL when the message unmounts. Recovery sits behind the same "media from this sender" gate as ordinary rendering, so nothing is fetched for a sender whose media is held back.

Left out on purpose

  • Profile pictures and link previews: message attachments only, as asked.
  • Verifying the first load of a link that works: it is displayed exactly as before, so ordinary rendering costs nothing extra.
  • Publishing anything: the lookups read other people's lists, they never write one.

Verified

Unit tests cover hash extraction and candidate building; recovery skipping the failed server, rejecting a wrong-hash server and moving on, treating 404s as a miss, honouring the size ceiling, one attempt per link, shared downloads, the request timeout, and reset; the author lookup using their relays plus ours plus the indexers, the ten-minute cache and the re-query after it, coalescing, the logged-out bail-out, remembering an author with no list while retrying a failed query, and a recovery driven end to end through the runtime.

Removing the hash check makes the "rejects a server that answers with the wrong bytes" test fail; removing the "skip the server that just failed" rule breaks five of them — both watched to fail and restored.

e2e/blossom-media-recovery.spec.ts does it in a browser: alice publishes a list naming a lying server and then a good one, bob's client holds a message whose media is dead, and the test asserts the lying server is asked first, rejected, and the good server's bytes render as a blob; a second message whose file no server has stays as it was. On the unpatched app the same test fails with the image still pointing at the dead URL.

Ran locally: npm run quality:all (0 errors), npm run test:unit (141 files, 1112 tests), npm run build, npm run verify:iroh, npx playwright test e2e/blossom-media-recovery.spec.ts six times (all pass, ~8s), and the closest existing coverage e2e/parity/dm/media.spec.ts + e2e/message-formatting.spec.ts (6 passed).

Two review notes

  • Stacking with Read the Blossom server list published to relays #92: this PR contains src/utils/blossomServerList.ts and the one-line NostrKind.BlossomServerList constant, byte-identical to that PR's copy so it can be reviewed standalone; if Read the Blossom server list published to relays #92 lands first they are a "both added the same file" conflict resolved by keeping either. Both PRs also add one entry to authSessionRuntime's reset list — adjacent lines, same trivial resolution. I have deliberately not edited the CI browser list here, since Read the Blossom server list published to relays #92 already edits that exact line; this spec belongs on it, so add it alongside if both are taken.
  • Flake, pre-existing and not from this change: e2e/parity/helpers.ts's sendMessage clicks send immediately after a chat opens, and in one of the first runs the button was still disabled, so establishAcceptedDirectChat timed out. Six later runs were clean. A toBeEnabled() wait before the click would fix it; happy to PR that one-liner separately if useful.

A Stored Blossom link that stops loading is now looked for on the other
servers its author published in their kind 10063 list (NIP-B7), and only
bytes whose SHA-256 matches the hash in the link are shown.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant