web_lib

Common web application libraries
git clone https://radroots.dev/git/web_lib.git
Log | Files | Refs | LICENSE

commit 9c6fc86afab20c81118be10bd873999478435439
parent 5759ef7b4f3b7beaae75c930dd3a1c2a9b803c2e
Author: triesap <137732411+triesap@users.noreply.github.com>
Date:   Thu,  7 Aug 2025 16:03:55 +0000

utils-nostr: restructure library modules. update build process to use tsc with targets for esm and cjs outputs

Diffstat:
Mutils-nostr/.gitignore | 5++++-
Mutils-nostr/package.json | 29++++++++++++++++-------------
Autils-nostr/src/events/lib.ts | 4++++
Autils-nostr/src/events/parse.ts | 17+++++++++++++++++
Autils-nostr/src/events/subscription.ts | 22++++++++++++++++++++++
Mutils-nostr/src/index.ts | 20++++++--------------
Autils-nostr/src/keys/lib.ts | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dutils-nostr/src/lib/events.ts | 46----------------------------------------------
Dutils-nostr/src/lib/keys.ts | 68--------------------------------------------------------------------
Dutils-nostr/src/lib/ndk.ts | 157-------------------------------------------------------------------------------
Dutils-nostr/src/lib/relay.ts | 46----------------------------------------------
Dutils-nostr/src/lib/tags.ts | 151------------------------------------------------------------------------------
Dutils-nostr/src/lib/types.ts | 157-------------------------------------------------------------------------------
Autils-nostr/src/schemas/lib.ts | 14++++++++++++++
Dutils-nostr/src/services/events/lib.ts | 79-------------------------------------------------------------------------------
Dutils-nostr/src/services/events/types.ts | 24------------------------
Dutils-nostr/src/services/keys/lib.ts | 120-------------------------------------------------------------------------------
Dutils-nostr/src/services/keys/types.ts | 11-----------
Dutils-nostr/src/services/relay/lib.ts | 17-----------------
Dutils-nostr/src/services/relay/types.ts | 6------
Dutils-nostr/src/types.ts | 15---------------
Autils-nostr/src/types/lib.ts | 4++++
Dutils-nostr/src/util.ts | 11-----------
Autils-nostr/tsconfig.base.json | 11+++++++++++
Autils-nostr/tsconfig.cjs.json | 10++++++++++
Autils-nostr/tsconfig.esm.json | 12++++++++++++
Dutils-nostr/tsconfig.json | 29-----------------------------
Dutils-nostr/tsup.config.ts | 11-----------
28 files changed, 215 insertions(+), 976 deletions(-)

