Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs-developers/architecture/cross-repo-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,15 @@ home only after a tokenless `GET <home>/api/v1/server` answered with this connec
its `server_id` matched.** The web player never switches (it is served same-origin).
Details: [Connect and home/away addresses](../frontend/connect-and-addresses.md#home-and-away-addresses).

**Listen in player.** The admin console's book page mints a token for its own user
(`POST /auth/pair`) and opens the same-origin
`/web/connect?token=…&sid=…&uid=…&book=<library_id>&path=<rel_path>` in a new tab; the
player skips the exchange when it is already signed in as `uid` on `sid`, and ends the flow
on that book (rebuilt with `bookHref`, never a URL from the link). Details:
[Connect and onboarding](../frontend/connect-and-addresses.md#a-link-that-names-a-book).

**A change requires:** the scheme, the query keys (`server`, `token`, and the optional
`home`, `away`), and the `/web/connect` route path are a contract - change the server
`home`, `away`, `sid`, and `uid`, `book`, `path`), and the `/web/connect` route path are a contract - change the server
emitter, the parser, and `app.json` together, or pairing breaks.

## 8. Capability flags: `GET /api/v1/server`
Expand Down
33 changes: 32 additions & 1 deletion docs-developers/frontend/connect-and-addresses.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,45 @@ the server's own name when the flow knows it (the probe, the redeem payload's
addresses the link (`linkAddresses`) and the answer taught, merged there once
(`mergeAddresses`). The device's **first**
connection then shows `/connect/ready`; an added server goes straight back with
`leaveOnboarding()`; and whenever the person listening is in kids mode afterwards (a
`leaveOnboarding()`; a link that names a book ends on its page instead
([below](#a-link-that-names-a-book)); and whenever the person listening is in kids mode afterwards (a
kid's account signing in, below) it goes straight to `/kids` instead, with no "Your
library is ready.". A link's exchange that fails is said in the app's words, never the
server's (`apiErrorKind`): 429 "Too many tries. Wait a few minutes and try again.", a
refused link (expired, used up, unknown, malformed) "This pairing link has expired or
was already used...", a 5xx "The server couldn't finish pairing...", and no answer
"Could not reach the server to finish pairing.".

### A link that names a book

A connect link can name a book to end on: `book=<library_id>&path=<rel_path>`, with
`uid=<user id>`, the user its token was minted for, beside `token` and `sid`. The admin
console's **Listen in player** builds one (`/web/connect?token=…&sid=…&uid=…&book=…&path=…`,
same origin); nothing else does yet. `parseConnectBook` (`src/lib/paths.ts`) reads it: `book`
must be a positive integer and `path` non-empty, else the link names no book and the flow
ends as it always does. It travels on the `PairingScan` as `book` and `userId`, filled the
same way from the route's params and from a pasted link (`parsePairingScan`), so a pasted
link behaves as the opened one. The destination is rebuilt with `bookHref`, never followed
as a URL from the link, so there is no open redirect. Then:

- **Already signed in as that user there** (`signedInAs` in `connect-model.ts`: a
connection on the `sid` server whose `user.id` is `uid`, its token not refused, no
`needsReconnect`): no exchange. `useLinkPairing.pair` checks this first (reading the
connections again when the session read failed at launch, as `finishConnect` does),
then asks `GET /me` with the stored token at the connection's own address (never the
link's), and ends on the book; the unused token expires by itself, and no second
session (or "new device" notice) is made at each press. A 401 there (the token was
revoked) falls through to the exchange; a server that doesn't answer still opens the
book. "Add someone" never skips, so the exchange switches to that person.
- **Otherwise** the token is exchanged as for any link, and `finishConnect`'s `book` ends
the flow on the book's page (`router.dismissTo(bookHref(…))`) instead of
`/connect/ready` or Home, first connection or not.

Both end through `endConnect` (`finish-connect.ts`), the flow's one ending, so kids mode
still wins (`/kids`) either way.

Native is unchanged: no link it receives carries `book`.

**Kids mode never runs the flow**: `src/app/connect/_layout.tsx` is the one gate (see
[Kids mode](kids-mode.md#the-route-and-its-guard)). A kid's account placed for the
first time on this device starts in kids mode (`kidsOnForKidAccount`: see
Expand Down
3 changes: 2 additions & 1 deletion docs-developers/server/api/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,8 @@ on a server with that capability - the same shape as `/auth/exchange`.
device" from an existing session. No request body. Response `200`: a
`PairingPayload` (same shape as `/auth/redeem`, without the invite budget fields). Its
token is single-use and lasts 10 minutes (`pairingTTL`), so the player counts down from
when the payload arrived. A kid's account (a [household member](#household-members)
when the payload arrived. The admin console's **Listen in player** uses it too, to sign the
web player in as the admin in a new tab. An API key gets `403`. A kid's account (a [household member](#household-members)
with `member_kind: "kid"`) gets `403` `code: "kid_account"`: its owner sets up its
devices with [`POST /me/household/members/{id}/pair`](#post-apiv1mehouseholdmembersidpair).

Expand Down
11 changes: 10 additions & 1 deletion docs-developers/server/web-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,16 @@ CSP-sensitive work against a real build served by Go. The console's own gate is
leaves the dialog open),
listeners, shares
and the "Files on disk" section with the disabled on-disk rename. The more
menu's "Read the files again" calls `POST …/book/rescan`. `?match=1` opens the
menu's "Read the files again" calls `POST …/book/rescan`. **Listen in player**
(shown while `GET /server`'s `web_player` is on) opens the book in the web
player in a new tab as the same user: the click opens the tab at once
(`window.open('', '_blank')`, so no pop-up blocker stops it; its `opener` is
cleared), `POST /auth/pair` mints a single-use, 10-minute pairing token, and
the tab goes to the same-origin `/web/connect?token=&sid=&uid=&book=&path=`
(`playerConnectUrl` in `book-model.ts`; never the payload's `web_url`, whose
base may be the `public_url`, another origin). A failed call closes the tab
and toasts. What the player does with the link is in
[Connect and onboarding](../frontend/connect-and-addresses.md#a-link-that-names-a-book). `?match=1` opens the
match dialog on arrival (Health's "Review match"); closing it drops the param.
The Chapters card opens with the **Community chapters** panel
(`features/book/community-chapters.tsx`; its words and its one action come
Expand Down
5 changes: 5 additions & 0 deletions docs-users/admin/books.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ The buttons there are:
- **Match with community metadata** (it reads **Compare with community** once
the book has an ASIN or ISBN) - see
[Matching with community metadata](#matching-with-community-metadata).
- **Listen in player** - opens the book in the web player in a new tab,
already signed in as you, so you can check how it sounds or test its
chapters without signing in again. If the browser blocks the new tab, the
console says so: allow pop-ups for your server's address and click it again.
It only shows when your server has the web player.
- **Change cover** - see [Covers](#covers).
- The **⋯** menu (**More book actions**) - **Copy path**, **Add to a
share...** and **Read the files again**, which re-reads this book's files
Expand Down
Loading