diff --git a/package-lock.json b/package-lock.json index 3315468..685afd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,10 @@ "name": "revanced-website", "version": "0.0.1", "dependencies": { + "@tanstack/query-core": "^4.24.4", + "@tanstack/query-persist-client-core": "^4.24.4", + "@tanstack/query-sync-storage-persister": "^4.24.4", + "@tanstack/svelte-query": "^4.24.4", "asciidoctor": "^2.2.6", "marked": "^4.1.1" }, @@ -624,6 +628,54 @@ "vite": "^4.0.0" } }, + "node_modules/@tanstack/query-core": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.24.4.tgz", + "integrity": "sha512-9dqjv9eeB6VHN7lD3cLo16ZAjfjCsdXetSAD5+VyKqLUvcKTL0CklGQRJu+bWzdrS69R6Ea4UZo8obHYZnG6aA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-persist-client-core": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-4.24.4.tgz", + "integrity": "sha512-t4BR/th3tu2tkfF0Tcl5z+MbglDjTdIbsLZYlly7l2M6EhGXRjOLbvVUA5Kcx7E2a81Vup0zx0z0wlx+RPGfmg==", + "dependencies": { + "@tanstack/query-core": "4.24.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-sync-storage-persister": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-4.24.4.tgz", + "integrity": "sha512-0wffVqoOydMc1TDjOiATv/TM8wJfMpRcM82Cr19TuepListopTsuZ3RzSzLKBCo8WRl/0zCR1Ti9t1zn+Oai/A==", + "dependencies": { + "@tanstack/query-persist-client-core": "4.24.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/svelte-query": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@tanstack/svelte-query/-/svelte-query-4.24.4.tgz", + "integrity": "sha512-B2N+nNPOe9wxZ01i3Ebvkc8vDX9ZDQTiaazXqpld7O27R+zhIBlk6MU8vNzPBDSKrhhl2kak8SwyJOExsVZQXA==", + "dependencies": { + "@tanstack/query-core": "4.24.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "svelte": "^3.54.0" + } + }, "node_modules/@types/cookie": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.1.tgz", @@ -3200,7 +3252,6 @@ "version": "3.55.0", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.0.tgz", "integrity": "sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA==", - "dev": true, "engines": { "node": ">= 8" } @@ -3990,6 +4041,35 @@ "vitefu": "^0.2.3" } }, + "@tanstack/query-core": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.24.4.tgz", + "integrity": "sha512-9dqjv9eeB6VHN7lD3cLo16ZAjfjCsdXetSAD5+VyKqLUvcKTL0CklGQRJu+bWzdrS69R6Ea4UZo8obHYZnG6aA==" + }, + "@tanstack/query-persist-client-core": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-4.24.4.tgz", + "integrity": "sha512-t4BR/th3tu2tkfF0Tcl5z+MbglDjTdIbsLZYlly7l2M6EhGXRjOLbvVUA5Kcx7E2a81Vup0zx0z0wlx+RPGfmg==", + "requires": { + "@tanstack/query-core": "4.24.4" + } + }, + "@tanstack/query-sync-storage-persister": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-4.24.4.tgz", + "integrity": "sha512-0wffVqoOydMc1TDjOiATv/TM8wJfMpRcM82Cr19TuepListopTsuZ3RzSzLKBCo8WRl/0zCR1Ti9t1zn+Oai/A==", + "requires": { + "@tanstack/query-persist-client-core": "4.24.4" + } + }, + "@tanstack/svelte-query": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@tanstack/svelte-query/-/svelte-query-4.24.4.tgz", + "integrity": "sha512-B2N+nNPOe9wxZ01i3Ebvkc8vDX9ZDQTiaazXqpld7O27R+zhIBlk6MU8vNzPBDSKrhhl2kak8SwyJOExsVZQXA==", + "requires": { + "@tanstack/query-core": "4.24.4" + } + }, "@types/cookie": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.1.tgz", @@ -5799,8 +5879,7 @@ "svelte": { "version": "3.55.0", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.0.tgz", - "integrity": "sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA==", - "dev": true + "integrity": "sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA==" }, "svelte-check": { "version": "2.10.3", diff --git a/package.json b/package.json index 16a032d..34679de 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,10 @@ }, "type": "module", "dependencies": { + "@tanstack/query-core": "^4.24.4", + "@tanstack/query-persist-client-core": "^4.24.4", + "@tanstack/query-sync-storage-persister": "^4.24.4", + "@tanstack/svelte-query": "^4.24.4", "asciidoctor": "^2.2.6", "marked": "^4.1.1" } diff --git a/src/data/api/cache.ts b/src/data/api/cache.ts deleted file mode 100644 index 269e7ac..0000000 --- a/src/data/api/cache.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { browser } from '$app/environment'; - -import { dev_log } from '$lib/utils'; - -const CACHE_KEY_PREFIX = 'revanced_api_cache_l1'; -const L1_CACHE_VALIDITY = 5 * 60 * 1000; // 5 minutes - -function l1_key_name(endpoint: string) { - return `${CACHE_KEY_PREFIX}:${endpoint}`; -} - -// Get item from the cache -export function get(endpoint: string) { - if (!browser) { - return null; - } - - const key_name = l1_key_name(endpoint); - const ls_data: { valid_until: number; data: any } | null = JSON.parse( - localStorage.getItem(key_name) as string - ); - - if (ls_data === null || ls_data.valid_until <= Date.now()) { - dev_log('Cache', `missed "${endpoint}"`); - localStorage.removeItem(key_name); - return null; - } - - dev_log('Cache', `hit "${endpoint}"`); - return ls_data.data; -} - -// Update the cache -export function update(endpoint: string, data: any) { - if (!browser) { - return; - } - - localStorage.setItem( - l1_key_name(endpoint), - JSON.stringify({ - data, - valid_until: Date.now() + L1_CACHE_VALIDITY - }) - ); -} - -// Clear the cache and reload -export function clear_and_reload() { - for (const key of Object.keys(localStorage)) { - if (key.startsWith(CACHE_KEY_PREFIX)) { - localStorage.removeItem(key); - } - } - location.reload(); -} diff --git a/src/data/api/index.ts b/src/data/api/index.ts index 1ac517f..5614536 100644 --- a/src/data/api/index.ts +++ b/src/data/api/index.ts @@ -1,130 +1,24 @@ -import type { Readable, Subscriber, Unsubscriber, Writable } from 'svelte/store'; -import { writable } from 'svelte/store'; -import { error } from '@sveltejs/kit'; - -import { building, browser, dev } from '$app/environment'; - import * as settings from './settings'; -import * as cache from './cache'; - -export class API implements Readable { - private store: Writable; - // True if we have or are about to request data from the API. - has_requested: boolean; - - // `transform` will transform the data received from the API. - constructor( - public readonly endpoint: string, - private readonly default_value: T, - private readonly transform: (v: any) => T = (v) => v as T - ) { - // Initialize with cached data if possible. - const cached_data = cache.get(this.endpoint); - this.has_requested = cached_data !== null; - - this.store = writable(cached_data || this.default_value); - } - - private url() { - return `${settings.api_base_url()}/${this.endpoint}`; - } - - // Please don't call this directly - private async _update(fetch_fn: typeof fetch) { - // Try to get data from the cache. - let data = cache.get(this.endpoint); - - if (data === null) { - // Fetch and transform data - const response = await fetch_fn(this.url()); - data = this.transform(await response.json()); - - // Update the cache. - cache.update(this.endpoint, data); - } - - this.store.set(data); - } - - // Retrieve data and update. - private update(fetch_fn = fetch) { - // Make sure we set this immediately outside of the async function to avoid JS event loop weirdness. - this.has_requested = true; - return this._update(fetch_fn); - } - - // Start retrieving data if needed. - retrieve_if_needed() { - if (!this.has_requested) { - return this.update(); - } - return Promise.resolve(); - } - - // Implements the load function found in `+page/layout.ts` files. - page_load_impl() { - return async ({ fetch }) => { - if (building) { - return {}; - } - - // Might be better to actually return some data from the load function and use that on the client. - if (!(dev || browser || building)) { - throw new Error( - 'The API client is not optimized for production server-side rendering. Please change that :)' - ); - } - - try { - await this.update(fetch); - return {}; - } catch (e) { - console.error(e); - throw error(504, 'API Request Error'); - } - }; - } - - // Implement Svelte store. - subscribe(run: Subscriber, invalidate?: any): Unsubscriber { - // Make sure we have up-to-date data from the API. - if (browser) { - this.retrieve_if_needed(); - } - - return this.store.subscribe(run, invalidate); - } -} // API Endpoints -import type { Patch, Repository, Tool } from '../../utils/types'; -import { dev_log } from '$lib/utils'; +import type { Patch, Repository, Tool } from '$lib/types'; export type ReposData = Repository[]; export type PatchesData = { patches: Patch[]; packages: string[] }; export type ToolsData = { [repo: string]: Tool }; -export const repositories = new API('contributors', [], (json) => json.repositories); +async function get_json(endpoint: string) { + const url = `${settings.api_base_url()}/${endpoint}`; + return await fetch(url).then((r) => r.json()); +} -// It needs to look this way to not break everything. -const tools_placeholder: ToolsData = { - 'revanced/revanced-manager': { - version: 'v0.0.0', - timestamp: '', - repository: '', - assets: [ - { - url: '', - name: '', - content_type: '', - size: null - } - ] - } -}; +async function repositories(): Promise { + return await get_json('contributors').then((json) => json.repositories); +} -export const tools = new API('tools', tools_placeholder, (json) => { - // The API returns data in a weird shape. Make it easier to work with. +async function tools(): Promise { + const json = await get_json('tools'); + // Make the data easier to work with. let map: Map = new Map(); for (const tool of json['tools']) { const repo: string = tool.repository; @@ -139,7 +33,7 @@ export const tools = new API('tools', tools_placeholder, (json) => { }); } - let value = map.get(repo); + let value = map.get(repo)!!; value.assets.push({ name: tool.name, size: tool.size, @@ -151,14 +45,19 @@ export const tools = new API('tools', tools_placeholder, (json) => { } return Object.fromEntries(map); -}); +} -export const patches = new API('patches', { patches: [], packages: [] }, (patches) => { +async function manager(): Promise { + return await tools().then((data) => data['revanced/revanced-manager']); +} + +async function patches(): Promise { + const json = await get_json('patches'); const packagesWithCount: { [key: string]: number } = {}; // gets packages and patch count - for (let i = 0; i < patches.length; i++) { - patches[i].compatiblePackages.forEach((pkg: Patch) => { + for (let i = 0; i < json.length; i++) { + json[i].compatiblePackages.forEach((pkg: Patch) => { packagesWithCount[pkg.name] = (packagesWithCount[pkg.name] || 0) + 1; }); } @@ -168,5 +67,24 @@ export const patches = new API('patches', { patches: [], packages: .sort((a, b) => b[1] - a[1]) .map((pkg) => pkg[0]); - return { patches, packages }; -}); + return { patches: json, packages }; +} + +export const staleTime = 5 * 60 * 1000; +export const queries = { + manager: { + queryKey: ['manager'], + queryFn: manager, + staleTime + }, + patches: { + queryKey: ['patches'], + queryFn: patches, + staleTime + }, + repositories: { + queryKey: ['repositories'], + queryFn: repositories, + staleTime + } +}; diff --git a/src/layout/Footer.svelte b/src/layout/Footer.svelte index b06734b..2399fad 100644 --- a/src/layout/Footer.svelte +++ b/src/layout/Footer.svelte @@ -1,6 +1,11 @@ ReVanced Logo

- ReVanced was born out of Vanced's discontinuation and it is our goal to continue the legacy - of what Vanced left behind. Thanks to ReVanced Patcher, it's possible to create long-lasting - patches for nearly any Android app. ReVanced's patching system is designed to allow patches - to work on new versions of the apps automatically with bare minimum maintenance. + ReVanced was born out of Vanced's discontinuation and it is our goal to continue the + legacy of what Vanced left behind. Thanks to ReVanced Patcher, it's possible to create + long-lasting patches for nearly any Android app. ReVanced's patching system is designed to + allow patches to work on new versions of the apps automatically with bare minimum + maintenance.

- + @@ -66,7 +80,6 @@ -