Repository navigation
Recover dead media links from the author's published Blossom servers - #93
Open
dadofsambonzuki wants to merge 1 commit into
Open
dadofsambonzuki wants to merge 1 commit into
dadofsambonzuki wants to merge 1 commit into
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
src/utils/blossomMedia.tsreads 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.src/services/blossomMediaRecoveryService.tsruns 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, andcontent-lengthwhen 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.src/stores/nostr/blobMediaRecoveryRuntime.tsfinds 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.MessageBody.sveltefalls back to the recovered blob when animg,videooraudiofails 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
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.tsdoes 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.tssix times (all pass, ~8s), and the closest existing coveragee2e/parity/dm/media.spec.ts+e2e/message-formatting.spec.ts(6 passed).Two review notes
src/utils/blossomServerList.tsand the one-lineNostrKind.BlossomServerListconstant, 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 toauthSessionRuntime'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.e2e/parity/helpers.ts'ssendMessageclicks send immediately after a chat opens, and in one of the first runs the button was still disabled, soestablishAcceptedDirectChattimed out. Six later runs were clean. AtoBeEnabled()wait before the click would fix it; happy to PR that one-liner separately if useful.