@bsv/sdk 1.8.5 → 1.8.7
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.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/auth/Peer.js +21 -6
- package/dist/cjs/src/auth/Peer.js.map +1 -1
- package/dist/cjs/src/auth/clients/AuthFetch.js +229 -13
- package/dist/cjs/src/auth/clients/AuthFetch.js.map +1 -1
- package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js +189 -0
- package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js.map +1 -0
- package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js +162 -36
- package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js.map +1 -1
- package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +134 -0
- package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +1 -0
- package/dist/cjs/src/overlay-tools/LookupResolver.js +4 -4
- package/dist/cjs/src/overlay-tools/LookupResolver.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/auth/Peer.js +21 -6
- package/dist/esm/src/auth/Peer.js.map +1 -1
- package/dist/esm/src/auth/clients/AuthFetch.js +229 -13
- package/dist/esm/src/auth/clients/AuthFetch.js.map +1 -1
- package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js +187 -0
- package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js.map +1 -0
- package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js +162 -36
- package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js.map +1 -1
- package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +109 -0
- package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +1 -0
- package/dist/esm/src/overlay-tools/LookupResolver.js +4 -4
- package/dist/esm/src/overlay-tools/LookupResolver.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/auth/Peer.d.ts +1 -0
- package/dist/types/src/auth/Peer.d.ts.map +1 -1
- package/dist/types/src/auth/clients/AuthFetch.d.ts +37 -0
- package/dist/types/src/auth/clients/AuthFetch.d.ts.map +1 -1
- package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts +2 -0
- package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts.map +1 -0
- package/dist/types/src/auth/transports/SimplifiedFetchTransport.d.ts +6 -0
- package/dist/types/src/auth/transports/SimplifiedFetchTransport.d.ts.map +1 -1
- package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts +2 -0
- package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts.map +1 -0
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +3 -3
- package/dist/umd/bundle.js.map +1 -1
- package/package.json +1 -1
- package/src/auth/Peer.ts +25 -18
- package/src/auth/__tests/Peer.test.ts +238 -1
- package/src/auth/clients/AuthFetch.ts +327 -18
- package/src/auth/clients/__tests__/AuthFetch.test.ts +262 -0
- package/src/auth/transports/SimplifiedFetchTransport.ts +185 -35
- package/src/auth/transports/__tests__/SimplifiedFetchTransport.test.ts +126 -0
- package/src/overlay-tools/LookupResolver.ts +3 -3
|
@@ -44,27 +44,37 @@ export class SimplifiedFetchTransport implements Transport {
|
|
|
44
44
|
return await new Promise((resolve, reject) => {
|
|
45
45
|
void (async () => {
|
|
46
46
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
const authUrl = `${this.baseUrl}/.well-known/auth`
|
|
48
|
+
const responsePromise = (async () => {
|
|
49
|
+
try {
|
|
50
|
+
return await this.fetchClient(authUrl, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: {
|
|
53
|
+
'Content-Type': 'application/json'
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify(message)
|
|
56
|
+
})
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw this.createNetworkError(authUrl, error)
|
|
59
|
+
}
|
|
60
|
+
})()
|
|
54
61
|
|
|
55
62
|
// For initialRequest message, mark connection as established and start pool.
|
|
56
63
|
if (message.messageType !== 'initialRequest') {
|
|
57
64
|
resolve()
|
|
58
65
|
}
|
|
66
|
+
|
|
59
67
|
const response = await responsePromise
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
const responseBodyArray = Array.from(new Uint8Array(await response.arrayBuffer()))
|
|
70
|
+
throw this.createUnauthenticatedResponseError(authUrl, response, responseBodyArray)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (this.onDataCallback != null) {
|
|
62
74
|
const responseMessage = await response.json()
|
|
63
75
|
this.onDataCallback(responseMessage as AuthMessage)
|
|
64
|
-
} else {
|
|
65
|
-
// Server may be a non authenticated server
|
|
66
|
-
throw new Error('HTTP server failed to authenticate')
|
|
67
76
|
}
|
|
77
|
+
|
|
68
78
|
if (message.messageType === 'initialRequest') {
|
|
69
79
|
resolve()
|
|
70
80
|
}
|
|
@@ -118,22 +128,39 @@ export class SimplifiedFetchTransport implements Transport {
|
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
// Send the actual fetch request to the server
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
let response: Response
|
|
132
|
+
try {
|
|
133
|
+
response = await this.fetchClient(url, {
|
|
134
|
+
method: httpRequestWithAuthHeaders.method,
|
|
135
|
+
headers: httpRequestWithAuthHeaders.headers,
|
|
136
|
+
body: httpRequestWithAuthHeaders.body
|
|
137
|
+
})
|
|
138
|
+
} catch (error) {
|
|
139
|
+
throw this.createNetworkError(url, error)
|
|
140
|
+
}
|
|
126
141
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
142
|
+
const responseBodyBuffer = await response.arrayBuffer()
|
|
143
|
+
const responseBodyArray = Array.from(new Uint8Array(responseBodyBuffer))
|
|
144
|
+
|
|
145
|
+
const missingAuthHeaders = ['x-bsv-auth-version', 'x-bsv-auth-identity-key', 'x-bsv-auth-signature']
|
|
146
|
+
.filter(headerName => {
|
|
147
|
+
const headerValue = response.headers.get(headerName)
|
|
148
|
+
return headerValue == null || headerValue.trim().length === 0
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
if (missingAuthHeaders.length > 0) {
|
|
152
|
+
throw this.createUnauthenticatedResponseError(url, response, responseBodyArray, missingAuthHeaders)
|
|
134
153
|
}
|
|
135
154
|
|
|
136
|
-
const
|
|
155
|
+
const requestedCertificatesHeader = response.headers.get('x-bsv-auth-requested-certificates')
|
|
156
|
+
let requestedCertificates: RequestedCertificateSet | undefined
|
|
157
|
+
if (requestedCertificatesHeader != null) {
|
|
158
|
+
try {
|
|
159
|
+
requestedCertificates = JSON.parse(requestedCertificatesHeader) as RequestedCertificateSet
|
|
160
|
+
} catch (error) {
|
|
161
|
+
throw this.createMalformedHeaderError(url, 'x-bsv-auth-requested-certificates', requestedCertificatesHeader, error)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
137
164
|
const payloadWriter = new Utils.Writer()
|
|
138
165
|
if (response.headers.get('x-bsv-auth-request-id') != null) {
|
|
139
166
|
payloadWriter.write(Utils.toArray(response.headers.get('x-bsv-auth-request-id'), 'base64'))
|
|
@@ -171,12 +198,9 @@ export class SimplifiedFetchTransport implements Transport {
|
|
|
171
198
|
}
|
|
172
199
|
|
|
173
200
|
// Handle body
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
payloadWriter.
|
|
177
|
-
payloadWriter.write(bodyAsArray)
|
|
178
|
-
} else {
|
|
179
|
-
payloadWriter.writeVarIntNum(-1)
|
|
201
|
+
payloadWriter.writeVarIntNum(responseBodyArray.length)
|
|
202
|
+
if (responseBodyArray.length > 0) {
|
|
203
|
+
payloadWriter.write(responseBodyArray)
|
|
180
204
|
}
|
|
181
205
|
|
|
182
206
|
// Build the correct AuthMessage for the response
|
|
@@ -184,16 +208,16 @@ export class SimplifiedFetchTransport implements Transport {
|
|
|
184
208
|
version: response.headers.get('x-bsv-auth-version'),
|
|
185
209
|
messageType: response.headers.get('x-bsv-auth-message-type') === 'certificateRequest' ? 'certificateRequest' : 'general',
|
|
186
210
|
identityKey: response.headers.get('x-bsv-auth-identity-key'),
|
|
187
|
-
nonce: response.headers.get('x-bsv-auth-nonce'),
|
|
188
|
-
yourNonce: response.headers.get('x-bsv-auth-your-nonce'),
|
|
189
|
-
requestedCertificates
|
|
211
|
+
nonce: response.headers.get('x-bsv-auth-nonce') ?? undefined,
|
|
212
|
+
yourNonce: response.headers.get('x-bsv-auth-your-nonce') ?? undefined,
|
|
213
|
+
requestedCertificates,
|
|
190
214
|
payload: payloadWriter.toArray(),
|
|
191
215
|
signature: Utils.toArray(response.headers.get('x-bsv-auth-signature'), 'hex')
|
|
192
216
|
}
|
|
193
217
|
|
|
194
218
|
// If the server didn't provide the correct authentication headers, throw an error
|
|
195
219
|
if (responseMessage.version == null) {
|
|
196
|
-
throw
|
|
220
|
+
throw this.createUnauthenticatedResponseError(url, response, responseBodyArray)
|
|
197
221
|
}
|
|
198
222
|
|
|
199
223
|
// Handle the response if data is received and callback is set
|
|
@@ -214,6 +238,132 @@ export class SimplifiedFetchTransport implements Transport {
|
|
|
214
238
|
}
|
|
215
239
|
}
|
|
216
240
|
|
|
241
|
+
private createNetworkError (url: string, originalError: unknown): Error {
|
|
242
|
+
const baseMessage = `Network error while sending authenticated request to ${url}`
|
|
243
|
+
if (originalError instanceof Error) {
|
|
244
|
+
const error = new Error(`${baseMessage}: ${originalError.message}`)
|
|
245
|
+
error.stack = originalError.stack
|
|
246
|
+
;(error as any).cause = originalError
|
|
247
|
+
return error
|
|
248
|
+
}
|
|
249
|
+
return new Error(`${baseMessage}: ${String(originalError)}`)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private createUnauthenticatedResponseError (
|
|
253
|
+
url: string,
|
|
254
|
+
response: Response,
|
|
255
|
+
bodyBytes: number[],
|
|
256
|
+
missingHeaders: string[] = []
|
|
257
|
+
): Error {
|
|
258
|
+
const statusText = (response.statusText ?? '').trim()
|
|
259
|
+
const statusDescription = statusText.length > 0
|
|
260
|
+
? `${response.status} ${statusText}`
|
|
261
|
+
: `${response.status}`
|
|
262
|
+
const headerMessage = missingHeaders.length > 0
|
|
263
|
+
? `missing headers: ${missingHeaders.join(', ')}`
|
|
264
|
+
: 'response lacked required BSV auth headers'
|
|
265
|
+
const bodyPreview = this.getBodyPreview(bodyBytes, response.headers.get('content-type'))
|
|
266
|
+
const parts = [`Received HTTP ${statusDescription} from ${url} without valid BSV authentication (${headerMessage})`]
|
|
267
|
+
if (bodyPreview != null) {
|
|
268
|
+
parts.push(`body preview: ${bodyPreview}`)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const error = new Error(parts.join(' - '))
|
|
272
|
+
;(error as any).details = {
|
|
273
|
+
url,
|
|
274
|
+
status: response.status,
|
|
275
|
+
statusText: response.statusText,
|
|
276
|
+
missingHeaders,
|
|
277
|
+
bodyPreview
|
|
278
|
+
}
|
|
279
|
+
return error
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private createMalformedHeaderError (
|
|
283
|
+
url: string,
|
|
284
|
+
headerName: string,
|
|
285
|
+
headerValue: string,
|
|
286
|
+
cause: unknown
|
|
287
|
+
): Error {
|
|
288
|
+
const errorMessage = `Failed to parse ${headerName} returned by ${url}: ${headerValue}`
|
|
289
|
+
if (cause instanceof Error) {
|
|
290
|
+
const error = new Error(`${errorMessage}. ${cause.message}`)
|
|
291
|
+
error.stack = cause.stack
|
|
292
|
+
;(error as any).cause = cause
|
|
293
|
+
return error
|
|
294
|
+
}
|
|
295
|
+
return new Error(`${errorMessage}. ${String(cause)}`)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private getBodyPreview (bodyBytes: number[], contentType: string | null): string | undefined {
|
|
299
|
+
if (bodyBytes.length === 0) {
|
|
300
|
+
return undefined
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const maxBytesForPreview = 1024
|
|
304
|
+
const truncated = bodyBytes.length > maxBytesForPreview
|
|
305
|
+
const slice = truncated ? bodyBytes.slice(0, maxBytesForPreview) : bodyBytes
|
|
306
|
+
const isText = this.isTextualContent(contentType, slice)
|
|
307
|
+
|
|
308
|
+
let preview: string
|
|
309
|
+
if (isText) {
|
|
310
|
+
try {
|
|
311
|
+
preview = Utils.toUTF8(slice)
|
|
312
|
+
} catch {
|
|
313
|
+
preview = this.formatBinaryPreview(slice, truncated)
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
preview = this.formatBinaryPreview(slice, truncated)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (preview.length > 512) {
|
|
320
|
+
preview = `${preview.slice(0, 512)}…`
|
|
321
|
+
}
|
|
322
|
+
if (truncated) {
|
|
323
|
+
preview = `${preview} (truncated)`
|
|
324
|
+
}
|
|
325
|
+
return preview
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private isTextualContent (contentType: string | null, sample: number[]): boolean {
|
|
329
|
+
if (sample.length === 0) {
|
|
330
|
+
return false
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (contentType != null) {
|
|
334
|
+
const lowered = contentType.toLowerCase()
|
|
335
|
+
const textualTokens = [
|
|
336
|
+
'application/json',
|
|
337
|
+
'application/problem+json',
|
|
338
|
+
'application/xml',
|
|
339
|
+
'application/xhtml+xml',
|
|
340
|
+
'application/javascript',
|
|
341
|
+
'application/ecmascript',
|
|
342
|
+
'application/x-www-form-urlencoded',
|
|
343
|
+
'text/'
|
|
344
|
+
]
|
|
345
|
+
if (textualTokens.some(token => lowered.includes(token)) || lowered.includes('charset=')) {
|
|
346
|
+
return true
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const printableCount = sample.reduce((count, byte) => {
|
|
351
|
+
if (byte === 9 || byte === 10 || byte === 13) {
|
|
352
|
+
return count + 1
|
|
353
|
+
}
|
|
354
|
+
if (byte >= 32 && byte <= 126) {
|
|
355
|
+
return count + 1
|
|
356
|
+
}
|
|
357
|
+
return count
|
|
358
|
+
}, 0)
|
|
359
|
+
return (printableCount / sample.length) > 0.8
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private formatBinaryPreview (bytes: number[], truncated: boolean): string {
|
|
363
|
+
const hex = bytes.map(byte => byte.toString(16).padStart(2, '0')).join('')
|
|
364
|
+
return `0x${hex}${truncated ? '…' : ''}`
|
|
365
|
+
}
|
|
366
|
+
|
|
217
367
|
/**
|
|
218
368
|
* Deserializes a request payload from a byte array into an HTTP request-like structure.
|
|
219
369
|
*
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { jest } from '@jest/globals'
|
|
2
|
+
import { SimplifiedFetchTransport } from '../SimplifiedFetchTransport.js'
|
|
3
|
+
import * as Utils from '../../../primitives/utils.js'
|
|
4
|
+
import { AuthMessage } from '../../types.js'
|
|
5
|
+
|
|
6
|
+
function createGeneralPayload (path = '/resource', method = 'GET'): number[] {
|
|
7
|
+
const writer = new Utils.Writer()
|
|
8
|
+
const requestId = new Array(32).fill(1)
|
|
9
|
+
writer.write(requestId)
|
|
10
|
+
|
|
11
|
+
const methodBytes = Utils.toArray(method, 'utf8')
|
|
12
|
+
writer.writeVarIntNum(methodBytes.length)
|
|
13
|
+
writer.write(methodBytes)
|
|
14
|
+
|
|
15
|
+
const pathBytes = Utils.toArray(path, 'utf8')
|
|
16
|
+
writer.writeVarIntNum(pathBytes.length)
|
|
17
|
+
writer.write(pathBytes)
|
|
18
|
+
|
|
19
|
+
writer.writeVarIntNum(-1) // no query string
|
|
20
|
+
writer.writeVarIntNum(0) // no headers
|
|
21
|
+
writer.writeVarIntNum(-1) // no body
|
|
22
|
+
|
|
23
|
+
return writer.toArray()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createGeneralMessage (overrides: Partial<AuthMessage> = {}): AuthMessage {
|
|
27
|
+
return {
|
|
28
|
+
version: '1.0',
|
|
29
|
+
messageType: 'general',
|
|
30
|
+
identityKey: 'client-key',
|
|
31
|
+
nonce: 'client-nonce',
|
|
32
|
+
yourNonce: 'server-nonce',
|
|
33
|
+
payload: createGeneralPayload(),
|
|
34
|
+
signature: new Array(64).fill(0),
|
|
35
|
+
...overrides
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
jest.restoreAllMocks()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('SimplifiedFetchTransport send', () => {
|
|
44
|
+
test('wraps network failures with context', async () => {
|
|
45
|
+
const fetchMock: jest.MockedFunction<typeof fetch> = jest.fn()
|
|
46
|
+
fetchMock.mockRejectedValue(new Error('network down'))
|
|
47
|
+
const transport = new SimplifiedFetchTransport('https://api.example.com', fetchMock as any)
|
|
48
|
+
await transport.onData(async () => {})
|
|
49
|
+
const message = createGeneralMessage()
|
|
50
|
+
|
|
51
|
+
let caught: any
|
|
52
|
+
await expect((async () => {
|
|
53
|
+
try {
|
|
54
|
+
await transport.send(message)
|
|
55
|
+
} catch (error) {
|
|
56
|
+
caught = error
|
|
57
|
+
throw error
|
|
58
|
+
}
|
|
59
|
+
})()).rejects.toThrow('Network error while sending authenticated request to https://api.example.com/resource: network down')
|
|
60
|
+
|
|
61
|
+
expect(fetchMock).toHaveBeenCalledTimes(1)
|
|
62
|
+
expect(fetchMock.mock.calls[0][0]).toBe('https://api.example.com/resource')
|
|
63
|
+
expect(caught).toBeInstanceOf(Error)
|
|
64
|
+
expect(caught.cause).toBeInstanceOf(Error)
|
|
65
|
+
expect(caught.cause?.message).toBe('network down')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('throws when server omits authentication headers', async () => {
|
|
69
|
+
const response = new Response('missing auth', {
|
|
70
|
+
status: 200,
|
|
71
|
+
headers: {
|
|
72
|
+
'Content-Type': 'text/plain'
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
const fetchMock: jest.MockedFunction<typeof fetch> = jest.fn()
|
|
76
|
+
fetchMock.mockResolvedValue(response)
|
|
77
|
+
const transport = new SimplifiedFetchTransport('https://api.example.com', fetchMock as any)
|
|
78
|
+
await transport.onData(async () => {})
|
|
79
|
+
|
|
80
|
+
const message = createGeneralMessage()
|
|
81
|
+
|
|
82
|
+
let thrown: any
|
|
83
|
+
await expect((async () => {
|
|
84
|
+
try {
|
|
85
|
+
await transport.send(message)
|
|
86
|
+
} catch (error) {
|
|
87
|
+
thrown = error
|
|
88
|
+
throw error
|
|
89
|
+
}
|
|
90
|
+
})()).rejects.toThrow('Received HTTP 200 from https://api.example.com/resource without valid BSV authentication (missing headers: x-bsv-auth-version, x-bsv-auth-identity-key, x-bsv-auth-signature)')
|
|
91
|
+
|
|
92
|
+
expect(thrown.details).toMatchObject({
|
|
93
|
+
url: 'https://api.example.com/resource',
|
|
94
|
+
status: 200,
|
|
95
|
+
missingHeaders: [
|
|
96
|
+
'x-bsv-auth-version',
|
|
97
|
+
'x-bsv-auth-identity-key',
|
|
98
|
+
'x-bsv-auth-signature'
|
|
99
|
+
]
|
|
100
|
+
})
|
|
101
|
+
expect(thrown.details.bodyPreview).toContain('missing auth')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('rejects malformed requested certificates header', async () => {
|
|
105
|
+
const fetchMock: jest.MockedFunction<typeof fetch> = jest.fn()
|
|
106
|
+
fetchMock.mockResolvedValue(new Response('', {
|
|
107
|
+
status: 200,
|
|
108
|
+
headers: {
|
|
109
|
+
'x-bsv-auth-version': '0.1',
|
|
110
|
+
'x-bsv-auth-identity-key': 'server-key',
|
|
111
|
+
'x-bsv-auth-signature': 'deadbeef',
|
|
112
|
+
'x-bsv-auth-message-type': 'general',
|
|
113
|
+
'x-bsv-auth-request-id': Utils.toBase64(new Array(32).fill(2)),
|
|
114
|
+
'x-bsv-auth-requested-certificates': 'not-json'
|
|
115
|
+
}
|
|
116
|
+
}))
|
|
117
|
+
|
|
118
|
+
const transport = new SimplifiedFetchTransport('https://api.example.com', fetchMock as any)
|
|
119
|
+
await transport.onData(async () => {})
|
|
120
|
+
const message = createGeneralMessage()
|
|
121
|
+
|
|
122
|
+
await expect(transport.send(message)).rejects.toThrow(
|
|
123
|
+
'Failed to parse x-bsv-auth-requested-certificates returned by https://api.example.com/resource: not-json'
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
@@ -146,9 +146,7 @@ export class HTTPSOverlayLookupFacilitator implements OverlayLookupFacilitator {
|
|
|
146
146
|
const response: Response = await this.fetchClient(`${url}/lookup`, fco)
|
|
147
147
|
|
|
148
148
|
if (!response.ok) throw new Error(`Failed to facilitate lookup (HTTP ${response.status})`)
|
|
149
|
-
if (response.headers.get('content-type') === 'application/
|
|
150
|
-
return await response.json()
|
|
151
|
-
} else {
|
|
149
|
+
if (response.headers.get('content-type') === 'application/octet-stream') {
|
|
152
150
|
const payload = await response.arrayBuffer()
|
|
153
151
|
const r = new Utils.Reader([...new Uint8Array(payload)])
|
|
154
152
|
const nOutpoints = r.readVarIntNum()
|
|
@@ -176,6 +174,8 @@ export class HTTPSOverlayLookupFacilitator implements OverlayLookupFacilitator {
|
|
|
176
174
|
beef: Transaction.fromBEEF(beef, x.txid).toBEEF()
|
|
177
175
|
}))
|
|
178
176
|
}
|
|
177
|
+
} else {
|
|
178
|
+
return await response.json()
|
|
179
179
|
}
|
|
180
180
|
} catch (e) {
|
|
181
181
|
// Normalize timeouts to a consistent error message
|