Files
ue4ss-explorer/app/lib/hosted.ts
Hunter 45b1539c68 V.1
2026-08-07 17:11:04 -04:00

71 lines
2.2 KiB
TypeScript

/**
* Dumps the site ships with.
*
* These are never fetched on load. A prebaked dump is tens of megabytes, and
* most visitors arrive with their own file or with one already in IndexedDB;
* spending their bandwidth on a speculative download would be rude. The
* manifest is a few hundred bytes and is all we read until someone asks for
* a dump by name.
*/
export interface HostedDump {
/** Stable id, independent of the file name. Recorded on the cached copy so
* a downloaded dump can be struck from the offer list. */
id: string
/** Path under the site root. */
file: string
title: string
note?: string
/** Bytes on the wire, for the "how big is this" question. Optional. */
bytes?: number
/** 'pdx' is a prebaked column bundle; 'text' is a raw UE4SS dump. */
format?: 'text' | 'pdx'
}
const MANIFEST = '/dumps.json'
export async function listHosted(): Promise<HostedDump[]> {
const res = await fetch(MANIFEST, { cache: 'no-cache' })
if (!res.ok) return []
const body = await res.json()
return Array.isArray(body) ? body : (body.dumps ?? [])
}
/**
* Fetch with byte progress. `Content-Length` is absent under chunked transfer
* encoding, which nginx will use for these once gzip is on, so the caller has
* to tolerate a null fraction rather than a bogus one.
*/
export async function download(
d: HostedDump,
onProgress?: (fraction: number | null, received: number) => void,
): Promise<ArrayBuffer> {
const res = await fetch(d.file)
if (!res.ok) throw new Error(`Could not fetch ${d.file} (${res.status})`)
const declared = Number(res.headers.get('content-length')) || d.bytes || 0
if (!res.body) return res.arrayBuffer()
const reader = res.body.getReader()
const chunks: Uint8Array[] = []
let received = 0
for (;;) {
const { done, value } = await reader.read()
if (done) break
chunks.push(value)
received += value.length
onProgress?.(declared ? Math.min(1, received / declared) : null, received)
}
const out = new Uint8Array(received)
let at = 0
for (const c of chunks) {
out.set(c, at)
at += c.length
}
return out.buffer
}
/** File name to record on the cached copy, so titles read like a real file. */
export const fileNameOf = (d: HostedDump) => d.file.split('/').pop() || d.id