Skip to content

Commit

Permalink
feat: config collapsed, reload button, sw-ready-btn
Browse files Browse the repository at this point in the history
  • Loading branch information
SgtPooki committed Feb 27, 2024
1 parent 78ec8a3 commit 21c2e7b
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 35 deletions.
23 changes: 23 additions & 0 deletions src/components/collapsible.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { useState } from 'react'

export interface CollapsibleProps {
children: React.ReactNode
collapsedLabel: string
expandedLabel: string
collapsed: boolean
}

export function Collapsible ({ children, collapsedLabel, expandedLabel, collapsed }: CollapsibleProps): JSX.Element {
const [cId] = useState(Math.random().toString(36).substring(7))
const [isCollapsed, setCollapsed] = useState(collapsed)

return (
<React.Fragment>
<input type="checkbox" className="dn" name="collapsible" id={`collapsible-${cId}`} onClick={() => { setCollapsed(!isCollapsed) }} />
<label htmlFor={`collapsible-${cId}`} className="collapsible__item-label db pv3 link black hover-blue pointer blue">{isCollapsed ? collapsedLabel : expandedLabel}</label>
<div className={`bb b--black-20 ${isCollapsed ? 'dn' : ''}`}>
{children}
</div>
</React.Fragment>
)
}
19 changes: 11 additions & 8 deletions src/components/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { ServiceWorkerContext } from '../context/service-worker-context.tsx'
import { HeliaServiceWorkerCommsChannel } from '../lib/channel.ts'
import { getConfig, loadConfigFromLocalStorage } from '../lib/config-db.ts'
import { LOCAL_STORAGE_KEYS } from '../lib/local-storage.ts'
import { Collapsible } from './collapsible'
import LocalStorageInput from './local-storage-input.tsx'
import { LocalStorageToggle } from './local-storage-toggle'
import { ServiceWorkerReadyButton } from './sw-ready-button.tsx'

const channel = new HeliaServiceWorkerCommsChannel('WINDOW')

