import { multiServerUpload, BlossomClient } from "blossom-client-sdk"; import { SimplePool } from "nostr-tools"; const uploadButton = document.getElementById("upload-button"); /** @type {HTMLInputElement} */ const filesInput = document.getElementById("files"); /** * @param {FileSystemFileEntry} fileEntry * @returns {File} */ export function readFileSystemFile(fileEntry) { return new Promise((res, rej) => { fileEntry.file( (file) => res(file), (err) => rej(err), ); }); } /** * @param {FileSystemDirectoryEntry} directory * @returns {FileSystemEntry[]} */ export function readFileSystemDirectory(directory) { return new Promise((res, rej) => { directory.createReader().readEntries( (entries) => res(entries), (err) => rej(err), ); }); } /** * uploads a file system entry to blossom servers * @param {FileSystemEntry} entry * @returns {{file: File, path: string, sha256: string}[]} */ async function readFileSystemEntry(entry) { const files = []; if (entry instanceof FileSystemFileEntry && entry.isFile) { try { const file = await readFileSystemFile(entry); const sha256 = await BlossomClient.getFileSha256(file); const path = entry.fullPath; files.push({ file, path, sha256 }); } catch (e) { console.log("Failed to add" + entry.fullPath); console.log(e); } } else if (entry instanceof FileSystemDirectoryEntry && entry.isDirectory) { const entries = await readFileSystemDirectory(entry); for (const e of entries) files.push(...(await readFileSystemEntry(e))); } return files; } /** * uploads a file system entry to blossom servers * @param {FileList} list * @returns {{file: File, path: string, sha256: string}[]} */ async function readFileList(list) { const files = []; for (const file of list) { const path = file.webkitRelativePath ? file.webkitRelativePath : file.name; const sha256 = await BlossomClient.getFileSha256(file); files.push({ file, path, sha256 }); } return files; } const pool = new SimplePool(); /** * uploads a file system entry to blossom servers * @param {{file:File, path:string}} files * @param {import("blossom-client-sdk").Signer} signer */ async function uploadFiles(files, signer, auth) { for (const { file, path, sha256 } of files) { try { const upload = multiServerUpload(["https://cdn.hzrd149.com", "https://cdn.satellite.earth"], file, signer, auth); let published = false; for await (let { blob } of upload) { if (!published) { const signed = await signer({ kind: 34128, content: "", created_at: Math.round(Date.now() / 1000), tags: [ ["d", path], ["x", sha256], ], }); await pool.publish(["wss://nostrue.com"], signed); console.log("Published", path, sha256, signed); } } } catch (error) { console.warn(`Failed to upload ${path}`, error); } } } uploadButton.addEventListener("click", async () => { if (!window.nostr) return alert("Missing NIP-07 signer"); const signer = (draft) => window.nostr.signEvent(draft); try { if (filesInput.files) { const files = await readFileList(filesInput.files); // strip leading dir for (const file of files) file.path = file.path.replace(/^[^\/]+\//, "/"); console.log(`Found files`, files); // const auth = await BlossomClient.createUploadAuth( // files.map((f) => f.sha256), // signer, // ); // console.log("Created upload auth", auth); await uploadFiles(files, signer); } } catch (error) { alert(`Failed to upload files: ${error.message}`); } });