@helia/verified-fetch 4.1.1 → 5.0.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 (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 +0,0 @@
1
- {"version":3,"file":"walk-path.js","sourceRoot":"","sources":["../../../src/utils/walk-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAoBrE,KAAK,UAAU,QAAQ,CAAE,UAA2B,EAAE,IAAY,EAAE,OAA2B;IAC7F,MAAM,SAAS,GAAU,EAAE,CAAA;IAC3B,IAAI,eAAwC,CAAA;IAE5C,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC;QAClE,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACzB,eAAe,GAAG,KAAK,CAAA;IACzB,CAAC;IAED,IAAI,eAAe,IAAI,IAAI,EAAE,CAAC;QAC5B,MAAM,IAAI,iBAAiB,CAAC,2BAA2B,CAAC,CAAA;IAC1D,CAAC;IAED,OAAO;QACL,SAAS;QACT,eAAe;KAChB,CAAA;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAE,IAAiB;IAC7C,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAA;AAC/B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAE,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAA2D;IACjJ,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,UAAU,EAAE,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IACxE,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,IAAI,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC7B,MAAM,IAAI,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;QAC/C,CAAC;QAED,IAAI,CAAC,aAAa,EAAE,yBAAyB,EAAE,eAAe,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACnF,OAAO,gBAAgB,CAAC,QAAQ,CAAC,CAAA;QACnC,CAAC;QAED,GAAG,CAAC,KAAK,CAAC,8BAA8B,EAAE,IAAI,EAAE,GAAG,CAAC,CAAA;QACpD,OAAO,kBAAkB,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAA;IAC3D,CAAC;AACH,CAAC"}
@@ -1,30 +0,0 @@
1
- import { ByteRangeContext } from '../utils/byte-range-context.js'
2
- import { badRangeResponse } from '../utils/responses.js'
3
- import { BasePlugin } from './plugin-base.js'
4
- import type { PluginContext } from './types.js'
5
-
6
- /**
7
- * This plugin simply adds the ByteRangeContext to the PluginContext.
8
- */
9
- export class ByteRangeContextPlugin extends BasePlugin {
10
- readonly id = 'byte-range-context-plugin'
11
-
12
- /**
13
- * Return false if the ByteRangeContext has already been set, otherwise return true.
14
- */
15
- canHandle (context: PluginContext): boolean {
16
- return context.byteRangeContext == null
17
- }
18
-
19
- async handle (context: PluginContext): Promise<Response | null> {
20
- context.byteRangeContext = new ByteRangeContext(this.pluginOptions.logger, context.options?.headers)
21
- context.modified++
22
-
23
- if (context.byteRangeContext.isRangeRequest && !context.byteRangeContext.isValidRangeRequest) {
24
- // invalid range request.. fail
25
- return badRangeResponse(context.resource)
26
- }
27
-
28
- return null
29
- }
30
- }
@@ -1,107 +0,0 @@
1
- import * as ipldDagCbor from '@ipld/dag-cbor'
2
- import { CID } from 'multiformats/cid'
3
- import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
4
- import { CODEC_CBOR } from '../constants.ts'
5
- import { cborToObject, dagCborToSafeJSON } from '../utils/dag-cbor-to-safe-json.js'
6
- import { setIpfsRoots } from '../utils/response-headers.js'
7
- import { notAcceptableResponse, okRangeResponse } from '../utils/responses.js'
8
- import { isObjectNode } from '../utils/walk-path.js'
9
- import { BasePlugin } from './plugin-base.js'
10
- import type { PluginContext } from './types.js'
11
- import type { ObjectNode } from 'ipfs-unixfs-exporter'
12
-
13
- /**
14
- * Handles `cbor` content
15
- */
16
- export class CborPlugin extends BasePlugin {
17
- readonly id = 'cbor-plugin'
18
- readonly codes = [CODEC_CBOR]
19
-
20
- canHandle ({ cid, pathDetails, byteRangeContext }: PluginContext): boolean {
21
- if (pathDetails == null) {
22
- return false
23
- }
24
-
25
- if (!isObjectNode(pathDetails.terminalElement)) {
26
- return false
27
- }
28
-
29
- if (cid.code !== CODEC_CBOR) {
30
- return false
31
- }
32
-
33
- if (byteRangeContext == null) {
34
- return false
35
- }
36
-
37
- return true
38
- }
39
-
40
- async handle (context: PluginContext & Required<Pick<PluginContext, 'byteRangeContext' | 'pathDetails'>> & { pathDetails: { terminalElement: ObjectNode } }): Promise<Response> {
41
- const { cid, path, resource, accept, pathDetails: { terminalElement, ipfsRoots } } = context
42
-
43
- this.log.trace('fetching %c/%s with accept header %s', cid, path.join('/'), accept?.mimeType)
44
-
45
- const block = terminalElement.node
46
-
47
- let responseContentType = 'application/cbor'
48
- let body: string | Uint8Array = block
49
-
50
- if (accept?.mimeType === 'application/json' || accept?.mimeType === 'application/vnd.ipld.dag-json') {
51
- try {
52
- body = dagCborToSafeJSON(block)
53
- responseContentType = accept.mimeType
54
- } catch (err) {
55
- this.log.error('could not decode CBOR as JSON-safe - %e', err)
56
- return notAcceptableResponse(resource)
57
- }
58
- } else if (accept?.mimeType === 'application/vnd.ipld.dag-cbor') {
59
- try {
60
- const obj = cborToObject(block)
61
- convert(obj)
62
- body = ipldDagCbor.encode(obj)
63
- responseContentType = accept.mimeType
64
- } catch (err) {
65
- this.log.error('could not translate CBOR to DAG-CBOR - %e', err)
66
-
67
- return notAcceptableResponse(resource)
68
- }
69
- } else if (accept?.mimeType === 'application/octet-stream' || accept?.mimeType === 'application/vnd.ipld.raw') {
70
- responseContentType = accept.mimeType
71
- }
72
-
73
- context.byteRangeContext.setBody(body)
74
-
75
- const response = okRangeResponse(resource, context.byteRangeContext.getBody(responseContentType), { byteRangeContext: context.byteRangeContext, log: this.log })
76
-
77
- response.headers.set('content-type', context.byteRangeContext.getContentType() ?? responseContentType)
78
-
79
- if (responseContentType !== 'application/json') {
80
- context.query.download = true
81
- context.query.filename ??= `${cid}.cbor`
82
- }
83
-
84
- this.log.trace('setting content type to "%s"', context.byteRangeContext.getContentType() ?? responseContentType)
85
- setIpfsRoots(response, ipfsRoots)
86
-
87
- return response
88
- }
89
- }
90
-
91
- /**
92
- * Turns `{ "/": string }` properties into `CID` instances and
93
- * `{ "/": { "bytes": base64 } }` into `Uint8Array`s
94
- */
95
- function convert (obj: any): void {
96
- try {
97
- for (const key of Object.getOwnPropertyNames(obj)) {
98
- if (typeof obj[key]?.['/'] === 'string') {
99
- obj[key] = CID.parse(obj[key]['/'])
100
- } else if (typeof obj[key]?.['/']?.['bytes'] === 'string') {
101
- obj[key] = uint8ArrayFromString(obj[key]['/']['bytes'], 'base64')
102
- } else {
103
- convert(obj[key])
104
- }
105
- }
106
- } catch {}
107
- }
@@ -1,295 +0,0 @@
1
- import * as ipldDagCbor from '@ipld/dag-cbor'
2
- import { base32 } from 'multiformats/bases/base32'
3
- import { CID } from 'multiformats/cid'
4
- import { sha256 } from 'multiformats/hashes/sha2'
5
- import { isLink } from 'multiformats/link'
6
- import { getETag } from '../utils/get-e-tag.js'
7
- import { getIpfsRoots } from '../utils/response-headers.js'
8
- import { isObjectNode } from '../utils/walk-path.js'
9
- import { BasePlugin } from './plugin-base.js'
10
- import type { PluginContext, VerifiedFetchPluginFactory } from './types.js'
11
- import type { ObjectNode } from 'ipfs-unixfs-exporter'
12
-
13
- function isPrimitive (value: unknown): boolean {
14
- return value === null ||
15
- value === undefined ||
16
- typeof value === 'string' ||
17
- typeof value === 'number' ||
18
- typeof value === 'boolean' ||
19
- typeof value === 'bigint' ||
20
- typeof value === 'symbol'
21
- }
22
-
23
- /**
24
- * Converts a cborObject and it's children into a small hash that can be used in the etag header.
25
- *
26
- * @see https://github.com/ipfs/boxo/blob/dc60fe747c375c631a92fcfd6c7456f44a760d24/gateway/assets/assets.go#L84
27
- * @see https://github.com/ipfs/boxo/blob/dc60fe747c375c631a92fcfd6c7456f44a760d24/gateway/handler_unixfs_dir.go#L233-L235
28
- */
29
- async function getAssetHash (cborObject: Record<string, any>): Promise<string> {
30
- /**
31
- * Plugin Version represents a "version of gateway implementation" that allows for cache busting if necessary.
32
- *
33
- * @see https://specs.ipfs.tech/http-gateways/path-gateway/#etag-response-header
34
- */
35
- const pluginVersion = '0.0.1'
36
- const entryDetails = Object.entries(cborObject).reduce((acc, [key, value]) => {
37
- if (isPrimitive(value)) {
38
- return `${acc}${key}${value}`
39
- }
40
- return `${acc}${key}${getAssetHash(value)}`
41
- }, '')
42
- const hashBytes = await sha256.encode(new TextEncoder().encode(pluginVersion + entryDetails))
43
- return base32.encode(hashBytes)
44
- }
45
-
46
- /**
47
- * Handles `dag-cbor` content where the Accept: `text/html` header is present.
48
- */
49
- export class DagCborHtmlPreviewPlugin extends BasePlugin {
50
- readonly id = 'dag-cbor-plugin-html-preview'
51
- readonly codes = [ipldDagCbor.code]
52
-
53
- canHandle ({ cid, accept, pathDetails }: PluginContext): boolean {
54
- if (pathDetails == null) {
55
- return false
56
- }
57
-
58
- if (!isObjectNode(pathDetails.terminalElement)) {
59
- return false
60
- }
61
-
62
- if (cid.code !== ipldDagCbor.code) {
63
- return false
64
- }
65
-
66
- if (accept == null || !accept?.mimeType.includes('text/html')) {
67
- return false
68
- }
69
-
70
- return isObjectNode(pathDetails.terminalElement)
71
- }
72
-
73
- async handle (context: PluginContext & Required<Pick<PluginContext, 'byteRangeContext' | 'pathDetails'>> & { pathDetails: { terminalElement: ObjectNode } }): Promise<Response> {
74
- const { cid, path, pathDetails: { terminalElement, ipfsRoots } } = context
75
- this.log.trace('generating html preview for %c/%s', cid, path)
76
-
77
- const block = terminalElement.node
78
- let obj: Record<string, any>
79
- try {
80
- obj = ipldDagCbor.decode(block)
81
- } catch (err) {
82
- return new Response(`<pre>Failed to decode DAG-CBOR: ${String(err)}</pre>`, {
83
- status: 500,
84
- headers: { 'Content-Type': 'text/html' }
85
- })
86
- }
87
-
88
- const html = this.getHtml({ path, obj, cid })
89
-
90
- return new Response(html, {
91
- status: 200,
92
- headers: {
93
- 'Content-Type': 'text/html',
94
- 'X-Ipfs-Roots': getIpfsRoots(ipfsRoots),
95
- 'Cache-Control': 'public, max-age=604800, stale-while-revalidate=2678400',
96
- Etag: getETag({ cid, reqFormat: context.reqFormat, contentPrefix: `DagIndex-${await getAssetHash(obj)}_CID-` })
97
- }
98
- })
99
- }
100
-
101
- getHtml ({ path, obj, cid }: { path?: string[], obj: Record<string, any>, cid: CID }): string {
102
- const style = `
103
- :root {
104
- --sans-serif: "Plex", system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif;
105
- --monospace: Consolas, monaco, monospace;
106
- --navy: #073a53;
107
- --teal: #6bc4ce;
108
- --turquoise: #47AFB4;
109
- --steel-gray: #3f5667;
110
- --dark-white: #d9dbe2;
111
- --light-white: #edf0f4;
112
- --near-white: #f7f8fa;
113
- --radius: 4px;
114
- }
115
- body {
116
- color: #34373f;
117
- font-family: var(--sans-serif);
118
- line-height: 1.43;
119
- margin: 0;
120
- word-break: break-all;
121
- -webkit-text-size-adjust: 100%;
122
- -ms-text-size-adjust: 100%;
123
- -webkit-tap-highlight-color: transparent;
124
- }
125
- main {
126
- border: 1px solid var(--dark-white);
127
- border-radius: var(--radius);
128
- overflow: hidden;
129
- margin: 1em;
130
- font-size: .875em;
131
- }
132
- main section header {
133
- background-color: var(--near-white);
134
- }
135
- main header, main section:not(:last-child) {
136
- border-bottom: 1px solid var(--dark-white);
137
- }
138
- main header {
139
- padding-top: .7em;
140
- padding-bottom: .7em;
141
- background-color: var(--light-white);
142
- }
143
- main header, main .container {
144
- padding-left: 1em;
145
- padding-right: 1em;
146
- }
147
- section {
148
- display: block;
149
- }
150
- .grid.dag {
151
- grid-template-columns: max-content 1fr;
152
- }
153
- .grid {
154
- display: grid;
155
- overflow-x: auto;
156
- }
157
- .grid.dag > div {
158
- background: white;
159
- }
160
- .grid.dag > div:nth-of-type(2n+1) {
161
- padding-left: 1em;
162
- }
163
- .grid.dag > div:nth-child(4n), .grid.dag > div:nth-child(4n+3) {
164
- background-color: var(--near-white);
165
- }
166
- .grid.dag .grid {
167
- padding: 0;
168
- }
169
- /* change coloring of nested grid
170
- .grid.dag .grid.dag > div:nth-child(4n), .grid.dag .grid.dag > div:nth-child(4n+3) {
171
- background-color: white;
172
- }
173
- .grid.dag .grid.dag div:nth-child(4n+1), .grid.dag .grid.dag div:nth-child(4n+2) {
174
- background-color: var(--near-white);
175
- } */
176
- .grid.dag > div:nth-last-child(-n+2) {
177
- border-bottom: 0;
178
- }
179
- .grid .grid {
180
- overflow-x: visible;
181
- }
182
- .grid > div {
183
- padding: .7em;
184
- border-bottom: 1px solid var(--dark-white);
185
- }
186
- .nowrap {
187
- white-space: nowrap;
188
- }
189
- pre, code {
190
- font-family: var(--monospace);
191
- }
192
- strong {
193
- font-weight: bolder;
194
- }
195
- a:active, a:visited {
196
- color: #00b0e9;
197
- }
198
- .ipfs-hash {
199
- color: #7f8491;
200
- font-family: var(--monospace);
201
- }
202
- a {
203
- color: #117eb3;
204
- text-decoration: none;
205
- }
206
- header { margin-bottom: 2em; }
207
- .ipfs-logo { width: 32px; height: 32px; background: url('data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlo89/56ZQ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUjDu1lo89/6mhTP+zrVP/nplD/5+aRK8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHNiIS6Wjz3/ubFY/761W/+vp1D/urRZ/8vDZf/GvmH/nplD/1BNIm8AAAAAAAAAAAAAAAAAAAAAAAAAAJaPPf+knEj/vrVb/761W/++tVv/r6dQ/7q0Wf/Lw2X/y8Nl/8vDZf+tpk7/nplD/wAAAAAAAAAAAAAAAJaPPf+2rVX/vrVb/761W/++tVv/vrVb/6+nUP+6tFn/y8Nl/8vDZf/Lw2X/y8Nl/8G6Xv+emUP/AAAAAAAAAACWjz3/vrVb/761W/++tVv/vrVb/761W/+vp1D/urRZ/8vDZf/Lw2X/y8Nl/8vDZf/Lw2X/nplD/wAAAAAAAAAAlo89/761W/++tVv/vrVb/761W/++tVv/r6dQ/7q0Wf/Lw2X/y8Nl/8vDZf/Lw2X/y8Nl/56ZQ/8AAAAAAAAAAJaPPf++tVv/vrVb/761W/++tVv/vbRa/5aPPf+emUP/y8Nl/8vDZf/Lw2X/y8Nl/8vDZf+emUP/AAAAAAAAAACWjz3/vrVb/761W/++tVv/vrVb/5qTQP+inkb/op5G/6KdRv/Lw2X/y8Nl/8vDZf/Lw2X/nplD/wAAAAAAAAAAlo89/761W/++tVv/sqlS/56ZQ//LxWb/0Mlp/9DJaf/Kw2X/oJtE/7+3XP/Lw2X/y8Nl/56ZQ/8AAAAAAAAAAJaPPf+9tFr/mJE+/7GsUv/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav+xrFL/nplD/8vDZf+emUP/AAAAAAAAAACWjz3/op5G/9HKav/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav+inkb/nplD/wAAAAAAAAAAAAAAAKKeRv+3slb/0cpq/9HKav/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav+1sFX/op5G/wAAAAAAAAAAAAAAAAAAAAAAAAAAop5GUKKeRv/Nxmf/0cpq/9HKav/Rymr/0cpq/83GZ/+inkb/op5GSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAop5G16KeRv/LxWb/y8Vm/6KeRv+inkaPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAop5G/6KeRtcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/n8AAPgfAADwDwAAwAMAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAwAMAAPAPAAD4HwAA/n8AAA==') no-repeat center/contain; display: inline-block; }
208
- `
209
-
210
- return `<!DOCTYPE html>
211
- <html lang="en">
212
- <head>
213
- <meta charset="utf-8">
214
- <meta name="description" content="Content-addressed dag-cbor document hosted on IPFS.">
215
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
216
- <link rel="shortcut icon" href="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlo89/56ZQ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUjDu1lo89/6mhTP+zrVP/nplD/5+aRK8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHNiIS6Wjz3/ubFY/761W/+vp1D/urRZ/8vDZf/GvmH/nplD/1BNIm8AAAAAAAAAAAAAAAAAAAAAAAAAAJaPPf+knEj/vrVb/761W/++tVv/r6dQ/7q0Wf/Lw2X/y8Nl/8vDZf+tpk7/nplD/wAAAAAAAAAAAAAAAJaPPf+2rVX/vrVb/761W/++tVv/vrVb/6+nUP+6tFn/y8Nl/8vDZf/Lw2X/y8Nl/8G6Xv+emUP/AAAAAAAAAACWjz3/vrVb/761W/++tVv/vrVb/761W/+vp1D/urRZ/8vDZf/Lw2X/y8Nl/8vDZf/Lw2X/nplD/wAAAAAAAAAAlo89/761W/++tVv/vrVb/761W/++tVv/r6dQ/7q0Wf/Lw2X/y8Nl/8vDZf/Lw2X/y8Nl/56ZQ/8AAAAAAAAAAJaPPf++tVv/vrVb/761W/++tVv/vbRa/5aPPf+emUP/y8Nl/8vDZf/Lw2X/y8Nl/8vDZf+emUP/AAAAAAAAAACWjz3/vrVb/761W/++tVv/vrVb/5qTQP+inkb/op5G/6KdRv/Lw2X/y8Nl/8vDZf/Lw2X/nplD/wAAAAAAAAAAlo89/761W/++tVv/sqlS/56ZQ//LxWb/0Mlp/9DJaf/Kw2X/oJtE/7+3XP/Lw2X/y8Nl/56ZQ/8AAAAAAAAAAJaPPf+9tFr/mJE+/7GsUv/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav+xrFL/nplD/8vDZf+emUP/AAAAAAAAAACWjz3/op5G/9HKav/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav+inkb/nplD/wAAAAAAAAAAAAAAAKKeRv+3slb/0cpq/9HKav/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav+1sFX/op5G/wAAAAAAAAAAAAAAAAAAAAAAAAAAop5GUKKeRv/Nxmf/0cpq/9HKav/Rymr/0cpq/83GZ/+inkb/op5GSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAop5G16KeRv/LxWb/y8Vm/6KeRv+inkaPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAop5G/6KeRtcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/n8AAPgfAADwDwAAwAMAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAwAMAAPAPAAD4HwAA/n8AAA==">
217
- <title>${cid.toString()} DAG-CBOR Preview</title>
218
- <style>${style}</style>
219
- </head>
220
- <body>
221
- <main>
222
- <header>
223
- <div><strong>CID: </strong> <code class="nowrap">${cid}</code></div>
224
- <div><strong>Codec: </strong> ${this.valueHTML('dag-cbor (0x71)', [])}</div>
225
- </header>
226
- <section class="container">
227
- <p>You can download this block as:</p>
228
- <ul>
229
- <li><a href="?format=raw&download=true&filename=${cid.toString()}.bin" rel="nofollow" download="${cid.toString()}.bin">Raw Block</a> (no conversion)</li>
230
- <li><a href="?format=dag-json&download=true&filename=${cid.toString()}.json" rel="nofollow" download="${cid.toString()}">Valid DAG-JSON</a> (specs at <a href="https://ipld.io/specs/codecs/dag-json/spec/" target="_blank" rel="noopener noreferrer">IPLD</a> and <a href="https://www.iana.org/assignments/media-types/application/vnd.ipld.dag-json" target="_blank" rel="noopener noreferrer">IANA</a>)</li>
231
- <li><a href="?format=dag-cbor&download=true&filename=${cid.toString()}.dag-cbor" rel="nofollow" download="${cid.toString()}.dag-cbor">Valid DAG-CBOR</a> (specs at <a href="https://ipld.io/specs/codecs/dag-cbor/spec/" target="_blank" rel="noopener noreferrer">IPLD</a> and <a href="https://www.iana.org/assignments/media-types/application/vnd.ipld.dag-cbor" target="_blank" rel="noopener noreferrer">IANA</a>)</li>
232
- </ul>
233
- </section>
234
- <section>
235
- <header><strong>DAG-CBOR Preview</strong></header>
236
- <div class="grid dag">
237
- ${this.renderRows(obj, path)}
238
- </div>
239
- </section>
240
- </main>
241
- </body>
242
- </html>`
243
- }
244
-
245
- valueHTML (value: any, link: string[]): string {
246
- let valueString: string
247
- const isALinkObject = isLink(value)
248
- if (!isALinkObject && typeof value !== 'string') {
249
- valueString = JSON.stringify(value)
250
- } else {
251
- // it can be a string or a link object.. call .toString() on it
252
- valueString = value.toString()
253
- }
254
- const valueCodeBlock = `<code class="nowrap">${valueString}</code>`
255
- if (isALinkObject && link != null) {
256
- return `<a class="ipfs-hash" href="/${link}">${valueCodeBlock}</a>`
257
- }
258
-
259
- return valueCodeBlock
260
- }
261
-
262
- private renderValue (key: string, value: any, currentPath: string[]): string {
263
- let rows = ''
264
- value.forEach((item: any, idx: number) => {
265
- const itemPath = [...currentPath, key, idx.toString()]
266
- rows += `<div>${this.valueHTML(idx, [])}</div>`
267
- if (isPrimitive(item)) {
268
- rows += `<div>${this.valueHTML(item, itemPath)}</div>`
269
- } else {
270
- rows += '<div class="grid dag">'
271
- rows += this.renderRows(item, itemPath)
272
- rows += '</div>'
273
- }
274
- })
275
- return rows
276
- }
277
-
278
- renderRows (obj: Record<string, any>, currentPath: string[] = []): string {
279
- let rows = ''
280
- for (const [key, value] of Object.entries(obj)) {
281
- if (Array.isArray(value)) {
282
- rows += `<div>${key}</div>`
283
- rows += '<div class="grid dag">'
284
- rows += this.renderValue(key, value, currentPath)
285
- rows += '</div>'
286
- } else {
287
- const valuePath = [...currentPath, key]
288
- rows += `<div>${key}</div><div>${this.valueHTML(value, valuePath)}</div>`
289
- }
290
- }
291
- return rows
292
- }
293
- }
294
-
295
- export const dagCborHtmlPreviewPluginFactory: VerifiedFetchPluginFactory = (opts) => new DagCborHtmlPreviewPlugin(opts)
@@ -1,83 +0,0 @@
1
- import * as ipldDagCbor from '@ipld/dag-cbor'
2
- import { dagCborToSafeJSON } from '../utils/dag-cbor-to-safe-json.ts'
3
- import { setIpfsRoots } from '../utils/response-headers.js'
4
- import { notAcceptableResponse, okRangeResponse } from '../utils/responses.js'
5
- import { isObjectNode } from '../utils/walk-path.js'
6
- import { BasePlugin } from './plugin-base.js'
7
- import type { PluginContext } from './types.js'
8
- import type { ObjectNode } from 'ipfs-unixfs-exporter'
9
-
10
- /**
11
- * Handles `dag-cbor` content, including requests with Accept: `application/vnd.ipld.dag-json` and `application/json`.
12
- */
13
- export class DagCborPlugin extends BasePlugin {
14
- readonly id = 'dag-cbor-plugin'
15
- readonly codes = [ipldDagCbor.code]
16
-
17
- canHandle ({ cid, accept, pathDetails, byteRangeContext, plugins }: PluginContext): boolean {
18
- if (pathDetails == null) {
19
- return false
20
- }
21
-
22
- if (!isObjectNode(pathDetails.terminalElement)) {
23
- return false
24
- }
25
-
26
- if (cid.code !== ipldDagCbor.code) {
27
- return false
28
- }
29
-
30
- if (byteRangeContext == null) {
31
- return false
32
- }
33
-
34
- if (accept != null && accept.mimeType === 'text/html' && plugins.includes('dag-cbor-plugin-html-preview')) {
35
- // let the dag-cbor-html-preview plugin handle it
36
- return false
37
- }
38
-
39
- return isObjectNode(pathDetails.terminalElement)
40
- }
41
-
42
- async handle (context: PluginContext & Required<Pick<PluginContext, 'byteRangeContext' | 'pathDetails'>> & { pathDetails: { terminalElement: ObjectNode } }): Promise<Response> {
43
- const { cid, path, resource, accept, pathDetails: { terminalElement, ipfsRoots } } = context
44
-
45
- this.log.trace('fetching %c/%s with accept header %s', cid, path.join('/'), accept?.mimeType)
46
-
47
- const block = terminalElement.node
48
-
49
- let responseContentType = 'application/cbor'
50
- let body: string | Uint8Array = block
51
-
52
- if (accept?.mimeType === 'application/json' || accept?.mimeType === 'application/vnd.ipld.dag-json') {
53
- try {
54
- // if vnd.ipld.dag-json has been specified, convert to the format - note
55
- // that this supports more data types than regular JSON
56
- body = dagCborToSafeJSON(body)
57
- responseContentType = accept.mimeType
58
- } catch (err) {
59
- this.log.error('could not decode CBOR as JSON-safe - %e', err)
60
- return notAcceptableResponse(resource)
61
- }
62
- } else if (accept?.mimeType === 'application/octet-stream' || accept?.mimeType === 'application/vnd.ipld.raw' || accept?.mimeType === 'application/vnd.ipld.dag-cbor') {
63
- responseContentType = accept.mimeType
64
- }
65
-
66
- context.byteRangeContext.setBody(body)
67
-
68
- // const responseContentType = accept?.mimeType ?? (body instanceof Uint8Array ? 'application/octet-stream' : 'application/json')
69
- const response = okRangeResponse(resource, context.byteRangeContext.getBody(responseContentType), { byteRangeContext: context.byteRangeContext, log: this.log })
70
-
71
- response.headers.set('content-type', context.byteRangeContext.getContentType() ?? responseContentType)
72
-
73
- if (responseContentType !== 'application/json') {
74
- context.query.download = true
75
- context.query.filename ??= `${cid}.cbor`
76
- }
77
-
78
- this.log.trace('setting content type to "%s"', context.byteRangeContext.getContentType() ?? responseContentType)
79
- setIpfsRoots(response, ipfsRoots)
80
-
81
- return response
82
- }
83
- }