iOS: the app's settings in Settings, not behind a shake; iPhone upright (#445)

The server address and the player token were set from alerts that
opened on shaking the phone, which nobody finds. Settings now has a This
device section in the app: the server, the player token and its
address, and a Make button that creates an API token and hands it
straight to the app's keychain. The app sends what it has at ready
(appSettings) and takes setServer, setToken and clearToken; a new
server rebuilds the web view as the alert did. The first-launch prompt
and the Cannot reach alert stay native, since there is no page then.

The iPhone is portrait only; the iPad keeps all four orientations, which
Split View and Slide Over need (#434 checked the iPad's layout).

Build 14.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ray Slakinski
2026-11-13 08:39:54 -05:00
parent 69f1a38e9f
commit 171c77475a
10 changed files with 142 additions and 61 deletions

View File

@@ -25,7 +25,8 @@ CarPlay does not: Apple grants `com.apple.developer.carplay-audio` on request (g
## Which server
The first launch asks, with `https://ipodderx.sdf1.net` filled in; a bare host gets `https://`.
Shake the phone to change it. Plain `http://` only on your own network
Change it in the page's Settings, under This device, or from the alert that comes up when the
server cannot be reached. Plain `http://` only on your own network
(`http://192.168.1.130:8099`), which is all `NSAllowsLocalNetworking` allows. Sign in on the page
that follows, through Cloudflare Access or ipx's own form; the cookies that leaves are what the
player uses (`CookieBridge`). `-ipx.server <url>` as a launch argument overrides it for a run. `-ipx.noBadgePrompt YES` skips the badge permission dialog, which covers the page in a simulator run nobody is there to tap.
@@ -51,8 +52,8 @@ A debug build adds `localhost`, so a simulator can test the worker against a pag
## Player token
The player normally rides on the web view's cookie, which ends with Cloudflare Access's session.
To make it outlast that, shake the phone, choose Player Token and enter an ipx API token (the
page's Settings, API tokens) and the address to send it to, `https://ipxapi.sdf1.net`. The token is
To make it outlast that, open Settings in the app and, under This device, press Make, or paste an
ipx API token (Settings, API tokens), with the address to send it to, `https://ipxapi.sdf1.net`. The token is
kept in the keychain (`Credentials.swift`). The player's audio, positions, read flags, artwork and
the unread badge go to that address with `Authorization: Bearer`; the page still signs in as it
did, at the server address. Without a token the player sends only the cookie, as before.
@@ -117,6 +118,8 @@ Page to app, on `webkit.messageHandlers.ipx`:
| `{t:"sleep", secs}` | pause after `secs`, fading out over the last ten; 0 turns the timer off (#429) |
| `{t:"unread", n}` | All Subscriptions' unread count, whenever the page's list is drawn with a new one: the icon's badge (#421) |
| `{t:"haptic", kind}` | a press felt: `tap`, `done` (marked read) or `warn` (an error) (#430) |
| `{t:"setServer", url}` | Settings' This device: open another server, which replaces the page (#445) |
| `{t:"setToken", api, token?}` `{t:"clearToken"}` | Settings' This device: the player address, and a token to keep in the keychain or none (#445) |
| `{t:"route", x, y, w, h}` | open the AirPlay picker, pointing at the page's button, in the web view's points (#429) |
App to page, as `window.ipxNative.on(...)`:
@@ -130,7 +133,9 @@ App to page, as `window.ipxNative.on(...)`:
| `{t:"error", message}` | the page says so |
| `{t:"kept", urls, keeping}` | what the phone holds and is fetching, at `ready` and after each change; sent whether or not anything plays |
| `{t:"keepFailed", url, message}` | one that could not be kept, and why |
| `{t:"host", can}` | at `ready`: what this build does itself (`sleep`, `route`), so a page newer than the app offers only those |
| `{t:"host", can}` | at `ready`: what this build does itself (`sleep`, `route`, `settings`), so a page newer than the app offers only those |
| `{t:"appSettings", server, api, tokenSet, saved}` | at `ready` and after a change: the app's server, player address and whether a player token is set, for Settings' This device (#445) |
| `{t:"appSettingsError", message}` | a server or player address the app would not take |
| `{t:"push", token, sandbox}` | at `ready` and when Apple gives a new one: the phone's push token, for the page to register with ipx (`POST /api/push/devices`) (#433) |
| `{t:"sleep", left}` | at `ready`, while the timer runs: seconds left, for a page that reloaded |
| `{t:"slept"}` | the timer ran out and playback paused |