import json import uuid from channels.generic.websocket import AsyncWebsocketConsumer class SceneConsumer(AsyncWebsocketConsumer): """One connection per open project. Two things ride it. Scene deltas: relayed from what the server broadcasts on a save (see scenes.views.scene). Read-only — edits still go through the PUT endpoint, which is the broadcast source. Presence: who's viewing, which ad-hoc party they're in, and where that party is looking. Peers gossip it between themselves and work the party out locally (tl.party); all this does is hand out a connection id, stamp the sender's server-side identity onto every message — so nobody can post as somebody else — and relay. Nothing about a party is stored anywhere. """ RELAYED = ("state", "nav") async def connect(self): self.pk = self.scope["url_route"]["kwargs"]["pk"] self.group = f"project_{self.pk}" self.cid = uuid.uuid4().hex[:12] user = self.scope.get("user") self.username = user.get_username() if user and user.is_authenticated else None await self.channel_layer.group_add(self.group, self.channel_name) await self.accept() await self.send(text_data=json.dumps( {"kind": "welcome", "cid": self.cid, "user": self.username})) # the room answers a join with their own state, so rosters fill in both ways await self._relay({"kind": "join"}) async def disconnect(self, code): if hasattr(self, "cid"): await self._relay({"kind": "leave"}) await self.channel_layer.group_discard(self.group, self.channel_name) async def receive(self, text_data=None, bytes_data=None): try: msg = json.loads(text_data or "{}") except ValueError: return if isinstance(msg, dict) and msg.get("kind") in self.RELAYED: await self._relay(msg) async def _relay(self, msg): await self.channel_layer.group_send( self.group, {"type": "peer.msg", "msg": {**msg, "cid": self.cid, "user": self.username}}) # broadcast handler: {type: "peer.msg", msg: {...}} — presence gossip async def peer_msg(self, event): await self.send(text_data=json.dumps(event["msg"])) # broadcast handler: {type: "scene.delta", delta: {...}} async def scene_delta(self, event): await self.send(text_data=json.dumps(event["delta"]))