// Serving blobs (and locating record dirs) from the SHARED content-addressed overlay store — favela-web
// slice 3b. These are the SAME files hpp writes via atproto-store's OverlayRepoStore/OverlayBlobStore, so a
// co-located tabertron (running BOTH hpp and favela-web) keeps ONE physical copy per DID, no data drift.
//
// The derivation here MUST match atproto-store exactly, or the two processes would write to separate dirs and
// the whole "one copy" invariant breaks. roomKey16(wj62) === '0d6d2249141a9549' matches hpp's on-disk dir.
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
const ROOM_KEY_NS = b4a.from('atproto-base/v1/room-key')
// The 16-hex per-DID namespace dir shared by hpp and favela. Content lives under OVERLAY_ROOT/<roomKey16>/.
export function roomKey16 (did) {
return b4a.toString(crypto.hash([ROOM_KEY_NS, b4a.from(did)]), 'hex').slice(0, 16)
}
// Absolute path of a DID's content-addressed blob file in the shared store: <root>/<roomKey16>/blobs/<cid>.
// Joined by hand (no bare-path) so this module stays runtime-agnostic — it runs under both bare (the daemon)
// and node (tests). cid MUST be pre-validated with isSafeCid; roomKey16/'blobs' are trusted segments.
export function blobPath (root, did, cid) {
return `${root.replace(/\/+$/, '')}/${roomKey16(did)}/blobs/${cid}`
}
// atproto blobs are content-addressed and the mimeType lives in the record's blob ref (not the file), so we
// sniff the content-type from magic bytes for the getBlob response header. Default: application/octet-stream.
export function sniffMime (b) {
if (b.length >= 3 && b[0] === 0xFF && b[1] === 0xD8 && b[2] === 0xFF) return 'image/jpeg'
if (b.length >= 8 && b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4E && b[3] === 0x47) return 'image/png'
if (b.length >= 4 && b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x38) return 'image/gif'
if (b.length >= 12 && b[0] === 0x52 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x46 && b[8] === 0x57 && b[9] === 0x45 && b[10] === 0x42 && b[11] === 0x50) return 'image/webp'
return 'application/octet-stream'
}
// True iff a caller-supplied cid is safe to use as a path segment (no traversal, bounded length). getBlob MUST
// reject anything else — the cid comes straight off an untrusted query string.
export function isSafeCid (cid) {
return typeof cid === 'string' && cid.length > 0 && cid.length <= 128 && !cid.includes('/') && !cid.includes('..')
}