From 16379de7d980c7e9eb917f5f60c7845a9566c663 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 30 Apr 2025 17:26:09 -0300 Subject: [PATCH 1/5] Clarify rules for calling #attach callback I want to copy some of the #attach rules for #detach, so let's clarify a few things first. --- textile/features.textile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/textile/features.textile b/textile/features.textile index 9ebd3cd2..bca72c8a 100644 --- a/textile/features.textile +++ b/textile/features.textile @@ -663,16 +663,16 @@ h3(#realtime-channel). RealtimeChannel * @(RTL11)@ If a channel enters the @DETACHED@, @SUSPENDED@ or @FAILED@ state, then all presence actions that are still queued for send on that channel per "RTP16b":#RTP16b should be deleted from the queue, and any callback passed to the corresponding presence method invocation should be called with an @ErrorInfo@ indicating the failure ** @(RTL11a)@ For clarity, any messages awaiting an @ACK@ or @NACK@ are unaffected by channel state changes i.e. a channel that becomes detached following an explicit request to detach may still receive an @ACK@ or @NACK@ for messages published on that channel later * @(RTL4)@ @RealtimeChannel#attach@ function: -** @(RTL4a)@ If already @ATTACHED@ nothing is done +** @(RTL4a)@ If already @ATTACHED@ nothing is done, and the optional callback should be called with no argument ** @(RTL4h)@ If the channel is in a pending state @DETACHING@ or @ATTACHING@, do the attach operation after the completion of the pending request ** @(RTL4g)@ If the channel is in the @FAILED@ state, the @attach@ request sets its @errorReason@ to @null@, and proceeds with a channel attach described in "RTL4b":#RTL4b, "RTL4i":#RTL4i and "RTL4c":#RTL4c -** @(RTL4b)@ If the connection state is @INITIALIZED@, @CLOSED@, @CLOSING@, @SUSPENDED@ or @FAILED@, the @attach@ request results in an error +** @(RTL4b)@ If the connection state is @INITIALIZED@, @CLOSED@, @CLOSING@, @SUSPENDED@ or @FAILED@, the @attach@ request results in an error (that is, depending on what is idiomatic for the language it should either throw an error or call its optional callback with an error) *** @(RTL4b1)@ Note that an attach attempt immediately after the library is instantiated, assuming @autoConnect@ (@TO3e@)is not set to @false@, should not raise an error (that is, should fall under @RTL4i@, not @RTL4b@), since the library should be in a @CONNECTING@ state at that point ** @(RTL4i)@ If the connection state is @CONNECTING@ or @DISCONNECTED@, the channel should be put into the @ATTACHING@ state. (Attach message will be sent once the the connection becomes @CONNECTED@ per @RTL3d@). ** @(RTL4c)@ Otherwise an @ATTACH@ ProtocolMessage is sent to the server, the state transitions to @ATTACHING@ and the channel becomes @ATTACHED@ when the confirmation @ATTACHED@ ProtocolMessage is received *** @(RTL4c1)@ The @ATTACH@ ProtocolMessage @channelSerial@ field must be set to the "@RTL15b@":#RTL15b @channelSerial@. If the @RTL15b@ @channelSerial@ is not set, the field may be set to @null@ or omitted. ** @(RTL4f)@ Once an @ATTACH@ @ProtocolMessage@ is sent, if an @ATTACHED@ @ProtocolMessage@ is not received within "@realtimeRequestTimeout@":#TO3l11, the attach request should be treated as though it has failed and the channel should transition to the @SUSPENDED@ state. The channel will then be subsequently automatically re-attached as described in "RTL13":#RTL13 -** @(RTL4d)@ A callback (or other language-idiomatic equivalent) can be provided that is called when the channel next moves to one of @ATTACHED@, @DETACHED@, @SUSPENDED@, or @FAILED@ states. In the case of @ATTACHED@ the callback is called with no argument. In all other cases it is called with an @ErrorInfo@ corresponding to the @ChannelStateChange.reason@ of the state change (or a fallback if there is no @reason@) to indicate that the attach has failed. (Note: when combined with RTL4f, this means that if the connection is @CONNECTED@, the callback is guaranteed to be called within @realtimeRequestTimeout@ of the @attach()@ call) +** @(RTL4d)@ A callback (or other language-idiomatic equivalent) can be provided that is called by either "@RTL4a@":#RTL4a or "@RTL4b@":#RTL4b, or when, after transitioning to @ATTACHING@ in "@RTL4c@":#RTL4c, the channel next moves to one of @ATTACHED@, @DETACHED@, @SUSPENDED@, or @FAILED@ states. In the case of @ATTACHED@ the callback is called with no argument. In all other cases it is called with an @ErrorInfo@ corresponding to the @ChannelStateChange.reason@ of the state change (or a fallback if there is no @reason@) to indicate that the attach has failed. (Note: when combined with RTL4f, this means that if the connection is @CONNECTED@, the callback is guaranteed to be called within @realtimeRequestTimeout@ of the @attach()@ call) *** @(RTL4d1)@ Optionally, upon success, the callback may be invoked with the @ChannelStateChange@ object once the channel is attached. If the channel is already attached, it should be invoked with @null@. ** @(RTL4e)@ This clause has been deleted (redundant to "@RTL14@":#RTL14). ** @(RTL4j)@ If the attach is not a clean attach (defined in @RTL4j1@), for example an automatic reattach triggered by "@RTN15c3@":#RTN15c3 or "@RTL13a@":#RTL13a (non-exhaustive), the library should set the "@ATTACH_RESUME@":#TR3f flag in the @ATTACH@ message From 5c6b2c1e89cadad864797edb317684599b2fe9e0 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 29 Apr 2025 15:45:40 -0300 Subject: [PATCH 2/5] Explain the conditions for transitioning to DETACHING On a first read of the preceding spec points, I found it a bit hard to reason about under which conditions we actually end up putting a DETACH on the wire. I think a bit of commentary here is helpful. --- textile/features.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/textile/features.textile b/textile/features.textile index bca72c8a..b06e09b3 100644 --- a/textile/features.textile +++ b/textile/features.textile @@ -689,7 +689,7 @@ h3(#realtime-channel). RealtimeChannel ** @(RTL5j)@ If the channel state is @SUSPENDED@, the @detach@ request transitions the channel immediately to the @DETACHED@ state ** @(RTL5g)@ If the connection state is @CLOSING@ or @FAILED@, the @detach@ request results in an error ** @(RTL5h)@ If the connection state is @CONNECTING@ or @DISCONNECTED@, do the detach operation once the connection state is @CONNECTED@ -** @(RTL5d)@ Otherwise a @DETACH@ ProtocolMessage is sent to the server, the state transitions to @DETACHING@ and the channel becomes @DETACHED@ when the confirmation @DETACHED@ ProtocolMessage is received +** @(RTL5d)@ Otherwise (i.e. the channel state is @ATTACHED@ and the connection — given this channel state, "@RTL3@":#RTL3, and the above connection state conditions — is thus @CONNECTED@) a @DETACH@ ProtocolMessage is sent to the server, the state transitions to @DETACHING@ and the channel becomes @DETACHED@ when the confirmation @DETACHED@ ProtocolMessage is received ** @(RTL5f)@ Once a @DETACH@ @ProtocolMessage@ is sent, if a @DETACHED@ @ProtocolMessage@ is not received within "@realtimeRequestTimeout@":#TO3l11, the detach request should be treated as though it has failed and the channel will return to its previous state ** @(RTL5k)@ If the channel receives an @ATTACHED@ message while in the @DETACHING@ or @DETACHED@ state, it should send a new @DETACH@ message and remain in (or transition to) the @DETACHING@ state ** @(RTL5e)@ If the language permits, a callback can be provided that is called when the channel is detached successfully or the detach fails and the @ErrorInfo@ error is passed as an argument to the callback From 845173d7785b68e57e84f2a8ac789c0ceb65193d Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 30 Apr 2025 15:49:10 -0300 Subject: [PATCH 3/5] Move RTN19b to connection state side effects section The ATTACHING part was already redundant to RTL3d. I'm moving the DETACHING part there for consistency (also, the rest of RTN19 is specifically about ACKs so it doesn't really fit there) and to provide a natural place to add upcoming spec points about how a DETACHING channel should respond to other connection states. --- textile/features.textile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/textile/features.textile b/textile/features.textile index b06e09b3..3a0a2d93 100644 --- a/textile/features.textile +++ b/textile/features.textile @@ -616,7 +616,7 @@ h3(#realtime-connection). Connection ** @(RTN19a)@ Any @ProtocolMessage@ that is awaiting an @ACK@/@NACK@ on the old transport will not receive the @ACK@/@NACK@ on the new transport. The client library must therefore resend any @ProtocolMessage@ that is awaiting a @ACK@/@NACK@ to Ably in order to receive the expected @ACK@/@NACK@ for that message (subject to @RTN7e@/@RTN7d@). The Ably service is responsible for keeping track of messages, ignoring duplicates and responding with suitable @ACK@/@NACK@ messages *** @(RTN19a1)@ One possible implementation of this requirement would be to add any in-flight messages to the @RTL6c2@ connection-wide queue of messages that will be sent once the connection next becomes @CONNECTED@ *** @(RTN19a2)@ In the case of an @RTN15c6@ successful resume, the @msgSerial@ of the reattempted @ProtocolMessage@s should remain the same as for the original attempt. In the case of an @RTN15c7@ failed resume, the message must be assigned a new @msgSerial@ from the SDK's internal counter. -** @(RTN19b)@ If there are any pending channels i.e. in the @ATTACHING@ or @DETACHING@ state, the respective @ATTACH@ or @DETACH@ message should be resent to Ably +** @(RTN19b)@ This clause has been replaced by "@RTL3d@":#RTL3d and "@RTL3f@":#RTL3f. It was valid up to and including specification version @TBD@. * @(RTN23)@ Heartbeats ** @(RTN23a)@ If a transport does not receive any indication of activity on a transport for a period greater than the sum of the @maxIdleInterval@ (which will be sent in the @connectionDetails@ of the most recent @CONNECTED@ message received on that transport) and the "@realtimeRequestTimeout@":#TO3l11@, that transport should be disconnected. Any message (or non-message indicator, see @RTN23b@) received counts as an indication of activity and should reset the timer, not merely heartbeat messages. However, it must be received (that is, sent from the server to the client); client-sent data does not count. ** @(RTN23b)@ When initiating a connection, the client may send a @heartbeats@ param in the querystring, with value @true@ or @false@. If the value is true, the server will use Ably protocol messages (for example, a message with a @HEARTBEAT@ action) to satisfy the @maxIdleInterval@ requirement. If it is false or unspecified, the server is permitted to use any transport-level mechanism (for example, "websocket":https://ably.com/topic/websockets ping frames) to satisfy this. So for example, for "websocket transports":https://ably.com/topic/websockets, if the client is able to observe websocket pings, then it should send @heartbeats=false@. If not, it should send @heartbeats=true@. @@ -660,6 +660,7 @@ h3(#realtime-channel). RealtimeChannel ** @(RTL3b)@ If the connection state enters the @CLOSED@ state, then an @ATTACHING@ or @ATTACHED@ channel state will transition to @DETACHED@ ** @(RTL3c)@ If the connection state enters the @SUSPENDED@ state, then an @ATTACHING@ or @ATTACHED@ channel state will transition to @SUSPENDED@ ** @(RTL3d)@ If the connection state enters the @CONNECTED@ state, any channels in the @ATTACHING@, @ATTACHED@, or @SUSPENDED@ states should be transitioned to @ATTACHING@ (other than ones already in that state), and initiate an @RTL4c@ attach sequence. (If the attach operation times out and the channel was previously @SUSPENDED@, it should return to the @SUSPENDED@ state, see "RTL4f":#RTL4f). The connection should also process any messages that had been queued per @RTL6c2@ (it should do this immediately, without waiting for the attach operations to finish). +** @(RTL3f)@ If the connection enters the @CONNECTED@ state then any channels in the @DETACHING@ state should initiate an "@RTL5d@":#RTL5d detach sequence. * @(RTL11)@ If a channel enters the @DETACHED@, @SUSPENDED@ or @FAILED@ state, then all presence actions that are still queued for send on that channel per "RTP16b":#RTP16b should be deleted from the queue, and any callback passed to the corresponding presence method invocation should be called with an @ErrorInfo@ indicating the failure ** @(RTL11a)@ For clarity, any messages awaiting an @ACK@ or @NACK@ are unaffected by channel state changes i.e. a channel that becomes detached following an explicit request to detach may still receive an @ACK@ or @NACK@ for messages published on that channel later * @(RTL4)@ @RealtimeChannel#attach@ function: From 14665bf057e9eddac23bfc35cb4dfac33ebb4121 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 29 Apr 2025 17:35:48 -0300 Subject: [PATCH 4/5] Clarify what state to transition to on #detach timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since this behaviour relates to what to do when you've sent a DETACH but haven't yet received a DETACHED, I want to make sure I understand it before moving on to considering the same thing in the context of some not-currently-handled connection state changes. The current language of "previous state" is a bit vague to me — I think that you can interpret it as meaning either the start-of-RTL5d ATTACHED or the triggered-by-RTL5d DETACHING. ably-js [1] and ably-cocoa [2] transition back to ATTACHED so that's the behaviour that I've specified here, but I don't fully understand the logic — it seems a bit misleading that you'd describe a channel as ATTACHED when in fact the detach request may have been honoured (in which case Realtime is not going to send you any further messages on that channel). (TODO: Would be good to get some insight on this one from someone involved in this decision, so that I can add an explanatory comment. I guess the thinking might be that the only way you're not going to receive the DETACHED is because of a connection issue, in which case you're eventually going to try reattaching and your ATTACHED state will not be a lie any more. And if you receive the DETACHED late, you'll reattach per RTL13) [1] https://github.com/ably/ably-js/blob/b5fbb90c805d560813eec0637f140c39106dd105/src/common/lib/client/realtimechannel.ts#L842-L846 [2] https://github.com/ably/ably-cocoa/blob/ebe0e752e8d35ac9cceb0b4f2508675a4d519e15/Source/ARTRealtimeChannel.m#L1025 --- textile/features.textile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/textile/features.textile b/textile/features.textile index 3a0a2d93..cf8af227 100644 --- a/textile/features.textile +++ b/textile/features.textile @@ -691,7 +691,8 @@ h3(#realtime-channel). RealtimeChannel ** @(RTL5g)@ If the connection state is @CLOSING@ or @FAILED@, the @detach@ request results in an error ** @(RTL5h)@ If the connection state is @CONNECTING@ or @DISCONNECTED@, do the detach operation once the connection state is @CONNECTED@ ** @(RTL5d)@ Otherwise (i.e. the channel state is @ATTACHED@ and the connection — given this channel state, "@RTL3@":#RTL3, and the above connection state conditions — is thus @CONNECTED@) a @DETACH@ ProtocolMessage is sent to the server, the state transitions to @DETACHING@ and the channel becomes @DETACHED@ when the confirmation @DETACHED@ ProtocolMessage is received -** @(RTL5f)@ Once a @DETACH@ @ProtocolMessage@ is sent, if a @DETACHED@ @ProtocolMessage@ is not received within "@realtimeRequestTimeout@":#TO3l11, the detach request should be treated as though it has failed and the channel will return to its previous state +** @(RTL5f)@ This clause has been replaced by "@RTL5l@":#RTL5l. It was valid up to and including specification version @TBD@. +** @(RTL5l)@ Once a @DETACH@ @ProtocolMessage@ is sent, if a @DETACHED@ @ProtocolMessage@ is not received within "@realtimeRequestTimeout@":#TO3l11, the detach request should be treated as though it has failed and the channel will transition back to @ATTACHED@ ** @(RTL5k)@ If the channel receives an @ATTACHED@ message while in the @DETACHING@ or @DETACHED@ state, it should send a new @DETACH@ message and remain in (or transition to) the @DETACHING@ state ** @(RTL5e)@ If the language permits, a callback can be provided that is called when the channel is detached successfully or the detach fails and the @ErrorInfo@ error is passed as an argument to the callback * @(RTL6)@ @RealtimeChannel#publish@ function: From 34e53e54fd4fad9380a86cd52089506b3de6a568 Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 30 Apr 2025 17:01:54 -0300 Subject: [PATCH 5/5] Add side effects of more connection states for DETACHING channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We add handling for the following connection states: FAILED: We're never going to receive the DETACHED that we're waiting for, but we don't want to remain in DETACHING forever. So, let's put the channel into FAILED and fail the #detach request. CLOSED: We're never going to receive the DETACHED that we're waiting for, but we don't want to remain in DETACHING forever. I don't think we should consider this as a failure of the #detach request, because by becoming CLOSED we've decided that we consider the server-side connection state to have been torn down, and so it seems like we should also consider the server-side attachment state to have also been torn down. So, succeed the #detach request. SUSPENDED: By becoming SUSPENDED, we've decided that there is no longer any server-side connection state, and so I think we can conclude there is also no longer any server-side attachment state and thus consider the request to tear it down (that is, the #detach request) to have succeeded. It's also consistent with what we do in RTL5j when #detach is called on a SUSPENDED channel. Now that there are more ways in which a #detach request can resolve, we bring #detach in line with #attach and make its callback be driven by the channel state. Note: we noticed the need for these spec points because we noticed ([1], [2]) that, in the absence of guidance from the spec, in ably-js a DETACHING channel actually ends up becoming SUSPENDED when the connection becomes SUSPENDED, which means it ends up reattaching when the connection becomes CONNECTED again. This is not what we want — a DETACHING channel should never autonomously reattach. [1] https://ably-real-time.slack.com/archives/C8SPU4589/p1732273028523529?thread_ts=1732191641.458019&cid=C8SPU4589 [2] https://github.com/ably/ably-js/blob/18a255948c38d1e60715c8f5d6173369b57cb8d6/src/common/lib/client/baserealtime.ts#L178 --- textile/features.textile | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/textile/features.textile b/textile/features.textile index cf8af227..a78dc15f 100644 --- a/textile/features.textile +++ b/textile/features.textile @@ -657,8 +657,10 @@ h3(#realtime-channel). RealtimeChannel * @(RTL3)@ Connection state change side effects: ** @(RTL3e)@ If the connection state enters the @DISCONNECTED@ state, it will have no effect on the channel states. ** @(RTL3a)@ If the connection state enters the @FAILED@ state, then an @ATTACHING@ or @ATTACHED@ channel state will transition to @FAILED@ and set the @RealtimeChannel#errorReason@ +** @(RTL3g)@ If the connection state enters the @FAILED@ state, then any channels in the @DETACHING@ state should transition to @FAILED@ and set their @errorReason@ to that of the connection. ** @(RTL3b)@ If the connection state enters the @CLOSED@ state, then an @ATTACHING@ or @ATTACHED@ channel state will transition to @DETACHED@ ** @(RTL3c)@ If the connection state enters the @SUSPENDED@ state, then an @ATTACHING@ or @ATTACHED@ channel state will transition to @SUSPENDED@ +** @(RTL3h)@ If the connection enters the @CLOSED@ or @SUSPENDED@ state, then any channels in the @DETACHING@ state should transition to @DETACHED@. ** @(RTL3d)@ If the connection state enters the @CONNECTED@ state, any channels in the @ATTACHING@, @ATTACHED@, or @SUSPENDED@ states should be transitioned to @ATTACHING@ (other than ones already in that state), and initiate an @RTL4c@ attach sequence. (If the attach operation times out and the channel was previously @SUSPENDED@, it should return to the @SUSPENDED@ state, see "RTL4f":#RTL4f). The connection should also process any messages that had been queued per @RTL6c2@ (it should do this immediately, without waiting for the attach operations to finish). ** @(RTL3f)@ If the connection enters the @CONNECTED@ state then any channels in the @DETACHING@ state should initiate an "@RTL5d@":#RTL5d detach sequence. * @(RTL11)@ If a channel enters the @DETACHED@, @SUSPENDED@ or @FAILED@ state, then all presence actions that are still queued for send on that channel per "RTP16b":#RTP16b should be deleted from the queue, and any callback passed to the corresponding presence method invocation should be called with an @ErrorInfo@ indicating the failure @@ -684,17 +686,18 @@ h3(#realtime-channel). RealtimeChannel ** @(RTL4l)@ If the user has specified a @modes@ array in the @ChannelOptions@ ("@TB2d@":#TB2d), it must be encoded as a bitfield per "@TR3@":#TR3 and set as the @flags@ field of the @ATTACH@ @ProtocolMessage@. (For the avoidance of doubt, when multiple different spec items require flags to be set in the @ATTACH@, the final @flags@ field should be the bitwise OR of them all) ** @(RTL4m)@ On receipt of an @ATTACHED@, the client library should decode the @flags@ into an array of @ChannelMode@ s (that is, the same format as @ChannelOptions.modes@) and expose it as a read-only @modes@ field of the @RealtimeChannel@ (or a @getModes()@ method where that is more idiomatic). This should only contain @ChannelMode@ s: it should not contain flags which are not modes (see "@TB2d@":#TB2d) * @(RTL5)@ @RealtimeChannel#detach@ function: -** @(RTL5a)@ If the channel state is @INITIALIZED@ or @DETACHED@ nothing is done +** @(RTL5a)@ If the channel state is @INITIALIZED@ or @DETACHED@ nothing is done, and the optional callback should be called with no argument ** @(RTL5i)@ If the channel is in a pending state @DETACHING@ or @ATTACHING@, do the detach operation after the completion of the pending request -** @(RTL5b)@ If the channel state is @FAILED@, the @detach@ request results in an error -** @(RTL5j)@ If the channel state is @SUSPENDED@, the @detach@ request transitions the channel immediately to the @DETACHED@ state -** @(RTL5g)@ If the connection state is @CLOSING@ or @FAILED@, the @detach@ request results in an error +** @(RTL5b)@ If the channel state is @FAILED@, the @detach@ request results in an error (that is, depending on what is idiomatic for the language it should either throw an error or call its optional callback with an error) +** @(RTL5j)@ If the channel state is @SUSPENDED@, the @detach@ request transitions the channel immediately to the @DETACHED@ state, and calls the optional callback with no argument +** @(RTL5g)@ If the connection state is @CLOSING@ or @FAILED@, the @detach@ request results in an error (that is, depending on what is idiomatic for the language it should either throw an error or call its optional callback with an error) ** @(RTL5h)@ If the connection state is @CONNECTING@ or @DISCONNECTED@, do the detach operation once the connection state is @CONNECTED@ ** @(RTL5d)@ Otherwise (i.e. the channel state is @ATTACHED@ and the connection — given this channel state, "@RTL3@":#RTL3, and the above connection state conditions — is thus @CONNECTED@) a @DETACH@ ProtocolMessage is sent to the server, the state transitions to @DETACHING@ and the channel becomes @DETACHED@ when the confirmation @DETACHED@ ProtocolMessage is received ** @(RTL5f)@ This clause has been replaced by "@RTL5l@":#RTL5l. It was valid up to and including specification version @TBD@. -** @(RTL5l)@ Once a @DETACH@ @ProtocolMessage@ is sent, if a @DETACHED@ @ProtocolMessage@ is not received within "@realtimeRequestTimeout@":#TO3l11, the detach request should be treated as though it has failed and the channel will transition back to @ATTACHED@ +** @(RTL5l)@ Once a @DETACH@ @ProtocolMessage@ is sent, if a @DETACHED@ @ProtocolMessage@ is not received within "@realtimeRequestTimeout@":#TO3l11, the detach request should be treated as though it has failed, and the channel will transition back to @ATTACHED@ and call its optional callback with an error ** @(RTL5k)@ If the channel receives an @ATTACHED@ message while in the @DETACHING@ or @DETACHED@ state, it should send a new @DETACH@ message and remain in (or transition to) the @DETACHING@ state -** @(RTL5e)@ If the language permits, a callback can be provided that is called when the channel is detached successfully or the detach fails and the @ErrorInfo@ error is passed as an argument to the callback +** @(RTL5e)@ This clause has been replaced by "@RTL5m@":#RTL5m. It was valid up to and including specification version @TBD@. +** @(RTL5m)@ A callback (or other language-idiomatic equivalent) can be provided that is called by either "@RTL5a@":#RTL5a, "@RTL5b@":#RTL5b, "@RTL5j@":#RTL5j, "@RTL5g@":#RTL5g or "@RTL5l@":#RTL5l, or when, after the channel transitions to @DETACHING@ in "@RTL5d@":#RTL5d and the request does not subsequently time out in "@RTL5l@":#RTL5l, the channel next moves to one of the @DETACHED@ or @FAILED@ states. In the case of @DETACHED@ the callback is called with no argument. In the case of @FAILED@ it is called with an @ErrorInfo@ corresponding to the @ChannelStateChange.reason@ of the state change to indicate that the detach has failed. * @(RTL6)@ @RealtimeChannel#publish@ function: ** @(RTL6a)@ Messages are encoded in the same way as the @RestChannel#publish@ method, and "RSL1g":#RSL1g (size limit) applies similarly *** @(RTL6a1)@ "RSL1k":#RSL1k (@idempotentRestPublishing@ option), "RSL1j1":#RSL1j1 (idempotent publishing test), and "RSL1l":#RSL1l (@publish(Message, params)@ form) do not apply to realtime publishes @@ -778,7 +781,7 @@ h3(#realtime-channel). RealtimeChannel ** @(RTL22b)@ The method must allow for matching only messages which do not have @extras.ref@. ** @(RTL22c)@ The listener must only execute if all provided criteria are met. ** @(RTL22d)@ The method should use the @MessageFilter@ object if possible and idiomatic for the language. -* @(RTL24)@ @RealtimeChannel#errorReason@ attribute is an optional @ErrorInfo@ object which is set by the library when an error occurs on the channel, as described by "RTN11d":#RTN11d, "RTL3a":#RTL3a, "RTL4g":#RTL4g, "RTL14":#RTL14. +* @(RTL24)@ @RealtimeChannel#errorReason@ attribute is an optional @ErrorInfo@ object which is set by the library when an error occurs on the channel, as described by "RTN11d":#RTN11d, "RTL3a":#RTL3a, "RTL3g":#RTL3g, "RTL4g":#RTL4g, "RTL14":#RTL14. * @(RTL25)@ @RealtimeChannel#whenState@ function: ** @(RTL25a)@ If the channel is already in the given state, calls the listener with a `null` argument. ** @(RTL25b)@ Else, calls @#once@ with the given state and listener.