@qntx/nostr 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/{abort-Ba8tDrOI.mjs → abort-DBq2STPj.mjs} +13 -6
  2. package/dist/abort-DBq2STPj.mjs.map +1 -0
  3. package/dist/{builder-9tjI6WQ3.mjs → builder-CdLE4J6O.mjs} +11 -17
  4. package/dist/builder-CdLE4J6O.mjs.map +1 -0
  5. package/dist/{builder-sSflu6nB.d.mts → builder-DgkqDylK.d.mts} +5 -6
  6. package/dist/{client-CadO5fAX.mjs → client-CILhdfsF.mjs} +104 -314
  7. package/dist/client-CILhdfsF.mjs.map +1 -0
  8. package/dist/client.d.mts +2 -2
  9. package/dist/client.mjs +2 -2
  10. package/dist/collections-DtwMc1NU.mjs +33 -0
  11. package/dist/collections-DtwMc1NU.mjs.map +1 -0
  12. package/dist/core.d.mts +9 -8
  13. package/dist/core.mjs +7 -7
  14. package/dist/{error-BW7re1UN.d.mts → error-BD_0QFKT.d.mts} +2 -2
  15. package/dist/{error-CR8Urc84.d.mts → error-BquXDmIN.d.mts} +4 -4
  16. package/dist/{error-DrEIM7FO.mjs → error-L_QdmSnV.mjs} +2 -2
  17. package/dist/{error-DrEIM7FO.mjs.map → error-L_QdmSnV.mjs.map} +1 -1
  18. package/dist/{error-B__OMBRY.mjs → error-gtJvV6YS.mjs} +2 -2
  19. package/dist/{error-B__OMBRY.mjs.map → error-gtJvV6YS.mjs.map} +1 -1
  20. package/dist/{event-fU3Y01PR.d.mts → event-DJeb_DIL.d.mts} +4 -2
  21. package/dist/{event-D8Nfoh4K.mjs → event-DinQMmJ1.mjs} +16 -10
  22. package/dist/event-DinQMmJ1.mjs.map +1 -0
  23. package/dist/{filter-C0IMByE3.d.mts → filter-jI_9htF7.d.mts} +2 -2
  24. package/dist/{gossip-DpIvkBuj.d.mts → gossip-C7HLW5kI.d.mts} +35 -26
  25. package/dist/{gossip-oloDMWEj.mjs → gossip-CcTp9VpU.mjs} +23 -33
  26. package/dist/gossip-CcTp9VpU.mjs.map +1 -0
  27. package/dist/gossip.d.mts +1 -1
  28. package/dist/gossip.mjs +1 -1
  29. package/dist/{index-CvkvqB16.d.mts → index-DU9tRFTZ.d.mts} +7 -20
  30. package/dist/{index-q4MDce_r.d.mts → index-DiXwNn2M.d.mts} +6 -8
  31. package/dist/{index-CNl2q67H.d.mts → index-DocgD9K0.d.mts} +92 -167
  32. package/dist/{index-D1bqFHKB.d.mts → index-InQZpHyQ.d.mts} +18 -18
  33. package/dist/{index-MxC28S4P.d.mts → index-Tx15HGAD.d.mts} +26 -28
  34. package/dist/{index-lvXeER5t.d.mts → index-iLT8UzY3.d.mts} +19 -37
  35. package/dist/index.d.mts +23 -21
  36. package/dist/index.mjs +21 -19
  37. package/dist/{instance-DQE8BoWt.mjs → instance-BcKzxHIP.mjs} +26 -27
  38. package/dist/instance-BcKzxHIP.mjs.map +1 -0
  39. package/dist/{instance-CIca-ynn.d.mts → instance-DjsL8h84.d.mts} +5 -5
  40. package/dist/{key-Cv4_pN3x.d.mts → key-D_5OgGT7.d.mts} +11 -7
  41. package/dist/{key-Bps-LqX7.mjs → key-bshqfmZL.mjs} +13 -9
  42. package/dist/key-bshqfmZL.mjs.map +1 -0
  43. package/dist/{loaders-o6YXB_Ri.mjs → loaders-DD0XAGzw.mjs} +73 -82
  44. package/dist/loaders-DD0XAGzw.mjs.map +1 -0
  45. package/dist/loaders.d.mts +3 -3
  46. package/dist/loaders.mjs +2 -2
  47. package/dist/{memory-C2EVx0_O.mjs → memory-D_-5-T31.mjs} +2 -2
  48. package/dist/memory-D_-5-T31.mjs.map +1 -0
  49. package/dist/{memory-index-Do1mz0WF.mjs → memory-index-C6rujtoc.mjs} +30 -247
  50. package/dist/memory-index-C6rujtoc.mjs.map +1 -0
  51. package/dist/message-CWLcFTQ1.mjs +194 -0
  52. package/dist/message-CWLcFTQ1.mjs.map +1 -0
  53. package/dist/{message-iGPIqnn0.d.mts → message-CbBZOhq3.d.mts} +3 -3
  54. package/dist/{nip17-CKIufV98.d.mts → nip17-cTDFfFVt.d.mts} +5 -6
  55. package/dist/{nip19-BevCJZsp.d.mts → nip19-D1OnJ192.d.mts} +3 -9
  56. package/dist/{nip46-DDV4dC56.d.mts → nip46-DrsPI8EX.d.mts} +3 -5
  57. package/dist/nip51-DoPUvQ--.d.mts +87 -0
  58. package/dist/{nip59-zQgdLcEr.d.mts → nip59-BFIihLYY.d.mts} +3 -4
  59. package/dist/nip65-52HiwiyD.d.mts +23 -0
  60. package/dist/{nip77-DH4RhS5r.d.mts → nip77-C9QcZn7b.d.mts} +3 -4
  61. package/dist/nips/blossom.d.mts +12 -8
  62. package/dist/nips/blossom.mjs +30 -9
  63. package/dist/nips/blossom.mjs.map +1 -1
  64. package/dist/nips/nip04.mjs +17 -7
  65. package/dist/nips/nip04.mjs.map +1 -1
  66. package/dist/nips/nip05.d.mts +2 -3
  67. package/dist/nips/nip05.mjs +1 -4
  68. package/dist/nips/nip05.mjs.map +1 -1
  69. package/dist/nips/nip10.d.mts +3 -3
  70. package/dist/nips/nip10.mjs +3 -3
  71. package/dist/nips/nip11.d.mts +1 -1
  72. package/dist/nips/nip11.mjs +4 -4
  73. package/dist/nips/nip11.mjs.map +1 -1
  74. package/dist/nips/nip13.d.mts +7 -7
  75. package/dist/nips/nip13.mjs +32 -10
  76. package/dist/nips/nip13.mjs.map +1 -1
  77. package/dist/nips/nip17.d.mts +1 -1
  78. package/dist/nips/nip17.mjs +13 -33
  79. package/dist/nips/nip17.mjs.map +1 -1
  80. package/dist/nips/nip19.d.mts +2 -2
  81. package/dist/nips/nip19.mjs +16 -28
  82. package/dist/nips/nip19.mjs.map +1 -1
  83. package/dist/nips/nip21.d.mts +6 -1
  84. package/dist/nips/nip21.mjs +15 -5
  85. package/dist/nips/nip21.mjs.map +1 -1
  86. package/dist/nips/nip27.d.mts +4 -4
  87. package/dist/nips/nip27.mjs.map +1 -1
  88. package/dist/nips/nip42.d.mts +1 -1
  89. package/dist/nips/nip42.mjs +2 -1
  90. package/dist/nips/nip42.mjs.map +1 -1
  91. package/dist/nips/nip44.mjs +1 -1
  92. package/dist/nips/nip46.d.mts +2 -2
  93. package/dist/nips/nip46.mjs +15 -19
  94. package/dist/nips/nip46.mjs.map +1 -1
  95. package/dist/nips/nip49.d.mts +5 -5
  96. package/dist/nips/nip49.mjs +4 -4
  97. package/dist/nips/nip49.mjs.map +1 -1
  98. package/dist/nips/nip51.d.mts +2 -86
  99. package/dist/nips/nip51.mjs +7 -23
  100. package/dist/nips/nip51.mjs.map +1 -1
  101. package/dist/nips/nip57.d.mts +7 -5
  102. package/dist/nips/nip57.mjs +12 -17
  103. package/dist/nips/nip57.mjs.map +1 -1
  104. package/dist/nips/nip59.d.mts +1 -1
  105. package/dist/nips/nip59.mjs +16 -16
  106. package/dist/nips/nip59.mjs.map +1 -1
  107. package/dist/nips/nip65.d.mts +2 -2
  108. package/dist/nips/nip65.mjs +14 -37
  109. package/dist/nips/nip65.mjs.map +1 -1
  110. package/dist/nips/nip77.d.mts +1 -1
  111. package/dist/nips/nip77.mjs +33 -32
  112. package/dist/nips/nip77.mjs.map +1 -1
  113. package/dist/nips/nip98.d.mts +5 -5
  114. package/dist/nips/nip98.mjs +8 -8
  115. package/dist/nips/nip98.mjs.map +1 -1
  116. package/dist/{pool-DBIYMtwy.d.mts → pool-SzJKPIa3.d.mts} +96 -91
  117. package/dist/pool-YHRZgcQ3.mjs +596 -0
  118. package/dist/pool-YHRZgcQ3.mjs.map +1 -0
  119. package/dist/put-DS0yzVJx.mjs +187 -0
  120. package/dist/put-DS0yzVJx.mjs.map +1 -0
  121. package/dist/{reactive-7aqeip4A.d.mts → reactive-BJ1ggn4d.d.mts} +7 -22
  122. package/dist/{reactive-BfhPp9a3.mjs → reactive-BJRoVHxU.mjs} +80 -65
  123. package/dist/reactive-BJRoVHxU.mjs.map +1 -0
  124. package/dist/{pool-As20OGxB.mjs → relay-CvxPiQ_m.mjs} +178 -721
  125. package/dist/relay-CvxPiQ_m.mjs.map +1 -0
  126. package/dist/relay.d.mts +4 -4
  127. package/dist/relay.mjs +3 -2
  128. package/dist/serial-Bzpw-xz3.mjs +21 -0
  129. package/dist/serial-Bzpw-xz3.mjs.map +1 -0
  130. package/dist/{signer-CYQutDzD.mjs → signer-yW3slDC2.mjs} +12 -14
  131. package/dist/signer-yW3slDC2.mjs.map +1 -0
  132. package/dist/signer.d.mts +2 -2
  133. package/dist/signer.mjs +2 -2
  134. package/dist/{storage-Dky3wuoH.mjs → storage-CZ52ifcQ.mjs} +17 -42
  135. package/dist/storage-CZ52ifcQ.mjs.map +1 -0
  136. package/dist/storage.d.mts +3 -3
  137. package/dist/storage.mjs +4 -4
  138. package/dist/store.d.mts +1 -1
  139. package/dist/store.mjs +1 -1
  140. package/dist/testing.d.mts +18 -18
  141. package/dist/testing.mjs +9 -9
  142. package/dist/testing.mjs.map +1 -1
  143. package/dist/{types-2pCUprRU.d.mts → types-Cm-XMgtH.d.mts} +6 -6
  144. package/dist/{types-CRQEk0Oi.d.mts → types-DNmiscfd.d.mts} +2 -2
  145. package/dist/{util-LfQirgDk.mjs → util-yFYzlbrm.mjs} +29 -7
  146. package/dist/util-yFYzlbrm.mjs.map +1 -0
  147. package/dist/{verifier-t5WoWbtY.mjs → verifier-BTLOpo1n.mjs} +3 -3
  148. package/dist/verifier-BTLOpo1n.mjs.map +1 -0
  149. package/dist/verifier-CgTFpgsC.d.mts +22 -0
  150. package/dist/wasm.browser.d.mts +3 -3
  151. package/dist/wasm.browser.mjs +3 -3
  152. package/dist/wasm.d.mts +3 -3
  153. package/dist/wasm.mjs +3 -3
  154. package/dist/wasm.mjs.map +1 -1
  155. package/package.json +5 -9
  156. package/dist/abort-Ba8tDrOI.mjs.map +0 -1
  157. package/dist/builder-9tjI6WQ3.mjs.map +0 -1
  158. package/dist/client-CadO5fAX.mjs.map +0 -1
  159. package/dist/event-D8Nfoh4K.mjs.map +0 -1
  160. package/dist/gossip-oloDMWEj.mjs.map +0 -1
  161. package/dist/instance-DQE8BoWt.mjs.map +0 -1
  162. package/dist/key-Bps-LqX7.mjs.map +0 -1
  163. package/dist/loaders-o6YXB_Ri.mjs.map +0 -1
  164. package/dist/memory-C2EVx0_O.mjs.map +0 -1
  165. package/dist/memory-index-Do1mz0WF.mjs.map +0 -1
  166. package/dist/message-DcY6Fi9o.mjs +0 -193
  167. package/dist/message-DcY6Fi9o.mjs.map +0 -1
  168. package/dist/nip65-CjgmdpPB.d.mts +0 -26
  169. package/dist/nips/nip96.d.mts +0 -40
  170. package/dist/nips/nip96.mjs +0 -118
  171. package/dist/nips/nip96.mjs.map +0 -1
  172. package/dist/pool-As20OGxB.mjs.map +0 -1
  173. package/dist/reactive-BfhPp9a3.mjs.map +0 -1
  174. package/dist/signer-CYQutDzD.mjs.map +0 -1
  175. package/dist/storage-Dky3wuoH.mjs.map +0 -1
  176. package/dist/util-LfQirgDk.mjs.map +0 -1
  177. package/dist/verifier-t5WoWbtY.mjs.map +0 -1
