import { multiServerUpload, BlossomClient } from "blossom-client-sdk"; import { SimplePool } from "nostr-tools"; const logContainer = document.getElementById("log"); function log(...args) { const el = document.createElement("div"); el.innerText = args.join(" "); logContainer.appendChild(el); } 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) { log("Failed to add" + entry.fullPath); log(e.message); } } 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 * @param {*} auth * @param {string[]} servers * @param {string[]} relays */ async function uploadFiles(files, signer, auth, servers, relays) { for (const { file, path, sha256 } of files) { try { const upload = multiServerUpload(servers, 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(relays, signed); log("Published", path, sha256, signed.id); } } } catch (error) { log(`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); const relays = document.getElementById("relays").value.split(/\n|,/); const servers = document.getElementById("servers").value.split(/\n|,/); try { if (filesInput.files) { const files = await readFileList(filesInput.files); // strip leading dir for (const file of files) file.path = file.path.replace(/^[^\/]+\//, "/"); log(`Found ${files.length} files`); await uploadFiles(files, signer, undefined, servers, relays); } } catch (error) { alert(`Failed to upload files: ${error.message}`); } });