Parent epic: fluent-web#387
Repos affected: fluent-api, fluent-web
Scope: YouVersion only. Aquifer is tracked separately as the sibling sub-ticket.
Problem
VITE_YOUVERSION_API_KEY is inlined into the client JS bundle by Vite at build time (.github/workflows/deploy.yml, env block on the Deploy to Azure step) and read in the browser at src/lib/config.ts → getYouVersionApiHeaders(). All three YouVersion requests in src/features/resources/hooks/useYouVersion.ts send it from the visitor's browser, so anyone can extract it from the deployed bundle. See the epic for full background.
Unlike Aquifer, this is greenfield
fluent-api has no YouVersion presence at all — no client, no env var, no routes. The whole server side has to be built. The Aquifer integration is the pattern to copy:
src/lib/services/aquifer/aquifer.client.ts — client, timeouts, response validation
src/lib/services/aquifer/aquifer.errors.ts — upstream failure → 502 mapping
src/env.ts — optional server-held key; routes degrade to a service-unavailable response when unset rather than blocking boot
Endpoints to cover
Everything src/features/resources/hooks/useYouVersion.ts calls today:
| fluent-web call |
Purpose |
/bibles?language_tag=…&language_ranges[]=… |
bible picker options for a language |
/bibles/{bibleId}/books/{bookId}/chapters/{chapterId} |
chapter metadata → verse list |
| passage fetch (fanned out per verse) |
verse text for the reference column |
Note the per-verse fan-out in src/features/resources/hooks/hooks.ts — a chapter produces one request per verse. Proxying puts all of that on a single server-held key, so caching and/or a chapter-level batch route should be considered rather than a naive 1:1 passthrough.
Work
fluent-api
fluent-web
Decommission
Check before building
- Terms of use. Confirm YouVersion's API terms permit server-side proxying and any response caching we add, and that attribution/copyright fields returned by the API (
copyright, publisher_url, promotional_content, youversion_deep_link) keep being surfaced as required.
- Key scope. Document the current scope/permissions on the key. If YouVersion offers a tightly-scoped publishable key, that is worth knowing — but it is a mitigation, not a substitute for this work, since a client-side key stays extractable either way.
Landing constraint
The fluent-api and fluent-web changes must land together: fluent-web won't boot without the key until config.ts stops requiring it, and it can't stop requiring it until the API routes exist. Ship the API routes first (additive, harmless on their own), then the web migration, then the workflow/secret removal and rotation.
This ticket is independent of the Aquifer sub-ticket — the two services can land in either order. Note that the rotation/decommission benefit is only fully realized once both keys are out of the bundle.
Parent epic: fluent-web#387
Repos affected: fluent-api, fluent-web
Scope: YouVersion only. Aquifer is tracked separately as the sibling sub-ticket.
Problem
VITE_YOUVERSION_API_KEYis inlined into the client JS bundle by Vite at build time (.github/workflows/deploy.yml, env block on theDeploy to Azurestep) and read in the browser atsrc/lib/config.ts→getYouVersionApiHeaders(). All three YouVersion requests insrc/features/resources/hooks/useYouVersion.tssend it from the visitor's browser, so anyone can extract it from the deployed bundle. See the epic for full background.Unlike Aquifer, this is greenfield
fluent-api has no YouVersion presence at all — no client, no env var, no routes. The whole server side has to be built. The Aquifer integration is the pattern to copy:
src/lib/services/aquifer/aquifer.client.ts— client, timeouts, response validationsrc/lib/services/aquifer/aquifer.errors.ts— upstream failure → 502 mappingsrc/env.ts— optional server-held key; routes degrade to a service-unavailable response when unset rather than blocking bootEndpoints to cover
Everything
src/features/resources/hooks/useYouVersion.tscalls today:/bibles?language_tag=…&language_ranges[]=…/bibles/{bibleId}/books/{bookId}/chapters/{chapterId}Note the per-verse fan-out in
src/features/resources/hooks/hooks.ts— a chapter produces one request per verse. Proxying puts all of that on a single server-held key, so caching and/or a chapter-level batch route should be considered rather than a naive 1:1 passthrough.Work
fluent-api
YOUVERSION_API_URL/YOUVERSION_API_KEYtosrc/env.ts(optional key, same degrade-don't-crash treatment asAQUIFER_API_KEY)src/lib/services/youversion/client + error mapping, modelled on the Aquifer servicefluent-web
src/features/resources/hooks/useYouVersion.tsat fluent-apiYOUVERSION_API_KEY/youversion_keyandgetYouVersionApiHeaders()fromsrc/lib/config.ts(including theenvSchemaentry that currently makes it required to boot)VITE_YOUVERSION_API_KEY(andVITE_YOUVERSION_API_URL, once unused) from.env.exampleDecommission
VITE_YOUVERSION_API_KEYfrom the env block in.github/workflows/deploy.ymlVITE_YOUVERSION_API_KEYGitHub secretCheck before building
copyright,publisher_url,promotional_content,youversion_deep_link) keep being surfaced as required.Landing constraint
The fluent-api and fluent-web changes must land together: fluent-web won't boot without the key until
config.tsstops requiring it, and it can't stop requiring it until the API routes exist. Ship the API routes first (additive, harmless on their own), then the web migration, then the workflow/secret removal and rotation.This ticket is independent of the Aquifer sub-ticket — the two services can land in either order. Note that the rotation/decommission benefit is only fully realized once both keys are out of the bundle.