@qntx/nostr 0.7.0 → 0.8.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 (147) hide show
  1. package/dist/{builder-DgkqDylK.d.mts → builder-CTwkB2zM.d.mts} +10 -10
  2. package/dist/{builder-CdLE4J6O.mjs → builder-aNU2gGFP.mjs} +4 -4
  3. package/dist/builder-aNU2gGFP.mjs.map +1 -0
  4. package/dist/{client-CILhdfsF.mjs → client-Cj2lUaVh.mjs} +70 -72
  5. package/dist/client-Cj2lUaVh.mjs.map +1 -0
  6. package/dist/client.d.mts +1 -1
  7. package/dist/client.mjs +1 -1
  8. package/dist/core.d.mts +2 -2
  9. package/dist/core.mjs +6 -6
  10. package/dist/emitter-DkCY6NkU.mjs +32 -0
  11. package/dist/emitter-DkCY6NkU.mjs.map +1 -0
  12. package/dist/{error-L_QdmSnV.mjs → error-BILMe3Gr.mjs} +2 -2
  13. package/dist/{error-L_QdmSnV.mjs.map → error-BILMe3Gr.mjs.map} +1 -1
  14. package/dist/{error-gtJvV6YS.mjs → error-DlhGW4mJ.mjs} +2 -2
  15. package/dist/{error-gtJvV6YS.mjs.map → error-DlhGW4mJ.mjs.map} +1 -1
  16. package/dist/{event-DinQMmJ1.mjs → event-CHf47dPM.mjs} +2 -2
  17. package/dist/{event-DinQMmJ1.mjs.map → event-CHf47dPM.mjs.map} +1 -1
  18. package/dist/{gossip-CcTp9VpU.mjs → gossip-DFxIBlsr.mjs} +3 -3
  19. package/dist/gossip-DFxIBlsr.mjs.map +1 -0
  20. package/dist/{gossip-C7HLW5kI.d.mts → gossip-OOeeFzG5.d.mts} +4 -4
  21. package/dist/gossip.d.mts +1 -1
  22. package/dist/gossip.mjs +1 -1
  23. package/dist/{index-InQZpHyQ.d.mts → index-BxCBX-8R.d.mts} +2 -2
  24. package/dist/{index-iLT8UzY3.d.mts → index-CPVEyzNX.d.mts} +22 -22
  25. package/dist/{index-Tx15HGAD.d.mts → index-C_w2WfVz.d.mts} +4 -4
  26. package/dist/{index-DocgD9K0.d.mts → index-Cl_1XdYP.d.mts} +29 -23
  27. package/dist/{index-DiXwNn2M.d.mts → index-DyKwQnbt.d.mts} +2 -2
  28. package/dist/index.d.mts +14 -14
  29. package/dist/index.mjs +17 -17
  30. package/dist/{instance-BcKzxHIP.mjs → instance-C62w3cEt.mjs} +3 -3
  31. package/dist/{instance-BcKzxHIP.mjs.map → instance-C62w3cEt.mjs.map} +1 -1
  32. package/dist/{key-bshqfmZL.mjs → key-DnVpchl6.mjs} +3 -3
  33. package/dist/{key-bshqfmZL.mjs.map → key-DnVpchl6.mjs.map} +1 -1
  34. package/dist/{loaders-DD0XAGzw.mjs → loaders-BmPkQfzI.mjs} +6 -6
  35. package/dist/loaders-BmPkQfzI.mjs.map +1 -0
  36. package/dist/loaders.d.mts +2 -2
  37. package/dist/loaders.mjs +1 -1
  38. package/dist/{memory-D_-5-T31.mjs → memory-CdJ07kub.mjs} +2 -2
  39. package/dist/memory-CdJ07kub.mjs.map +1 -0
  40. package/dist/{memory-index-C6rujtoc.mjs → memory-index-Cbc83vOZ.mjs} +3 -3
  41. package/dist/{memory-index-C6rujtoc.mjs.map → memory-index-Cbc83vOZ.mjs.map} +1 -1
  42. package/dist/{message-CWLcFTQ1.mjs → message-oNr7t0SJ.mjs} +3 -3
  43. package/dist/message-oNr7t0SJ.mjs.map +1 -0
  44. package/dist/{message-CbBZOhq3.d.mts → message-tclYCVNc.d.mts} +4 -4
  45. package/dist/{nip17-cTDFfFVt.d.mts → nip17-B5WgW4C4.d.mts} +4 -4
  46. package/dist/{nip19-D1OnJ192.d.mts → nip19-DpPN17bY.d.mts} +23 -23
  47. package/dist/{nip46-DrsPI8EX.d.mts → nip46-BHaxC9TZ.d.mts} +17 -17
  48. package/dist/{nip51-DoPUvQ--.d.mts → nip51-CR0Q8eym.d.mts} +10 -10
  49. package/dist/{nip59-BFIihLYY.d.mts → nip59-C-RWFQIv.d.mts} +9 -2
  50. package/dist/{nip65-52HiwiyD.d.mts → nip65-D1fbmhOg.d.mts} +2 -2
  51. package/dist/{nip77-C9QcZn7b.d.mts → nip77-DzUwUxHq.d.mts} +6 -6
  52. package/dist/nips/blossom.d.mts +6 -6
  53. package/dist/nips/blossom.mjs +2 -2
  54. package/dist/nips/blossom.mjs.map +1 -1
  55. package/dist/nips/nip04.mjs +1 -1
  56. package/dist/nips/nip05.d.mts +8 -8
  57. package/dist/nips/nip05.mjs +1 -1
  58. package/dist/nips/nip05.mjs.map +1 -1
  59. package/dist/nips/nip10.d.mts +7 -7
  60. package/dist/nips/nip10.mjs +34 -32
  61. package/dist/nips/nip10.mjs.map +1 -1
  62. package/dist/nips/nip11.d.mts +27 -27
  63. package/dist/nips/nip11.mjs +1 -1
  64. package/dist/nips/nip11.mjs.map +1 -1
  65. package/dist/nips/nip13.mjs +1 -1
  66. package/dist/nips/nip17.d.mts +1 -1
  67. package/dist/nips/nip17.mjs +6 -4
  68. package/dist/nips/nip17.mjs.map +1 -1
  69. package/dist/nips/nip19.d.mts +1 -1
  70. package/dist/nips/nip19.mjs +1 -1
  71. package/dist/nips/nip19.mjs.map +1 -1
  72. package/dist/nips/nip21.d.mts +4 -4
  73. package/dist/nips/nip21.mjs +1 -1
  74. package/dist/nips/nip21.mjs.map +1 -1
  75. package/dist/nips/nip27.d.mts +19 -19
  76. package/dist/nips/nip27.mjs +2 -2
  77. package/dist/nips/nip27.mjs.map +1 -1
  78. package/dist/nips/nip42.mjs +1 -1
  79. package/dist/nips/nip44.mjs +2 -2
  80. package/dist/nips/nip44.mjs.map +1 -1
  81. package/dist/nips/nip46.d.mts +1 -1
  82. package/dist/nips/nip46.mjs +1 -1
  83. package/dist/nips/nip46.mjs.map +1 -1
  84. package/dist/nips/nip49.d.mts +4 -4
  85. package/dist/nips/nip49.mjs +1 -1
  86. package/dist/nips/nip49.mjs.map +1 -1
  87. package/dist/nips/nip51.d.mts +1 -1
  88. package/dist/nips/nip51.mjs +3 -3
  89. package/dist/nips/nip51.mjs.map +1 -1
  90. package/dist/nips/nip57.d.mts +18 -18
  91. package/dist/nips/nip57.mjs +3 -3
  92. package/dist/nips/nip57.mjs.map +1 -1
  93. package/dist/nips/nip59.d.mts +1 -1
  94. package/dist/nips/nip59.mjs +15 -7
  95. package/dist/nips/nip59.mjs.map +1 -1
  96. package/dist/nips/nip65.d.mts +1 -1
  97. package/dist/nips/nip65.mjs +2 -2
  98. package/dist/nips/nip77.d.mts +1 -1
  99. package/dist/nips/nip77.mjs +1 -1
  100. package/dist/nips/nip77.mjs.map +1 -1
  101. package/dist/nips/nip98.mjs +3 -3
  102. package/dist/{pool-SzJKPIa3.d.mts → pool-CqQvUnsq.d.mts} +43 -24
  103. package/dist/{pool-YHRZgcQ3.mjs → pool-vlw74C4t.mjs} +39 -29
  104. package/dist/pool-vlw74C4t.mjs.map +1 -0
  105. package/dist/{put-DS0yzVJx.mjs → put-w13ci3sC.mjs} +3 -3
  106. package/dist/put-w13ci3sC.mjs.map +1 -0
  107. package/dist/{reactive-BJRoVHxU.mjs → reactive-Tz0otDIP.mjs} +49 -27
  108. package/dist/reactive-Tz0otDIP.mjs.map +1 -0
  109. package/dist/{reactive-BJ1ggn4d.d.mts → reactive-gGqUrzW8.d.mts} +16 -7
  110. package/dist/{relay-CvxPiQ_m.mjs → relay-quCs7MHD.mjs} +1049 -861
  111. package/dist/relay-quCs7MHD.mjs.map +1 -0
  112. package/dist/relay.d.mts +4 -4
  113. package/dist/relay.mjs +2 -2
  114. package/dist/{signer-yW3slDC2.mjs → signer-poTGe6Er.mjs} +6 -6
  115. package/dist/signer-poTGe6Er.mjs.map +1 -0
  116. package/dist/signer.d.mts +1 -1
  117. package/dist/signer.mjs +2 -2
  118. package/dist/{storage-CZ52ifcQ.mjs → storage-B5IXTVuS.mjs} +16 -14
  119. package/dist/storage-B5IXTVuS.mjs.map +1 -0
  120. package/dist/storage.d.mts +2 -2
  121. package/dist/storage.mjs +4 -4
  122. package/dist/store.d.mts +2 -2
  123. package/dist/store.mjs +1 -1
  124. package/dist/testing.d.mts +3 -3
  125. package/dist/testing.mjs +6 -6
  126. package/dist/testing.mjs.map +1 -1
  127. package/dist/{types-Cm-XMgtH.d.mts → types-BacwnDtU.d.mts} +5 -5
  128. package/dist/{util-yFYzlbrm.mjs → util-DMF9AIO0.mjs} +6 -2
  129. package/dist/util-DMF9AIO0.mjs.map +1 -0
  130. package/dist/{verifier-BTLOpo1n.mjs → verifier-B51Wkt5H.mjs} +3 -3
  131. package/dist/{verifier-BTLOpo1n.mjs.map → verifier-B51Wkt5H.mjs.map} +1 -1
  132. package/dist/wasm.browser.mjs +2 -2
  133. package/dist/wasm.mjs +2 -2
  134. package/package.json +1 -1
  135. package/dist/builder-CdLE4J6O.mjs.map +0 -1
  136. package/dist/client-CILhdfsF.mjs.map +0 -1
  137. package/dist/gossip-CcTp9VpU.mjs.map +0 -1
  138. package/dist/loaders-DD0XAGzw.mjs.map +0 -1
  139. package/dist/memory-D_-5-T31.mjs.map +0 -1
  140. package/dist/message-CWLcFTQ1.mjs.map +0 -1
  141. package/dist/pool-YHRZgcQ3.mjs.map +0 -1
  142. package/dist/put-DS0yzVJx.mjs.map +0 -1
  143. package/dist/reactive-BJRoVHxU.mjs.map +0 -1
  144. package/dist/relay-CvxPiQ_m.mjs.map +0 -1
  145. package/dist/signer-yW3slDC2.mjs.map +0 -1
  146. package/dist/storage-CZ52ifcQ.mjs.map +0 -1
  147. package/dist/util-yFYzlbrm.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { _ as Tag, t as Event } from "./event-DJeb_DIL.mjs";
