@bsv/sdk 1.2.20 → 1.2.21

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 (154) hide show
  1. package/dist/cjs/package.json +3 -3
  2. package/dist/cjs/src/auth/Peer.js +536 -0
  3. package/dist/cjs/src/auth/Peer.js.map +1 -0
  4. package/dist/cjs/src/auth/SessionManager.js +66 -0
  5. package/dist/cjs/src/auth/SessionManager.js.map +1 -0
  6. package/dist/cjs/src/auth/{Certificate.js → certificates/Certificate.js} +22 -26
  7. package/dist/cjs/src/auth/certificates/Certificate.js.map +1 -0
  8. package/dist/cjs/src/auth/certificates/MasterCertificate.js +79 -0
  9. package/dist/cjs/src/auth/certificates/MasterCertificate.js.map +1 -0
  10. package/dist/cjs/src/auth/certificates/VerifiableCertificate.js +49 -0
  11. package/dist/cjs/src/auth/certificates/VerifiableCertificate.js.map +1 -0
  12. package/dist/cjs/src/auth/certificates/index.js +25 -0
  13. package/dist/cjs/src/auth/certificates/index.js.map +1 -0
  14. package/dist/cjs/src/auth/clients/AuthFetch.js +411 -0
  15. package/dist/cjs/src/auth/clients/AuthFetch.js.map +1 -0
  16. package/dist/cjs/src/auth/clients/index.js +18 -0
  17. package/dist/cjs/src/auth/clients/index.js.map +1 -0
  18. package/dist/cjs/src/auth/index.js +20 -5
  19. package/dist/cjs/src/auth/index.js.map +1 -1
  20. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js +259 -0
  21. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js.map +1 -0
  22. package/dist/cjs/src/auth/transports/index.js +18 -0
  23. package/dist/cjs/src/auth/transports/index.js.map +1 -0
  24. package/dist/cjs/src/auth/types.js +3 -0
  25. package/dist/cjs/src/auth/types.js.map +1 -0
  26. package/dist/cjs/src/auth/utils/certificateHelpers.js +51 -0
  27. package/dist/cjs/src/auth/utils/certificateHelpers.js.map +1 -0
  28. package/dist/cjs/src/auth/utils/createNonce.js +19 -0
  29. package/dist/cjs/src/auth/utils/createNonce.js.map +1 -0
  30. package/dist/cjs/src/auth/utils/getVerifiableCertificates.js +31 -0
  31. package/dist/cjs/src/auth/utils/getVerifiableCertificates.js.map +1 -0
  32. package/dist/cjs/src/auth/utils/index.js +21 -0
  33. package/dist/cjs/src/auth/utils/index.js.map +1 -0
  34. package/dist/cjs/src/auth/utils/validateCertificates.js +42 -0
  35. package/dist/cjs/src/auth/utils/validateCertificates.js.map +1 -0
  36. package/dist/cjs/src/auth/utils/verifyNonce.js +27 -0
  37. package/dist/cjs/src/auth/utils/verifyNonce.js.map +1 -0
  38. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js +1 -1
  39. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  40. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js +148 -148
  41. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
  42. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  43. package/dist/esm/src/auth/Peer.js +533 -0
  44. package/dist/esm/src/auth/Peer.js.map +1 -0
  45. package/dist/esm/src/auth/SessionManager.js +63 -0
  46. package/dist/esm/src/auth/SessionManager.js.map +1 -0
  47. package/dist/esm/src/auth/{Certificate.js → certificates/Certificate.js} +1 -2
  48. package/dist/esm/src/auth/certificates/Certificate.js.map +1 -0
  49. package/dist/esm/src/auth/certificates/MasterCertificate.js +73 -0
  50. package/dist/esm/src/auth/certificates/MasterCertificate.js.map +1 -0
  51. package/dist/esm/src/auth/certificates/VerifiableCertificate.js +44 -0
  52. package/dist/esm/src/auth/certificates/VerifiableCertificate.js.map +1 -0
  53. package/dist/esm/src/auth/certificates/index.js +4 -0
  54. package/dist/esm/src/auth/certificates/index.js.map +1 -0
  55. package/dist/esm/src/auth/clients/AuthFetch.js +409 -0
  56. package/dist/esm/src/auth/clients/AuthFetch.js.map +1 -0
  57. package/dist/esm/src/auth/clients/index.js +2 -0
  58. package/dist/esm/src/auth/clients/index.js.map +1 -0
  59. package/dist/esm/src/auth/index.js +7 -1
  60. package/dist/esm/src/auth/index.js.map +1 -1
  61. package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js +258 -0
  62. package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js.map +1 -0
  63. package/dist/esm/src/auth/transports/index.js +2 -0
  64. package/dist/esm/src/auth/transports/index.js.map +1 -0
  65. package/dist/esm/src/auth/types.js +2 -0
  66. package/dist/esm/src/auth/types.js.map +1 -0
  67. package/dist/esm/src/auth/utils/certificateHelpers.js +47 -0
  68. package/dist/esm/src/auth/utils/certificateHelpers.js.map +1 -0
  69. package/dist/esm/src/auth/utils/createNonce.js +16 -0
  70. package/dist/esm/src/auth/utils/createNonce.js.map +1 -0
  71. package/dist/esm/src/auth/utils/getVerifiableCertificates.js +27 -0
  72. package/dist/esm/src/auth/utils/getVerifiableCertificates.js.map +1 -0
  73. package/dist/esm/src/auth/utils/index.js +5 -0
  74. package/dist/esm/src/auth/utils/index.js.map +1 -0
  75. package/dist/esm/src/auth/utils/validateCertificates.js +38 -0
  76. package/dist/esm/src/auth/utils/validateCertificates.js.map +1 -0
  77. package/dist/esm/src/auth/utils/verifyNonce.js +24 -0
  78. package/dist/esm/src/auth/utils/verifyNonce.js.map +1 -0
  79. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js +1 -1
  80. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  81. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js +1 -1
  82. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
  83. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  84. package/dist/types/src/auth/Peer.d.ts +193 -0
  85. package/dist/types/src/auth/Peer.d.ts.map +1 -0
  86. package/dist/types/src/auth/SessionManager.d.ts +42 -0
  87. package/dist/types/src/auth/SessionManager.d.ts.map +1 -0
  88. package/dist/types/src/auth/{Certificate.d.ts → certificates/Certificate.d.ts} +1 -1
  89. package/dist/types/src/auth/certificates/Certificate.d.ts.map +1 -0
  90. package/dist/types/src/auth/certificates/MasterCertificate.d.ts +38 -0
  91. package/dist/types/src/auth/certificates/MasterCertificate.d.ts.map +1 -0
  92. package/dist/types/src/auth/certificates/VerifiableCertificate.d.ts +26 -0
  93. package/dist/types/src/auth/certificates/VerifiableCertificate.d.ts.map +1 -0
  94. package/dist/types/src/auth/certificates/index.d.ts +4 -0
  95. package/dist/types/src/auth/certificates/index.d.ts.map +1 -0
  96. package/dist/types/src/auth/clients/AuthFetch.d.ts +87 -0
  97. package/dist/types/src/auth/clients/AuthFetch.d.ts.map +1 -0
  98. package/dist/types/src/auth/clients/index.d.ts +2 -0
  99. package/dist/types/src/auth/clients/index.d.ts.map +1 -0
  100. package/dist/types/src/auth/index.d.ts +7 -1
  101. package/dist/types/src/auth/index.d.ts.map +1 -1
  102. package/dist/types/src/auth/transports/SimplifiedFetchTransport.d.ts +51 -0
  103. package/dist/types/src/auth/transports/SimplifiedFetchTransport.d.ts.map +1 -0
  104. package/dist/types/src/auth/transports/index.d.ts +2 -0
  105. package/dist/types/src/auth/transports/index.d.ts.map +1 -0
  106. package/dist/types/src/auth/types.d.ts +31 -0
  107. package/dist/types/src/auth/types.d.ts.map +1 -0
  108. package/dist/types/src/auth/utils/certificateHelpers.d.ts +26 -0
  109. package/dist/types/src/auth/utils/certificateHelpers.d.ts.map +1 -0
  110. package/dist/types/src/auth/utils/createNonce.d.ts +8 -0
  111. package/dist/types/src/auth/utils/createNonce.d.ts.map +1 -0
  112. package/dist/types/src/auth/utils/getVerifiableCertificates.d.ts +13 -0
  113. package/dist/types/src/auth/utils/getVerifiableCertificates.d.ts.map +1 -0
  114. package/dist/types/src/auth/utils/index.d.ts +5 -0
  115. package/dist/types/src/auth/utils/index.d.ts.map +1 -0
  116. package/dist/types/src/auth/utils/validateCertificates.d.ts +12 -0
  117. package/dist/types/src/auth/utils/validateCertificates.d.ts.map +1 -0
  118. package/dist/types/src/auth/utils/verifyNonce.d.ts +9 -0
  119. package/dist/types/src/auth/utils/verifyNonce.d.ts.map +1 -0
  120. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  121. package/dist/umd/bundle.js +1 -1
  122. package/docs/README.md +1 -0
  123. package/docs/auth.md +1119 -0
  124. package/package.json +13 -3
  125. package/src/auth/Peer.ts +600 -0
  126. package/src/auth/SessionManager.ts +71 -0
  127. package/src/auth/__tests/Peer.test.ts +599 -0
  128. package/src/auth/__tests/SessionManager.test.ts +87 -0
  129. package/src/auth/{Certificate.ts → certificates/Certificate.ts} +15 -8
  130. package/src/auth/certificates/MasterCertificate.ts +106 -0
  131. package/src/auth/certificates/VerifiableCertificate.ts +73 -0
  132. package/src/auth/certificates/__tests/Certificate.test.ts +282 -0
  133. package/src/auth/certificates/index.ts +3 -0
  134. package/src/auth/clients/AuthFetch.ts +482 -0
  135. package/src/auth/clients/index.ts +1 -0
  136. package/src/auth/index.ts +7 -1
  137. package/src/auth/transports/SimplifiedFetchTransport.ts +288 -0
  138. package/src/auth/transports/index.ts +1 -0
  139. package/src/auth/types.ts +41 -0
  140. package/src/auth/utils/__tests/cryptononce.test.ts +84 -0
  141. package/src/auth/utils/__tests/getVerifiableCertificates.test.ts +126 -0
  142. package/src/auth/utils/__tests/validateCertificates.test.ts +142 -0
  143. package/src/auth/utils/certificateHelpers.ts +86 -0
  144. package/src/auth/utils/createNonce.ts +16 -0
  145. package/src/auth/utils/getVerifiableCertificates.ts +40 -0
  146. package/src/auth/utils/index.ts +4 -0
  147. package/src/auth/utils/validateCertificates.ts +54 -0
  148. package/src/auth/utils/verifyNonce.ts +27 -0
  149. package/src/wallet/substrates/WalletWireProcessor.ts +1 -1
  150. package/src/wallet/substrates/WalletWireTransceiver.ts +1 -1
  151. package/dist/cjs/src/auth/Certificate.js.map +0 -1
  152. package/dist/esm/src/auth/Certificate.js.map +0 -1
  153. package/dist/types/src/auth/Certificate.d.ts.map +0 -1
  154. package/src/auth/__tests/Certificate.test.ts +0 -282
