Femte trin mod iOS-appen. Fladen fra trin 3 pakket ind i en rigtig app. - `app/`: Capacitor 8 med SPM (ingen CocoaPods). `npm run sync` bygger web med VERDANDE_TARGET=app og kopierer det ind i Xcode-projektet; `npm run open` åbner det på en Mac. Bundle-id `dk.nolimit.verdande`. - `app/ios/` er committet. Webbygget og den genererede konfiguration i det er ikke — sync lægger dem der. - `NSAllowsLocalNetworking`: en hjemmeserver uden certifikat kan nås med http på det lokale net; alt på internettet skal stadig være https. - Ikon og opstartsbillede tegnes fra web/static/icon.svg (scripts/generate-ios-assets.js). Kvadratisk og uden gennemsigtighed, fordi iOS runder hjørnerne selv. - CI-jobbet "iOS app shell" bygger og synker på Linux og tjekker, at app-bygget ikke registrerer en service worker. Ingen Xcode i CI; det dækker alt op til den. - docs/ios.md: hvordan appen adskiller sig fra en browser, hvordan den bygges, hvad der er ejerens skridt (Apple-konto, signering, TestFlight), og hvad der mangler — notifikationer via APNs venter på en beslutning om en relay. Co-Authored-By: Claude Opus 5.5 <[email protected]>
3.6 KiB
The iOS app
The iOS app is the web interface bundled into a native shell with
Capacitor. It is not a second interface: the same
Svelte build runs in both, and everything that differs between a browser and the
app lives in web/src/lib/platform.js.
It talks to any Verdande server. The first screen asks for the address, checks
it with /api/v1/ping, and then signs in there.
How the app differs from a browser
| Browser | App | |
|---|---|---|
| Where the interface comes from | the server | inside the app, capacitor://localhost |
| Which server | the one it was loaded from | the one its owner typed in |
| Session | httpOnly cookie | a session token, sent as Authorization: Bearer |
| Live sync | WebSocket with the cookie | WebSocket opened with a one-use ticket (POST /ws-ticket) |
| Pictures and downloads | plain <img> and links |
fetched with the token and shown from a blob (nativemedia.js) |
| Service worker, Web Push | yes | no — WKWebView has neither |
| Passkeys | yes | not yet — a passkey is bound to the server's domain |
| Creating the first account | yes | no — done in a browser, then sign in from the app |
The server side of this is client: "app" on /auth/login, the
capacitor://localhost CORS rule, and the ticket. All three are in
docs/openapi.yaml.
An app session slides: every use moves its end forward, up to
VERDANDE_APP_SESSION_TTL (180 days) unused. It is listed under Enheder in
settings with the name the app gave itself, and can be signed out from there.
Building
Everything up to Xcode runs anywhere, and runs in CI (iOS app shell):
cd app
npm ci
npm run sync # builds web with VERDANDE_TARGET=app, copies it into ios/
On a Mac with Xcode:
npm run open # opens ios/App/App.xcodeproj
Choose a simulator and press Run. In the app, type the address of a Verdande —
http://localhost:8080 reaches a dev server on the same Mac from the simulator.
Plain http is allowed to local addresses only (NSAllowsLocalNetworking in
Info.plist), so a home server without a certificate works on the same network;
anything on the internet has to be https.
app/ios/ is committed. The web build inside it (App/App/public) and the
generated config are not — npm run sync puts them there.
The icon and launch image come from web/static/icon.svg:
cd web && node scripts/generate-ios-assets.js
Putting it on a phone
These steps are the account owner's — they involve Apple's terms, a payment and signing keys.
- Apple Developer Program membership (99 USD a year) on the Apple ID that should own the app.
- In Xcode → Signing & Capabilities: choose that team. The bundle id is
dk.nolimit.verdande; it can change until the first upload and never after. - Product → Archive, then Distribute App → TestFlight.
- In App Store Connect, add testers to the TestFlight build.
Not yet
- Notifications. Web Push does not exist in the app. Notifications on an iPhone go through Apple's APNs, and the key that sends them is bound to the app's bundle id — so it cannot live on every self-hosted server. That means a small relay run by the app's publisher, which every instance with an app user would talk to. That is a new connection from other people's servers, and it waits on a decision before it is built.
- The session token in the Keychain. It is in the WebView's own storage for
now, behind one object in
platform.jsso the Keychain plugin can replace it without anything else changing. - Passkeys in the app.
- Share sheet (share text or a link into Verdande from another app).