Add docs for blossom, add nip 86 and 98 support

This commit is contained in:
Jon Staab
2025-06-10 13:18:03 -07:00
parent 90b2ab2974
commit 4cabf53c2f
13 changed files with 314 additions and 17 deletions
+3 -3
View File
@@ -57,9 +57,9 @@ describe("Events", () => {
],
})
describe("createEvent", () => {
describe("makeEvent", () => {
it("should create event with defaults", () => {
const event = Events.createEvent(1, {})
const event = Events.makeEvent(1, {})
expect(event.kind).toBe(1)
expect(event.content).toBe("")
expect(event.tags).toEqual([])
@@ -67,7 +67,7 @@ describe("Events", () => {
})
it("should create event with provided values", () => {
const event = Events.createEvent(1, {
const event = Events.makeEvent(1, {
content: "Hello Nostr!",
tags: [["p", pubkey]],
created_at: currentTime,
+6 -10
View File
@@ -1,7 +1,7 @@
import {Base64} from "js-base64"
import {now, bytesToHex, hexToBytes} from "@welshman/lib"
import {BLOSSOM_AUTH} from "./Kinds.js"
import {makeEvent, SignedEvent} from "./Events.js"
import {makeHttpAuthHeader} from "./Nip98.js"
export type BlossomAuthAction = "get" | "upload" | "list" | "delete"
@@ -48,10 +48,6 @@ export const makeBlossomAuthEvent = ({
return makeEvent(BLOSSOM_AUTH, {content, tags})
}
export const createAuthorizationHeader = (event: SignedEvent): string => {
return `Nostr ${Base64.encode(JSON.stringify(event))}`
}
export const buildBlobUrl = (server: string, sha256: string, extension?: string): string => {
const url = new URL(server)
const filename = extension ? `${sha256}.${extension}` : sha256
@@ -69,7 +65,7 @@ export const checkBlobExists = async (
const headers: Record<string, string> = {}
if (options.authEvent) {
headers.Authorization = createAuthorizationHeader(options.authEvent)
headers.Authorization = makeHttpAuthHeader(options.authEvent)
}
try {
@@ -101,7 +97,7 @@ export const getBlob = async (
const headers: Record<string, string> = {}
if (options.authEvent) {
headers.Authorization = createAuthorizationHeader(options.authEvent)
headers.Authorization = makeHttpAuthHeader(options.authEvent)
}
if (options.range) {
@@ -126,7 +122,7 @@ export const uploadBlob = async (
const headers: Record<string, string> = {}
if (options.authEvent) {
headers.Authorization = createAuthorizationHeader(options.authEvent)
headers.Authorization = makeHttpAuthHeader(options.authEvent)
}
return fetch(uploadUrl, {method: "PUT", headers, body})
@@ -144,7 +140,7 @@ export const deleteBlob = async (
const headers: Record<string, string> = {}
if (options.authEvent) {
headers.Authorization = createAuthorizationHeader(options.authEvent)
headers.Authorization = makeHttpAuthHeader(options.authEvent)
}
return fetch(url, {method: "DELETE", headers})
@@ -175,7 +171,7 @@ export const listBlobs = async (
const headers: Record<string, string> = {}
if (options.authEvent) {
headers.Authorization = createAuthorizationHeader(options.authEvent)
headers.Authorization = makeHttpAuthHeader(options.authEvent)
}
return fetch(fullUrl, {headers})
+9 -1
View File
@@ -54,12 +54,14 @@ export type MakeEventOpts = {
created_at?: number
}
// Event template creation
export const makeEvent = (
kind: number,
{content = "", tags = [], created_at = now()}: MakeEventOpts = {},
) => ({kind, content, tags, created_at})
export const createEvent = makeEvent
// Event signature verification
export const verifyEvent = (() => {
let verify = verifyEventPure
@@ -80,6 +82,8 @@ export const verifyEvent = (() => {
Boolean(event.sig && (event[verifiedSymbol] || verify(event as SignedEvent)))
})()
// Type guards
export const isEventTemplate = (e: EventTemplate): e is EventTemplate =>
Boolean(typeof e.kind === "number" && Array.isArray(e.tags) && typeof e.content === "string")
@@ -100,6 +104,8 @@ export const isUnwrappedEvent = (e: TrustedEvent): e is UnwrappedEvent =>
export const isTrustedEvent = (e: TrustedEvent): e is TrustedEvent =>
isSignedEvent(e) || isUnwrappedEvent(e)
// Type coercion and attribute stripping
export const asEventTemplate = (e: EventTemplate): EventTemplate =>
pick(["kind", "tags", "content"], e)
@@ -121,6 +127,8 @@ export const asUnwrappedEvent = (e: UnwrappedEvent): UnwrappedEvent =>
export const asTrustedEvent = (e: TrustedEvent): TrustedEvent =>
pick(["kind", "tags", "content", "created_at", "pubkey", "id", "sig", "wrap"], e)
// Utilities for working with events
export const getIdentifier = (e: EventTemplate) => e.tags.find(t => t[0] === "d")?.[1]
export const getIdOrAddress = (e: HashedEvent) => (isReplaceable(e) ? getAddress(e) : e.id)
+41
View File
@@ -0,0 +1,41 @@
import {postJson} from "@welshman/lib"
import {SignedEvent} from "./Events.js"
import {makeHttpAuthHeader} from "./Nip98.js"
export enum ManagementMethod {
SupportedMethods = "supportedmethods",
BanPubkey = "banpubkey",
AllowPubkey = "allowpubkey",
ListBannedPubkeys = "listbannedpubkeys",
ListAllowedPubkeys = "listallowedpubkeys",
ListEventsNeedingModeration = "listeventsneedingmoderation",
AllowEvent = "allowevent",
BanEvent = "banevent",
ListBannedEvents = "listbannedevents",
ChangeRelayName = "changerelayname",
ChangeRelayDescription = "changerelaydescription",
ChangeRelayIcon = "changerelayicon",
AllowKind = "allowkind",
DisallowKind = "disallowkind",
ListAllowedKinds = "listallowedkinds",
BlockIp = "blockip",
UnblockIp = "unblockip",
ListBlockedIps = "listblockedips",
}
export type ManagementRequest = {
method: ManagementMethod
params: string[]
}
export const sendManagementRequest = (
url: string,
request: ManagementRequest,
authEvent: SignedEvent,
) =>
postJson(url, request, {
headers: {
"Content-Type": "application/nostr+json+rpc",
Authorization: makeHttpAuthHeader(authEvent),
},
})
+20
View File
@@ -0,0 +1,20 @@
import {Base64} from "js-base64"
import {sha256, textEncoder} from "@welshman/lib"
import {makeEvent, SignedEvent} from "./Events.js"
import {HTTP_AUTH} from "./Kinds.js"
export const makeHttpAuth = async (url: string, method = "GET", body?: string) => {
const tags = [
["u", url],
["method", method],
]
if (body) {
tags.push(["payload", await sha256(textEncoder.encode(body))])
}
return makeEvent(HTTP_AUTH, {tags})
}
export const makeHttpAuthHeader = (event: SignedEvent) =>
`Nostr ${Base64.encode(JSON.stringify(event))}`
+2
View File
@@ -7,6 +7,8 @@ export * from "./Handler.js"
export * from "./Kinds.js"
export * from "./Links.js"
export * from "./List.js"
export * from "./Nip86.js"
export * from "./Nip98.js"
export * from "./Profile.js"
export * from "./Relay.js"
export * from "./Tags.js"