@@ -0,0 +1,482 @@
1
+ import { Utils, Random, P2PKH, PublicKey, Wallet } from '../../../mod.js'
2
+ import { Peer } from '../Peer.js'
3
+ import { SimplifiedFetchTransport } from '../transports/SimplifiedFetchTransport.js'
4
+ import { SessionManager } from '../SessionManager.js'
5
+ import { RequestedCertificateSet } from '../types.js'
6
+ import { VerifiableCertificate } from '../certificates/VerifiableCertificate.js'
7
+ import { Writer } from 'src/primitives/utils.js'
8
+
9
+ type SimplifiedFetchRequestOptions = {
10
+ method?: string,
11
+ headers?: Record<string, string>,
12
+ body?: any,
13
+ retryCounter?: number
14
+ }
15
+ type AuthPeer = { peer: Peer, identityKey?: string, supportsMutualAuth?: boolean }
16
+
17
+ const PAYMENT_VERSION = '1.0'
18
+
19
+ /**
20
+ * AuthFetch provides a lightweight fetch client for interacting with servers
21
+ * over a simplified HTTP transport mechanism. It integrates session management, peer communication,
22
+ * and certificate handling to enable secure and mutually-authenticated requests.
23
+ *
24
+ * Additionally, it automatically handles 402 Payment Required responses by creating
25
+ * and sending BSV payment transactions when necessary.
26
+ */
27
+ export class AuthFetch {
28
+ private sessionManager: SessionManager
29
+ private wallet: Wallet
30
+ private callbacks: Record<string, { resolve: Function, reject: Function }> = {}
31
+ private certificatesReceived: VerifiableCertificate[] = []
32
+ private requestedCertificates?: RequestedCertificateSet
33
+ peers: Record<string, AuthPeer> = {}
34
+
35
+ /**
36
+ * Constructs a new SimplifiedFetch instance.
37
+ * @param wallet - The wallet instance for signing and authentication.
38
+ * @param requestedCertificates - Optional set of certificates to request from peers.
39
+ */
40
+ constructor(wallet: Wallet, requestedCertificates?: RequestedCertificateSet, sessionManager?: SessionManager) {
41
+ this.wallet = wallet
42
+ this.requestedCertificates = requestedCertificates
43
+ this.sessionManager = sessionManager || new SessionManager()
44
+ }
45
+
46
+ /**
47
+ * Mutually authenticates and sends a HTTP request to a server.
48
+ *
49
+ * 1) Attempt the request.
50
+ * 2) If 402 Payment Required, automatically create and send payment.
51
+ * 3) Return the final response.
52
+ *
53
+ * @param url - The URL to send the request to.
54
+ * @param config - Configuration options for the request, including method, headers, and body.
55
+ * @returns A promise that resolves with the server's response, structured as a Response-like object.
56
+ *
57
+ * @throws Will throw an error if unsupported headers are used or other validation fails.
58
+ */
59
+ async fetch(url: string, config: SimplifiedFetchRequestOptions = {}): Promise<Response> {
60
+ if (config.retryCounter) {
61
+ if (config.retryCounter <= 0) {
62
+ throw new Error('Request failed after maximum number of retries.')
63
+ }
64
+ config.retryCounter--
65
+ }
66
+ const response = await new Promise<Response>(async (resolve, reject) => {
67
+ try {
68
+ // Apply defaults
69
+ const { method = 'GET', headers = {}, body } = config
70
+
71
+ // Extract a base url
72
+ const parsedUrl = new URL(url)
73
+ const baseURL = parsedUrl.origin
74
+
75
+ // Create a new transport for this base url if needed
76
+ let peerToUse: AuthPeer
77
+ if (!this.peers[baseURL]) {
78
+ // Create a peer for the request
79
+ const newTransport = new SimplifiedFetchTransport(baseURL)
80
+ peerToUse = {
81
+ peer: new Peer(this.wallet, newTransport, this.requestedCertificates, this.sessionManager)
82
+ }
83
+ this.peers[baseURL] = peerToUse
84
+ const callbackId = this.peers[baseURL].peer.listenForCertificatesReceived((senderPublicKey: string, certs: VerifiableCertificate[]) => {
85
+ this.certificatesReceived.push(...certs)
86
+ // peerToUse.peer.stopListeningForCertificatesReceived()
87
+ })
88
+ } else {
89
+ // Check if there's a session associated with this baseURL
90
+ if (this.peers[baseURL].supportsMutualAuth === false) {
91
+ // Use standard fetch if mutual authentication is not supported
92
+ try {
93
+ const response = await this.handleFetchAndValidate(url, config, this.peers[baseURL])
94
+ resolve(response)
95
+ } catch (error) {
96
+ reject(error)
97
+ }
98
+ }
99
+ peerToUse = this.peers[baseURL]
100
+ }
101
+
102
+ // Serialize the simplified fetch request.
103
+ const requestNonce = Random(32)
104
+ const requestNonceAsBase64 = Utils.toBase64(requestNonce)
105
+
106
+ const writer = await this.serializeRequest(
107
+ method,
108
+ headers,
109
+ body,
110
+ parsedUrl,
111
+ requestNonce
112
+ )
113
+
114
+ // Setup general message listener to resolve requests once a response is received
115
+ this.callbacks[requestNonceAsBase64] = { resolve, reject }
116
+ const listenerId = peerToUse.peer.listenForGeneralMessages((senderPublicKey: string, payload: number[]) => {
117
+ // Create a reader
118
+ const responseReader = new Utils.Reader(payload)
119
+ // Deserialize first 32 bytes of payload
120
+ const responseNonceAsBase64 = Utils.toBase64(responseReader.read(32))
121
+ if (responseNonceAsBase64 === requestNonceAsBase64) {
122
+ peerToUse.peer.stopListeningForGeneralMessages(listenerId)
123
+
124
+ // Save the identity key for the peer for future requests, since we have it here.
125
+ this.peers[baseURL].identityKey = senderPublicKey
126
+ this.peers[baseURL].supportsMutualAuth = true
127
+
128
+ // Status code
129
+ const statusCode = responseReader.readVarIntNum()
130
+
131
+ // Headers
132
+ const responseHeaders = {}
133
+ const nHeaders = responseReader.readVarIntNum()
134
+ if (nHeaders > 0) {
135
+ for (let i = 0; i < nHeaders; i++) {
136
+ const nHeaderKeyBytes = responseReader.readVarIntNum()
137
+ const headerKeyBytes = responseReader.read(nHeaderKeyBytes)
138
+ const headerKey = Utils.toUTF8(headerKeyBytes)
139
+ const nHeaderValueBytes = responseReader.readVarIntNum()
140
+ const headerValueBytes = responseReader.read(nHeaderValueBytes)
141
+ const headerValue = Utils.toUTF8(headerValueBytes)
142
+ responseHeaders[headerKey] = headerValue
143
+ }
144
+ }
145
+
146
+ // Add back the server identity key header
147
+ responseHeaders['x-bsv-auth-identity-key'] = senderPublicKey
148
+
149
+ // Body
150
+ let responseBody
151
+ const responseBodyBytes = responseReader.readVarIntNum()
152
+ if (responseBodyBytes > 0) {
153
+ responseBody = responseReader.read(responseBodyBytes)
154
+ }
155
+
156
+ // Create the Response object
157
+ const responseValue = new Response(
158
+ responseBody ? new Uint8Array(responseBody) : null, {
159
+ status: statusCode,
160
+ statusText: `${statusCode}`,
161
+ headers: new Headers(responseHeaders)
162
+ })
163
+
164
+ // Resolve or reject the correct request with the response data
165
+ this.callbacks[requestNonceAsBase64].resolve(responseValue)
166
+
167
+ // Clean up
168
+ delete this.callbacks[requestNonceAsBase64]
169
+ }
170
+ })
171
+
172
+ // Send the request, now that all listeners are set up
173
+ await peerToUse.peer.toPeer(writer.toArray(), peerToUse.identityKey).catch(async error => {
174
+ if (error.message.includes('HTTP server failed to authenticate')) {
175
+ try {
176
+ const response = await this.handleFetchAndValidate(url, config, peerToUse)
177
+ resolve(response)
178
+ } catch (fetchError) {
179
+ reject(fetchError)
180
+ }
181
+ } else {
182
+ reject(error)
183
+ }
184
+ })
185
+ } catch (e) {
186
+ reject(e)
187
+ }
188
+ })
189
+
190
+ // Check if server requires payment to access the requested route
191
+ if (response.status === 402) {
192
+ // Create and attach a payment, then retry
193
+ return await this.handlePaymentAndRetry(url, config, response)
194
+ }
195
+
196
+ return response
197
+ }
198
+
199
+ /**
200
+ * Request Certificates from a Peer
201
+ * @param baseUrl
202
+ * @param certificatesToRequest
203
+ */
204
+ async sendCertificateRequest(baseUrl: string, certificatesToRequest: RequestedCertificateSet): Promise<VerifiableCertificate[]> {
205
+ const parsedUrl = new URL(baseUrl)
206
+ const baseURL = parsedUrl.origin
207
+
208
+ let peerToUse: { peer: Peer; identityKey?: string }
209
+ if (this.peers[baseURL]) {
210
+ peerToUse = { peer: this.peers[baseURL].peer }
211
+ } else {
212
+ const newTransport = new SimplifiedFetchTransport(baseURL)
213
+ peerToUse = {
214
+ peer: new Peer(
215
+ this.wallet,
216
+ newTransport,
217
+ this.requestedCertificates,
218
+ this.sessionManager
219
+ )
220
+ }
221
+ this.peers[baseURL] = peerToUse
222
+ }
223
+
224
+ // Return a promise that resolves when certificates are received
225
+ return new Promise<VerifiableCertificate[]>(async (resolve, reject) => {
226
+ // Set up the listener before making the request
227
+ const callbackId = peerToUse.peer.listenForCertificatesReceived((_senderPublicKey: string, certs: VerifiableCertificate[]) => {
228
+ peerToUse.peer.stopListeningForCertificatesReceived(callbackId)
229
+ this.certificatesReceived.push(...certs)
230
+ resolve(certs)
231
+ })
232
+
233
+ try {
234
+ // Initiate the certificate request
235
+ await peerToUse.peer.requestCertificates(certificatesToRequest, peerToUse.identityKey)
236
+ } catch (err) {
237
+ peerToUse.peer.stopListeningForCertificatesReceived(callbackId)
238
+ reject(err)
239
+ }
240
+ })
241
+ }
242
+
243
+ /**
244
+ * Return any certificates we've collected thus far, then clear them out.
245
+ */
246
+ public consumeReceivedCertificates(): VerifiableCertificate[] {
247
+ return this.certificatesReceived.splice(0)
248
+ }
249
+
250
+ /**
251
+ * Serializes the HTTP request to be sent over the Transport.
252
+ *
253
+ * @param method - The HTTP method (e.g., 'GET', 'POST') for the request.
254
+ * @param headers - A record of HTTP headers to include in the request.
255
+ * @param body - The body of the request, if applicable (e.g., for POST/PUT requests).
256
+ * @param parsedUrl - The parsed URL object containing the full request URL.
257
+ * @param requestNonce - A unique random nonce to ensure request integrity.
258
+ * @returns A promise that resolves to a `Writer` containing the serialized request.
259
+ *
260
+ * @throws Will throw an error if unsupported headers are used or serialization fails.
261
+ */
262
+ private async serializeRequest(
263
+ method: string,
264
+ headers: Record<string, string>,
265
+ body: any,
266
+ parsedUrl: URL,
267
+ requestNonce: number[]
268
+ ): Promise<Writer> {
269
+ const writer = new Utils.Writer()
270
+ // Write request nonce
271
+ writer.write(requestNonce)
272
+ // Method length
273
+ writer.writeVarIntNum(method.length)
274
+ // Method
275
+ writer.write(Utils.toArray(method))
276
+
277
+ // Handle pathname (e.g. /path/to/resource)
278
+ if (parsedUrl.pathname.length > 0) {
279
+ // Pathname length
280
+ const pathnameAsArray = Utils.toArray(parsedUrl.pathname)
281
+ writer.writeVarIntNum(pathnameAsArray.length)
282
+ // Pathname
283
+ writer.write(pathnameAsArray)
284
+ } else {
285
+ writer.writeVarIntNum(-1)
286
+ }
287
+
288
+ // Handle search params (e.g. ?q=hello)
289
+ if (parsedUrl.search.length > 0) {
290
+ // search length
291
+ const searchAsArray = Utils.toArray(parsedUrl.search)
292
+ writer.writeVarIntNum(searchAsArray.length)
293
+ // search
294
+ writer.write(searchAsArray)
295
+ } else {
296
+ writer.writeVarIntNum(-1)
297
+ }
298
+
299
+ // Construct headers to send / sign:
300
+ // - Custom headers prefixed with x-bsv are included
301
+ // - x-bsv-auth headers are not allowed
302
+ // - content-type and authorization are signed by client
303
+ const includedHeaders: [string, string][] = []
304
+ for (let [k, v] of Object.entries(headers)) {
305
+ k = k.toLowerCase() // We will always sign lower-case header keys
306
+ if (k.startsWith('x-bsv-') || k === 'content-type' || k === 'authorization') {
307
+ if (k.startsWith('x-bsv-auth')) {
308
+ throw new Error('No BSV auth headers allowed here!')
309
+ }
310
+ includedHeaders.push([k, v])
311
+ } else {
312
+ throw new Error('Unsupported header in the simplified fetch implementation. Only content-type, authorization, and x-bsv-* headers are supported.')
313
+ }
314
+ }
315
+
316
+ // nHeaders
317
+ writer.writeVarIntNum(includedHeaders.length)
318
+ for (let i = 0; i < includedHeaders.length; i++) {
319
+ // headerKeyLength
320
+ const headerKeyAsArray = Utils.toArray(includedHeaders[i][0], 'utf8')
321
+ writer.writeVarIntNum(headerKeyAsArray.length)
322
+ // headerKey
323
+ writer.write(headerKeyAsArray)
324
+ // headerValueLength
325
+ const headerValueAsArray = Utils.toArray(includedHeaders[i][1], 'utf8')
326
+ writer.writeVarIntNum(headerValueAsArray.length)
327
+ // headerValue
328
+ writer.write(headerValueAsArray)
329
+ }
330
+
331
+ // Handle body
332
+ if (body) {
333
+ const reqBody = await this.normalizeBodyToNumberArray(body) // Use the utility function
334
+ writer.writeVarIntNum(reqBody.length)
335
+ writer.write(reqBody)
336
+ } else {
337
+ writer.writeVarIntNum(-1) // No body
338
+ }
339
+ return writer
340
+ }
341
+
342
+ /**
343
+ * Handles a non-authenticated fetch requests and validates that the server is not claiming to be authenticated.
344
+ */
345
+ private async handleFetchAndValidate(url: string, config: RequestInit, peerToUse: AuthPeer): Promise<Response> {
346
+ const response = await fetch(url, config)
347
+ response.headers.forEach(header => {
348
+ if (header.toLocaleLowerCase().startsWith('x-bsv')) {
349
+ throw new Error('The server is trying to claim it has been authenticated when it has not!')
350
+ }
351
+ })
352
+
353
+ if (response.ok) {
354
+ peerToUse.supportsMutualAuth = false
355
+ return response
356
+ } else {
357
+ throw new Error(`Request failed with status: ${response.status}`)
358
+ }
359
+ }
360
+
361
+ /**
362
+ * If we get 402 Payment Required, we build a transaction via wallet.createAction()
363
+ * and re-attempt the request with an x-bsv-payment header.
364
+ */
365
+ private async handlePaymentAndRetry(
366
+ url: string,
367
+ config: SimplifiedFetchRequestOptions = {},
368
+ originalResponse: Response
369
+ ): Promise<Response | null> {
370
+ // Make sure the server is using the correct payment version
371
+ const paymentVersion = originalResponse.headers.get('x-bsv-payment-version')
372
+ if (!paymentVersion || paymentVersion !== PAYMENT_VERSION) {
373
+ throw new Error(`Unsupported x-bsv-payment-version response header. Client version: ${PAYMENT_VERSION}, Server version: ${paymentVersion}`)
374
+ }
375
+
376
+ // Get required headers from the 402 response
377
+ const satoshisRequiredHeader = originalResponse.headers.get(
378
+ 'x-bsv-payment-satoshis-required'
379
+ )
380
+ if (!satoshisRequiredHeader) {
381
+ throw new Error('Missing x-bsv-payment-satoshis-required response header.')
382
+ }
383
+ const satoshisRequired = parseInt(satoshisRequiredHeader)
384
+ if (isNaN(satoshisRequired) || satoshisRequired <= 0) {
385
+ throw new Error('Invalid x-bsv-payment-satoshis-required response header value.')
386
+ }
387
+
388
+ const serverIdentityKey = originalResponse.headers.get('x-bsv-auth-identity-key')
389
+ if (!serverIdentityKey) {
390
+ throw new Error('Missing x-bsv-auth-identity-key response header.')
391
+ }
392
+
393
+ const derivationPrefix = originalResponse.headers.get('x-bsv-payment-derivation-prefix')
394
+ if (!derivationPrefix) {
395
+ throw new Error('Missing x-bsv-payment-derivation-prefix response header.')
396
+ }
397
+
398
+ // Create a random suffix for the derivation path
399
+ const derivationSuffix = Utils.toBase64(Random(10))
400
+
401
+ // Derive the script hex from the server identity key
402
+ const { publicKey: derivedPublicKey } = await this.wallet.getPublicKey({
403
+ protocolID: [2, 'wallet payment'],
404
+ keyID: `${derivationPrefix} ${derivationSuffix}`,
405
+ counterparty: serverIdentityKey
406
+ })
407
+ const lockingScript = new P2PKH().lock(PublicKey.fromString(derivedPublicKey).toHash()).toHex()
408
+
409
+ // Create the payment transaction using createAction
410
+ const { tx } = await this.wallet.createAction({
411
+ description: `Payment for request to ${new URL(url).origin}`,
412
+ outputs: [{
413
+ satoshis: satoshisRequired,
414
+ lockingScript,
415
+ outputDescription: 'HTTP request payment'
416
+ }]
417
+ })
418
+
419
+ // Attach the payment to the request headers
420
+ config.headers = config.headers || {}
421
+ config.headers['x-bsv-payment'] = JSON.stringify({
422
+ derivationPrefix,
423
+ transaction: Utils.toBase64(tx)
424
+ })
425
+ config.retryCounter ??= 3
426
+
427
+ // Re-attempt request with payment attached
428
+ return this.fetch(url, config)
429
+ }
430
+
431
+ private async normalizeBodyToNumberArray(body: BodyInit | null | undefined): Promise<number[]> {
432
+ // 1. Null / undefined
433
+ if (body == null) {
434
+ return []
435
+ }
436
+
437
+ // 2. number[]
438
+ if (Array.isArray(body) && body.every((item) => typeof item === 'number')) {
439
+ return body // Return the array as is
440
+ }
441
+
442
+ // 3. string
443
+ if (typeof body === 'string') {
444
+ return Utils.toArray(body, 'utf8')
445
+ }
446
+
447
+ // 4. ArrayBuffer / TypedArrays
448
+ if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
449
+ const typedArray = body instanceof ArrayBuffer ? new Uint8Array(body) : new Uint8Array(body.buffer)
450
+ return Array.from(typedArray)
451
+ }
452
+
453
+ // 5. Blob
454
+ if (body instanceof Blob) {
455
+ const arrayBuffer = await body.arrayBuffer()
456
+ return Array.from(new Uint8Array(arrayBuffer))
457
+ }
458
+
459
+ // 6. FormData
460
+ if (body instanceof FormData) {
461
+ const entries: [string, string][] = []
462
+ body.forEach((value, key) => {
463
+ entries.push([key, value.toString()])
464
+ })
465
+ const urlEncoded = new URLSearchParams(entries).toString()
466
+ return Utils.toArray(urlEncoded, 'utf8')
467
+ }
468
+
469
+ // 7. URLSearchParams
470
+ if (body instanceof URLSearchParams) {
471
+ return Utils.toArray(body.toString(), 'utf8')
472
+ }
473
+
474
+ // 8. ReadableStream
475
+ if (body instanceof ReadableStream) {
476
+ throw new Error('ReadableStream cannot be directly converted to number[].')
477
+ }
478
+
479
+ // 9. Fallback
480
+ throw new Error('Unsupported body type in this SimplifiedFetch implementation.')
481
+ }
482
+ }
@@ -0,0 +1 @@
1
+ export * from './AuthFetch.js'
package/src/auth/index.ts CHANGED
@@ -1 +1,7 @@
1
- export { default as Certificate } from './Certificate.js'
1
+ export * from './certificates/index.js'
2
+ export * from './Peer.js'
3
+ export * from './SessionManager.js'
4
+ export * from './types.js'
5
+ export * from './utils/index.js'
6
+ export * from './clients/index.js'
7
+ export * from './transports/index.js'