aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py26
-rw-r--r--packages/meshbay-node/tests/test_reply_correlation.py27
2 files changed, 44 insertions, 9 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py
index f200884..ed2c43a 100644
--- a/packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py
+++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py
@@ -20,6 +20,7 @@ from meshbay_common.protocol import MNP
from meshbay_node import linkpreview, ops
from meshbay_node.chat import FORMAT_SEALED_V1, ReplayedMessage
+from meshbay_node.transport.webrtc.channel import _REPLY_TO
log = logging.getLogger("meshbay_node.transport.webrtc_server")
@@ -115,15 +116,22 @@ class ChatMixin:
self._audit("chat_epoch_failed", reason)
return
self._audit("chat_epoch", f"{reason}:{result['epoch']}")
- # Everyone still connected picks the new key up without reconnecting.
- for session in list(
- (self._ctx.get("groups") or {}).get(group_id, {})
- .get("_peers", {}).values()):
- try:
- session._send({"type": MNP.CHAT_EPOCH_ACK, "v": MNP_VERSION,
- "epoch": result["epoch"]})
- except Exception:
- pass
+ # Everyone still connected picks the new key up without reconnecting —
+ # this session too, which is why the notice leaves outside the reply
+ # context: stamped with the id of the request that caused it (a device
+ # revoked, a member removed), it would arrive as that request's answer.
+ token = _REPLY_TO.set((None, None))
+ try:
+ for session in list(
+ (self._ctx.get("groups") or {}).get(group_id, {})
+ .get("_peers", {}).values()):
+ try:
+ session._send({"type": MNP.CHAT_EPOCH_ACK, "v": MNP_VERSION,
+ "epoch": result["epoch"]})
+ except Exception:
+ pass
+ finally:
+ _REPLY_TO.reset(token)
def _do_chat_directory(self, msg: dict) -> None:
"""
diff --git a/packages/meshbay-node/tests/test_reply_correlation.py b/packages/meshbay-node/tests/test_reply_correlation.py
index bfb336e..a97ec1e 100644
--- a/packages/meshbay-node/tests/test_reply_correlation.py
+++ b/packages/meshbay-node/tests/test_reply_correlation.py
@@ -28,6 +28,7 @@ import base64
import msgpack
import pytest
from meshbay_common.protocol import MNP
+from meshbay_node.transport.webrtc.channel import _REPLY_TO
from meshbay_node.transport.webrtc_server import WebRTCPeerSession
pytestmark = pytest.mark.asyncio
@@ -163,3 +164,29 @@ async def test_work_handed_to_a_task_still_answers_under_the_right_id():
by_id = {m["req_id"]: m["detail"] for m in s._channel.sent}
assert by_id == {11: "first", 12: "second"}, (
"a late reply answered under whichever request arrived most recently")
+
+
+async def test_a_new_chat_epoch_answers_no_request_not_even_the_askers():
+ """Revoking a device or removing a member opens a chat epoch, announced to
+ everyone connected — the asker included. Stamped with the asker's request,
+ that notice arrived as the answer to `device_revoke` before the real one."""
+ asker, other = _session("asker"), _session("other")
+ asker._ctx = {"groups": {"g": {"_peers": {"a": asker, "b": other}}}}
+ asker._audit = lambda *a, **k: None
+
+ async def run_op(fn, *args):
+ return {"epoch": 2}
+ asker._run_op = run_op
+
+ token = _REPLY_TO.set((asker, 9)) # inside the handler of request 9
+ try:
+ await asker._new_chat_epoch("g", "device_revoke")
+ asker._send({"type": MNP.DEVICE_ADD_ACK})
+ finally:
+ _REPLY_TO.reset(token)
+
+ notice, reply = asker._channel.sent
+ assert notice["type"] == MNP.CHAT_EPOCH_ACK and "req_id" not in notice
+ assert reply["type"] == MNP.DEVICE_ADD_ACK and reply["req_id"] == 9
+ (seen,) = other._channel.sent
+ assert "req_id" not in seen