Repository navigation
Show each output's Sendspin pairing token on the Audio tab - #224
Merged
Merged
Conversation
The player writes its Pairing PSK token to a 0600 file in its state dir, and the output card's details can reveal or copy it on demand for pasting into Music Assistant's "Pairing token" option. The token never goes to stdout, PubSub, logs or the page until the user asks for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Clipboard feedback reports success on unsupported HTTP origins, and token validation can accept truncated malformed files.
2 open issues
What changed in this PR
Exposes each output’s persisted Sendspin pairing token on the Audio tab for manual pairing with Music Assistant.
Changes:
- Writes tokens privately through the player’s existing persistence path.
- Adds on-demand token reading and validation APIs.
- Adds Show/Hide/Copy controls and persistence, privacy, and UI tests.
| File | Description |
|---|---|
| test/universal_proxy/audio/server_test.exs | Tests output state-directory lookup. |
| test/universal_proxy/audio/player_test.exs | Checks token storage and PubSub privacy. |
| test/universal_proxy/audio/pairing_token_test.exs | Tests token validation and read errors. |
| test/universal_proxy_web/live/audio_live_test.exs | Tests disclosure, copying, and stale events. |
| test/support/fake_sendspin_player.py | Simulates private token persistence. |
| test/sendspin_player_contract_test.exs | Tests real-binary persistence and privacy. |
| lib/universal_proxy/audio/server.ex | Exposes output state-directory lookup. |
| lib/universal_proxy/audio/player.ex | Publishes shared state-directory logic. |
| lib/universal_proxy/audio/pairing_token.ex | Reads and validates token files. |
| lib/universal_proxy/audio.ex | Adds the public token API. |
| lib/universal_proxy_web/live/audio_live.ex | Adds token disclosure and copy controls. |
| c_src/sendspin_player/src/main.cpp | Persists the pairing token after startup. |
| c_src/sendspin_player/src/file_persistence.h | Declares private-file persistence API. |
| c_src/sendspin_player/src/file_persistence.cpp | Reuses atomic writes for token storage. |
| c_src/sendspin_player/README.md | Documents token storage and pairing. |
Review effort: Balanced
The device UI is plain HTTP, where navigator.clipboard is unavailable, so copy falls back to execCommand and reports "Copied" only on success; the pairing token is copied client-side once revealed. A token file that fills the read buffer is now rejected before trimming. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Since #222, every output's player offers Sendspin's
pairing_pskpairing method, backed by a pairing key it saves in its state dir. Music Assistant 2.10.7 (current stable) already lets you pair such a client by pasting itsSP:…pairing token. This PR makes that token visible, so an output can be paired with MA today without waiting for Home Assistant's automatic pairing (planned for HA 2026.11).Outputs only: inputs still use the older wire format with PIN pairing, which has no pairing token.
Changes
start(), writesclient.pairing_token()to<state_dir>/pairing_token. The file is mode 0600, written atomically through the existing persistence path, and rewritten on every start. A missing token or failed write is non-fatal: it emits{"event":"error","kind":"pairing_token"}with no secret in the message, and the player keeps running.Audio.PairingToken: reads and validates the file (SP:prefix, at most 127 bytes, printable ASCII). It returns:not_readyor:unavailableand never puts the file contents in an error.Audio.pairing_token/1: follows thecatch :exitidiom.Audio.Server.state_dir/2comes fromPlayer.state_dir/1, now public, so the path logic isn't duplicated.Keeping the token secret
The token is a long-lived pairing secret that can't be revoked.
copyevent, so copying never puts it in the DOM.Inspectexcludes the token, so a LiveView crash report can't print it.Like the API encryption key on the Security tab, anyone who can open the web UI can reveal it.
Test plan
mise run test: 1687 passed, 0 failures. New coverage includes the contract test (0600 file, format, stable across a restart, never in output),pairing_token_test.exs, a check that no PubSub message carries the token, and LiveView tests (not in the HTML before Show, Copy pushes the event, not-ready hint, stale events ignored).compile --warnings-as-errors,credo --strict, dialyzer;mix assets.buildincludes the new classes🤖 Generated with Claude Code