Map tiles: new lib/tileCache.ts (Dexie SkateSpotsTilesDB, bounded to ~3000 tiles) and lib/cachedTileLayer.ts, a Leaflet TileLayer that reads through it (cached -> fetch+cache -> plain URL fallback). Wired into both maps (SpotMap, RouteDialog). Chosen over a service worker so it works in the Capacitor WebView too, where a SW never sees tile requests. Photos: useUploadFile drops from three Blossom hosts to two and records ['fallback', '<mirror>/<sha256>']. BlossomUploader signs once and Promise.any PUTs to every server in parallel, so the old three-host list was triple- uploading every photo (the 'tries them in order' comment was wrong). Read side: lib/imageFallback.ts derives same-hash mirrors and ImageWithFallback retries them before showing the placeholder - one choke point for ~20 photo call sites, so a dead host no longer blanks a spot.
52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
import { useMutation } from "@tanstack/react-query";
|
|
import { BlossomUploader } from '@nostrify/nostrify/uploaders';
|
|
|
|
import { useCurrentUser } from "./useCurrentUser";
|
|
|
|
/**
|
|
* Two Blossom hosts. `BlossomUploader` signs ONE auth event and PUTs to every
|
|
* server in parallel, resolving with the first success — so the same bytes
|
|
* normally land on both from a single signature (no double Amber prompt). We then
|
|
* record a mirror URL on the returned tags so photo redundancy is explicit in the
|
|
* event; the read side also derives it from the sha256 (lib/imageFallback).
|
|
*/
|
|
const UPLOAD_SERVERS = ['https://blossom.primal.net/', 'https://blossom.band/'];
|
|
|
|
function hostOf(url: string): string {
|
|
try {
|
|
return new URL(url).hostname;
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
export function useUploadFile() {
|
|
const { user } = useCurrentUser();
|
|
|
|
return useMutation({
|
|
mutationFn: async (file: File) => {
|
|
if (!user) {
|
|
throw new Error('Must be logged in to upload files');
|
|
}
|
|
|
|
const uploader = new BlossomUploader({
|
|
servers: UPLOAD_SERVERS,
|
|
signer: user.signer,
|
|
});
|
|
|
|
const tags = await uploader.upload(file);
|
|
|
|
// Remember the mirror copy of the same blob. Blossom is content-addressed,
|
|
// so `<other-host>/<sha256>` is the same file.
|
|
const primaryUrl = tags[0]?.[1];
|
|
const sha = tags.find(([name]) => name === 'x')?.[1];
|
|
if (primaryUrl && sha) {
|
|
const primaryHost = hostOf(primaryUrl);
|
|
const mirror = UPLOAD_SERVERS.find((s) => hostOf(s) !== primaryHost);
|
|
if (mirror) tags.push(['fallback', `${mirror.replace(/\/+$/, '')}/${sha}`]);
|
|
}
|
|
|
|
return tags;
|
|
},
|
|
});
|
|
}
|