A client for manage blobs on blossom servers
import { BlossomClient } from "blossom-client-sdk/client";
async function signer(event) {
return await window.nostr.signEvent(event);
}
// create an upload auth event
const uploadAuth = await BlossomClient.getUploadAuth(file, server, "Upload bitcoin.pdf");
// encode it using base64
const encodedAuthHeader = BlossomClient.encodeAuthorizationHeader(auth);
// manually make the request
const res = await fetch(new URL("/upload", server), {
method: "PUT",
body: file,
headers: { authorization: encodedAuthHeader },
});
// or use the static method
const res = await BlossomClient.uploadBlob(server, file, uploadAuth);
// check if successful
if (res.ok) {
console.log("Blob uploaded!");
}
The BlossomClient
class can be used to talk to a single server
import { BlossomClient } from "blossom-client-sdk";
async function signer(event) {
return await window.nostr.signEvent(event);
}
const client = new BlossomClient("https://cdn.example.com", signer);
const pubkey = "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5";
const blobs = await client.listBlobs(pubkey, undefined, true);
// passing true as the last argument will make it send an auth event with the list request
The BlossomClient
class and methods optionally take a signer
method that is used to sign the upload auth events
If your using NDK in your app you can use this method
const signer = async (draft: EventTemplate) => {
// add the pubkey to the draft event
const event: UnsignedEvent = { ...draft, pubkey: user.pubkey };
// get the signature
const sig = await ndk.signer!.sign(event);
// return the event + id + sig
return { ...event, sig, id: getEventHash(event) };
};
The getHashFromURL
method will return the last SHA256 hash it finds in a URL
import { getHashFromURL } from "blossom-client-sdk";
// blossom compatible URLs
console.log(
getHashFromURL("https://cdn.example.com/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf"),
);
// -> b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553
// non-blossom URLs
console.log(
getHashFromURL(
"https://cdn.example.com/266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5/media/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf",
),
);
// -> b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553
// returns null when no hash is found
console.log(getHashFromURL("https://example.com/index.html"));
// -> null
This package also exports a few helper methods for handling broken images
The handleImageFallbacks(image, getServers)
method listen for an error
event on an <img/>
element and if the element has a data-pubkey
attribute. it will call getServers
to ask for a list of blossom servers for the pubkey
import { handleImageFallbacks, USER_BLOSSOM_SERVER_LIST_KIND, getServersFromServerListEvent } from "blossom-client-sdk";
const image = new Image();
image.src = "https://cdn.censorship.com/72cb99b689b4cfe1a9fb6937f779f3f9c65094bf0e6ac72a8f8261efa96653f5.png";
// set the pubkey from the kind 1 event this image was found it
image.dataset.pubkey = event.pubkey;
// this is called when
async function getServers(pubkey) {
if (pubkey) {
// use NDK to find the users blossom server list event (k:10063)
const event = await ndk.fetchEvent({ kinds: [USER_BLOSSOM_SERVER_LIST_KIND], authors: [pubkey] });
// if its found return a list of blossom servers
if (event) return getServersFromServerListEvent(event);
}
return undefined;
}
// listen for "error" events
handleImageFallbacks(image, getServers);
document.body.appendChild(image);
import { BlossomClient } from "blossom-client-sdk";
async function signer(event) {
return await window.nostr.signEvent(event);
}
const pubkey = "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5";
const server = "https://cdn.example.com";
async function listBlobs() {
try {
return BlossomClient.listBlobs(server, pubkey);
} catch (e) {
if (e.status === 401) {
const auth = await BlossomClient.getListAuth(signer, "List Blobs from " + server);
return BlossomClient.listBlobs(server, pubkey, undefined, auth);
}
}
}
import { BlossomClient } from "blossom-client-sdk";
async function signer(event) {
return await window.nostr.signEvent(event);
}
const client = new BlossomClient("https://cdn.example.com", signer);
const blobs = await client.listBlobs();
await client.uploadBlob(new File(["testing"], "test.txt"));
import { BlossomClient } from "blossom-client-sdk";
async function signer(event) {
return await window.nostr.signEvent(event);
}
const servers = ["https://cdn.example.com", "https://cdn.other.com"];
const file = new File(["testing"], "test.txt");
const auth = await BlossomClient.getUploadAuth(file, signer, "Upload test.txt");
for (let server of servers) {
await BlossomClient.uploadBlob(server, file, auth);
}
The multiServerUpload
method can be used to upload a single blob to multiple servers
Example of uploading to each server one at time
import { multiServerUpload } from "blossom-server-sdk";
async function signer(event: any) {
// @ts-expect-error
return await window.nostr.signEvent(event);
}
const servers = ["https://cdn.server-a.com", "https://cdn.example.com", "https://cdn.other.com"];
const file = new File(["testing"], "test.txt");
// create async generator for upload
const upload = multiServerUpload(servers, file, signer);
let successful = 0;
while (true) {
let result: Awaited<ReturnType<typeof upload.next>> | undefined = undefined;
// attempt to upload
try {
result = await upload.next();
successful++;
} catch (error) {
if (error instanceof Error) console.log(`Failed to upload ${error.message}`);
}
if (result) {
// if upload was successful log progress
if (!result.done) {
console.log(`Uploaded to ${result.value.server} ${result.value.progress * 100}%`);
} else if (result.done && result.value) {
console.log(`Successfully uploaded ${result.value.sha256} to ${servers} servers`);
// exit while loop (important)
break;
} else {
throw Error("Failed to upload blob to any servers");
}
}
}
import { BlossomClient } from "blossom-client-sdk";
async function signer(event) {
return await window.nostr.signEvent(event);
}
const mainServer = "https://cdn.server-a.com";
const mirrorServers = ["https://cdn.example.com", "https://cdn.other.com"];
const file = new File(["testing"], "test.txt");
const auth = await BlossomClient.getUploadAuth(file, signer, "Upload test.txt");
// first upload blob to main server
const blob = await BlossomClient.uploadBlob(mainServer, file, auth);
// then tell mirror servers to download it
for (let server of mirrorServers) {
// reuse the same auth for mirroring
await BlossomClient.mirrorBlob(server, blob.url, auth);
}