@@ -1,11 +1,10 @@
1
- import { _ as Tag, t as Event } from "./event-fU3Y01PR.mjs";
2
- import { t as EventBuilder } from "./builder-sSflu6nB.mjs";
3
- import { a as NostrError } from "./error-CR8Urc84.mjs";
4
- import { a as Rumor, l as WrapOptions, r as Nip59Crypto } from "./nip59-zQgdLcEr.mjs";
1
+ import { _ as Tag, t as Event } from "./event-DJeb_DIL.mjs";
2
+ import { t as EventBuilder } from "./builder-DgkqDylK.mjs";
3
+ import { a as NostrError } from "./error-BquXDmIN.mjs";
4
+ import { a as Rumor, l as WrapOptions, r as Nip59Crypto } from "./nip59-BFIihLYY.mjs";
5
5
  //#region src/nips/nip17.d.ts
6
6
  declare class Nip17Error extends NostrError {
7
7
  name: string;
8
- constructor(message: string, options?: ErrorOptions);
9
8
  }
10
9
  type Recipient = {
11
10
  readonly pubkey: string;
@@ -36,4 +35,4 @@ declare function wrapDirectMessage(crypto: Nip59Crypto, recipients: ReadonlyArra
36
35
  declare function requireDmRelays(pubkey: string, relays: ReadonlyArray<string>): string[];
37
36
  //#endregion
38
37
  export { buildChatMessageRumor as a, normalizeRecipients as c, wrapDirectMessage as d, ReplyTo as i, parseDmRelayList as l, Nip17Error as n, dmRelayListEventBuilder as o, Recipient as r, dmRelayListToTags as s, ChatMessageOptions as t, requireDmRelays as u };
39
- //# sourceMappingURL=nip17-CKIufV98.d.mts.map
38
+ //# sourceMappingURL=nip17-cTDFfFVt.d.mts.map
@@ -1,4 +1,4 @@
1
- import { a as NostrError } from "./error-CR8Urc84.mjs";
1
+ import { a as NostrError } from "./error-BquXDmIN.mjs";
2
2
  //#region src/nips/nip19.d.ts
3
3
  /** Bech32 `nprofile1…` string (TLV profile pointer). */
4
4
  type NProfile = `nprofile1${string}`;
@@ -56,7 +56,6 @@ type DecodedResult = {
56
56
  /** Error thrown by NIP-19 encoding/decoding failures. */
57
57
  declare class Nip19Error extends NostrError {
58
58
  name: string;
59
- constructor(message: string);
60
59
  }
61
60
  declare function encodeBytes<Prefix extends string>(prefix: Prefix, bytes: Uint8Array): `${Prefix}1${string}`;
62
61
  /** Encode a 32-byte secret key as `nsec1…`; throws {@link Nip19Error} on wrong length. */
@@ -73,11 +72,6 @@ declare function neventEncode(event: EventPointer): NEvent;
73
72
  declare function naddrEncode(addr: AddressPointer): NAddr;
74
73
  /** Decode a NIP-19 bech32 entity; throws {@link Nip19Error} on bad input or unknown prefix. */
75
74
  declare function decode(code: string): DecodedResult;
76
- /** Decode `nostr:` URI or bare bech32; returns invalid sentinel instead of throwing. */
77
- declare function decodeNostrURI(nip19code: string): DecodedResult | {
78
- type: "invalid";
79
- data: undefined;
80
- };
81
75
  //#endregion
82
- export { neventEncode as _, NAddr as a, npubEncode as b, NPub as c, Note as d, ProfilePointer as f, naddrEncode as g, encodeBytes as h, EventPointer as i, NSec as l, decodeNostrURI as m, Bech32MaxSize as n, NEvent as o, decode as p, DecodedResult as r, NProfile as s, AddressPointer as t, Nip19Error as u, noteEncode as v, nsecEncode as x, nprofileEncode as y };
83
- //# sourceMappingURL=nip19-BevCJZsp.d.mts.map
76
+ export { noteEncode as _, NAddr as a, nsecEncode as b, NPub as c, Note as d, ProfilePointer as f, neventEncode as g, naddrEncode as h, EventPointer as i, NSec as l, encodeBytes as m, Bech32MaxSize as n, NEvent as o, decode as p, DecodedResult as r, NProfile as s, AddressPointer as t, Nip19Error as u, nprofileEncode as v, npubEncode as y };
77
+ //# sourceMappingURL=nip19-D1OnJ192.d.mts.map
@@ -1,6 +1,5 @@
1
- import { a as NostrError } from "./error-CR8Urc84.mjs";
1
+ import { a as NostrError } from "./error-BquXDmIN.mjs";
2
2
  //#region src/nips/nip46.d.ts
3
- declare const BUNKER_REGEX: RegExp;
4
3
  type BunkerPointer = {
5
4
  /** Remote signer / bunker public key (hex). */
6
5
  pubkey: string;
@@ -30,7 +29,6 @@ type Nip46Response = {
30
29
  };
31
30
  declare class Nip46Error extends NostrError {
32
31
  name: string;
33
- constructor(message: string, options?: ErrorOptions);
34
32
  }
35
33
  /** Encode a bunker pointer as `bunker://…`. */
36
34
  declare function toBunkerURL(pointer: BunkerPointer): string;
@@ -48,5 +46,5 @@ declare function decodeNip46Request(json: string): Nip46Request;
48
46
  declare function encodeNip46Response(res: Nip46Response): string;
49
47
  declare function decodeNip46Response(json: string): Nip46Response;
50
48
  //#endregion
51
- export { Nip46Request as a, createNostrConnectURI as c, encodeNip46Request as d, encodeNip46Response as f, toBunkerURL as h, Nip46Error as i, decodeNip46Request as l, parseNostrConnectURI as m, BunkerPointer as n, Nip46Response as o, parseBunkerURL as p, ClientMetadata as r, NostrConnectParams as s, BUNKER_REGEX as t, decodeNip46Response as u };
52
- //# sourceMappingURL=nip46-DDV4dC56.d.mts.map
49
+ export { Nip46Response as a, decodeNip46Request as c, encodeNip46Response as d, parseBunkerURL as f, Nip46Request as i, decodeNip46Response as l, toBunkerURL as m, ClientMetadata as n, NostrConnectParams as o, parseNostrConnectURI as p, Nip46Error as r, createNostrConnectURI as s, BunkerPointer as t, encodeNip46Request as u };
50
+ //# sourceMappingURL=nip46-DrsPI8EX.d.mts.map
@@ -0,0 +1,87 @@
1
+ import { t as Event } from "./event-DJeb_DIL.mjs";
2
+ import { t as EventBuilder } from "./builder-DgkqDylK.mjs";
3
+ //#region src/nips/nip51.d.ts
4
+ /**
5
+ * Structural crypto used by NIP-51 private tags. Satisfied by NostrSigner when
6
+ * nip44Encrypt/nip44Decrypt are present. This module must not import src/signer/.
7
+ */
8
+ type Nip51Crypto = {
9
+ getPublicKey: () => Promise<string>;
10
+ nip44Encrypt: (peer: string, plaintext: string) => Promise<string>;
11
+ nip44Decrypt: (peer: string, payload: string) => Promise<string>;
12
+ };
13
+ type MuteItem = {
14
+ type: "pubkey";
15
+ value: string;
16
+ } | {
17
+ type: "event";
18
+ value: string;
19
+ } | {
20
+ type: "hashtag";
21
+ value: string;
22
+ } | {
23
+ type: "word";
24
+ value: string;
25
+ };
26
+ /** Parse kind:10000 mute list public tags (`p` / `e` / `t` / `word`). */
27
+ declare function parseMuteList(event: Pick<Event, "kind" | "tags">): MuteItem[];
28
+ /** Build an unsigned kind:10000 EventBuilder from public mute items. */
29
+ declare function muteListEventBuilder(items: ReadonlyArray<MuteItem>): EventBuilder;
30
+ /** NIP-44 ciphertext of tags, conversation peer = author's own pubkey. */
31
+ declare function encryptPrivateTags(crypto: Nip51Crypto, tags: Event["tags"]): Promise<string>;
32
+ /** Decrypt NIP-51 `.content`. Empty content is no private tags, not ciphertext. */
33
+ declare function decryptPrivateTags(crypto: Nip51Crypto, event: Pick<Event, "pubkey" | "content">): Promise<Event["tags"]>;
34
+ /** Public mute items from tags; private mute items from NIP-44 `.content`. */
35
+ declare function parseMuteListPrivate(crypto: Nip51Crypto, event: Pick<Event, "kind" | "tags" | "pubkey" | "content">): Promise<{
36
+ public: MuteItem[];
37
+ private: MuteItem[];
38
+ }>;
39
+ /** Parse kind:10001 pin list public `e` tags. */
40
+ declare function parsePinList(event: Pick<Event, "kind" | "tags">): string[];
41
+ /** Build an unsigned kind:10001 EventBuilder from event ids. */
42
+ declare function pinListEventBuilder(ids: ReadonlyArray<string>): EventBuilder;
43
+ /** Parse kind:10003 bookmark list public `e` and `a` tags. */
44
+ declare function parseBookmarkList(event: Pick<Event, "kind" | "tags">): {
45
+ e: string[];
46
+ a: string[];
47
+ };
48
+ /** Build an unsigned kind:10003 EventBuilder from public bookmark pointers. */
49
+ declare function bookmarkListEventBuilder(items: {
50
+ e?: ReadonlyArray<string>;
51
+ a?: ReadonlyArray<string>;
52
+ }): EventBuilder;
53
+ /** Parse kind:10030 user emoji list public `emoji` and `a` tags. */
54
+ declare function parseUserEmojiList(event: Pick<Event, "kind" | "tags">): {
55
+ emoji: Array<{
56
+ shortcode: string;
57
+ url: string;
58
+ }>;
59
+ sets: string[];
60
+ };
61
+ /** Parse kind:30002 relay set public `d` and `relay` tags. */
62
+ declare function parseRelaySet(event: Pick<Event, "kind" | "tags">): {
63
+ d: string;
64
+ relays: string[];
65
+ };
66
+ /** Parse kind:10012 favorite relays public `relay` tags and kind:30002 `a` tags. */
67
+ declare function parseFavoriteRelays(event: Pick<Event, "kind" | "tags">): {
68
+ relays: string[];
69
+ sets: string[];
70
+ };
71
+ /** Parse kind:30030 emoji set public `d` / `title` / `emoji` tags. */
72
+ declare function parseEmojiSet(event: Pick<Event, "kind" | "tags">): {
73
+ d: string;
74
+ title?: string;
75
+ emoji: Array<{
76
+ shortcode: string;
77
+ url: string;
78
+ }>;
79
+ };
80
+ /** Parse kind:39089 starter pack (follow pack) public `d` and `p` tags. */
81
+ declare function parseFollowPack(event: Pick<Event, "kind" | "tags">): {
82
+ d: string;
83
+ pubkeys: string[];
84
+ };
85
+ //#endregion
86
+ export { encryptPrivateTags as a, parseEmojiSet as c, parseMuteList as d, parseMuteListPrivate as f, pinListEventBuilder as g, parseUserEmojiList as h, decryptPrivateTags as i, parseFavoriteRelays as l, parseRelaySet as m, Nip51Crypto as n, muteListEventBuilder as o, parsePinList as p, bookmarkListEventBuilder as r, parseBookmarkList as s, MuteItem as t, parseFollowPack as u };
87
+ //# sourceMappingURL=nip51-DoPUvQ--.d.mts.map
@@ -1,5 +1,5 @@
1
- import { _ as Tag, r as UnsignedEvent, t as Event } from "./event-fU3Y01PR.mjs";
2
- import { a as NostrError } from "./error-CR8Urc84.mjs";
1
+ import { _ as Tag, r as UnsignedEvent, t as Event } from "./event-DJeb_DIL.mjs";
2
+ import { a as NostrError } from "./error-BquXDmIN.mjs";
3
3
  //#region src/nips/nip59.d.ts
4
4
  /** Unsigned event with a computed id. Never has `sig`. */
5
5
  type Rumor = UnsignedEvent & {
@@ -43,7 +43,6 @@ type WrapOptions = {
43
43
  type SealOptions = Pick<WrapOptions, "now" | "randomInt" | "timestamps" | "randomize">;
44
44
  declare class Nip59Error extends NostrError {
45
45
  name: string;
46
- constructor(message: string, options?: ErrorOptions);
47
46
  }
48
47
  declare const TWO_DAYS_SECS: number;
49
48
  declare function isGiftWrapKind(kind: number): boolean;
@@ -76,4 +75,4 @@ declare function wrap(crypto: Nip59Crypto, recipient: string, rumor: Rumor, opts
76
75
  declare function unwrap(crypto: Nip44Decryptor, giftWrap: Event): Promise<Rumor>;
77
76
  //#endregion
78
77
  export { requireNip59Crypto as _, Rumor as a, wrap as b, TimestampRandomize as c, createRumor as d, createSeal as f, requireNip44Decryptor as g, randomPastTimestamp as h, Nip59Error as i, WrapOptions as l, isGiftWrapKind as m, Nip44Decryptor as n, SealOptions as o, eventToJson as p, Nip59Crypto as r, TWO_DAYS_SECS as s, GiftWrapTimestamps as t, createGiftWrap as u, rumorToJson as v, unwrap as y };
79
- //# sourceMappingURL=nip59-zQgdLcEr.d.mts.map
78
+ //# sourceMappingURL=nip59-BFIihLYY.d.mts.map
@@ -0,0 +1,23 @@
1
+ import { _ as Tag, t as Event } from "./event-DJeb_DIL.mjs";
2
+ import { t as EventBuilder } from "./builder-DgkqDylK.mjs";
3
+ //#region src/nips/nip65.d.ts
4
+ /** NIP-65 `r` tag marker: read-only, write-only, or unmarked (both). */
5
+ type RelayMarker = "read" | "write" | "both";
6
+ /** One NIP-65 relay-list entry: normalized URL plus its marker. */
7
+ type RelayListItem = {
8
+ readonly url: string;
9
+ readonly marker: RelayMarker;
10
+ };
11
+ /** Parse a kind:10002 NIP-65 event into relay list entries. */
12
+ declare function parseRelayList(event: Event): RelayListItem[];
13
+ /** Encode relay list items as NIP-65 `r` tags (`both` is unmarked). */
14
+ declare function relayListToTags(items: ReadonlyArray<RelayListItem>): Tag[];
15
+ /** Build an unsigned kind:10002 EventBuilder from relay list items. */
16
+ declare function relayListEventBuilder(items: ReadonlyArray<RelayListItem>): EventBuilder;
17
+ /** URLs of the read-enabled items. */
18
+ declare function readRelays(items: ReadonlyArray<RelayListItem>): string[];
19
+ /** URLs of the write-enabled items. */
20
+ declare function writeRelays(items: ReadonlyArray<RelayListItem>): string[];
21
+ //#endregion
22
+ export { relayListEventBuilder as a, readRelays as i, RelayMarker as n, relayListToTags as o, parseRelayList as r, writeRelays as s, RelayListItem as t };
23
+ //# sourceMappingURL=nip65-52HiwiyD.d.mts.map
@@ -1,12 +1,11 @@
1
- import { t as Event } from "./event-fU3Y01PR.mjs";
2
- import { a as NostrError } from "./error-CR8Urc84.mjs";
1
+ import { t as Event } from "./event-DJeb_DIL.mjs";
2
+ import { a as NostrError } from "./error-BquXDmIN.mjs";
3
3
  //#region src/nips/nip77.d.ts
4
4
  declare const PROTOCOL_VERSION = 97;
5
5
  declare const DEFAULT_FRAME_SIZE_LIMIT = 60000;
6
6
  declare const MAX_NEG_ROUNDS = 1024;
7
7
  declare class Nip77Error extends NostrError {
8
8
  name: string;
9
- constructor(message: string, options?: ErrorOptions);
10
9
  }
11
10
  type NegItem = {
12
11
  timestamp: number;
@@ -53,4 +52,4 @@ declare function runNegSession(opts: {
53
52
  }>;
54
53
  //#endregion
55
54
  export { NegentropyStorageVector as a, ReconcileOutcome as c, storageFromItems as d, Negentropy as i, runNegSession as l, MAX_NEG_ROUNDS as n, Nip77Error as o, NegItem as r, PROTOCOL_VERSION as s, DEFAULT_FRAME_SIZE_LIMIT as t, storageFromEvents as u };
56
- //# sourceMappingURL=nip77-DH4RhS5r.d.mts.map
55
+ //# sourceMappingURL=nip77-C9QcZn7b.d.mts.map
@@ -1,6 +1,6 @@
1
- import { n as EventTemplate, t as Event } from "../event-fU3Y01PR.mjs";
2
- import { t as EventBuilder } from "../builder-sSflu6nB.mjs";
3
- import { a as NostrError } from "../error-CR8Urc84.mjs";
1
+ import { n as EventTemplate, t as Event } from "../event-DJeb_DIL.mjs";
2
+ import { t as EventBuilder } from "../builder-DgkqDylK.mjs";
3
+ import { a as NostrError } from "../error-BquXDmIN.mjs";
4
4
  import { t as ManualFetch } from "../http-C4HwIu4B.mjs";
5
5
  //#region src/nips/blossom.d.ts
6
6
  export type BlobDescriptor = {
@@ -30,17 +30,20 @@ export declare function verifyBlob(data: Blob | ArrayBuffer | Uint8Array, sha256
30
30
  /** `Nostr ` + base64url(utf8 JSON), padding stripped. NIP-98 uses standard base64; mixing them is a 401. */
31
31
  export declare function encodeAuthorizationHeader(event: Event): string;
32
32
  export declare function createAuthTemplate(verb: BlossomAuthVerb, opts?: {
33
- sha256?: string;
34
- expiration?: number;
35
- message?: string;
33
+ sha256?: string | undefined;
34
+ expiration?: number | undefined;
35
+ message?: string | undefined;
36
+ servers?: ReadonlyArray<string> | undefined;
36
37
  }): EventTemplate;
37
38
  export declare function createUploadAuth(sign: BlossomSign, file: Blob, opts?: {
38
- message?: string;
39
- expiration?: number;
39
+ message?: string | undefined;
40
+ expiration?: number | undefined;
41
+ servers?: ReadonlyArray<string> | undefined;
40
42
  }): Promise<Event>;
41
43
  export declare function upload(server: string, file: Blob, auth: Event, opts?: {
42
44
  fetch?: BlossomFetch;
43
45
  signal?: AbortSignal;
46
+ sha256?: string | undefined;
44
47
  }): Promise<BlobDescriptor>;
45
48
  export declare function listBlobs(server: string, pubkey: string, auth: Event | undefined, opts?: {
46
49
  fetch?: BlossomFetch;
@@ -59,6 +62,7 @@ export declare function mirrorBlob(server: string, blob: BlobDescriptor, opts: {
59
62
  export declare function checkUpload(server: string, file: Blob, auth: Event, opts?: {
60
63
  fetch?: BlossomFetch;
61
64
  signal?: AbortSignal;
65
+ sha256?: string | undefined;
62
66
  }): Promise<void>;
63
67
  /**
64
68
  * Strict HEAD probe. 2xx → true. 404 → false. Other HTTP (including 405) / network → throw
@@ -1,6 +1,6 @@
1
- import { c as isRecord, d as utf8Encoder, g as NostrError, i as bytesToHex, n as assertHex32, p as EventValidationError } from "../util-LfQirgDk.mjs";
1
+ import { c as isRecord, d as nowSeconds, h as EventValidationError, i as bytesToHex, n as assertHex32, p as utf8Encoder, v as NostrError } from "../util-yFYzlbrm.mjs";
2
2
  import { t as Kind } from "../kind-Cxwhhkx8.mjs";
3
- import { t as EventBuilder } from "../builder-9tjI6WQ3.mjs";
3
+ import { t as EventBuilder } from "../builder-CdLE4J6O.mjs";
4
4
  import { n as requireGlobalFetch, r as sendManual, t as fetchManual } from "../http-CuazNRIP.mjs";
5
5
  import { sha256 } from "@noble/hashes/sha2.js";
6
6
  import { base64urlnopad } from "@scure/base";
@@ -66,15 +66,36 @@ const AUTH_VERB_MESSAGES = {
66
66
  get: "Get Blob",
67
67
  media: "Upload Media"
68
68
  };
69
+ /**
70
+ * Hostname for a BUD-11 `server` tag: accepts a full URL or a bare domain; lowercased, undefined
71
+ * when unparseable.
72
+ */
73
+ function serverHostname(raw) {
74
+ try {
75
+ const url = new URL(raw.includes("://") ? raw : `https://${raw}`);
76
+ if (url.protocol !== "http:" && url.protocol !== "https:") return;
77
+ const hostname = url.hostname.toLowerCase();
78
+ return hostname === "" ? void 0 : hostname;
79
+ } catch {
80
+ return;
81
+ }
82
+ }
69
83
  function createAuthTemplate(verb, opts) {
70
84
  if (!AUTH_VERBS.has(verb)) throw new BlossomError(`invalid blossom auth verb: ${verb}`);
71
85
  if (opts?.message !== void 0 && opts.message.trim() === "") throw new BlossomError("blossom auth message must not be empty");
72
- const expiration = opts?.expiration ?? Math.floor(Date.now() / 1e3) + AUTH_EXPIRATION_SECS;
86
+ const expiration = opts?.expiration ?? nowSeconds() + AUTH_EXPIRATION_SECS;
73
87
  const tags = [["t", verb], ["expiration", String(expiration)]];
74
88
  if (opts?.sha256 !== void 0) tags.push(["x", assertHex32(opts.sha256, "blob sha256")]);
89
+ const seenServers = /* @__PURE__ */ new Set();
90
+ for (const server of opts?.servers ?? []) {
91
+ const hostname = serverHostname(server);
92
+ if (hostname === void 0 || seenServers.has(hostname)) continue;
93
+ seenServers.add(hostname);
94
+ tags.push(["server", hostname]);
95
+ }
75
96
  return {
76
97
  kind: Kind.BlobsAuth,
77
- created_at: Math.floor(Date.now() / 1e3),
98
+ created_at: nowSeconds(),
78
99
  tags,
79
100
  content: opts?.message ?? AUTH_VERB_MESSAGES[verb]
80
101
  };
@@ -86,7 +107,7 @@ async function createUploadAuth(sign, file, opts) {
86
107
  }));
87
108
  }
88
109
  async function upload(server, file, auth, opts) {
89
- const hash = await sha256Blob(file);
110
+ const hash = opts?.sha256 === void 0 ? await sha256Blob(file) : assertHex32(opts.sha256, "blob sha256");
90
111
  const headers = {
91
112
  Authorization: encodeAuthorizationHeader(auth),
92
113
  "X-SHA-256": hash
@@ -138,7 +159,7 @@ async function mirrorBlob(server, blob, opts) {
138
159
  }
139
160
  /** BUD-06 preflight. Does not PUT the body. */
140
161
  async function checkUpload(server, file, auth, opts) {
141
- const hash = await sha256Blob(file);
162
+ const hash = opts?.sha256 === void 0 ? await sha256Blob(file) : assertHex32(opts.sha256, "blob sha256");
142
163
  const headers = {
143
164
  Authorization: encodeAuthorizationHeader(auth),
144
165
  "X-SHA-256": hash,
@@ -207,7 +228,7 @@ async function uploadToServers(servers, file, auth, opts) {
207
228
  return await upload(server, file, auth, opts);
208
229
  } catch (error) {
209
230
  if (isAbortError(error)) throw error;
210
- last = error instanceof BlossomError ? error : new BlossomError("blossom upload failed", { cause: error instanceof Error ? error : void 0 });
231
+ last = error instanceof BlossomError ? error : new BlossomError("blossom upload failed", { cause: error });
211
232
  }
212
233
  throw last ?? new BlossomError("no servers");
213
234
  }
@@ -282,7 +303,7 @@ function blossomHttpError(method, url, res) {
282
303
  return new BlossomError(detail === void 0 || detail === "" ? `blossom ${method} ${url} failed (${res.status})` : detail, { status: res.status });
283
304
  }
284
305
  async function blossomFetch(fetchImpl, url, init) {
285
- return fetchManual(fetchImpl ?? requireGlobalFetch(missingBlossomFetch), url, init, (err) => new BlossomError(`blossom ${init.method} ${url} failed`, { cause: err instanceof Error ? err : void 0 }));
306
+ return fetchManual(fetchImpl ?? requireGlobalFetch(missingBlossomFetch), url, init, (err) => new BlossomError(`blossom ${init.method} ${url} failed`, { cause: err }));
286
307
  }
287
308
  /**
288
309
  * HEAD with redirect:manual. Network → undefined. AbortError propagates. Never throws on HTTP
@@ -309,7 +330,7 @@ async function readJson(res) {
309
330
  return await res.json();
310
331
  } catch (error) {
311
332
  throw new BlossomError("invalid blossom JSON response", {
312
- cause: error instanceof Error ? error : void 0,
333
+ cause: error,
313
334
  status: res.status
314
335
  });
315
336
  }
@@ -1 +1 @@
1
- {"version":3,"file":"blossom.mjs","names":[],"sources":["../../src/nips/blossom.ts"],"sourcesContent":["/**\n * Blossom BUD-01/02/04/06 HTTP helpers, NIP-B7 kind 10063 server lists, and URL healing. Auth is\n * kind 24242, not NIP-98; the Authorization value is base64url.\n *\n * @see https://github.com/hzrd149/blossom\n * @see https://github.com/nostr-protocol/nips/blob/master/B7.md\n */\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { base64urlnopad } from \"@scure/base\";\n\nimport { EventBuilder } from \"../core/builder.ts\";\nimport { EventValidationError, NostrError } from \"../core/error.ts\";\nimport type { Event, EventTemplate } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport type { Tag } from \"../core/tag.ts\";\nimport { assertHex32, bytesToHex, isRecord, utf8Encoder } from \"../core/util.ts\";\nimport { fetchManual, requireGlobalFetch, sendManual } from \"./http.ts\";\nimport type { ManualFetch } from \"./http.ts\";\n\nexport type BlobDescriptor = {\n url: string;\n sha256: string;\n size: number;\n type?: string;\n uploaded?: number;\n};\n\nexport type BlossomFetch = ManualFetch;\n\n/** Signs a kind 24242 auth template (draft; pubkey is filled by the signer). */\nexport type BlossomSign = (template: EventTemplate) => Promise<Event>;\n\n/** BUD-11 `t` verbs. Mirror reuses `upload`; there is no `t=mirror`. */\nexport type BlossomAuthVerb = \"upload\" | \"delete\" | \"list\" | \"get\" | \"media\";\n\nconst AUTH_VERBS = new Set<BlossomAuthVerb>([\"upload\", \"delete\", \"list\", \"get\", \"media\"]);\n\n/** Default BUD-11 expiration: now + 1h. Servers 401 when the tag is missing. */\nconst AUTH_EXPIRATION_SECS = 3600;\n\nexport class BlossomError extends NostrError {\n override name = \"BlossomError\";\n readonly status?: number | undefined;\n\n constructor(message: string, options?: ErrorOptions & { status?: number }) {\n const { status, ...rest } = options ?? {};\n super(message, rest);\n this.status = status;\n }\n}\n\n/** Last 64-char hex in the URL path (extension ignored). Undefined if none or the URL is invalid. */\nexport function getHashFromURL(url: string | URL): string | undefined {\n let path: string;\n try {\n path = (url instanceof URL ? url : new URL(url)).pathname;\n } catch {\n return undefined;\n }\n const matches = path.match(/[0-9a-f]{64}/gi);\n if (matches === null || matches.length === 0) {\n return undefined;\n }\n return matches.at(-1)?.toLowerCase();\n}\n\nexport async function sha256Blob(data: Blob | ArrayBuffer | Uint8Array): Promise<string> {\n let bytes: Uint8Array;\n if (data instanceof Uint8Array) {\n bytes = data;\n } else if (data instanceof ArrayBuffer) {\n bytes = new Uint8Array(data);\n } else {\n bytes = new Uint8Array(await data.arrayBuffer());\n }\n return bytesToHex(sha256(bytes));\n}\n\n/** True when `sha256Blob(data)` equals the expected hex (any case). */\nexport async function verifyBlob(\n data: Blob | ArrayBuffer | Uint8Array,\n sha256Hex: string,\n): Promise<boolean> {\n const expected = assertHex32(sha256Hex, \"blob sha256\");\n return (await sha256Blob(data)) === expected;\n}\n\n/** `Nostr ` + base64url(utf8 JSON), padding stripped. NIP-98 uses standard base64; mixing them is a 401. */\nexport function encodeAuthorizationHeader(event: Event): string {\n return `Nostr ${base64urlnopad.encode(utf8Encoder.encode(JSON.stringify(event)))}`;\n}\n\n/** Default BUD-11 human-readable content per verb. */\nconst AUTH_VERB_MESSAGES: Record<BlossomAuthVerb, string> = {\n upload: \"Upload Blob\",\n delete: \"Delete Blob\",\n list: \"List Blobs\",\n get: \"Get Blob\",\n media: \"Upload Media\",\n};\n\nexport function createAuthTemplate(\n verb: BlossomAuthVerb,\n opts?: { sha256?: string; expiration?: number; message?: string },\n): EventTemplate {\n if (!AUTH_VERBS.has(verb)) {\n throw new BlossomError(`invalid blossom auth verb: ${verb}`);\n }\n if (opts?.message !== undefined && opts.message.trim() === \"\") {\n throw new BlossomError(\"blossom auth message must not be empty\");\n }\n const expiration = opts?.expiration ?? Math.floor(Date.now() / 1000) + AUTH_EXPIRATION_SECS;\n const tags: Tag[] = [\n [\"t\", verb],\n [\"expiration\", String(expiration)],\n ];\n if (opts?.sha256 !== undefined) {\n tags.push([\"x\", assertHex32(opts.sha256, \"blob sha256\")]);\n }\n return {\n kind: Kind.BlobsAuth,\n created_at: Math.floor(Date.now() / 1000),\n tags,\n content: opts?.message ?? AUTH_VERB_MESSAGES[verb],\n };\n}\n\nexport async function createUploadAuth(\n sign: BlossomSign,\n file: Blob,\n opts?: { message?: string; expiration?: number },\n): Promise<Event> {\n const hash = await sha256Blob(file);\n return sign(createAuthTemplate(\"upload\", { sha256: hash, ...opts }));\n}\n\nexport async function upload(\n server: string,\n file: Blob,\n auth: Event,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<BlobDescriptor> {\n const hash = await sha256Blob(file);\n const headers: Record<string, string> = {\n Authorization: encodeAuthorizationHeader(auth),\n \"X-SHA-256\": hash,\n };\n if (file.type !== \"\") {\n headers[\"Content-Type\"] = file.type;\n }\n const res = await blossomRequest(opts?.fetch, blossomUrl(server, \"/upload\"), {\n method: \"PUT\",\n headers,\n body: file,\n signal: opts?.signal,\n });\n return requireDescriptorHash(parseBlobDescriptor(await readJson(res)), hash);\n}\n\nexport async function listBlobs(\n server: string,\n pubkey: string,\n auth: Event | undefined,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<BlobDescriptor[]> {\n const pk = assertHex32(pubkey, \"pubkey\");\n const headers: Record<string, string> = {};\n if (auth) {\n headers[\"Authorization\"] = encodeAuthorizationHeader(auth);\n }\n const res = await blossomRequest(opts?.fetch, blossomUrl(server, `/list/${pk}`), {\n method: \"GET\",\n headers,\n signal: opts?.signal,\n });\n const json = await readJson(res);\n if (!Array.isArray(json)) {\n throw new BlossomError(\"blossom list response must be a JSON array\", { status: res.status });\n }\n return json.map(parseBlobDescriptor);\n}\n\nexport async function deleteBlob(\n server: string,\n sha256Hex: string,\n auth: Event,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<void> {\n const hash = assertHex32(sha256Hex, \"blob sha256\");\n await blossomRequest(opts?.fetch, blossomUrl(server, `/${hash}`), {\n method: \"DELETE\",\n headers: { Authorization: encodeAuthorizationHeader(auth) },\n signal: opts?.signal,\n });\n}\n\nexport async function mirrorBlob(\n server: string,\n blob: BlobDescriptor,\n opts: { auth: Event; fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<BlobDescriptor> {\n const hash = assertHex32(blob.sha256, \"blob sha256\");\n const headers: Record<string, string> = {\n Authorization: encodeAuthorizationHeader(opts.auth),\n \"Content-Type\": \"application/json\",\n \"X-SHA-256\": hash,\n \"X-Content-Length\": String(blob.size),\n };\n if (blob.type !== undefined && blob.type !== \"\") {\n headers[\"X-Content-Type\"] = blob.type;\n }\n const res = await blossomRequest(opts.fetch, blossomUrl(server, \"/mirror\"), {\n method: \"PUT\",\n headers,\n body: JSON.stringify({ url: blob.url }),\n signal: opts.signal,\n });\n return requireDescriptorHash(parseBlobDescriptor(await readJson(res)), hash);\n}\n\n/** BUD-06 preflight. Does not PUT the body. */\nexport async function checkUpload(\n server: string,\n file: Blob,\n auth: Event,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<void> {\n const hash = await sha256Blob(file);\n const headers: Record<string, string> = {\n Authorization: encodeAuthorizationHeader(auth),\n \"X-SHA-256\": hash,\n \"X-Content-Length\": String(file.size),\n };\n if (file.type) {\n headers[\"X-Content-Type\"] = file.type;\n }\n await blossomRequest(opts?.fetch, blossomUrl(server, \"/upload\"), {\n method: \"HEAD\",\n headers,\n signal: opts?.signal,\n });\n}\n\n/**\n * Strict HEAD probe. 2xx → true. 404 → false. Other HTTP (including 405) / network → throw\n * BlossomError. AbortError propagates. No GET fallback.\n */\nexport async function blobExists(\n server: string,\n sha256Hex: string,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<boolean> {\n const hash = assertHex32(sha256Hex, \"blob sha256\");\n const url = blossomUrl(server, `/${hash}`);\n const res = await blossomFetch(opts?.fetch, url, { method: \"HEAD\", signal: opts?.signal });\n if (res.status === 404) {\n return false;\n }\n if (res.ok) {\n return true;\n }\n throw blossomHttpError(\"HEAD\", url, res);\n}\n\n/** GET body, then verifyBlob. Non-2xx or sha256 mismatch → throw BlossomError. */\nexport async function getBlob(\n server: string,\n sha256Hex: string,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<Uint8Array> {\n const hash = assertHex32(sha256Hex, \"blob sha256\");\n const url = blossomUrl(server, `/${hash}`);\n const res = await blossomFetch(opts?.fetch, url, { method: \"GET\", signal: opts?.signal });\n if (!res.ok) {\n throw blossomHttpError(\"GET\", url, res);\n }\n const bytes = new Uint8Array(await res.arrayBuffer());\n if (!(await verifyBlob(bytes, hash))) {\n throw new BlossomError(\"blob sha256 mismatch\");\n }\n return bytes;\n}\n\n/**\n * If `url` is not available, try `servers` for the same hash (NIP-B7 SHOULD). Never throws on HTTP\n * status. AbortError still throws. Returns the original URL when no hash, original still available,\n * or nothing else responds.\n */\nexport async function healBlobUrl(\n url: string,\n servers: ReadonlyArray<string>,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<string> {\n const hash = getHashFromURL(url);\n if (hash === undefined) {\n return url;\n }\n\n const ext = extensionFromHashUrl(url, hash);\n const fetchImpl = opts?.fetch ?? requireGlobalFetch(missingBlossomFetch);\n\n const originalStatus = await headStatus(fetchImpl, url, opts?.signal);\n // 2xx/3xx: still available. NIP-B7 only heals URLs that are not.\n if (originalStatus !== undefined && originalStatus >= 200 && originalStatus < 400) {\n return url;\n }\n\n for (const server of servers) {\n const base = parseHttpUrl(server);\n if (base === undefined) {\n continue;\n }\n const candidate = `${base}/${hash}${ext}`;\n // oxlint-disable-next-line no-await-in-loop -- probes servers in order, returning on the first hit\n const status = await headStatus(fetchImpl, candidate, opts?.signal);\n if (status !== undefined && status >= 200 && status < 300) {\n return candidate;\n }\n }\n return url;\n}\n\n/** PUT /upload to each server in order until one descriptor succeeds. */\nexport async function uploadToServers(\n servers: ReadonlyArray<string>,\n file: Blob,\n auth: Event,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<BlobDescriptor> {\n if (servers.length === 0) {\n throw new BlossomError(\"no servers\");\n }\n let last: BlossomError | undefined;\n for (const server of servers) {\n try {\n // oxlint-disable-next-line no-await-in-loop -- tries servers sequentially until one succeeds\n return await upload(server, file, auth, opts);\n } catch (error) {\n if (isAbortError(error)) {\n throw error;\n }\n last =\n error instanceof BlossomError\n ? error\n : new BlossomError(\"blossom upload failed\", {\n cause: error instanceof Error ? error : undefined,\n });\n }\n }\n throw last ?? new BlossomError(\"no servers\");\n}\n\nexport function blossomServerListEventBuilder(servers: ReadonlyArray<string>): EventBuilder {\n const tags: Tag[] = [];\n const seen = new Set<string>();\n for (const raw of servers) {\n const url = parseHttpUrl(raw);\n if (url === undefined || seen.has(url)) {\n continue;\n }\n seen.add(url);\n tags.push([\"server\", url]);\n }\n if (tags.length === 0) {\n throw new BlossomError(\"server list requires at least one valid http(s) URL\");\n }\n return new EventBuilder(Kind.BlossomServerList, \"\").tags(tags);\n}\n\nexport function parseBlossomServerList(event: Pick<Event, \"kind\" | \"tags\">): string[] {\n if (event.kind !== Kind.BlossomServerList) {\n throw new EventValidationError(`expected kind ${Kind.BlossomServerList}, got ${event.kind}`);\n }\n const out: string[] = [];\n const seen = new Set<string>();\n for (const tag of event.tags) {\n const tagValue = tag.at(1);\n if (tag[0] !== \"server\" || tagValue === undefined || tagValue === \"\") {\n continue;\n }\n const url = parseHttpUrl(tagValue);\n if (url === undefined || seen.has(url)) {\n continue;\n }\n seen.add(url);\n out.push(url);\n }\n return out;\n}\n\nfunction parseHttpUrl(raw: string): string | undefined {\n try {\n const u = new URL(raw);\n if (u.protocol !== \"http:\" && u.protocol !== \"https:\") {\n return undefined;\n }\n const path = u.pathname.replace(/\\/+$/, \"\");\n return `${u.origin}${path}`;\n } catch {\n return undefined;\n }\n}\n\n/** Join `/upload` etc. onto the stored server, keeping a path prefix (`/v1/upload`). */\nfunction blossomUrl(server: string, path: string): string {\n const base = parseHttpUrl(server);\n if (base === undefined) {\n throw new BlossomError(`invalid blossom server URL: ${server}`);\n }\n const suffix = path.startsWith(\"/\") ? path : `/${path}`;\n return `${base}${suffix}`;\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && err.name === \"AbortError\";\n}\n\n/** Path suffix after the last 64-hex segment (`HASH.png` → `.png`). */\nfunction extensionFromHashUrl(url: string, hash: string): string {\n let pathname: string;\n try {\n ({ pathname } = new URL(url));\n } catch {\n return \"\";\n }\n const i = pathname.toLowerCase().lastIndexOf(hash);\n if (i === -1) {\n return \"\";\n }\n const m = /^\\.[^/]+/.exec(pathname.slice(i + 64));\n return m?.[0] ?? \"\";\n}\n\nfunction missingBlossomFetch(): BlossomError {\n return new BlossomError(\"no fetch implementation available; pass opts.fetch\");\n}\n\ntype ManualResponse = Awaited<ReturnType<ManualFetch>>;\n\nfunction blossomHttpError(method: string, url: string, res: ManualResponse): BlossomError {\n let reason: string | undefined;\n try {\n reason = res.headers.get(\"x-reason\") ?? undefined;\n } catch {\n reason = undefined;\n }\n const detail = reason?.trim();\n return new BlossomError(\n detail === undefined || detail === \"\"\n ? `blossom ${method} ${url} failed (${res.status})`\n : detail,\n { status: res.status },\n );\n}\n\nasync function blossomFetch(\n fetchImpl: BlossomFetch | undefined,\n url: string,\n init: {\n method: string;\n headers?: Record<string, string>;\n body?: Blob | string;\n signal?: AbortSignal | undefined;\n },\n): Promise<ManualResponse> {\n return fetchManual(\n fetchImpl ?? requireGlobalFetch(missingBlossomFetch),\n url,\n init,\n (err) =>\n new BlossomError(`blossom ${init.method} ${url} failed`, {\n cause: err instanceof Error ? err : undefined,\n }),\n );\n}\n\n/**\n * HEAD with redirect:manual. Network → undefined. AbortError propagates. Never throws on HTTP\n * status.\n */\nasync function headStatus(\n fetchImpl: ManualFetch,\n url: string,\n signal?: AbortSignal,\n): Promise<number | undefined> {\n try {\n const res = await sendManual(fetchImpl, url, { method: \"HEAD\", signal });\n return res.status;\n } catch (error) {\n if (isAbortError(error)) {\n throw error;\n }\n return undefined;\n }\n}\n\nasync function blossomRequest(\n fetchImpl: BlossomFetch | undefined,\n url: string,\n init: {\n method: string;\n headers?: Record<string, string>;\n body?: Blob | string;\n signal?: AbortSignal | undefined;\n },\n): Promise<ManualResponse> {\n const res = await blossomFetch(fetchImpl, url, init);\n if (res.ok) {\n return res;\n }\n throw blossomHttpError(init.method, url, res);\n}\n\nasync function readJson(res: ManualResponse): Promise<unknown> {\n try {\n return await res.json();\n } catch (error) {\n throw new BlossomError(\"invalid blossom JSON response\", {\n cause: error instanceof Error ? error : undefined,\n status: res.status,\n });\n }\n}\n\nfunction parseBlobDescriptor(json: unknown): BlobDescriptor {\n if (!isRecord(json)) {\n throw new BlossomError(\"invalid blob descriptor\");\n }\n const raw = json;\n if (typeof raw[\"url\"] !== \"string\" || !raw[\"url\"]) {\n throw new BlossomError(\"blob descriptor missing url\");\n }\n if (typeof raw[\"sha256\"] !== \"string\") {\n throw new BlossomError(\"blob descriptor missing sha256\");\n }\n const hash = assertHex32(raw[\"sha256\"], \"blob sha256\");\n if (typeof raw[\"size\"] !== \"number\" || !Number.isInteger(raw[\"size\"]) || raw[\"size\"] < 0) {\n throw new BlossomError(\"blob descriptor missing size\");\n }\n const desc: BlobDescriptor = { url: raw[\"url\"], sha256: hash, size: raw[\"size\"] };\n if (typeof raw[\"type\"] === \"string\") {\n desc.type = raw[\"type\"];\n }\n if (\n typeof raw[\"uploaded\"] === \"number\" &&\n Number.isInteger(raw[\"uploaded\"]) &&\n raw[\"uploaded\"] >= 0\n ) {\n desc.uploaded = raw[\"uploaded\"];\n }\n return desc;\n}\n\nfunction requireDescriptorHash(desc: BlobDescriptor, expected: string): BlobDescriptor {\n if (desc.sha256 !== expected) {\n throw new BlossomError(\"blob descriptor sha256 mismatch\");\n }\n return desc;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"blossom.mjs","names":[],"sources":["../../src/nips/blossom.ts"],"sourcesContent":["/**\n * Blossom BUD-01/02/04/06 HTTP helpers, NIP-B7 kind 10063 server lists, and URL healing. Auth is\n * kind 24242, not NIP-98; the Authorization value is base64url.\n *\n * @see https://github.com/hzrd149/blossom\n * @see https://github.com/nostr-protocol/nips/blob/master/B7.md\n */\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { base64urlnopad } from \"@scure/base\";\n\nimport { EventBuilder } from \"../core/builder.ts\";\nimport { EventValidationError, NostrError } from \"../core/error.ts\";\nimport type { Event, EventTemplate } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport type { Tag } from \"../core/tag.ts\";\nimport { assertHex32, bytesToHex, isRecord, nowSeconds, utf8Encoder } from \"../core/util.ts\";\nimport { fetchManual, requireGlobalFetch, sendManual } from \"./http.ts\";\nimport type { ManualFetch } from \"./http.ts\";\n\nexport type BlobDescriptor = {\n url: string;\n sha256: string;\n size: number;\n type?: string;\n uploaded?: number;\n};\n\nexport type BlossomFetch = ManualFetch;\n\n/** Signs a kind 24242 auth template (draft; pubkey is filled by the signer). */\nexport type BlossomSign = (template: EventTemplate) => Promise<Event>;\n\n/** BUD-11 `t` verbs. Mirror reuses `upload`; there is no `t=mirror`. */\nexport type BlossomAuthVerb = \"upload\" | \"delete\" | \"list\" | \"get\" | \"media\";\n\nconst AUTH_VERBS = new Set<BlossomAuthVerb>([\"upload\", \"delete\", \"list\", \"get\", \"media\"]);\n\n/** Default BUD-11 expiration: now + 1h. Servers 401 when the tag is missing. */\nconst AUTH_EXPIRATION_SECS = 3600;\n\nexport class BlossomError extends NostrError {\n override name = \"BlossomError\";\n readonly status?: number | undefined;\n\n constructor(message: string, options?: ErrorOptions & { status?: number }) {\n const { status, ...rest } = options ?? {};\n super(message, rest);\n this.status = status;\n }\n}\n\n/** Last 64-char hex in the URL path (extension ignored). Undefined if none or the URL is invalid. */\nexport function getHashFromURL(url: string | URL): string | undefined {\n let path: string;\n try {\n path = (url instanceof URL ? url : new URL(url)).pathname;\n } catch {\n return undefined;\n }\n const matches = path.match(/[0-9a-f]{64}/gi);\n if (matches === null || matches.length === 0) {\n return undefined;\n }\n return matches.at(-1)?.toLowerCase();\n}\n\nexport async function sha256Blob(data: Blob | ArrayBuffer | Uint8Array): Promise<string> {\n let bytes: Uint8Array;\n if (data instanceof Uint8Array) {\n bytes = data;\n } else if (data instanceof ArrayBuffer) {\n bytes = new Uint8Array(data);\n } else {\n bytes = new Uint8Array(await data.arrayBuffer());\n }\n return bytesToHex(sha256(bytes));\n}\n\n/** True when `sha256Blob(data)` equals the expected hex (any case). */\nexport async function verifyBlob(\n data: Blob | ArrayBuffer | Uint8Array,\n sha256Hex: string,\n): Promise<boolean> {\n const expected = assertHex32(sha256Hex, \"blob sha256\");\n return (await sha256Blob(data)) === expected;\n}\n\n/** `Nostr ` + base64url(utf8 JSON), padding stripped. NIP-98 uses standard base64; mixing them is a 401. */\nexport function encodeAuthorizationHeader(event: Event): string {\n return `Nostr ${base64urlnopad.encode(utf8Encoder.encode(JSON.stringify(event)))}`;\n}\n\n/** Default BUD-11 human-readable content per verb. */\nconst AUTH_VERB_MESSAGES: Record<BlossomAuthVerb, string> = {\n upload: \"Upload Blob\",\n delete: \"Delete Blob\",\n list: \"List Blobs\",\n get: \"Get Blob\",\n media: \"Upload Media\",\n};\n\n/**\n * Hostname for a BUD-11 `server` tag: accepts a full URL or a bare domain; lowercased, undefined\n * when unparseable.\n */\nfunction serverHostname(raw: string): string | undefined {\n try {\n const url = new URL(raw.includes(\"://\") ? raw : `https://${raw}`);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n return undefined;\n }\n const hostname = url.hostname.toLowerCase();\n return hostname === \"\" ? undefined : hostname;\n } catch {\n return undefined;\n }\n}\n\nexport function createAuthTemplate(\n verb: BlossomAuthVerb,\n opts?: {\n sha256?: string | undefined;\n expiration?: number | undefined;\n message?: string | undefined;\n servers?: ReadonlyArray<string> | undefined;\n },\n): EventTemplate {\n if (!AUTH_VERBS.has(verb)) {\n throw new BlossomError(`invalid blossom auth verb: ${verb}`);\n }\n if (opts?.message !== undefined && opts.message.trim() === \"\") {\n throw new BlossomError(\"blossom auth message must not be empty\");\n }\n const expiration = opts?.expiration ?? nowSeconds() + AUTH_EXPIRATION_SECS;\n const tags: Tag[] = [\n [\"t\", verb],\n [\"expiration\", String(expiration)],\n ];\n if (opts?.sha256 !== undefined) {\n tags.push([\"x\", assertHex32(opts.sha256, \"blob sha256\")]);\n }\n const seenServers = new Set<string>();\n for (const server of opts?.servers ?? []) {\n const hostname = serverHostname(server);\n if (hostname === undefined || seenServers.has(hostname)) {\n continue;\n }\n seenServers.add(hostname);\n tags.push([\"server\", hostname]);\n }\n return {\n kind: Kind.BlobsAuth,\n created_at: nowSeconds(),\n tags,\n content: opts?.message ?? AUTH_VERB_MESSAGES[verb],\n };\n}\n\nexport async function createUploadAuth(\n sign: BlossomSign,\n file: Blob,\n opts?: {\n message?: string | undefined;\n expiration?: number | undefined;\n servers?: ReadonlyArray<string> | undefined;\n },\n): Promise<Event> {\n const hash = await sha256Blob(file);\n return sign(createAuthTemplate(\"upload\", { sha256: hash, ...opts }));\n}\n\nexport async function upload(\n server: string,\n file: Blob,\n auth: Event,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal; sha256?: string | undefined },\n): Promise<BlobDescriptor> {\n const hash =\n opts?.sha256 === undefined ? await sha256Blob(file) : assertHex32(opts.sha256, \"blob sha256\");\n const headers: Record<string, string> = {\n Authorization: encodeAuthorizationHeader(auth),\n \"X-SHA-256\": hash,\n };\n if (file.type !== \"\") {\n headers[\"Content-Type\"] = file.type;\n }\n const res = await blossomRequest(opts?.fetch, blossomUrl(server, \"/upload\"), {\n method: \"PUT\",\n headers,\n body: file,\n signal: opts?.signal,\n });\n return requireDescriptorHash(parseBlobDescriptor(await readJson(res)), hash);\n}\n\nexport async function listBlobs(\n server: string,\n pubkey: string,\n auth: Event | undefined,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<BlobDescriptor[]> {\n const pk = assertHex32(pubkey, \"pubkey\");\n const headers: Record<string, string> = {};\n if (auth) {\n headers[\"Authorization\"] = encodeAuthorizationHeader(auth);\n }\n const res = await blossomRequest(opts?.fetch, blossomUrl(server, `/list/${pk}`), {\n method: \"GET\",\n headers,\n signal: opts?.signal,\n });\n const json = await readJson(res);\n if (!Array.isArray(json)) {\n throw new BlossomError(\"blossom list response must be a JSON array\", { status: res.status });\n }\n return json.map(parseBlobDescriptor);\n}\n\nexport async function deleteBlob(\n server: string,\n sha256Hex: string,\n auth: Event,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<void> {\n const hash = assertHex32(sha256Hex, \"blob sha256\");\n await blossomRequest(opts?.fetch, blossomUrl(server, `/${hash}`), {\n method: \"DELETE\",\n headers: { Authorization: encodeAuthorizationHeader(auth) },\n signal: opts?.signal,\n });\n}\n\nexport async function mirrorBlob(\n server: string,\n blob: BlobDescriptor,\n opts: { auth: Event; fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<BlobDescriptor> {\n const hash = assertHex32(blob.sha256, \"blob sha256\");\n const headers: Record<string, string> = {\n Authorization: encodeAuthorizationHeader(opts.auth),\n \"Content-Type\": \"application/json\",\n \"X-SHA-256\": hash,\n \"X-Content-Length\": String(blob.size),\n };\n if (blob.type !== undefined && blob.type !== \"\") {\n headers[\"X-Content-Type\"] = blob.type;\n }\n const res = await blossomRequest(opts.fetch, blossomUrl(server, \"/mirror\"), {\n method: \"PUT\",\n headers,\n body: JSON.stringify({ url: blob.url }),\n signal: opts.signal,\n });\n return requireDescriptorHash(parseBlobDescriptor(await readJson(res)), hash);\n}\n\n/** BUD-06 preflight. Does not PUT the body. */\nexport async function checkUpload(\n server: string,\n file: Blob,\n auth: Event,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal; sha256?: string | undefined },\n): Promise<void> {\n const hash =\n opts?.sha256 === undefined ? await sha256Blob(file) : assertHex32(opts.sha256, \"blob sha256\");\n const headers: Record<string, string> = {\n Authorization: encodeAuthorizationHeader(auth),\n \"X-SHA-256\": hash,\n \"X-Content-Length\": String(file.size),\n };\n if (file.type) {\n headers[\"X-Content-Type\"] = file.type;\n }\n await blossomRequest(opts?.fetch, blossomUrl(server, \"/upload\"), {\n method: \"HEAD\",\n headers,\n signal: opts?.signal,\n });\n}\n\n/**\n * Strict HEAD probe. 2xx → true. 404 → false. Other HTTP (including 405) / network → throw\n * BlossomError. AbortError propagates. No GET fallback.\n */\nexport async function blobExists(\n server: string,\n sha256Hex: string,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<boolean> {\n const hash = assertHex32(sha256Hex, \"blob sha256\");\n const url = blossomUrl(server, `/${hash}`);\n const res = await blossomFetch(opts?.fetch, url, { method: \"HEAD\", signal: opts?.signal });\n if (res.status === 404) {\n return false;\n }\n if (res.ok) {\n return true;\n }\n throw blossomHttpError(\"HEAD\", url, res);\n}\n\n/** GET body, then verifyBlob. Non-2xx or sha256 mismatch → throw BlossomError. */\nexport async function getBlob(\n server: string,\n sha256Hex: string,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<Uint8Array> {\n const hash = assertHex32(sha256Hex, \"blob sha256\");\n const url = blossomUrl(server, `/${hash}`);\n const res = await blossomFetch(opts?.fetch, url, { method: \"GET\", signal: opts?.signal });\n if (!res.ok) {\n throw blossomHttpError(\"GET\", url, res);\n }\n const bytes = new Uint8Array(await res.arrayBuffer());\n if (!(await verifyBlob(bytes, hash))) {\n throw new BlossomError(\"blob sha256 mismatch\");\n }\n return bytes;\n}\n\n/**\n * If `url` is not available, try `servers` for the same hash (NIP-B7 SHOULD). Never throws on HTTP\n * status. AbortError still throws. Returns the original URL when no hash, original still available,\n * or nothing else responds.\n */\nexport async function healBlobUrl(\n url: string,\n servers: ReadonlyArray<string>,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<string> {\n const hash = getHashFromURL(url);\n if (hash === undefined) {\n return url;\n }\n\n const ext = extensionFromHashUrl(url, hash);\n const fetchImpl = opts?.fetch ?? requireGlobalFetch(missingBlossomFetch);\n\n const originalStatus = await headStatus(fetchImpl, url, opts?.signal);\n // 2xx/3xx: still available. NIP-B7 only heals URLs that are not.\n if (originalStatus !== undefined && originalStatus >= 200 && originalStatus < 400) {\n return url;\n }\n\n for (const server of servers) {\n const base = parseHttpUrl(server);\n if (base === undefined) {\n continue;\n }\n const candidate = `${base}/${hash}${ext}`;\n // oxlint-disable-next-line no-await-in-loop -- probes servers in order, returning on the first hit\n const status = await headStatus(fetchImpl, candidate, opts?.signal);\n if (status !== undefined && status >= 200 && status < 300) {\n return candidate;\n }\n }\n return url;\n}\n\n/** PUT /upload to each server in order until one descriptor succeeds. */\nexport async function uploadToServers(\n servers: ReadonlyArray<string>,\n file: Blob,\n auth: Event,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<BlobDescriptor> {\n if (servers.length === 0) {\n throw new BlossomError(\"no servers\");\n }\n let last: BlossomError | undefined;\n for (const server of servers) {\n try {\n // oxlint-disable-next-line no-await-in-loop -- tries servers sequentially until one succeeds\n return await upload(server, file, auth, opts);\n } catch (error) {\n if (isAbortError(error)) {\n throw error;\n }\n last =\n error instanceof BlossomError\n ? error\n : new BlossomError(\"blossom upload failed\", {\n cause: error,\n });\n }\n }\n throw last ?? new BlossomError(\"no servers\");\n}\n\nexport function blossomServerListEventBuilder(servers: ReadonlyArray<string>): EventBuilder {\n const tags: Tag[] = [];\n const seen = new Set<string>();\n for (const raw of servers) {\n const url = parseHttpUrl(raw);\n if (url === undefined || seen.has(url)) {\n continue;\n }\n seen.add(url);\n tags.push([\"server\", url]);\n }\n if (tags.length === 0) {\n throw new BlossomError(\"server list requires at least one valid http(s) URL\");\n }\n return new EventBuilder(Kind.BlossomServerList, \"\").tags(tags);\n}\n\nexport function parseBlossomServerList(event: Pick<Event, \"kind\" | \"tags\">): string[] {\n if (event.kind !== Kind.BlossomServerList) {\n throw new EventValidationError(`expected kind ${Kind.BlossomServerList}, got ${event.kind}`);\n }\n const out: string[] = [];\n const seen = new Set<string>();\n for (const tag of event.tags) {\n const tagValue = tag.at(1);\n if (tag[0] !== \"server\" || tagValue === undefined || tagValue === \"\") {\n continue;\n }\n const url = parseHttpUrl(tagValue);\n if (url === undefined || seen.has(url)) {\n continue;\n }\n seen.add(url);\n out.push(url);\n }\n return out;\n}\n\nfunction parseHttpUrl(raw: string): string | undefined {\n try {\n const u = new URL(raw);\n if (u.protocol !== \"http:\" && u.protocol !== \"https:\") {\n return undefined;\n }\n const path = u.pathname.replace(/\\/+$/, \"\");\n return `${u.origin}${path}`;\n } catch {\n return undefined;\n }\n}\n\n/** Join `/upload` etc. onto the stored server, keeping a path prefix (`/v1/upload`). */\nfunction blossomUrl(server: string, path: string): string {\n const base = parseHttpUrl(server);\n if (base === undefined) {\n throw new BlossomError(`invalid blossom server URL: ${server}`);\n }\n const suffix = path.startsWith(\"/\") ? path : `/${path}`;\n return `${base}${suffix}`;\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && err.name === \"AbortError\";\n}\n\n/** Path suffix after the last 64-hex segment (`HASH.png` → `.png`). */\nfunction extensionFromHashUrl(url: string, hash: string): string {\n let pathname: string;\n try {\n ({ pathname } = new URL(url));\n } catch {\n return \"\";\n }\n const i = pathname.toLowerCase().lastIndexOf(hash);\n if (i === -1) {\n return \"\";\n }\n const m = /^\\.[^/]+/.exec(pathname.slice(i + 64));\n return m?.[0] ?? \"\";\n}\n\nfunction missingBlossomFetch(): BlossomError {\n return new BlossomError(\"no fetch implementation available; pass opts.fetch\");\n}\n\ntype ManualResponse = Awaited<ReturnType<ManualFetch>>;\n\nfunction blossomHttpError(method: string, url: string, res: ManualResponse): BlossomError {\n let reason: string | undefined;\n try {\n reason = res.headers.get(\"x-reason\") ?? undefined;\n } catch {\n reason = undefined;\n }\n const detail = reason?.trim();\n return new BlossomError(\n detail === undefined || detail === \"\"\n ? `blossom ${method} ${url} failed (${res.status})`\n : detail,\n { status: res.status },\n );\n}\n\nasync function blossomFetch(\n fetchImpl: BlossomFetch | undefined,\n url: string,\n init: {\n method: string;\n headers?: Record<string, string>;\n body?: Blob | string;\n signal?: AbortSignal | undefined;\n },\n): Promise<ManualResponse> {\n return fetchManual(\n fetchImpl ?? requireGlobalFetch(missingBlossomFetch),\n url,\n init,\n (err) =>\n new BlossomError(`blossom ${init.method} ${url} failed`, {\n cause: err,\n }),\n );\n}\n\n/**\n * HEAD with redirect:manual. Network → undefined. AbortError propagates. Never throws on HTTP\n * status.\n */\nasync function headStatus(\n fetchImpl: ManualFetch,\n url: string,\n signal?: AbortSignal,\n): Promise<number | undefined> {\n try {\n const res = await sendManual(fetchImpl, url, { method: \"HEAD\", signal });\n return res.status;\n } catch (error) {\n if (isAbortError(error)) {\n throw error;\n }\n return undefined;\n }\n}\n\nasync function blossomRequest(\n fetchImpl: BlossomFetch | undefined,\n url: string,\n init: {\n method: string;\n headers?: Record<string, string>;\n body?: Blob | string;\n signal?: AbortSignal | undefined;\n },\n): Promise<ManualResponse> {\n const res = await blossomFetch(fetchImpl, url, init);\n if (res.ok) {\n return res;\n }\n throw blossomHttpError(init.method, url, res);\n}\n\nasync function readJson(res: ManualResponse): Promise<unknown> {\n try {\n return await res.json();\n } catch (error) {\n throw new BlossomError(\"invalid blossom JSON response\", {\n cause: error,\n status: res.status,\n });\n }\n}\n\nfunction parseBlobDescriptor(json: unknown): BlobDescriptor {\n if (!isRecord(json)) {\n throw new BlossomError(\"invalid blob descriptor\");\n }\n const raw = json;\n if (typeof raw[\"url\"] !== \"string\" || !raw[\"url\"]) {\n throw new BlossomError(\"blob descriptor missing url\");\n }\n if (typeof raw[\"sha256\"] !== \"string\") {\n throw new BlossomError(\"blob descriptor missing sha256\");\n }\n const hash = assertHex32(raw[\"sha256\"], \"blob sha256\");\n if (typeof raw[\"size\"] !== \"number\" || !Number.isInteger(raw[\"size\"]) || raw[\"size\"] < 0) {\n throw new BlossomError(\"blob descriptor missing size\");\n }\n const desc: BlobDescriptor = { url: raw[\"url\"], sha256: hash, size: raw[\"size\"] };\n if (typeof raw[\"type\"] === \"string\") {\n desc.type = raw[\"type\"];\n }\n if (\n typeof raw[\"uploaded\"] === \"number\" &&\n Number.isInteger(raw[\"uploaded\"]) &&\n raw[\"uploaded\"] >= 0\n ) {\n desc.uploaded = raw[\"uploaded\"];\n }\n return desc;\n}\n\nfunction requireDescriptorHash(desc: BlobDescriptor, expected: string): BlobDescriptor {\n if (desc.sha256 !== expected) {\n throw new BlossomError(\"blob descriptor sha256 mismatch\");\n }\n return desc;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { a as hexToBytes$1, d as utf8Encoder, f as CryptoError, n as assertHex32, u as utf8Decoder } from "../util-LfQirgDk.mjs";
1
+ import { a as hexToBytes$1, f as utf8Decoder, m as CryptoError, n as assertHex32, p as utf8Encoder, r as assertSecretKeyBytes } from "../util-yFYzlbrm.mjs";
2
2
  import { randomBytes } from "@noble/hashes/utils.js";
3
3
  import { secp256k1 } from "@noble/curves/secp256k1.js";
4
4
  import { cbc } from "@noble/ciphers/aes.js";
@@ -7,10 +7,16 @@ import { base64 } from "@scure/base";
7
7
  /** NIP-04 legacy encrypted direct messages. Prefer NIP-44 for new applications. */
8
8
  function normalizeSharedSecret(privkey, pubkey) {
9
9
  assertHex32(pubkey, "public key");
10
- return secp256k1.getSharedSecret(privkey, hexToBytes$1(`02${pubkey.toLowerCase()}`)).slice(1, 33);
10
+ try {
11
+ return secp256k1.getSharedSecret(privkey, hexToBytes$1(`02${pubkey.toLowerCase()}`)).slice(1, 33);
12
+ } catch (error) {
13
+ throw new CryptoError("invalid NIP-04 public key", { cause: error });
14
+ }
11
15
  }
12
16
  function resolveSecret(secretKey) {
13
- return typeof secretKey === "string" ? hexToBytes$1(assertHex32(secretKey, "secret key")) : secretKey;
17
+ if (typeof secretKey === "string") return hexToBytes$1(assertHex32(secretKey, "secret key"));
18
+ assertSecretKeyBytes(secretKey);
19
+ return secretKey;
14
20
  }
15
21
  /** Encrypt plaintext to a peer pubkey (NIP-04). */
16
22
  function encrypt(secretKey, pubkey, text) {
@@ -26,10 +32,14 @@ function decrypt(secretKey, pubkey, data) {
26
32
  const parts = data.split("?iv=");
27
33
  if (parts.length !== 2 || parts[0] === void 0 || parts[0] === "" || parts[1] === void 0 || parts[1] === "") throw new CryptoError("invalid NIP-04 payload: missing iv");
28
34
  const normalizedKey = normalizeSharedSecret(privkey, pubkey);
29
- const iv = base64.decode(parts[1]);
30
- const ciphertext = base64.decode(parts[0]);
31
- const plaintext = cbc(normalizedKey, iv).decrypt(ciphertext);
32
- return utf8Decoder.decode(plaintext);
35
+ try {
36
+ const iv = base64.decode(parts[1]);
37
+ const ciphertext = base64.decode(parts[0]);
38
+ const plaintext = cbc(normalizedKey, iv).decrypt(ciphertext);
39
+ return utf8Decoder.decode(plaintext);
40
+ } catch (error) {
41
+ throw new CryptoError("invalid NIP-04 payload", { cause: error });
42
+ }
33
43
  }
34
44
  //#endregion
35
45
  export { decrypt, encrypt };
@@ -1 +1 @@
1
- {"version":3,"file":"nip04.mjs","names":["hexToBytes"],"sources":["../../src/nips/nip04.ts"],"sourcesContent":["/** NIP-04 legacy encrypted direct messages. Prefer NIP-44 for new applications. */\nimport { cbc } from \"@noble/ciphers/aes.js\";\nimport { secp256k1 } from \"@noble/curves/secp256k1.js\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\nimport { base64 } from \"@scure/base\";\n\nimport { CryptoError } from \"../core/error.ts\";\nimport { assertHex32, hexToBytes, utf8Decoder, utf8Encoder } from \"../core/util.ts\";\n\nfunction normalizeSharedSecret(privkey: Uint8Array, pubkey: string): Uint8Array {\n assertHex32(pubkey, \"public key\");\n const key = secp256k1.getSharedSecret(privkey, hexToBytes(`02${pubkey.toLowerCase()}`));\n return key.slice(1, 33);\n}\n\nfunction resolveSecret(secretKey: string | Uint8Array): Uint8Array {\n return typeof secretKey === \"string\"\n ? hexToBytes(assertHex32(secretKey, \"secret key\"))\n : secretKey;\n}\n\n/** Encrypt plaintext to a peer pubkey (NIP-04). */\nexport function encrypt(secretKey: string | Uint8Array, pubkey: string, text: string): string {\n const privkey = resolveSecret(secretKey);\n const normalizedKey = normalizeSharedSecret(privkey, pubkey);\n const iv = randomBytes(16);\n const plaintext = utf8Encoder.encode(text);\n const ciphertext = cbc(normalizedKey, iv).encrypt(plaintext);\n return `${base64.encode(ciphertext)}?iv=${base64.encode(iv)}`;\n}\n\n/** Decrypt a NIP-04 payload from a peer pubkey. */\nexport function decrypt(secretKey: string | Uint8Array, pubkey: string, data: string): string {\n const privkey = resolveSecret(secretKey);\n const parts = data.split(\"?iv=\");\n if (\n parts.length !== 2 ||\n parts[0] === undefined ||\n parts[0] === \"\" ||\n parts[1] === undefined ||\n parts[1] === \"\"\n ) {\n throw new CryptoError(\"invalid NIP-04 payload: missing iv\");\n }\n const normalizedKey = normalizeSharedSecret(privkey, pubkey);\n const iv = base64.decode(parts[1]);\n const ciphertext = base64.decode(parts[0]);\n const plaintext = cbc(normalizedKey, iv).decrypt(ciphertext);\n return utf8Decoder.decode(plaintext);\n}\n"],"mappings":";;;;;;;AASA,SAAS,sBAAsB,SAAqB,QAA4B;CAC9E,YAAY,QAAQ,YAAY;CAEhC,OADY,UAAU,gBAAgB,SAASA,aAAW,KAAK,OAAO,YAAY,GAAG,CAC5E,CAAC,CAAC,MAAM,GAAG,EAAE;AACxB;AAEA,SAAS,cAAc,WAA4C;CACjE,OAAO,OAAO,cAAc,WACxBA,aAAW,YAAY,WAAW,YAAY,CAAC,IAC/C;AACN;;AAGA,SAAgB,QAAQ,WAAgC,QAAgB,MAAsB;CAE5F,MAAM,gBAAgB,sBADN,cAAc,SACoB,GAAG,MAAM;CAC3D,MAAM,KAAK,YAAY,EAAE;CACzB,MAAM,YAAY,YAAY,OAAO,IAAI;CACzC,MAAM,aAAa,IAAI,eAAe,EAAE,CAAC,CAAC,QAAQ,SAAS;CAC3D,OAAO,GAAG,OAAO,OAAO,UAAU,EAAE,MAAM,OAAO,OAAO,EAAE;AAC5D;;AAGA,SAAgB,QAAQ,WAAgC,QAAgB,MAAsB;CAC5F,MAAM,UAAU,cAAc,SAAS;CACvC,MAAM,QAAQ,KAAK,MAAM,MAAM;CAC/B,IACE,MAAM,WAAW,KACjB,MAAM,OAAO,KAAA,KACb,MAAM,OAAO,MACb,MAAM,OAAO,KAAA,KACb,MAAM,OAAO,IAEb,MAAM,IAAI,YAAY,oCAAoC;CAE5D,MAAM,gBAAgB,sBAAsB,SAAS,MAAM;CAC3D,MAAM,KAAK,OAAO,OAAO,MAAM,EAAE;CACjC,MAAM,aAAa,OAAO,OAAO,MAAM,EAAE;CACzC,MAAM,YAAY,IAAI,eAAe,EAAE,CAAC,CAAC,QAAQ,UAAU;CAC3D,OAAO,YAAY,OAAO,SAAS;AACrC"}
1
+ {"version":3,"file":"nip04.mjs","names":["hexToBytes"],"sources":["../../src/nips/nip04.ts"],"sourcesContent":["/** NIP-04 legacy encrypted direct messages. Prefer NIP-44 for new applications. */\nimport { cbc } from \"@noble/ciphers/aes.js\";\nimport { secp256k1 } from \"@noble/curves/secp256k1.js\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\nimport { base64 } from \"@scure/base\";\n\nimport { CryptoError } from \"../core/error.ts\";\nimport {\n assertHex32,\n assertSecretKeyBytes,\n hexToBytes,\n utf8Decoder,\n utf8Encoder,\n} from \"../core/util.ts\";\n\nfunction normalizeSharedSecret(privkey: Uint8Array, pubkey: string): Uint8Array {\n assertHex32(pubkey, \"public key\");\n try {\n const key = secp256k1.getSharedSecret(privkey, hexToBytes(`02${pubkey.toLowerCase()}`));\n return key.slice(1, 33);\n } catch (error) {\n throw new CryptoError(\"invalid NIP-04 public key\", { cause: error });\n }\n}\n\nfunction resolveSecret(secretKey: string | Uint8Array): Uint8Array {\n if (typeof secretKey === \"string\") {\n return hexToBytes(assertHex32(secretKey, \"secret key\"));\n }\n assertSecretKeyBytes(secretKey);\n return secretKey;\n}\n\n/** Encrypt plaintext to a peer pubkey (NIP-04). */\nexport function encrypt(secretKey: string | Uint8Array, pubkey: string, text: string): string {\n const privkey = resolveSecret(secretKey);\n const normalizedKey = normalizeSharedSecret(privkey, pubkey);\n const iv = randomBytes(16);\n const plaintext = utf8Encoder.encode(text);\n const ciphertext = cbc(normalizedKey, iv).encrypt(plaintext);\n return `${base64.encode(ciphertext)}?iv=${base64.encode(iv)}`;\n}\n\n/** Decrypt a NIP-04 payload from a peer pubkey. */\nexport function decrypt(secretKey: string | Uint8Array, pubkey: string, data: string): string {\n const privkey = resolveSecret(secretKey);\n const parts = data.split(\"?iv=\");\n if (\n parts.length !== 2 ||\n parts[0] === undefined ||\n parts[0] === \"\" ||\n parts[1] === undefined ||\n parts[1] === \"\"\n ) {\n throw new CryptoError(\"invalid NIP-04 payload: missing iv\");\n }\n const normalizedKey = normalizeSharedSecret(privkey, pubkey);\n try {\n const iv = base64.decode(parts[1]);\n const ciphertext = base64.decode(parts[0]);\n const plaintext = cbc(normalizedKey, iv).decrypt(ciphertext);\n return utf8Decoder.decode(plaintext);\n } catch (error) {\n throw new CryptoError(\"invalid NIP-04 payload\", { cause: error });\n }\n}\n"],"mappings":";;;;;;;AAeA,SAAS,sBAAsB,SAAqB,QAA4B;CAC9E,YAAY,QAAQ,YAAY;CAChC,IAAI;EAEF,OADY,UAAU,gBAAgB,SAASA,aAAW,KAAK,OAAO,YAAY,GAAG,CAC5E,CAAC,CAAC,MAAM,GAAG,EAAE;CACxB,SAAS,OAAO;EACd,MAAM,IAAI,YAAY,6BAA6B,EAAE,OAAO,MAAM,CAAC;CACrE;AACF;AAEA,SAAS,cAAc,WAA4C;CACjE,IAAI,OAAO,cAAc,UACvB,OAAOA,aAAW,YAAY,WAAW,YAAY,CAAC;CAExD,qBAAqB,SAAS;CAC9B,OAAO;AACT;;AAGA,SAAgB,QAAQ,WAAgC,QAAgB,MAAsB;CAE5F,MAAM,gBAAgB,sBADN,cAAc,SACoB,GAAG,MAAM;CAC3D,MAAM,KAAK,YAAY,EAAE;CACzB,MAAM,YAAY,YAAY,OAAO,IAAI;CACzC,MAAM,aAAa,IAAI,eAAe,EAAE,CAAC,CAAC,QAAQ,SAAS;CAC3D,OAAO,GAAG,OAAO,OAAO,UAAU,EAAE,MAAM,OAAO,OAAO,EAAE;AAC5D;;AAGA,SAAgB,QAAQ,WAAgC,QAAgB,MAAsB;CAC5F,MAAM,UAAU,cAAc,SAAS;CACvC,MAAM,QAAQ,KAAK,MAAM,MAAM;CAC/B,IACE,MAAM,WAAW,KACjB,MAAM,OAAO,KAAA,KACb,MAAM,OAAO,MACb,MAAM,OAAO,KAAA,KACb,MAAM,OAAO,IAEb,MAAM,IAAI,YAAY,oCAAoC;CAE5D,MAAM,gBAAgB,sBAAsB,SAAS,MAAM;CAC3D,IAAI;EACF,MAAM,KAAK,OAAO,OAAO,MAAM,EAAE;EACjC,MAAM,aAAa,OAAO,OAAO,MAAM,EAAE;EACzC,MAAM,YAAY,IAAI,eAAe,EAAE,CAAC,CAAC,QAAQ,UAAU;EAC3D,OAAO,YAAY,OAAO,SAAS;CACrC,SAAS,OAAO;EACd,MAAM,IAAI,YAAY,0BAA0B,EAAE,OAAO,MAAM,CAAC;CAClE;AACF"}
@@ -1,5 +1,5 @@
1
- import { a as NostrError } from "../error-CR8Urc84.mjs";
2
- import { f as ProfilePointer } from "../nip19-BevCJZsp.mjs";
1
+ import { a as NostrError } from "../error-BquXDmIN.mjs";
2
+ import { f as ProfilePointer } from "../nip19-D1OnJ192.mjs";
3
3
  import { t as ManualFetch } from "../http-C4HwIu4B.mjs";
4
4
  //#region src/nips/nip05.d.ts
5
5
  /** Root local-part (`_@domain` rendered as just the domain). */
@@ -32,7 +32,6 @@ export type Nip05Document = {
32
32
  export type Nip05Fetch = ManualFetch;
33
33
  export declare class Nip05Error extends NostrError {
34
34
  name: string;
35
- constructor(message: string, options?: ErrorOptions);
36
35
  }
37
36
  /** Matches optional local@domain. Groups: 1=local (optional), 2=domain. */
38
37
  export declare const NIP05_REGEX: RegExp;
@@ -1,4 +1,4 @@
1
- import { c as isRecord, g as NostrError, o as isHex32 } from "../util-LfQirgDk.mjs";
1
+ import { c as isRecord, o as isHex32, v as NostrError } from "../util-yFYzlbrm.mjs";
2
2
  import { n as requireGlobalFetch, t as fetchManual } from "../http-CuazNRIP.mjs";
3
3
  //#region src/nips/nip05.ts
4
4
  /**
@@ -11,9 +11,6 @@ const NIP05_ROOT_LOCAL = "_";
11
11
  const WELL_KNOWN_PATH = "/.well-known/nostr.json";
12
12
  var Nip05Error = class extends NostrError {
13
13
  name = "Nip05Error";
14
- constructor(message, options) {
15
- super(message, options);
16
- }
17
14
  };
18
15
  /** Matches optional local@domain. Groups: 1=local (optional), 2=domain. */
19
16
  const NIP05_REGEX = /^(?:([a-z0-9._-]+)@)?([a-z0-9-]+(?:\.[a-z0-9-]+)+)$/i;