mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-11 00:37:41 +00:00
fix(dm): gate forwarded callbacks on the bound scope (single-active orchestrator)
The single-active BackwardRelayPager applies forwarded relay callbacks to whichever scope is currently bound. Its doc already states this is "safe as long as the caller only advances the bound scope", but a subscription for a *just-backgrounded* scope (conversation navigation overlap, account switch, a second pane) can still deliver a late onEvent/onEose/onClosed — which would move the newly-bound scope's cursors instead. Now that those cursors persist on the Chatroom/ChatroomList model, that corruption would stick. Add BackwardRelayPager.isBoundTo(cursors) (cursor identity == scope identity) and gate each manager's forwarded callbacks on it, so a stray callback from a non-bound scope is dropped, not mis-applied. The framework's own newEose bookkeeping still runs. No-op on the happy single-scope path.
This commit is contained in:
+11
-6
@@ -111,22 +111,26 @@ class AccountGiftWrapsHistoryEoseManager(
|
||||
return requestNewSubscription(historyListener(key))
|
||||
}
|
||||
|
||||
private fun historyListener(key: AccountQueryState): SubscriptionListener =
|
||||
object : SubscriptionListener {
|
||||
private fun historyListener(key: AccountQueryState): SubscriptionListener {
|
||||
// A just-backgrounded account's subscription can still deliver after the orchestrator rebinds to
|
||||
// another account; gate the pager (single-active) on whether it's still bound to THIS account's
|
||||
// cursors so a late callback can't move another account's cursors. newEose runs regardless.
|
||||
val myCursors = key.account.chatroomList.giftWrapHistory
|
||||
return object : SubscriptionListener {
|
||||
override fun onEvent(
|
||||
event: Event,
|
||||
isLive: Boolean,
|
||||
relay: NormalizedRelayUrl,
|
||||
forFilters: List<Filter>?,
|
||||
) {
|
||||
pager.onEvent(relay, event.createdAt)
|
||||
if (pager.isBoundTo(myCursors)) pager.onEvent(relay, event.createdAt)
|
||||
}
|
||||
|
||||
override fun onEose(
|
||||
relay: NormalizedRelayUrl,
|
||||
forFilters: List<Filter>?,
|
||||
) {
|
||||
if (pager.onEose(relay)) {
|
||||
if (pager.isBoundTo(myCursors) && pager.onEose(relay)) {
|
||||
Log.d(TAG) { "[giftwrap.history] ${relay.url} reached the bottom (done)" }
|
||||
}
|
||||
// No auto-advance: the relay parks here until its marker asks for the next page.
|
||||
@@ -138,7 +142,7 @@ class AccountGiftWrapsHistoryEoseManager(
|
||||
relay: NormalizedRelayUrl,
|
||||
forFilters: List<Filter>?,
|
||||
) {
|
||||
pager.onClosed(relay, message)
|
||||
if (pager.isBoundTo(myCursors)) pager.onClosed(relay, message)
|
||||
}
|
||||
|
||||
override fun onCannotConnect(
|
||||
@@ -146,9 +150,10 @@ class AccountGiftWrapsHistoryEoseManager(
|
||||
message: String,
|
||||
forFilters: List<Filter>?,
|
||||
) {
|
||||
pager.onCannotConnect(relay, message)
|
||||
if (pager.isBoundTo(myCursors)) pager.onCannotConnect(relay, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DMPagination"
|
||||
|
||||
+11
-6
@@ -114,22 +114,26 @@ class ChatroomNip04HistorySubAssembler(
|
||||
return requestNewSubscription(historyListener(key))
|
||||
}
|
||||
|
||||
private fun historyListener(key: ChatroomQueryState): SubscriptionListener =
|
||||
object : SubscriptionListener {
|
||||
private fun historyListener(key: ChatroomQueryState): SubscriptionListener {
|
||||
// A just-backgrounded room's subscription can still deliver after the orchestrator rebinds to
|
||||
// another room; gate the pager (single-active) on whether it's still bound to THIS room's cursors
|
||||
// so a late callback can't move another room's cursors. newEose (framework bookkeeping) runs anyway.
|
||||
val myCursors = cursorsFor(key)
|
||||
return object : SubscriptionListener {
|
||||
override fun onEvent(
|
||||
event: Event,
|
||||
isLive: Boolean,
|
||||
relay: NormalizedRelayUrl,
|
||||
forFilters: List<Filter>?,
|
||||
) {
|
||||
pager.onEvent(relay, event.createdAt)
|
||||
if (pager.isBoundTo(myCursors)) pager.onEvent(relay, event.createdAt)
|
||||
}
|
||||
|
||||
override fun onEose(
|
||||
relay: NormalizedRelayUrl,
|
||||
forFilters: List<Filter>?,
|
||||
) {
|
||||
if (pager.onEose(relay)) {
|
||||
if (pager.isBoundTo(myCursors) && pager.onEose(relay)) {
|
||||
Log.d("DMPagination") { "[convo.nip04.history] ${relay.url} reached the bottom (done)" }
|
||||
}
|
||||
newEose(key, relay, TimeUtils.now(), forFilters)
|
||||
@@ -140,7 +144,7 @@ class ChatroomNip04HistorySubAssembler(
|
||||
relay: NormalizedRelayUrl,
|
||||
forFilters: List<Filter>?,
|
||||
) {
|
||||
pager.onClosed(relay, message)
|
||||
if (pager.isBoundTo(myCursors)) pager.onClosed(relay, message)
|
||||
}
|
||||
|
||||
override fun onCannotConnect(
|
||||
@@ -148,7 +152,8 @@ class ChatroomNip04HistorySubAssembler(
|
||||
message: String,
|
||||
forFilters: List<Filter>?,
|
||||
) {
|
||||
pager.onCannotConnect(relay, message)
|
||||
if (pager.isBoundTo(myCursors)) pager.onCannotConnect(relay, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-6
@@ -104,22 +104,26 @@ class ChatroomListNip04HistorySubAssembler(
|
||||
return requestNewSubscription(historyListener(key))
|
||||
}
|
||||
|
||||
private fun historyListener(key: ChatroomListState): SubscriptionListener =
|
||||
object : SubscriptionListener {
|
||||
private fun historyListener(key: ChatroomListState): SubscriptionListener {
|
||||
// A just-backgrounded account's subscription can still deliver after the orchestrator rebinds to
|
||||
// another account; gate the pager (single-active) on whether it's still bound to THIS account's
|
||||
// cursors so a late callback can't move another account's cursors. newEose runs regardless.
|
||||
val myCursors = key.account.chatroomList.nip04History
|
||||
return object : SubscriptionListener {
|
||||
override fun onEvent(
|
||||
event: Event,
|
||||
isLive: Boolean,
|
||||
relay: NormalizedRelayUrl,
|
||||
forFilters: List<Filter>?,
|
||||
) {
|
||||
pager.onEvent(relay, event.createdAt)
|
||||
if (pager.isBoundTo(myCursors)) pager.onEvent(relay, event.createdAt)
|
||||
}
|
||||
|
||||
override fun onEose(
|
||||
relay: NormalizedRelayUrl,
|
||||
forFilters: List<Filter>?,
|
||||
) {
|
||||
if (pager.onEose(relay)) {
|
||||
if (pager.isBoundTo(myCursors) && pager.onEose(relay)) {
|
||||
Log.d("DMPagination") { "[rooms.nip04.history] ${relay.url} reached the bottom (done)" }
|
||||
}
|
||||
newEose(key, relay, TimeUtils.now(), forFilters)
|
||||
@@ -130,7 +134,7 @@ class ChatroomListNip04HistorySubAssembler(
|
||||
relay: NormalizedRelayUrl,
|
||||
forFilters: List<Filter>?,
|
||||
) {
|
||||
pager.onClosed(relay, message)
|
||||
if (pager.isBoundTo(myCursors)) pager.onClosed(relay, message)
|
||||
}
|
||||
|
||||
override fun onCannotConnect(
|
||||
@@ -138,7 +142,8 @@ class ChatroomListNip04HistorySubAssembler(
|
||||
message: String,
|
||||
forFilters: List<Filter>?,
|
||||
) {
|
||||
pager.onCannotConnect(relay, message)
|
||||
if (pager.isBoundTo(myCursors)) pager.onCannotConnect(relay, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -147,6 +147,15 @@ class BackwardRelayPager(
|
||||
recomputeExhausted()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether [c] is the currently-bound scope's cursor object. The orchestrator is single-active: it can
|
||||
* only correctly process callbacks for the bound scope. A caller whose subscription may still be alive
|
||||
* for a *just-backgrounded* scope (navigation overlap, a second pane) must gate its forwarded callbacks
|
||||
* on this — otherwise a late EOSE from scope A would move scope B's cursors. The cursor object is the
|
||||
* scope identity (one per `Chatroom`/`ChatroomList`), so reference identity is the check.
|
||||
*/
|
||||
fun isBoundTo(c: RelayLoadingCursors): Boolean = c === cursors
|
||||
|
||||
// --- Filter building support: the caller assembles the actual REQ from these. ---
|
||||
|
||||
/** Relays of the active scope that have been advanced (armed) and aren't done — i.e. carry a REQ. */
|
||||
|
||||
Reference in New Issue
Block a user