@@ -136,6 +136,10 @@ def __init__(self, realtime: AblyRealtime, name: str, channel_options: ChannelOp
136136 # will be disrupted if the user called .off() to remove all listeners
137137 self .__internal_state_emitter = EventEmitter ()
138138
139+ # Initialize presence for this channel
140+ from ably .realtime .realtimepresence import RealtimePresence
141+ self .__presence = RealtimePresence (self )
142+
139143 # Pass channel options as dictionary to parent Channel class
140144 Channel .__init__ (self , realtime , name , self .__channel_options .to_dict ())
141145
@@ -528,6 +532,7 @@ def _on_message(self, proto_msg: dict) -> None:
528532 error = proto_msg .get ("error" )
529533 exception = None
530534 resumed = False
535+ has_presence = False
531536
532537 self .__attach_serial = channel_serial
533538 self .__channel_serial = channel_serial
@@ -538,14 +543,16 @@ def _on_message(self, proto_msg: dict) -> None:
538543
539544 if flags :
540545 resumed = has_flag (flags , Flag .RESUMED )
546+ # RTP1: Check for HAS_PRESENCE flag
547+ has_presence = has_flag (flags , Flag .HAS_PRESENCE )
541548
542549 # RTL12
543550 if self .state == ChannelState .ATTACHED :
544551 if not resumed :
545552 state_change = ChannelStateChange (self .state , ChannelState .ATTACHED , resumed , exception )
546553 self ._emit ("update" , state_change )
547554 elif self .state == ChannelState .ATTACHING :
548- self ._notify_state (ChannelState .ATTACHED , resumed = resumed )
555+ self ._notify_state (ChannelState .ATTACHED , resumed = resumed , has_presence = has_presence )
549556 else :
550557 log .warn ("RealtimeChannel._on_message(): ATTACHED received while not attaching" )
551558 elif action == ProtocolMessageAction .DETACHED :
@@ -568,6 +575,19 @@ def _on_message(self, proto_msg: dict) -> None:
568575 log .error (f"Message processing error { e } . Skip messages { proto_msg .get ('messages' )} " )
569576 for message in messages :
570577 self .__message_emitter ._emit (message .name , message )
578+ elif action == ProtocolMessageAction .PRESENCE :
579+ # Handle PRESENCE messages
580+ from ably .types .presence import PresenceMessage
581+ presence_messages = proto_msg .get ('presence' , [])
582+ decoded_presence = PresenceMessage .from_encoded_array (presence_messages , cipher = self .cipher )
583+ self .__presence .set_presence (decoded_presence , is_sync = False )
584+ elif action == ProtocolMessageAction .SYNC :
585+ # Handle SYNC messages (RTP18)
586+ from ably .types .presence import PresenceMessage
587+ presence_messages = proto_msg .get ('presence' , [])
588+ decoded_presence = PresenceMessage .from_encoded_array (presence_messages , cipher = self .cipher )
589+ sync_channel_serial = proto_msg .get ('channelSerial' )
590+ self .__presence .set_presence (decoded_presence , is_sync = True , sync_channel_serial = sync_channel_serial )
571591 elif action == ProtocolMessageAction .ERROR :
572592 error = AblyException .from_dict (proto_msg .get ('error' ))
573593 self ._notify_state (ChannelState .FAILED , reason = error )
@@ -578,7 +598,7 @@ def _request_state(self, state: ChannelState) -> None:
578598 self ._check_pending_state ()
579599
580600 def _notify_state (self , state : ChannelState , reason : AblyException | None = None ,
581- resumed : bool = False ) -> None :
601+ resumed : bool = False , has_presence : bool = False ) -> None :
582602 log .debug (f'RealtimeChannel._notify_state(): state = { state } ' )
583603
584604 self .__clear_state_timer ()
@@ -616,6 +636,9 @@ def _notify_state(self, state: ChannelState, reason: AblyException | None = None
616636 self ._emit (state , state_change )
617637 self .__internal_state_emitter ._emit (state , state_change )
618638
639+ # RTP5: Notify presence of channel state change
640+ self .__presence .act_on_channel_state (state , has_presence = has_presence , error = reason )
641+
619642 def _send_message (self , msg : dict ) -> None :
620643 asyncio .create_task (self .__realtime .connection .connection_manager .send_protocol_message (msg ))
621644
@@ -706,6 +729,11 @@ def params(self) -> dict[str, str]:
706729 """Get channel parameters"""
707730 return self .__params
708731
732+ @property
733+ def presence (self ):
734+ """Get the RealtimePresence object for this channel"""
735+ return self .__presence
736+
709737 def _start_decode_failure_recovery (self , error : AblyException ) -> None :
710738 """Start RTL18 decode failure recovery procedure"""
711739
0 commit comments