From 5942d929230c39c2141a0f8e3984a74dfbc92348 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 20 Nov 2023 15:36:01 +0000 Subject: [PATCH] feat: nip96 --- README.md | 1 + packages/app/src/Const.ts | 5 -- packages/app/src/Login/Preferences.ts | 2 +- .../app/src/Pages/settings/Preferences.tsx | 1 + packages/app/src/Upload/Nip96.ts | 73 +++++++++++++++++++ packages/app/src/Upload/VoidCat.ts | 6 +- packages/app/src/Upload/index.ts | 6 +- 7 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 packages/app/src/Upload/Nip96.ts diff --git a/README.md b/README.md index c7888b96..1bcece40 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ Snort supports the following NIP's: - [x] NIP-78: App specific data - [ ] NIP-89: App handlers - [x] NIP-94: File Metadata +- [x] NIP-96: HTTP File Storage Integration (Draft) - [x] NIP-98: HTTP Auth ### Running diff --git a/packages/app/src/Const.ts b/packages/app/src/Const.ts index 2984872a..904ccc34 100644 --- a/packages/app/src/Const.ts +++ b/packages/app/src/Const.ts @@ -13,11 +13,6 @@ export const Day = Hour * 24; */ export const ApiHost = "https://api.snort.social"; -/** - * Void.cat file upload service url - */ -export const VoidCatHost = "https://void.cat"; - /** * Kierans pubkey */ diff --git a/packages/app/src/Login/Preferences.ts b/packages/app/src/Login/Preferences.ts index c4c6d50d..29497bb3 100644 --- a/packages/app/src/Login/Preferences.ts +++ b/packages/app/src/Login/Preferences.ts @@ -45,7 +45,7 @@ export interface UserPreferences { /** * File uploading service to upload attachments to */ - fileUploader: "void.cat" | "nostr.build" | "nostrimg.com"; + fileUploader: "void.cat" | "nostr.build" | "nostrimg.com" | "void.cat-NIP96"; /** * Use imgproxy to optimize images diff --git a/packages/app/src/Pages/settings/Preferences.tsx b/packages/app/src/Pages/settings/Preferences.tsx index d9a39aed..8c545357 100644 --- a/packages/app/src/Pages/settings/Preferences.tsx +++ b/packages/app/src/Pages/settings/Preferences.tsx @@ -471,6 +471,7 @@ const PreferencesPage = () => { + diff --git a/packages/app/src/Upload/Nip96.ts b/packages/app/src/Upload/Nip96.ts new file mode 100644 index 00000000..bb815f23 --- /dev/null +++ b/packages/app/src/Upload/Nip96.ts @@ -0,0 +1,73 @@ +import { base64 } from "@scure/base"; +import { throwIfOffline } from "@snort/shared"; +import { EventPublisher, EventKind } from "@snort/system"; +import { UploadResult, Uploader } from "Upload"; + +export class Nip96Uploader implements Uploader { + constructor( + readonly url: string, + readonly publisher: EventPublisher, + ) {} + + get progress() { + return []; + } + + async upload(file: File | Blob, filename: string): Promise { + throwIfOffline(); + const auth = async (url: string, method: string) => { + const auth = await this.publisher.generic(eb => { + return eb.kind(EventKind.HttpAuthentication).tag(["u", url]).tag(["method", method]); + }); + return `Nostr ${base64.encode(new TextEncoder().encode(JSON.stringify(auth)))}`; + }; + + const fd = new FormData(); + fd.append("size", file.size.toString()); + fd.append("alt", filename); + fd.append("media_type", file.type); + fd.append("file", file); + + const rsp = await fetch(this.url, { + body: fd, + method: "POST", + headers: { + accept: "application/json", + authorization: await auth(this.url, "POST"), + }, + }); + if (rsp.ok) { + throwIfOffline(); + const data = (await rsp.json()) as Nip96Result; + if (data.status === "success") { + const dim = data.nip94_event.tags + .find(a => a[0] === "dim") + ?.at(1) + ?.split("x"); + return { + url: data.nip94_event.tags.find(a => a[0] === "url")?.at(1), + metadata: { + width: dim?.at(0) ? Number(dim[0]) : undefined, + height: dim?.at(1) ? Number(dim[1]) : undefined, + }, + }; + } + return { + error: data.message, + }; + } + return { + error: "Upload failed", + }; + } +} + +export interface Nip96Result { + status: string; + message: string; + processing_url?: string; + nip94_event: { + tags: Array>; + content: string; + }; +} diff --git a/packages/app/src/Upload/VoidCat.ts b/packages/app/src/Upload/VoidCat.ts index 14b99142..cef1db59 100644 --- a/packages/app/src/Upload/VoidCat.ts +++ b/packages/app/src/Upload/VoidCat.ts @@ -1,7 +1,7 @@ import { EventKind, EventPublisher } from "@snort/system"; import { UploadState, VoidApi } from "@void-cat/api"; -import { FileExtensionRegex, VoidCatHost } from "Const"; +import { FileExtensionRegex } from "Const"; import { UploadResult } from "Upload"; import { base64 } from "@scure/base"; import { throwIfOffline } from "@snort/shared"; @@ -26,7 +26,7 @@ export default async function VoidCatUpload( return `Nostr ${base64.encode(new TextEncoder().encode(JSON.stringify(auth)))}`; } : undefined; - const api = new VoidApi(VoidCatHost, auth); + const api = new VoidApi("https://void.cat", auth); const uploader = api.getUploader( file, sx => { @@ -58,7 +58,7 @@ export default async function VoidCatUpload( if (rsp.file?.metadata?.mimeType === "image/webp") { ext = ["", "webp"]; } - const resultUrl = rsp.file?.metadata?.url ?? `${VoidCatHost}/d/${rsp.file?.id}${ext ? `.${ext[1]}` : ""}`; + const resultUrl = rsp.file?.metadata?.url ?? `https://void.cat/d/${rsp.file?.id}${ext ? `.${ext[1]}` : ""}`; const ret = { url: resultUrl, diff --git a/packages/app/src/Upload/index.ts b/packages/app/src/Upload/index.ts index 28e5bc01..591274f6 100644 --- a/packages/app/src/Upload/index.ts +++ b/packages/app/src/Upload/index.ts @@ -7,8 +7,9 @@ import NostrBuild from "Upload/NostrBuild"; import VoidCat from "Upload/VoidCat"; import NostrImg from "Upload/NostrImg"; import { KieranPubKey } from "Const"; -import { bech32ToHex } from "SnortUtils"; +import { bech32ToHex, unwrap } from "SnortUtils"; import useEventPublisher from "Hooks/useEventPublisher"; +import { Nip96Uploader } from "./Nip96"; export interface UploadResult { url?: string; @@ -74,6 +75,9 @@ export default function useFileUpload(): Uploader { progress: [], } as Uploader; } + case "void.cat-NIP96": { + return new Nip96Uploader("https://void.cat/nostr", unwrap(publisher)); + } case "nostrimg.com": { return { upload: NostrImg,