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

fix: config ui is in sync with IDB #528

Merged
merged 4 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading