Skip to content

Commit efe4f81

Browse files
authored
fix(adobe): refresh kit meta every five minutes (#198)
1 parent eb5f2e0 commit efe4f81

File tree

2 files changed

+151
-11
lines changed

2 files changed

+151
-11
lines changed

src/providers/adobe.ts

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,50 @@ async function getAdobeFontMeta(id: string): Promise<AdobeFontKit> {
1616
return kit
1717
}
1818

19+
const KIT_REFRESH_TIMEOUT = 5 * 60 * 1000
20+
1921
export default defineFontProvider<ProviderOption>('adobe', async (options, ctx) => {
2022
if (!options.id) {
2123
return
2224
}
2325

2426
const familyMap = new Map<string, string>()
27+
const notFoundFamilies = new Set<string>()
2528
const fonts = {
2629
kits: [] as AdobeFontKit[],
2730
}
31+
let lastRefreshKitTime: number
2832

2933
const kits = typeof options.id === 'string' ? [options.id] : options.id
30-
await Promise.all(kits.map(async (id) => {
31-
const meta = await ctx.storage.getItem<AdobeFontKit>(`adobe:meta-${id}.json`, () => getAdobeFontMeta(id))
32-
if (!meta) {
33-
throw new TypeError('No font metadata found in adobe response.')
34-
}
3534

36-
fonts.kits.push(meta)
37-
for (const family of meta.families) {
38-
familyMap.set(family.name, family.id)
39-
}
40-
}))
35+
await fetchKits()
36+
37+
async function fetchKits(bypassCache: boolean = false) {
38+
familyMap.clear()
39+
notFoundFamilies.clear()
40+
fonts.kits = []
41+
42+
await Promise.all(kits.map(async (id) => {
43+
let meta: AdobeFontKit
44+
const key = `adobe:meta-${id}.json`
45+
if (bypassCache) {
46+
meta = await getAdobeFontMeta(id)
47+
await ctx.storage.setItem(key, meta)
48+
}
49+
else {
50+
meta = await ctx.storage.getItem(key, () => getAdobeFontMeta(id))
51+
}
52+
53+
if (!meta) {
54+
throw new TypeError('No font metadata found in adobe response.')
55+
}
56+
57+
fonts.kits.push(meta)
58+
for (const family of meta.families) {
59+
familyMap.set(family.name, family.id)
60+
}
61+
}))
62+
}
4163

4264
async function getFontDetails(family: string, options: ResolveFontOptions) {
4365
options.weights = options.weights.map(String)
@@ -90,7 +112,25 @@ export default defineFontProvider<ProviderOption>('adobe', async (options, ctx)
90112
return [...familyMap.keys()]
91113
},
92114
async resolveFont(family, options) {
115+
// Check if family is in negative cache first (used to prevent unnecessary refreshes)
116+
if (notFoundFamilies.has(family)) {
117+
return
118+
}
119+
120+
// Try refreshing the kit metadata if family is not found. We use a debounce mechanism to avoid frequent refreshes.
121+
if (!familyMap.has(family)) {
122+
const lastRefetch = lastRefreshKitTime || 0
123+
const now = Date.now()
124+
125+
if (now - lastRefetch > KIT_REFRESH_TIMEOUT) {
126+
lastRefreshKitTime = Date.now()
127+
await fetchKits(true)
128+
}
129+
}
130+
93131
if (!familyMap.has(family)) {
132+
// Add to negative cache to avoid future refreshes for this family
133+
notFoundFamilies.add(family)
94134
return
95135
}
96136

test/providers/adobe.test.ts

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('adobe', () => {
2828
const unifont = await createUnifont([providers.adobe({ id: ['bob'] })])
2929

3030
const restoreFetch = mockFetchReturn(/^https:\/\/typekit.com\/api\//, () => {
31-
return { json: () => Promise.resolve({ kit: '' }) }
31+
return Promise.resolve({ json: () => Promise.resolve({ kit: '' }) })
3232
})
3333
expect(await unifont.resolveFont('Aleo').then(r => r.fonts)).toMatchInlineSnapshot(`[]`)
3434

@@ -38,6 +38,7 @@ describe('adobe', () => {
3838
)
3939

4040
restoreFetch()
41+
vi.restoreAllMocks()
4142
})
4243

4344
it('works', async () => {
@@ -120,4 +121,103 @@ describe('adobe', () => {
120121
})
121122
expect(fonts.length).toBe(4)
122123
})
124+
125+
it('refreshes kit metadata when font is not found in cache', async () => {
126+
let apiCallCount = 0
127+
128+
// Mock the API endpoint to return different kits on subsequent calls
129+
const restoreFetch = mockFetchReturn(
130+
/^https:\/\/typekit.com\/api\/v1\/json\/kits\/test123\/published/,
131+
() => {
132+
apiCallCount++
133+
134+
if (apiCallCount === 1) {
135+
// First API call - return kit without NewFont
136+
return Promise.resolve({
137+
json: () => Promise.resolve({
138+
kit: {
139+
id: 'test123',
140+
families: [
141+
{
142+
id: 'aleo',
143+
name: 'Aleo',
144+
slug: 'aleo',
145+
css_names: ['aleo'],
146+
css_stack: 'aleo, serif',
147+
variations: ['n4', 'i4'],
148+
},
149+
],
150+
},
151+
}),
152+
})
153+
}
154+
else {
155+
// Second API call - return kit with NewFont
156+
return Promise.resolve({
157+
json: () => Promise.resolve({
158+
kit: {
159+
id: 'test123',
160+
families: [
161+
{
162+
id: 'aleo',
163+
name: 'Aleo',
164+
slug: 'aleo',
165+
css_names: ['aleo'],
166+
css_stack: 'aleo, serif',
167+
variations: ['n4', 'i4'],
168+
},
169+
{
170+
id: 'newfont',
171+
name: 'NewFont',
172+
slug: 'newfont',
173+
css_names: ['newfont'],
174+
css_stack: 'newfont, sans-serif',
175+
variations: ['n4', 'n7'],
176+
},
177+
],
178+
},
179+
}),
180+
})
181+
}
182+
},
183+
)
184+
185+
// Add a mock for the CSS file that will be fetched
186+
mockFetchReturn(
187+
/^https:\/\/use\.typekit\.net\/test123\.css/,
188+
() => {
189+
return Promise.resolve({
190+
text: () => Promise.resolve(`
191+
@font-face {
192+
font-family: "newfont";
193+
src: url("https://use.typekit.net/font.woff2") format("woff2");
194+
font-weight: 400;
195+
font-style: normal;
196+
}
197+
`),
198+
})
199+
},
200+
)
201+
202+
try {
203+
// Initialize unifont with the initial kit (without NewFont)
204+
const unifont = await createUnifont([providers.adobe({ id: 'test123' })])
205+
expect(apiCallCount).toBe(1)
206+
207+
// Verify NewFont is not initially available
208+
const initialFonts = await unifont.listFonts()
209+
expect(initialFonts).not.toContain('NewFont')
210+
211+
// Try to resolve NewFont - this should trigger a refetch
212+
const result = await unifont.resolveFont('NewFont')
213+
214+
// Ensure the font is now available
215+
expect(result).toBeDefined()
216+
expect(result.fonts).toBeDefined()
217+
expect(result.fonts.length).toBeGreaterThan(0)
218+
}
219+
finally {
220+
restoreFetch()
221+
}
222+
})
123223
})

0 commit comments

Comments
 (0)