diff --git a/utils-nostr/.gitignore b/utils-nostr/.gitignore @@ -43,5 +43,8 @@ vite.config.ts.timestamp-* .vscode notes*.txt notes*.md -git-diff.txt +notes*.json +git-diff*.txt +prompt*.txt +tree*.txt justfile \ No newline at end of file diff --git a/utils-nostr/package.json b/utils-nostr/package.json @@ -1,25 +1,29 @@ { "name": "@radroots/utils-nostr", - "version": "0.0.0", + "version": "0.0.1", "private": true, "type": "module", - "main": "dist/index.js", - "module": "dist/index.js", - "types": "dist/index.d.ts", + "main": "./dist/cjs/index.cjs", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", "exports": { ".": { - "import": "./dist/index.js", - "require": "./dist/index.cjs" + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js" } }, "scripts": { - "build": "tsup", - "dev": "tsup --watch", + "build:esm": "tsc -p tsconfig.esm.json", + "build:cjs": "tsc -p tsconfig.cjs.json", + "build": "npm run clean && npm run build:esm && npm run build:cjs", + "prebuild": "npm run clean", + "clean": "rimraf dist && rimraf tsconfig.tsbuildinfo", + "dev": "npm run watch", "watch": "tsc -w" }, "devDependencies": { - "@types/uuid": "^10.0.0", - "tsup": "^6.2.3", + "rimraf": "^6.0.1", "typescript": "5.8.3" }, "publishConfig": { @@ -28,10 +32,9 @@ "dependencies": { "@noble/curves": "^1.6.0", "@noble/hashes": "^1.4.0", - "@nostr-dev-kit/ndk": "^2.11.0", + "@nostr-dev-kit/ndk": "2.14.33", "@radroots/radroots-common-bindings": "*", "nostr-geotags": "*", - "nostr-tools": "^2.10.4", - "uuid": "^10.0.0" + "nostr-tools": "^2.10.4" } } \ No newline at end of file diff --git a/utils-nostr/src/events/lib.ts b/utils-nostr/src/events/lib.ts @@ -0,0 +1,4 @@ +import { NDKTag } from "@nostr-dev-kit/ndk"; + +export const get_event_tag = (tags: NDKTag[], key: string): string => tags.find(t => t[0] === key)?.[1] ?? ''; +export const get_event_tags = (tags: NDKTag[], key: string): NDKTag[] => tags.filter(t => t[0] === key); diff --git a/utils-nostr/src/events/parse.ts b/utils-nostr/src/events/parse.ts @@ -0,0 +1,17 @@ +import { NDKEvent } from "@nostr-dev-kit/ndk"; +import { nostr_event_metadata_schema } from "../schemas/lib.js"; +import { type NostrEventMetadata } from "../types/lib.js"; + +export const parse_nostr_metadata_event = (event: NDKEvent): NostrEventMetadata | undefined => { + if (!event || typeof event.content !== 'string' || event.kind !== 0) return undefined; + + try { + const parsed = JSON.parse(event.content); + const result = nostr_event_metadata_schema.parse(parsed); + return result; + } catch { + return undefined; + } +}; + + diff --git a/utils-nostr/src/events/subscription.ts b/utils-nostr/src/events/subscription.ts @@ -0,0 +1,22 @@ +import { NDKEvent } from "@nostr-dev-kit/ndk"; +import { type NostrEventMetadata } from "../types/lib.js"; +import { parse_nostr_metadata_event } from "./parse.js"; + +export type NdkEventPayload = + | { kind: 0; metadata: NostrEventMetadata; } + +export const on_ndk_event = (event: NDKEvent): NdkEventPayload | undefined => { + if (!event || typeof event.kind !== 'number') return undefined; + + switch (event.kind) { + case 0: { + const data = parse_nostr_metadata_event(event); + if (!data) return; + return { kind: event.kind, metadata: data }; + }; + + default: return undefined; + } +}; + + diff --git a/utils-nostr/src/index.ts b/utils-nostr/src/index.ts @@ -1,14 +1,6 @@ -export * from "./lib/events" -export * from "./lib/keys" -export * from "./lib/ndk" -export * from "./lib/relay" -export * from "./lib/tags" -export * from "./lib/types" -export * from "./services/events/lib" -export * from "./services/events/types" -export * from "./services/keys/lib" -export * from "./services/keys/types" -export * from "./services/relay/lib" -export * from "./services/relay/types" -export * from "./types" -export * from "./util" +export * from "./events/lib.js" +export * from "./events/parse.js" +export * from "./events/subscription.js" +export * from "./keys/lib.js" +export * from "./schemas/lib.js" +export * from "./types/lib.js" diff --git a/utils-nostr/src/keys/lib.ts b/utils-nostr/src/keys/lib.ts @@ -0,0 +1,94 @@ +import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; +import { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk"; +import { generateSecretKey, getPublicKey, nip19 } from "nostr-tools"; + +export const regex_nostr_key = /^[a-f0-9]{64}$/; + +export const lib_nostr_get_key_bytes = (hex: string): Uint8Array => { + return hexToBytes(hex); +}; + +export const lib_nostr_get_key_hex = (bytes: Uint8Array): string => { + return bytesToHex(bytes); +}; + +export const lib_nostr_key_generate = (): string => { + const bytes = generateSecretKey(); + return lib_nostr_get_key_hex(bytes); +}; + +export const lib_nostr_npub_encode = (public_key_hex?: string): string | undefined => { + try { + if (!public_key_hex) return undefined; + const npub = nip19.npubEncode(public_key_hex) + return npub; + } catch { + return undefined; + } +}; + +export const lib_nostr_npub_decode = (npub?: string): string | undefined => { + try { + if (!npub) return undefined; + const { type, data } = nip19.decode(npub); + if (type === `npub` && data) return data; + } catch { + return undefined; + } +}; + +export const lib_nostr_nsec_encode = (secret_key_hex?: string): string | undefined => { + try { + if (!secret_key_hex) return undefined; + const bytes = lib_nostr_get_key_bytes(secret_key_hex); + return nip19.nsecEncode(bytes); + } catch { + return undefined; + } +}; + +export const lib_nostr_nsec_decode = (nsec?: string): string | undefined => { + try { + if (!nsec) return undefined; + const decode = nip19.decode(nsec); + if (decode && decode.type === `nsec` && decode.data) return bytesToHex(decode.data); + return undefined; + } catch { + return undefined; + } +}; + +export const lib_nostr_nprofile_encode = (public_key_hex: string, relays: string[]): string | undefined => { + try { + if (!public_key_hex || !relays.length) return undefined; + const nprofile = nip19.nprofileEncode({ pubkey: public_key_hex, relays }) + return nprofile; + } catch { + return undefined; + } + +}; + +export const lib_nostr_nprofile_decode = (nprofile?: string): nip19.ProfilePointer | undefined => { + try { + if (!nprofile) return undefined; + const { type, data } = nip19.decode(nprofile); + if (type === `nprofile` && data) return data; + } catch { + return undefined; + } +}; + +export const lib_nostr_public_key = (secret_key_hex: string): string => { + const bytes = lib_nostr_get_key_bytes(secret_key_hex); + return getPublicKey(bytes); +}; + +export const lib_nostr_secret_key_validate = (secret_key: string): string | undefined => { + try { + const signer = new NDKPrivateKeySigner(secret_key); + return signer.privateKey; + } catch { + return undefined; + } +}; +\ No newline at end of file diff --git a/utils-nostr/src/lib/events.ts b/utils-nostr/src/lib/events.ts @@ -1,45 +0,0 @@ -import { INostrEventServiceNeventEncode, uuidv4, type INostrEventEventSign } from "$root"; -import { schnorr } from "@noble/curves/secp256k1"; -import { hexToBytes } from "@noble/hashes/utils"; -import { finalizeEvent, getEventHash, nip19, type NostrEvent as NostrToolsEvent } from "nostr-tools"; - -export const lib_nostr_event_verify = (event: NostrToolsEvent): boolean => { - const hash = getEventHash(event); - if (hash !== event.id) return false - const valid = schnorr.verify(event.sig, hash, event.pubkey); - return valid; -}; - -export const lib_nostr_event_sign = (opts: INostrEventEventSign): NostrToolsEvent => { - return finalizeEvent(opts.event, hexToBytes(opts.secret_key)) -}; - -export const lib_nostr_event_sign_attest = (secret_key: string): NostrToolsEvent => { - return lib_nostr_event_sign({ - secret_key, - event: { - kind: 1, - created_at: Math.floor(Date.now() / 1000), - tags: [], - content: uuidv4(), - }, - }); -}; - - -export const lib_nostr_event_verify_serialized = async (event_serialized: string): Promise<{ public_key: string } | undefined> => { - try { - const event = JSON.parse(event_serialized); - const hash = getEventHash(event); - if (hash !== event.id) return undefined; - const valid = schnorr.verify(event.sig, hash, event.pubkey); - if (valid) return { public_key: String(event.pubkey) }; - return undefined; - } catch { - return undefined; - } -}; - -export const lib_nostr_nevent_encode = (opts: INostrEventServiceNeventEncode): string => { - return nip19.neventEncode(opts); -}; -\ No newline at end of file diff --git a/utils-nostr/src/lib/keys.ts b/utils-nostr/src/lib/keys.ts @@ -1,67 +0,0 @@ -import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; -import { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk"; -import { generateSecretKey, getPublicKey, nip19 } from "nostr-tools"; - -export const lib_nostr_get_key_bytes = (hex: string): Uint8Array => { - return hexToBytes(hex); -}; - -export const lib_nostr_get_key_hex = (bytes: Uint8Array): string => { - return bytesToHex(bytes); -}; - -export const lib_nostr_key_generate = (): string => { - const bytes = generateSecretKey(); - return lib_nostr_get_key_hex(bytes); -}; - -export const lib_nostr_npub_encode = (public_key_hex?: string): string | undefined => { - if (!public_key_hex) return undefined; - const npub = nip19.npubEncode(public_key_hex) - return npub; -}; - -export const lib_nostr_npub_decode = (npub?: string): string | undefined => { - if (!npub) return undefined; - const { type, data } = nip19.decode(npub) - if (type === `npub` && data) return data -}; - -export const lib_nostr_nsec_encode = (secret_key_hex?: string): string | undefined => { - if (!secret_key_hex) return undefined; - const bytes = lib_nostr_get_key_bytes(secret_key_hex); - return nip19.nsecEncode(bytes); -}; - -export const lib_nostr_nsec_decode = (nsec?: string): string | undefined => { - if (!nsec) return undefined; - const decode = nip19.decode(nsec); - if (decode && decode.type === `nsec` && decode.data) return bytesToHex(decode.data); - return undefined; -}; - -export const lib_nostr_nprofile_encode = (public_key_hex: string, relays: string[]): string | undefined => { - if (!public_key_hex || !relays.length) return undefined; - const nprofile = nip19.nprofileEncode({ pubkey: public_key_hex,relays }) - return nprofile; -}; - -export const lib_nostr_nprofile_decode = (nprofile?: string): nip19.ProfilePointer | undefined => { - if (!nprofile) return undefined; - const { type, data } = nip19.decode(nprofile) - if (type === `nprofile` && data) return data -}; - -export const lib_nostr_public_key = (secret_key_hex: string): string => { - const bytes = lib_nostr_get_key_bytes(secret_key_hex); - return getPublicKey(bytes); -}; - -export const lib_nostr_secret_key_validate = (secret_key: string): string | undefined => { - try { - const signer = new NDKPrivateKeySigner(secret_key); - return signer.privateKey; - } catch { - return undefined; - } -}; -\ No newline at end of file diff --git a/utils-nostr/src/lib/ndk.ts b/utils-nostr/src/lib/ndk.ts @@ -1,157 +0,0 @@ -import { INostrClassified, INostrComment, INostrFollow, INostrJobRequest, INostrReaction, NostrEventTags, tags_classified, tags_comment, tags_follow_list, tags_job_request, tags_reaction, time_now_ms, type INostrMetadata } from '$root'; -import NDK, { NDKCacheAdapter, NDKEvent, NDKKind, NDKPrivateKeySigner, NDKUser } from '@nostr-dev-kit/ndk'; - -export type NDKEventFigure<T extends object> = { - ndk: NDK; - ndk_user: NDKUser; - date_published?: Date; -} & T; - -export const create_ndk = (explicitRelayUrls: string[], cacheAdapter?: NDKCacheAdapter): NDK => { - return new NDK({ - explicitRelayUrls, - enableOutboxModel: false, - cacheAdapter - }); -}; - -export const create_ndk_signer = (secret_key: string): NDKPrivateKeySigner => { - return new NDKPrivateKeySigner(secret_key); -}; - -export const ndk_init = async (opts: { - ndk: NDK; - secret_key: string; -}): Promise<NDKUser | undefined> => { - try { - const { ndk, secret_key } = opts; - const signer = new NDKPrivateKeySigner(secret_key); - ndk.signer = signer; - const user = await signer.user(); - if (user) { - user.ndk = ndk; - return user; - } - } catch (e) { - console.log(`(error) ndk_init `, e); - }; -}; - -export const ndk_event = async (opts: NDKEventFigure<{ - basis: { - kind: number; - content: string; - tags?: NostrEventTags; - } -}>): Promise<NDKEvent | undefined> => { - try { - const { ndk: ndk, ndk_user: ndk_user, basis } = opts; - const time_now = time_now_ms(); - const published_at = opts.date_published ? Math.floor(opts.date_published.getTime() / 1000).toString() - : time_now.toString() - const tags: NostrEventTags = [ - ['published_at', published_at], - ]; - if (basis.tags?.length) tags.push(...basis.tags); - const ev = new NDKEvent(ndk, { - kind: basis.kind, - pubkey: ndk_user.pubkey, - content: basis.content, - created_at: time_now, - tags - }); - return ev; - } catch (e) { - console.log(`(error) ndk_event `, e); - }; -}; - -export const ndk_event_metadata = async (opts: NDKEventFigure<{ - data: INostrMetadata -}>): Promise<NDKEvent | undefined> => { - const { ndk, ndk_user, data } = opts; - return await ndk_event({ - ndk, - ndk_user, - basis: { - kind: 0, - content: JSON.stringify(data), - }, - }); -}; - -export const ndk_event_follows = async (opts: NDKEventFigure<{ - data: INostrFollow; -}>): Promise<NDKEvent | undefined> => { - const { ndk, ndk_user, data } = opts; - return await ndk_event({ - ndk, - ndk_user, - basis: { - kind: 3, - content: ``, - tags: tags_follow_list(data.list), - }, - }); -}; - -export const ndk_event_classified = async (opts: NDKEventFigure<{ - data: INostrClassified; -}>): Promise<NDKEvent | undefined> => { - const { ndk, ndk_user, data } = opts; - return await ndk_event({ - ndk, - ndk_user, - basis: { - kind: NDKKind.Classified, - content: ``, - tags: tags_classified(data), - }, - }); -}; - -export const ndk_event_job_request = async (opts: NDKEventFigure<{ - data: INostrJobRequest; -}>): Promise<NDKEvent | undefined> => { - const { ndk, ndk_user, data } = opts; - return await ndk_event({ - ndk, - ndk_user, - basis: { - kind: NDKKind.DVMReqDiscoveryNostrContent, - content: ``, - tags: tags_job_request(data) - }, - }); -}; - -export const ndk_event_reaction = async (opts: NDKEventFigure<{ - data: INostrReaction; -}>): Promise<NDKEvent | undefined> => { - const { ndk, ndk_user, data } = opts; - return await ndk_event({ - ndk, - ndk_user, - basis: { - kind: NDKKind.Reaction, - content: data.content, - tags: tags_reaction(data) - }, - }); -}; - -export const ndk_event_comment = async (opts: NDKEventFigure<{ - data: INostrComment; -}>): Promise<NDKEvent | undefined> => { - const { ndk, ndk_user, data } = opts; - return await ndk_event({ - ndk, - ndk_user, - basis: { - kind: NDKKind.GenericReply, - content: data.content, - tags: tags_comment(data) - }, - }); -}; - diff --git a/utils-nostr/src/lib/relay.ts b/utils-nostr/src/lib/relay.ts @@ -1,45 +0,0 @@ -export type NostrRelayInformationDocument = { - id?: string; - name?: string; - description?: string; - pubkey?: string; - contact?: string; - supported_nips?: number[]; - software?: string; - version?: string; - limitation_payment_required?: string; - limitation_restricted_writes?: boolean; -} - -export type NostrRelayInformationDocumentFields = { [K in keyof NostrRelayInformationDocument]: string; }; - -export const lib_nostr_relay_parse_information_document = (data: any): NostrRelayInformationDocument | undefined => { - const obj = typeof data === `string` ? JSON.parse(data) : data; - return { - id: typeof obj.id === 'string' ? obj.id : undefined, - name: typeof obj.name === 'string' ? obj.name : undefined, - description: typeof obj.description === 'string' ? obj.description : undefined, - pubkey: typeof obj.pubkey === 'string' ? obj.pubkey : undefined, - contact: typeof obj.contact === 'string' ? obj.contact : undefined, - supported_nips: Array.isArray(obj.supported_nips) && obj.supported_nips.every((nip: any) => typeof nip === 'number') - ? obj.supported_nips - : undefined, - software: typeof obj.software === 'string' ? obj.software : undefined, - version: typeof obj.version === 'string' ? obj.version : undefined, - limitation_payment_required: obj.limitation && typeof obj.limitation === 'object' && typeof obj.limitation.payment_required === 'string' ? obj.limitation.payment_required : undefined, - limitation_restricted_writes: obj.limitation && typeof obj.limitation === 'object' && typeof obj.limitation.restricted_writes === 'boolean' ? obj.limitation.restricted_writes : undefined, - }; -}; - -export const lib_nostr_relay_build_information_document = (data: any): NostrRelayInformationDocumentFields | undefined => { - const doc = lib_nostr_relay_parse_information_document(data); - if (!doc) return; - const result: Partial<NostrRelayInformationDocumentFields> = {}; - Object.entries(doc).forEach(([key, value]) => { - if (typeof value === 'boolean') result[key as keyof NostrRelayInformationDocument] = value ? '1' : '0'; - else if (Array.isArray(value)) result[key as keyof NostrRelayInformationDocument] = value.join(', '); - else if (value === null || value === undefined) result[key as keyof NostrRelayInformationDocument] = ''; - else result[key as keyof NostrRelayInformationDocument] = String(value); - }); - return result; -}; -\ No newline at end of file diff --git a/utils-nostr/src/lib/tags.ts b/utils-nostr/src/lib/tags.ts @@ -1,150 +0,0 @@ -import { INostrClassified, INostrComment, INostrFollowList, INostrJobRequest, INostrReaction, NostrEventTagClient, NostrEventTagLocation, NostrEventTagMediaUpload, NostrEventTagPrice, NostrEventTagPriceDiscount, NostrEventTagQuantity, type NostrEventTag, type NostrEventTags } from "$root"; -import ngeotags, { type InputData as NostrGeotagsInputData } from "nostr-geotags"; - -export const tag_client = (opts: NostrEventTagClient, d_tag?: string): NostrEventTag => { - const tag = [`client`, opts.name]; - if (d_tag) tag.push(`31990:${opts.pubkey}:${d_tag}`); - tag.push(opts.relay); - return tag; -}; - -export const tags_follow_list = (list: INostrFollowList[]): NostrEventTags => { - return list.map(({ public_key, relay_url, contact_name }) => { - const entry = [`p`, public_key]; - if (relay_url) entry.push(relay_url); - if (contact_name) entry.push(contact_name); - return entry; - }); -}; - -export const tag_classified_quantity = (opts: NostrEventTagQuantity): NostrEventTag => { - const tag = [`quantity`, opts.amt, opts.unit]; - if (opts.label) tag.push(opts.label); - return tag.map(i => i.toLowerCase()); -}; - -export const tag_classified_price_discount = (discount: NostrEventTagPriceDiscount): NostrEventTag => { - const tag = [`price-discount-${Object.keys(discount)[0]}`]; - if (`mass` in discount) tag.push(...Object.values(discount.mass)); - else if (`quantity` in discount) tag.push(...Object.values(discount.quantity)); - else if (`subtotal` in discount) tag.push(...Object.values(discount.subtotal)); - else if (`total` in discount) tag.push(...Object.values(discount.total)); - return tag.map(i => i.toLowerCase()); -}; - -export const tag_classified_price = (price: NostrEventTagPrice): NostrEventTag => { - const tag = [`price`, price.amt, price.currency, price.qty_amt, price.qty_unit, price.qty_key]; - return tag.map(i => i.toLowerCase()); -}; - -export const tag_classified_image = (opts: NostrEventTagMediaUpload): NostrEventTag => { - const tag = [`image`, opts.url]; - if (opts.size) tag.push(`${opts.size.w}x${opts.size.h}`) - return tag; -}; - -export const tag_classified_location = (opts: NostrEventTagLocation): NostrEventTag => { - if (!opts.primary) return []; - const tag = [`location`, opts.primary]; - if (opts.city) tag.push(opts.city); - if (opts.region) tag.push(opts.region); - if (opts.country) tag.push(opts.country); - return tag; -}; - -export const tags_classified_location_geotags = (opts: NostrEventTagLocation): NostrEventTags => { - const { lat, lng: lon, city, region: regionName, country } = opts; - const country_raw = country || ``; - const countryCode = country_raw && country_raw?.length <= 3 ? country_raw : undefined; - const countryName = country_raw && country_raw?.length > 3 ? country_raw : undefined; - return ngeotags({ lat, lon, city, regionName, countryCode, countryName } satisfies NostrGeotagsInputData, { geohash: true, gps: true, city: true, iso31662: true }); -}; - -export const tags_classified = (opts: INostrClassified): NostrEventTags => { - const { d_tag, listing, quantities, prices } = opts; - const tags: NostrEventTags = [[`d`, d_tag]]; - if (opts.client) tags.push(tag_client(opts.client, opts.d_tag)); - for (const [k, v] of Object.entries(listing)) if (v) tags.push([k, v]); - for (const quantity of quantities) { - tags.push(tag_classified_quantity(quantity)); - } - for (const price of prices) { - tags.push(tag_classified_price(price)); - } - for (const discount of opts.discounts || []) { - tags.push(tag_classified_price_discount(discount)); - } - if (opts.location) { - tags.push(tag_classified_location(opts.location)); - tags.push(...tags_classified_location_geotags(opts.location)); - } - if (opts.images) for (const image_tags of opts.images) tags.push(tag_classified_image(image_tags)); - return tags; -}; - -export const tags_job_request = (opts: INostrJobRequest): NostrEventTags => { - const tag_i: string[] = [`i`]; - if (`classified` in opts.input && opts.input?.classified) { - const { classified: event_request } = opts.input; - let marker = `*`; - let data = `*`; - if (event_request.marker && `order` in event_request.marker) { - marker = `order`; - data = JSON.stringify({ event: { id: event_request.id }, order: event_request.marker.order }); - } - tag_i.push(...[data, `text`, event_request.relay, marker]); - tag_i.push(...(opts.input.tags || [])) - } - - const tags: NostrEventTags = [tag_i]; - tags.push(...(opts.tags || [])) - return tags; -}; - -export const tags_reaction = (opts: INostrReaction): NostrEventTags => { - const { ref_event } = opts; - const ref_kind = ref_event.kind.toString(); - const ref_author = ref_event.author; - const tags: NostrEventTags = [ - [`e`, ref_event.id, ...ref_event.relays || ``], - [`p`, ref_author], - [`k`, ref_kind], - ]; - if (ref_event.d_tag) tags.push([`a`, `${ref_kind}:${ref_author}:${ref_event.d_tag}`, ...ref_event.relays || ``]) - return tags; -}; - -export const tags_comment = (opts: INostrComment): NostrEventTags => { - const { root_event, ref_event } = opts; - - const root = { - kind: root_event.kind.toString(), - author: root_event.author, - id: root_event.id, - d_tag: root_event.d_tag, - relays: root_event.relays || [], - }; - - const parent = (ref_event && ref_event.id) - ? { - kind: ref_event.kind.toString(), - author: ref_event.author, - id: ref_event.id, - d_tag: ref_event.d_tag, - relays: ref_event.relays || [], - } - : root; - - const tags: NostrEventTags = [ - ["E", root.id, ...root.relays], - ["P", root.author], - ["K", root.kind], - ...(root.d_tag ? [["A", `${root.kind}:${root.author}:${root.d_tag}`, ...root.relays]] : []), - ["e", parent.id, ...parent.relays], - ["p", parent.author], - ["k", parent.kind], - ...(parent.d_tag ? [["a", `${parent.kind}:${parent.author}:${parent.d_tag}`, ...parent.relays]] : []), - ]; - - return tags; -}; -\ No newline at end of file diff --git a/utils-nostr/src/lib/types.ts b/utils-nostr/src/lib/types.ts @@ -1,156 +0,0 @@ -import { NostrEventReferenced } from "$root"; -import { ListingOrder } from "@radroots/radroots-common-bindings"; -import { type EventTemplate as NostrToolsEventTemplate } from "nostr-tools"; - -export type INostrMetadata = { - name?: string; - display_name?: string; - about?: string; - website?: string; - picture?: string; - banner?: string; - nip05?: string; - lud06?: string; - lud16?: string; - bot?: boolean; -}; - -export type INostrFollowList = { - public_key: string; - relay_url?: string; - contact_name?: string; -}; - -export type INostrFollow = { - list: INostrFollowList[] -}; - -export type NostrEventTagQuantity = { - amt: string; - unit: string; - label?: string; -}; - -export type NostrEventTagPriceDiscount = ( - { - quantity: { - ref_quantity: string; - threshold: string; - value: string; - currency: string; - } - } | - { - mass: { - unit: string; - threshold: string; - threshold_unit: string; - value: string; - currency: string; - } - } | - { - subtotal: { - threshold: string; - currency: string; - value: string; - measure: string; - } - } | - { - total: { - total_min: string; - value: string; - measure: string; - } - } -); - -export type NostrEventTagPrice = { - amt: string; - currency: string; - qty_amt: string; - qty_unit: string; - qty_key: string; -}; - -export type INostrClassified = { - d_tag: string; - listing: NostrEventTagListing; - quantities: NostrEventTagQuantity[]; - prices: NostrEventTagPrice[]; - discounts?: NostrEventTagPriceDiscount[]; - location?: NostrEventTagLocation; - images?: NostrEventTagMediaUpload[]; - client?: NostrEventTagClient; -}; - -export type NostrJobRequestMassUnit = 'g' | 'kg' | 'lb'; - -export type INostrJobRequestInput = { - tags?: string[]; -} & ({ - classified: { - id: string; - relay: string; - marker?: ({ - order: ListingOrder; - }); - } -}) - -export type INostrJobRequest = { - input: INostrJobRequestInput; - tags?: string[][]; -}; - -export type INostrEventEventSign = { - secret_key: string; - event: NostrToolsEventTemplate; -} - -export type NostrEventTagListing = { - key: string; - title: string; - category: string; - summary?: string; - process?: string; - lot?: string; - location?: string; - profile?: string; - year?: string; -}; - -export type NostrEventTagLocation = { - primary: string; - city?: string; - region?: string; - country?: string; - lat?: number; - lng?: number; -}; - -export type NostrEventTagMediaUpload = { - url: string; - size?: { - w: number; - h: number; - }; -}; - -export type NostrEventTagClient = { - name: string; - pubkey: string; - relay: string; -}; - -export type INostrReaction = { - ref_event: NostrEventReferenced; - content: string; -}; - -export type INostrComment = { - root_event: NostrEventReferenced; - ref_event?: NostrEventReferenced; - content: string; -}; -\ No newline at end of file diff --git a/utils-nostr/src/schemas/lib.ts b/utils-nostr/src/schemas/lib.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; + +export const nostr_event_metadata_schema = z.object({ + name: z.string(), + display_name: z.string().optional(), + about: z.string().optional(), + website: z.url().optional(), + picture: z.url().optional(), + banner: z.url().optional(), + nip05: z.string().optional(), + lud06: z.string().optional(), + lud16: z.string().optional(), + bot: z.boolean().optional(), +}); diff --git a/utils-nostr/src/services/events/lib.ts b/utils-nostr/src/services/events/lib.ts @@ -1,79 +0,0 @@ -import { err_msg, ErrorMessage, INostrClassified, INostrEventEventSign, INostrEventService, INostrEventServiceEventResolve, INostrEventServiceNeventEncode, INostrFollow, INostrMetadata, lib_nostr_event_sign, lib_nostr_event_sign_attest, lib_nostr_event_verify, lib_nostr_event_verify_serialized, lib_nostr_nevent_encode, ndk_event, ndk_event_classified, ndk_event_follows } from "$root"; -import NDK, { NDKKind, NDKUser, type NDKEvent } from "@nostr-dev-kit/ndk"; -import { type NostrEvent as NostrToolsEvent } from "nostr-tools"; - -export class NostrEventService implements INostrEventService { - private resolve_ndk_user = async (ndk: NDK): Promise<NDKUser | ErrorMessage<string>> => { - const user = await ndk.signer?.user(); - if (!user) return err_msg(`error.ndk.user_undefined`); - return user; - } - - private resolve_ndk_event = (ev?: NDKEvent) => { - if (!ev) return err_msg(`error.event_undefined`); - return ev; - } - - public first_tag_value = (event: NDKEvent, tag_name: string): string => { - const tag = event.getMatchingTags(tag_name)[0]; - return tag ? tag[1] : ""; - } - - public nostr_event_sign = (opts: INostrEventEventSign): NostrToolsEvent => { - return lib_nostr_event_sign(opts); - }; - - public nostr_event_sign_attest = (secret_key: string): NostrToolsEvent => { - return lib_nostr_event_sign_attest(secret_key); - }; - - public nostr_event_verify = (event: NostrToolsEvent): boolean => { - return lib_nostr_event_verify(event); - }; - - public nostr_event_verify_serialized = (event_serialized: string): boolean => { - const result = lib_nostr_event_verify_serialized(event_serialized); - return !!result; - }; - - public nevent_encode = (opts: INostrEventServiceNeventEncode): string => { - return lib_nostr_nevent_encode(opts); - }; - - public metadata = async (ndk: NDK, opts: INostrMetadata): Promise<INostrEventServiceEventResolve> => { - const ndk_user = await this.resolve_ndk_user(ndk); - if (`err` in ndk_user) return ndk_user; - const ev = await ndk_event({ - ndk, - ndk_user, - basis: { - kind: NDKKind.Metadata, - content: JSON.stringify(opts), - }, - }); - return this.resolve_ndk_event(ev); - } - - public follows = async (ndk: NDK, data: INostrFollow): Promise<INostrEventServiceEventResolve> => { - const ndk_user = await this.resolve_ndk_user(ndk); - if (`err` in ndk_user) return ndk_user; - const ev = await ndk_event_follows({ - ndk, - ndk_user, - data - }); - return this.resolve_ndk_event(ev); - } - - public classified = async (ndk: NDK, data: INostrClassified): Promise<INostrEventServiceEventResolve> => { - const ndk_user = await this.resolve_ndk_user(ndk); - if (`err` in ndk_user) return ndk_user; - const ev = await ndk_event_classified({ - ndk, - ndk_user, - data - }); - return this.resolve_ndk_event(ev); - } -} - diff --git a/utils-nostr/src/services/events/types.ts b/utils-nostr/src/services/events/types.ts @@ -1,24 +0,0 @@ -import type { INostrClassified, INostrEventEventSign, INostrFollow, INostrMetadata, ResolveError } from "$root"; -import NDK, { NDKEvent } from "@nostr-dev-kit/ndk"; -import { type NostrEvent as NostrToolsEvent } from "nostr-tools"; - -export type INostrEventServiceNeventEncode = { - id: string; - relays: string[]; - author: string; - kind: number; -}; - -export type INostrEventServiceEventResolve = ResolveError<NDKEvent>; - -export type INostrEventService = { - first_tag_value(event: NDKEvent, tag_name: string): string; - nostr_event_sign: (opts: INostrEventEventSign) => NostrToolsEvent; - nostr_event_sign_attest: (secret_key: string) => NostrToolsEvent; - nostr_event_verify_serialized: (event_serialized: string) => boolean; - nostr_event_verify: (event: NostrToolsEvent) => boolean; - nevent_encode: (opts: INostrEventServiceNeventEncode) => string; - metadata: (ndk: NDK, opts: INostrMetadata) => Promise<INostrEventServiceEventResolve>; - follows: (ndk: NDK, opts: INostrFollow) => Promise<INostrEventServiceEventResolve>; - classified: (ndk: NDK, opts: INostrClassified) => Promise<INostrEventServiceEventResolve>; -}; diff --git a/utils-nostr/src/services/keys/lib.ts b/utils-nostr/src/services/keys/lib.ts @@ -1,120 +0,0 @@ -import { type INostrKeyService, lib_nostr_get_key_bytes, lib_nostr_key_generate, lib_nostr_nsec_decode, lib_nostr_nsec_encode } from '$root'; -import { getPublicKey, nip19 } from 'nostr-tools'; - -export class NostrKeyService implements INostrKeyService { - /** - * - * @returns nostr secret key hex - */ - public generate_key(): string { - return lib_nostr_key_generate(); - }; - - - /** - * - * @returns nostr public key hex from secret key - */ - public public_key(secret_key_hex: string | undefined): string { - try { - if (!secret_key_hex) return ``; - const bytes = lib_nostr_get_key_bytes(secret_key_hex); - const hex = getPublicKey(bytes) - return hex; - } catch (e) { - return `` - } - } - - /** - * - * @returns nostr secret key to public key hex - */ - public publickey_decode(secret_key_hex?: string): string | undefined { - try { - if (secret_key_hex) { - return getPublicKey(lib_nostr_get_key_bytes(secret_key_hex)) - } - return undefined; - } catch (e) { - return undefined; - } - } - - /** - * - * @returns nostr public key npub - */ - public npub(public_key_hex: string | undefined, fallback_to_hex?: boolean): string { - if (!public_key_hex) return ``; - const npub = nip19.npubEncode(public_key_hex); - return npub ? npub : fallback_to_hex ? public_key_hex : ``; - } - - /** - * - * @returns public key hex from npub - */ - public npub_decode(npub: string): string { - const decode = nip19.decode(npub); - if (decode && decode.type === `npub` && decode.data) return decode.data - return ``; - } - - /** - * - * @returns nostr secret key nsec - */ - public nsec(secret_key_hex?: string): string | undefined { - return lib_nostr_nsec_encode(secret_key_hex); - } - - /** - * - * @returns nostr secret key hex from nsec - */ - public nsec_decode(nsec: string): string | undefined { - return lib_nostr_nsec_decode(nsec); - } - - /** - * - * @returns - */ - public nevent(event_pointer: nip19.EventPointer, relays: string[]): string { - return nip19.neventEncode(event_pointer) - } - - /** - * - * @returns nostr public key nprofile - */ - public nprofile(public_key_hex: string, relays: string[]): string { - if (!public_key_hex || !relays.length) return ``; - return nip19.nprofileEncode({ pubkey: public_key_hex, relays }) - } - - /** - * - * @returns nostr public key nprofile - */ - public nprofile_decode(nprofile: string): [string, string[]] | undefined { - if (!nprofile) return undefined; - const decode = nip19.decode(nprofile); - if (decode && decode.type === `nprofile` && decode.data && decode.data.pubkey && decode.data.relays) return [decode.data.pubkey, decode.data.relays] - return undefined; - } - - /** - * - * @returns - */ - public secretkey_to_publickey(nsec_or_hex: string): string | undefined { - if (nsec_or_hex.startsWith(`nsec1`)) { - return this.nsec_decode(nsec_or_hex); - } else if (nsec_or_hex.length === 64) { - return this.publickey_decode(nsec_or_hex) - } - return undefined; - } -}; diff --git a/utils-nostr/src/services/keys/types.ts b/utils-nostr/src/services/keys/types.ts @@ -1,11 +0,0 @@ -export type INostrKeyService = { - generate_key(): string; - public_key(secret_key_hex: string | undefined): string; - npub(public_key_hex: string | undefined): string; - npub_decode(npub: string): string; - nsec(secret_key_hex: string | undefined): string | undefined; - nsec_decode(nsec: string): string | undefined; - nprofile(public_key_hex: string, relays: string[]): string; - nprofile_decode(nprofile: string): [string, string[]] | undefined; - secretkey_to_publickey(nsec_or_hex: string): string | undefined; -}; diff --git a/utils-nostr/src/services/relay/lib.ts b/utils-nostr/src/services/relay/lib.ts @@ -1,16 +0,0 @@ -import { lib_nostr_relay_parse_information_document, type INostrRelayService, type NostrRelayInformationDocument, type NostrRelayInformationDocumentFields } from "$root"; - -export class NostrRelayService implements INostrRelayService { - public parse_information_document = (data: any): NostrRelayInformationDocumentFields | undefined => { - const doc = lib_nostr_relay_parse_information_document(data); - if (!doc) return; - const result: Partial<NostrRelayInformationDocumentFields> = {}; - Object.entries(doc).forEach(([key, value]) => { - if (typeof value === 'boolean') result[key as keyof NostrRelayInformationDocument] = value ? '1' : '0'; - else if (Array.isArray(value)) result[key as keyof NostrRelayInformationDocument] = value.join(', '); - else if (value === null || value === undefined) result[key as keyof NostrRelayInformationDocument] = ''; - else result[key as keyof NostrRelayInformationDocument] = String(value); - }); - return result; - }; -} -\ No newline at end of file diff --git a/utils-nostr/src/services/relay/types.ts b/utils-nostr/src/services/relay/types.ts @@ -1,6 +0,0 @@ -import { type NostrRelayInformationDocumentFields } from "$root"; - -export type INostrRelayService = { - parse_information_document: (data: any) => NostrRelayInformationDocumentFields | undefined; -}; - diff --git a/utils-nostr/src/types.ts b/utils-nostr/src/types.ts @@ -1,14 +0,0 @@ -import { NDKEvent } from "@nostr-dev-kit/ndk"; - -export type NostrNdkEvent = NDKEvent; -export type NostrEventTag = string[]; -export type NostrEventTags = NostrEventTag[]; -export type ErrorMessage<T extends string> = { err: T }; -export type ResolveError<T> = T | ErrorMessage<string>; -export type NostrEventReferenced = { - id: string; - kind: number; - author: string; - relays?: string[]; - d_tag?: string; -} -\ No newline at end of file diff --git a/utils-nostr/src/types/lib.ts b/utils-nostr/src/types/lib.ts @@ -0,0 +1,4 @@ +import { z } from 'zod'; +import { nostr_event_metadata_schema } from "../schemas/lib.js"; + +export type NostrEventMetadata = z.infer<typeof nostr_event_metadata_schema>; diff --git a/utils-nostr/src/util.ts b/utils-nostr/src/util.ts @@ -1,10 +0,0 @@ -import { type ErrorMessage } from "$root"; -import { v4 } from "uuid"; - -export const time_now_ms = (): number => Math.floor(new Date().getTime() / 1000); - -export const uuidv4 = (): string => v4(); - -export const err_msg = <T extends string>(err: T): ErrorMessage<T> => { - return { err }; -}; -\ No newline at end of file diff --git a/utils-nostr/tsconfig.base.json b/utils-nostr/tsconfig.base.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2019", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "sourceMap": false, + "rootDir": "src" + } +} diff --git a/utils-nostr/tsconfig.cjs.json b/utils-nostr/tsconfig.cjs.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist/cjs", + "module": "CommonJS", + "declaration": false, + "moduleResolution": "Node" + }, + "include": ["src"] +} diff --git a/utils-nostr/tsconfig.esm.json b/utils-nostr/tsconfig.esm.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist/esm", + "module": "NodeNext", + "declaration": true, + "declarationDir": "./dist/types", + "moduleResolution": "NodeNext", + "emitDeclarationOnly": false + }, + "include": ["src"] +} diff --git a/utils-nostr/tsconfig.json b/utils-nostr/tsconfig.json @@ -1,28 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "target": "es2021", - "lib": [ - "es2021", - "dom" - ], - "module": "ESNext", - "moduleResolution": "node", - "declaration": true, - "declarationMap": true, - "outDir": "./dist", - "esModuleInterop": true, - "skipLibCheck": true, - "baseUrl": ".", - "paths": { - "$root": ["src/index.js"] - } - }, - "include": [ - "src" - ], - "exclude": [ - "node_modules", - "dist" - ], -} -\ No newline at end of file diff --git a/utils-nostr/tsup.config.ts b/utils-nostr/tsup.config.ts @@ -1,11 +0,0 @@ -import { defineConfig } from "tsup"; - -export default defineConfig({ - entry: ['src/index.ts'], - format: ['esm', 'cjs'], - dts: true, - outDir: 'dist', - splitting: false, - clean: true, - sourcemap: true, -});