"use client" import { useState, useEffect, useCallback, type ChangeEvent, type FormEvent } from "react" import { ReloadIcon } from "@radix-ui/react-icons" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { type NostrEvent } from "nostr-tools" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, } from "@/components/ui/drawer" import { Spinner } from "./spinner" import { signEvent } from "@/lib/utils" async function calculateBlurhash(file: File): Promise { return new Promise((resolve, reject) => { const canvas = document.createElement("canvas") const ctx = canvas.getContext("2d") const img = new Image() img.crossOrigin = "anonymous" img.onload = () => { canvas.width = 32 canvas.height = 32 ctx?.drawImage(img, 0, 0, 32, 32) const imageData = ctx?.getImageData(0, 0, 32, 32) if (imageData) { // Mock blurhash calculation - in a real app you'd use the blurhash library const mockBlurhash = "LGF5?xYk^6#M@-5c,1J5@[or[Q6." resolve(mockBlurhash) } else { reject(new Error("Failed to get image data")) } } img.onerror = reject img.src = URL.createObjectURL(file) }) } async function calculateSHA256(file: File): Promise { // In a browser environment, we'd use the Web Crypto API // This is a simplified mock implementation return new Promise((resolve) => { setTimeout(() => { // Generate a random SHA256-like hash for demo purposes const mockHash = Array.from({ length: 64 }, () => "0123456789abcdef"[Math.floor(Math.random() * 16)]).join("") resolve(mockHash) }, 500) }) } const UploadComponent = () => { const { createHash } = require("crypto") const loginType = typeof window !== "undefined" ? window.localStorage.getItem("loginType") : null const [previewUrl, setPreviewUrl] = useState("") const [isLoading, setIsLoading] = useState(false) const [isDrawerOpen, setIsDrawerOpen] = useState(false) const [uploadedNoteId, setUploadedNoteId] = useState("") const [retryCount, setRetryCount] = useState(0) const [shouldFetch, setShouldFetch] = useState(false) const [serverChoice, setServerChoice] = useState("blossom.band") const [events, setEvents] = useState([]) const [isNoteLoading, setIsNoteLoading] = useState(false) useEffect(() => { if (uploadedNoteId) { setShouldFetch(true) } }, [uploadedNoteId]) useEffect(() => { let timeoutId: NodeJS.Timeout if (shouldFetch && events.length === 0 && !isNoteLoading) { setIsNoteLoading(true) // Simulate fetching events timeoutId = setTimeout(() => { setIsNoteLoading(false) // After a few retries, simulate finding the event if (retryCount >= 2) { setEvents([{ id: uploadedNoteId }]) } else { setRetryCount((prevCount) => prevCount + 1) } }, 2000) } return () => { if (timeoutId) { clearTimeout(timeoutId) } } }, [shouldFetch, events, isNoteLoading, retryCount, uploadedNoteId]) const handleRetry = useCallback(() => { setRetryCount((prevCount) => prevCount + 1) setShouldFetch(false) setTimeout(() => setShouldFetch(true), 100) }, []) const handleFileChange = (event: ChangeEvent) => { const file = event.target.files?.[0] if (file) { const url = URL.createObjectURL(file) setPreviewUrl(url) return () => URL.revokeObjectURL(url) } } const handleServerChange = (value: string) => { setServerChoice(value) } async function onSubmit(event: FormEvent) { event.preventDefault() setIsLoading(true) const formData = new FormData(event.currentTarget) const desc = formData.get("description") as string const file = formData.get("file") as File let sha256 = "" let finalNoteContent = desc let finalFileUrl = "" console.log("File:", file) if (!desc && !file.size) { alert("Please enter a description and/or upload a file") setIsLoading(false) return } // If file is present, upload it to the media server if (file) { const readFileAsArrayBuffer = (file: File): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader() reader.onload = () => resolve(reader.result as ArrayBuffer) reader.onerror = () => reject(reader.error) reader.readAsArrayBuffer(file) }) } try { const arrayBuffer = await readFileAsArrayBuffer(file) const hashBuffer = createHash("sha256").update(Buffer.from(arrayBuffer)).digest() sha256 = hashBuffer.toString("hex") const unixNow = () => Math.floor(Date.now() / 1000) const newExpirationValue = () => (unixNow() + 60 * 5).toString() const pubkey = window.localStorage.getItem("pubkey") const createdAt = Math.floor(Date.now() / 1000) // alert("SHA256: " + sha256) // Create auth event for blossom auth via nostr const authEvent: NostrEvent = { kind: 24242, // content: desc, content: "File upload", created_at: createdAt, tags: [ ["t", "media"], ["x", sha256], ["expiration", newExpirationValue()], ], pubkey: "", // Add a placeholder for pubkey id: "", // Add a placeholder for id sig: "", // Add a placeholder for sig } console.log(authEvent) // Sign auth event const authEventSigned = (await signEvent(loginType, authEvent)) as NostrEvent // authEventSigned as base64 encoded string const authString = Buffer.from(JSON.stringify(authEventSigned)).toString("base64") const blossomServer = "https://" + serverChoice await fetch(blossomServer + "/media", { method: "PUT", body: file, headers: { authorization: "Nostr " + authString }, }).then(async (res) => { if (res.ok) { const responseText = await res.text() const responseJson = JSON.parse(responseText) finalFileUrl = responseJson.url sha256 = responseJson.sha256 let blurhash = "" if (file && file.type.startsWith("image/")) { try { blurhash = await calculateBlurhash(file) } catch (error) { console.error("Error calculating blurhash:", error) } } if (finalFileUrl) { const image = new Image() image.src = URL.createObjectURL(file) await new Promise((resolve) => { image.onload = resolve }) finalNoteContent = desc } setIsLoading(false) if (finalFileUrl != null) { setIsDrawerOpen(true) setShouldFetch(true) setRetryCount(0) } } else { // alert(await res.text()) throw new Error("Failed to upload file: " + (await res.text())) } }) } catch (error) { alert(error) console.error("Error reading file:", error) setIsLoading(false) } } } return ( <>
{previewUrl && (
Preview
)} {isLoading ? ( ) : ( )}
Upload Status {isNoteLoading ? (
Checking note status...
) : events.length > 0 ? (
Success! Note found with ID: {`${uploadedNoteId.slice(0, 5)}...${uploadedNoteId.slice(-3)}`}
) : (

Note not found. It may take a moment to propagate.

)}
{events.length === 0 && ( )}
) } export default UploadComponent