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

NameTypeRequiredDefaultDescription
imgKeystring✓—AES decryption key. Reuses the SwImage prop name, aligned with useSystemStore().info.img_key. Not used on the plain path, but still required
urlstring''Text URL. URLs containing .bnc go through decryption; otherwise read as plain UTF-8 text

Events

NamePayloadDescription
update:respresp: stringFires when text loading completes (including cache hits). Triggers once on the null → string transition; switching url and finishing again triggers it again

Slots

NameSlot propsDescription
default{ text: string | null }Scoped slot. text === null means loading (or during SSR); string means ready

How It Works

  1. SSR short-circuit: when import.meta.client is false, returns null immediately — no network request, no decryption
  2. Cache lookup: fileId = md5(url.pathname); queries IndexedDB text table first; on hit, returns TextDecoder().decode(bytes)
  3. Remote load + conditional decryption: on miss, fetch(url) returns an ArrayBuffer; if URL contains .bnc → aes.decrypt(buf, imgKey), otherwise new Uint8Array(buf)
  4. Cache write + notify: decrypted bytes are written to IndexedDB (instant on next visit); result flips from null → string and emits update:resp
  5. Reactive reload: watchEffect watches props.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 imgKey via useRuntimeConfig().public.cacheAesKey — never hard-code keys
  • In the scoped slot, text === null is "loading"; text === '' is "loaded but empty string" — handle both
  • Client-only — during SSR the slot always sees text === null. Render a skeleton placeholder
  • update:resp also fires on cache hits (consistent for callers — any "loaded" event is delivered). Deduplicate when reporting first-screen PV