storebound/skills/web-presence/SKILL.md

86 lines
7.1 KiB
Markdown

---
name: web-presence
description: Set up the web side of a mobile app or game - iOS universal links (apple-app-site-association, Associated Domains entitlement), Android App Links (Digital Asset Links assetlinks.json with the Play app signing SHA-256, intent filter autoVerify), the Safari Smart App Banner meta tag and a minimal landing page with App Store and Google Play links - then check the live files (HTTPS, no redirects, application/json, ids match). Use when the user wants links to open the app, sets up a domain or landing page for an app, sees universal links or app links not opening the app, gets "assetlinks" / "apple-app-site-association" errors, or asks for a Smart App Banner.
---
# Web presence: universal links, app links, Smart App Banner, landing page
CLI: `storebound` (or `bunx storebound` in a project that has it, or `bun <toolchain>/cli/index.ts`).
- `storebound web init [--out web] [--landing] [--version-code N] [--dry] [--json]`: writes the files into a local
folder (default `web/`). Local writes only; nothing is uploaded.
- `storebound web check [<domain>] [--json]`: read-only, no credentials. Fetches the live files from
`https://<domain>` without following redirects and lists every check as ok / problem / info with the fix. Exit code
1 when there is a problem.
Config (`storebound.config.ts`):
- `apple.teamId` (10 characters, developer.apple.com > Account > Membership details), `apple.bundleId`,
`apple.appId` (App Store Connect > App Information > Apple ID, for the banner and the landing link).
- `play.packageName`.
- `web.domain` (`links.example.com`, no scheme or path), `web.paths` (default `["/*"]`), `web.exclude` (paths that stay
in the browser), `web.androidSha256` (Play app signing SHA-256, `AB:CD:...`), `web.webcredentials` (password
AutoFill), `web.landing`, `web.out`.
- `app.name`, `app.privacyPolicyUrl`, `app.supportUrl`, `app.termsUrl` fill the landing page.
## Steps
1. Pick the domain with the owner. A subdomain only for links (`links.example.com`) is the simplest: then
`web.paths: ["/*"]` is fine. On a domain that also has a normal website, list only the app paths
(`["/invite/*", "/open/*"]`) so the rest of the site stays in the browser.
2. Fingerprint for Android. With Play App Signing the key on users' devices is Google's, not the local keystore:
- Play Console > Test and release > App integrity > Play app signing: copy "SHA-256 certificate fingerprint" of the
app signing key into `web.androidSha256`. Add the upload key too if you install locally signed builds and want
links to verify there (an extra fingerprint is fine).
- Or `storebound web init --version-code <a versionCode you uploaded>`: reads it read-only from the Play Developer
API (generatedapks.list, `certificateSha256Hash`) and prints it; then put it in `web.androidSha256`.
3. `storebound web init --dry`, show the output, then `storebound web init` (add `--landing` for `index.html`). Missing
inputs are listed as `skipped: <why, how to fix>`; unchanged files are left alone.
4. Owner step: deploy the folder contents to the domain root over HTTPS. Requirements the host must meet:
- `/.well-known/apple-app-site-association` (no extension) and `/.well-known/assetlinks.json`, status 200,
`Content-Type: application/json`, no redirect (no http -> https, apex -> www or trailing-slash redirect for these
paths), public (no login, no IP or bot blocking, any user agent), valid certificate.
- Many static hosts skip dot-folders or serve extension-less files as `application/octet-stream`: add a headers
rule for the AASA path, and make sure a single-page-app fallback does not answer these paths with `index.html`.
- The files go on every host the app handles (`example.com` and `www.example.com` are two hosts).
5. App side (the code; storebound does not edit it):
- iOS: Signing & Capabilities > Associated Domains: `applinks:<domain>` (and `webcredentials:<domain>` with
`web.webcredentials`). The App ID needs the Associated Domains capability. Handle the URL in
`onOpenURL` / `scene(_:continue:)`. While developing against a server that is not public, use
`applinks:<domain>?mode=developer` (developer mode on the device) to bypass Apple's CDN.
- Android: in the activity, an intent filter with `android:autoVerify="true"`, action `VIEW`, categories `DEFAULT`
and `BROWSABLE`, `<data android:scheme="https" android:host="<domain>" />` plus the same paths as `web.paths`
(`android:pathPattern` / `pathPrefix`). `web.exclude` is also written as dynamic rules in assetlinks.json
(Android 15+); older versions only use the intent filter, so keep excluded paths out of it.
6. `storebound web check` until there are no problems. Then test on devices: Apple's CDN fetches the AASA within about
a day after install and rechecks about weekly; Android verifies at install (`adb shell pm get-app-links
<package>`, re-verify with `adb shell pm verify-app-links --re-verify <package>`).
7. Smart App Banner: `smart-app-banner.html` holds the `<meta name="apple-itunes-app" content="app-id=...">` tag. Put
it in the `<head>` of each page that should show it (the landing page already has it). Add
`app-argument=<url>` by hand to pass a deep link. It shows in Safari on iPhone and iPad, not in the simulator or
inside frames, and not again after the user closes it.
## Landing page
`index.html` is a small, responsive page with no external requests: the app name, an App Store link
(`https://apps.apple.com/app/id<appId>`), a Google Play link (`https://play.google.com/store/apps/details?id=<package>`)
and privacy / terms / support links. The store marks are the simple-icons files (`appstore.svg`, `googleplay.svg`,
copied unchanged next to it) shown in the text colour, with the trademark note in the footer. They only name the
store; for Apple's and Google's official badges, download them from Apple's marketing resources and Google Play's
badge page and follow their guidelines. Edit the page freely after the first write; `web init` reports it as
`updated` when it would replace your edits, so run `--dry` first.
## Owner decisions
- The domain, which paths open the app, and whether a landing page replaces an existing site.
- Hosting and DNS changes: storebound writes files, the owner deploys them.
## Out of scope
- Deploying or hosting the files, DNS, certificates.
- Editing the Xcode entitlements or AndroidManifest.xml.
- Deferred deep links (install then open the right screen) and link attribution: that needs a link service.
- App Clips (`appclips` in the AASA): not generated.
## Gotchas
- A redirect on the AASA or assetlinks path breaks verification even when the browser shows the file fine.
- `text/plain` or `application/octet-stream` on assetlinks.json fails Android verification; Apple wants
`application/json` and an AASA of at most 128 KB.
- Lower-case or colon-less fingerprints: assetlinks.json uses upper-case `AB:CD:...`; `web init` writes that form.
- The local keystore fingerprint usually differs from the Play app signing one: links that work in debug builds can
fail in the Play build.
- iOS caches the AASA: after a fix, reinstall the app or wait for the CDN (developer mode skips it while testing).