A federated thread travels under its address, not this app's key for it
This app keeps one `threads` map and namespaces it with prefixes: `group:` for a group, `addr:` for somebody on another relay. `toShared` strips the group one and never stripped the address one, so a backup written here said `addr:ada@relay.example`, which is not an address. The iPhone writes the bare one. Neither side could read the other's federated conversations, and the damaging direction is iPhone to Android: a bare address arriving here lands in a map whose unprefixed keys are user threads. Same mistake as the story id one commit ago, one field over, and the group prefix beside it was already being handled correctly, which makes it a slip rather than a decision. An internal storage detail does not belong in a shared shape. Coming back in, the prefix goes on unless the file already carries it, so an older backup does not end up with two. One test was pinning the bug: it asserted the shared shape contained the prefixed key. It now asserts the bare address, and that the prefix is absent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
88397a8bda
commit
3607a85459
@@ -69,7 +69,17 @@ object BackupPayload {
|
||||
}
|
||||
|
||||
tid.startsWith(ADDRESS_PREFIX) -> {
|
||||
addressed.put(tid, mapArray(messages, ::messageToShared))
|
||||
// The prefix comes OFF, exactly as the group one does
|
||||
// above. It is how this app namespaces one `threads`
|
||||
// map; the shared shape keys these by the address
|
||||
// itself. Leaving it on wrote `addr:ada@relay.example`
|
||||
// into the file, which is not an address, and the
|
||||
// iPhone writes the bare one, so neither side could
|
||||
// read the other's federated conversations.
|
||||
addressed.put(
|
||||
tid.removePrefix(ADDRESS_PREFIX),
|
||||
mapArray(messages, ::messageToShared),
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
@@ -302,9 +312,15 @@ object BackupPayload {
|
||||
}
|
||||
}
|
||||
shared.optJSONObject(ADDRESS_THREADS)?.let { all ->
|
||||
// Already prefixed when written, so they go back under their own id.
|
||||
for (tid in all.keys()) {
|
||||
all.optJSONArray(tid)?.let { threads.put(tid, mapArray(it, ::messageFromShared)) }
|
||||
for (address in all.keys()) {
|
||||
// The prefix goes back on. A file written before it was
|
||||
// stripped carries it already, and adding a second one would
|
||||
// make a thread nobody can find.
|
||||
val tid =
|
||||
if (address.startsWith(ADDRESS_PREFIX)) address else ADDRESS_PREFIX + address
|
||||
all.optJSONArray(address)?.let {
|
||||
threads.put(tid, mapArray(it, ::messageFromShared))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,15 @@ class BackupPayloadTest {
|
||||
// so an address left in `threads` would be dropped in silence. Keeping
|
||||
// it separate means an iPhone ignores it and this app gets it back.
|
||||
assertFalse(shared.getJSONObject("threads").has("addr:someone@elsewhere.example"))
|
||||
assertTrue(shared.getJSONObject("addrThreads").has("addr:someone@elsewhere.example"))
|
||||
// Keyed by the ADDRESS, with no `addr:` on it. That prefix is how this
|
||||
// app namespaces one `threads` map, and it used to travel: the file
|
||||
// said `addr:someone@elsewhere.example`, which is not an address, while
|
||||
// the iPhone wrote the bare one. Neither side could read the other's
|
||||
// federated conversations, and the group prefix beside it was already
|
||||
// being stripped correctly.
|
||||
assertTrue(shared.getJSONObject("addrThreads").has("someone@elsewhere.example"))
|
||||
assertFalse(shared.getJSONObject("addrThreads").has("addr:someone@elsewhere.example"))
|
||||
// And it goes back on, because that is this app's own key for it.
|
||||
assertTrue(roundTrip().getJSONObject("threads").has("addr:someone@elsewhere.example"))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user