Skip to content

Commit

Permalink
Features spec: implement only emitting a leave if there was an existi…
Browse files Browse the repository at this point in the history
…ng member

per #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)
  • Loading branch information
SimonWoolf committed Nov 28, 2024
1 parent e3e1be2 commit bec5b7f
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 7 deletions.
2 changes: 1 addition & 1 deletion meta.yaml
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
21 changes: 15 additions & 6 deletions textile/features.textile
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 @<sync sequence id>:<cursor value>@. 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 @<sync sequence id>:@
Expand Down

0 comments on commit bec5b7f

Please sign in to comment.