From c4a23ab2084b12961c260cd117367a6b5588d41b Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Wed, 13 Nov 2024 19:11:17 +0000 Subject: [PATCH] Features spec: implement only emitting a leave if there was an existing member per https://github.com/ably/specification/issues/211 The actual behaviour change here is small (only whether non-sync leaves where there is no matching member currently present should emit a leave event), but I found the current structure of this spec section quite confusing (why are broadcast and members-map add/remove requirements defined separately, despite having the same requirements?), so ended up rewriting it. (Without deprecating the old spec items since this isn't actually a behaviour change except in that one case) --- meta.yaml | 2 +- textile/features.textile | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/meta.yaml b/meta.yaml index 623003d76..eeea3ebae 100644 --- a/meta.yaml +++ b/meta.yaml @@ -1,7 +1,7 @@ versions: # Must conform to Semantic Versioning (https://semver.org/). # Should never need to use a pre-release suffix. - specification: 3.0.0 + specification: 3.0.1 # Must be an Integer value. # Previously, prior to version 2 (i.e. versions 0.8, 1.0, 1.1 and 1.2) it was a Decimal value. diff --git a/textile/features.textile b/textile/features.textile index 6d7ff4c79..c2ac4dc3f 100644 --- a/textile/features.textile +++ b/textile/features.textile @@ -226,7 +226,7 @@ h3(#rest-auth). Auth ** @(RSA15b)@ If the clientId from @TokenDetails@ or @connectionDetails@ contains only a wildcard string '*', then the client is permitted to be either unidentified (i.e. authorised to act on behalf of any clientId) or identified by providing a @clientId@ when communicating with Ably ** @(RSA15c)@ Following an auth request which uses a @TokenDetails@ or @TokenRequest@ object that contains an incompatible @clientId@, the library should in the case of Realtime transition the connection state to @FAILED@, and in the case of REST result in an appropriate error response * @(RSA5)@ TTL for new tokens is specified in milliseconds. If the user-provided @tokenParams@ does not specify a TTL, the TTL field should be omitted from the @tokenRequest@, and Ably will supply a token with a TTL of 60 minutes. See "TK2a":#TK2a -* @(RSA6)@ The @capability@ for new tokens is JSON stringified. If If the user-provided @tokenParams@ does not specify capabilities, the @capability@ field should be omitted from the @tokenRequest@, and Ably will supply a token with the capabilities of the underlying key. See "TK2b":#TK2b +* @(RSA6)@ The @capability@ for new tokens is JSON stringified. If the user-provided @tokenParams@ does not specify capabilities, the @capability@ field should be omitted from the @tokenRequest@, and Ably will supply a token with the capabilities of the underlying key. See "TK2b":#TK2b * @(RSA7)@ @clientId@ and authenticated clients: ** @(RSA7d)@ If @clientId@ is provided in @ClientOptions@ and @RSA4@ indicates that token auth is to be used, the @clientId@ field in the @TokenParams@ (@TK2c@) should be set to that @clientId@ when requesting a token ** @(RSA7e)@ If a valid (per @RSA7c@) @clientId@ is provided in @ClientOptions@, then: @@ -769,16 +769,25 @@ h3(#realtime-presence). RealtimePresence * @(RTP1)@ When a channel @ATTACHED@ @ProtocolMessage@ is received, the @ProtocolMessage@ may contain a @HAS_PRESENCE@ bit flag indicating that it will perform a presence sync, see "TR3":#TR3 . (Note that this does not imply that there are definitely members present, only that there may be; the sync may be empty). If the flag is 1, the server will shortly perform a @SYNC@ operation as described in "RTP18":#RTP18 . If that flag is 0 or there is no @flags@ field, the presence map should be considered in sync immediately with no members present on the channel * @(RTP2)@ A @PresenceMap@ should be used to maintain a list of members present on a channel. Broadly, this is is a map of "memberKeys":#TP3h to presence messages, all with @PRESENT@ actions (during a sync there may also be ones with an @ABSENT@ action, see "RTP2f":#RTP2f). -** @(RTP2a)@ All incoming presence messages must be compared for newness with the matching member already in the @PresenceMap@, if one exists, where "matching" means they share the same @memberKey@ (or equivalently, they share both @connectionId@ and @clientId@) +** @(RTP2a)@ All incoming presence messages must be compared for newness with the matching member already in the @PresenceMap@, if one exists, where "matching" means they share the same @memberKey@ (or equivalently, they share both @connectionId@ and @clientId@). If there is an existing message but it is @RTP2b@-newer than the incoming message, the incoming message should be discarded without taking any action ** @(RTP2b)@ To compare for newness: *** @(RTP2b1)@ If either presence message has a @connectionId@ which is not an initial substring of its @id@, compare them by @timestamp@ numerically. (This will be the case when one of them is a 'synthesized leave' event sent by realtime to indicate a connection disconnected unexpectedly 15s ago. Such messages will have an @id@ that does not correspond to its @connectionId@, as it wasn't actually published by that connection) **** @(RTP2b1a)@ If the timestamps compare equal, the newly-incoming message is considered newer than the existing one *** @(RTP2b2)@ Else split the @id@ of both presence messages (which will be of the form @connid:msgSerial:index@, e.g. @aaaaaa:0:0@) on the separator @:@, and parse the latter two as integers. Compare them first by @msgSerial@ numerically, then (if @msgSerial@ is equal) by @index@ numerically, larger being newer in both cases ** @(RTP2c)@ As there are no guarantees that during a @SYNC@ operation presence events will arrive in order, all presence messages from a @SYNC@ must also be compared for newness in the same way as they would from a @PRESENCE@ -** @(RTP2d)@ When a presence message with an action of @ENTER@, @UPDATE@, or @PRESENT@ arrives, it should be added to the presence map with the action set to @PRESENT@ -** @(RTP2e)@ If a @SYNC@ is not in progress, then when a presence message with an action of @LEAVE@ arrives, that @memberKey@ should be deleted from the presence map, if present -** @(RTP2f)@ If a @SYNC@ is in progress, then when a presence message with an action of @LEAVE@ arrives, it should be stored in the presence map with the action set to @ABSENT@. When the @SYNC@ completes, any @ABSENT@ members should be deleted from the presence map. (This is because in a @SYNC@, we might receive a @LEAVE@ before the corresponding @ENTER@). -** @(RTP2g)@ Any incoming presence message that passes the newness check should be emitted on the @RealtimePresence@ object, with an event name set to its original action. Note: this action may not be the same one that it will have when stored in the presence map. For example: an incoming presence message with an @ENTER@ action will be emitted as an @enter@ event, and the emitted presence message will have its action set to @ENTER@. However, it will be stored in the presence map with a @PRESENT@ action. +** @(RTP2d)@ When a presence message with an action of @ENTER@, @UPDATE@, or @PRESENT@ arrives (if it satisfies the @RTP2a@ newness check): +*** @(RTP2d1)@ It must be emitted to @RealtimePresence@ subscribers (with its original presence action, and an event name set to the stringified form of that action) +*** @(RTP2d2)@ It must be added to the presence map with the action set to @PRESENT@ +*** @(RTP2e)@ This clause has been replaced by @RTP2h1@. It was valid up to and including specification version @3.0.0@. +*** @(RTP2f)@ This clause has been replaced by @RTP2h2@. It was valid up to and including specification version @3.0.0@. +** @(RTP2h)@ When a presence message with an action of @LEAVE@ arrives, if and only if there is a member with a matching @memberKey@ currently in the presence map (and the incoming event satisfies the @RTP2b@ newness check against it): +*** @(RTP2h1)@ If a @SYNC@ is not in progress: +**** @(RTP2h1a)@ The incoming message must be emitted to @RealtimePresence@ subscribers (with an event name set to the stringified form of the @LEAVE@ action) +**** @(RTP2h1b)@ That member must be deleted from the presence map +*** @(RTP2h2)@ If a @SYNC@ is in progress: +**** @(RTP2h2a)@ The incoming message must be stored in the presence map with the action set to @ABSENT@ +**** @(RTP2h2b)@ When the @SYNC@ completes, then all @ABSENT@ members in the presence map must be deleted. (No leave events should be emitted other than those required by @RTP19@) +** @(RTP2g)@ This clause has been replaced by @RTP2d1@ and @RTP2h1a@. It was valid up to and including specification version @3.0.0@. * @(RTP18)@ The realtime system reserves the right to initiate a sync of the presence members at any point once a channel is attached. A server initiated sync provides Ably with a means to send a complete list of members present on the channel at any point ** @(RTP18a)@ The client library determines that a new sync has started whenever a @SYNC@ @ProtocolMessage@ is received with a @channel@ attribute and a new sync sequence identifier in the @channelSerial@ attribute. The @channelSerial@ is used as the sync cursor and is a two-part identifier @:@. If a new sequence identifier is sent from Ably, then the client library must consider that to be the start of a new sync sequence and any previous in-flight sync should be discarded ** @(RTP18b)@ The sync operation for that sequence identifier has completed once the cursor is empty; that is, when the @channelSerial@ looks like @:@