🍱 URL resolver api caching handled by the client

- Told browser to not to cache the api request
- Removed manual memory cache
- Implimented caching using indexedDB
- Cache time is 1 day
This commit is contained in:
Shiba 2022-01-09 11:54:53 +00:00
parent 73508ff532
commit 685f9615ca
2 changed files with 94 additions and 15 deletions

View file

@ -38,6 +38,69 @@ export interface YtIdResolverDescriptor {
type: 'channel' | 'video' type: 'channel' | 'video'
} }
const URLResolverCache = (() =>
{
const openRequest = indexedDB.open("yt-url-resolver-cache")
if (typeof self.indexedDB !== 'undefined')
{
openRequest.addEventListener('upgradeneeded', () =>
{
const db = openRequest.result
const store = db.createObjectStore("store")
store.createIndex("expireAt", "expireAt")
})
// Delete Expired
openRequest.addEventListener('success', () =>
{
const db = openRequest.result
const transaction = db.transaction("store", "readwrite")
const range = IDBKeyRange.upperBound(new Date())
const expireAtCursorRequest = transaction.objectStore("store").index("expireAt").openCursor(range)
expireAtCursorRequest.addEventListener('success', () =>
{
const timestampCursor = expireAtCursorRequest.result
if (!timestampCursor) return
console.log("deleting: " + timestampCursor)
timestampCursor.delete()
timestampCursor.continue()
})
})
}
else console.warn(`IndexedDB not supported`)
async function put(url: string, id: string) : Promise<void>
{
return await new Promise((resolve, reject) =>
{
const db = openRequest.result
console.log('new put!')
if (!db) return resolve()
console.log('new put')
const store = db.transaction("store", "readwrite").objectStore("store")
const putRequest = store.put({ value: url, expireAt: new Date(Date.now() + 24 * 60 * 60 * 1000) }, id)
putRequest.addEventListener('success', () => resolve())
putRequest.addEventListener('error', () => reject(putRequest.error))
})
}
async function get(id: string): Promise<string>
{
return (await new Promise((resolve, reject) =>
{
const db = openRequest.result
if (!db) return resolve(null)
const store = db.transaction("store", "readonly").objectStore("store")
const getRequest = store.get(id)
getRequest.addEventListener('success', () => resolve(getRequest.result))
getRequest.addEventListener('error', () => reject(getRequest.error))
}) as any)?.value
}
return { put, get }
})()
export const ytService = { export const ytService = {
/** /**
@ -113,9 +176,24 @@ export const ytService = {
*/ */
async resolveById(descriptors: YtIdResolverDescriptor[], progressCallback?: (progress: number) => void): Promise<string[]> { async resolveById(descriptors: YtIdResolverDescriptor[], progressCallback?: (progress: number) => void): Promise<string[]> {
const descriptorsWithIndex: (YtIdResolverDescriptor & { index: number })[] = descriptors.map((descriptor, index) => ({...descriptor, index})) const descriptorsWithIndex: (YtIdResolverDescriptor & { index: number })[] = descriptors.map((descriptor, index) => ({...descriptor, index}))
descriptors = null as any
const results: string[] = []
await Promise.all(descriptorsWithIndex.map(async (descriptor, index) => {
if (!descriptor) return
const cache = await URLResolverCache.get(descriptor.id)
// Cache can be null, if there is no lbry url yet
if (cache !== undefined) {
// Directly setting it to results
results[index] = cache
// We remove it so we dont ask it to API
descriptorsWithIndex.splice(index, 1)
}
}))
const descriptorsChunks = chunk(descriptorsWithIndex, QUERY_CHUNK_SIZE); const descriptorsChunks = chunk(descriptorsWithIndex, QUERY_CHUNK_SIZE);
const results: string[] = []
let progressCount = 0; let progressCount = 0;
await Promise.all(descriptorsChunks.map(async (descriptorChunk) => await Promise.all(descriptorsChunks.map(async (descriptorChunk) =>
{ {
@ -154,6 +232,7 @@ export const ytService = {
async function requestGroup(urlResolverFunction: YtUrlResolveFunction, descriptorsGroup: typeof descriptorsWithIndex) async function requestGroup(urlResolverFunction: YtUrlResolveFunction, descriptorsGroup: typeof descriptorsWithIndex)
{ {
url.pathname = urlResolverFunction.pathname url.pathname = urlResolverFunction.pathname
if (urlResolverFunction.paramArraySeperator === SingleValueAtATime) if (urlResolverFunction.paramArraySeperator === SingleValueAtATime)
{ {
await Promise.all(descriptorsGroup.map(async (descriptor) => { await Promise.all(descriptorsGroup.map(async (descriptor) => {
@ -163,10 +242,12 @@ export const ytService = {
if (!descriptor.id) break if (!descriptor.id) break
url.searchParams.set(urlResolverFunction.paramName, descriptor.id) url.searchParams.set(urlResolverFunction.paramName, descriptor.id)
const apiResponse = await fetch(url.toString(), { cache: 'reload' }); const apiResponse = await fetch(url.toString(), { cache: 'no-store' });
if (!apiResponse.ok) break if (!apiResponse.ok) break
const value = followResponsePath<string>(await apiResponse.json(), urlResolverFunction.responsePath) const value = followResponsePath<string>(await apiResponse.json(), urlResolverFunction.responsePath)
if (value) results[descriptor.index] = value if (value) results[descriptor.index] = value
await URLResolverCache.put(value, descriptor.id)
} }
progressCount++ progressCount++
if (progressCallback) progressCallback(progressCount / descriptorsWithIndex.length) if (progressCallback) progressCallback(progressCount / descriptorsWithIndex.length)
@ -184,13 +265,15 @@ export const ytService = {
.filter((descriptorId) => descriptorId) .filter((descriptorId) => descriptorId)
.join(urlResolverFunction.paramArraySeperator) .join(urlResolverFunction.paramArraySeperator)
) )
const apiResponse = await fetch(url.toString(), { cache: 'reload' }); const apiResponse = await fetch(url.toString(), { cache: 'no-store' });
if (!apiResponse.ok) break if (!apiResponse.ok) break
const values = followResponsePath<string[]>(await apiResponse.json(), urlResolverFunction.responsePath) const values = followResponsePath<string[]>(await apiResponse.json(), urlResolverFunction.responsePath)
values.forEach((value, index) => { console.log('hellooo')
const descriptorIndex = descriptorsGroup[index].index await Promise.all(values.map(async (value, index) => {
if (value) (results[descriptorIndex] = value) const descriptor = descriptorsGroup[index]
}) if (value) results[descriptor.index] = value
await URLResolverCache.put(value, descriptor.id)
}))
} }
progressCount += descriptorsGroup.length progressCount += descriptorsGroup.length
if (progressCallback) progressCallback(progressCount / descriptorsWithIndex.length) if (progressCallback) progressCallback(progressCount / descriptorsWithIndex.length)

View file

@ -16,7 +16,6 @@ async function resolveYT(descriptor: YtIdResolverDescriptor) {
return segments.join('/'); return segments.join('/');
} }
const ctxFromURLIdCache: Record<string, Promise<string | undefined>> = {}
async function ctxFromURL(href: string): Promise<UpdateContext | void> { async function ctxFromURL(href: string): Promise<UpdateContext | void> {
if (!href) return; if (!href) return;
@ -27,11 +26,8 @@ async function ctxFromURL(href: string): Promise<UpdateContext | void> {
const descriptor = ytService.getId(href); const descriptor = ytService.getId(href);
if (!descriptor) return; // couldn't get the ID, so we're done if (!descriptor) return; // couldn't get the ID, so we're done
// NOTE: API call cached by the browser for the future version // NOTE: API call cached by resolveYT method automatically
// But cache busting is active for now const res = await resolveYT(descriptor);
// Manual memory cache will be removed later
const promise = ctxFromURLIdCache[descriptor.id] ?? (ctxFromURLIdCache[descriptor.id] = resolveYT(descriptor))
const res = await promise;
if (!res) return; // couldn't find it on lbry, so we're done if (!res) return; // couldn't find it on lbry, so we're done
const { redirect, targetPlatform } = await getExtensionSettingsAsync('redirect', 'targetPlatform'); const { redirect, targetPlatform } = await getExtensionSettingsAsync('redirect', 'targetPlatform');