2
- import { t as EventBuilder } from "./builder-DgkqDylK.mjs";
2
+ import { t as EventBuilder } from "./builder-CTwkB2zM.mjs";
3
3
  import { a as NostrError } from "./error-BquXDmIN.mjs";
4
- import { a as Rumor, l as WrapOptions, r as Nip59Crypto } from "./nip59-BFIihLYY.mjs";
4
+ import { a as Rumor, l as WrapOptions, r as Nip59Crypto } from "./nip59-C-RWFQIv.mjs";
5
5
  //#region src/nips/nip17.d.ts
6
6
  declare class Nip17Error extends NostrError {
7
7
  name: string;
@@ -28,11 +28,11 @@ declare function dmRelayListEventBuilder(relays: ReadonlyArray<string>): EventBu
28
28
  /** Accept a hex pubkey, a Recipient, or a readonly array of either. Dedup by pubkey. */
29
29
  declare function normalizeRecipients(input: string | Recipient | ReadonlyArray<string | Recipient>): Recipient[];
30
30
  declare function buildChatMessageRumor(senderPubkey: string, recipients: ReadonlyArray<Recipient>, content: string, opts?: ChatMessageOptions): Rumor;
31
- declare function wrapDirectMessage(crypto: Nip59Crypto, recipients: ReadonlyArray<Recipient>, rumor: Rumor, opts?: Pick<WrapOptions, "now" | "randomInt" | "timestamps" | "randomize">): Promise<ReadonlyArray<{
31
+ declare function wrapDirectMessage(crypto: Nip59Crypto, recipients: ReadonlyArray<Recipient>, rumor: Rumor, opts?: Pick<WrapOptions, "now" | "randomInt" | "timestamps" | "expiration" | "ephemeral" | "randomize">): Promise<ReadonlyArray<{
32
32
  recipient: string;
33
33
  wrap: Event;
34
34
  }>>;
35
35
  declare function requireDmRelays(pubkey: string, relays: ReadonlyArray<string>): string[];
36
36
  //#endregion
37
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 };
38
- //# sourceMappingURL=nip17-cTDFfFVt.d.mts.map
38
+ //# sourceMappingURL=nip17-B5WgW4C4.d.mts.map
@@ -16,42 +16,42 @@ type Note = `note1${string}`;
16
16
  declare const Bech32MaxSize = 5000;
17
17
  /** Decoded `nprofile` payload: a pubkey plus optional relay hints. */
18
18
  type ProfilePointer = {
19
- pubkey: string;
20
- relays?: string[];
19
+ readonly pubkey: string;
20
+ readonly relays?: ReadonlyArray<string> | undefined;
21
21
  };
22
22
  /** Decoded `nevent` payload: an event id plus optional relay/author/kind hints. */
23
23
  type EventPointer = {
24
- id: string;
25
- relays?: string[];
26
- author?: string;
27
- kind?: number;
24
+ readonly id: string;
25
+ readonly relays?: ReadonlyArray<string> | undefined;
26
+ readonly author?: string | undefined;
27
+ readonly kind?: number | undefined;
28
28
  };
29
29
  /** Decoded `naddr` payload: a replaceable-event coordinate plus optional relay hints. */
30
30
  type AddressPointer = {
31
- identifier: string;
32
- pubkey: string;
33
- kind: number;
34
- relays?: string[];
31
+ readonly identifier: string;
32
+ readonly pubkey: string;
33
+ readonly kind: number;
34
+ readonly relays?: ReadonlyArray<string> | undefined;
35
35
  };
36
36
  /** Result of {@link decode}: the bech32 prefix and its decoded payload. */
37
37
  type DecodedResult = {
38
- type: "nprofile";
39
- data: ProfilePointer;
38
+ readonly type: "nprofile";
39
+ readonly data: ProfilePointer;
40
40
  } | {
41
- type: "nevent";
42
- data: EventPointer;
41
+ readonly type: "nevent";
42
+ readonly data: EventPointer;
43
43
  } | {
44
- type: "naddr";
45
- data: AddressPointer;
44
+ readonly type: "naddr";
45
+ readonly data: AddressPointer;
46
46
  } | {
47
- type: "nsec";
48
- data: Uint8Array;
47
+ readonly type: "nsec";
48
+ readonly data: Uint8Array;
49
49
  } | {
50
- type: "npub";
51
- data: string;
50
+ readonly type: "npub";
51
+ readonly data: string;
52
52
  } | {
53
- type: "note";
54
- data: string;
53
+ readonly type: "note";
54
+ readonly data: string;
55
55
  };
56
56
  /** Error thrown by NIP-19 encoding/decoding failures. */
57
57
  declare class Nip19Error extends NostrError {
@@ -74,4 +74,4 @@ declare function naddrEncode(addr: AddressPointer): NAddr;
74
74
  declare function decode(code: string): DecodedResult;
75
75
  //#endregion
76
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
77
+ //# sourceMappingURL=nip19-DpPN17bY.d.mts.map
@@ -2,30 +2,30 @@ import { a as NostrError } from "./error-BquXDmIN.mjs";
2
2
  //#region src/nips/nip46.d.ts
3
3
  type BunkerPointer = {
4
4
  /** Remote signer / bunker public key (hex). */
5
- pubkey: string;
6
- relays: string[];
7
- secret: string | undefined;
5
+ readonly pubkey: string;
6
+ readonly relays: ReadonlyArray<string>;
7
+ readonly secret?: string | undefined;
8
8
  };
9
9
  type ClientMetadata = {
10
- name?: string | undefined;
11
- url?: string | undefined;
12
- image?: string | undefined;
10
+ readonly name?: string | undefined;
11
+ readonly url?: string | undefined;
12
+ readonly image?: string | undefined;
13
13
  };
14
14
  type NostrConnectParams = {
15
- clientPubkey: string;
16
- relays: string[];
17
- secret: string;
18
- perms?: string[] | undefined;
15
+ readonly clientPubkey: string;
16
+ readonly relays: ReadonlyArray<string>;
17
+ readonly secret: string;
18
+ readonly perms?: ReadonlyArray<string> | undefined;
19
19
  } & ClientMetadata;
20
20
  type Nip46Request = {
21
- id: string;
22
- method: string;
23
- params: string[];
21
+ readonly id: string;
22
+ readonly method: string;
23
+ readonly params: ReadonlyArray<string>;
24
24
  };
25
25
  type Nip46Response = {
26
- id: string;
27
- result?: string | undefined;
28
- error?: string | undefined;
26
+ readonly id: string;
27
+ readonly result?: string | undefined;
28
+ readonly error?: string | undefined;
29
29
  };
30
30
  declare class Nip46Error extends NostrError {
31
31
  name: string;
@@ -47,4 +47,4 @@ declare function encodeNip46Response(res: Nip46Response): string;
47
47
  declare function decodeNip46Response(json: string): Nip46Response;
48
48
  //#endregion
49
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
50
+ //# sourceMappingURL=nip46-BHaxC9TZ.d.mts.map
@@ -1,5 +1,5 @@
1
1
  import { t as Event } from "./event-DJeb_DIL.mjs";
2
- import { t as EventBuilder } from "./builder-DgkqDylK.mjs";
2
+ import { t as EventBuilder } from "./builder-CTwkB2zM.mjs";
3
3
  //#region src/nips/nip51.d.ts
4
4
  /**
5
5
  * Structural crypto used by NIP-51 private tags. Satisfied by NostrSigner when
@@ -11,17 +11,17 @@ type Nip51Crypto = {
11
11
  nip44Decrypt: (peer: string, payload: string) => Promise<string>;
12
12
  };
13
13
  type MuteItem = {
14
- type: "pubkey";
15
- value: string;
14
+ readonly type: "pubkey";
15
+ readonly value: string;
16
16
  } | {
17
- type: "event";
18
- value: string;
17
+ readonly type: "event";
18
+ readonly value: string;
19
19
  } | {
20
- type: "hashtag";
21
- value: string;
20
+ readonly type: "hashtag";
21
+ readonly value: string;
22
22
  } | {
23
- type: "word";
24
- value: string;
23
+ readonly type: "word";
24
+ readonly value: string;
25
25
  };
26
26
  /** Parse kind:10000 mute list public tags (`p` / `e` / `t` / `word`). */
27
27
  declare function parseMuteList(event: Pick<Event, "kind" | "tags">): MuteItem[];
@@ -84,4 +84,4 @@ declare function parseFollowPack(event: Pick<Event, "kind" | "tags">): {
84
84
  };
85
85
  //#endregion
86
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
87
+ //# sourceMappingURL=nip51-CR0Q8eym.d.mts.map
@@ -34,13 +34,20 @@ type WrapOptions = {
34
34
  readonly relayHint?: string | undefined;
35
35
  /** Appended after the required wrap `p` tag. Never applied to the seal. */
36
36
  readonly extraTags?: ReadonlyArray<Tag> | undefined;
37
+ /**
38
+ * NIP-17 disappearing-message expiry (unix seconds): sets `["expiration", ts]` on the seal and on
39
+ * the gift wrap, signalling relays to discard the wrap after that time.
40
+ */
41
+ readonly expiration?: number | undefined;
42
+ /** Use ephemeral gift wrap kind 21059 instead of 1059 (NIP-59; relays MUST NOT store). */
43
+ readonly ephemeral?: boolean | undefined;
37
44
  /**
38
45
  * Default `"seal+wrap"` (NIP-59). `"wrap"` = only wrap timestamp is randomized; seal uses
39
46
  * rumor.created_at.
40
47
  */
41
48
  readonly randomize?: TimestampRandomize | undefined;
42
49
  };
43
- type SealOptions = Pick<WrapOptions, "now" | "randomInt" | "timestamps" | "randomize">;
50
+ type SealOptions = Pick<WrapOptions, "now" | "randomInt" | "timestamps" | "expiration" | "randomize">;
44
51
  declare class Nip59Error extends NostrError {
45
52
  name: string;
46
53
  }
@@ -75,4 +82,4 @@ declare function wrap(crypto: Nip59Crypto, recipient: string, rumor: Rumor, opts
75
82
  declare function unwrap(crypto: Nip44Decryptor, giftWrap: Event): Promise<Rumor>;
76
83
  //#endregion
77
84
  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 };
78
- //# sourceMappingURL=nip59-BFIihLYY.d.mts.map
85
+ //# sourceMappingURL=nip59-C-RWFQIv.d.mts.map
@@ -1,5 +1,5 @@
1
1
  import { _ as Tag, t as Event } from "./event-DJeb_DIL.mjs";
2
- import { t as EventBuilder } from "./builder-DgkqDylK.mjs";
2
+ import { t as EventBuilder } from "./builder-CTwkB2zM.mjs";
3
3
  //#region src/nips/nip65.d.ts
4
4
  /** NIP-65 `r` tag marker: read-only, write-only, or unmarked (both). */
5
5
  type RelayMarker = "read" | "write" | "both";
@@ -20,4 +20,4 @@ declare function readRelays(items: ReadonlyArray<RelayListItem>): string[];
20
20
  declare function writeRelays(items: ReadonlyArray<RelayListItem>): string[];
21
21
  //#endregion
22
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
23
+ //# sourceMappingURL=nip65-D1fbmhOg.d.mts.map
@@ -8,16 +8,16 @@ declare class Nip77Error extends NostrError {
8
8
  name: string;
9
9
  }
10
10
  type NegItem = {
11
- timestamp: number;
12
- id: Uint8Array;
11
+ readonly timestamp: number;
12
+ readonly id: Uint8Array;
13
13
  };
14
14
  type ReconcileOutcome = {
15
15
  /** Event ids the local side holds that the peer does not. */
16
- have: string[];
16
+ readonly have: ReadonlyArray<string>;
17
17
  /** Event ids the peer holds that the local side does not. */
18
- need: string[];
18
+ readonly need: ReadonlyArray<string>;
19
19
  /** Next NEG-MSG hex, or undefined when the session converged. */
20
- nextMessage: string | undefined;
20
+ readonly nextMessage: string | undefined;
21
21
  };
22
22
  declare class NegentropyStorageVector {
23
23
  #private;
@@ -52,4 +52,4 @@ declare function runNegSession(opts: {
52
52
  }>;
53
53
  //#endregion
54
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 };
55
- //# sourceMappingURL=nip77-C9QcZn7b.d.mts.map
55
+ //# sourceMappingURL=nip77-DzUwUxHq.d.mts.map
@@ -1,14 +1,14 @@
1
1
  import { n as EventTemplate, t as Event } from "../event-DJeb_DIL.mjs";
2
- import { t as EventBuilder } from "../builder-DgkqDylK.mjs";
2
+ import { t as EventBuilder } from "../builder-CTwkB2zM.mjs";
3
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 = {
7
- url: string;
8
- sha256: string;
9
- size: number;
10
- type?: string;
11
- uploaded?: number;
7
+ readonly url: string;
8
+ readonly sha256: string;
9
+ readonly size: number;
10
+ readonly type?: string | undefined;
11
+ readonly uploaded?: number | undefined;
12
12
  };
13
13
  export type BlossomFetch = ManualFetch;
14
14
  /** Signs a kind 24242 auth template (draft; pubkey is filled by the signer). */
@@ -1,6 +1,6 @@
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";
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-DMF9AIO0.mjs";
2
2
  import { t as Kind } from "../kind-Cxwhhkx8.mjs";
3
- import { t as EventBuilder } from "../builder-CdLE4J6O.mjs";
3
+ import { t as EventBuilder } from "../builder-aNU2gGFP.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";
@@ -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, 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
+ {"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 type { Mutable } from \"../core/util.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 readonly url: string;\n readonly sha256: string;\n readonly size: number;\n readonly type?: string | undefined;\n readonly uploaded?: number | undefined;\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: Mutable<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":";;;;;;;;;;;;;;AAoCA,MAAM,6BAAa,IAAI,IAAqB;CAAC;CAAU;CAAU;CAAQ;CAAO;AAAO,CAAC;;AAGxF,MAAM,uBAAuB;AAE7B,IAAa,eAAb,cAAkC,WAAW;CAC3C,OAAgB;CAChB;CAEA,YAAY,SAAiB,SAA8C;EACzE,MAAM,EAAE,QAAQ,GAAG,SAAS,WAAW,CAAC;EACxC,MAAM,SAAS,IAAI;EACnB,KAAK,SAAS;CAChB;AACF;;AAGA,SAAgB,eAAe,KAAuC;CACpE,IAAI;CACJ,IAAI;EACF,QAAQ,eAAe,MAAM,MAAM,IAAI,IAAI,GAAG,EAAA,CAAG;CACnD,QAAQ;EACN;CACF;CACA,MAAM,UAAU,KAAK,MAAM,gBAAgB;CAC3C,IAAI,YAAY,QAAQ,QAAQ,WAAW,GACzC;CAEF,OAAO,QAAQ,GAAG,EAAE,CAAC,EAAE,YAAY;AACrC;AAEA,eAAsB,WAAW,MAAwD;CACvF,IAAI;CACJ,IAAI,gBAAgB,YAClB,QAAQ;MACH,IAAI,gBAAgB,aACzB,QAAQ,IAAI,WAAW,IAAI;MAE3B,QAAQ,IAAI,WAAW,MAAM,KAAK,YAAY,CAAC;CAEjD,OAAO,WAAW,OAAO,KAAK,CAAC;AACjC;;AAGA,eAAsB,WACpB,MACA,WACkB;CAClB,MAAM,WAAW,YAAY,WAAW,aAAa;CACrD,OAAQ,MAAM,WAAW,IAAI,MAAO;AACtC;;AAGA,SAAgB,0BAA0B,OAAsB;CAC9D,OAAO,SAAS,eAAe,OAAO,YAAY,OAAO,KAAK,UAAU,KAAK,CAAC,CAAC;AACjF;;AAGA,MAAM,qBAAsD;CAC1D,QAAQ;CACR,QAAQ;CACR,MAAM;CACN,KAAK;CACL,OAAO;AACT;;;;;AAMA,SAAS,eAAe,KAAiC;CACvD,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,IAAI,SAAS,KAAK,IAAI,MAAM,WAAW,KAAK;EAChE,IAAI,IAAI,aAAa,WAAW,IAAI,aAAa,UAC/C;EAEF,MAAM,WAAW,IAAI,SAAS,YAAY;EAC1C,OAAO,aAAa,KAAK,KAAA,IAAY;CACvC,QAAQ;EACN;CACF;AACF;AAEA,SAAgB,mBACd,MACA,MAMe;CACf,IAAI,CAAC,WAAW,IAAI,IAAI,GACtB,MAAM,IAAI,aAAa,8BAA8B,MAAM;CAE7D,IAAI,MAAM,YAAY,KAAA,KAAa,KAAK,QAAQ,KAAK,MAAM,IACzD,MAAM,IAAI,aAAa,wCAAwC;CAEjE,MAAM,aAAa,MAAM,cAAc,WAAW,IAAI;CACtD,MAAM,OAAc,CAClB,CAAC,KAAK,IAAI,GACV,CAAC,cAAc,OAAO,UAAU,CAAC,CACnC;CACA,IAAI,MAAM,WAAW,KAAA,GACnB,KAAK,KAAK,CAAC,KAAK,YAAY,KAAK,QAAQ,aAAa,CAAC,CAAC;CAE1D,MAAM,8BAAc,IAAI,IAAY;CACpC,KAAK,MAAM,UAAU,MAAM,WAAW,CAAC,GAAG;EACxC,MAAM,WAAW,eAAe,MAAM;EACtC,IAAI,aAAa,KAAA,KAAa,YAAY,IAAI,QAAQ,GACpD;EAEF,YAAY,IAAI,QAAQ;EACxB,KAAK,KAAK,CAAC,UAAU,QAAQ,CAAC;CAChC;CACA,OAAO;EACL,MAAM,KAAK;EACX,YAAY,WAAW;EACvB;EACA,SAAS,MAAM,WAAW,mBAAmB;CAC/C;AACF;AAEA,eAAsB,iBACpB,MACA,MACA,MAKgB;CAEhB,OAAO,KAAK,mBAAmB,UAAU;EAAE,QAAQ,MADhC,WAAW,IAAI;EACuB,GAAG;CAAK,CAAC,CAAC;AACrE;AAEA,eAAsB,OACpB,QACA,MACA,MACA,MACyB;CACzB,MAAM,OACJ,MAAM,WAAW,KAAA,IAAY,MAAM,WAAW,IAAI,IAAI,YAAY,KAAK,QAAQ,aAAa;CAC9F,MAAM,UAAkC;EACtC,eAAe,0BAA0B,IAAI;EAC7C,aAAa;CACf;CACA,IAAI,KAAK,SAAS,IAChB,QAAQ,kBAAkB,KAAK;CAQjC,OAAO,sBAAsB,oBAAoB,MAAM,SAAS,MAN9C,eAAe,MAAM,OAAO,WAAW,QAAQ,SAAS,GAAG;EAC3E,QAAQ;EACR;EACA,MAAM;EACN,QAAQ,MAAM;CAChB,CAAC,CACkE,CAAC,GAAG,IAAI;AAC7E;AAEA,eAAsB,UACpB,QACA,QACA,MACA,MAC2B;CAC3B,MAAM,KAAK,YAAY,QAAQ,QAAQ;CACvC,MAAM,UAAkC,CAAC;CACzC,IAAI,MACF,QAAQ,mBAAmB,0BAA0B,IAAI;CAE3D,MAAM,MAAM,MAAM,eAAe,MAAM,OAAO,WAAW,QAAQ,SAAS,IAAI,GAAG;EAC/E,QAAQ;EACR;EACA,QAAQ,MAAM;CAChB,CAAC;CACD,MAAM,OAAO,MAAM,SAAS,GAAG;CAC/B,IAAI,CAAC,MAAM,QAAQ,IAAI,GACrB,MAAM,IAAI,aAAa,8CAA8C,EAAE,QAAQ,IAAI,OAAO,CAAC;CAE7F,OAAO,KAAK,IAAI,mBAAmB;AACrC;AAEA,eAAsB,WACpB,QACA,WACA,MACA,MACe;CACf,MAAM,OAAO,YAAY,WAAW,aAAa;CACjD,MAAM,eAAe,MAAM,OAAO,WAAW,QAAQ,IAAI,MAAM,GAAG;EAChE,QAAQ;EACR,SAAS,EAAE,eAAe,0BAA0B,IAAI,EAAE;EAC1D,QAAQ,MAAM;CAChB,CAAC;AACH;AAEA,eAAsB,WACpB,QACA,MACA,MACyB;CACzB,MAAM,OAAO,YAAY,KAAK,QAAQ,aAAa;CACnD,MAAM,UAAkC;EACtC,eAAe,0BAA0B,KAAK,IAAI;EAClD,gBAAgB;EAChB,aAAa;EACb,oBAAoB,OAAO,KAAK,IAAI;CACtC;CACA,IAAI,KAAK,SAAS,KAAA,KAAa,KAAK,SAAS,IAC3C,QAAQ,oBAAoB,KAAK;CAQnC,OAAO,sBAAsB,oBAAoB,MAAM,SAAS,MAN9C,eAAe,KAAK,OAAO,WAAW,QAAQ,SAAS,GAAG;EAC1E,QAAQ;EACR;EACA,MAAM,KAAK,UAAU,EAAE,KAAK,KAAK,IAAI,CAAC;EACtC,QAAQ,KAAK;CACf,CAAC,CACkE,CAAC,GAAG,IAAI;AAC7E;;AAGA,eAAsB,YACpB,QACA,MACA,MACA,MACe;CACf,MAAM,OACJ,MAAM,WAAW,KAAA,IAAY,MAAM,WAAW,IAAI,IAAI,YAAY,KAAK,QAAQ,aAAa;CAC9F,MAAM,UAAkC;EACtC,eAAe,0BAA0B,IAAI;EAC7C,aAAa;EACb,oBAAoB,OAAO,KAAK,IAAI;CACtC;CACA,IAAI,KAAK,MACP,QAAQ,oBAAoB,KAAK;CAEnC,MAAM,eAAe,MAAM,OAAO,WAAW,QAAQ,SAAS,GAAG;EAC/D,QAAQ;EACR;EACA,QAAQ,MAAM;CAChB,CAAC;AACH;;;;;AAMA,eAAsB,WACpB,QACA,WACA,MACkB;CAElB,MAAM,MAAM,WAAW,QAAQ,IADlB,YAAY,WAAW,aACE,GAAG;CACzC,MAAM,MAAM,MAAM,aAAa,MAAM,OAAO,KAAK;EAAE,QAAQ;EAAQ,QAAQ,MAAM;CAAO,CAAC;CACzF,IAAI,IAAI,WAAW,KACjB,OAAO;CAET,IAAI,IAAI,IACN,OAAO;CAET,MAAM,iBAAiB,QAAQ,KAAK,GAAG;AACzC;;AAGA,eAAsB,QACpB,QACA,WACA,MACqB;CACrB,MAAM,OAAO,YAAY,WAAW,aAAa;CACjD,MAAM,MAAM,WAAW,QAAQ,IAAI,MAAM;CACzC,MAAM,MAAM,MAAM,aAAa,MAAM,OAAO,KAAK;EAAE,QAAQ;EAAO,QAAQ,MAAM;CAAO,CAAC;CACxF,IAAI,CAAC,IAAI,IACP,MAAM,iBAAiB,OAAO,KAAK,GAAG;CAExC,MAAM,QAAQ,IAAI,WAAW,MAAM,IAAI,YAAY,CAAC;CACpD,IAAI,CAAE,MAAM,WAAW,OAAO,IAAI,GAChC,MAAM,IAAI,aAAa,sBAAsB;CAE/C,OAAO;AACT;;;;;;AAOA,eAAsB,YACpB,KACA,SACA,MACiB;CACjB,MAAM,OAAO,eAAe,GAAG;CAC/B,IAAI,SAAS,KAAA,GACX,OAAO;CAGT,MAAM,MAAM,qBAAqB,KAAK,IAAI;CAC1C,MAAM,YAAY,MAAM,SAAS,mBAAmB,mBAAmB;CAEvE,MAAM,iBAAiB,MAAM,WAAW,WAAW,KAAK,MAAM,MAAM;CAEpE,IAAI,mBAAmB,KAAA,KAAa,kBAAkB,OAAO,iBAAiB,KAC5E,OAAO;CAGT,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,OAAO,aAAa,MAAM;EAChC,IAAI,SAAS,KAAA,GACX;EAEF,MAAM,YAAY,GAAG,KAAK,GAAG,OAAO;EAEpC,MAAM,SAAS,MAAM,WAAW,WAAW,WAAW,MAAM,MAAM;EAClE,IAAI,WAAW,KAAA,KAAa,UAAU,OAAO,SAAS,KACpD,OAAO;CAEX;CACA,OAAO;AACT;;AAGA,eAAsB,gBACpB,SACA,MACA,MACA,MACyB;CACzB,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,aAAa,YAAY;CAErC,IAAI;CACJ,KAAK,MAAM,UAAU,SACnB,IAAI;EAEF,OAAO,MAAM,OAAO,QAAQ,MAAM,MAAM,IAAI;CAC9C,SAAS,OAAO;EACd,IAAI,aAAa,KAAK,GACpB,MAAM;EAER,OACE,iBAAiB,eACb,QACA,IAAI,aAAa,yBAAyB,EACxC,OAAO,MACT,CAAC;CACT;CAEF,MAAM,QAAQ,IAAI,aAAa,YAAY;AAC7C;AAEA,SAAgB,8BAA8B,SAA8C;CAC1F,MAAM,OAAc,CAAC;CACrB,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,OAAO,SAAS;EACzB,MAAM,MAAM,aAAa,GAAG;EAC5B,IAAI,QAAQ,KAAA,KAAa,KAAK,IAAI,GAAG,GACnC;EAEF,KAAK,IAAI,GAAG;EACZ,KAAK,KAAK,CAAC,UAAU,GAAG,CAAC;CAC3B;CACA,IAAI,KAAK,WAAW,GAClB,MAAM,IAAI,aAAa,qDAAqD;CAE9E,OAAO,IAAI,aAAa,KAAK,mBAAmB,EAAE,CAAC,CAAC,KAAK,IAAI;AAC/D;AAEA,SAAgB,uBAAuB,OAA+C;CACpF,IAAI,MAAM,SAAS,KAAK,mBACtB,MAAM,IAAI,qBAAqB,iBAAiB,KAAK,kBAAkB,QAAQ,MAAM,MAAM;CAE7F,MAAM,MAAgB,CAAC;CACvB,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,OAAO,MAAM,MAAM;EAC5B,MAAM,WAAW,IAAI,GAAG,CAAC;EACzB,IAAI,IAAI,OAAO,YAAY,aAAa,KAAA,KAAa,aAAa,IAChE;EAEF,MAAM,MAAM,aAAa,QAAQ;EACjC,IAAI,QAAQ,KAAA,KAAa,KAAK,IAAI,GAAG,GACnC;EAEF,KAAK,IAAI,GAAG;EACZ,IAAI,KAAK,GAAG;CACd;CACA,OAAO;AACT;AAEA,SAAS,aAAa,KAAiC;CACrD,IAAI;EACF,MAAM,IAAI,IAAI,IAAI,GAAG;EACrB,IAAI,EAAE,aAAa,WAAW,EAAE,aAAa,UAC3C;EAEF,MAAM,OAAO,EAAE,SAAS,QAAQ,QAAQ,EAAE;EAC1C,OAAO,GAAG,EAAE,SAAS;CACvB,QAAQ;EACN;CACF;AACF;;AAGA,SAAS,WAAW,QAAgB,MAAsB;CACxD,MAAM,OAAO,aAAa,MAAM;CAChC,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,aAAa,+BAA+B,QAAQ;CAGhE,OAAO,GAAG,OADK,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI;AAEnD;AAEA,SAAS,aAAa,KAAuB;CAC3C,OAAO,eAAe,SAAS,IAAI,SAAS;AAC9C;;AAGA,SAAS,qBAAqB,KAAa,MAAsB;CAC/D,IAAI;CACJ,IAAI;EACF,CAAC,CAAE,YAAa,IAAI,IAAI,GAAG;CAC7B,QAAQ;EACN,OAAO;CACT;CACA,MAAM,IAAI,SAAS,YAAY,CAAC,CAAC,YAAY,IAAI;CACjD,IAAI,MAAM,IACR,OAAO;CAGT,OADU,WAAW,KAAK,SAAS,MAAM,IAAI,EAAE,CACxC,CAAC,GAAG,MAAM;AACnB;AAEA,SAAS,sBAAoC;CAC3C,OAAO,IAAI,aAAa,oDAAoD;AAC9E;AAIA,SAAS,iBAAiB,QAAgB,KAAa,KAAmC;CACxF,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,QAAQ,IAAI,UAAU,KAAK,KAAA;CAC1C,QAAQ;EACN,SAAS,KAAA;CACX;CACA,MAAM,SAAS,QAAQ,KAAK;CAC5B,OAAO,IAAI,aACT,WAAW,KAAA,KAAa,WAAW,KAC/B,WAAW,OAAO,GAAG,IAAI,WAAW,IAAI,OAAO,KAC/C,QACJ,EAAE,QAAQ,IAAI,OAAO,CACvB;AACF;AAEA,eAAe,aACb,WACA,KACA,MAMyB;CACzB,OAAO,YACL,aAAa,mBAAmB,mBAAmB,GACnD,KACA,OACC,QACC,IAAI,aAAa,WAAW,KAAK,OAAO,GAAG,IAAI,UAAU,EACvD,OAAO,IACT,CAAC,CACL;AACF;;;;;AAMA,eAAe,WACb,WACA,KACA,QAC6B;CAC7B,IAAI;EAEF,QAAO,MADW,WAAW,WAAW,KAAK;GAAE,QAAQ;GAAQ;EAAO,CAAC,EAAA,CAC5D;CACb,SAAS,OAAO;EACd,IAAI,aAAa,KAAK,GACpB,MAAM;EAER;CACF;AACF;AAEA,eAAe,eACb,WACA,KACA,MAMyB;CACzB,MAAM,MAAM,MAAM,aAAa,WAAW,KAAK,IAAI;CACnD,IAAI,IAAI,IACN,OAAO;CAET,MAAM,iBAAiB,KAAK,QAAQ,KAAK,GAAG;AAC9C;AAEA,eAAe,SAAS,KAAuC;CAC7D,IAAI;EACF,OAAO,MAAM,IAAI,KAAK;CACxB,SAAS,OAAO;EACd,MAAM,IAAI,aAAa,iCAAiC;GACtD,OAAO;GACP,QAAQ,IAAI;EACd,CAAC;CACH;AACF;AAEA,SAAS,oBAAoB,MAA+B;CAC1D,IAAI,CAAC,SAAS,IAAI,GAChB,MAAM,IAAI,aAAa,yBAAyB;CAElD,MAAM,MAAM;CACZ,IAAI,OAAO,IAAI,WAAW,YAAY,CAAC,IAAI,QACzC,MAAM,IAAI,aAAa,6BAA6B;CAEtD,IAAI,OAAO,IAAI,cAAc,UAC3B,MAAM,IAAI,aAAa,gCAAgC;CAEzD,MAAM,OAAO,YAAY,IAAI,WAAW,aAAa;CACrD,IAAI,OAAO,IAAI,YAAY,YAAY,CAAC,OAAO,UAAU,IAAI,OAAO,KAAK,IAAI,UAAU,GACrF,MAAM,IAAI,aAAa,8BAA8B;CAEvD,MAAM,OAAgC;EAAE,KAAK,IAAI;EAAQ,QAAQ;EAAM,MAAM,IAAI;CAAQ;CACzF,IAAI,OAAO,IAAI,YAAY,UACzB,KAAK,OAAO,IAAI;CAElB,IACE,OAAO,IAAI,gBAAgB,YAC3B,OAAO,UAAU,IAAI,WAAW,KAChC,IAAI,eAAe,GAEnB,KAAK,WAAW,IAAI;CAEtB,OAAO;AACT;AAEA,SAAS,sBAAsB,MAAsB,UAAkC;CACrF,IAAI,KAAK,WAAW,UAClB,MAAM,IAAI,aAAa,iCAAiC;CAE1D,OAAO;AACT"}
@@ -1,4 +1,4 @@
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";
1
+ import { a as hexToBytes$1, f as utf8Decoder, m as CryptoError, n as assertHex32, p as utf8Encoder, r as assertSecretKeyBytes } from "../util-DMF9AIO0.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";
@@ -1,5 +1,5 @@
1
1
  import { a as NostrError } from "../error-BquXDmIN.mjs";
2
- import { f as ProfilePointer } from "../nip19-D1OnJ192.mjs";
2
+ import { f as ProfilePointer } from "../nip19-DpPN17bY.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). */
@@ -14,20 +14,20 @@ export declare const WELL_KNOWN_PATH = "/.well-known/nostr.json";
14
14
  export type Nip05 = string;
15
15
  export type Nip05Address = {
16
16
  /** Local part (lowercased). `_` for domain-only identifiers. */
17
- local: string;
17
+ readonly local: string;
18
18
  /** Domain (lowercased). */
19
- domain: string;
19
+ readonly domain: string;
20
20
  };
21
21
  /** 46.md appendix discovery metadata (`{relays, nostrconnect_url}`). Not a bunker pointer. */
22
22
  export type Nip05Nip46 = {
23
- relays?: string[];
24
- nostrconnectUrl?: string;
23
+ readonly relays?: ReadonlyArray<string> | undefined;
24
+ readonly nostrconnectUrl?: string | undefined;
25
25
  };
26
26
  export type Nip05Document = {
27
- names: Record<string, string>;
27
+ readonly names: Readonly<Record<string, string>>;
28
28
  /** NIP-05 profile relay hints */
29
- relays?: Record<string, string[]>;
30
- nip46?: Nip05Nip46;
29
+ readonly relays?: Readonly<Record<string, ReadonlyArray<string>>> | undefined;
30
+ readonly nip46?: Nip05Nip46 | undefined;
31
31
  };
32
32
  export type Nip05Fetch = ManualFetch;
33
33
  export declare class Nip05Error extends NostrError {
@@ -1,4 +1,4 @@
1
- import { c as isRecord, o as isHex32, v as NostrError } from "../util-yFYzlbrm.mjs";
1
+ import { c as isRecord, o as isHex32, v as NostrError } from "../util-DMF9AIO0.mjs";
2
2
  import { n as requireGlobalFetch, t as fetchManual } from "../http-CuazNRIP.mjs";
3
3
  //#region src/nips/nip05.ts
4
4
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"nip05.mjs","names":[],"sources":["../../src/nips/nip05.ts"],"sourcesContent":["/**\n * NIP-05: Mapping Nostr keys to DNS-based internet identifiers\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/05.md\n */\nimport { NostrError } from \"../core/error.ts\";\nimport { isHex32, isRecord } from \"../core/util.ts\";\nimport { fetchManual, requireGlobalFetch } from \"./http.ts\";\nimport type { ManualFetch } from \"./http.ts\";\nimport type { ProfilePointer } from \"./nip19.ts\";\n\n/** Root local-part (`_@domain` rendered as just the domain). */\nexport const NIP05_ROOT_LOCAL = \"_\";\n\nexport const WELL_KNOWN_PATH = \"/.well-known/nostr.json\";\n\n/**\n * NIP-05 identifier string.\n *\n * - Full: `name@domain`\n * - Root: `domain` or `_@domain`\n */\nexport type Nip05 = string;\n\nexport type Nip05Address = {\n /** Local part (lowercased). `_` for domain-only identifiers. */\n local: string;\n /** Domain (lowercased). */\n domain: string;\n};\n\n/** 46.md appendix discovery metadata (`{relays, nostrconnect_url}`). Not a bunker pointer. */\nexport type Nip05Nip46 = {\n relays?: string[];\n nostrconnectUrl?: string;\n};\n\nexport type Nip05Document = {\n names: Record<string, string>;\n /** NIP-05 profile relay hints */\n relays?: Record<string, string[]>;\n nip46?: Nip05Nip46;\n};\n\nexport type Nip05Fetch = ManualFetch;\n\nexport class Nip05Error extends NostrError {\n override name = \"Nip05Error\";\n}\n\n/** Matches optional local@domain. Groups: 1=local (optional), 2=domain. */\n// oxlint-disable-next-line no-inferrable-types -- isolatedDeclarations requires the annotation for dts emit\nexport const NIP05_REGEX: RegExp = /^(?:([a-z0-9._-]+)@)?([a-z0-9-]+(?:\\.[a-z0-9-]+)+)$/i;\n\nexport function isNip05(value: unknown): value is string {\n if (typeof value !== \"string\") {\n return false;\n }\n try {\n parseNip05(value);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Parse an identifier into local + domain. Accepts `name@domain`, `_@domain`, or bare `domain`\n * (local becomes `_`).\n */\nexport function parseNip05(input: string): Nip05Address {\n const match = NIP05_REGEX.exec(input.trim());\n const domain = match?.at(2);\n if (match === null || domain === undefined || domain === \"\") {\n throw new Nip05Error(`invalid NIP-05 identifier: ${input}`);\n }\n const localRaw = match.at(1) ?? NIP05_ROOT_LOCAL;\n return { local: localRaw.toLowerCase(), domain: domain.toLowerCase() };\n}\n\n/** Build the well-known HTTPS URL for an address. */\nexport function wellKnownUrl(address: Nip05Address): string {\n const url = new URL(`https://${address.domain}${WELL_KNOWN_PATH}`);\n url.searchParams.set(\"name\", address.local);\n return url.toString();\n}\n\nfunction stringUrls(list: unknown): string[] | undefined {\n if (!Array.isArray(list)) {\n return undefined;\n }\n return list.filter((u): u is string => typeof u === \"string\" && u.length > 0);\n}\n\n/**\n * Parse nostr.json `nip46`: 46.md appendix `{relays, nostrconnect_url}`. Hex-pubkey maps and other\n * keys are ignored.\n */\nexport function parseNip05Nip46(raw: unknown): Nip05Nip46 | undefined {\n if (!isRecord(raw)) {\n return undefined;\n }\n const obj = raw;\n const result: Nip05Nip46 = {};\n\n if (Array.isArray(obj[\"relays\"])) {\n result.relays = stringUrls(obj[\"relays\"]) ?? [];\n }\n\n if (typeof obj[\"nostrconnect_url\"] === \"string\" && obj[\"nostrconnect_url\"].length > 0) {\n result.nostrconnectUrl = obj[\"nostrconnect_url\"];\n }\n\n if (result.relays === undefined && result.nostrconnectUrl === undefined) {\n return undefined;\n }\n return result;\n}\n\n/** Parse and validate a nostr.json document body. */\nexport function parseNip05Document(json: unknown): Nip05Document {\n if (!isRecord(json)) {\n throw new Nip05Error(\"NIP-05 document must be a JSON object\");\n }\n const raw = json;\n if (!isRecord(raw[\"names\"])) {\n throw new Nip05Error(\"NIP-05 document missing names map\");\n }\n\n const names: Record<string, string> = {};\n for (const [k, v] of Object.entries(raw[\"names\"])) {\n if (typeof v !== \"string\" || !isHex32(v.toLowerCase())) {\n continue;\n }\n names[k.toLowerCase()] = v.toLowerCase();\n }\n\n const rawRelays = raw[\"relays\"];\n let relays: Record<string, string[]> | undefined;\n if (isRecord(rawRelays)) {\n relays = {};\n for (const [pk, list] of Object.entries(rawRelays)) {\n if (!isHex32(pk.toLowerCase())) {\n continue;\n }\n const urls = stringUrls(list);\n if (urls !== undefined && urls.length > 0) {\n relays[pk.toLowerCase()] = urls;\n }\n }\n }\n\n const doc: Nip05Document = { names };\n if (relays !== undefined) {\n doc.relays = relays;\n }\n const nip46 = parseNip05Nip46(raw[\"nip46\"]);\n if (nip46 !== undefined) {\n doc.nip46 = nip46;\n }\n return doc;\n}\n\n/** Resolve local name from an already-parsed document. */\nexport function lookupFromDocument(\n doc: Nip05Document,\n address: Nip05Address,\n): ProfilePointer | undefined {\n const pubkey = doc.names[address.local];\n if (pubkey === undefined) {\n return undefined;\n }\n const relays = doc.relays?.[pubkey];\n return relays !== undefined && relays.length > 0 ? { pubkey, relays: [...relays] } : { pubkey };\n}\n\n/**\n * Fetch and parse `/.well-known/nostr.json`. Returns `undefined` on network/parse failure (does not\n * throw for those). Rejects HTTP redirects (non-200) per NIP-05 security constraints. AbortError\n * rethrows; it is not mapped to `undefined`.\n */\nexport async function queryNip05Document(\n identifier: string,\n opts?: { fetch?: Nip05Fetch; signal?: AbortSignal },\n): Promise<{ address: Nip05Address; doc: Nip05Document } | undefined> {\n let address: Nip05Address;\n try {\n address = parseNip05(identifier);\n } catch {\n return undefined;\n }\n\n const url = wellKnownUrl(address);\n const fetchImpl =\n opts?.fetch ??\n requireGlobalFetch(() => new Nip05Error(\"no fetch implementation available; pass opts.fetch\"));\n\n try {\n const res = await fetchManual(fetchImpl, url, { signal: opts?.signal }, (err) =>\n err instanceof Error ? err : new Error(\"NIP-05 request failed\"),\n );\n // Redirects and errors must not be trusted (NIP-05 security).\n if (res.status !== 200) {\n return undefined;\n }\n const json = await res.json();\n return { address, doc: parseNip05Document(json) };\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n throw error;\n }\n return undefined;\n }\n}\n\n/**\n * Query `/.well-known/nostr.json` for an identifier. Returns `undefined` on network/parse/lookup\n * failure (does not throw for those). Profile `relays` only. `nip46` is discovery metadata, not a\n * bunker pointer.\n */\nexport async function queryProfile(\n identifier: string,\n opts?: { fetch?: Nip05Fetch; signal?: AbortSignal },\n): Promise<ProfilePointer | undefined> {\n const fetched = await queryNip05Document(identifier, opts);\n if (fetched === undefined) {\n return undefined;\n }\n return lookupFromDocument(fetched.doc, fetched.address);\n}\n\n/** True when the identifier resolves to exactly `pubkey`. */\nexport async function verifyNip05(\n pubkey: string,\n identifier: string,\n opts?: { fetch?: Nip05Fetch; signal?: AbortSignal },\n): Promise<boolean> {\n if (!isHex32(pubkey.toLowerCase())) {\n return false;\n }\n const profile = await queryProfile(identifier, opts);\n return profile !== undefined && profile.pubkey === pubkey.toLowerCase();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"nip05.mjs","names":[],"sources":["../../src/nips/nip05.ts"],"sourcesContent":["/**\n * NIP-05: Mapping Nostr keys to DNS-based internet identifiers\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/05.md\n */\nimport { NostrError } from \"../core/error.ts\";\nimport type { Mutable } from \"../core/util.ts\";\nimport { isHex32, isRecord } from \"../core/util.ts\";\nimport { fetchManual, requireGlobalFetch } from \"./http.ts\";\nimport type { ManualFetch } from \"./http.ts\";\nimport type { ProfilePointer } from \"./nip19.ts\";\n\n/** Root local-part (`_@domain` rendered as just the domain). */\nexport const NIP05_ROOT_LOCAL = \"_\";\n\nexport const WELL_KNOWN_PATH = \"/.well-known/nostr.json\";\n\n/**\n * NIP-05 identifier string.\n *\n * - Full: `name@domain`\n * - Root: `domain` or `_@domain`\n */\nexport type Nip05 = string;\n\nexport type Nip05Address = {\n /** Local part (lowercased). `_` for domain-only identifiers. */\n readonly local: string;\n /** Domain (lowercased). */\n readonly domain: string;\n};\n\n/** 46.md appendix discovery metadata (`{relays, nostrconnect_url}`). Not a bunker pointer. */\nexport type Nip05Nip46 = {\n readonly relays?: ReadonlyArray<string> | undefined;\n readonly nostrconnectUrl?: string | undefined;\n};\n\nexport type Nip05Document = {\n readonly names: Readonly<Record<string, string>>;\n /** NIP-05 profile relay hints */\n readonly relays?: Readonly<Record<string, ReadonlyArray<string>>> | undefined;\n readonly nip46?: Nip05Nip46 | undefined;\n};\n\nexport type Nip05Fetch = ManualFetch;\n\nexport class Nip05Error extends NostrError {\n override name = \"Nip05Error\";\n}\n\n/** Matches optional local@domain. Groups: 1=local (optional), 2=domain. */\n// oxlint-disable-next-line no-inferrable-types -- isolatedDeclarations requires the annotation for dts emit\nexport const NIP05_REGEX: RegExp = /^(?:([a-z0-9._-]+)@)?([a-z0-9-]+(?:\\.[a-z0-9-]+)+)$/i;\n\nexport function isNip05(value: unknown): value is string {\n if (typeof value !== \"string\") {\n return false;\n }\n try {\n parseNip05(value);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Parse an identifier into local + domain. Accepts `name@domain`, `_@domain`, or bare `domain`\n * (local becomes `_`).\n */\nexport function parseNip05(input: string): Nip05Address {\n const match = NIP05_REGEX.exec(input.trim());\n const domain = match?.at(2);\n if (match === null || domain === undefined || domain === \"\") {\n throw new Nip05Error(`invalid NIP-05 identifier: ${input}`);\n }\n const localRaw = match.at(1) ?? NIP05_ROOT_LOCAL;\n return { local: localRaw.toLowerCase(), domain: domain.toLowerCase() };\n}\n\n/** Build the well-known HTTPS URL for an address. */\nexport function wellKnownUrl(address: Nip05Address): string {\n const url = new URL(`https://${address.domain}${WELL_KNOWN_PATH}`);\n url.searchParams.set(\"name\", address.local);\n return url.toString();\n}\n\nfunction stringUrls(list: unknown): string[] | undefined {\n if (!Array.isArray(list)) {\n return undefined;\n }\n return list.filter((u): u is string => typeof u === \"string\" && u.length > 0);\n}\n\n/**\n * Parse nostr.json `nip46`: 46.md appendix `{relays, nostrconnect_url}`. Hex-pubkey maps and other\n * keys are ignored.\n */\nexport function parseNip05Nip46(raw: unknown): Nip05Nip46 | undefined {\n if (!isRecord(raw)) {\n return undefined;\n }\n const obj = raw;\n const result: Mutable<Nip05Nip46> = {};\n\n if (Array.isArray(obj[\"relays\"])) {\n result.relays = stringUrls(obj[\"relays\"]) ?? [];\n }\n\n if (typeof obj[\"nostrconnect_url\"] === \"string\" && obj[\"nostrconnect_url\"].length > 0) {\n result.nostrconnectUrl = obj[\"nostrconnect_url\"];\n }\n\n if (result.relays === undefined && result.nostrconnectUrl === undefined) {\n return undefined;\n }\n return result;\n}\n\n/** Parse and validate a nostr.json document body. */\nexport function parseNip05Document(json: unknown): Nip05Document {\n if (!isRecord(json)) {\n throw new Nip05Error(\"NIP-05 document must be a JSON object\");\n }\n const raw = json;\n if (!isRecord(raw[\"names\"])) {\n throw new Nip05Error(\"NIP-05 document missing names map\");\n }\n\n const names: Record<string, string> = {};\n for (const [k, v] of Object.entries(raw[\"names\"])) {\n if (typeof v !== \"string\" || !isHex32(v.toLowerCase())) {\n continue;\n }\n names[k.toLowerCase()] = v.toLowerCase();\n }\n\n const rawRelays = raw[\"relays\"];\n let relays: Record<string, string[]> | undefined;\n if (isRecord(rawRelays)) {\n relays = {};\n for (const [pk, list] of Object.entries(rawRelays)) {\n if (!isHex32(pk.toLowerCase())) {\n continue;\n }\n const urls = stringUrls(list);\n if (urls !== undefined && urls.length > 0) {\n relays[pk.toLowerCase()] = urls;\n }\n }\n }\n\n const doc: Mutable<Nip05Document> = { names };\n if (relays !== undefined) {\n doc.relays = relays;\n }\n const nip46 = parseNip05Nip46(raw[\"nip46\"]);\n if (nip46 !== undefined) {\n doc.nip46 = nip46;\n }\n return doc;\n}\n\n/** Resolve local name from an already-parsed document. */\nexport function lookupFromDocument(\n doc: Nip05Document,\n address: Nip05Address,\n): ProfilePointer | undefined {\n const pubkey = doc.names[address.local];\n if (pubkey === undefined) {\n return undefined;\n }\n const relays = doc.relays?.[pubkey];\n return relays !== undefined && relays.length > 0 ? { pubkey, relays: [...relays] } : { pubkey };\n}\n\n/**\n * Fetch and parse `/.well-known/nostr.json`. Returns `undefined` on network/parse failure (does not\n * throw for those). Rejects HTTP redirects (non-200) per NIP-05 security constraints. AbortError\n * rethrows; it is not mapped to `undefined`.\n */\nexport async function queryNip05Document(\n identifier: string,\n opts?: { fetch?: Nip05Fetch; signal?: AbortSignal },\n): Promise<{ address: Nip05Address; doc: Nip05Document } | undefined> {\n let address: Nip05Address;\n try {\n address = parseNip05(identifier);\n } catch {\n return undefined;\n }\n\n const url = wellKnownUrl(address);\n const fetchImpl =\n opts?.fetch ??\n requireGlobalFetch(() => new Nip05Error(\"no fetch implementation available; pass opts.fetch\"));\n\n try {\n const res = await fetchManual(fetchImpl, url, { signal: opts?.signal }, (err) =>\n err instanceof Error ? err : new Error(\"NIP-05 request failed\"),\n );\n // Redirects and errors must not be trusted (NIP-05 security).\n if (res.status !== 200) {\n return undefined;\n }\n const json = await res.json();\n return { address, doc: parseNip05Document(json) };\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n throw error;\n }\n return undefined;\n }\n}\n\n/**\n * Query `/.well-known/nostr.json` for an identifier. Returns `undefined` on network/parse/lookup\n * failure (does not throw for those). Profile `relays` only. `nip46` is discovery metadata, not a\n * bunker pointer.\n */\nexport async function queryProfile(\n identifier: string,\n opts?: { fetch?: Nip05Fetch; signal?: AbortSignal },\n): Promise<ProfilePointer | undefined> {\n const fetched = await queryNip05Document(identifier, opts);\n if (fetched === undefined) {\n return undefined;\n }\n return lookupFromDocument(fetched.doc, fetched.address);\n}\n\n/** True when the identifier resolves to exactly `pubkey`. */\nexport async function verifyNip05(\n pubkey: string,\n identifier: string,\n opts?: { fetch?: Nip05Fetch; signal?: AbortSignal },\n): Promise<boolean> {\n if (!isHex32(pubkey.toLowerCase())) {\n return false;\n }\n const profile = await queryProfile(identifier, opts);\n return profile !== undefined && profile.pubkey === pubkey.toLowerCase();\n}\n"],"mappings":"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"}
@@ -1,19 +1,19 @@
1
1
  import { _ as Tag, t as Event } from "../event-DJeb_DIL.mjs";
2
- import { t as EventBuilder } from "../builder-DgkqDylK.mjs";
3
- import { f as ProfilePointer, i as EventPointer, t as AddressPointer } from "../nip19-D1OnJ192.mjs";
2
+ import { t as EventBuilder } from "../builder-CTwkB2zM.mjs";
3
+ import { f as ProfilePointer, i as EventPointer, t as AddressPointer } from "../nip19-DpPN17bY.mjs";
4
4
  //#region src/nips/nip10.d.ts
5
5
  /** Parsed NIP-10 thread references from an event's `e`/`q`/`p` tags. */
6
6
  export type ThreadReferences = {
7
7
  /** Pointer to the root of the thread. */
8
- root: EventPointer | undefined;
8
+ readonly root: EventPointer | undefined;
9
9
  /** Pointer to the parent event this note replies to. */
10
- reply: EventPointer | undefined;
10
+ readonly reply: EventPointer | undefined;
11
11
  /** Other e-tagged events (not root/reply). */
12
- mentions: EventPointer[];
12
+ readonly mentions: ReadonlyArray<EventPointer>;
13
13
  /** Quoted events (`q` tags): event ids or addresses. Discriminate with `"id" in q`. */
14
- quotes: Array<EventPointer | AddressPointer>;
14
+ readonly quotes: ReadonlyArray<EventPointer | AddressPointer>;
15
15
  /** P-tagged profiles involved in the thread. */
16
- profiles: ProfilePointer[];
16
+ readonly profiles: ReadonlyArray<ProfilePointer>;
17
17
  };
18
18
  type ReplyParent = Pick<Event, "id" | "pubkey" | "tags" | "kind">;
19
19
  type QuoteInput = string | EventPointer | AddressPointer;