Expand Down Expand Up @@ -80,10 +82,9 @@ export default (): JSX.Element | null => {
if (!isConfigExpanded) {
return null
}
let saveDisabled = true

const buttonClasses = new Set(['button-reset', 'pv3', 'tc', 'bn', 'white', 'w-100', 'cursor-disabled', 'bg-gray'])
if (isServiceWorkerRegistered) {
saveDisabled = false
buttonClasses.delete('bg-gray')
buttonClasses.delete('cursor-disabled')
buttonClasses.add('bg-animate')
Expand All @@ -94,13 +95,15 @@ export default (): JSX.Element | null => {

return (
<main className='pa4-l bg-snow mw7 center pa4'>
<LocalStorageInput localStorageKey={LOCAL_STORAGE_KEYS.config.gateways} label='Gateways' validationFn={urlValidationFn} defaultValue='[]' />
<LocalStorageInput localStorageKey={LOCAL_STORAGE_KEYS.config.routers} label='Routers' validationFn={urlValidationFn} defaultValue='[]'/>
<LocalStorageToggle localStorageKey={LOCAL_STORAGE_KEYS.config.autoReload} onLabel='Auto Reload' offLabel='Show Config' />
<LocalStorageInput localStorageKey={LOCAL_STORAGE_KEYS.config.debug} label='Debug logging' validationFn={stringValidationFn} defaultValue=''/>
<button id="save-config" disabled={saveDisabled} onClick={() => { void saveConfig() }} className={Array.from(buttonClasses).join(' ')}>{saveDisabled ? 'Waiting for service worker registration...' : 'Save Config'}</button>
<Collapsible collapsedLabel="View config" expandedLabel='Hide config' collapsed={true}>
<LocalStorageInput localStorageKey={LOCAL_STORAGE_KEYS.config.gateways} label='Gateways' validationFn={urlValidationFn} defaultValue='[]' />
<LocalStorageInput localStorageKey={LOCAL_STORAGE_KEYS.config.routers} label='Routers' validationFn={urlValidationFn} defaultValue='[]'/>
<LocalStorageToggle localStorageKey={LOCAL_STORAGE_KEYS.config.autoReload} onLabel='Auto Reload' offLabel='Show Config' />
<LocalStorageInput localStorageKey={LOCAL_STORAGE_KEYS.config.debug} label='Debug logging' validationFn={stringValidationFn} defaultValue=''/>
<ServiceWorkerReadyButton id="save-config" label='Save Config' waitingLabel='Waiting for service worker registration...' onClick={() => { void saveConfig() }} />

{error != null && <span style={{ color: 'red' }}>{error.message}</span>}
{error != null && <span style={{ color: 'red' }}>{error.message}</span>}
</Collapsible>
</main>
)
}
39 changes: 39 additions & 0 deletions src/components/sw-ready-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { useContext, useMemo } from 'react'
import { ServiceWorkerContext } from '../context/service-worker-context.tsx'
type ButtonProps = JSX.IntrinsicElements['button']

interface ServiceWorkerReadyButtonProps extends ButtonProps {
label: string
waitingLabel?: string
}

export const ServiceWorkerReadyButton = ({ className, label, waitingLabel, ...props }: ServiceWorkerReadyButtonProps): JSX.Element => {
const { isServiceWorkerRegistered } = useContext(ServiceWorkerContext)

const buttonClasses = new Set(['button-reset', 'pv3', 'tc', 'bn', 'white', 'w-100', 'cursor-disabled', 'bg-gray'])
if (isServiceWorkerRegistered) {
buttonClasses.delete('bg-gray')
buttonClasses.delete('cursor-disabled')
buttonClasses.add('bg-animate')
buttonClasses.add('bg-black-80')
buttonClasses.add('hover-bg-aqua')
buttonClasses.add('pointer')
}

const lbl = useMemo(() => {
if (!isServiceWorkerRegistered) {
return waitingLabel ?? label
}
return label
}, [isServiceWorkerRegistered, waitingLabel, label])

return (
<button
disabled={!isServiceWorkerRegistered}
className={`${Array.from(buttonClasses).join(' ')} ${className}`}
{...props}
>
{lbl}
</button>
)
}
35 changes: 35 additions & 0 deletions src/lib/get-subdomain-parts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { dnsLinkLabelDecoder, isInlinedDnsLink } from './dns-link-labels.ts'

export interface UrlParts {
id: string | null
protocol: string | null
parentDomain: string
}

export function getSubdomainParts (urlString: string): UrlParts {
const labels = new URL(urlString).hostname.split('.')
let id: string | null = null
let protocol: string | null = null
let parentDomain: string = urlString

// DNS label inspection happens from from right to left
// to work fine with edge cases like docs.ipfs.tech.ipns.foo.localhost
for (let i = labels.length - 1; i >= 0; i--) {
if (labels[i].startsWith('ipfs') || labels[i].startsWith('ipns')) {
protocol = labels[i]
id = labels.slice(0, i).join('.')
parentDomain = labels.slice(i + 1).join('.')
if (protocol === 'ipns' && isInlinedDnsLink(id)) {
// un-inline DNSLink names according to https://specs.ipfs.tech/http-gateways/subdomain-gateway/#host-request-header
id = dnsLinkLabelDecoder(id)
}
break
}
}

return {
id,
parentDomain,
protocol
}
}
11 changes: 9 additions & 2 deletions src/redirectPage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import React, { useContext, useEffect, useMemo, useState } from 'react'
import { ServiceWorkerReadyButton } from './components/sw-ready-button.tsx'
import { ServiceWorkerContext } from './context/service-worker-context.tsx'
import { HeliaServiceWorkerCommsChannel } from './lib/channel.ts'
import { setConfig, type ConfigDb } from './lib/config-db.ts'
import { getSubdomainParts } from './lib/get-subdomain-parts'
import { error } from './lib/logger.ts'

const ConfigIframe = (): JSX.Element => {
const iframeSrc = `${window.location.origin}/config?origin=${encodeURIComponent(window.location.origin)}`
const { parentDomain } = getSubdomainParts(window.location.href)

const iframeSrc = `${window.location.protocol}//${parentDomain}/config?origin=${encodeURIComponent(window.location.origin)}`

return (
<iframe id="redirect-config-iframe" src={iframeSrc} style={{ width: '100vw', height: '100vh', border: 'none' }} />
Expand Down Expand Up @@ -75,7 +79,10 @@ export default function RedirectPage (): JSX.Element {

return (
<div className="redirect-page">
<h1 className="pa4-l mw7 mv5 center pa4">{displayString}</h1>
<div className="pa4-l mw7 mv5 center pa4">
<h3 className="">{displayString}</h3>
<ServiceWorkerReadyButton id="load-content" label='Load content' waitingLabel='Waiting for service worker registration...' onClick={() => { window.location.reload() }} />
</div>
<ConfigIframe />
</div>
)
Expand Down
28 changes: 3 additions & 25 deletions src/sw.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import mime from 'mime-types'
import { getHelia } from './get-helia.ts'
import { HeliaServiceWorkerCommsChannel, type ChannelMessage } from './lib/channel.ts'
import { dnsLinkLabelDecoder, isInlinedDnsLink } from './lib/dns-link-labels.ts'
import { getSubdomainParts } from './lib/get-subdomain-parts.ts'
import { heliaFetch } from './lib/heliaFetch.ts'
import { error, log, trace } from './lib/logger.ts'
import type { Helia } from '@helia/interface'
Expand Down Expand Up @@ -61,7 +61,7 @@ const fetchHandler = async ({ path, request }: FetchHandlerArg): Promise<Respons
// 5 minute timeout
const abortController = AbortSignal.timeout(5 * 60 * 1000)
try {
const { id, protocol } = getSubdomainParts(request)
const { id, protocol } = getSubdomainParts(request.url)
return await heliaFetch({ path, helia, signal: abortController, headers: request.headers, id, protocol })
} catch (err: unknown) {
const errorMessages: string[] = []
Expand Down Expand Up @@ -101,30 +101,8 @@ const isRootRequestForContent = (event: FetchEvent): boolean => {
return isRootRequest // && getCidFromUrl(event.request.url) != null
}

function getSubdomainParts (request: Request): { id: string | null, protocol: string | null } {
const urlString = request.url
const labels = new URL(urlString).hostname.split('.')
let id: string | null = null; let protocol: string | null = null

// DNS label inspection happens from from right to left
// to work fine with edge cases like docs.ipfs.tech.ipns.foo.localhost
for (let i = labels.length - 1; i >= 0; i--) {
if (labels[i].startsWith('ipfs') || labels[i].startsWith('ipns')) {
protocol = labels[i]
id = labels.slice(0, i).join('.')
if (protocol === 'ipns' && isInlinedDnsLink(id)) {
// un-inline DNSLink names according to https://specs.ipfs.tech/http-gateways/subdomain-gateway/#host-request-header
id = dnsLinkLabelDecoder(id)
}
break
}
}

return { id, protocol }
}

function isSubdomainRequest (event: FetchEvent): boolean {
const { id, protocol } = getSubdomainParts(event.request)
const { id, protocol } = getSubdomainParts(event.request.url)
trace('isSubdomainRequest.id: ', id)
trace('isSubdomainRequest.protocol: ', protocol)

Expand Down

0 comments on commit 21c2e7b

Please sign in to comment.