Documentation for controlling and preserving Bose SoundTouch devices
This document outlines the strategy for ensuring Bose SoundTouch devices are correctly “primed” for Spotify Connect integration within the AfterTouch ecosystem.
To enable Spotify Connect for SoundTouch devices, especially for remote availability outside the local network, the speaker must be associated with a Spotify account via a process called “priming.” This involves a two-step exchange with the speaker’s ZeroConf API (port 8200):
getInfo — retrieve the speaker’s Diffie-Hellman public key and device metadata.addUser — push encrypted Spotify credentials using the shared DH secret.This is the standard Spotify Connect ZeroConf protocol. Once the speaker holds a properly encrypted credential blob it can independently authenticate with Spotify’s servers and refresh its own session without any further involvement from AfterTouch.
The current implementation follows the full Spotify Connect ZeroConf protocol (pkg/service/spotify/zeroconf.go):
GET http://{ip}:8200/zc?action=getInfo → parse publicKey (base64 DH key, 768-bit Oakley Group 1 prime) from the response.sharedSecret = DH(clientPrivate, speakerPublicKey).baseKey = SHA1(sharedSecret)[:16], then HMAC-SHA1 with labels "encryption" and "checksum".LoginCredentials blob (username, AUTHENTICATION_SPOTIFY_TOKEN=4, access token) using AES-128-CTR + HMAC-SHA1 checksum.POST http://{ip}:8200/zc?action=addUser with blob={encryptedBlob}, clientKey={clientPublicKeyBase64}.The speaker decrypts the blob, stores long-lived credentials, and can handle token refresh with Spotify independently. No periodic re-priming is required for token expiry.
The algorithm is based on librespot (Rust reference implementation).
If getInfo fails (e.g. firmware that does not implement the DH exchange), PushSpotifyCredentials automatically falls back to the simplified tokenType=accesstoken approach: the raw OAuth access token is sent as the blob with an empty clientKey. This token expires after ~60 minutes and the speaker cannot self-refresh, so periodic re-priming is required in that case.
AfterTouch adopts a Server-Centric Hybrid Model that prioritizes device cleanliness and user intent while providing automated self-healing.
AfterTouch replicates the native Bose “Add Source” experience. No Spotify priming occurs until a user explicitly links their Spotify account through the AfterTouch Management Dashboard. This ensures privacy and respects users who do not wish to use Spotify.
We avoid invasive modifications to the speaker’s filesystem.
Priming is triggered when the speaker signals it is active and ready, specifically:
/marge/streaming/support/power_on endpoint, AfterTouch ensures the device’s ZeroConf state is correctly primed. This is the primary trigger.During any of these events, the server:
AfterTouch ensures that if a speaker loses its session (due to a crash or power loss), it is re-primed when it next powers on and reaches out to the service.
The logic for account management and device interaction remains decoupled:
Note: With the proper encrypted-blob flow now in place, the watchdog is only needed for the “speaker reboots and loses state” case — not for token expiry. Speakers running older firmware that trigger the
tokenType=accesstokenfallback still require periodic re-priming (~45 min) because the raw access token expires.
Users can manually trigger a “Re-prime” or “Refresh Link” from the device list in the UI if they suspect the automated self-healing is delayed or if they want to force a specific account onto a device.
The strategy adapts based on where the AfterTouch server is deployed:
As AfterTouch moves to the Server-Centric model, we will:
spotify-boot-primer scripts and rc.local hooks from the speakers./mnt/nv/soundtouch-service/ base directory for other configuration needs (e.g., aftertouch.resolv.conf), but it will no longer contain Spotify-specific credentials or scripts./mnt/nv/soundtouch-service/spotify-primer.conf will be removed, ensuring that no sensitive AfterTouch login details are stored on the speaker in plain text.PrimeDeviceWithSpotify(ip) and pushSpotifyTokenToDevice in pkg/service/handlers/server.go. Triggered on device registration (marge handlers) and via the manual HandleMgmtPrimeDevice endpoint.handleDiscoveredDevice calls PrimeDeviceWithSpotify when a speaker is found.LoginCredentials blob implemented in pkg/service/spotify/zeroconf.go. Automatically falls back to tokenType=accesstoken if getInfo fails (older firmware).spotify-boot-primer scripts and rc.local hooks from the speakers.