Skip to content

Commit 8761a3a

Browse files
authored
feat: add dag-cbor HTML preview plugin (#256)
* feat: add dag-cbor HTML preview plugin * fix: fix nested links and rendering objects * chore: fix lint and some code cleanup * fix: etag for cbor html preview is more accurate * fix: cbor html preview etag hash allows cache busting * chore: fix lint * docs: update README with non-default plugins info * docs: move plugins information to PLUGINS.md * docs: remove one typescript code example * docs(chore): remove extra hyphen for example * chore: update gwc passing/failing tests
1 parent 39facf6 commit 8761a3a

File tree

11 files changed

+381
-22
lines changed

11 files changed

+381
-22
lines changed

packages/gateway-conformance/src/expected-failing-tests.json

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,8 @@
177177
"TestNativeDag/HEAD_plain_CBOR_codec_with_an_explicit_DAG-JSON_format_returns_HTTP_200",
178178
"TestNativeDag/HEAD_plain_CBOR_codec_with_only-if-cached_for_missing_block_returns_HTTP_412_Precondition_Failed/Status_code",
179179
"TestNativeDag/HEAD_plain_CBOR_codec_with_only-if-cached_for_missing_block_returns_HTTP_412_Precondition_Failed",
180-
"TestNativeDag/GET_plain_CBOR_codec_on_%2Fipfs_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Header_Etag",
181-
"TestNativeDag/GET_plain_CBOR_codec_on_%2Fipfs_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Header_Content-Type",
182-
"TestNativeDag/GET_plain_CBOR_codec_on_%2Fipfs_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Body",
180+
"TestNativeDag/GET_plain_CBOR_codec_on_%2Fipfs_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Header_Cache-Control",
183181
"TestNativeDag/GET_plain_CBOR_codec_on_%2Fipfs_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29",
184-
"TestNativeDag/Convert_application%2Fvnd.ipld.dag-cbor_to_text%2Fhtml/Body",
185-
"TestNativeDag/Convert_application%2Fvnd.ipld.dag-cbor_to_text%2Fhtml",
186182
"TestNativeDag",
187183
"TestGatewayJSONCborAndIPNS/GET_plain_JSON_codec_from_%2Fipns_with_explicit_application%2Fvnd.ipld.dag-json_has_expected_headers/Header_Etag",
188184
"TestGatewayJSONCborAndIPNS/GET_plain_JSON_codec_from_%2Fipns_with_explicit_application%2Fvnd.ipld.dag-json_has_expected_headers/Header_X-Ipfs-Roots",
@@ -194,11 +190,6 @@
194190
"TestGatewayJSONCborAndIPNS/GET_plain_JSON_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29",
195191
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_from_%2Fipns_with_explicit_application%2Fvnd.ipld.dag-cbor_has_expected_headers/Header_Etag",
196192
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_from_%2Fipns_with_explicit_application%2Fvnd.ipld.dag-cbor_has_expected_headers",
197-
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Check_0/Header_Etag",
198-
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Check_0/Header_Content-Type",
199-
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Check_0/Body",
200-
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Check_0",
201-
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29",
202193
"TestGatewayJSONCborAndIPNS",
203194
"TestRedirectCanonicalIPNS/GET_for_%2Fipns%2F%7Bb58-multihash-of-ed25519-key%7D_redirects_to_%2Fipns%2F%7Bcidv1-libp2p-key-base36%7D/Status_code",
204195
"TestRedirectCanonicalIPNS/GET_for_%2Fipns%2F%7Bb58-multihash-of-ed25519-key%7D_redirects_to_%2Fipns%2F%7Bcidv1-libp2p-key-base36%7D/Header_Location",

packages/gateway-conformance/src/expected-passing-tests.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,10 @@
159159
"TestNativeDag/HEAD_plain_CBOR_codec_with_no_explicit_format_returns_HTTP_200/Status_code",
160160
"TestNativeDag/HEAD_plain_CBOR_codec_with_an_explicit_DAG-JSON_format_returns_HTTP_200/Status_code",
161161
"TestNativeDag/HEAD_plain_CBOR_codec_with_an_explicit_DAG-JSON_format_returns_HTTP_200/Header_Content-Type",
162+
"TestNativeDag/GET_plain_CBOR_codec_on_%2Fipfs_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Header_Etag",
163+
"TestNativeDag/GET_plain_CBOR_codec_on_%2Fipfs_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Header_Content-Type",
162164
"TestNativeDag/GET_plain_CBOR_codec_on_%2Fipfs_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Header_Content-Disposition",
163-
"TestNativeDag/GET_plain_CBOR_codec_on_%2Fipfs_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Header_Cache-Control",
165+
"TestNativeDag/GET_plain_CBOR_codec_on_%2Fipfs_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Body",
164166
"TestNativeDag/Convert_application%2Fvnd.ipld.dag-cbor_to_application%2Fvnd.ipld.dag-json/Body",
165167
"TestNativeDag/Convert_application%2Fvnd.ipld.dag-cbor_to_application%2Fvnd.ipld.dag-json",
166168
"TestNativeDag/Convert_application%2Fvnd.ipld.dag-cbor_to_application%2Fvnd.ipld.dag-json_with_range_request_includes_correct_bytes_-_single_range/Check_0",
@@ -169,6 +171,8 @@
169171
"TestNativeDag/Convert_application%2Fvnd.ipld.dag-cbor_to_application%2Fvnd.ipld.dag-json_with_range_request_includes_correct_bytes_-_single_range/Check_1",
170172
"TestNativeDag/Convert_application%2Fvnd.ipld.dag-cbor_to_application%2Fvnd.ipld.dag-json_with_range_request_includes_correct_bytes_-_single_range",
171173
"TestNativeDag/Convert_application%2Fvnd.ipld.dag-cbor_to_application%2Fvnd.ipld.dag-json_with_range_request_includes_correct_bytes_-_multi_range",
174+
"TestNativeDag/Convert_application%2Fvnd.ipld.dag-cbor_to_text%2Fhtml/Body",
175+
"TestNativeDag/Convert_application%2Fvnd.ipld.dag-cbor_to_text%2Fhtml",
172176
"TestGatewayJSONCborAndIPNS/GET_plain_JSON_codec_from_%2Fipns_without_explicit_format_returns_the_same_payload_as_%2Fipfs",
173177
"TestGatewayJSONCborAndIPNS/GET_plain_JSON_codec_from_%2Fipns_with_explicit_format_returns_the_same_payload_as_%2Fipfs",
174178
"TestGatewayJSONCborAndIPNS/GET_plain_JSON_codec_from_%2Fipns_with_explicit_application%2Fvnd.ipld.dag-json_has_expected_headers/Header_Content-Type",
@@ -182,10 +186,15 @@
182186
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_from_%2Fipns_with_explicit_application%2Fvnd.ipld.dag-cbor_has_expected_headers/Header_Content-Type",
183187
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_from_%2Fipns_with_explicit_application%2Fvnd.ipld.dag-cbor_has_expected_headers/Header_X-Ipfs-Path",
184188
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_from_%2Fipns_with_explicit_application%2Fvnd.ipld.dag-cbor_has_expected_headers/Header_X-Ipfs-Roots",
189+
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Check_0/Header_Etag",
190+
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Check_0/Header_Content-Type",
185191
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Check_0/Header_Content-Disposition",
192+
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Check_0/Body",
193+
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Check_0",
186194
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Check_1/Check_0",
187195
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Check_1/Check_1",
188196
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29/Check_1",
197+
"TestGatewayJSONCborAndIPNS/GET_plain_CBOR_codec_on_%2Fipns_with_Accept:_text%2Fhtml_returns_HTML_%28dag-index-html%29",
189198
"TestGatewayIPNSPath/GET_for_%2Fipns%2Fname_with_V1-only_signature_MUST_fail_with_5XX",
190199
"TestGatewayIPNSPath/GET_for_%2Fipns%2Fname_with_valid_V1+V2_signatures_with_V1-vs-V2_value_mismatch_MUST_fail_with_5XX",
191200
"TestGatewayIPNSPath/GET_for_%2Fipns%2Fname_with_valid_V2_and_broken_V1_signature_succeeds/Status_code",

packages/gateway-conformance/src/fixtures/basic-server.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { createServer } from 'node:http'
22
import { trustlessGateway } from '@helia/block-brokers'
33
import { createHeliaHTTP } from '@helia/http'
44
import { httpGatewayRouting } from '@helia/routers'
5-
import { dirIndexHtmlPluginFactory } from '@helia/verified-fetch/plugins'
5+
import { dagCborHtmlPreviewPluginFactory, dirIndexHtmlPluginFactory } from '@helia/verified-fetch/plugins'
66
import { logger } from '@libp2p/logger'
77
import { dns } from '@multiformats/dns'
88
import { MemoryBlockstore } from 'blockstore-core'
@@ -196,7 +196,7 @@ export async function startVerifiedFetchGateway ({ kuboGateway, serverPort, IPFS
196196
const helia = await createHelia({ gateways: [kuboGateway], dnsResolvers: [localDnsResolver], blockstore, datastore })
197197

198198
const verifiedFetch = await createVerifiedFetch(helia, {
199-
plugins: [dirIndexHtmlPluginFactory]
199+
plugins: [dirIndexHtmlPluginFactory, dagCborHtmlPreviewPluginFactory]
200200
})
201201

202202
const server = createServer((req, res) => {
@@ -220,7 +220,8 @@ export async function startVerifiedFetchGateway ({ kuboGateway, serverPort, IPFS
220220
})
221221

222222
server.listen(serverPort, () => {
223-
log(`Basic server listening on port ${serverPort}`)
223+
// eslint-disable-next-line no-console
224+
console.log(`Basic server listening on port ${serverPort}`)
224225
})
225226

226227
return async () => {

packages/verified-fetch/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,8 @@ Verified‑fetch can now be extended to alter how it handles requests by using p
664664
Plugins are classes that extend the `BasePlugin` class and implement the `VerifiedFetchPlugin`
665665
interface. They are instantiated with `PluginOptions` when the `VerifiedFetch` class is created.
666666

667+
### Plugin Interface
668+
667669
Each plugin must implement two methods:
668670

669671
- **`canHandle(context: PluginContext): boolean`**
@@ -678,6 +680,8 @@ Each plugin must implement two methods:
678680
- **Throw a `PluginError`**: This logs a non-fatal error and continues the pipeline.
679681
- **Throw a `PluginFatalError`**: This logs a fatal error and stops the pipeline immediately.
680682

683+
### Plugin Pipeline
684+
681685
Plugins are executed in a chain (a **plugin pipeline**):
682686

683687
1. **Initialization:**
@@ -724,6 +728,29 @@ Please see the original discussion on extensibility in [Issue #167](https://gith
724728

725729
***
726730

731+
### Non-default plugins provided by this library
732+
733+
#### `dir-index-html-plugin`
734+
735+
This plugin is used to serve dag-pb/unixfs without an `index.html` child as HTML directory listing of the content requested.
736+
737+
#### `dag-cbor-html-preview-plugin`
738+
739+
This plugin is used to serve the requested dag-cbor object as HTML when the Accept header includes `text/html`.
740+
741+
## Example - Using the plugins
742+
743+
```typescript
744+
import { createVerifiedFetch } from '@helia/verified-fetch'
745+
import { dagCborHtmlPreviewPluginFactory, dirIndexHtmlPluginFactory } from '@helia/verified-fetch/plugins'
746+
import { createHelia } from 'helia'
747+
748+
const helia = await createHelia()
749+
const fetch = await createVerifiedFetch(helia, {
750+
plugins: [dagCborHtmlPreviewPluginFactory, dirIndexHtmlPluginFactory, ]
751+
})
752+
```
753+
727754
### Extending Verified‑Fetch with Custom Plugins
728755

729756
To add your own plugin:

packages/verified-fetch/src/index.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,8 @@
633633
* Plugins are classes that extend the `BasePlugin` class and implement the `VerifiedFetchPlugin`
634634
* interface. They are instantiated with `PluginOptions` when the `VerifiedFetch` class is created.
635635
*
636+
* ### Plugin Interface
637+
*
636638
* Each plugin must implement two methods:
637639
*
638640
* - **`canHandle(context: PluginContext): boolean`**
@@ -647,6 +649,8 @@
647649
* - **Throw a `PluginError`**: This logs a non-fatal error and continues the pipeline.
648650
* - **Throw a `PluginFatalError`**: This logs a fatal error and stops the pipeline immediately.
649651
*
652+
* ### Plugin Pipeline
653+
*
650654
* Plugins are executed in a chain (a **plugin pipeline**):
651655
*
652656
* 1. **Initialization:**
@@ -692,6 +696,29 @@
692696
*
693697
* ---
694698
*
699+
* ### Non-default plugins provided by this library
700+
*
701+
* #### `dir-index-html-plugin`
702+
*
703+
* This plugin is used to serve dag-pb/unixfs without an `index.html` child as HTML directory listing of the content requested.
704+
*
705+
* #### `dag-cbor-html-preview-plugin`
706+
*
707+
* This plugin is used to serve the requested dag-cbor object as HTML when the Accept header includes `text/html`.
708+
*
709+
* @example Using the plugins
710+
*
711+
* ```typescript
712+
* import { createVerifiedFetch } from '@helia/verified-fetch'
713+
* import { dagCborHtmlPreviewPluginFactory, dirIndexHtmlPluginFactory } from '@helia/verified-fetch/plugins'
714+
* import { createHelia } from 'helia'
715+
*
716+
* const helia = await createHelia()
717+
* const fetch = await createVerifiedFetch(helia, {
718+
* plugins: [dagCborHtmlPreviewPluginFactory, dirIndexHtmlPluginFactory, ]
719+
* })
720+
* ```
721+
*
695722
* ### Extending Verified‑Fetch with Custom Plugins
696723
*
697724
* To add your own plugin:

0 commit comments

Comments
 (0)