Skip to content

Commit 2d8dbd9

Browse files
authored
fix: config ui is in sync with IDB (#528)
* 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
1 parent 8895691 commit 2d8dbd9

File tree

10 files changed

+861
-616
lines changed

10 files changed

+861
-616
lines changed

package-lock.json

Lines changed: 594 additions & 477 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/config-db.ts

Lines changed: 77 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -81,65 +81,86 @@ export async function setConfig (config: ConfigDbWithoutPrivateFields, logger: C
8181
}
8282
}
8383

84-
export async function getConfig (logger: ComponentLogger): Promise<ConfigDb> {
85-
const log = logger.forComponent('get-config')
86-
let gateways = defaultGateways
87-
let routers = defaultRouters
88-
let dnsJsonResolvers = defaultDnsJsonResolvers
89-
let enableRecursiveGateways
90-
let enableWss
91-
let enableWebTransport
92-
let enableGatewayProviders
93-
let debug = ''
94-
let _supportsSubdomains = defaultSupportsSubdomains
95-
96-
try {
97-
await configDb.open()
98-
99-
gateways = await configDb.get('gateways')
84+
let getConfigPromise: Promise<ConfigDb> | null = null
10085

101-
routers = await configDb.get('routers')
102-
103-
dnsJsonResolvers = await configDb.get('dnsJsonResolvers')
104-
105-
enableRecursiveGateways = await configDb.get('enableRecursiveGateways') ?? defaultEnableRecursiveGateways
106-
enableWss = await configDb.get('enableWss') ?? defaultEnableWss
107-
enableWebTransport = await configDb.get('enableWebTransport') ?? defaultEnableWebTransport
108-
enableGatewayProviders = await configDb.get('enableGatewayProviders') ?? defaultEnableGatewayProviders
109-
110-
debug = await configDb.get('debug') ?? defaultDebug()
111-
enable(debug)
112-
113-
_supportsSubdomains ??= await configDb.get('_supportsSubdomains')
114-
} catch (err) {
115-
log('error loading config from db', err)
116-
} finally {
117-
configDb.close()
118-
}
119-
120-
if (gateways == null || gateways.length === 0) {
121-
gateways = [...defaultGateways]
122-
}
123-
124-
if (routers == null || routers.length === 0) {
125-
routers = [...defaultRouters]
126-
}
127-
if (dnsJsonResolvers == null || Object.keys(dnsJsonResolvers).length === 0) {
128-
dnsJsonResolvers = { ...defaultDnsJsonResolvers }
86+
export async function getConfig (logger: ComponentLogger): Promise<ConfigDb> {
87+
if (getConfigPromise != null) {
88+
/**
89+
* If there is already a promise to get the config, return it.
90+
* This is to prevent multiple calls to the db to get the same config, because
91+
* each request will close the DB when done, and then the next request will fail at some point
92+
*/
93+
return getConfigPromise
12994
}
13095

131-
// always return the config, even if we failed to load it.
132-
return {
133-
gateways,
134-
routers,
135-
dnsJsonResolvers,
136-
enableRecursiveGateways,
137-
enableWss,
138-
enableWebTransport,
139-
enableGatewayProviders,
140-
debug,
141-
_supportsSubdomains
142-
}
96+
getConfigPromise = (async () => {
97+
const log = logger.forComponent('get-config')
98+
let gateways = defaultGateways
99+
let routers = defaultRouters
100+
let dnsJsonResolvers = defaultDnsJsonResolvers
101+
let enableRecursiveGateways
102+
let enableWss
103+
let enableWebTransport
104+
let enableGatewayProviders
105+
let debug = ''
106+
let _supportsSubdomains = defaultSupportsSubdomains
107+
108+
let config: ConfigDb
109+
110+
log('config-debug: getting config for domain %s', globalThis.location.origin)
111+
try {
112+
await configDb.open()
113+
114+
config = await configDb.getAll()
115+
debug = config.debug ?? defaultDebug()
116+
enable(debug)
117+
118+
gateways = config.gateways
119+
120+
routers = config.routers
121+
122+
dnsJsonResolvers = config.dnsJsonResolvers
123+
enableRecursiveGateways = config.enableRecursiveGateways ?? defaultEnableRecursiveGateways
124+
enableWss = config.enableWss ?? defaultEnableWss
125+
enableWebTransport = config.enableWebTransport ?? defaultEnableWebTransport
126+
enableGatewayProviders = config.enableGatewayProviders ?? defaultEnableGatewayProviders
127+
128+
_supportsSubdomains ??= config.thing
129+
} catch (err) {
130+
log('error loading config from db', err)
131+
} finally {
132+
configDb.close()
133+
}
134+
135+
if (gateways == null || gateways.length === 0) {
136+
gateways = [...defaultGateways]
137+
}
138+
139+
if (routers == null || routers.length === 0) {
140+
routers = [...defaultRouters]
141+
}
142+
if (dnsJsonResolvers == null || Object.keys(dnsJsonResolvers).length === 0) {
143+
dnsJsonResolvers = { ...defaultDnsJsonResolvers }
144+
}
145+
146+
// always return the config, even if we failed to load it.
147+
return {
148+
gateways,
149+
routers,
150+
dnsJsonResolvers,
151+
enableRecursiveGateways,
152+
enableWss,
153+
enableWebTransport,
154+
enableGatewayProviders,
155+
debug,
156+
_supportsSubdomains
157+
}
158+
})().finally(() => {
159+
getConfigPromise = null
160+
})
161+
162+
const result = await getConfigPromise
163+
return result
143164
}
144165

145166
export async function validateConfig (config: ConfigDbWithoutPrivateFields, logger: ComponentLogger): Promise<void> {

src/lib/generic-db.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,34 @@ export class GenericIDB<T extends BaseDbConfig> {
5959
})
6060
}
6161

62+
async getAll (): Promise<T> {
63+
if (this.db == null) {
64+
throw new Error('Database not opened')
65+
}
66+
const transaction = this.db.transaction(this.storeName, 'readonly')
67+
const store = transaction.objectStore(this.storeName)
68+
69+
return new Promise((resolve, reject) => {
70+
// @ts-expect-error - its empty right now...
71+
const result: { [K in keyof T]: T[K] } = {}
72+
const request = store.openCursor()
73+
74+
request.onerror = () => {
75+
reject(request.error ?? new Error(`Could not get all keys and values from store "${this.storeName}"`))
76+
}
77+
78+
request.onsuccess = () => {
79+
const cursor = request.result
80+
if (cursor != null) {
81+
result[cursor.key as keyof T] = cursor.value as T[keyof T]
82+
cursor.continue()
83+
} else {
84+
resolve(result)
85+
}
86+
}
87+
})
88+
}
89+
6290
close (): void {
6391
if (this.db != null) {
6492
this.db.close()

src/pages/config.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,23 +150,23 @@ const ConfigPage: FunctionComponent<ConfigPageProps> = () => {
150150
<h1 className='pa0 f3 ma0 mb4 teal tc'>Configure your IPFS Gateway</h1>
151151
<InputSection label='Direct Retrieval'>
152152
<InputToggle
153-
className="e2e-config-page-input"
153+
className="e2e-config-page-input e2e-config-page-input-enableGatewayProviders"
154154
label="Enable Delegated HTTP Gateway Providers"
155155
description="Use gateway providers returned from delegated routers for direct retrieval."
156156
value={enableGatewayProviders}
157157
onChange={(value) => { setConfig('enableGatewayProviders', value) }}
158158
resetKey={resetKey}
159159
/>
160160
<InputToggle
161-
className="e2e-config-page-input"
161+
className="e2e-config-page-input e2e-config-page-input-enableWss"
162162
label="Enable Secure WebSocket Providers"
163163
description="Use Secure WebSocket providers returned from delegated routers for direct retrieval."
164164
value={enableWss}
165165
onChange={(value) => { setConfig('enableWss', value) }}
166166
resetKey={resetKey}
167167
/>
168168
<InputToggle
169-
className="e2e-config-page-input"
169+
className="e2e-config-page-input e2e-config-page-input-enableWebTransport"
170170
label="Enable WebTransport Providers"
171171
description="Use WebTransport providers returned from delegated routers for direct retrieval."
172172
value={enableWebTransport}
@@ -186,7 +186,7 @@ const ConfigPage: FunctionComponent<ConfigPageProps> = () => {
186186
</InputSection>
187187
<InputSection label='Fallback Retrieval'>
188188
<InputToggle
189-
className="e2e-config-page-input"
189+
className="e2e-config-page-input e2e-config-page-input-enableRecursiveGateways"
190190
label="Enable Recursive Gateways"
191191
description="Use recursive gateways configured below for retrieval of content."
192192
value={enableRecursiveGateways}
@@ -216,7 +216,7 @@ const ConfigPage: FunctionComponent<ConfigPageProps> = () => {
216216
resetKey={resetKey}
217217
/>
218218
<Input
219-
className="e2e-config-page-input"
219+
className="e2e-config-page-input e2e-config-page-input-debug"
220220
description="A string that enables debug logging. Use '*,*:trace' to enable all debug logging."
221221
label='Debug'
222222
value={debug}

test-e2e/config-ui.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Ensure the config saves to IDB and on refresh, the config is loaded from IDB
3+
*/
4+
5+
import { testPathRouting as test, expect } from './fixtures/config-test-fixtures.js'
6+
import { getConfig, getConfigUi, setConfigViaUi } from './fixtures/set-sw-config.js'
7+
8+
test.describe('config-ui', () => {
9+
test('setting the config via UI actually works', async ({ page, protocol, rootDomain }) => {
10+
await page.goto(`${protocol}//${rootDomain}`)
11+
12+
// read the config from the page
13+
const config = await getConfigUi({ page })
14+
15+
// change the config
16+
const testConfig: typeof config = {
17+
...config,
18+
gateways: ['https://example.com'],
19+
routers: ['https://example2.com']
20+
}
21+
22+
// change the UI & save it
23+
await setConfigViaUi({ page, config: testConfig })
24+
25+
// verify that the IndexedDB has the new config
26+
expect(await getConfig({ page })).toMatchObject(testConfig)
27+
28+
// reload the page, and ensure the config is the same as the one we set
29+
await page.reload()
30+
expect(await getConfigUi({ page })).toMatchObject(testConfig)
31+
expect(await getConfig({ page })).toMatchObject(testConfig)
32+
})
33+
})

test-e2e/fixtures/config-test-fixtures.ts

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { test as base, type Page } from '@playwright/test'
2-
import { setConfig, setSubdomainConfig } from './set-sw-config.js'
1+
import { test as base } from '@playwright/test'
2+
import { setConfig } from './set-sw-config.js'
33
import { waitForServiceWorker } from './wait-for-service-worker.js'
44

55
function isNoServiceWorkerProject <T extends typeof base = typeof base> (test: T): boolean {
@@ -90,26 +90,6 @@ export const testSubdomainRouting = test.extend<{ rootDomain: string, baseURL: s
9090
throw new Error('KUBO_GATEWAY not set')
9191
}
9292
const kuboGateway = process.env.KUBO_GATEWAY
93-
const oldPageGoto = page.goto.bind(page)
94-
page.goto = async (url: Parameters<Page['goto']>[0], options: Parameters<Page['goto']>[1]): ReturnType<Page['goto']> => {
95-
const response = await oldPageGoto(url, options)
96-
if (['.ipfs.', '.ipns.'].some((part) => url.includes(part))) {
97-
await setSubdomainConfig({
98-
page,
99-
config: {
100-
autoReload: true,
101-
gateways: [kuboGateway],
102-
routers: [kuboGateway],
103-
dnsJsonResolvers: {
104-
'.': 'https://delegated-ipfs.dev/dns-query'
105-
}
106-
}
107-
})
108-
} else {
109-
// already set on root.
110-
}
111-
return response
112-
}
11393

11494
// set config for the initial page
11595
await setConfig({

test-e2e/fixtures/locators.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@ export const getConfigPage: GetLocator = (page) => page.locator('.e2e-config-pag
1616
export const getConfigPageInput: GetLocator = (page) => page.locator('.e2e-config-page-input')
1717
export const getConfigPageSaveButton: GetLocator = (page) => page.locator('.e2e-config-page-button#save-config')
1818
export const getIframeLocator: GetFrameLocator = (page) => page.frameLocator('iframe')
19-
export const getConfigGatewaysInput: GetLocator = (page) => page.locator('.e2e-config-page-input-gateways')
19+
export const getConfigEnableGatewayProviders: GetLocator = (page) => page.locator('.e2e-config-page-input-enableGatewayProviders')
20+
export const getConfigEnableWss: GetLocator = (page) => page.locator('.e2e-config-page-input-enableWss')
21+
export const getConfigEnableWebTransport: GetLocator = (page) => page.locator('.e2e-config-page-input-enableWebTransport')
2022
export const getConfigRoutersInput: GetLocator = (page) => page.locator('.e2e-config-page-input-routers')
21-
export const getConfigAutoReloadInput: GetLocator = (page) => page.locator('.e2e-config-page-input-autoreload')
23+
export const getConfigEnableRecursiveGateways: GetLocator = (page) => page.locator('.e2e-config-page-input-enableRecursiveGateways')
24+
export const getConfigGatewaysInput: GetLocator = (page) => page.locator('.e2e-config-page-input-gateways')
25+
export const getConfigDnsJsonResolvers: GetLocator = (page) => page.locator('.e2e-config-page-input-dnsJsonResolvers')
26+
export const getConfigDebug: GetLocator = (page) => page.locator('.e2e-config-page-input-debug')
2227

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

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

0 commit comments

Comments
 (0)