Skip to content

Commit

Permalink
fix: config ui is in sync with IDB (#528)
Browse files Browse the repository at this point in the history
* chore: update package-lock.json

* fix: config UI is up to date with IDB

* chore: apply self suggestions from code review

* chore: fix build and lint
  • Loading branch information
SgtPooki authored Dec 12, 2024
1 parent 8895691 commit 2d8dbd9
Show file tree
Hide file tree
Showing 10 changed files with 861 additions and 616 deletions.
1,071 changes: 594 additions & 477 deletions package-lock.json

Large diffs are not rendered by default.

133 changes: 77 additions & 56 deletions src/lib/config-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,65 +81,86 @@ export async function setConfig (config: ConfigDbWithoutPrivateFields, logger: C
}
}

export async function getConfig (logger: ComponentLogger): Promise<ConfigDb> {
const log = logger.forComponent('get-config')
let gateways = defaultGateways
let routers = defaultRouters
let dnsJsonResolvers = defaultDnsJsonResolvers
let enableRecursiveGateways
let enableWss
let enableWebTransport
let enableGatewayProviders
let debug = ''
let _supportsSubdomains = defaultSupportsSubdomains

try {
await configDb.open()

gateways = await configDb.get('gateways')
let getConfigPromise: Promise<ConfigDb> | null = null

routers = await configDb.get('routers')

dnsJsonResolvers = await configDb.get('dnsJsonResolvers')

enableRecursiveGateways = await configDb.get('enableRecursiveGateways') ?? defaultEnableRecursiveGateways
enableWss = await configDb.get('enableWss') ?? defaultEnableWss
enableWebTransport = await configDb.get('enableWebTransport') ?? defaultEnableWebTransport
enableGatewayProviders = await configDb.get('enableGatewayProviders') ?? defaultEnableGatewayProviders

debug = await configDb.get('debug') ?? defaultDebug()
enable(debug)

_supportsSubdomains ??= await configDb.get('_supportsSubdomains')
} catch (err) {
log('error loading config from db', err)
} finally {
configDb.close()
}

if (gateways == null || gateways.length === 0) {
gateways = [...defaultGateways]
}

if (routers == null || routers.length === 0) {
routers = [...defaultRouters]
}
if (dnsJsonResolvers == null || Object.keys(dnsJsonResolvers).length === 0) {
dnsJsonResolvers = { ...defaultDnsJsonResolvers }
export async function getConfig (logger: ComponentLogger): Promise<ConfigDb> {
if (getConfigPromise != null) {
/**
* If there is already a promise to get the config, return it.
* This is to prevent multiple calls to the db to get the same config, because
* each request will close the DB when done, and then the next request will fail at some point
*/
return getConfigPromise
}

// always return the config, even if we failed to load it.
return {
gateways,
routers,
dnsJsonResolvers,
enableRecursiveGateways,
enableWss,
enableWebTransport,
enableGatewayProviders,
debug,
_supportsSubdomains
}
getConfigPromise = (async () => {
const log = logger.forComponent('get-config')
let gateways = defaultGateways
let routers = defaultRouters
let dnsJsonResolvers = defaultDnsJsonResolvers
let enableRecursiveGateways
let enableWss
let enableWebTransport
let enableGatewayProviders
let debug = ''
let _supportsSubdomains = defaultSupportsSubdomains

let config: ConfigDb

log('config-debug: getting config for domain %s', globalThis.location.origin)
try {
await configDb.open()

config = await configDb.getAll()
debug = config.debug ?? defaultDebug()
enable(debug)

gateways = config.gateways

routers = config.routers

dnsJsonResolvers = config.dnsJsonResolvers
enableRecursiveGateways = config.enableRecursiveGateways ?? defaultEnableRecursiveGateways
enableWss = config.enableWss ?? defaultEnableWss
enableWebTransport = config.enableWebTransport ?? defaultEnableWebTransport
enableGatewayProviders = config.enableGatewayProviders ?? defaultEnableGatewayProviders

_supportsSubdomains ??= config.thing
} catch (err) {
log('error loading config from db', err)
} finally {
configDb.close()
}

if (gateways == null || gateways.length === 0) {
gateways = [...defaultGateways]
}

if (routers == null || routers.length === 0) {
routers = [...defaultRouters]
}
if (dnsJsonResolvers == null || Object.keys(dnsJsonResolvers).length === 0) {
dnsJsonResolvers = { ...defaultDnsJsonResolvers }
}

// always return the config, even if we failed to load it.
return {
gateways,
routers,
dnsJsonResolvers,
enableRecursiveGateways,
enableWss,
enableWebTransport,
enableGatewayProviders,
debug,
_supportsSubdomains
}
})().finally(() => {
getConfigPromise = null
})

const result = await getConfigPromise
return result
}

export async function validateConfig (config: ConfigDbWithoutPrivateFields, logger: ComponentLogger): Promise<void> {
Expand Down
28 changes: 28 additions & 0 deletions src/lib/generic-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,34 @@ export class GenericIDB<T extends BaseDbConfig> {
})
}

async getAll (): Promise<T> {
if (this.db == null) {
throw new Error('Database not opened')
}
const transaction = this.db.transaction(this.storeName, 'readonly')
const store = transaction.objectStore(this.storeName)

return new Promise((resolve, reject) => {
// @ts-expect-error - its empty right now...
const result: { [K in keyof T]: T[K] } = {}
const request = store.openCursor()

request.onerror = () => {
reject(request.error ?? new Error(`Could not get all keys and values from store "${this.storeName}"`))
}

request.onsuccess = () => {
const cursor = request.result
if (cursor != null) {
result[cursor.key as keyof T] = cursor.value as T[keyof T]
cursor.continue()
} else {
resolve(result)
}
}
})
}

close (): void {
if (this.db != null) {
this.db.close()
Expand Down
10 changes: 5 additions & 5 deletions src/pages/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,23 +150,23 @@ const ConfigPage: FunctionComponent<ConfigPageProps> = () => {
<h1 className='pa0 f3 ma0 mb4 teal tc'>Configure your IPFS Gateway</h1>
<InputSection label='Direct Retrieval'>
<InputToggle
className="e2e-config-page-input"
className="e2e-config-page-input e2e-config-page-input-enableGatewayProviders"
label="Enable Delegated HTTP Gateway Providers"
description="Use gateway providers returned from delegated routers for direct retrieval."
value={enableGatewayProviders}
onChange={(value) => { setConfig('enableGatewayProviders', value) }}
resetKey={resetKey}
/>
<InputToggle
className="e2e-config-page-input"
className="e2e-config-page-input e2e-config-page-input-enableWss"
label="Enable Secure WebSocket Providers"
description="Use Secure WebSocket providers returned from delegated routers for direct retrieval."
value={enableWss}
onChange={(value) => { setConfig('enableWss', value) }}
resetKey={resetKey}
/>
<InputToggle
className="e2e-config-page-input"
className="e2e-config-page-input e2e-config-page-input-enableWebTransport"
label="Enable WebTransport Providers"
description="Use WebTransport providers returned from delegated routers for direct retrieval."
value={enableWebTransport}
Expand All @@ -186,7 +186,7 @@ const ConfigPage: FunctionComponent<ConfigPageProps> = () => {
</InputSection>
<InputSection label='Fallback Retrieval'>
<InputToggle
className="e2e-config-page-input"
className="e2e-config-page-input e2e-config-page-input-enableRecursiveGateways"
label="Enable Recursive Gateways"
description="Use recursive gateways configured below for retrieval of content."
value={enableRecursiveGateways}
Expand Down Expand Up @@ -216,7 +216,7 @@ const ConfigPage: FunctionComponent<ConfigPageProps> = () => {
resetKey={resetKey}
/>
<Input
className="e2e-config-page-input"
className="e2e-config-page-input e2e-config-page-input-debug"
description="A string that enables debug logging. Use '*,*:trace' to enable all debug logging."
label='Debug'
value={debug}
Expand Down
33 changes: 33 additions & 0 deletions test-e2e/config-ui.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Ensure the config saves to IDB and on refresh, the config is loaded from IDB
*/

import { testPathRouting as test, expect } from './fixtures/config-test-fixtures.js'
import { getConfig, getConfigUi, setConfigViaUi } from './fixtures/set-sw-config.js'

test.describe('config-ui', () => {
test('setting the config via UI actually works', async ({ page, protocol, rootDomain }) => {
await page.goto(`${protocol}//${rootDomain}`)

// read the config from the page
const config = await getConfigUi({ page })

// change the config
const testConfig: typeof config = {
...config,
gateways: ['https://example.com'],
routers: ['https://example2.com']
}

// change the UI & save it
await setConfigViaUi({ page, config: testConfig })

// verify that the IndexedDB has the new config
expect(await getConfig({ page })).toMatchObject(testConfig)

// reload the page, and ensure the config is the same as the one we set
await page.reload()
expect(await getConfigUi({ page })).toMatchObject(testConfig)
expect(await getConfig({ page })).toMatchObject(testConfig)
})
})
24 changes: 2 additions & 22 deletions test-e2e/fixtures/config-test-fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { test as base, type Page } from '@playwright/test'
import { setConfig, setSubdomainConfig } from './set-sw-config.js'
import { test as base } from '@playwright/test'
import { setConfig } from './set-sw-config.js'
import { waitForServiceWorker } from './wait-for-service-worker.js'

function isNoServiceWorkerProject <T extends typeof base = typeof base> (test: T): boolean {
Expand Down Expand Up @@ -90,26 +90,6 @@ export const testSubdomainRouting = test.extend<{ rootDomain: string, baseURL: s
throw new Error('KUBO_GATEWAY not set')
}
const kuboGateway = process.env.KUBO_GATEWAY
const oldPageGoto = page.goto.bind(page)
page.goto = async (url: Parameters<Page['goto']>[0], options: Parameters<Page['goto']>[1]): ReturnType<Page['goto']> => {
const response = await oldPageGoto(url, options)
if (['.ipfs.', '.ipns.'].some((part) => url.includes(part))) {
await setSubdomainConfig({
page,
config: {
autoReload: true,
gateways: [kuboGateway],
routers: [kuboGateway],
dnsJsonResolvers: {
'.': 'https://delegated-ipfs.dev/dns-query'
}
}
})
} else {
// already set on root.
}
return response
}

// set config for the initial page
await setConfig({
Expand Down
10 changes: 7 additions & 3 deletions test-e2e/fixtures/locators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ export const getConfigPage: GetLocator = (page) => page.locator('.e2e-config-pag
export const getConfigPageInput: GetLocator = (page) => page.locator('.e2e-config-page-input')
export const getConfigPageSaveButton: GetLocator = (page) => page.locator('.e2e-config-page-button#save-config')
export const getIframeLocator: GetFrameLocator = (page) => page.frameLocator('iframe')
export const getConfigGatewaysInput: GetLocator = (page) => page.locator('.e2e-config-page-input-gateways')
export const getConfigEnableGatewayProviders: GetLocator = (page) => page.locator('.e2e-config-page-input-enableGatewayProviders')
export const getConfigEnableWss: GetLocator = (page) => page.locator('.e2e-config-page-input-enableWss')
export const getConfigEnableWebTransport: GetLocator = (page) => page.locator('.e2e-config-page-input-enableWebTransport')
export const getConfigRoutersInput: GetLocator = (page) => page.locator('.e2e-config-page-input-routers')
export const getConfigAutoReloadInput: GetLocator = (page) => page.locator('.e2e-config-page-input-autoreload')
export const getConfigEnableRecursiveGateways: GetLocator = (page) => page.locator('.e2e-config-page-input-enableRecursiveGateways')
export const getConfigGatewaysInput: GetLocator = (page) => page.locator('.e2e-config-page-input-gateways')
export const getConfigDnsJsonResolvers: GetLocator = (page) => page.locator('.e2e-config-page-input-dnsJsonResolvers')
export const getConfigDebug: GetLocator = (page) => page.locator('.e2e-config-page-input-debug')

export const getNoServiceWorkerError: GetLocator = (page) => page.locator('.e2e-no-service-worker-error')

Expand All @@ -31,5 +36,4 @@ export const getAboutSection: GetLocator = (page) => page.locator('.e2e-about-se
export const getConfigButtonIframe: GetLocator = (page) => getIframeLocator(page).locator('.e2e-collapsible-button')
export const getConfigGatewaysInputIframe: GetLocator = (page) => getConfigGatewaysInput(getIframeLocator(page))
export const getConfigRoutersInputIframe: GetLocator = (page) => getConfigRoutersInput(getIframeLocator(page))
export const getConfigAutoReloadInputIframe: GetLocator = (page) => getConfigAutoReloadInput(getIframeLocator(page))
export const getConfigPageSaveButtonIframe: GetLocator = (page) => getConfigPageSaveButton(getIframeLocator(page))
Loading

0 comments on commit 2d8dbd9

Please sign in to comment.