SwAsyncText
Asynchronously loads remote text. URLs with a .bnc suffix go through AES decryption; everything else is read as plain UTF-8 text. Callers just pass url — encryption routing happens inside the component. fileId = md5(url.pathname) is persisted in IndexedDB (text table); cache hits return immediately. The component watches props.url and reloads automatically — a natural fit for chapter switching.
Basic Usage
Use the scoped slot to render the load result. text === null means loading; a string means ready (including cache hits).
Basic (plain text)
<template>
<SwAsyncText :url="textUrl" :img-key="aesKey">
<template #default="{ text }">
<div v-if="text === null" class="loading">Loading…</div>
<article v-else>{{ text }}</article>
</template>
</SwAsyncText>
</template>
<script setup lang="ts">
const textUrl = '/articles/welcome.txt'
const aesKey = useRuntimeConfig().public.cacheAesKey
</script>
Encrypted Content (.bnc Routing)
Below is a really encrypted demo asset (chapter-secret.bnc, encrypted with the key matching the demo imgKey): the ciphertext is fetched asynchronously → decrypted client-side with AES → plaintext rendered.
Encrypted content: async decrypt & display
URLs containing the .bnc suffix are automatically routed through AES decryption. imgKey reuses the SwImage prop name and aligns with useSystemStore().info.img_key; on the plain path imgKey is still required (the prop is mandatory) but is not used internally.
<template>
<SwAsyncText :url="chapter.url" :img-key="aesKey">
<template #default="{ text }">
<div v-if="text === null" class="skeleton" />
<article v-else v-html="text" />
</template>
</SwAsyncText>
</template>
<script setup lang="ts">
// e.g. chapter.url = https://cdn.example.com/novel/chap-001.bnc
const aesKey = useRuntimeConfig().public.cacheAesKey
</script>
Auto-Reload on URL Change
When url changes the component re-fetches and re-decrypts automatically — no manual state reset needed. Ideal for paginated readers.
Chapter switching
Listening to Load Events
@update:resp fires after the text is fully loaded (including cache hits) — useful for word counts, reading-progress reports, and other follow-up actions.
Event listener + char count
Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
imgKey | string | ✓ | — | AES decryption key. Reuses the SwImage prop name, aligned with useSystemStore().info.img_key. Not used on the plain path, but still required |
url | string | '' | Text URL. URLs containing .bnc go through decryption; otherwise read as plain UTF-8 text |
Events
| Name | Payload | Description |
|---|---|---|
update:resp | resp: string | Fires when text loading completes (including cache hits). Triggers once on the null → string transition; switching url and finishing again triggers it again |
Slots
| Name | Slot props | Description |
|---|---|---|
default | { text: string | null } | Scoped slot. text === null means loading (or during SSR); string means ready |
How It Works
- SSR short-circuit: when
import.meta.clientisfalse, returnsnullimmediately — no network request, no decryption - Cache lookup:
fileId = md5(url.pathname); queries IndexedDBtexttable first; on hit, returnsTextDecoder().decode(bytes) - Remote load + conditional decryption: on miss,
fetch(url)returns anArrayBuffer; if URL contains.bnc→aes.decrypt(buf, imgKey), otherwisenew Uint8Array(buf) - Cache write + notify: decrypted bytes are written to IndexedDB (instant on next visit);
resultflips fromnull→stringand emitsupdate:resp - Reactive reload:
watchEffectwatchesprops.url; on change, state is cleared and the pipeline re-runs
The shared logic lives in the useDecryptedAsset composable — SwAsyncText and SwImage share the same fetch + conditional decryption + cache + state-machine implementation.
Caveats
- Pass
imgKeyviauseRuntimeConfig().public.cacheAesKey— never hard-code keys - In the scoped slot,
text === nullis "loading";text === ''is "loaded but empty string" — handle both - Client-only — during SSR the slot always sees
text === null. Render a skeleton placeholder update:respalso fires on cache hits (consistent for callers — any "loaded" event is delivered). Deduplicate when reporting first-screen PV