@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,4 +1,4 @@
1
- import { g as NostrError } from "../util-LfQirgDk.mjs";
1
+ import { v as NostrError } from "../util-yFYzlbrm.mjs";
2
2
  import { decode } from "./nip19.mjs";
3
3
  //#region src/nips/nip21.ts
4
4
  /**
@@ -6,6 +6,10 @@ import { decode } from "./nip19.mjs";
6
6
  *
7
7
  * @see https://github.com/nostr-protocol/nips/blob/master/21.md
8
8
  */
9
+ /** Error thrown by {@link parseNostrURI} on malformed or non-decodable URIs. */
10
+ var Nip21Error = class extends NostrError {
11
+ name = "Nip21Error";
12
+ };
9
13
  /** Matches `nostr:<bech32>` (not anchored). */
10
14
  const NOSTR_URI_REGEX = /nostr:([a-z0-9]+1[02-9ac-hj-np-z]+)/i;
11
15
  function isNostrURI(value) {
@@ -16,15 +20,21 @@ function isNostrURI(value) {
16
20
  /** Parse and decode a full `nostr:…` URI. NIP-21 excludes `nsec`. */
17
21
  function parseNostrURI(uri) {
18
22
  const entity = /^nostr:([a-z0-9]+1[02-9ac-hj-np-z]+)$/i.exec(uri)?.at(1);
19
- if (entity === void 0) throw new NostrError(`invalid Nostr URI: ${uri}`);
20
- if (entity.toLowerCase().startsWith("nsec1")) throw new NostrError("NIP-21 identifiers exclude nsec");
23
+ if (entity === void 0) throw new Nip21Error(`invalid Nostr URI: ${uri}`);
24
+ if (entity.toLowerCase().startsWith("nsec1")) throw new Nip21Error("NIP-21 identifiers exclude nsec");
25
+ let decoded;
26
+ try {
27
+ decoded = decode(entity);
28
+ } catch (error) {
29
+ throw new Nip21Error(`invalid Nostr URI: ${uri}`, { cause: error });
30
+ }
21
31
  return {
22
32
  uri: `nostr:${entity}`,
23
33
  value: entity,
24
- decoded: decode(entity)
34
+ decoded
25
35
  };
26
36
  }
27
37
  //#endregion
28
- export { NOSTR_URI_REGEX, isNostrURI, parseNostrURI };
38
+ export { NOSTR_URI_REGEX, Nip21Error, isNostrURI, parseNostrURI };
29
39
 
30
40
  //# sourceMappingURL=nip21.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"nip21.mjs","names":[],"sources":["../../src/nips/nip21.ts"],"sourcesContent":["/**\n * NIP-21: `nostr:` URI scheme\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/21.md\n */\nimport { NostrError } from \"../core/error.ts\";\nimport { decode } from \"./nip19.ts\";\nimport type { DecodedResult } from \"./nip19.ts\";\n\n/** Matches `nostr:<bech32>` (not anchored). */\n// oxlint-disable-next-line no-inferrable-types -- isolatedDeclarations requires the annotation for dts emit\nexport const NOSTR_URI_REGEX: RegExp = /nostr:([a-z0-9]+1[02-9ac-hj-np-z]+)/i;\n\nexport type NostrURI = {\n /** Full URI including `nostr:` */\n uri: `nostr:${string}`;\n /** Bech32 entity without prefix */\n value: string;\n decoded: DecodedResult;\n};\n\nexport function isNostrURI(value: unknown): value is `nostr:${string}` {\n if (typeof value !== \"string\") {\n return false;\n }\n if (!/^nostr:[a-z0-9]+1[02-9ac-hj-np-z]+$/i.test(value)) {\n return false;\n }\n return !value.toLowerCase().startsWith(\"nostr:nsec1\");\n}\n\n/** Parse and decode a full `nostr:…` URI. NIP-21 excludes `nsec`. */\nexport function parseNostrURI(uri: string): NostrURI {\n const match = /^nostr:([a-z0-9]+1[02-9ac-hj-np-z]+)$/i.exec(uri);\n const entity = match?.at(1);\n if (entity === undefined) {\n throw new NostrError(`invalid Nostr URI: ${uri}`);\n }\n if (entity.toLowerCase().startsWith(\"nsec1\")) {\n throw new NostrError(\"NIP-21 identifiers exclude nsec\");\n }\n return {\n uri: `nostr:${entity}`,\n value: entity,\n decoded: decode(entity),\n };\n}\n"],"mappings":";;;;;;;;;AAWA,MAAa,kBAA0B;AAUvC,SAAgB,WAAW,OAA4C;CACrE,IAAI,OAAO,UAAU,UACnB,OAAO;CAET,IAAI,CAAC,uCAAuC,KAAK,KAAK,GACpD,OAAO;CAET,OAAO,CAAC,MAAM,YAAY,CAAC,CAAC,WAAW,aAAa;AACtD;;AAGA,SAAgB,cAAc,KAAuB;CAEnD,MAAM,SADQ,yCAAyC,KAAK,GACzC,CAAC,EAAE,GAAG,CAAC;CAC1B,IAAI,WAAW,KAAA,GACb,MAAM,IAAI,WAAW,sBAAsB,KAAK;CAElD,IAAI,OAAO,YAAY,CAAC,CAAC,WAAW,OAAO,GACzC,MAAM,IAAI,WAAW,iCAAiC;CAExD,OAAO;EACL,KAAK,SAAS;EACd,OAAO;EACP,SAAS,OAAO,MAAM;CACxB;AACF"}
1
+ {"version":3,"file":"nip21.mjs","names":[],"sources":["../../src/nips/nip21.ts"],"sourcesContent":["/**\n * NIP-21: `nostr:` URI scheme\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/21.md\n */\nimport { NostrError } from \"../core/error.ts\";\nimport { decode } from \"./nip19.ts\";\nimport type { DecodedResult } from \"./nip19.ts\";\n\n/** Error thrown by {@link parseNostrURI} on malformed or non-decodable URIs. */\nexport class Nip21Error extends NostrError {\n override name = \"Nip21Error\";\n}\n\n/** Matches `nostr:<bech32>` (not anchored). */\n// oxlint-disable-next-line no-inferrable-types -- isolatedDeclarations requires the annotation for dts emit\nexport const NOSTR_URI_REGEX: RegExp = /nostr:([a-z0-9]+1[02-9ac-hj-np-z]+)/i;\n\nexport type NostrURI = {\n /** Full URI including `nostr:` */\n uri: `nostr:${string}`;\n /** Bech32 entity without prefix */\n value: string;\n decoded: DecodedResult;\n};\n\nexport function isNostrURI(value: unknown): value is `nostr:${string}` {\n if (typeof value !== \"string\") {\n return false;\n }\n if (!/^nostr:[a-z0-9]+1[02-9ac-hj-np-z]+$/i.test(value)) {\n return false;\n }\n return !value.toLowerCase().startsWith(\"nostr:nsec1\");\n}\n\n/** Parse and decode a full `nostr:…` URI. NIP-21 excludes `nsec`. */\nexport function parseNostrURI(uri: string): NostrURI {\n const match = /^nostr:([a-z0-9]+1[02-9ac-hj-np-z]+)$/i.exec(uri);\n const entity = match?.at(1);\n if (entity === undefined) {\n throw new Nip21Error(`invalid Nostr URI: ${uri}`);\n }\n if (entity.toLowerCase().startsWith(\"nsec1\")) {\n throw new Nip21Error(\"NIP-21 identifiers exclude nsec\");\n }\n let decoded: DecodedResult;\n try {\n decoded = decode(entity);\n } catch (error) {\n throw new Nip21Error(`invalid Nostr URI: ${uri}`, { cause: error });\n }\n return {\n uri: `nostr:${entity}`,\n value: entity,\n decoded,\n };\n}\n"],"mappings":";;;;;;;;;AAUA,IAAa,aAAb,cAAgC,WAAW;CACzC,OAAgB;AAClB;;AAIA,MAAa,kBAA0B;AAUvC,SAAgB,WAAW,OAA4C;CACrE,IAAI,OAAO,UAAU,UACnB,OAAO;CAET,IAAI,CAAC,uCAAuC,KAAK,KAAK,GACpD,OAAO;CAET,OAAO,CAAC,MAAM,YAAY,CAAC,CAAC,WAAW,aAAa;AACtD;;AAGA,SAAgB,cAAc,KAAuB;CAEnD,MAAM,SADQ,yCAAyC,KAAK,GACzC,CAAC,EAAE,GAAG,CAAC;CAC1B,IAAI,WAAW,KAAA,GACb,MAAM,IAAI,WAAW,sBAAsB,KAAK;CAElD,IAAI,OAAO,YAAY,CAAC,CAAC,WAAW,OAAO,GACzC,MAAM,IAAI,WAAW,iCAAiC;CAExD,IAAI;CACJ,IAAI;EACF,UAAU,OAAO,MAAM;CACzB,SAAS,OAAO;EACd,MAAM,IAAI,WAAW,sBAAsB,OAAO,EAAE,OAAO,MAAM,CAAC;CACpE;CACA,OAAO;EACL,KAAK,SAAS;EACd,OAAO;EACP;CACF;AACF"}
@@ -1,5 +1,5 @@
1
- import { t as Event } from "../event-fU3Y01PR.mjs";
2
- import { f as ProfilePointer, i as EventPointer, t as AddressPointer } from "../nip19-BevCJZsp.mjs";
1
+ import { t as Event } from "../event-DJeb_DIL.mjs";
2
+ import { f as ProfilePointer, i as EventPointer, t as AddressPointer } from "../nip19-D1OnJ192.mjs";
3
3
  //#region src/nips/nip27.d.ts
4
4
  export type ContentBlock = {
5
5
  type: "text";
@@ -37,12 +37,12 @@ export type ParseContentOptions = {
37
37
  * Recognize bare `npub`, `nprofile`, `note`, `nevent`, `naddr` without the `nostr:` prefix.
38
38
  * Default `false`.
39
39
  */
40
- legacyBech32?: boolean;
40
+ legacyBech32?: boolean | undefined;
41
41
  /**
42
42
  * URL → MIME type (the `m` field of NIP-92 `imeta` tags); takes precedence over extension
43
43
  * classification.
44
44
  */
45
- imeta?: ReadonlyMap<string, string>;
45
+ imeta?: ReadonlyMap<string, string> | undefined;
46
46
  };
47
47
  /**
48
48
  * Parse note content (or a full event, to pick up `emoji` tags) into ordered blocks. One
@@ -1 +1 @@
1
- {"version":3,"file":"nip27.mjs","names":[],"sources":["../../src/nips/nip27.ts"],"sourcesContent":["/**\n * NIP-27: Text Note References. Tokenizes note content into text, `nostr:` references, URLs, media,\n * relays, hashtags, custom emoji, and Lightning invoices.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/27.md\n */\nimport type { Event } from \"../core/event.ts\";\nimport { decode } from \"./nip19.ts\";\nimport type { AddressPointer, EventPointer, ProfilePointer } from \"./nip19.ts\";\n\nexport type ContentBlock =\n | { type: \"text\"; text: string }\n | {\n type: \"reference\";\n pointer: ProfilePointer | EventPointer | AddressPointer;\n /**\n * `true` when the reference had no `nostr:` prefix (see\n * {@link ParseContentOptions.legacyBech32}).\n */\n bare: boolean;\n }\n | { type: \"url\"; url: string }\n | { type: \"image\" | \"video\" | \"audio\"; url: string }\n | { type: \"relay\"; url: string }\n | { type: \"hashtag\"; value: string }\n | { type: \"emoji\"; shortcode: string; url: string }\n | { type: \"invoice\"; bolt11: string };\n\nexport type ParseContentOptions = {\n /**\n * Recognize bare `npub`, `nprofile`, `note`, `nevent`, `naddr` without the `nostr:` prefix.\n * Default `false`.\n */\n legacyBech32?: boolean;\n /**\n * URL → MIME type (the `m` field of NIP-92 `imeta` tags); takes precedence over extension\n * classification.\n */\n imeta?: ReadonlyMap<string, string>;\n};\n\n// Bech32 data charset ([02-9ac-hj-np-z], case-insensitive) and the NIP-19 entity prefixes.\nconst BECH32_CHARS = \"02-9ac-hj-np-z\";\nconst NIP19_PREFIX = \"(?:npub|nprofile|note|nevent|naddr|nsec)1\";\nconst NIP19_BARE_PREFIX = \"(?:npub|nprofile|note|nevent|naddr)1\";\n\n// A URL run ends at whitespace, angle brackets, double quotes, CJK punctuation\n// (U+3000–U+303F), or full-width forms (U+FF00–U+FFEF).\nconst URL_RUN = `[^\\\\s<>\"\\\\u3000-\\\\u303f\\\\uff00-\\\\uffef]+`;\n\n// Rule order matters: at every position the first matching alternative wins.\nconst SCAN_BODY = [\n // 1. nostr: URIs carry a NIP-19 entity.\n `(?<nostr>nostr:${NIP19_PREFIX}[${BECH32_CHARS}]+)`,\n // 2./3. http(s) and ws(s) URLs share the same boundary rules.\n `(?<url>https?://${URL_RUN})`,\n `(?<relay>wss?://${URL_RUN})`,\n // 4. BOLT11 invoices, with an optional lightning: URI prefix. The HRP may carry an amount\n // (`lnbc10u1…` = ln + currency + amount + multiplier + the `1` separator).\n `(?<invoice>(?:lightning:)?ln(?:bcrt|tbs|bc|tb)(?:\\\\d+[munp]?)?1[${BECH32_CHARS}]+)`,\n // 5. Hashtags: Unicode letters/marks/numbers and underscore, at most 42 code points.\n `(?<hashtag>#[\\\\p{L}\\\\p{M}\\\\p{N}_]{1,42})`,\n // 6. Custom emoji shortcodes (validated against the event's emoji tags).\n `(?<emoji>:[A-Za-z0-9_]+:)`,\n].join(\"|\");\n\nconst SCAN = new RegExp(SCAN_BODY, \"giu\");\n// 7. Bare NIP-19 entities — legacy rendering support, opt-in only.\nconst SCAN_LEGACY = new RegExp(\n `${SCAN_BODY}|(?<bare>${NIP19_BARE_PREFIX}[${BECH32_CHARS}]+)`,\n \"giu\",\n);\n\n// The character before `#` must be absent or a non-tag character.\nconst TAG_CHAR = /[\\p{L}\\p{M}\\p{N}_]/u;\n// Invoice and bare-bech32 tokens must not be glued to a letter or digit.\nconst WORD_CHAR = /[\\p{L}\\p{N}]/u;\n\nconst TRAILING_URL_CHARS = new Set([\".\", \",\", \";\", \":\", \"!\", \"?\", \"'\"]);\n\nconst IMAGE_EXT = new Set([\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\", \"avif\", \"svg\"]);\nconst VIDEO_EXT = new Set([\"mp4\", \"webm\", \"mov\", \"m4v\", \"m3u8\"]);\nconst AUDIO_EXT = new Set([\"mp3\", \"m4a\", \"ogg\", \"oga\", \"opus\", \"wav\", \"flac\", \"aac\"]);\n\nfunction isChar(cp: number | undefined, re: RegExp): boolean {\n return cp !== undefined && re.test(String.fromCodePoint(cp));\n}\n\n/** Drop trailing sentence punctuation and closers that have no opener inside the URL. */\nfunction trimUrlTail(raw: string): string {\n let url = raw;\n for (;;) {\n const last = url.at(-1);\n if (last !== undefined && TRAILING_URL_CHARS.has(last)) {\n url = url.slice(0, -1);\n continue;\n }\n if (last === \")\" || last === \"]\") {\n const open = last === \")\" ? \"(\" : \"[\";\n let opens = 0;\n let closes = 0;\n for (const c of url) {\n if (c === open) {\n opens++;\n } else if (c === last) {\n closes++;\n }\n }\n if (closes > opens) {\n url = url.slice(0, -1);\n continue;\n }\n }\n return url;\n }\n}\n\ntype MediaType = \"image\" | \"video\" | \"audio\";\n\nfunction mimeMedia(mime: string | undefined): MediaType | undefined {\n if (mime === undefined) {\n return undefined;\n }\n const slash = mime.indexOf(\"/\");\n const base = slash === -1 ? mime : mime.slice(0, slash);\n return base === \"image\" || base === \"video\" || base === \"audio\" ? base : undefined;\n}\n\nfunction extMedia(pathname: string): MediaType | undefined {\n const dot = pathname.lastIndexOf(\".\");\n if (dot === -1) {\n return undefined;\n }\n const ext = pathname.slice(dot + 1).toLowerCase();\n if (IMAGE_EXT.has(ext)) {\n return \"image\";\n }\n if (VIDEO_EXT.has(ext)) {\n return \"video\";\n }\n return AUDIO_EXT.has(ext) ? \"audio\" : undefined;\n}\n\nfunction urlBlock(\n raw: string,\n relay: boolean,\n imeta: ReadonlyMap<string, string> | undefined,\n): ContentBlock | undefined {\n const url = trimUrlTail(raw);\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return undefined;\n }\n if (!parsed.hostname.includes(\".\")) {\n return undefined;\n }\n if (relay) {\n return { type: \"relay\", url };\n }\n const media = mimeMedia(imeta?.get(url)) ?? extMedia(parsed.pathname);\n return media === undefined ? { type: \"url\", url } : { type: media, url };\n}\n\nfunction referenceFor(code: string, bare: boolean): ContentBlock | undefined {\n let pointer: ProfilePointer | EventPointer | AddressPointer;\n try {\n const decoded = decode(code);\n switch (decoded.type) {\n case \"npub\":\n pointer = { pubkey: decoded.data };\n break;\n case \"note\":\n pointer = { id: decoded.data };\n break;\n case \"nprofile\":\n case \"nevent\":\n case \"naddr\":\n pointer = decoded.data;\n break;\n case \"nsec\":\n return undefined;\n }\n } catch {\n return undefined;\n }\n return { type: \"reference\", pointer, bare };\n}\n\n/**\n * Parse note content (or a full event, to pick up `emoji` tags) into ordered blocks. One\n * left-to-right scan; at every position the rules fire in the alternation order above. Adjacent\n * text merges and no empty text blocks are emitted.\n */\nexport function parseContent(\n content: string | Pick<Event, \"content\" | \"tags\">,\n opts?: ParseContentOptions,\n): ContentBlock[] {\n const emojis = new Map<string, string>();\n let text: string;\n if (typeof content === \"string\") {\n text = content;\n } else {\n for (const tag of content.tags) {\n if (\n tag[0] === \"emoji\" &&\n tag[1] !== undefined &&\n tag[1] !== \"\" &&\n tag[2] !== undefined &&\n tag[2] !== \"\"\n ) {\n emojis.set(tag[1], tag[2]);\n }\n }\n text = content.content;\n }\n\n const scan = opts?.legacyBech32 === true ? SCAN_LEGACY : SCAN;\n scan.lastIndex = 0;\n\n const blocks: ContentBlock[] = [];\n let textStart = 0;\n const flushText = (end: number): void => {\n if (end > textStart) {\n blocks.push({ type: \"text\", text: text.slice(textStart, end) });\n }\n };\n\n for (let m = scan.exec(text); m !== null; m = scan.exec(text)) {\n const start = m.index;\n const [token] = m;\n const end = start + token.length;\n const g = m.groups;\n let block: ContentBlock | undefined;\n\n if (g?.[\"nostr\"] !== undefined) {\n block = referenceFor(token.slice(\"nostr:\".length), false);\n } else if (g?.[\"url\"] !== undefined) {\n block = urlBlock(token, false, opts?.imeta);\n } else if (g?.[\"relay\"] !== undefined) {\n block = urlBlock(token, true, undefined);\n } else if (g?.[\"invoice\"] !== undefined) {\n const prev = start === 0 ? undefined : text.codePointAt(start - 1);\n if (!isChar(prev, WORD_CHAR) && !isChar(text.codePointAt(end), WORD_CHAR)) {\n const invoice = token.toLowerCase().startsWith(\"lightning:\")\n ? token.slice(\"lightning:\".length)\n : token;\n block = { type: \"invoice\", bolt11: invoice.toLowerCase() };\n }\n } else if (g?.[\"hashtag\"] !== undefined) {\n const prev = start === 0 ? undefined : text.codePointAt(start - 1);\n if (!isChar(prev, TAG_CHAR)) {\n block = { type: \"hashtag\", value: token.slice(1) };\n }\n } else if (g?.[\"emoji\"] !== undefined) {\n const url = emojis.get(token.slice(1, -1));\n if (url !== undefined) {\n block = { type: \"emoji\", shortcode: token.slice(1, -1), url };\n }\n } else if (g?.[\"bare\"] !== undefined) {\n const prev = start === 0 ? undefined : text.codePointAt(start - 1);\n if (!isChar(prev, WORD_CHAR) && !isChar(text.codePointAt(end), WORD_CHAR)) {\n block = referenceFor(token, true);\n }\n }\n\n if (block === undefined) {\n // The candidate is plain text; resume scanning one code unit later so any token inside\n // its span can still match.\n scan.lastIndex = start + 1;\n continue;\n }\n flushText(start);\n blocks.push(block);\n // URL-family blocks may end before the regex run once trailing punctuation is trimmed.\n textStart =\n block.type === \"url\" ||\n block.type === \"relay\" ||\n block.type === \"image\" ||\n block.type === \"video\" ||\n block.type === \"audio\"\n ? start + block.url.length\n : end;\n }\n\n flushText(text.length);\n return blocks;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"nip27.mjs","names":[],"sources":["../../src/nips/nip27.ts"],"sourcesContent":["/**\n * NIP-27: Text Note References. Tokenizes note content into text, `nostr:` references, URLs, media,\n * relays, hashtags, custom emoji, and Lightning invoices.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/27.md\n */\nimport type { Event } from \"../core/event.ts\";\nimport { decode } from \"./nip19.ts\";\nimport type { AddressPointer, EventPointer, ProfilePointer } from \"./nip19.ts\";\n\nexport type ContentBlock =\n | { type: \"text\"; text: string }\n | {\n type: \"reference\";\n pointer: ProfilePointer | EventPointer | AddressPointer;\n /**\n * `true` when the reference had no `nostr:` prefix (see\n * {@link ParseContentOptions.legacyBech32}).\n */\n bare: boolean;\n }\n | { type: \"url\"; url: string }\n | { type: \"image\" | \"video\" | \"audio\"; url: string }\n | { type: \"relay\"; url: string }\n | { type: \"hashtag\"; value: string }\n | { type: \"emoji\"; shortcode: string; url: string }\n | { type: \"invoice\"; bolt11: string };\n\nexport type ParseContentOptions = {\n /**\n * Recognize bare `npub`, `nprofile`, `note`, `nevent`, `naddr` without the `nostr:` prefix.\n * Default `false`.\n */\n legacyBech32?: boolean | undefined;\n /**\n * URL → MIME type (the `m` field of NIP-92 `imeta` tags); takes precedence over extension\n * classification.\n */\n imeta?: ReadonlyMap<string, string> | undefined;\n};\n\n// Bech32 data charset ([02-9ac-hj-np-z], case-insensitive) and the NIP-19 entity prefixes.\nconst BECH32_CHARS = \"02-9ac-hj-np-z\";\nconst NIP19_PREFIX = \"(?:npub|nprofile|note|nevent|naddr|nsec)1\";\nconst NIP19_BARE_PREFIX = \"(?:npub|nprofile|note|nevent|naddr)1\";\n\n// A URL run ends at whitespace, angle brackets, double quotes, CJK punctuation\n// (U+3000–U+303F), or full-width forms (U+FF00–U+FFEF).\nconst URL_RUN = `[^\\\\s<>\"\\\\u3000-\\\\u303f\\\\uff00-\\\\uffef]+`;\n\n// Rule order matters: at every position the first matching alternative wins.\nconst SCAN_BODY = [\n // 1. nostr: URIs carry a NIP-19 entity.\n `(?<nostr>nostr:${NIP19_PREFIX}[${BECH32_CHARS}]+)`,\n // 2./3. http(s) and ws(s) URLs share the same boundary rules.\n `(?<url>https?://${URL_RUN})`,\n `(?<relay>wss?://${URL_RUN})`,\n // 4. BOLT11 invoices, with an optional lightning: URI prefix. The HRP may carry an amount\n // (`lnbc10u1…` = ln + currency + amount + multiplier + the `1` separator).\n `(?<invoice>(?:lightning:)?ln(?:bcrt|tbs|bc|tb)(?:\\\\d+[munp]?)?1[${BECH32_CHARS}]+)`,\n // 5. Hashtags: Unicode letters/marks/numbers and underscore, at most 42 code points.\n `(?<hashtag>#[\\\\p{L}\\\\p{M}\\\\p{N}_]{1,42})`,\n // 6. Custom emoji shortcodes (validated against the event's emoji tags).\n `(?<emoji>:[A-Za-z0-9_]+:)`,\n].join(\"|\");\n\nconst SCAN = new RegExp(SCAN_BODY, \"giu\");\n// 7. Bare NIP-19 entities — legacy rendering support, opt-in only.\nconst SCAN_LEGACY = new RegExp(\n `${SCAN_BODY}|(?<bare>${NIP19_BARE_PREFIX}[${BECH32_CHARS}]+)`,\n \"giu\",\n);\n\n// The character before `#` must be absent or a non-tag character.\nconst TAG_CHAR = /[\\p{L}\\p{M}\\p{N}_]/u;\n// Invoice and bare-bech32 tokens must not be glued to a letter or digit.\nconst WORD_CHAR = /[\\p{L}\\p{N}]/u;\n\nconst TRAILING_URL_CHARS = new Set([\".\", \",\", \";\", \":\", \"!\", \"?\", \"'\"]);\n\nconst IMAGE_EXT = new Set([\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\", \"avif\", \"svg\"]);\nconst VIDEO_EXT = new Set([\"mp4\", \"webm\", \"mov\", \"m4v\", \"m3u8\"]);\nconst AUDIO_EXT = new Set([\"mp3\", \"m4a\", \"ogg\", \"oga\", \"opus\", \"wav\", \"flac\", \"aac\"]);\n\nfunction isChar(cp: number | undefined, re: RegExp): boolean {\n return cp !== undefined && re.test(String.fromCodePoint(cp));\n}\n\n/** Drop trailing sentence punctuation and closers that have no opener inside the URL. */\nfunction trimUrlTail(raw: string): string {\n let url = raw;\n for (;;) {\n const last = url.at(-1);\n if (last !== undefined && TRAILING_URL_CHARS.has(last)) {\n url = url.slice(0, -1);\n continue;\n }\n if (last === \")\" || last === \"]\") {\n const open = last === \")\" ? \"(\" : \"[\";\n let opens = 0;\n let closes = 0;\n for (const c of url) {\n if (c === open) {\n opens++;\n } else if (c === last) {\n closes++;\n }\n }\n if (closes > opens) {\n url = url.slice(0, -1);\n continue;\n }\n }\n return url;\n }\n}\n\ntype MediaType = \"image\" | \"video\" | \"audio\";\n\nfunction mimeMedia(mime: string | undefined): MediaType | undefined {\n if (mime === undefined) {\n return undefined;\n }\n const slash = mime.indexOf(\"/\");\n const base = slash === -1 ? mime : mime.slice(0, slash);\n return base === \"image\" || base === \"video\" || base === \"audio\" ? base : undefined;\n}\n\nfunction extMedia(pathname: string): MediaType | undefined {\n const dot = pathname.lastIndexOf(\".\");\n if (dot === -1) {\n return undefined;\n }\n const ext = pathname.slice(dot + 1).toLowerCase();\n if (IMAGE_EXT.has(ext)) {\n return \"image\";\n }\n if (VIDEO_EXT.has(ext)) {\n return \"video\";\n }\n return AUDIO_EXT.has(ext) ? \"audio\" : undefined;\n}\n\nfunction urlBlock(\n raw: string,\n relay: boolean,\n imeta: ReadonlyMap<string, string> | undefined,\n): ContentBlock | undefined {\n const url = trimUrlTail(raw);\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return undefined;\n }\n if (!parsed.hostname.includes(\".\")) {\n return undefined;\n }\n if (relay) {\n return { type: \"relay\", url };\n }\n const media = mimeMedia(imeta?.get(url)) ?? extMedia(parsed.pathname);\n return media === undefined ? { type: \"url\", url } : { type: media, url };\n}\n\nfunction referenceFor(code: string, bare: boolean): ContentBlock | undefined {\n let pointer: ProfilePointer | EventPointer | AddressPointer;\n try {\n const decoded = decode(code);\n switch (decoded.type) {\n case \"npub\":\n pointer = { pubkey: decoded.data };\n break;\n case \"note\":\n pointer = { id: decoded.data };\n break;\n case \"nprofile\":\n case \"nevent\":\n case \"naddr\":\n pointer = decoded.data;\n break;\n case \"nsec\":\n return undefined;\n }\n } catch {\n return undefined;\n }\n return { type: \"reference\", pointer, bare };\n}\n\n/**\n * Parse note content (or a full event, to pick up `emoji` tags) into ordered blocks. One\n * left-to-right scan; at every position the rules fire in the alternation order above. Adjacent\n * text merges and no empty text blocks are emitted.\n */\nexport function parseContent(\n content: string | Pick<Event, \"content\" | \"tags\">,\n opts?: ParseContentOptions,\n): ContentBlock[] {\n const emojis = new Map<string, string>();\n let text: string;\n if (typeof content === \"string\") {\n text = content;\n } else {\n for (const tag of content.tags) {\n if (\n tag[0] === \"emoji\" &&\n tag[1] !== undefined &&\n tag[1] !== \"\" &&\n tag[2] !== undefined &&\n tag[2] !== \"\"\n ) {\n emojis.set(tag[1], tag[2]);\n }\n }\n text = content.content;\n }\n\n const scan = opts?.legacyBech32 === true ? SCAN_LEGACY : SCAN;\n scan.lastIndex = 0;\n\n const blocks: ContentBlock[] = [];\n let textStart = 0;\n const flushText = (end: number): void => {\n if (end > textStart) {\n blocks.push({ type: \"text\", text: text.slice(textStart, end) });\n }\n };\n\n for (let m = scan.exec(text); m !== null; m = scan.exec(text)) {\n const start = m.index;\n const [token] = m;\n const end = start + token.length;\n const g = m.groups;\n let block: ContentBlock | undefined;\n\n if (g?.[\"nostr\"] !== undefined) {\n block = referenceFor(token.slice(\"nostr:\".length), false);\n } else if (g?.[\"url\"] !== undefined) {\n block = urlBlock(token, false, opts?.imeta);\n } else if (g?.[\"relay\"] !== undefined) {\n block = urlBlock(token, true, undefined);\n } else if (g?.[\"invoice\"] !== undefined) {\n const prev = start === 0 ? undefined : text.codePointAt(start - 1);\n if (!isChar(prev, WORD_CHAR) && !isChar(text.codePointAt(end), WORD_CHAR)) {\n const invoice = token.toLowerCase().startsWith(\"lightning:\")\n ? token.slice(\"lightning:\".length)\n : token;\n block = { type: \"invoice\", bolt11: invoice.toLowerCase() };\n }\n } else if (g?.[\"hashtag\"] !== undefined) {\n const prev = start === 0 ? undefined : text.codePointAt(start - 1);\n if (!isChar(prev, TAG_CHAR)) {\n block = { type: \"hashtag\", value: token.slice(1) };\n }\n } else if (g?.[\"emoji\"] !== undefined) {\n const url = emojis.get(token.slice(1, -1));\n if (url !== undefined) {\n block = { type: \"emoji\", shortcode: token.slice(1, -1), url };\n }\n } else if (g?.[\"bare\"] !== undefined) {\n const prev = start === 0 ? undefined : text.codePointAt(start - 1);\n if (!isChar(prev, WORD_CHAR) && !isChar(text.codePointAt(end), WORD_CHAR)) {\n block = referenceFor(token, true);\n }\n }\n\n if (block === undefined) {\n // The candidate is plain text; resume scanning one code unit later so any token inside\n // its span can still match.\n scan.lastIndex = start + 1;\n continue;\n }\n flushText(start);\n blocks.push(block);\n // URL-family blocks may end before the regex run once trailing punctuation is trimmed.\n textStart =\n block.type === \"url\" ||\n block.type === \"relay\" ||\n block.type === \"image\" ||\n block.type === \"video\" ||\n block.type === \"audio\"\n ? start + block.url.length\n : end;\n }\n\n flushText(text.length);\n return blocks;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { n as EventTemplate } from "../event-fU3Y01PR.mjs";
1
+ import { n as EventTemplate } from "../event-DJeb_DIL.mjs";
2
2
  //#region src/nips/nip42.d.ts
3
3
  /**
4
4
  * Build an unsigned NIP-42 AUTH event template for the given relay challenge. Caller must sign with
@@ -1,3 +1,4 @@
1
+ import { d as nowSeconds } from "../util-yFYzlbrm.mjs";
1
2
  import { t as Kind } from "../kind-Cxwhhkx8.mjs";
2
3
  //#region src/nips/nip42.ts
3
4
  /**
@@ -7,7 +8,7 @@ import { t as Kind } from "../kind-Cxwhhkx8.mjs";
7
8
  function makeAuthEvent(relayURL, challenge) {
8
9
  return {
9
10
  kind: Kind.ClientAuth,
10
- created_at: Math.floor(Date.now() / 1e3),
11
+ created_at: nowSeconds(),
11
12
  tags: [["relay", relayURL], ["challenge", challenge]],
12
13
  content: ""
13
14
  };
@@ -1 +1 @@
1
- {"version":3,"file":"nip42.mjs","names":[],"sources":["../../src/nips/nip42.ts"],"sourcesContent":["import type { EventTemplate } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\n\n/**\n * Build an unsigned NIP-42 AUTH event template for the given relay challenge. Caller must sign with\n * their `NostrSigner` / keys.\n */\nexport function makeAuthEvent(relayURL: string, challenge: string): EventTemplate {\n return {\n kind: Kind.ClientAuth,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n [\"relay\", relayURL],\n [\"challenge\", challenge],\n ],\n content: \"\",\n };\n}\n\n/** True when a CLOSED reason requests NIP-42 authentication. */\nexport function isAuthRequired(reason: string): boolean {\n return reason.startsWith(\"auth-required:\");\n}\n"],"mappings":";;;;;;AAOA,SAAgB,cAAc,UAAkB,WAAkC;CAChF,OAAO;EACL,MAAM,KAAK;EACX,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;EACxC,MAAM,CACJ,CAAC,SAAS,QAAQ,GAClB,CAAC,aAAa,SAAS,CACzB;EACA,SAAS;CACX;AACF;;AAGA,SAAgB,eAAe,QAAyB;CACtD,OAAO,OAAO,WAAW,gBAAgB;AAC3C"}
1
+ {"version":3,"file":"nip42.mjs","names":[],"sources":["../../src/nips/nip42.ts"],"sourcesContent":["import type { EventTemplate } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { nowSeconds } from \"../core/util.ts\";\n\n/**\n * Build an unsigned NIP-42 AUTH event template for the given relay challenge. Caller must sign with\n * their `NostrSigner` / keys.\n */\nexport function makeAuthEvent(relayURL: string, challenge: string): EventTemplate {\n return {\n kind: Kind.ClientAuth,\n created_at: nowSeconds(),\n tags: [\n [\"relay\", relayURL],\n [\"challenge\", challenge],\n ],\n content: \"\",\n };\n}\n\n/** True when a CLOSED reason requests NIP-42 authentication. */\nexport function isAuthRequired(reason: string): boolean {\n return reason.startsWith(\"auth-required:\");\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,cAAc,UAAkB,WAAkC;CAChF,OAAO;EACL,MAAM,KAAK;EACX,YAAY,WAAW;EACvB,MAAM,CACJ,CAAC,SAAS,QAAQ,GAClB,CAAC,aAAa,SAAS,CACzB;EACA,SAAS;CACX;AACF;;AAGA,SAAgB,eAAe,QAAyB;CACtD,OAAO,OAAO,WAAW,gBAAgB;AAC3C"}
@@ -1,4 +1,4 @@
1
- import { a as hexToBytes$1, d as utf8Encoder, f as CryptoError, n as assertHex32, r as assertSecretKeyBytes, 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 { concatBytes, randomBytes } from "@noble/hashes/utils.js";
3
3
  import { sha256 } from "@noble/hashes/sha2.js";
4
4
  import { secp256k1 } from "@noble/curves/secp256k1.js";
@@ -1,2 +1,2 @@
1
- import { a as Nip46Request, c as createNostrConnectURI, d as encodeNip46Request, f as encodeNip46Response, h as toBunkerURL, i as Nip46Error, l as decodeNip46Request, m as parseNostrConnectURI, n as BunkerPointer, o as Nip46Response, p as parseBunkerURL, r as ClientMetadata, s as NostrConnectParams, t as BUNKER_REGEX, u as decodeNip46Response } from "../nip46-DDV4dC56.mjs";
2
- export { BUNKER_REGEX, BunkerPointer, ClientMetadata, Nip46Error, Nip46Request, Nip46Response, NostrConnectParams, createNostrConnectURI, decodeNip46Request, decodeNip46Response, encodeNip46Request, encodeNip46Response, parseBunkerURL, parseNostrConnectURI, toBunkerURL };
1
+ import { a as Nip46Response, c as decodeNip46Request, d as encodeNip46Response, f as parseBunkerURL, i as Nip46Request, l as decodeNip46Response, m as toBunkerURL, n as ClientMetadata, o as NostrConnectParams, p as parseNostrConnectURI, r as Nip46Error, s as createNostrConnectURI, t as BunkerPointer, u as encodeNip46Request } from "../nip46-DrsPI8EX.mjs";
2
+ export { BunkerPointer, ClientMetadata, Nip46Error, Nip46Request, Nip46Response, NostrConnectParams, createNostrConnectURI, decodeNip46Request, decodeNip46Response, encodeNip46Request, encodeNip46Response, parseBunkerURL, parseNostrConnectURI, toBunkerURL };
@@ -1,4 +1,4 @@
1
- import { c as isRecord, g as NostrError, n as assertHex32, o as isHex32 } from "../util-LfQirgDk.mjs";
1
+ import { c as isRecord, o as isHex32, v as NostrError } from "../util-yFYzlbrm.mjs";
2
2
  //#region src/nips/nip46.ts
3
3
  /**
4
4
  * NIP-46 (Nostr Connect) protocol helpers: bunker URI, nostrconnect URI, RPC JSON.
@@ -6,12 +6,8 @@ import { c as isRecord, g as NostrError, n as assertHex32, o as isHex32 } from "
6
6
  *
7
7
  * @see https://github.com/nostr-protocol/nips/blob/master/46.md
8
8
  */
9
- const BUNKER_REGEX = /^bunker:\/\/([0-9a-fA-F]{64})\??([?/\w:.=&%-]*)$/;
10
9
  var Nip46Error = class extends NostrError {
11
10
  name = "Nip46Error";
12
- constructor(message, options) {
13
- super(message, options);
14
- }
15
11
  };
16
12
  /** Encode a bunker pointer as `bunker://…`. */
17
13
  function toBunkerURL(pointer) {
@@ -25,20 +21,20 @@ function toBunkerURL(pointer) {
25
21
  * (including NIP-05 identifiers).
26
22
  */
27
23
  function parseBunkerURL(input) {
28
- const match = BUNKER_REGEX.exec(input.trim());
29
- const hex = match?.at(1);
30
- if (match === null || hex === void 0) return;
24
+ let url;
31
25
  try {
32
- const pubkey = assertHex32(hex, "bunker pubkey");
33
- const qs = new URLSearchParams(match.at(2) ?? "");
34
- return {
35
- pubkey,
36
- relays: qs.getAll("relay"),
37
- secret: qs.get("secret") ?? void 0
38
- };
26
+ url = new URL(input.trim());
39
27
  } catch {
40
28
  return;
41
29
  }
30
+ if (url.protocol !== "bunker:") return;
31
+ const pubkey = url.hostname || url.pathname.replace(/^\/*/, "");
32
+ if (!isHex32(pubkey.toLowerCase())) return;
33
+ return {
34
+ pubkey: pubkey.toLowerCase(),
35
+ relays: url.searchParams.getAll("relay"),
36
+ secret: url.searchParams.get("secret") ?? void 0
37
+ };
42
38
  }
43
39
  /** Build a client-initiated `nostrconnect://` URI. */
44
40
  function createNostrConnectURI(params) {
@@ -60,7 +56,7 @@ function parseNostrConnectURI(uri) {
60
56
  try {
61
57
  url = new URL(uri);
62
58
  } catch (error) {
63
- throw new Nip46Error(`invalid nostrconnect URI: ${uri}`, { cause: error instanceof Error ? error : void 0 });
59
+ throw new Nip46Error(`invalid nostrconnect URI: ${uri}`, { cause: error });
64
60
  }
65
61
  if (url.protocol !== "nostrconnect:") throw new Nip46Error(`expected nostrconnect: scheme, got ${url.protocol}`);
66
62
  const clientPubkey = url.hostname || url.pathname.replace(/^\/*/, "");
@@ -96,7 +92,7 @@ function decodeNip46Request(json) {
96
92
  try {
97
93
  data = JSON.parse(json);
98
94
  } catch (error) {
99
- throw new Nip46Error("invalid NIP-46 request JSON", { cause: error instanceof Error ? error : void 0 });
95
+ throw new Nip46Error("invalid NIP-46 request JSON", { cause: error });
100
96
  }
101
97
  if (!isRecord(data) || typeof data["id"] !== "string" || typeof data["method"] !== "string") throw new Nip46Error("invalid NIP-46 request shape");
102
98
  if (!Array.isArray(data["params"]) || !data["params"].every((p) => typeof p === "string")) throw new Nip46Error("invalid NIP-46 request params");
@@ -117,7 +113,7 @@ function decodeNip46Response(json) {
117
113
  try {
118
114
  data = JSON.parse(json);
119
115
  } catch (error) {
120
- throw new Nip46Error("invalid NIP-46 response JSON", { cause: error instanceof Error ? error : void 0 });
116
+ throw new Nip46Error("invalid NIP-46 response JSON", { cause: error });
121
117
  }
122
118
  if (!isRecord(data) || typeof data["id"] !== "string") throw new Nip46Error("invalid NIP-46 response shape");
123
119
  const result = data["result"] === null ? void 0 : data["result"];
@@ -130,6 +126,6 @@ function decodeNip46Response(json) {
130
126
  return response;
131
127
  }
132
128
  //#endregion
133
- export { BUNKER_REGEX, Nip46Error, createNostrConnectURI, decodeNip46Request, decodeNip46Response, encodeNip46Request, encodeNip46Response, parseBunkerURL, parseNostrConnectURI, toBunkerURL };
129
+ export { Nip46Error, createNostrConnectURI, decodeNip46Request, decodeNip46Response, encodeNip46Request, encodeNip46Response, parseBunkerURL, parseNostrConnectURI, toBunkerURL };
134
130
 
135
131
  //# sourceMappingURL=nip46.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"nip46.mjs","names":[],"sources":["../../src/nips/nip46.ts"],"sourcesContent":["import { NostrError } from \"../core/error.ts\";\n/**\n * NIP-46 (Nostr Connect) protocol helpers: bunker URI, nostrconnect URI, RPC JSON.\n * Transport/signing live in {@link Nip46Signer}.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/46.md\n */\nimport { assertHex32, isHex32, isRecord } from \"../core/util.ts\";\n\n// oxlint-disable-next-line no-inferrable-types -- isolatedDeclarations requires the annotation for dts emit\nexport const BUNKER_REGEX: RegExp = /^bunker:\\/\\/([0-9a-fA-F]{64})\\??([?/\\w:.=&%-]*)$/;\n\nexport type BunkerPointer = {\n /** Remote signer / bunker public key (hex). */\n pubkey: string;\n relays: string[];\n secret: string | undefined;\n};\n\nexport type ClientMetadata = {\n name?: string | undefined;\n url?: string | undefined;\n image?: string | undefined;\n};\n\nexport type NostrConnectParams = {\n clientPubkey: string;\n relays: string[];\n secret: string;\n perms?: string[] | undefined;\n} & ClientMetadata;\n\nexport type Nip46Request = {\n id: string;\n method: string;\n params: string[];\n};\n\nexport type Nip46Response = {\n id: string;\n result?: string | undefined;\n error?: string | undefined;\n};\n\nexport class Nip46Error extends NostrError {\n override name = \"Nip46Error\";\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n }\n}\n\n/** Encode a bunker pointer as `bunker://…`. */\nexport function toBunkerURL(pointer: BunkerPointer): string {\n const url = new URL(`bunker://${pointer.pubkey.toLowerCase()}`);\n for (const relay of pointer.relays) {\n url.searchParams.append(\"relay\", relay);\n }\n if (pointer.secret !== undefined && pointer.secret !== \"\") {\n url.searchParams.set(\"secret\", pointer.secret);\n }\n return url.toString();\n}\n\n/**\n * Parse a `bunker://` URL into a pointer. Returns undefined when the input is not a bunker URL\n * (including NIP-05 identifiers).\n */\nexport function parseBunkerURL(input: string): BunkerPointer | undefined {\n const match = BUNKER_REGEX.exec(input.trim());\n const hex = match?.at(1);\n if (match === null || hex === undefined) {\n return undefined;\n }\n try {\n const pubkey = assertHex32(hex, \"bunker pubkey\");\n const qs = new URLSearchParams(match.at(2) ?? \"\");\n return {\n pubkey,\n relays: qs.getAll(\"relay\"),\n secret: qs.get(\"secret\") ?? undefined,\n };\n } catch {\n return undefined;\n }\n}\n\n/** Build a client-initiated `nostrconnect://` URI. */\nexport function createNostrConnectURI(params: NostrConnectParams): string {\n if (!isHex32(params.clientPubkey.toLowerCase())) {\n throw new Nip46Error(\"invalid client pubkey\");\n }\n if (!params.secret) {\n throw new Nip46Error(\"nostrconnect secret is required\");\n }\n if (params.relays.length === 0) {\n throw new Nip46Error(\"at least one relay is required\");\n }\n\n const query = new URLSearchParams();\n for (const relay of params.relays) {\n query.append(\"relay\", relay);\n }\n query.set(\"secret\", params.secret);\n if (params.perms !== undefined && params.perms.length > 0) {\n query.set(\"perms\", params.perms.join(\",\"));\n }\n if (params.name !== undefined && params.name !== \"\") {\n query.set(\"name\", params.name);\n }\n if (params.url !== undefined && params.url !== \"\") {\n query.set(\"url\", params.url);\n }\n if (params.image !== undefined && params.image !== \"\") {\n query.set(\"image\", params.image);\n }\n\n return `nostrconnect://${params.clientPubkey.toLowerCase()}?${query.toString()}`;\n}\n\n/** Parse a `nostrconnect://` URI. */\nexport function parseNostrConnectURI(uri: string): NostrConnectParams {\n let url: URL;\n try {\n url = new URL(uri);\n } catch (error) {\n throw new Nip46Error(`invalid nostrconnect URI: ${uri}`, {\n cause: error instanceof Error ? error : undefined,\n });\n }\n if (url.protocol !== \"nostrconnect:\") {\n throw new Nip46Error(`expected nostrconnect: scheme, got ${url.protocol}`);\n }\n const clientPubkey = url.hostname || url.pathname.replace(/^\\/*/, \"\");\n if (!isHex32(clientPubkey.toLowerCase())) {\n throw new Nip46Error(\"invalid client pubkey in nostrconnect URI\");\n }\n const secret = url.searchParams.get(\"secret\");\n if (secret === null || secret === \"\") {\n throw new Nip46Error(\"missing secret in nostrconnect URI\");\n }\n const relays = url.searchParams.getAll(\"relay\");\n if (relays.length === 0) {\n throw new Nip46Error(\"missing relays in nostrconnect URI\");\n }\n const params: NostrConnectParams = {\n clientPubkey: clientPubkey.toLowerCase(),\n relays,\n secret,\n };\n const permsRaw = url.searchParams.get(\"perms\");\n if (permsRaw !== null && permsRaw !== \"\") {\n params.perms = permsRaw.split(\",\").filter((p) => p !== \"\");\n }\n const name = url.searchParams.get(\"name\");\n if (name !== null) {\n params.name = name;\n }\n const metadataUrl = url.searchParams.get(\"url\");\n if (metadataUrl !== null) {\n params.url = metadataUrl;\n }\n const image = url.searchParams.get(\"image\");\n if (image !== null) {\n params.image = image;\n }\n return params;\n}\n\nexport function encodeNip46Request(req: Nip46Request): string {\n return JSON.stringify({ id: req.id, method: req.method, params: req.params });\n}\n\nexport function decodeNip46Request(json: string): Nip46Request {\n let data: unknown;\n try {\n data = JSON.parse(json);\n } catch (error) {\n throw new Nip46Error(\"invalid NIP-46 request JSON\", {\n cause: error instanceof Error ? error : undefined,\n });\n }\n if (!isRecord(data) || typeof data[\"id\"] !== \"string\" || typeof data[\"method\"] !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 request shape\");\n }\n if (!Array.isArray(data[\"params\"]) || !data[\"params\"].every((p) => typeof p === \"string\")) {\n throw new Nip46Error(\"invalid NIP-46 request params\");\n }\n return { id: data[\"id\"], method: data[\"method\"], params: data[\"params\"] };\n}\n\nexport function encodeNip46Response(res: Nip46Response): string {\n const body: Record<string, string> = { id: res.id };\n if (res.result !== undefined) {\n body[\"result\"] = res.result;\n }\n if (res.error !== undefined) {\n body[\"error\"] = res.error;\n }\n return JSON.stringify(body);\n}\n\nexport function decodeNip46Response(json: string): Nip46Response {\n let data: unknown;\n try {\n data = JSON.parse(json);\n } catch (error) {\n throw new Nip46Error(\"invalid NIP-46 response JSON\", {\n cause: error instanceof Error ? error : undefined,\n });\n }\n if (!isRecord(data) || typeof data[\"id\"] !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response shape\");\n }\n // Some remote signers send explicit nulls for absent fields.\n const result = data[\"result\"] === null ? undefined : data[\"result\"];\n const error = data[\"error\"] === null ? undefined : data[\"error\"];\n if (result !== undefined && typeof result !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response result\");\n }\n if (error !== undefined && typeof error !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response error\");\n }\n const response: Nip46Response = { id: data[\"id\"] };\n if (result !== undefined) {\n response.result = result;\n }\n if (error !== undefined) {\n response.error = error;\n }\n return response;\n}\n"],"mappings":";;;;;;;;AAUA,MAAa,eAAuB;AAkCpC,IAAa,aAAb,cAAgC,WAAW;CACzC,OAAgB;CAChB,YAAY,SAAiB,SAAwB;EACnD,MAAM,SAAS,OAAO;CACxB;AACF;;AAGA,SAAgB,YAAY,SAAgC;CAC1D,MAAM,MAAM,IAAI,IAAI,YAAY,QAAQ,OAAO,YAAY,GAAG;CAC9D,KAAK,MAAM,SAAS,QAAQ,QAC1B,IAAI,aAAa,OAAO,SAAS,KAAK;CAExC,IAAI,QAAQ,WAAW,KAAA,KAAa,QAAQ,WAAW,IACrD,IAAI,aAAa,IAAI,UAAU,QAAQ,MAAM;CAE/C,OAAO,IAAI,SAAS;AACtB;;;;;AAMA,SAAgB,eAAe,OAA0C;CACvE,MAAM,QAAQ,aAAa,KAAK,MAAM,KAAK,CAAC;CAC5C,MAAM,MAAM,OAAO,GAAG,CAAC;CACvB,IAAI,UAAU,QAAQ,QAAQ,KAAA,GAC5B;CAEF,IAAI;EACF,MAAM,SAAS,YAAY,KAAK,eAAe;EAC/C,MAAM,KAAK,IAAI,gBAAgB,MAAM,GAAG,CAAC,KAAK,EAAE;EAChD,OAAO;GACL;GACA,QAAQ,GAAG,OAAO,OAAO;GACzB,QAAQ,GAAG,IAAI,QAAQ,KAAK,KAAA;EAC9B;CACF,QAAQ;EACN;CACF;AACF;;AAGA,SAAgB,sBAAsB,QAAoC;CACxE,IAAI,CAAC,QAAQ,OAAO,aAAa,YAAY,CAAC,GAC5C,MAAM,IAAI,WAAW,uBAAuB;CAE9C,IAAI,CAAC,OAAO,QACV,MAAM,IAAI,WAAW,iCAAiC;CAExD,IAAI,OAAO,OAAO,WAAW,GAC3B,MAAM,IAAI,WAAW,gCAAgC;CAGvD,MAAM,QAAQ,IAAI,gBAAgB;CAClC,KAAK,MAAM,SAAS,OAAO,QACzB,MAAM,OAAO,SAAS,KAAK;CAE7B,MAAM,IAAI,UAAU,OAAO,MAAM;CACjC,IAAI,OAAO,UAAU,KAAA,KAAa,OAAO,MAAM,SAAS,GACtD,MAAM,IAAI,SAAS,OAAO,MAAM,KAAK,GAAG,CAAC;CAE3C,IAAI,OAAO,SAAS,KAAA,KAAa,OAAO,SAAS,IAC/C,MAAM,IAAI,QAAQ,OAAO,IAAI;CAE/B,IAAI,OAAO,QAAQ,KAAA,KAAa,OAAO,QAAQ,IAC7C,MAAM,IAAI,OAAO,OAAO,GAAG;CAE7B,IAAI,OAAO,UAAU,KAAA,KAAa,OAAO,UAAU,IACjD,MAAM,IAAI,SAAS,OAAO,KAAK;CAGjC,OAAO,kBAAkB,OAAO,aAAa,YAAY,EAAE,GAAG,MAAM,SAAS;AAC/E;;AAGA,SAAgB,qBAAqB,KAAiC;CACpE,IAAI;CACJ,IAAI;EACF,MAAM,IAAI,IAAI,GAAG;CACnB,SAAS,OAAO;EACd,MAAM,IAAI,WAAW,6BAA6B,OAAO,EACvD,OAAO,iBAAiB,QAAQ,QAAQ,KAAA,EAC1C,CAAC;CACH;CACA,IAAI,IAAI,aAAa,iBACnB,MAAM,IAAI,WAAW,sCAAsC,IAAI,UAAU;CAE3E,MAAM,eAAe,IAAI,YAAY,IAAI,SAAS,QAAQ,QAAQ,EAAE;CACpE,IAAI,CAAC,QAAQ,aAAa,YAAY,CAAC,GACrC,MAAM,IAAI,WAAW,2CAA2C;CAElE,MAAM,SAAS,IAAI,aAAa,IAAI,QAAQ;CAC5C,IAAI,WAAW,QAAQ,WAAW,IAChC,MAAM,IAAI,WAAW,oCAAoC;CAE3D,MAAM,SAAS,IAAI,aAAa,OAAO,OAAO;CAC9C,IAAI,OAAO,WAAW,GACpB,MAAM,IAAI,WAAW,oCAAoC;CAE3D,MAAM,SAA6B;EACjC,cAAc,aAAa,YAAY;EACvC;EACA;CACF;CACA,MAAM,WAAW,IAAI,aAAa,IAAI,OAAO;CAC7C,IAAI,aAAa,QAAQ,aAAa,IACpC,OAAO,QAAQ,SAAS,MAAM,GAAG,CAAC,CAAC,QAAQ,MAAM,MAAM,EAAE;CAE3D,MAAM,OAAO,IAAI,aAAa,IAAI,MAAM;CACxC,IAAI,SAAS,MACX,OAAO,OAAO;CAEhB,MAAM,cAAc,IAAI,aAAa,IAAI,KAAK;CAC9C,IAAI,gBAAgB,MAClB,OAAO,MAAM;CAEf,MAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;CAC1C,IAAI,UAAU,MACZ,OAAO,QAAQ;CAEjB,OAAO;AACT;AAEA,SAAgB,mBAAmB,KAA2B;CAC5D,OAAO,KAAK,UAAU;EAAE,IAAI,IAAI;EAAI,QAAQ,IAAI;EAAQ,QAAQ,IAAI;CAAO,CAAC;AAC9E;AAEA,SAAgB,mBAAmB,MAA4B;CAC7D,IAAI;CACJ,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,SAAS,OAAO;EACd,MAAM,IAAI,WAAW,+BAA+B,EAClD,OAAO,iBAAiB,QAAQ,QAAQ,KAAA,EAC1C,CAAC;CACH;CACA,IAAI,CAAC,SAAS,IAAI,KAAK,OAAO,KAAK,UAAU,YAAY,OAAO,KAAK,cAAc,UACjF,MAAM,IAAI,WAAW,8BAA8B;CAErD,IAAI,CAAC,MAAM,QAAQ,KAAK,SAAS,KAAK,CAAC,KAAK,SAAS,CAAC,OAAO,MAAM,OAAO,MAAM,QAAQ,GACtF,MAAM,IAAI,WAAW,+BAA+B;CAEtD,OAAO;EAAE,IAAI,KAAK;EAAO,QAAQ,KAAK;EAAW,QAAQ,KAAK;CAAU;AAC1E;AAEA,SAAgB,oBAAoB,KAA4B;CAC9D,MAAM,OAA+B,EAAE,IAAI,IAAI,GAAG;CAClD,IAAI,IAAI,WAAW,KAAA,GACjB,KAAK,YAAY,IAAI;CAEvB,IAAI,IAAI,UAAU,KAAA,GAChB,KAAK,WAAW,IAAI;CAEtB,OAAO,KAAK,UAAU,IAAI;AAC5B;AAEA,SAAgB,oBAAoB,MAA6B;CAC/D,IAAI;CACJ,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,SAAS,OAAO;EACd,MAAM,IAAI,WAAW,gCAAgC,EACnD,OAAO,iBAAiB,QAAQ,QAAQ,KAAA,EAC1C,CAAC;CACH;CACA,IAAI,CAAC,SAAS,IAAI,KAAK,OAAO,KAAK,UAAU,UAC3C,MAAM,IAAI,WAAW,+BAA+B;CAGtD,MAAM,SAAS,KAAK,cAAc,OAAO,KAAA,IAAY,KAAK;CAC1D,MAAM,QAAQ,KAAK,aAAa,OAAO,KAAA,IAAY,KAAK;CACxD,IAAI,WAAW,KAAA,KAAa,OAAO,WAAW,UAC5C,MAAM,IAAI,WAAW,gCAAgC;CAEvD,IAAI,UAAU,KAAA,KAAa,OAAO,UAAU,UAC1C,MAAM,IAAI,WAAW,+BAA+B;CAEtD,MAAM,WAA0B,EAAE,IAAI,KAAK,MAAM;CACjD,IAAI,WAAW,KAAA,GACb,SAAS,SAAS;CAEpB,IAAI,UAAU,KAAA,GACZ,SAAS,QAAQ;CAEnB,OAAO;AACT"}
1
+ {"version":3,"file":"nip46.mjs","names":[],"sources":["../../src/nips/nip46.ts"],"sourcesContent":["/**\n * NIP-46 (Nostr Connect) protocol helpers: bunker URI, nostrconnect URI, RPC JSON.\n * Transport/signing live in {@link Nip46Signer}.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/46.md\n */\nimport { NostrError } from \"../core/error.ts\";\nimport { isHex32, isRecord } from \"../core/util.ts\";\n\nexport type BunkerPointer = {\n /** Remote signer / bunker public key (hex). */\n pubkey: string;\n relays: string[];\n secret: string | undefined;\n};\n\nexport type ClientMetadata = {\n name?: string | undefined;\n url?: string | undefined;\n image?: string | undefined;\n};\n\nexport type NostrConnectParams = {\n clientPubkey: string;\n relays: string[];\n secret: string;\n perms?: string[] | undefined;\n} & ClientMetadata;\n\nexport type Nip46Request = {\n id: string;\n method: string;\n params: string[];\n};\n\nexport type Nip46Response = {\n id: string;\n result?: string | undefined;\n error?: string | undefined;\n};\n\nexport class Nip46Error extends NostrError {\n override name = \"Nip46Error\";\n}\n\n/** Encode a bunker pointer as `bunker://…`. */\nexport function toBunkerURL(pointer: BunkerPointer): string {\n const url = new URL(`bunker://${pointer.pubkey.toLowerCase()}`);\n for (const relay of pointer.relays) {\n url.searchParams.append(\"relay\", relay);\n }\n if (pointer.secret !== undefined && pointer.secret !== \"\") {\n url.searchParams.set(\"secret\", pointer.secret);\n }\n return url.toString();\n}\n\n/**\n * Parse a `bunker://` URL into a pointer. Returns undefined when the input is not a bunker URL\n * (including NIP-05 identifiers).\n */\nexport function parseBunkerURL(input: string): BunkerPointer | undefined {\n let url: URL;\n try {\n url = new URL(input.trim());\n } catch {\n return undefined;\n }\n if (url.protocol !== \"bunker:\") {\n return undefined;\n }\n const pubkey = url.hostname || url.pathname.replace(/^\\/*/, \"\");\n if (!isHex32(pubkey.toLowerCase())) {\n return undefined;\n }\n return {\n pubkey: pubkey.toLowerCase(),\n relays: url.searchParams.getAll(\"relay\"),\n secret: url.searchParams.get(\"secret\") ?? undefined,\n };\n}\n\n/** Build a client-initiated `nostrconnect://` URI. */\nexport function createNostrConnectURI(params: NostrConnectParams): string {\n if (!isHex32(params.clientPubkey.toLowerCase())) {\n throw new Nip46Error(\"invalid client pubkey\");\n }\n if (!params.secret) {\n throw new Nip46Error(\"nostrconnect secret is required\");\n }\n if (params.relays.length === 0) {\n throw new Nip46Error(\"at least one relay is required\");\n }\n\n const query = new URLSearchParams();\n for (const relay of params.relays) {\n query.append(\"relay\", relay);\n }\n query.set(\"secret\", params.secret);\n if (params.perms !== undefined && params.perms.length > 0) {\n query.set(\"perms\", params.perms.join(\",\"));\n }\n if (params.name !== undefined && params.name !== \"\") {\n query.set(\"name\", params.name);\n }\n if (params.url !== undefined && params.url !== \"\") {\n query.set(\"url\", params.url);\n }\n if (params.image !== undefined && params.image !== \"\") {\n query.set(\"image\", params.image);\n }\n\n return `nostrconnect://${params.clientPubkey.toLowerCase()}?${query.toString()}`;\n}\n\n/** Parse a `nostrconnect://` URI. */\nexport function parseNostrConnectURI(uri: string): NostrConnectParams {\n let url: URL;\n try {\n url = new URL(uri);\n } catch (error) {\n throw new Nip46Error(`invalid nostrconnect URI: ${uri}`, {\n cause: error,\n });\n }\n if (url.protocol !== \"nostrconnect:\") {\n throw new Nip46Error(`expected nostrconnect: scheme, got ${url.protocol}`);\n }\n const clientPubkey = url.hostname || url.pathname.replace(/^\\/*/, \"\");\n if (!isHex32(clientPubkey.toLowerCase())) {\n throw new Nip46Error(\"invalid client pubkey in nostrconnect URI\");\n }\n const secret = url.searchParams.get(\"secret\");\n if (secret === null || secret === \"\") {\n throw new Nip46Error(\"missing secret in nostrconnect URI\");\n }\n const relays = url.searchParams.getAll(\"relay\");\n if (relays.length === 0) {\n throw new Nip46Error(\"missing relays in nostrconnect URI\");\n }\n const params: NostrConnectParams = {\n clientPubkey: clientPubkey.toLowerCase(),\n relays,\n secret,\n };\n const permsRaw = url.searchParams.get(\"perms\");\n if (permsRaw !== null && permsRaw !== \"\") {\n params.perms = permsRaw.split(\",\").filter((p) => p !== \"\");\n }\n const name = url.searchParams.get(\"name\");\n if (name !== null) {\n params.name = name;\n }\n const metadataUrl = url.searchParams.get(\"url\");\n if (metadataUrl !== null) {\n params.url = metadataUrl;\n }\n const image = url.searchParams.get(\"image\");\n if (image !== null) {\n params.image = image;\n }\n return params;\n}\n\nexport function encodeNip46Request(req: Nip46Request): string {\n return JSON.stringify({ id: req.id, method: req.method, params: req.params });\n}\n\nexport function decodeNip46Request(json: string): Nip46Request {\n let data: unknown;\n try {\n data = JSON.parse(json);\n } catch (error) {\n throw new Nip46Error(\"invalid NIP-46 request JSON\", {\n cause: error,\n });\n }\n if (!isRecord(data) || typeof data[\"id\"] !== \"string\" || typeof data[\"method\"] !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 request shape\");\n }\n if (!Array.isArray(data[\"params\"]) || !data[\"params\"].every((p) => typeof p === \"string\")) {\n throw new Nip46Error(\"invalid NIP-46 request params\");\n }\n return { id: data[\"id\"], method: data[\"method\"], params: data[\"params\"] };\n}\n\nexport function encodeNip46Response(res: Nip46Response): string {\n const body: Record<string, string> = { id: res.id };\n if (res.result !== undefined) {\n body[\"result\"] = res.result;\n }\n if (res.error !== undefined) {\n body[\"error\"] = res.error;\n }\n return JSON.stringify(body);\n}\n\nexport function decodeNip46Response(json: string): Nip46Response {\n let data: unknown;\n try {\n data = JSON.parse(json);\n } catch (error) {\n throw new Nip46Error(\"invalid NIP-46 response JSON\", {\n cause: error,\n });\n }\n if (!isRecord(data) || typeof data[\"id\"] !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response shape\");\n }\n // Some remote signers send explicit nulls for absent fields.\n const result = data[\"result\"] === null ? undefined : data[\"result\"];\n const error = data[\"error\"] === null ? undefined : data[\"error\"];\n if (result !== undefined && typeof result !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response result\");\n }\n if (error !== undefined && typeof error !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response error\");\n }\n const response: Nip46Response = { id: data[\"id\"] };\n if (result !== undefined) {\n response.result = result;\n }\n if (error !== undefined) {\n response.error = error;\n }\n return response;\n}\n"],"mappings":"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"}
@@ -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/nip49.d.ts
3
3
  export type Ncryptsec = `ncryptsec1${string}`;
4
4
  export type KeySecurityByte = 0x00 | 0x01 | 0x02;
@@ -12,15 +12,15 @@ export type ScryptParams = {
12
12
  export type Scrypt = (password: Uint8Array, salt: Uint8Array, params: ScryptParams) => Promise<Uint8Array>;
13
13
  export type Nip49EncryptOptions = {
14
14
  /** Scrypt log2(N) work factor, 1..22. Defaults to 16. */
15
- logn?: number;
15
+ logn?: number | undefined;
16
16
  /** Key security byte recorded in the payload. Defaults to 0x02. */
17
- ksb?: KeySecurityByte;
17
+ ksb?: KeySecurityByte | undefined;
18
18
  /** Scrypt implementation. Defaults to noble `scryptAsync`. */
19
- scrypt?: Scrypt;
19
+ scrypt?: Scrypt | undefined;
20
20
  };
21
21
  export type Nip49DecryptOptions = {
22
22
  /** Scrypt implementation. Defaults to noble `scryptAsync`. */
23
- scrypt?: Scrypt;
23
+ scrypt?: Scrypt | undefined;
24
24
  };
25
25
  export declare class Nip49Error extends NostrError {
26
26
  name: string;
@@ -1,4 +1,4 @@
1
- import { d as utf8Encoder, g as NostrError, r as assertSecretKeyBytes } from "../util-LfQirgDk.mjs";
1
+ import { p as utf8Encoder, r as assertSecretKeyBytes, v as NostrError } from "../util-yFYzlbrm.mjs";
2
2
  import { Bech32MaxSize, encodeBytes } from "./nip19.mjs";
3
3
  import { concatBytes, randomBytes } from "@noble/hashes/utils.js";
4
4
  import { bech32 } from "@scure/base";
@@ -39,7 +39,7 @@ async function deriveKey(password, salt, logn, kdf) {
39
39
  dkLen: 32
40
40
  });
41
41
  } catch (error) {
42
- throw new Nip49Error("scrypt failed", { cause: error instanceof Error ? error : void 0 });
42
+ throw new Nip49Error("scrypt failed", { cause: error });
43
43
  }
44
44
  if (key.length !== 32) throw new Nip49Error(`scrypt returned ${key.length} bytes, expected 32`);
45
45
  return key;
@@ -65,7 +65,7 @@ async function decrypt(ncryptsec, password, opts) {
65
65
  ({prefix} = decoded);
66
66
  b = new Uint8Array(bech32.fromWords(decoded.words));
67
67
  } catch (error) {
68
- throw new Nip49Error("invalid ncryptsec", { cause: error instanceof Error ? error : void 0 });
68
+ throw new Nip49Error("invalid ncryptsec", { cause: error });
69
69
  }
70
70
  if (prefix !== "ncryptsec") throw new Nip49Error(`invalid prefix ${prefix}, expected 'ncryptsec'`);
71
71
  if (b.length !== PAYLOAD_LEN) throw new Nip49Error("invalid ncryptsec length");
@@ -85,7 +85,7 @@ async function decrypt(ncryptsec, password, opts) {
85
85
  return xchacha20poly1305(key, nonce, aad).decrypt(ciphertext);
86
86
  } catch (error) {
87
87
  if (error instanceof Nip49Error) throw error;
88
- throw new Nip49Error("failed to decrypt", { cause: error instanceof Error ? error : void 0 });
88
+ throw new Nip49Error("failed to decrypt", { cause: error });
89
89
  }
90
90
  }
91
91
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"nip49.mjs","names":[],"sources":["../../src/nips/nip49.ts"],"sourcesContent":["/**\n * NIP-49: Private Key Encryption (`ncryptsec`).\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/49.md\n */\nimport { xchacha20poly1305 } from \"@noble/ciphers/chacha.js\";\nimport { scryptAsync } from \"@noble/hashes/scrypt.js\";\nimport { concatBytes, randomBytes } from \"@noble/hashes/utils.js\";\nimport { bech32 } from \"@scure/base\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport { assertSecretKeyBytes, utf8Encoder } from \"../core/util.ts\";\nimport { Bech32MaxSize, encodeBytes } from \"./nip19.ts\";\n\nexport type Ncryptsec = `ncryptsec1${string}`;\nexport type KeySecurityByte = 0x00 | 0x01 | 0x02;\n\nexport type ScryptParams = { N: number; r: number; p: number; dkLen: number };\n\n/** Derive `params.dkLen` bytes from the NFKC-normalized UTF-8 password and salt (RFC 7914). */\nexport type Scrypt = (\n password: Uint8Array,\n salt: Uint8Array,\n params: ScryptParams,\n) => Promise<Uint8Array>;\n\nexport type Nip49EncryptOptions = {\n /** Scrypt log2(N) work factor, 1..22. Defaults to 16. */\n logn?: number;\n /** Key security byte recorded in the payload. Defaults to 0x02. */\n ksb?: KeySecurityByte;\n /** Scrypt implementation. Defaults to noble `scryptAsync`. */\n scrypt?: Scrypt;\n};\n\nexport type Nip49DecryptOptions = {\n /** Scrypt implementation. Defaults to noble `scryptAsync`. */\n scrypt?: Scrypt;\n};\n\nconst VERSION = 0x02;\nconst SALT_LEN = 16;\nconst NONCE_LEN = 24;\nconst PAYLOAD_LEN = 91;\nconst LOGN_MIN = 1;\nconst LOGN_MAX = 22;\nconst SCRYPT_R = 8;\nconst SCRYPT_P = 1;\n\nexport class Nip49Error extends NostrError {\n override name = \"Nip49Error\";\n}\n\nfunction assertLogn(logn: number): void {\n if (!Number.isInteger(logn) || logn < LOGN_MIN || logn > LOGN_MAX) {\n throw new Nip49Error(`invalid logn ${logn}, expected integer ${LOGN_MIN}..${LOGN_MAX}`);\n }\n}\n\n// noble 2.3: V (N blocks) + p B blocks + one tmp scratch block.\nconst nobleScrypt: Scrypt = async (password, salt, params) =>\n scryptAsync(password, salt, {\n ...params,\n maxmem: 128 * params.r * (params.N + params.p + 1),\n });\n\nasync function deriveKey(\n password: string,\n salt: Uint8Array,\n logn: number,\n kdf: Scrypt,\n): Promise<Uint8Array> {\n assertLogn(logn);\n let key: Uint8Array;\n try {\n key = await kdf(utf8Encoder.encode(password.normalize(\"NFKC\")), salt, {\n N: 2 ** logn,\n r: SCRYPT_R,\n p: SCRYPT_P,\n dkLen: 32,\n });\n } catch (error) {\n throw new Nip49Error(\"scrypt failed\", {\n cause: error instanceof Error ? error : undefined,\n });\n }\n if (key.length !== 32) {\n throw new Nip49Error(`scrypt returned ${key.length} bytes, expected 32`);\n }\n return key;\n}\n\n/** Encrypt a 32-byte secret key to an `ncryptsec` bech32 string. */\nexport async function encrypt(\n secretKey: Uint8Array,\n password: string,\n opts?: Nip49EncryptOptions,\n): Promise<Ncryptsec> {\n assertSecretKeyBytes(secretKey);\n const logn = opts?.logn ?? 16;\n const salt = randomBytes(SALT_LEN);\n const key = await deriveKey(password, salt, logn, opts?.scrypt ?? nobleScrypt);\n const nonce = randomBytes(NONCE_LEN);\n const aad = Uint8Array.from([opts?.ksb ?? 0x02]);\n const ciphertext = xchacha20poly1305(key, nonce, aad).encrypt(secretKey);\n const bytes = concatBytes(\n Uint8Array.from([VERSION]),\n Uint8Array.from([logn]),\n salt,\n nonce,\n aad,\n ciphertext,\n );\n return encodeBytes(\"ncryptsec\", bytes);\n}\n\n/** Decrypt an `ncryptsec` bech32 string to a 32-byte secret key. */\nexport async function decrypt(\n ncryptsec: string,\n password: string,\n opts?: Nip49DecryptOptions,\n): Promise<Uint8Array> {\n let prefix: string;\n let b: Uint8Array;\n try {\n const decoded = bech32.decode(ncryptsec, Bech32MaxSize);\n ({ prefix } = decoded);\n b = new Uint8Array(bech32.fromWords(decoded.words));\n } catch (error) {\n throw new Nip49Error(\"invalid ncryptsec\", {\n cause: error instanceof Error ? error : undefined,\n });\n }\n if (prefix !== \"ncryptsec\") {\n throw new Nip49Error(`invalid prefix ${prefix}, expected 'ncryptsec'`);\n }\n if (b.length !== PAYLOAD_LEN) {\n throw new Nip49Error(\"invalid ncryptsec length\");\n }\n const version = b.at(0);\n if (version === undefined) {\n throw new Nip49Error(\"invalid ncryptsec length\");\n }\n if (version !== VERSION) {\n throw new Nip49Error(`invalid version ${version}, expected 0x02`);\n }\n const logn = b.at(1);\n if (logn === undefined) {\n throw new Nip49Error(\"invalid ncryptsec length\");\n }\n const salt = b.subarray(2, 2 + SALT_LEN);\n const nonce = b.subarray(2 + SALT_LEN, 2 + SALT_LEN + NONCE_LEN);\n const ksb = b.at(2 + SALT_LEN + NONCE_LEN);\n if (ksb === undefined) {\n throw new Nip49Error(\"invalid ncryptsec length\");\n }\n const aad = Uint8Array.from([ksb]);\n const ciphertext = b.subarray(2 + SALT_LEN + NONCE_LEN + 1);\n try {\n const key = await deriveKey(password, salt, logn, opts?.scrypt ?? nobleScrypt);\n return xchacha20poly1305(key, nonce, aad).decrypt(ciphertext);\n } catch (error) {\n if (error instanceof Nip49Error) {\n throw error;\n }\n throw new Nip49Error(\"failed to decrypt\", {\n cause: error instanceof Error ? error : undefined,\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"nip49.mjs","names":[],"sources":["../../src/nips/nip49.ts"],"sourcesContent":["/**\n * NIP-49: Private Key Encryption (`ncryptsec`).\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/49.md\n */\nimport { xchacha20poly1305 } from \"@noble/ciphers/chacha.js\";\nimport { scryptAsync } from \"@noble/hashes/scrypt.js\";\nimport { concatBytes, randomBytes } from \"@noble/hashes/utils.js\";\nimport { bech32 } from \"@scure/base\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport { assertSecretKeyBytes, utf8Encoder } from \"../core/util.ts\";\nimport { Bech32MaxSize, encodeBytes } from \"./nip19.ts\";\n\nexport type Ncryptsec = `ncryptsec1${string}`;\nexport type KeySecurityByte = 0x00 | 0x01 | 0x02;\n\nexport type ScryptParams = { N: number; r: number; p: number; dkLen: number };\n\n/** Derive `params.dkLen` bytes from the NFKC-normalized UTF-8 password and salt (RFC 7914). */\nexport type Scrypt = (\n password: Uint8Array,\n salt: Uint8Array,\n params: ScryptParams,\n) => Promise<Uint8Array>;\n\nexport type Nip49EncryptOptions = {\n /** Scrypt log2(N) work factor, 1..22. Defaults to 16. */\n logn?: number | undefined;\n /** Key security byte recorded in the payload. Defaults to 0x02. */\n ksb?: KeySecurityByte | undefined;\n /** Scrypt implementation. Defaults to noble `scryptAsync`. */\n scrypt?: Scrypt | undefined;\n};\n\nexport type Nip49DecryptOptions = {\n /** Scrypt implementation. Defaults to noble `scryptAsync`. */\n scrypt?: Scrypt | undefined;\n};\n\nconst VERSION = 0x02;\nconst SALT_LEN = 16;\nconst NONCE_LEN = 24;\nconst PAYLOAD_LEN = 91;\nconst LOGN_MIN = 1;\nconst LOGN_MAX = 22;\nconst SCRYPT_R = 8;\nconst SCRYPT_P = 1;\n\nexport class Nip49Error extends NostrError {\n override name = \"Nip49Error\";\n}\n\nfunction assertLogn(logn: number): void {\n if (!Number.isInteger(logn) || logn < LOGN_MIN || logn > LOGN_MAX) {\n throw new Nip49Error(`invalid logn ${logn}, expected integer ${LOGN_MIN}..${LOGN_MAX}`);\n }\n}\n\n// noble 2.3: V (N blocks) + p B blocks + one tmp scratch block.\nconst nobleScrypt: Scrypt = async (password, salt, params) =>\n scryptAsync(password, salt, {\n ...params,\n maxmem: 128 * params.r * (params.N + params.p + 1),\n });\n\nasync function deriveKey(\n password: string,\n salt: Uint8Array,\n logn: number,\n kdf: Scrypt,\n): Promise<Uint8Array> {\n assertLogn(logn);\n let key: Uint8Array;\n try {\n key = await kdf(utf8Encoder.encode(password.normalize(\"NFKC\")), salt, {\n N: 2 ** logn,\n r: SCRYPT_R,\n p: SCRYPT_P,\n dkLen: 32,\n });\n } catch (error) {\n throw new Nip49Error(\"scrypt failed\", {\n cause: error,\n });\n }\n if (key.length !== 32) {\n throw new Nip49Error(`scrypt returned ${key.length} bytes, expected 32`);\n }\n return key;\n}\n\n/** Encrypt a 32-byte secret key to an `ncryptsec` bech32 string. */\nexport async function encrypt(\n secretKey: Uint8Array,\n password: string,\n opts?: Nip49EncryptOptions,\n): Promise<Ncryptsec> {\n assertSecretKeyBytes(secretKey);\n const logn = opts?.logn ?? 16;\n const salt = randomBytes(SALT_LEN);\n const key = await deriveKey(password, salt, logn, opts?.scrypt ?? nobleScrypt);\n const nonce = randomBytes(NONCE_LEN);\n const aad = Uint8Array.from([opts?.ksb ?? 0x02]);\n const ciphertext = xchacha20poly1305(key, nonce, aad).encrypt(secretKey);\n const bytes = concatBytes(\n Uint8Array.from([VERSION]),\n Uint8Array.from([logn]),\n salt,\n nonce,\n aad,\n ciphertext,\n );\n return encodeBytes(\"ncryptsec\", bytes);\n}\n\n/** Decrypt an `ncryptsec` bech32 string to a 32-byte secret key. */\nexport async function decrypt(\n ncryptsec: string,\n password: string,\n opts?: Nip49DecryptOptions,\n): Promise<Uint8Array> {\n let prefix: string;\n let b: Uint8Array;\n try {\n const decoded = bech32.decode(ncryptsec, Bech32MaxSize);\n ({ prefix } = decoded);\n b = new Uint8Array(bech32.fromWords(decoded.words));\n } catch (error) {\n throw new Nip49Error(\"invalid ncryptsec\", {\n cause: error,\n });\n }\n if (prefix !== \"ncryptsec\") {\n throw new Nip49Error(`invalid prefix ${prefix}, expected 'ncryptsec'`);\n }\n if (b.length !== PAYLOAD_LEN) {\n throw new Nip49Error(\"invalid ncryptsec length\");\n }\n const version = b.at(0);\n if (version === undefined) {\n throw new Nip49Error(\"invalid ncryptsec length\");\n }\n if (version !== VERSION) {\n throw new Nip49Error(`invalid version ${version}, expected 0x02`);\n }\n const logn = b.at(1);\n if (logn === undefined) {\n throw new Nip49Error(\"invalid ncryptsec length\");\n }\n const salt = b.subarray(2, 2 + SALT_LEN);\n const nonce = b.subarray(2 + SALT_LEN, 2 + SALT_LEN + NONCE_LEN);\n const ksb = b.at(2 + SALT_LEN + NONCE_LEN);\n if (ksb === undefined) {\n throw new Nip49Error(\"invalid ncryptsec length\");\n }\n const aad = Uint8Array.from([ksb]);\n const ciphertext = b.subarray(2 + SALT_LEN + NONCE_LEN + 1);\n try {\n const key = await deriveKey(password, salt, logn, opts?.scrypt ?? nobleScrypt);\n return xchacha20poly1305(key, nonce, aad).decrypt(ciphertext);\n } catch (error) {\n if (error instanceof Nip49Error) {\n throw error;\n }\n throw new Nip49Error(\"failed to decrypt\", {\n cause: error,\n });\n }\n}\n"],"mappings":"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"}
@@ -1,86 +1,2 @@
1
- import { t as Event } from "../event-fU3Y01PR.mjs";
2
- import { t as EventBuilder } from "../builder-sSflu6nB.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
- export type Nip51Crypto = {
9
- getPublicKey: () => Promise<string>;
10
- nip44Encrypt: (peer: string, plaintext: string) => Promise<string>;
11
- nip44Decrypt: (peer: string, payload: string) => Promise<string>;
12
- };
13
- export 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
- export declare function parseMuteList(event: Pick<Event, "kind" | "tags">): MuteItem[];
28
- /** Build an unsigned kind:10000 EventBuilder from public mute items. */
29
- export declare function muteListEventBuilder(items: ReadonlyArray<MuteItem>): EventBuilder;
30
- /** NIP-44 ciphertext of tags, conversation peer = author's own pubkey. */
31
- export declare function encryptPrivateTags(crypto: Nip51Crypto, tags: Event["tags"]): Promise<string>;
32
- /** Decrypt NIP-51 `.content`. Empty content is no private tags, not ciphertext. */
33
- export 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
- export 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
- export declare function parsePinList(event: Pick<Event, "kind" | "tags">): string[];
41
- /** Build an unsigned kind:10001 EventBuilder from event ids. */
42
- export declare function pinListEventBuilder(ids: ReadonlyArray<string>): EventBuilder;
43
- /** Parse kind:10003 bookmark list public `e` and `a` tags. */
44
- export 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
- export 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
- export 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
- export 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
- export 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
- export 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
- export declare function parseFollowPack(event: Pick<Event, "kind" | "tags">): {
82
- d: string;
83
- pubkeys: string[];
84
- };
85
- //#endregion
86
- //# sourceMappingURL=nip51.d.mts.map
1
+ import { a as encryptPrivateTags, c as parseEmojiSet, d as parseMuteList, f as parseMuteListPrivate, g as pinListEventBuilder, h as parseUserEmojiList, i as decryptPrivateTags, l as parseFavoriteRelays, m as parseRelaySet, n as Nip51Crypto, o as muteListEventBuilder, p as parsePinList, r as bookmarkListEventBuilder, s as parseBookmarkList, t as MuteItem, u as parseFollowPack } from "../nip51-DoPUvQ--.mjs";
2
+ export { MuteItem, Nip51Crypto, bookmarkListEventBuilder, decryptPrivateTags, encryptPrivateTags, muteListEventBuilder, parseBookmarkList, parseEmojiSet, parseFavoriteRelays, parseFollowPack, parseMuteList, parseMuteListPrivate, parsePinList, parseRelaySet, parseUserEmojiList, pinListEventBuilder };
@@ -1,7 +1,7 @@
1
- import { l as normalizeURL, n as assertHex32, o as isHex32, p as EventValidationError } from "../util-LfQirgDk.mjs";
1
+ import { h as EventValidationError, l as normalizeRelayUrls, n as assertHex32, o as isHex32 } from "../util-yFYzlbrm.mjs";
2
2
  import { t as Kind } from "../kind-Cxwhhkx8.mjs";
3
- import { h as Tag, v as getDTag, y as isTag } from "../event-D8Nfoh4K.mjs";
4
- import { t as EventBuilder } from "../builder-9tjI6WQ3.mjs";
3
+ import { _ as firstTagValue, b as isTag, h as Tag, y as getDTag } from "../event-DinQMmJ1.mjs";
4
+ import { t as EventBuilder } from "../builder-CdLE4J6O.mjs";
5
5
  //#region src/nips/nip51.ts
6
6
  /**
7
7
  * NIP-51: Lists Public tags plus NIP-44 private `.content` (author encrypts to self). Crypto is
@@ -14,22 +14,12 @@ function requireKind(event, kind) {
14
14
  if (event.kind !== kind) throw new EventValidationError(`expected kind ${kind}, got ${event.kind}`);
15
15
  }
16
16
  function collectRelays(tags) {
17
- const out = [];
18
- const seen = /* @__PURE__ */ new Set();
17
+ const urls = [];
19
18
  for (const tag of tags) {
20
19
  const value = tag.at(1);
21
- if (tag[0] !== "relay" || value === void 0 || value === "") continue;
22
- let url;
23
- try {
24
- url = normalizeURL(value);
25
- } catch {
26
- continue;
27
- }
28
- if (seen.has(url)) continue;
29
- seen.add(url);
30
- out.push(url);
20
+ if (tag[0] === "relay" && value !== void 0) urls.push(value);
31
21
  }
32
- return out;
22
+ return normalizeRelayUrls(urls);
33
23
  }
34
24
  function collectEmoji(tags) {
35
25
  const out = [];
@@ -45,12 +35,6 @@ function collectEmoji(tags) {
45
35
  }
46
36
  return out;
47
37
  }
48
- function firstTagValue(tags, name) {
49
- for (const tag of tags) {
50
- const value = tag.at(1);
51
- if (tag[0] === name && value !== void 0 && value !== "") return value;
52
- }
53
- }
54
38
  function identifier(tags) {
55
39
  return getDTag(tags) ?? "";
56
40
  }
@@ -234,7 +218,7 @@ function parseEmojiSet(event) {
234
218
  emoji: collectEmoji(event.tags)
235
219
  };
236
220
  const title = firstTagValue(event.tags, "title");
237
- if (title !== void 0) result.title = title;
221
+ if (title !== void 0 && title !== "") result.title = title;
238
222
  return result;
239
223
  }
240
224
  /** Parse kind:39089 starter pack (follow pack) public `d` and `p` tags. */