An API client for ipx, and its event stream #1

Closed
opened 2026-09-19 16:56:07 -07:00 by rays · 1 comment
Owner

Everything native needs the same thing first: a typed client for ipx's HTTP API, and a reader for its event stream.

ipx already answers everything a client could want and needs no server change. The routes that matter here are GET /api/feeds, GET /api/entries and GET /api/feeds/{id}/entries (paged, with filter, q, sort and dir), POST /api/entries/{feed}/{guid}/flags and .../position, POST /api/read-all and /api/feeds/{id}/read-all, POST /api/enclosures/{id}/download and DELETE /api/enclosures/{id}, POST /api/fetch, and GET /media/{id}, which supports Range.

GET /api/events is server-sent events, the same broadcast the control socket carries: feed_start, feed_done, progress, download_done, reaped, scan_done and the rest. URLSession's bytes(for:) reads it without another dependency. Progress carries the enclosure id, without which a UI cannot tell one download from another.

Authentication is the web view's: ipx decides who is asking by cookie, and CookieBridge already keeps HTTPCookieStorage in step. The client sends nothing of its own.

This is worth doing whatever happens to the rest. A CarPlay browse tree is built from exactly these calls, so none of it is wasted even if the interface stays a web view.

Sources/Library.swift is the seed: it already does position, read and artwork.

Everything native needs the same thing first: a typed client for ipx's HTTP API, and a reader for its event stream. ipx already answers everything a client could want and needs no server change. The routes that matter here are GET /api/feeds, GET /api/entries and GET /api/feeds/{id}/entries (paged, with filter, q, sort and dir), POST /api/entries/{feed}/{guid}/flags and .../position, POST /api/read-all and /api/feeds/{id}/read-all, POST /api/enclosures/{id}/download and DELETE /api/enclosures/{id}, POST /api/fetch, and GET /media/{id}, which supports Range. GET /api/events is server-sent events, the same broadcast the control socket carries: feed_start, feed_done, progress, download_done, reaped, scan_done and the rest. URLSession's bytes(for:) reads it without another dependency. Progress carries the enclosure id, without which a UI cannot tell one download from another. Authentication is the web view's: ipx decides who is asking by cookie, and CookieBridge already keeps HTTPCookieStorage in step. The client sends nothing of its own. This is worth doing whatever happens to the rest. A CarPlay browse tree is built from exactly these calls, so none of it is wasted even if the interface stays a web view. Sources/Library.swift is the seed: it already does position, read and artwork.
rays added the enhancement label 2026-09-19 16:56:07 -07:00
rays added a new dependency 2026-09-19 16:58:52 -07:00
rays added a new dependency 2026-09-19 16:58:57 -07:00
Author
Owner

Done in f88ae65.

Sources/API.swift is the client: feeds, entries with the server's own paging, filtering, sorting and search, read and pinned, position, read-all, download, delete, fetch, and artwork. Sources/APIModels.swift is what ipx sends, with the server's field names kept so a type here can be checked against web.rs without translating first. It sends nothing of its own to authenticate — ipx decides who is asking by cookie and CookieBridge keeps HTTPCookieStorage in step.

Sources/Events.swift reads /api/events with URLSession's byte stream, so no new dependency. An event this build has never heard of decodes as .other rather than throwing: a new one on the server must not stop the stream. It reconnects with a backoff, since a deploy closes the stream cleanly and a daemon that is down should not be hammered.

Library.swift is gone, folded in. Two clients writing positions was one too many, and the page's forwarded beacon no longer carries a path — it asks the host to save and the host uses its own clock, the only one that is not stale when the web view has been frozen.

Thirteen tests pass against the browser suite's fixture daemon rather than canned JSON, because the shapes are the server's and a fixture would only prove it matches itself. That includes a live scan reported over SSE, and the playback round trip, which still works after the move.

TEST_RUNNER_IPX_SERVER=http://127.0.0.1:8791 TEST_RUNNER_IPX_TOKEN=<fixture token> xcodebuild test ...
Done in f88ae65. `Sources/API.swift` is the client: feeds, entries with the server's own paging, filtering, sorting and search, read and pinned, position, read-all, download, delete, fetch, and artwork. `Sources/APIModels.swift` is what ipx sends, with the server's field names kept so a type here can be checked against `web.rs` without translating first. It sends nothing of its own to authenticate — ipx decides who is asking by cookie and `CookieBridge` keeps `HTTPCookieStorage` in step. `Sources/Events.swift` reads `/api/events` with URLSession's byte stream, so no new dependency. An event this build has never heard of decodes as `.other` rather than throwing: a new one on the server must not stop the stream. It reconnects with a backoff, since a deploy closes the stream cleanly and a daemon that is down should not be hammered. `Library.swift` is gone, folded in. Two clients writing positions was one too many, and the page's forwarded beacon no longer carries a path — it asks the host to save and the host uses its own clock, the only one that is not stale when the web view has been frozen. Thirteen tests pass against the browser suite's fixture daemon rather than canned JSON, because the shapes are the server's and a fixture would only prove it matches itself. That includes a live scan reported over SSE, and the playback round trip, which still works after the move. TEST_RUNNER_IPX_SERVER=http://127.0.0.1:8791 TEST_RUNNER_IPX_TOKEN=<fixture token> xcodebuild test ...
rays closed this issue 2026-09-19 19:48:28 -07:00
Sign in to join this conversation.