@helia/verified-fetch 4.1.1 → 5.0.1

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 (231) hide show
  1. package/README.md +6 -40
  2. package/dist/index.min.js +73 -534
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/constants.d.ts +2 -0
  5. package/dist/src/constants.d.ts.map +1 -1
  6. package/dist/src/constants.js +2 -0
  7. package/dist/src/constants.js.map +1 -1
  8. package/dist/src/index.d.ts +162 -68
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +7 -40
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/plugins/index.d.ts +0 -5
  13. package/dist/src/plugins/index.d.ts.map +1 -1
  14. package/dist/src/plugins/index.js +0 -4
  15. package/dist/src/plugins/index.js.map +1 -1
  16. package/dist/src/plugins/plugin-base.d.ts +8 -9
  17. package/dist/src/plugins/plugin-base.d.ts.map +1 -1
  18. package/dist/src/plugins/plugin-base.js +5 -6
  19. package/dist/src/plugins/plugin-base.js.map +1 -1
  20. package/dist/src/plugins/plugin-handle-car.d.ts +3 -3
  21. package/dist/src/plugins/plugin-handle-car.d.ts.map +1 -1
  22. package/dist/src/plugins/plugin-handle-car.js +38 -39
  23. package/dist/src/plugins/plugin-handle-car.js.map +1 -1
  24. package/dist/src/plugins/plugin-handle-ipld.d.ts +12 -0
  25. package/dist/src/plugins/plugin-handle-ipld.d.ts.map +1 -0
  26. package/dist/src/plugins/plugin-handle-ipld.js +83 -0
  27. package/dist/src/plugins/plugin-handle-ipld.js.map +1 -0
  28. package/dist/src/plugins/plugin-handle-ipns-record.d.ts +3 -3
  29. package/dist/src/plugins/plugin-handle-ipns-record.d.ts.map +1 -1
  30. package/dist/src/plugins/plugin-handle-ipns-record.js +25 -34
  31. package/dist/src/plugins/plugin-handle-ipns-record.js.map +1 -1
  32. package/dist/src/plugins/plugin-handle-tar.d.ts +3 -3
  33. package/dist/src/plugins/plugin-handle-tar.d.ts.map +1 -1
  34. package/dist/src/plugins/plugin-handle-tar.js +20 -22
  35. package/dist/src/plugins/plugin-handle-tar.js.map +1 -1
  36. package/dist/src/plugins/plugin-handle-unixfs.d.ts +14 -0
  37. package/dist/src/plugins/plugin-handle-unixfs.d.ts.map +1 -0
  38. package/dist/src/plugins/plugin-handle-unixfs.js +180 -0
  39. package/dist/src/plugins/plugin-handle-unixfs.js.map +1 -0
  40. package/dist/src/plugins/types.d.ts +1 -77
  41. package/dist/src/plugins/types.d.ts.map +1 -1
  42. package/dist/src/url-resolver.d.ts +29 -11
  43. package/dist/src/url-resolver.d.ts.map +1 -1
  44. package/dist/src/url-resolver.js +152 -74
  45. package/dist/src/url-resolver.js.map +1 -1
  46. package/dist/src/utils/content-type-parser.d.ts.map +1 -1
  47. package/dist/src/utils/content-type-parser.js +4 -3
  48. package/dist/src/utils/content-type-parser.js.map +1 -1
  49. package/dist/src/utils/content-types.d.ts +26 -0
  50. package/dist/src/utils/content-types.d.ts.map +1 -0
  51. package/dist/src/utils/content-types.js +137 -0
  52. package/dist/src/utils/content-types.js.map +1 -0
  53. package/dist/src/utils/convert-output.d.ts +17 -0
  54. package/dist/src/utils/convert-output.d.ts.map +1 -0
  55. package/dist/src/utils/convert-output.js +176 -0
  56. package/dist/src/utils/convert-output.js.map +1 -0
  57. package/dist/src/utils/error-to-response.d.ts +3 -0
  58. package/dist/src/utils/error-to-response.d.ts.map +1 -0
  59. package/dist/src/utils/error-to-response.js +40 -0
  60. package/dist/src/utils/error-to-response.js.map +1 -0
  61. package/dist/src/utils/get-content-disposition-filename.d.ts +1 -1
  62. package/dist/src/utils/get-content-disposition-filename.d.ts.map +1 -1
  63. package/dist/src/utils/get-content-disposition-filename.js +4 -0
  64. package/dist/src/utils/get-content-disposition-filename.js.map +1 -1
  65. package/dist/src/utils/get-e-tag.d.ts +20 -15
  66. package/dist/src/utils/get-e-tag.d.ts.map +1 -1
  67. package/dist/src/utils/get-e-tag.js +8 -22
  68. package/dist/src/utils/get-e-tag.js.map +1 -1
  69. package/dist/src/utils/get-offset-and-length.d.ts +12 -2
  70. package/dist/src/utils/get-offset-and-length.d.ts.map +1 -1
  71. package/dist/src/utils/get-offset-and-length.js +63 -21
  72. package/dist/src/utils/get-offset-and-length.js.map +1 -1
  73. package/dist/src/utils/get-range-header.d.ts +22 -0
  74. package/dist/src/utils/get-range-header.d.ts.map +1 -0
  75. package/dist/src/utils/get-range-header.js +69 -0
  76. package/dist/src/utils/get-range-header.js.map +1 -0
  77. package/dist/src/utils/parse-url-string.d.ts +2 -1
  78. package/dist/src/utils/parse-url-string.d.ts.map +1 -1
  79. package/dist/src/utils/parse-url-string.js +46 -71
  80. package/dist/src/utils/parse-url-string.js.map +1 -1
  81. package/dist/src/utils/resource-to-cache-key.d.ts +3 -3
  82. package/dist/src/utils/resource-to-cache-key.js +5 -5
  83. package/dist/src/utils/resource-to-cache-key.js.map +1 -1
  84. package/dist/src/utils/response-headers.d.ts +4 -14
  85. package/dist/src/utils/response-headers.d.ts.map +1 -1
  86. package/dist/src/utils/response-headers.js +36 -36
  87. package/dist/src/utils/response-headers.js.map +1 -1
  88. package/dist/src/utils/responses.d.ts +30 -11
  89. package/dist/src/utils/responses.d.ts.map +1 -1
  90. package/dist/src/utils/responses.js +146 -39
  91. package/dist/src/utils/responses.js.map +1 -1
  92. package/dist/src/verified-fetch.d.ts +16 -15
  93. package/dist/src/verified-fetch.d.ts.map +1 -1
  94. package/dist/src/verified-fetch.js +302 -238
  95. package/dist/src/verified-fetch.js.map +1 -1
  96. package/dist/typedoc-urls.json +64 -45
  97. package/package.json +4 -3
  98. package/src/constants.ts +3 -0
  99. package/src/index.ts +199 -68
  100. package/src/plugins/index.ts +0 -6
  101. package/src/plugins/plugin-base.ts +8 -10
  102. package/src/plugins/plugin-handle-car.ts +48 -46
  103. package/src/plugins/plugin-handle-ipld.ts +93 -0
  104. package/src/plugins/plugin-handle-ipns-record.ts +31 -41
  105. package/src/plugins/plugin-handle-tar.ts +25 -29
  106. package/src/plugins/plugin-handle-unixfs.ts +217 -0
  107. package/src/plugins/types.ts +0 -86
  108. package/src/url-resolver.ts +197 -83
  109. package/src/utils/content-type-parser.ts +4 -3
  110. package/src/utils/content-types.ts +159 -0
  111. package/src/utils/convert-output.ts +187 -0
  112. package/src/utils/error-to-response.ts +49 -0
  113. package/src/utils/get-content-disposition-filename.ts +7 -1
  114. package/src/utils/get-e-tag.ts +26 -35
  115. package/src/utils/get-offset-and-length.ts +75 -21
  116. package/src/utils/get-range-header.ts +107 -0
  117. package/src/utils/parse-url-string.ts +51 -80
  118. package/src/utils/resource-to-cache-key.ts +5 -5
  119. package/src/utils/response-headers.ts +40 -41
  120. package/src/utils/responses.ts +186 -45
  121. package/src/verified-fetch.ts +353 -270
  122. package/dist/src/plugins/plugin-handle-byte-range-context.d.ts +0 -14
  123. package/dist/src/plugins/plugin-handle-byte-range-context.d.ts.map +0 -1
  124. package/dist/src/plugins/plugin-handle-byte-range-context.js +0 -25
  125. package/dist/src/plugins/plugin-handle-byte-range-context.js.map +0 -1
  126. package/dist/src/plugins/plugin-handle-cbor.d.ts +0 -17
  127. package/dist/src/plugins/plugin-handle-cbor.d.ts.map +0 -1
  128. package/dist/src/plugins/plugin-handle-cbor.js +0 -94
  129. package/dist/src/plugins/plugin-handle-cbor.js.map +0 -1
  130. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts +0 -27
  131. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts.map +0 -1
  132. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js +0 -279
  133. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js.map +0 -1
  134. package/dist/src/plugins/plugin-handle-dag-cbor.d.ts +0 -17
  135. package/dist/src/plugins/plugin-handle-dag-cbor.d.ts.map +0 -1
  136. package/dist/src/plugins/plugin-handle-dag-cbor.js +0 -66
  137. package/dist/src/plugins/plugin-handle-dag-cbor.js.map +0 -1
  138. package/dist/src/plugins/plugin-handle-dag-pb.d.ts +0 -17
  139. package/dist/src/plugins/plugin-handle-dag-pb.d.ts.map +0 -1
  140. package/dist/src/plugins/plugin-handle-dag-pb.js +0 -209
  141. package/dist/src/plugins/plugin-handle-dag-pb.js.map +0 -1
  142. package/dist/src/plugins/plugin-handle-dag-walk.d.ts +0 -21
  143. package/dist/src/plugins/plugin-handle-dag-walk.d.ts.map +0 -1
  144. package/dist/src/plugins/plugin-handle-dag-walk.js +0 -95
  145. package/dist/src/plugins/plugin-handle-dag-walk.js.map +0 -1
  146. package/dist/src/plugins/plugin-handle-dir-index-html.d.ts +0 -10
  147. package/dist/src/plugins/plugin-handle-dir-index-html.d.ts.map +0 -1
  148. package/dist/src/plugins/plugin-handle-dir-index-html.js +0 -59
  149. package/dist/src/plugins/plugin-handle-dir-index-html.js.map +0 -1
  150. package/dist/src/plugins/plugin-handle-json.d.ts +0 -12
  151. package/dist/src/plugins/plugin-handle-json.d.ts.map +0 -1
  152. package/dist/src/plugins/plugin-handle-json.js +0 -73
  153. package/dist/src/plugins/plugin-handle-json.js.map +0 -1
  154. package/dist/src/plugins/plugin-handle-raw.d.ts +0 -9
  155. package/dist/src/plugins/plugin-handle-raw.d.ts.map +0 -1
  156. package/dist/src/plugins/plugin-handle-raw.js +0 -92
  157. package/dist/src/plugins/plugin-handle-raw.js.map +0 -1
  158. package/dist/src/plugins/plugins.d.ts +0 -6
  159. package/dist/src/plugins/plugins.d.ts.map +0 -1
  160. package/dist/src/plugins/plugins.js +0 -6
  161. package/dist/src/plugins/plugins.js.map +0 -1
  162. package/dist/src/utils/byte-range-context.d.ts +0 -103
  163. package/dist/src/utils/byte-range-context.d.ts.map +0 -1
  164. package/dist/src/utils/byte-range-context.js +0 -504
  165. package/dist/src/utils/byte-range-context.js.map +0 -1
  166. package/dist/src/utils/dag-cbor-to-safe-json.d.ts +0 -15
  167. package/dist/src/utils/dag-cbor-to-safe-json.d.ts.map +0 -1
  168. package/dist/src/utils/dag-cbor-to-safe-json.js +0 -54
  169. package/dist/src/utils/dag-cbor-to-safe-json.js.map +0 -1
  170. package/dist/src/utils/dir-index-html.d.ts +0 -19
  171. package/dist/src/utils/dir-index-html.d.ts.map +0 -1
  172. package/dist/src/utils/dir-index-html.js +0 -438
  173. package/dist/src/utils/dir-index-html.js.map +0 -1
  174. package/dist/src/utils/get-peer-id-from-string.d.ts +0 -3
  175. package/dist/src/utils/get-peer-id-from-string.d.ts.map +0 -1
  176. package/dist/src/utils/get-peer-id-from-string.js +0 -10
  177. package/dist/src/utils/get-peer-id-from-string.js.map +0 -1
  178. package/dist/src/utils/get-resolved-accept-header.d.ts +0 -9
  179. package/dist/src/utils/get-resolved-accept-header.d.ts.map +0 -1
  180. package/dist/src/utils/get-resolved-accept-header.js +0 -27
  181. package/dist/src/utils/get-resolved-accept-header.js.map +0 -1
  182. package/dist/src/utils/get-stream-from-async-iterable.d.ts +0 -9
  183. package/dist/src/utils/get-stream-from-async-iterable.d.ts.map +0 -1
  184. package/dist/src/utils/get-stream-from-async-iterable.js +0 -43
  185. package/dist/src/utils/get-stream-from-async-iterable.js.map +0 -1
  186. package/dist/src/utils/handle-redirects.d.ts +0 -16
  187. package/dist/src/utils/handle-redirects.d.ts.map +0 -1
  188. package/dist/src/utils/handle-redirects.js +0 -84
  189. package/dist/src/utils/handle-redirects.js.map +0 -1
  190. package/dist/src/utils/is-accept-explicit.d.ts +0 -15
  191. package/dist/src/utils/is-accept-explicit.d.ts.map +0 -1
  192. package/dist/src/utils/is-accept-explicit.js +0 -26
  193. package/dist/src/utils/is-accept-explicit.js.map +0 -1
  194. package/dist/src/utils/request-headers.d.ts +0 -13
  195. package/dist/src/utils/request-headers.d.ts.map +0 -1
  196. package/dist/src/utils/request-headers.js +0 -63
  197. package/dist/src/utils/request-headers.js.map +0 -1
  198. package/dist/src/utils/select-output-type.d.ts +0 -17
  199. package/dist/src/utils/select-output-type.d.ts.map +0 -1
  200. package/dist/src/utils/select-output-type.js +0 -153
  201. package/dist/src/utils/select-output-type.js.map +0 -1
  202. package/dist/src/utils/tlru.d.ts +0 -15
  203. package/dist/src/utils/tlru.d.ts.map +0 -1
  204. package/dist/src/utils/tlru.js +0 -34
  205. package/dist/src/utils/tlru.js.map +0 -1
  206. package/dist/src/utils/walk-path.d.ts +0 -27
  207. package/dist/src/utils/walk-path.d.ts.map +0 -1
  208. package/dist/src/utils/walk-path.js +0 -45
  209. package/dist/src/utils/walk-path.js.map +0 -1
  210. package/src/plugins/plugin-handle-byte-range-context.ts +0 -30
  211. package/src/plugins/plugin-handle-cbor.ts +0 -107
  212. package/src/plugins/plugin-handle-dag-cbor-html-preview.ts +0 -295
  213. package/src/plugins/plugin-handle-dag-cbor.ts +0 -83
  214. package/src/plugins/plugin-handle-dag-pb.ts +0 -248
  215. package/src/plugins/plugin-handle-dag-walk.ts +0 -110
  216. package/src/plugins/plugin-handle-dir-index-html.ts +0 -72
  217. package/src/plugins/plugin-handle-json.ts +0 -80
  218. package/src/plugins/plugin-handle-raw.ts +0 -110
  219. package/src/plugins/plugins.ts +0 -5
  220. package/src/utils/byte-range-context.ts +0 -597
  221. package/src/utils/dag-cbor-to-safe-json.ts +0 -63
  222. package/src/utils/dir-index-html.ts +0 -505
  223. package/src/utils/get-peer-id-from-string.ts +0 -12
  224. package/src/utils/get-resolved-accept-header.ts +0 -42
  225. package/src/utils/get-stream-from-async-iterable.ts +0 -49
  226. package/src/utils/handle-redirects.ts +0 -109
  227. package/src/utils/is-accept-explicit.ts +0 -38
  228. package/src/utils/request-headers.ts +0 -65
  229. package/src/utils/select-output-type.ts +0 -175
  230. package/src/utils/tlru.ts +0 -42
  231. package/src/utils/walk-path.ts +0 -69
