Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remove unused helia, use verified fetch, and simplify code #89

Merged
merged 10 commits into from
Mar 9, 2024
50 changes: 1 addition & 49 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 1 addition & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,10 @@
}
},
"dependencies": {
"@helia/block-brokers": "^2.0.1",
"@helia/http": "^1.0.0",
"@helia/interface": "^4.0.0",
"@helia/ipns": "^6.0.0",
"@helia/routers": "^1.0.0",
"@helia/ipns": "^6.0.1",
"@helia/verified-fetch": "^1.1.0",
"@libp2p/logger": "^4.0.6",
"@sgtpooki/file-type": "^1.0.1",
"blockstore-idb": "^1.1.8",
"datastore-idb": "^2.1.8",
"debug": "^4.3.4",
"mime-types": "^2.1.35",
"multiformats": "^11.0.2",
Expand Down
5 changes: 3 additions & 2 deletions src/components/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ export default (): JSX.Element | null => {
}
// we get the iframe origin from a query parameter called 'origin', if this is loaded in an iframe
// TODO: why we need this origin here? where is targetOrigin used?
const targetOrigin = decodeURIComponent(window.location.search.split('origin=')[1])
const targetOrigin = decodeURIComponent(window.location.hash.split('@origin=')[1])
const config = await getConfig()

// eslint-disable-next-line no-console
console.log('config-page: postMessage config to origin ', config, origin)
2color marked this conversation as resolved.
Show resolved Hide resolved
/**
* The reload page in the parent window is listening for this message, and then it passes a RELOAD_CONFIG message to the service worker
*/
Expand Down
36 changes: 13 additions & 23 deletions src/get-helia.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
import { trustlessGateway } from '@helia/block-brokers'
import { createHeliaHTTP } from '@helia/http'
import { delegatedHTTPRouting } from '@helia/routers'
import { IDBBlockstore } from 'blockstore-idb'
import { IDBDatastore } from 'datastore-idb'
import { dnsJsonOverHttps } from '@helia/ipns/dns-resolvers'
import { createVerifiedFetch, type VerifiedFetch } from '@helia/verified-fetch'
2color marked this conversation as resolved.
Show resolved Hide resolved
import { getConfig } from './lib/config-db.ts'
import { trace } from './lib/logger.ts'
import type { Helia } from '@helia/interface'
import { contentTypeParser } from './lib/content-type-parser.ts'
import { log } from './lib/logger.ts'

export async function getHelia (): Promise<Helia> {
export async function getVerifiedFetch (): Promise<VerifiedFetch> {
const config = await getConfig()
trace(`config-debug: got config for sw location ${self.location.origin}`, config)
const blockstore = new IDBBlockstore('./helia-sw/blockstore')
const datastore = new IDBDatastore('./helia-sw/datastore')
await blockstore.open()
await datastore.open()
log(`config-debug: got config for sw location ${self.location.origin}`, config)

const helia = await createHeliaHTTP({
blockstore,
datastore,
blockBrokers: [
trustlessGateway({
gateways: [...config.gateways, 'https://trustless-gateway.link']
})
],
routers: [...config.routers, 'https://delegated-ipfs.dev'].map(rUrl => delegatedHTTPRouting(rUrl))
const verifiedFetch = await createVerifiedFetch({
gateways: config.gateways ?? ['https://trustless-gateway.link'],
routers: config.routers ?? ['https://delegated-ipfs.dev'],
dnsResolvers: ['https://delegated-ipfs.dev/dns-query'].map(dnsJsonOverHttps)
}, {
contentTypeParser
})

return helia
return verifiedFetch
}
8 changes: 4 additions & 4 deletions src/lib/config-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export async function loadConfigFromLocalStorage (): Promise<void> {
if (typeof globalThis.localStorage !== 'undefined') {
const db = await openDatabase()
const localStorage = globalThis.localStorage
const localStorageGatewaysString = localStorage.getItem(LOCAL_STORAGE_KEYS.config.gateways) ?? '[]'
const localStorageRoutersString = localStorage.getItem(LOCAL_STORAGE_KEYS.config.routers) ?? '[]'
const localStorageGatewaysString = localStorage.getItem(LOCAL_STORAGE_KEYS.config.gateways) ?? '["https://trustless-gateway.link"]'
const localStorageRoutersString = localStorage.getItem(LOCAL_STORAGE_KEYS.config.routers) ?? '["https://delegated-ipfs.dev"]'
const autoReload = localStorage.getItem(LOCAL_STORAGE_KEYS.config.autoReload) === 'true'
const debug = localStorage.getItem(LOCAL_STORAGE_KEYS.config.debug) ?? ''
const gateways = JSON.parse(localStorageGatewaysString)
Expand Down Expand Up @@ -84,8 +84,8 @@ export async function setConfig (config: ConfigDb): Promise<void> {
export async function getConfig (): Promise<ConfigDb> {
const db = await openDatabase()

const gateways = await getFromDatabase(db, 'gateways') ?? []
const routers = await getFromDatabase(db, 'routers') ?? []
const gateways = await getFromDatabase(db, 'gateways') ?? ['https://trustless-gateway.link']
const routers = await getFromDatabase(db, 'routers') ?? ['https://delegated-ipfs.dev']
const autoReload = await getFromDatabase(db, 'autoReload') ?? false
const debug = await getFromDatabase(db, 'debug') ?? ''
debugLib.enable(debug)
Expand Down
47 changes: 47 additions & 0 deletions src/lib/content-type-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { type ContentTypeParser } from '@helia/verified-fetch'
import { fileTypeFromBuffer } from '@sgtpooki/file-type'

// default from verified-fetch is application/octect-stream, which forces a download. This is not what we want for MANY file types.
export const defaultMimeType = 'text/html'

export const contentTypeParser: ContentTypeParser = async (bytes, fileName) => {
const detectedType = (await fileTypeFromBuffer(bytes))?.mime
if (detectedType != null) {
return detectedType
}
if (fileName == null) {
// no other way to determine file-type.
return defaultMimeType
}

// no need to include file-types listed at https://github.com/SgtPooki/file-type#supported-file-types
switch (fileName.split('.').pop()) {
case 'css':
return 'text/css'
case 'html':
return 'text/html'
case 'js':
return 'application/javascript'
case 'json':
return 'application/json'
case 'txt':
return 'text/plain'
case 'woff2':
return 'font/woff2'
// see bottom of https://github.com/SgtPooki/file-type#supported-file-types
case 'svg':
return 'image/svg+xml'
case 'csv':
return 'text/csv'
case 'doc':
return 'application/msword'
case 'xls':
return 'application/vnd.ms-excel'
case 'ppt':
return 'application/vnd.ms-powerpoint'
case 'msi':
return 'application/x-msdownload'
default:
return defaultMimeType
}
}
121 changes: 34 additions & 87 deletions src/lib/heliaFetch.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,11 @@
import { dnsJsonOverHttps } from '@helia/ipns/dns-resolvers'
import { createVerifiedFetch, type ContentTypeParser } from '@helia/verified-fetch'
import { fileTypeFromBuffer } from '@sgtpooki/file-type'
import { getConfig } from './config-db.ts'
import { type VerifiedFetch } from '@helia/verified-fetch'
import { trace } from './logger.ts'
import type { Helia } from '@helia/interface'

export interface HeliaFetchOptions {
path: string
helia: Helia
verifiedFetch: VerifiedFetch
verifiedFetchUrl: string
signal?: AbortSignal
headers?: Headers
id?: string | null
protocol?: string | null
}

// default from verified-fetch is application/octect-stream, which forces a download. This is not what we want for MANY file types.
const defaultMimeType = 'text/html'
const contentTypeParser: ContentTypeParser = async (bytes, fileName) => {
const detectedType = (await fileTypeFromBuffer(bytes))?.mime
if (detectedType != null) {
return detectedType
}
if (fileName == null) {
// no other way to determine file-type.
return defaultMimeType
}

// no need to include file-types listed at https://github.com/SgtPooki/file-type#supported-file-types
switch (fileName.split('.').pop()) {
case 'css':
return 'text/css'
case 'html':
return 'text/html'
case 'js':
return 'application/javascript'
case 'json':
return 'application/json'
case 'txt':
return 'text/plain'
case 'woff2':
return 'font/woff2'
// see bottom of https://github.com/SgtPooki/file-type#supported-file-types
case 'svg':
return 'image/svg+xml'
case 'csv':
return 'text/csv'
case 'doc':
return 'application/msword'
case 'xls':
return 'application/vnd.ms-excel'
case 'ppt':
return 'application/vnd.ms-powerpoint'
case 'msi':
return 'application/x-msdownload'
default:
return defaultMimeType
}
}

// Check for **/*.css/fonts/**/*.ttf urls */
Expand Down Expand Up @@ -123,45 +73,42 @@ function changeCssFontPath (path: string): string {
* * TODO: have error handling that renders 404/500/other if the request is bad.
*
*/
export async function heliaFetch ({ path, helia, signal, headers, id, protocol }: HeliaFetchOptions): Promise<Response> {
const config = await getConfig()
const verifiedFetch = await createVerifiedFetch({
gateways: [...config.gateways, 'https://trustless-gateway.link'],
routers: [...config.routers, 'https://delegated-ipfs.dev'],
dnsResolvers: ['https://delegated-ipfs.dev/dns-query'].map(dnsJsonOverHttps)
}, {
contentTypeParser
})

let verifiedFetchUrl: string

if (id != null && protocol != null) {
verifiedFetchUrl = `${protocol}://${id}${path}`
} else {
const pathParts = path.split('/')

let pathPartIndex = 0
let namespaceString = pathParts[pathPartIndex++]
if (namespaceString === '') {
// we have a prefixed '/' in the path, use the new index instead
namespaceString = pathParts[pathPartIndex++]
}
if (namespaceString !== 'ipfs' && namespaceString !== 'ipns') {
throw new Error(`only /ipfs or /ipns namespaces supported got ${namespaceString}`)
}
const pathRootString = pathParts[pathPartIndex++]
const contentPath = pathParts.slice(pathPartIndex++).join('/')
verifiedFetchUrl = `${namespaceString}://${pathRootString}/${changeCssFontPath(contentPath)}`
}

// eslint-disable-next-line no-console
console.log('verifiedFetch for ', verifiedFetchUrl)
return verifiedFetch(verifiedFetchUrl, {
export async function heliaFetch ({ verifiedFetch, verifiedFetchUrl, signal, headers }: HeliaFetchOptions): Promise<Response> {
const response = await verifiedFetch(verifiedFetchUrl, {
2color marked this conversation as resolved.
Show resolved Hide resolved
signal,
headers,
// TODO redirect: 'manual', // enable when http urls are supported by verified-fetch: https://github.com/ipfs-shipyard/helia-service-worker-gateway/issues/62#issuecomment-1977661456
onProgress: (e) => {
trace(`${e.type}: `, e.detail)
}
})

return response
}

export interface GetVerifiedFetchUrlOptions {
protocol?: string | null
id?: string | null
path: string
}

export function getVerifiedFetchUrl ({ protocol, id, path }: GetVerifiedFetchUrlOptions): string {
if (id != null && protocol != null) {
return `${protocol}://${id}${path}`
}

const pathParts = path.split('/')

let pathPartIndex = 0
let namespaceString = pathParts[pathPartIndex++]
if (namespaceString === '') {
// we have a prefixed '/' in the path, use the new index instead
namespaceString = pathParts[pathPartIndex++]
}
if (namespaceString !== 'ipfs' && namespaceString !== 'ipns') {
throw new Error(`only /ipfs or /ipns namespaces supported got ${namespaceString}`)
}
const pathRootString = pathParts[pathPartIndex++]
const contentPath = pathParts.slice(pathPartIndex++).join('/')
return `${namespaceString}://${pathRootString}/${changeCssFontPath(contentPath)}`
}
2 changes: 1 addition & 1 deletion src/lib/is-config-page.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export function isConfigPage (): boolean {
const isConfigPathname = window.location.pathname === '/config'
const isConfigHashPath = window.location.hash === '#/config' // needed for _redirects and IPFS hosted sw gateways
const isConfigHashPath = window.location.hash.startsWith('#/config') // needed for _redirects and IPFS hosted sw gateways
return isConfigPathname || isConfigHashPath
}
2 changes: 1 addition & 1 deletion src/redirectPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const ConfigIframe = (): JSX.Element => {
const { parentDomain } = getSubdomainParts(window.location.href)

const portString = window.location.port === '' ? '' : `:${window.location.port}`
const iframeSrc = `${window.location.protocol}//${parentDomain}${portString}/config?origin=${encodeURIComponent(window.location.origin)}`
const iframeSrc = `${window.location.protocol}//${parentDomain}${portString}#/config@origin=${encodeURIComponent(window.location.origin)}`

return (
<iframe id="redirect-config-iframe" src={iframeSrc} style={{ width: '100vw', height: '100vh', border: 'none' }} />
Expand Down
Loading
Loading