commit efa8d40abf6de154651d9f3fbd0e4abae8703759 parent 998b84da27aad5a0179086630e5232e67d5caac6 Author: triesap <triesap@radroots.dev> Date: Wed, 24 Dec 2025 21:26:30 +0000 nostr: move tag generation to wasm codec - Delegate tag construction to events-codec-wasm helpers - Remove manual tag assembly logic across event types - Add shared wasm initialization and JSON tag parsing - Update callers to await wasm-backed tag builders Diffstat:
18 files changed, 132 insertions(+), 296 deletions(-)
diff --git a/nostr/src/domain/trade/listing/accept/lib.ts b/nostr/src/domain/trade/listing/accept/lib.ts @@ -28,7 +28,7 @@ export const nostr_event_trade_listing_accept_request = async ( make_event_input(data.listing_event_id, MARKER_LISTING), ]; - const tags = build_request_tags(KIND_TRADE_LISTING_ACCEPT_REQ, inputs, options); + const tags = await build_request_tags(KIND_TRADE_LISTING_ACCEPT_REQ, inputs, options); return nostr_event_create({ ...opts, @@ -45,7 +45,11 @@ export const nostr_event_trade_listing_accept_result = async ( ): Promise<NostrSignedEvent | undefined> => { const { request_event_id, content, options } = opts; - const base_tags = build_result_tags(KIND_TRADE_LISTING_ACCEPT_RES, request_event_id, options); + const base_tags = await build_result_tags( + KIND_TRADE_LISTING_ACCEPT_RES, + request_event_id, + options, + ); const tags = options?.chain ? [...base_tags, ...tags_trade_listing_chain(options.chain)] diff --git a/nostr/src/domain/trade/listing/conveyance/lib.ts b/nostr/src/domain/trade/listing/conveyance/lib.ts @@ -29,7 +29,7 @@ export const nostr_event_trade_listing_conveyance_request = async ( make_text_input({ method: data.method }, MARKER_PAYLOAD), ]; - const tags = build_request_tags(KIND_TRADE_LISTING_CONVEYANCE_REQ, inputs, options); + const tags = await build_request_tags(KIND_TRADE_LISTING_CONVEYANCE_REQ, inputs, options); return nostr_event_create({ ...opts, @@ -46,7 +46,11 @@ export const nostr_event_trade_listing_conveyance_result = async ( ): Promise<NostrSignedEvent | undefined> => { const { request_event_id, content, options } = opts; - const base_tags = build_result_tags(KIND_TRADE_LISTING_CONVEYANCE_RES, request_event_id, options); + const base_tags = await build_result_tags( + KIND_TRADE_LISTING_CONVEYANCE_RES, + request_event_id, + options, + ); const tags = options?.chain ? [...base_tags, ...tags_trade_listing_chain(options.chain)] diff --git a/nostr/src/domain/trade/listing/fulfillment/lib.ts b/nostr/src/domain/trade/listing/fulfillment/lib.ts @@ -26,7 +26,7 @@ export const nostr_event_trade_listing_fulfillment_request = async ( make_event_input(data.payment_result_event_id, MARKER_PAYMENT_RESULT), ]; - const tags = build_request_tags(KIND_TRADE_LISTING_FULFILL_REQ, inputs, options); + const tags = await build_request_tags(KIND_TRADE_LISTING_FULFILL_REQ, inputs, options); return nostr_event_create({ ...opts, @@ -43,7 +43,11 @@ export const nostr_event_trade_listing_fulfillment_result = async ( ): Promise<NostrSignedEvent | undefined> => { const { request_event_id, content, options } = opts; - const base_tags = build_result_tags(KIND_TRADE_LISTING_FULFILL_RES, request_event_id, options); + const base_tags = await build_result_tags( + KIND_TRADE_LISTING_FULFILL_RES, + request_event_id, + options, + ); const tags = options?.chain ? [...base_tags, ...tags_trade_listing_chain(options.chain)] diff --git a/nostr/src/domain/trade/listing/invoice/lib.ts b/nostr/src/domain/trade/listing/invoice/lib.ts @@ -26,7 +26,7 @@ export const nostr_event_trade_listing_invoice_request = async ( make_event_input(data.accept_result_event_id, MARKER_ACCEPT_RESULT), ]; - const tags = build_request_tags(KIND_TRADE_LISTING_INVOICE_REQ, inputs, options); + const tags = await build_request_tags(KIND_TRADE_LISTING_INVOICE_REQ, inputs, options); return nostr_event_create({ ...opts, @@ -47,7 +47,7 @@ export const nostr_event_trade_listing_invoice_result = async ( const parsed = typeof content === "string" ? undefined : content; - const base_tags = build_result_tags( + const base_tags = await build_result_tags( KIND_TRADE_LISTING_INVOICE_RES, request_event_id, options, diff --git a/nostr/src/domain/trade/listing/order/lib.ts b/nostr/src/domain/trade/listing/order/lib.ts @@ -29,7 +29,7 @@ export const nostr_event_trade_listing_order_request = async ( make_text_input(data.payload, MARKER_PAYLOAD), ]; - const tags = build_request_tags(KIND_TRADE_LISTING_ORDER_REQ, inputs, options); + const tags = await build_request_tags(KIND_TRADE_LISTING_ORDER_REQ, inputs, options); return nostr_event_create({ ...opts, @@ -46,7 +46,11 @@ export const nostr_event_trade_listing_order_result = async ( ): Promise<NostrSignedEvent | undefined> => { const { request_event_id, content, options } = opts; - const base_tags = build_result_tags(KIND_TRADE_LISTING_ORDER_RES, request_event_id, options); + const base_tags = await build_result_tags( + KIND_TRADE_LISTING_ORDER_RES, + request_event_id, + options, + ); const tags = options?.chain ? [...base_tags, ...tags_trade_listing_chain(options.chain)] diff --git a/nostr/src/domain/trade/listing/payment/lib.ts b/nostr/src/domain/trade/listing/payment/lib.ts @@ -29,7 +29,7 @@ export const nostr_event_trade_listing_payment_request = async ( make_text_input(data.proof, MARKER_PROOF), ]; - const tags = build_request_tags(KIND_TRADE_LISTING_PAYMENT_REQ, inputs, options); + const tags = await build_request_tags(KIND_TRADE_LISTING_PAYMENT_REQ, inputs, options); return nostr_event_create({ ...opts, @@ -46,7 +46,11 @@ export const nostr_event_trade_listing_payment_result = async ( ): Promise<NostrSignedEvent | undefined> => { const { request_event_id, content, options } = opts; - const base_tags = build_result_tags(KIND_TRADE_LISTING_PAYMENT_RES, request_event_id, options); + const base_tags = await build_result_tags( + KIND_TRADE_LISTING_PAYMENT_RES, + request_event_id, + options, + ); const tags = options?.chain ? [...base_tags, ...tags_trade_listing_chain(options.chain)] diff --git a/nostr/src/domain/trade/listing/receipt/lib.ts b/nostr/src/domain/trade/listing/receipt/lib.ts @@ -29,7 +29,7 @@ export const nostr_event_trade_listing_receipt_request = async ( ...(data.note ? [make_text_input({ note: data.note }, MARKER_PAYLOAD)] : []), ]; - const tags = build_request_tags(KIND_TRADE_LISTING_RECEIPT_REQ, inputs, options); + const tags = await build_request_tags(KIND_TRADE_LISTING_RECEIPT_REQ, inputs, options); return nostr_event_create({ ...opts, @@ -46,7 +46,11 @@ export const nostr_event_trade_listing_receipt_result = async ( ): Promise<NostrSignedEvent | undefined> => { const { request_event_id, content, options } = opts; - const base_tags = build_result_tags(KIND_TRADE_LISTING_RECEIPT_RES, request_event_id, options); + const base_tags = await build_result_tags( + KIND_TRADE_LISTING_RECEIPT_RES, + request_event_id, + options, + ); const tags = options?.chain ? [...base_tags, ...tags_trade_listing_chain(options.chain)] diff --git a/nostr/src/domain/trade/tags.ts b/nostr/src/domain/trade/tags.ts @@ -42,12 +42,12 @@ export const make_text_input = ( marker, }); -export const build_request_tags = ( +export const build_request_tags = async ( kind: number, inputs: RadrootsJobInput[], opts?: CommonRequestOpts, ) => - tags_job_request({ + await tags_job_request({ kind, inputs, output: opts?.output, @@ -59,7 +59,7 @@ export const build_request_tags = ( encrypted: !!opts?.encrypted, }); -export const build_result_tags = ( +export const build_result_tags = async ( kind: number, request_event_id: string, opts?: CommonResultOpts, @@ -69,7 +69,7 @@ export const build_result_tags = ( payment_bolt11?: string; }, ) => - tags_job_result({ + await tags_job_result({ kind, request_event: { id: request_event_id, diff --git a/nostr/src/events/comment/lib.ts b/nostr/src/events/comment/lib.ts @@ -10,12 +10,13 @@ export const nostr_event_comment = async ( opts: NostrEventFigure<{ data: RadrootsComment }>, ): Promise<NostrSignedEvent | undefined> => { const { data } = opts; + const tags = await tags_comment(data); return nostr_event_create({ ...opts, basis: { kind: KIND_RADROOTS_COMMENT, content: data.content, - tags: tags_comment(data), + tags, }, }); }; diff --git a/nostr/src/events/comment/tags.ts b/nostr/src/events/comment/tags.ts @@ -1,37 +1,10 @@ -import { RadrootsComment } from "@radroots/events-bindings"; -import { NostrEventTags } from "../../types/lib.js"; - -export const tags_comment = (opts: RadrootsComment): NostrEventTags => { - const { root: root_event, parent: parent_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 = parent_event && parent_event.id - ? { - kind: parent_event.kind.toString(), - author: parent_event.author, - id: parent_event.id, - d_tag: parent_event.d_tag, - relays: parent_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; +import type { RadrootsComment } from "@radroots/events-bindings"; +import { comment_tags } from "@radroots/events-codec-wasm"; +import type { NostrEventTags } from "../../types/lib.js"; +import { ensure_codec_wasm, parse_tags_json } from "../wasm.js"; + +export const tags_comment = async (opts: RadrootsComment): Promise<NostrEventTags> => { + await ensure_codec_wasm(); + const tags_json = comment_tags(JSON.stringify(opts)); + return parse_tags_json(tags_json); }; diff --git a/nostr/src/events/follow/lib.ts b/nostr/src/events/follow/lib.ts @@ -1,7 +1,7 @@ import type { RadrootsFollow } from "@radroots/events-bindings"; import type { NostrEventFigure, NostrSignedEvent } from "../../types/nostr.js"; import { nostr_event_create } from "../lib.js"; -import { tags_follow_list } from "./tags.js"; +import { tags_follow } from "./tags.js"; export const KIND_RADROOTS_FOLLOW = 3; export type KindRadrootsFollow = typeof KIND_RADROOTS_FOLLOW; @@ -10,12 +10,13 @@ export const nostr_event_follows = async ( opts: NostrEventFigure<{ data: RadrootsFollow }>, ): Promise<NostrSignedEvent | undefined> => { const { data } = opts; + const tags = await tags_follow(data); return nostr_event_create({ ...opts, basis: { kind: KIND_RADROOTS_FOLLOW, content: "", - tags: tags_follow_list(data.list), + tags, }, }); }; diff --git a/nostr/src/events/follow/tags.ts b/nostr/src/events/follow/tags.ts @@ -1,11 +1,10 @@ -import { RadrootsFollowProfile } from "@radroots/events-bindings"; -import { NostrEventTags } from "../../types/lib.js"; +import type { RadrootsFollow } from "@radroots/events-bindings"; +import { follow_tags } from "@radroots/events-codec-wasm"; +import type { NostrEventTags } from "../../types/lib.js"; +import { ensure_codec_wasm, parse_tags_json } from "../wasm.js"; -export const tags_follow_list = (list: RadrootsFollowProfile[]): 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 tags_follow = async (opts: RadrootsFollow): Promise<NostrEventTags> => { + await ensure_codec_wasm(); + const tags_json = follow_tags(JSON.stringify(opts)); + return parse_tags_json(tags_json); }; diff --git a/nostr/src/events/job/lib.ts b/nostr/src/events/job/lib.ts @@ -13,12 +13,13 @@ export const nostr_event_job_request = async ( opts: NostrEventFigure<{ data: RadrootsJobRequest }>, ): Promise<NostrSignedEvent | undefined> => { const { data } = opts; + const tags = await tags_job_request(data); return nostr_event_create({ ...opts, basis: { kind: data.kind, content: "", - tags: tags_job_request(data), + tags, }, }); }; @@ -27,12 +28,13 @@ export const nostr_event_job_result = async ( opts: NostrEventFigure<{ data: RadrootsJobResult }>, ): Promise<NostrSignedEvent | undefined> => { const { data } = opts; + const tags = await tags_job_result(data); return nostr_event_create({ ...opts, basis: { kind: data.kind, content: data.content || "", - tags: tags_job_result(data), + tags, }, }); }; @@ -41,12 +43,13 @@ export const nostr_event_job_feedback = async ( opts: NostrEventFigure<{ data: RadrootsJobFeedback }>, ): Promise<NostrSignedEvent | undefined> => { const { data } = opts; + const tags = await tags_job_feedback(data); return nostr_event_create({ ...opts, basis: { kind: data.kind, content: data.content || "", - tags: tags_job_feedback(data), + tags, }, }); }; @@ -91,7 +94,7 @@ export const nostr_event_job_feedback_todo = async ( encrypted: !!options?.encrypted, }; - const tags = tags_job_feedback(fb); + const tags = await tags_job_feedback(fb); return nostr_event_create({ ...opts, diff --git a/nostr/src/events/job/tags.ts b/nostr/src/events/job/tags.ts @@ -1,81 +1,30 @@ -import { +import type { RadrootsJobFeedback, - RadrootsJobInput, RadrootsJobRequest, RadrootsJobResult, } from "@radroots/events-bindings"; -import { NostrEventTag, NostrEventTags } from "../../types/lib.js"; - -export const tag_job_input = (input: RadrootsJobInput): NostrEventTag => { - const tag: NostrEventTag = ["i", input.data, input.input_type]; - if (input.relay) tag.push(input.relay); - if (input.marker) tag.push(input.marker); - return tag; -}; - -export const tag_job_output = (mime: string): NostrEventTag => ["output", mime]; - -export const tag_job_param = (key: string, value: string): NostrEventTag => ["param", key, value]; - -export const tag_job_bid = (sat: number): NostrEventTag => ["bid", String(sat)]; - -export const tags_job_relays = (relays: string[]): NostrEventTags => - relays.map(r => ["relays", r]); - -export const tags_job_providers = (pubkeys: string[]): NostrEventTags => - pubkeys.map(p => ["p", p]); - -export const tags_job_topics = (topics: string[]): NostrEventTags => - topics.map(t => ["t", t]); - -export const tag_job_amount = (msat: number, bolt11?: string | null): NostrEventTag => - bolt11 ? ["amount", String(msat), bolt11] : ["amount", String(msat)]; - -export const tag_job_encrypted = (): NostrEventTag => ["encrypted"]; - -export const tags_job_request = (opts: RadrootsJobRequest): NostrEventTags => { - const tags: NostrEventTags = []; - for (const input of opts.inputs) tags.push(tag_job_input(input)); - if (opts.output) tags.push(tag_job_output(opts.output)); - if (opts.params) for (const p of opts.params) tags.push(tag_job_param(p.key, p.value)); - if (typeof opts.bid_sat === "number") tags.push(tag_job_bid(opts.bid_sat)); - if (opts.relays?.length) tags.push(...tags_job_relays(opts.relays)); - if (opts.providers?.length) tags.push(...tags_job_providers(opts.providers)); - if (opts.topics?.length) tags.push(...tags_job_topics(opts.topics)); - if (opts.encrypted) tags.push(tag_job_encrypted()); - return tags; +import { + job_feedback_tags, + job_request_tags, + job_result_tags, +} from "@radroots/events-codec-wasm"; +import type { NostrEventTags } from "../../types/lib.js"; +import { ensure_codec_wasm, parse_tags_json } from "../wasm.js"; + +export const tags_job_request = async (opts: RadrootsJobRequest): Promise<NostrEventTags> => { + await ensure_codec_wasm(); + const tags_json = job_request_tags(JSON.stringify(opts)); + return parse_tags_json(tags_json); }; -export const tags_job_result = (opts: RadrootsJobResult): NostrEventTags => { - const tags: NostrEventTags = []; - const event_tag: NostrEventTag = ["e", opts.request_event.id]; - if (opts.request_event.relays) event_tag.push(opts.request_event.relays); - tags.push(event_tag); - if (opts.request_json) tags.push(["request", opts.request_json]); - if (!opts.encrypted && opts.inputs?.length) - for (const input of opts.inputs) tags.push(tag_job_input(input)); - if (opts.customer_pubkey) tags.push(["p", opts.customer_pubkey]); - if (opts.payment?.amount_sat !== undefined) { - const msat = Math.round(Number(opts.payment.amount_sat) * 1000); - tags.push(tag_job_amount(msat, opts.payment.bolt11)); - } - if (opts.encrypted) tags.push(tag_job_encrypted()); - return tags; +export const tags_job_result = async (opts: RadrootsJobResult): Promise<NostrEventTags> => { + await ensure_codec_wasm(); + const tags_json = job_result_tags(JSON.stringify(opts)); + return parse_tags_json(tags_json); }; -export const tags_job_feedback = (opts: RadrootsJobFeedback): NostrEventTags => { - const tags: NostrEventTags = []; - const status_tag: NostrEventTag = ["status", String(opts.status)]; - if (opts.extra_info) status_tag.push(opts.extra_info); - tags.push(status_tag); - if (opts.payment?.amount_sat !== undefined) { - const msat = Math.round(Number(opts.payment.amount_sat) * 1000); - tags.push(tag_job_amount(msat, opts.payment.bolt11)); - } - const event_tag: NostrEventTag = ["e", opts.request_event.id]; - if (opts.request_event.relays) event_tag.push(opts.request_event.relays); - tags.push(event_tag); - if (opts.customer_pubkey) tags.push(["p", opts.customer_pubkey]); - if (opts.encrypted) tags.push(tag_job_encrypted()); - return tags; +export const tags_job_feedback = async (opts: RadrootsJobFeedback): Promise<NostrEventTags> => { + await ensure_codec_wasm(); + const tags_json = job_feedback_tags(JSON.stringify(opts)); + return parse_tags_json(tags_json); }; diff --git a/nostr/src/events/listing/tags.ts b/nostr/src/events/listing/tags.ts @@ -1,140 +1,11 @@ -import type { RadrootsCoreQuantityPrice } from "@radroots/core-bindings"; -import { - RADROOTS_LISTING_PRODUCT_TAG_KEYS, - type RadrootsListing, - type RadrootsListingDiscount, - type RadrootsListingImage, - type RadrootsListingLocation, - type RadrootsListingQuantity, -} from "@radroots/events-bindings"; -import nostr_geotags, { type InputData as NostrGeotagsInputData } from "nostr-geotags"; -import { NostrEventTag, NostrEventTagLocation, NostrEventTags } from "../../types/lib.js"; +import type { RadrootsListing } from "@radroots/events-bindings"; +import { listing_tags_full } from "@radroots/events-codec-wasm"; +import type { NostrEventTags } from "../../types/lib.js"; +import { ensure_codec_wasm, parse_tags_json } from "../wasm.js"; -type CoreUnit = RadrootsListingQuantity["value"]["unit"]; -type CoreCurrency = RadrootsCoreQuantityPrice["amount"]["currency"]; - -const currency_to_code = (currency: CoreCurrency): string => { - if (Array.isArray(currency) && currency.length >= 3) { - const [a, b, c] = currency; - return String.fromCharCode(Number(a), Number(b), Number(c)); - } - return String(currency); -}; - -const unit_to_code = (unit: CoreUnit): string => { - switch (unit) { - case "Each": return "each"; - case "MassKg": return "kg"; - case "MassG": return "g"; - case "MassOz": return "oz"; - case "MassLb": return "lb"; - case "VolumeL": return "l"; - case "VolumeMl": return "ml"; - default: return String(unit).toLowerCase(); - } -}; - -const clean_label = (value?: string | null) => value?.trim() || undefined; - -const normalize_listing_location = ( - location?: RadrootsListingLocation | null, -): NostrEventTagLocation | undefined => { - if (!location?.primary) return undefined; - const { primary, city, region, country, lat, lng } = location; - return { - primary, - city: city ?? undefined, - region: region ?? undefined, - country: country ?? undefined, - lat: typeof lat === "number" ? lat : undefined, - lng: typeof lng === "number" ? lng : undefined, - }; -}; - -const normalize_image_size = (size: RadrootsListingImage["size"]) => - size && typeof size?.w === "number" && typeof size?.h === "number" ? size : undefined; - -export const tag_listing_quantity = (opts: RadrootsListingQuantity): NostrEventTag => { - const tag: NostrEventTag = ["quantity", String(opts.value.amount), unit_to_code(opts.value.unit)]; - const label = clean_label(opts.label ?? opts.value.label); - if (label) tag.push(label); - if (opts.count !== undefined && opts.count !== null) tag.push(String(opts.count)); - return tag; -}; - -export const tag_listing_price = (price: RadrootsCoreQuantityPrice): NostrEventTag => { - const tag: NostrEventTag = [ - "price", - String(price.amount.amount), - currency_to_code(price.amount.currency).toLowerCase(), - String(price.quantity.amount), - unit_to_code(price.quantity.unit), - ]; - const label = clean_label(price.quantity.label); - if (label) tag.push(label); - return tag; -}; - -export const tag_listing_price_discount = (discount: RadrootsListingDiscount): NostrEventTag => { - const tag: NostrEventTag = [`price-discount-${discount.kind}`]; - tag.push(JSON.stringify(discount.amount)); - return tag; -}; - -export const tag_listing_location = (opts: NostrEventTagLocation): NostrEventTag => { - if (!opts.primary) return []; - const tag: NostrEventTag = ["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_listing = async (opts: RadrootsListing): Promise<NostrEventTags> => { + await ensure_codec_wasm(); + const listing_json = JSON.stringify(opts); + const tags_json = listing_tags_full(listing_json); + return parse_tags_json(tags_json); }; - -export const tags_listing_location_geotags = ( - opts: NostrEventTagLocation, - geohash?: string | null, -): NostrEventTags => { - const { lat, lng: lon } = opts; - const input: NostrGeotagsInputData = { - lat, - lon, - geohash: clean_label(geohash), - }; - return nostr_geotags(input, { - geohash: true, - gps: true, - iso31661: false, - iso31662: false, - city: false, - }); -}; - -export const tag_listing_image = (opts: RadrootsListingImage): NostrEventTag => { - const tag: NostrEventTag = ["image", opts.url]; - const size = normalize_image_size(opts.size); - if (size) tag.push(`${size.w}x${size.h}`); - return tag; -}; - -export const tags_listing = (opts: RadrootsListing): NostrEventTags => { - const { d_tag, product, quantities, prices } = opts; - const tags: NostrEventTags = [["d", d_tag]]; - for (const key of RADROOTS_LISTING_PRODUCT_TAG_KEYS) { - const value = product[key]; - if (value) tags.push([key, String(value)]); - } - for (const quantity of quantities) { - tags.push(tag_listing_quantity(quantity)); - } - for (const price of prices) { - tags.push(tag_listing_price(price)); - } - if (opts.discounts?.length) for (const discount of opts.discounts) if (discount) tags.push(tag_listing_price_discount(discount)); - const location = normalize_listing_location(opts.location); - if (location) { - tags.push(tag_listing_location(location)); - tags.push(...tags_listing_location_geotags(location, opts.location?.geohash)); - } - if (opts.images) for (const image_tags of opts.images) if (image_tags) tags.push(tag_listing_image(image_tags)); - return tags; -}; -\ No newline at end of file diff --git a/nostr/src/events/reaction/lib.ts b/nostr/src/events/reaction/lib.ts @@ -10,12 +10,13 @@ export const nostr_event_reaction = async ( opts: NostrEventFigure<{ data: RadrootsReaction }>, ): Promise<NostrSignedEvent | undefined> => { const { data } = opts; + const tags = await tags_reaction(data); return nostr_event_create({ ...opts, basis: { kind: KIND_RADROOTS_REACTION, content: data.content, - tags: tags_reaction(data), + tags, }, }); }; diff --git a/nostr/src/events/reaction/tags.ts b/nostr/src/events/reaction/tags.ts @@ -1,16 +1,10 @@ -import { RadrootsReaction } from "@radroots/events-bindings"; -import { NostrEventTags } from "../../types/lib.js"; +import type { RadrootsReaction } from "@radroots/events-bindings"; +import { reaction_tags } from "@radroots/events-codec-wasm"; +import type { NostrEventTags } from "../../types/lib.js"; +import { ensure_codec_wasm, parse_tags_json } from "../wasm.js"; -export const tags_reaction = (opts: RadrootsReaction): NostrEventTags => { - const { root } = opts; - const ref_kind = root.kind.toString(); - const ref_author = root.author; - const relays = root.relays ?? []; - const tags: NostrEventTags = [ - ["e", root.id, ...relays], - ["p", ref_author], - ["k", ref_kind], - ]; - if (root.d_tag) tags.push(["a", `${ref_kind}:${ref_author}:${root.d_tag}`, ...relays]); - return tags; +export const tags_reaction = async (opts: RadrootsReaction): Promise<NostrEventTags> => { + await ensure_codec_wasm(); + const tags_json = reaction_tags(JSON.stringify(opts)); + return parse_tags_json(tags_json); }; diff --git a/nostr/src/events/wasm.ts b/nostr/src/events/wasm.ts @@ -0,0 +1,21 @@ +import init_wasm, { type InitOutput } from "@radroots/events-codec-wasm"; +import type { NostrEventTags } from "../types/lib.js"; + +let codec_wasm_init_promise: Promise<InitOutput> | null = null; + +export const ensure_codec_wasm = async (): Promise<void> => { + if (!codec_wasm_init_promise) codec_wasm_init_promise = init_wasm(); + await codec_wasm_init_promise; +}; + +const is_string_array = (value: unknown): value is string[] => + Array.isArray(value) && value.every((item) => typeof item === "string"); + +const is_tag_list = (value: unknown): value is NostrEventTags => + Array.isArray(value) && value.every(is_string_array); + +export const parse_tags_json = (value: string): NostrEventTags => { + const parsed: unknown = JSON.parse(value); + if (!is_tag_list(parsed)) throw new Error("invalid nostr tags"); + return parsed; +};