@@ -1,6 +1,9 @@
1
- import type { ByteRangeContext } from './byte-range-context.js'
2
- import type { SupportedBodyTypes } from '../index.js'
3
- import type { Logger } from '@libp2p/interface'
1
+ import itToBrowserReadableStream from 'it-to-browser-readablestream'
2
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
3
+ import { rangeToOffsetAndLength } from './get-offset-and-length.ts'
4
+ import { getContentRangeHeader } from './response-headers.ts'
5
+ import type { SupportedBodyTypes, ContentType } from '../index.js'
6
+ import type { Range, RangeHeader } from './get-range-header.ts'
4
7
 
5
8
  function setField (response: Response, name: string, value: string | boolean): void {
6
9
  Object.defineProperty(response, name, {
@@ -12,17 +15,22 @@ function setField (response: Response, name: string, value: string | boolean): v
12
15
  }
13
16
 
14
17
  function setType (response: Response, value: 'basic' | 'cors' | 'error' | 'opaque' | 'opaqueredirect'): void {
15
- setField(response, 'type', value)
18
+ if (response.type !== value) {
19
+ setField(response, 'type', value)
20
+ }
16
21
  }
17
22
 
18
- function setUrl (response: Response, value: string): void {
23
+ function setUrl (response: Response, value: string | URL): void {
24
+ value = value.toString()
19
25
  const fragmentStart = value.indexOf('#')
20
26
 
21
27
  if (fragmentStart > -1) {
22
28
  value = value.substring(0, fragmentStart)
23
29
  }
24
30
 
25
- setField(response, 'url', value)
31
+ if (response.url !== value) {
32
+ setField(response, 'url', value)
33
+ }
26
34
  }
27
35
 
28
36
  function setRedirected (response: Response): void {
@@ -64,6 +72,26 @@ export function internalServerErrorResponse (url: string, body?: SupportedBodyTy
64
72
  return response
65
73
  }
66
74
 
75
+ /**
76
+ * A 504 Gateway Timeout for when a request made to an upstream server timed out
77
+ */
78
+ export function gatewayTimeoutResponse (url: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
79
+ const response = new Response(body, {
80
+ ...(init ?? {}),
81
+ status: 504,
82
+ statusText: 'Gateway Timeout'
83
+ })
84
+
85
+ setType(response, 'basic')
86
+ setUrl(response, url)
87
+
88
+ return response
89
+ }
90
+
91
+ /**
92
+ * A 502 Bad Gateway is for when an invalid response was received from an
93
+ * upstream server.
94
+ */
67
95
  export function badGatewayResponse (url: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
68
96
  const response = new Response(body, {
69
97
  ...(init ?? {}),
@@ -91,11 +119,17 @@ export function notImplementedResponse (url: string, body?: SupportedBodyTypes,
91
119
  return response
92
120
  }
93
121
 
94
- export function notAcceptableResponse (url: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
95
- const response = new Response(body, {
122
+ export function notAcceptableResponse (url: string | URL, acceptable: ContentType[], init?: ResponseInit): Response {
123
+ const headers = new Headers(init?.headers)
124
+ headers.set('content-type', 'application/json')
125
+
126
+ const response = new Response(JSON.stringify({
127
+ acceptable: acceptable.map(contentType => contentType.mediaType)
128
+ }), {
96
129
  ...(init ?? {}),
97
130
  status: 406,
98
- statusText: 'Not Acceptable'
131
+ statusText: 'Not Acceptable',
132
+ headers
99
133
  })
100
134
 
101
135
  setType(response, 'basic')
@@ -125,6 +159,7 @@ export function badRequestResponse (url: string, errors: Error | Error[], init?:
125
159
  // stacktrace of the single error, or the stacktrace of the last error in the array
126
160
  let stack: string | undefined
127
161
  let convertedErrors: Array<{ message: string, stack: string }> | undefined
162
+
128
163
  if (isArrayOfErrors(errors)) {
129
164
  stack = errors[errors.length - 1].stack
130
165
  convertedErrors = errors.map(e => ({ message: e.message, stack: e.stack ?? '' }))
@@ -171,70 +206,176 @@ export function movedPermanentlyResponse (url: string, location: string, init?:
171
206
  return response
172
207
  }
173
208
 
174
- interface RangeOptions {
175
- byteRangeContext: ByteRangeContext
176
- log?: Logger
209
+ export interface PartialContent {
210
+ /**
211
+ * Yield data from the content starting at `start` (or 0) inclusive and ending
212
+ * at `end` exclusive
213
+ */
214
+ (offset: number, length: number): AsyncGenerator<Uint8Array>
177
215
  }
178
216
 
179
- export function okRangeResponse (url: string, body: SupportedBodyTypes, { byteRangeContext, log }: RangeOptions, init?: ResponseOptions): Response {
180
- if (!byteRangeContext.isRangeRequest) {
181
- return okResponse(url, body, init)
217
+ export function partialContentResponse (url: string, getSlice: PartialContent, range: RangeHeader, documentSize: number | bigint, init?: ResponseOptions): Response {
218
+ let response: Response
219
+
220
+ if (range.ranges.length === 1) {
221
+ response = singleRangeResponse(url, getSlice, range.ranges[0], documentSize, init)
222
+ } else if (range.ranges.length > 1) {
223
+ response = multiRangeResponse(url, getSlice, range, documentSize, init)
224
+ } else {
225
+ return notSatisfiableResponse(url, documentSize)
182
226
  }
183
227
 
184
- if (!byteRangeContext.isValidRangeRequest) {
185
- return badRangeResponse(url, body, init)
228
+ if (init?.redirected === true) {
229
+ setRedirected(response)
186
230
  }
187
231
 
188
- let response: Response
232
+ setType(response, 'basic')
233
+ setUrl(response, url)
234
+
235
+ return response
236
+ }
237
+
238
+ function singleRangeResponse (url: string, getSlice: PartialContent, range: Range, documentSize: number | bigint, init?: ResponseOptions): Response {
189
239
  try {
190
- // Create headers object with any initial headers from init
240
+ // create headers object with any initial headers from init
191
241
  const headers = new Headers(init?.headers)
242
+ const { offset, length } = rangeToOffsetAndLength(documentSize, range.start, range.end)
192
243
 
193
- // For multipart responses, we should use the content-type header instead of content-range
194
- const multipartContentType = byteRangeContext.getContentType()
195
-
196
- if (multipartContentType != null) {
197
- headers.set('content-type', multipartContentType)
198
- } else {
199
- if (byteRangeContext.isMultiRangeRequest) {
200
- headers.set('content-type', 'multipart/byteranges')
201
- } else {
202
- headers.set('content-range', byteRangeContext.contentRangeHeaderValue)
203
- }
204
- }
244
+ headers.set('content-length', `${length}`)
245
+
246
+ // see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Range
247
+ headers.set('content-range', getContentRangeHeader(documentSize, range.start, range.end))
248
+
249
+ const stream = itToBrowserReadableStream(getSlice(offset, length))
205
250
 
206
- response = new Response(body, {
251
+ return new Response(stream, {
207
252
  ...(init ?? {}),
208
253
  status: 206,
209
254
  statusText: 'Partial Content',
210
255
  headers
211
256
  })
212
- } catch (e: any) {
213
- log?.error('failed to create range response', e)
214
- return badRangeResponse(url, body, init)
257
+ } catch (err: any) {
258
+ if (err.name === 'InvalidRangeError') {
259
+ return notSatisfiableResponse(url, documentSize, init)
260
+ }
261
+
262
+ return internalServerErrorResponse(url, '', init)
215
263
  }
264
+ }
216
265
 
217
- if (init?.redirected === true) {
218
- setRedirected(response)
266
+ /**
267
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Range_requests
268
+ */
269
+ function multiRangeResponse (url: string, getSlice: PartialContent, range: RangeHeader, documentSize: number | bigint, init?: ResponseOptions): Response {
270
+ // create headers object with any initial headers from init
271
+ const headers = new Headers(init?.headers)
272
+
273
+ const contentType = headers.get('content-type')
274
+
275
+ if (contentType == null) {
276
+ throw new Error('Content-Type header must be set')
219
277
  }
220
278
 
221
- setType(response, 'basic')
222
- setUrl(response, url)
279
+ headers.delete('content-type')
223
280
 
224
- return response
281
+ let contentLength = 0n
282
+
283
+ // calculate content range based on range headers
284
+ const rangeHeaders = range.ranges.map(({ start, end }) => {
285
+ const header = uint8ArrayFromString([
286
+ `--${range.multipartBoundary}`,
287
+
288
+ // content-type of multipart part
289
+ `Content-Type: ${contentType}`,
290
+
291
+ // see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Range
292
+ `Content-Range: ${getContentRangeHeader(documentSize, start, end)}`,
293
+ '',
294
+ ''
295
+ ].join('\r\n'))
296
+
297
+ contentLength += BigInt(header.byteLength) + (BigInt(end ?? documentSize) - BigInt(start ?? 0))
298
+
299
+ return header
300
+ })
301
+
302
+ const trailer = uint8ArrayFromString([
303
+ `--${range.multipartBoundary}--`,
304
+ ''
305
+ ].join('\r\n'))
306
+
307
+ contentLength += BigInt(trailer.byteLength)
308
+
309
+ // content length is the expected length of all multipart parts
310
+ headers.set('content-length', `${contentLength}`)
311
+
312
+ // content type of response is multipart
313
+ headers.set('content-type', `multipart/byteranges; boundary=${range.multipartBoundary}`)
314
+
315
+ const stream = itToBrowserReadableStream(async function * () {
316
+ for (let i = 0; i < rangeHeaders.length; i++) {
317
+ yield rangeHeaders[i]
318
+
319
+ const { offset, length } = rangeToOffsetAndLength(documentSize, range.ranges[i].start, range.ranges[i].end)
320
+ yield * getSlice(offset, length)
321
+
322
+ yield uint8ArrayFromString('\r\n')
323
+ }
324
+
325
+ yield trailer
326
+ }())
327
+
328
+ return new Response(stream, {
329
+ ...(init ?? {}),
330
+ status: 206,
331
+ statusText: 'Partial Content',
332
+ headers
333
+ })
225
334
  }
226
335
 
227
336
  /**
228
- * We likely need to catch errors handled by upstream helia libraries if range-request throws an error. Some examples:
337
+ * We likely need to catch errors handled by upstream helia libraries if
338
+ * range-request throws an error. Some examples:
339
+ *
229
340
  * - The range is out of bounds
230
341
  * - The range is invalid
231
342
  * - The range is not supported for the given type
232
343
  */
233
- export function badRangeResponse (url: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
234
- const response = new Response(body, {
235
- ...(init ?? {}),
344
+ export function notSatisfiableResponse (url: string, documentSize?: number | bigint | string, init?: ResponseInit): Response {
345
+ const headers = new Headers(init?.headers)
346
+
347
+ if (documentSize != null) {
348
+ headers.set('content-range', `bytes */${documentSize}`)
349
+ }
350
+
351
+ const response = new Response('Range Not Satisfiable', {
352
+ ...init,
353
+ headers,
236
354
  status: 416,
237
- statusText: 'Requested Range Not Satisfiable'
355
+ statusText: 'Range Not Satisfiable'
356
+ })
357
+
358
+ setType(response, 'basic')
359
+ setUrl(response, url)
360
+
361
+ return response
362
+ }
363
+
364
+ /**
365
+ * Error to indicate that request was formally correct, but Gateway is unable to
366
+ * return requested data under the additional (usually cache-related) conditions
367
+ * sent by the client.
368
+ *
369
+ * @see https://specs.ipfs.tech/http-gateways/path-gateway/#412-precondition-failed
370
+ */
371
+ export function preconditionFailedResponse (url: string, init?: ResponseInit): Response {
372
+ const headers = new Headers(init?.headers)
373
+
374
+ const response = new Response('Precondition Failed', {
375
+ ...init,
376
+ headers,
377
+ status: 412,
378
+ statusText: 'Precondition Failed'
238
379
  })
239
380
 
240
381
  setType(response, 'basic')