mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-19 09:06:14 +00:00
The PUT handler shipped in 5f31616 + the routing fix in 66b83b6 made
the rename PUT reach AfterTouch and return 200. But the response and
the on-disk record both drifted away from real Bose's parity on every
rename: CreatedOn was rewritten to now() (so the "first paired in
2017" semantics evaporated on the second rename) and IPAddress
landed empty (because the speaker's PUT body doesn't carry it and
the marge handler had no preservation path).
Pre-shutdown capture at
data/parity_mismatches/1771797308__streaming_account_3230304_device_A81B6A536A98.json
shows real Bose's 200 OK shape: createdOn pinned to the original
pairing timestamp (2017-02-07), ipaddress populated, only updatedOn
and name change across renames. Aligning with that.
Three small persistence additions:
- models.ServiceDeviceInfo grows CreatedOn + UpdatedOn (ISO8601
strings, omitempty so existing JSON consumers don't break).
- datastore.SaveDeviceInfo persists them inside the DeviceInfo.xml
payload as <createdOn> / <updatedOn> alongside the other fields.
- mergeWithExistingDeviceInfo preserves CreatedOn unconditionally
(it's the "first-paired" timestamp and never re-derived from
inbound data) and preserves UpdatedOn only if the caller didn't
set a fresh one.
marge.AddDeviceToAccount becomes precedence-aware:
- Reads the existing record once at the top.
- CreatedOn: preserved from existing if present, else now() for
first registration.
- IPAddress: preserves what's in the existing record; falls back
to r.RemoteAddr's host portion only when no prior IP exists.
Lets first-time PUTs seed an IP from the inbound connection
without later renames clobbering a known-good value.
- UpdatedOn: always now().
- Response XML now re-reads the persisted record so the
response body matches what's on disk — no parallel hand-built
XML drifting from the merge result.
Function signature gained a remoteAddr parameter. Both callers
(HandleMargeAddDevice and HandleMargeUpdateDevice) pass r.RemoteAddr.
Test coverage:
- TestIssue285_RenamePutAcceptedAndPersisted seeds the datastore
with a 2017 CreatedOn and a known IP, then PUTs the rename;
asserts both survive on disk AND in the response body, and
that UpdatedOn refreshes. The same pre-shutdown capture cited
above is the parity reference.
- TestIssue285_NewDeviceGetsRemoteAddrAndFreshTimestamps (new)
covers the no-prior-record path: first-time PUT against an
unknown device produces CreatedOn = now() and IPAddress
pulled from the inbound TCP connection. Pins the fallback
behaviour so it can't quietly stop seeding new devices.
Authorization is still not enforced — the speaker has no Bose token
to send post-shutdown, and we don't (yet) have a token-authority
story of our own. Adding a warn-only auth check is a deferred
follow-up (see NEXT.md). Real Bose returned 401 for this PUT in the
2026-05-15 capture; we knowingly accept anything.
Refs #285.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>