diff --git a/.gitignore b/.gitignore index c2f574e92e..bf76cf1838 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ tmp output -crash.log +/crash.log .sass* -config/jsbin_config.yaml -package-lock.json +/config/jsbin_config.yaml +/package-lock.json *.DS_Store tags diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 322a2f34d6..35c05ece95 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -13,6 +13,7 @@ jump_to: - Auth#rest-auth - Channels#rest-channels - Channel#rest-channel + - Plugins#plugins - Presence#rest-presence - Encryption#rest-encryption - Forwards compatibility#rest-compatibility @@ -52,7 +53,7 @@ h2(#test-guidelines). Test guidelines * @(G1)@ Every test should be executed using all supported protocols (i.e. JSON and "MessagePack":http://msgpack.org/ if supported). This includes both sending & receiving data * @(G2)@ All tests by default are run against a special Ably sandbox environment. This environment allows apps to be provisioned without any authentication that can then be used for client library testing. Bear in mind that all apps created in the sandbox environment are automatically deleted after 60 minutes and have low limits to prevent abuse. Apps are configured by sending a @POST@ request to @https://sandbox-rest.ably.io/apps@ with a JSON body that specifies the keys and their associated capabilities, channel namespace rules and any presence fixture data that is required; see "ably-common test-app-setup.json":https://github.com/ably/ably-common/blob/master/test-resources/test-app-setup.json. Presence fixture data is necessary for the REST library presence tests as there is no way to register presence on a channel in the REST library * @(G3)@ Testing statistics can be tricky due to timing issues and slow test suites as a result of sending requests to generate statistics. As such, we provide a special stats endpoint in our sandbox environment that allows stats to be injected into our metrics system so that stats tests can make predictable assertions. To create stats you must send an authenticated @POST@ request to the stats JSON to @https://sandbox-rest.ably.io/stats@ with the stats data you wish to create. See the "Javascript stats fixture":https://github.com/ably/ably-js/blob/4e65d4e13eb8750a375b9511e4dd059092c0e481/spec/rest/stats.test.js#L8-L51 and "setup helper":https://github.com/ably/ably-js/blob/4e65d4e13eb8750a375b9511e4dd059092c0e481/spec/common/modules/testapp_manager.js#L158-L182 as an example -* @(G4)@ This spec defines API version 1.1. A client library must identify to Ably the version of the spec it uses in all requests and connections, per "RSC7a":#RSC7a and "RTN2f":#RTN2f. The spec it uses is defined as the latest API version for which the library implements all spec items relating to the wire protocol +* @(G4)@ This spec defines API version 1.2. A client library must identify to Ably the version of the spec it uses in all requests and connections, per "RSC7a":#RSC7a and "RTN2f":#RTN2f. The spec it uses is defined as the latest API version for which the library implements all spec items relating to the wire protocol h2(#rest). REST client library @@ -75,9 +76,10 @@ h3(#restclient). RestClient *** @(RSC6b4)@ @unit@ is the period for which the stats will be aggregated by, values supported are @minute@, @hour@, @day@ or @month@; if omitted the unit defaults to the REST API default (@minute@) * @(RSC16)@ @RestClient#time@ function sends a get request to @rest.ably.io/time@ and returns the server time in milliseconds since epoch or as a Date/Time object where suitable * @(RSC7)@ Sends REST requests over HTTP and HTTPS to the REST endpoint @rest.ably.io@ -** @(RSC7a)@ The header @X-Ably-Version: 1.1@ must be included in all REST requests to the Ably endpoint +** @(RSC7a)@ The header @X-Ably-Version: 1.2@ must be included in all REST requests to the Ably endpoint ** @(RSC7b)@ The header @X-Ably-Lib: [lib][.optional variant]?-[version]@ should be included in all REST requests to the Ably endpoint where @[lib]@ is the name of the library such as @js@ for @ably-js@, @[.optional variant]@ is an optional library variant, such as @laravel@ for the @php@ library, which is always delimited with a period such as @php.laravel@, and where @[version]@ is the full client library version using "Semver":http://semver.org/ such as @1.0.2@. For example, the 1.0.0 version of the Javascript library would use the header @X-Ably-Lib: js-1.0.0@ *** @(RSC7b1)@ When it is not possible to send the @X-Ably-Lib@ header, such as for @JSONP@ requests, the library version should be sent as a query param such as @lib=js-1.0.0@ +** @(RSC7c)@ If the @addRequestIds@ client option is enabled, every REST request to Ably should include in a @request_id@ query string parameter a random string obtained by url-safe base64-encoding a sequence of at least 9 bytes obtained from a source of randomness. This request ID must remain the same if a request is retried to a fallback host per @RSC15@. Any log messages associated with the request should include the request ID. If the request fails, the request ID must be included in the @ErrorInfo@ returned to the user. * @(RSC18)@ If @ClientOptions#tls@ is true, then all communication is over HTTPS. If false, all communication is over HTTP however "Basic Auth":https://en.wikipedia.org/wiki/Basic_access_authentication over HTTP will result in an error as private keys cannot be submitted over an insecure connection. See @Auth@ below * @(RSC8)@ Supports two protocols: ** @(RSC8a)@ "MessagePack":http://msgpack.org/ binary protocol (this is the default for environments having a suitable level or support for binary data) @@ -207,9 +209,9 @@ h3(#rest-channels). Channels * @(RSN1)@ @Channels@ is a collection of @Channel@ objects accessible through @Rest#channels@ * @(RSN2)@ Methods should exist to check if a channel exists or iterate through the existing channels * @(RSN3)@ @Channels#get@ function: -** @(RSN3a)@ Creates a new @Channel@ object for the specified channel if none exists, or returns the existing channel. @ChannelOptions@ can be specified when instancing a new @Channel@ +** @(RSN3a)@ Creates a new @Channel@ object for the specified channel if none exists, or returns the existing channel. @ChannelOptions@ can be provided in an optional second argument ** @(RSN3b)@ If options are provided, the options are set on the @Channel@ -** @(RSN3c)@ Accessing an existing @Channel@ with options in the form @Channels#get(channel, options)@ will update the options on the channel and then return the existing @Channel@ object +** @(RSN3c)@ Accessing an existing @Channel@ with options in the form @Channels#get(channel, options)@ will update the options on the channel and then return the existing @Channel@ object. (Note that this is soft-deprecated and may be removed in a future release, so should not be implemented in new client libraries. The supported way to update a set of @ChannelOptions@ is @Channel#setOptions@) * @(RSN4)@ @Channels#release@ function: ** @(RSN4a)@ Releases the channel resource i.e. it's deleted and can then be garbage collected @@ -234,8 +236,8 @@ h3(#rest-channel). Channel ** @(RSL1i)@ If the total size of the message or (if publishing an array) messages, calculated per "TO3l8":#TO3l8, exceeds the @maxMessageSize@, then the client library should reject the publish and indicate an error with code 40009 ** @(RSL1j)@ When @Message@ objects are provided, any valid @Message@ attribute (that is, an attribute specified in "TM2":#TM2) that is supplied by the caller must be included in the encoded message. (This does not mean it must be included _unaltered_; for example the @data@ and @encoding@ will be subject to processing per "RSL4":#RSL4) ** @(RSL1k)@ Idempotent publishing via REST is supported by populating the @id@ attribute of @Message@ instances passed to @publish()@: -*** @(RSL1k1)@ Idempotent publishing via library-generated @Message@ @id@s is supported if @idempotentRestPublishing@ (see "TO3n":#TO3n) is enabled and one or more @Message@ instances are passed to @publish()@ and all @Message@s have an empty @id@ attribute. The library generates a base @id@ string by base64-encoding a sequence of at least 9 bytes obtained from a source of randomness. Each individual @Message@ in the set of messages to be published is assigned a unique @id@ of the form <base id>:<serial> (where @serial@ is the zero-based index into the set). -*** @(RSL1k2)@ Idempotent publishing via client-supplied @Message@ @id@s is supported where a single @Message@ is passed to @publish()@ and it contains a non-empty @id@. The @id@ is preserved on sending the message. +*** @(RSL1k1)@ Idempotent publishing via library-generated @Message@ @id@ s is supported if @idempotentRestPublishing@ (see "TO3n":#TO3n) is enabled and one or more @Message@ instances are passed to @publish()@ and all @Message@ s have an empty @id@ attribute. The library generates a base @id@ string by base64-encoding a sequence of at least 9 bytes obtained from a source of randomness. Each individual @Message@ in the set of messages to be published is assigned a unique @id@ of the form <base id>:<serial> (where @serial@ is the zero-based index into the set). +*** @(RSL1k2)@ Idempotent publishing via client-supplied @Message@ @id@ s is supported where a single @Message@ is passed to @publish()@ and it contains a non-empty @id@. The @id@ is preserved on sending the message. *** @(RSL1k3)@ If more than one @Message@ is passed to @publish()@ and one or more of those messages contains a non-empty @id@ attribute, then all message ids (present or absent) are preserved on sending the batch of messages. *** @(RSL1k4)@ An explicit test for idempotency of publishes with library-generated ids shall exist that simulates an error response to a successful publish of a batch of messages, expects an automatic retry by the library, and verifies that the net outcome is that the batch is published only once. *** @(RSL1k5)@ An explicit test for idempotency of publishes with client-supplied ids shall exist that involves multiple explicit publish requests for a given message and verifies that the net outcome is that the message is published only once. @@ -269,7 +271,14 @@ h3(#rest-channel). Channel ** @(RSL6a)@ All messages received will be decoded automatically based on the @encoding@ field and the payloads will be converted into the format they were originally sent using i.e. binary, string, or JSON *** @(RSL6a1)@ A set of tests must exist to ensure that the client library provides data encoding & decoding interoperability with other client libraries. The tests must use the "set of predefined interoperability message fixtures":https://github.com/ably/ably-common/blob/master/test-resources/messages-encoding.json to 1) publish a raw message to the REST API using the JSON transport and subscribe to the message using Realtime to ensure the @data@ attribute matches the fixture; 2) publish a message using the REST client library and retrieve the raw message using the history REST API using the JSON transport ensuring the @data@ matches the fixture; 3) perform the client library operation using both @JSON@ and @MsgPack@ transports. For reference, see the "Ruby":https://github.com/ably/ably-ruby/pull/94 and "iOS":https://github.com/ably/ably-cocoa/pull/459 implementations *** @(RSL6a2)@ A set of tests must exist to ensure that the client library provides interoperability for the @extras@ field which is a JSON-encodable object (ie a value that represents a JSON @object@ value and supports serialization to and from JSON text). The test, at a minimum, should publish a message with an @extras@ object such as @{"push":[{"title":"Testing"}]}@ and ensure it is received with an equivalent JSON-encodable object -** @(RSL6b)@ If, for example, incompatible encryption details are provided or invalid Base64 is detected in the message payload, an error message will be sent to the logger, but the message will still be delivered with last successful decoding and the @encoding@ field. For example, if a message had a decoding of "utf-8/cipher+aes-128-cbc/base64", and the payload was successfully Base64 decoded but the payload could not be encrypted because the @CipherParam@ details were not configured, the message would be delivered with a binary payload and an @encoding@ with the value "utf-8/cipher+aes-128-cbc" +** @(RSL6b)@ If, for example, incompatible encryption details are provided or invalid Base64 is detected in the message payload, an error message will be sent to the logger, but the message will still be delivered with last successful decoding and the @encoding@ field. For example, if a message had a decoding of "utf-8/cipher+aes-128-cbc/base64", and the payload was successfully Base64 decoded but the payload could not be decrypted because the @CipherParam@ details were not configured, the message would be delivered with a binary payload and an @encoding@ with the value "utf-8/cipher+aes-128-cbc". Additional steps need to be taken if decoding failed on "vcdiff" encoding; see "RTL18":#RTL18 +* @(RSL7)@ @Channel#setOptions@ takes a @ChannelOptions@ object and sets or updates the stored channel options, then indicates success + +h3(#plugins). Plugins +* @(PC1)@ Specific client library features that are not commonly used may be supplied as independent libraries, as plugins, in order to avoid excessively bloating the client library. Although such plugins are packaged as independent libraries, they are still considered logically to be part of the client library code and, as such, may be tightly coupled with the client library implementation. The client library can be assumed to be aware of the plugin specific type and capabilities, and such plugins may by design be coupled to a specific version of the client library. +* @(PC2)@ No generic plugin interface is specified, and therefore there is no common API exposed by all plugins. However, for type-safety, the opaque interface @Plugin@ should be used in strongly-typed languages as the type of the @ClientOptions.plugins@ collection as per "TO3o":#TO3o. +* @(PC3)@ A plugin provided with the @PluginType@ enum key value of @vcdiff@ should be capable of decoding "vcdiff"-encoded messages. It must implement the the @VCDiffDecoder@ interface and the client library must be able to use it by casting it to this interface. +** @(PC3a)@ The base argument of the @VCDiffDecoder.decode@ method should receive the stored base payload of the last message on a channel as specified by "RTL19":#RTL19. If the base payload is a string it should be encoded to binary using UTF-8 before being passed as base argument of the @VCDiffDecoder.decode@ method. h3(#rest-presence). Presence * @(RSP1)@ Presence object is associated with a single channel and is accessible through @Channel#presence@ @@ -456,9 +465,10 @@ h3(#realtime-channels). Channels * @(RTS1)@ @Channels@ is a collection of @Channel@ objects accessible through @Realtime#channels@ * @(RTS2)@ Methods should exist to check if a channel exists or iterate through the existing channels * @(RTS3)@ @Channels#get@ function: -** @(RTS3a)@ Creates a new @Channel@ object for the specified channel if none exists, or returns the existing channel. @ChannelOptions@ can be specified when instancing a new @Channel@ +** @(RTS3a)@ Creates a new @Channel@ object for the specified channel if none exists, or returns the existing channel. @ChannelOptions@ can be provided in an optional second argument ** @(RTS3b)@ If options are provided, the options are set on the @Channel@ when creating a new @Channel@ -** @(RTS3c)@ Accessing an existing @Channel@ with options in the form @Channels#get(channel, options)@ will update the options on the channel and then return the existing @Channel@ object +** @(RTS3c)@ Accessing an existing @Channel@ with options in the form @Channels#get(channel, options)@ will update the options on the channel and then return the existing @Channel@ object. (Note that this is soft-deprecated and may be removed in a future release, so should not be implemented in new client libraries. The supported way to update a set of @ChannelOptions@ is @Channel#setOptions@) +*** @(RTS3c1)@ If a new set of @ChannelOptions@ is supplied to @Channels#get@ that would trigger a reattachment of the channel if supplied to @Channel#setOptions@ per "@RTL16a@":#RTL16a, it must raise an error, informing the user that they must use @Channel#setOptions@ instead * @(RTS4)@ @Channels#release@ function: ** @(RTS4a)@ Detaches the channel and then releases the channel resource i.e. it's deleted and can then be garbage collected @@ -490,6 +500,13 @@ h3(#realtime-channel). Channel ** @(RTL4f)@ Once an @ATTACH@ @ProtocolMessage@ is sent, if an @ATTACHED@ @ProtocolMessage@ is not received within the "default realtime request timeout":#defaults, 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)@ If the language permits, a callback can be provided that is called when the channel is attached successfully or the attach fails and the @ErrorInfo@ error is passed as an argument to the callback ** @(RTL4e)@ If the user does not have sufficient permissions to attach to the channel, the channel will transition to @FAILED@ and set the @Channel#errorReason@ +** @(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 +*** @(RTL4j1)@ A 'clean attach' is an attach attempt where the channel has either not previously been attached or has been explicitly detached since the last time it was attached. Note that this is not purely a function of the immediate previous channel state. An example implementation would be to set the flag from an @attachResume@ private boolean variable on the channel, that starts out set to @false@, is set to @true@ when the channel moves to the @ATTACHED@ state, and set to @false@ when the channel moves to the @DETACHING@ or @FAILED@ states. +*** @(RTL4j2)@ The client library can test that the flag is being correctly encoded (and that @RTL4k@ channel params are correctly included) by publishing a message on a channel, then having another two clients attach to that channel both specifying a @rewind@ channel param of @"1"@, one of which has the @ATTACH_RESUME@ flag forcibly set, other doesn't. The client without the flag set should receive the previously-published message once the attach succeeds; the one with that flag set should not +** @(RTL4k)@ If the user has specified a non-empty @params@ object in the @ChannelOptions@ ("@TB2c@":#TB2c), it must be included in a @params@ field of the @ATTACH@ @ProtocolMessage@ +*** @(RTL4k1)@ If any channel parameters are requested (which may be through the @params@ field of the @ATTACH@ message or some other way opaque to the client library), the @ATTACHED@ (and any subsequent @ATTACHED@ s) will include a @params@ property (also a @Dict@) containing the subset of those params that the server has recognised and validated. This should be exposed as a read-only @params@ field of the @Channel@ (or a @getParams()@ method where that is more idiomatic). An @ATTACHED@ message with no @params@ property must be treated as equivalent to a @params@ of @{}@ (that is, @Channel.params@ should be set to the empty dict) +** @(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 @Channel@ (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)@ @Channel#detach@ function: ** @(RTL5a)@ If the channel state is @INITIALIZED@ or @DETACHED@ nothing is done ** @(RTL5i)@ If the channel is in a pending state @DETACHING@ or @ATTACHING@, do the detach operation after the completion of the pending request @@ -557,6 +574,19 @@ h3(#realtime-channel). Channel ** @(RTL13b)@ If the attempt to re-attach fails, or if the channel was already in the @ATTACHING@ state, the channel will transition to the @SUSPENDED@ state and the error will be emitted in the @ChannelStateChange@ event. An attempt to re-attach the channel automatically will then be made after the period defined in @ClientOptions#channelRetryTimeout@. When re-attaching the channel, the channel will transition to the @ATTACHING@ state. If that request to attach fails i.e. it times out or a @DETACHED@ message is received, then the process described here in @RTL13b@ will be repeated, indefinitely ** @(RTL13c)@ If the connection is no longer @CONNECTED@, then the automatic attempts to re-attach the channel described in "RTL13b":#RTL13b must be cancelled as any implicit channel state changes subsequently will be covered by "RTL3":#RTL3 * @(RTL14)@ If an @ERROR ProtocolMessage@ is received for this channel (the channel attribute matches this channel's name), then the channel should immediately transition to the FAILED state, and the @Channel.errorReason@ should be set +* @(RTL16)@ @Channel#setOptions@ takes a @ChannelOptions@ object and sets or updates the stored channel options. +** @(RTL16a)@ If the user has provided either @ChannelOptions.params@ or @ChannelOptions.modes@ and the channel is in either the @attached@ or @attaching@ state, @Channel#setOptions@ sends an @ATTACH@ message to the server with the params & modes encoded per "@RTL4@":#RTL4, and indicates success once the server has replied with an @ATTACHED@ (or indicates failure if the channel becomes detached or failed before that happens, as with @Channel#attach@); else it indicates success immediately +* @(RTL17)@ No messages should be passed to subscribers if the channel is in any state other than @ATTACHED@. +* @(RTL18)@ Given "vcdiff"-encoded deltas are applied to the previous message published on a channel, when a "vcdiff" encoding fails to be decoded, it makes it impossible for a client to apply subsequent deltas received from that point of failure forward. As such, the client must automatically execute the following recovery procedure in lieu of "RTL7e":#"RTL7e": +** @(RTL18a)@ Log error with code 40018 +** @(RTL18b)@ Discard the message +** @(RTL18c)@ Send an @ATTACH@ @ProtocolMessage@ with the @channelSerial@ set to the previous message to the message for which "vcdiff" decoding failed to the server, transitioning the channel state to @ATTACHING@, and waiting for a confirmation @ATTACHED@, as per @RTL4c@ and @RTL4f@. @ChannelStateChange.reason@ should be set to @ErrorInfo@ object with with code 40018. +* @(RTL19)@ The data payload of the last message on each channel must be stored at all times since it will be needed to decode any subsequent message that has a "vcdiff" encoding step. The stored value is the "base payload" of the most recent message; this is the @data@ member of the message, in string or binary form, once all application-level encoding steps have been applied. The base payload is derived initially by processing a non-delta message; the processing and bookkeeping rules are as follows: +** @(RTL19a)@ When processing any message (whether a delta or a full message), if the message @encoding@ string ends in @base64@, the message @data@ should be base64-decoded (and the @encoding@ string modified accordingly per "RSL6":#RSL6). +** @(RTL19b)@ In the case of a non-delta message, the resulting @data@ value is stored as the base payload. +** @(RTL19c)@ In the case of a delta message with a @vcdiff@ @encoding@ step, the @vcdiff@ decoder must be used to decode the base payload of the of delta message, applying that delta to the stored base payload. The direct result of that vcdiff delta application, before performing any further decoding steps, is stored as the updated base payload. +* @(RTL20)@ The @id@ of the last received message on each channel must be stored along with the base payload. When processing a delta message (i.e. one whose @encoding@ contains @vcdiff@ step) the stored last message @id@ must be compared against the delta reference @id@, indicated in the @Message.extras.delta.from@ field of the delta message. If the delta reference @id@ of the received delta message does not equal the stored @id@ corresponding to the base payload, the message decoding must fail. The recovery procedure from "RTL18":#RTL18 must be executed. +* @(RTL21)@ The messages in the @messages@ array of a @ProtocolMessage@ should each be decoded in ascending order of their index in the array. h3(#realtime-presence). Presence @@ -1096,7 +1126,7 @@ h4. Message ** @(TM2g)@ @name@ string ** @(TM2d)@ @data@ string, buffer or JSON-encodable object or array ** @(TM2e)@ @encoding@ string -** @(TM2i)@ @extras@ JSON-encodable object, used to contain any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays. The extras field is provided to contain message metadata and/or ancillary payloads in support of specific functionality, e.g. push. Each of these supported extensions is documented separately; for 1.1 the only supported extension is @push@, via the @extras.push@ member. The processing of any other members is undefined +** @(TM2i)@ @extras@ JSON-encodable object, used to contain any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays. The @extras@ field is provided to contain message metadata and/or ancillary payloads in support of specific functionality, e.g. push. Each of these supported extensions is documented separately; for 1.1 the only supported extension is @push@, via the @extras.push@ member; 1.2 adds the @delta@ extension which is of type @DeltaExtras@. The processing of any other members is undefined ** @(TM2f)@ @timestamp@ time in milliseconds since epoch. If a message received from Ably does not contain a @timestamp@, it should be set to the @timestamp@ of the encapsulating @ProtocolMessage@ * @(TM3)@ @fromEncoded@ and @fromEncodedArray@ are alternative constructors that take an (already deserialized) @Message@-like object (or array of such objects), and optionally a @channelOptions@, and return a @Message@ (or array of such @Messages@) that's decoded and decrypted as specified in @RSL6@, using the cipher in the @channelOptions@ if the message is encrypted, with any residual transforms (ones that the library cannot decode or decrypt) left in the @encoding@ property per @RSL6b@. This is intended for users receiving messages other than from a REST or Realtime channel (for example, from a queue), to avoid them having to parse the @encoding@ string themselves. @@ -1126,6 +1156,7 @@ h4. ProtocolMessage ** @(TR3c)@ 2: @RESUMED@ ** @(TR3d)@ 3: @HAS_LOCAL_PRESENCE@ ** @(TR3e)@ 4: @TRANSIENT@ +** @(TR3f)@ 5: @ATTACH_RESUME@ ** @(TR3q)@ 16: @PRESENCE@ ** @(TR3r)@ 17: @PUBLISH@ ** @(TR3s)@ 18: @SUBSCRIBE@ @@ -1141,11 +1172,12 @@ h4. ProtocolMessage ** @(TR4o)@ @connectionDetails@ @ConnectionDetails@ object - provides details on the constraints or defaults for the connection such as max message size, client ID or connection state TTL ** @(TR4g)@ @count@ integer ** @(TR4h)@ @error@ @ErrorInfo@ object -** @(TR4i)@ @flags@ integer. Contains one or more of the following bit flags: @HAS_PRESENCE: 1@, @HAS_BACKLOG: 2@, @RESUMED: 4@ +** @(TR4i)@ @flags@ integer. Contains one or more of the bit flags specified in @TR3@ ** @(TR4j)@ @msgSerial@ long ** @(TR4k)@ @messages@ Array of @Message@ objects ** @(TR4l)@ @presence@ Array of @PresenceMessage@ objects ** @(TR4m)@ @timestamp@ time in milliseconds since epoch +** @(TR4q)@ @params@ @Dict@ key-value pairs h4. PaginatedResult @@ -1230,7 +1262,7 @@ h4. Stats h4. ErrorInfo -* @(TI1)@ Provides a generic Ably @ErrorInfo@ object that contains Ably @code@, @statusCode@ (analogous to HTTP status code), @message@ and @cause@ attributes +* @(TI1)@ Provides a generic Ably @ErrorInfo@ object that contains Ably @code@, @statusCode@ (analogous to HTTP status code), @message@, optional @cause@, optional @href@, and optional @requestId@ (@RSC7c@) attributes * @(TI2)@ Errors returned from the Ably server are compatible with the @ErrorInfo@ structure and should result in errors that inherit from @ErrorInfo@ * @(TI3)@ "Ably-common":https://github.com/ably/ably-common should be included as a submodule so that "consistent error codes":https://github.com/ably/ably-common/blob/master/protocol/errors.json can be used * @(TI4)@ Ably may additionally include a @href@ attribute with a string value. This is included for REST responses to provide a URL for customers to find more help on the error code @@ -1326,6 +1358,8 @@ h4. ClientOptions **** @(TO3l8e)@ The size of a @null@ or omitted property is zero *** @(TO3l9)@ @maxFrameSize@ integer - default 524288 (512KiB). The maximum size of a single POST body or "WebSocket":/concepts/websockets frame. This is mostly only relevant for `Rest#request` (e.g. for batch publishes), since publishes will hit the @maxMessageSize@ limit before this *** @(TO3l10)@ @fallbackRetryTimeout@ integer - default 600000 (10 minutes). (After a failed request to the default endpoint, followed by a successful request to a fallback endpoint), the period in milliseconds before HTTP requests are retried against the default endpoint +** @(TO3o)@ @plugins@ @Dict@ A map between a @PluginType@ and a @Plugin@ object. The client library might downcast a @Plugin@ to particular plugin type. +** @(TO3p)@ @addRequestIds@ boolean - defaults to false. If true, @RSC7c@ applies h4(#token-params). TokenParams * @(TK1)@ A class providing parameters of a token request. These params are used when invoking @Auth#authorize@, @Auth#requestToken@ and @Auth#createTokenRequest@ @@ -1354,6 +1388,8 @@ h4. ChannelOptions ** @(TB2b)@ @cipher@, which is either: *** @(TB2b1)@ A @CipherParams@ instance, or *** @(TB2b2)@ an options hash (or language equivalent) consisting of any subset of @CipherParams@ fields that includes a @key@. In this case, the client library should call @getDefaultParams@, passing it the options hash, to obtain a @CipherParams@ instance +** @(TB2c)@ @params@ (for realtime client libraries only) a @Dict@ of key/value pairs +** @(TB2d)@ @modes@ (for realtime client libraries only) an array of @ChannelMode@ s, where a @ChannelMode@ is a member of an enum containing the names of those children of "@TR3@":#TR3 whose value is ≥16 (or see the IDL below) * @(TB3)@ The client lib may optionally provide an alternative constructor @withCipherKey@ for ChannelOptions that takes a @key@ only. (This must be differentiated from the normal constructor such that it is clear that the value being passed in is a key). (This is intended for languages where requiring a hash map is unidiomatic) h4. CipherParams @@ -1484,6 +1520,7 @@ class ClientOptions: tlsPort: Int default 443 // TO3k5 useBinaryProtocol: Bool default true // TO3f transportParams: [String: Stringifiable]? // RTC1f + addRequestIds: Bool default false // TO3p // configurable retry and failure defaults disconnectedRetryTimeout: Duration default 15s // TO3l1 suspendedRetryTimeout: Duration default 30s // RTN14d, TO3l2 @@ -1494,6 +1531,7 @@ class ClientOptions: httpMaxRetryDuration: Duration default 15s // TO3l6 maxMessageSize: Int default 65536 // TO3l8 maxFrameSize: Int default 524288 // TO3l8 + plugins: Dict // TO3o class AuthOptions: // RSA8e authCallback: ((TokenParams) -> io (String | TokenDetails | TokenRequest | JsonObject))? // RSA4a, RSA4, TO3j5, AO2b @@ -1557,6 +1595,8 @@ class RestChannel: publish(Message, params?: Dict) => io // RSL1 publish([Message], params?: Dict) => io // RSL1 publish(name: String?, data: Data?) => io // RSL1 + setOptions(options: ChannelOptions) => io // RSL7 - note asynchronous return value for + // compatibility with RealtimeChannel#setOptions; not required for REST-only libraries // Only on platforms that support receiving notifications: push: PushChannel // RSH4 @@ -1568,6 +1608,8 @@ class RealtimeChannel: presence: RealtimePresence // RTL9 properties: ChannelProperties // CP1, RTL15 push: PushChannel + modes: readonly [ChannelMode] // RTL4m + params: readonly Dict // RTL4k1 attach() => io // RTL4d detach() => io // RTL5e history( @@ -1585,6 +1627,7 @@ class RealtimeChannel: unsubscribe() // RTL8a, RTE5 unsubscribe((Message) ->) // RTL8a unsubscribe(String, (Message) ->) // RTL8a + setOptions(options: ChannelOptions) => io // RTL16 class PushChannel: subscribeDevice() => io // RSH7a @@ -1606,6 +1649,12 @@ enum ChannelEvent: embeds ChannelState UPDATE // RTL2g +enum ChannelMode: // TB2d + PRESENCE + PUBLISH + SUBSCRIBE + PRESENCE_SUBSCRIBE + class ChannelStateChange: current: ChannelState // RTL2a, RTL2b event: ChannelEvent // TH5 @@ -1616,6 +1665,8 @@ class ChannelStateChange: class ChannelOptions: +withCipherKey(key: Binary | String)? -> ChannelOptions // TB3 cipher: (CipherParams | Params)? // RSL5a, TB2b + params?: Dict // TB2c + modes?: [ChannelMode] // TB2d class CipherParams: algorithm: String default "AES" // TZ2a @@ -1721,12 +1772,13 @@ class ProtocolMessage: connectionSerial: Int? // RTN10c, TR4f count: Int? // TR4g error: ErrorInfo? // RTN15c2, TR4h - flags: .HAS_PRESENCE & .HAS_BACKLOG & .RESUMED ? // RTP1, TR3, TR4i, RTL2f + flags: Int? // TR4i; bitfield containing zero or more boolean flags specified in TR3 id: String? // TR4b messages: [Message]? // TR4k msgSerial: Int? // RTN7b, TR4j presence: [PresenceMessage]? // TR4l timestamp: Time? // TR4m + params: Dict? // TR4q, RTL4k enum ProtocolMessageAction: HEARTBEAT // TR2 @@ -1897,6 +1949,7 @@ class ErrorInfo: message: String // TI1 cause: ErrorInfo? // TI1 statusCode: Int // TI1 + requestId: String? // RSC7c class EventEmitter: on((Data...) ->) // RTE4 @@ -1923,11 +1976,26 @@ class HttpPaginatedResponse // RSC19b errorCode: Int // HP6 errorMessage: String // HP7 headers: Dict // HP8 + +class Plugin // PC2 + // Empty class/interface. Plugins are not expected to share any common interface. + // An opaque base interface type for plugins is defined for type-safety in statically-typed languages. + +enum PluginType + "vcdiff" + +class VCDiffDecoder + decode([byte] delta, [byte] base) -> [byte] + +class DeltaExtras + from: String // the id of the message the delta was generated from + format: String //the delta format. Only vcdiff is supported as at API version 1.2 ``` h2(#old-specs). Old specs Use the version navigation to view older versions. References to diffs for each version are maintained below: +* v1.1 deprecated in Mar 2020. "View 1.1 → 1.2 changes":https://github.com/ably/docs/blob/master/content/client-lib-development-guide/versions/features-1-1__1-2.diff * v1.0 deprecated in Jan 2019. "View 1.0 → 1.1 changes":https://github.com/ably/docs/blob/master/content/client-lib-development-guide/versions/features-1-0__1-1.diff * v0.8 deprecated in Jan 2017. "View 0.8 → 1.0 changes":https://github.com/ably/docs/blob/master/content/client-lib-development-guide/versions/features-0-8__1-0.diff diff --git a/content/client-lib-development-guide/versions/features-1-1__1-2.diff b/content/client-lib-development-guide/versions/features-1-1__1-2.diff new file mode 100644 index 0000000000..143ed4b51f --- /dev/null +++ b/content/client-lib-development-guide/versions/features-1-1__1-2.diff @@ -0,0 +1,285 @@ +diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile +index 322a2f34..7b837728 100644 +--- a/content/client-lib-development-guide/features.textile ++++ b/content/client-lib-development-guide/features.textile +@@ -13,6 +13,7 @@ jump_to: + - Auth#rest-auth + - Channels#rest-channels + - Channel#rest-channel ++ - Plugins#plugins + - Presence#rest-presence + - Encryption#rest-encryption + - Forwards compatibility#rest-compatibility +@@ -52,7 +53,7 @@ h2(#test-guidelines). Test guidelines + * @(G1)@ Every test should be executed using all supported protocols (i.e. JSON and "MessagePack":http://msgpack.org/ if supported). This includes both sending & receiving data + * @(G2)@ All tests by default are run against a special Ably sandbox environment. This environment allows apps to be provisioned without any authentication that can then be used for client library testing. Bear in mind that all apps created in the sandbox environment are automatically deleted after 60 minutes and have low limits to prevent abuse. Apps are configured by sending a @POST@ request to @https://sandbox-rest.ably.io/apps@ with a JSON body that specifies the keys and their associated capabilities, channel namespace rules and any presence fixture data that is required; see "ably-common test-app-setup.json":https://github.com/ably/ably-common/blob/master/test-resources/test-app-setup.json. Presence fixture data is necessary for the REST library presence tests as there is no way to register presence on a channel in the REST library + * @(G3)@ Testing statistics can be tricky due to timing issues and slow test suites as a result of sending requests to generate statistics. As such, we provide a special stats endpoint in our sandbox environment that allows stats to be injected into our metrics system so that stats tests can make predictable assertions. To create stats you must send an authenticated @POST@ request to the stats JSON to @https://sandbox-rest.ably.io/stats@ with the stats data you wish to create. See the "Javascript stats fixture":https://github.com/ably/ably-js/blob/4e65d4e13eb8750a375b9511e4dd059092c0e481/spec/rest/stats.test.js#L8-L51 and "setup helper":https://github.com/ably/ably-js/blob/4e65d4e13eb8750a375b9511e4dd059092c0e481/spec/common/modules/testapp_manager.js#L158-L182 as an example +-* @(G4)@ This spec defines API version 1.1. A client library must identify to Ably the version of the spec it uses in all requests and connections, per "RSC7a":#RSC7a and "RTN2f":#RTN2f. The spec it uses is defined as the latest API version for which the library implements all spec items relating to the wire protocol ++* @(G4)@ This spec defines API version 1.2. A client library must identify to Ably the version of the spec it uses in all requests and connections, per "RSC7a":#RSC7a and "RTN2f":#RTN2f. The spec it uses is defined as the latest API version for which the library implements all spec items relating to the wire protocol + + h2(#rest). REST client library + +@@ -75,9 +76,10 @@ h3(#restclient). RestClient + *** @(RSC6b4)@ @unit@ is the period for which the stats will be aggregated by, values supported are @minute@, @hour@, @day@ or @month@; if omitted the unit defaults to the REST API default (@minute@) + * @(RSC16)@ @RestClient#time@ function sends a get request to @rest.ably.io/time@ and returns the server time in milliseconds since epoch or as a Date/Time object where suitable + * @(RSC7)@ Sends REST requests over HTTP and HTTPS to the REST endpoint @rest.ably.io@ +-** @(RSC7a)@ The header @X-Ably-Version: 1.1@ must be included in all REST requests to the Ably endpoint ++** @(RSC7a)@ The header @X-Ably-Version: 1.2@ must be included in all REST requests to the Ably endpoint + ** @(RSC7b)@ The header @X-Ably-Lib: [lib][.optional variant]?-[version]@ should be included in all REST requests to the Ably endpoint where @[lib]@ is the name of the library such as @js@ for @ably-js@, @[.optional variant]@ is an optional library variant, such as @laravel@ for the @php@ library, which is always delimited with a period such as @php.laravel@, and where @[version]@ is the full client library version using "Semver":http://semver.org/ such as @1.0.2@. For example, the 1.0.0 version of the Javascript library would use the header @X-Ably-Lib: js-1.0.0@ + *** @(RSC7b1)@ When it is not possible to send the @X-Ably-Lib@ header, such as for @JSONP@ requests, the library version should be sent as a query param such as @lib=js-1.0.0@ ++** @(RSC7c)@ If the @addRequestIds@ client option is enabled, every REST request to Ably should include in a @request_id@ query string parameter a random string obtained by url-safe base64-encoding a sequence of at least 9 bytes obtained from a source of randomness. This request ID must remain the same if a request is retried to a fallback host per @RSC15@. Any log messages associated with the request should include the request ID. If the request fails, the request ID must be included in the @ErrorInfo@ returned to the user. + * @(RSC18)@ If @ClientOptions#tls@ is true, then all communication is over HTTPS. If false, all communication is over HTTP however "Basic Auth":https://en.wikipedia.org/wiki/Basic_access_authentication over HTTP will result in an error as private keys cannot be submitted over an insecure connection. See @Auth@ below + * @(RSC8)@ Supports two protocols: + ** @(RSC8a)@ "MessagePack":http://msgpack.org/ binary protocol (this is the default for environments having a suitable level or support for binary data) +@@ -207,9 +209,9 @@ h3(#rest-channels). Channels + * @(RSN1)@ @Channels@ is a collection of @Channel@ objects accessible through @Rest#channels@ + * @(RSN2)@ Methods should exist to check if a channel exists or iterate through the existing channels + * @(RSN3)@ @Channels#get@ function: +-** @(RSN3a)@ Creates a new @Channel@ object for the specified channel if none exists, or returns the existing channel. @ChannelOptions@ can be specified when instancing a new @Channel@ ++** @(RSN3a)@ Creates a new @Channel@ object for the specified channel if none exists, or returns the existing channel. @ChannelOptions@ can be provided in an optional second argument + ** @(RSN3b)@ If options are provided, the options are set on the @Channel@ +-** @(RSN3c)@ Accessing an existing @Channel@ with options in the form @Channels#get(channel, options)@ will update the options on the channel and then return the existing @Channel@ object ++** @(RSN3c)@ Accessing an existing @Channel@ with options in the form @Channels#get(channel, options)@ will update the options on the channel and then return the existing @Channel@ object. (Note that this is soft-deprecated and may be removed in a future release, so should not be implemented in new client libraries. The supported way to update a set of @ChannelOptions@ is @Channel#setOptions@) + * @(RSN4)@ @Channels#release@ function: + ** @(RSN4a)@ Releases the channel resource i.e. it's deleted and can then be garbage collected + +@@ -234,8 +236,8 @@ h3(#rest-channel). Channel + ** @(RSL1i)@ If the total size of the message or (if publishing an array) messages, calculated per "TO3l8":#TO3l8, exceeds the @maxMessageSize@, then the client library should reject the publish and indicate an error with code 40009 + ** @(RSL1j)@ When @Message@ objects are provided, any valid @Message@ attribute (that is, an attribute specified in "TM2":#TM2) that is supplied by the caller must be included in the encoded message. (This does not mean it must be included _unaltered_; for example the @data@ and @encoding@ will be subject to processing per "RSL4":#RSL4) + ** @(RSL1k)@ Idempotent publishing via REST is supported by populating the @id@ attribute of @Message@ instances passed to @publish()@: +-*** @(RSL1k1)@ Idempotent publishing via library-generated @Message@ @id@s is supported if @idempotentRestPublishing@ (see "TO3n":#TO3n) is enabled and one or more @Message@ instances are passed to @publish()@ and all @Message@s have an empty @id@ attribute. The library generates a base @id@ string by base64-encoding a sequence of at least 9 bytes obtained from a source of randomness. Each individual @Message@ in the set of messages to be published is assigned a unique @id@ of the form <base id>:<serial> (where @serial@ is the zero-based index into the set). +-*** @(RSL1k2)@ Idempotent publishing via client-supplied @Message@ @id@s is supported where a single @Message@ is passed to @publish()@ and it contains a non-empty @id@. The @id@ is preserved on sending the message. ++*** @(RSL1k1)@ Idempotent publishing via library-generated @Message@ @id@ s is supported if @idempotentRestPublishing@ (see "TO3n":#TO3n) is enabled and one or more @Message@ instances are passed to @publish()@ and all @Message@ s have an empty @id@ attribute. The library generates a base @id@ string by base64-encoding a sequence of at least 9 bytes obtained from a source of randomness. Each individual @Message@ in the set of messages to be published is assigned a unique @id@ of the form <base id>:<serial> (where @serial@ is the zero-based index into the set). ++*** @(RSL1k2)@ Idempotent publishing via client-supplied @Message@ @id@ s is supported where a single @Message@ is passed to @publish()@ and it contains a non-empty @id@. The @id@ is preserved on sending the message. + *** @(RSL1k3)@ If more than one @Message@ is passed to @publish()@ and one or more of those messages contains a non-empty @id@ attribute, then all message ids (present or absent) are preserved on sending the batch of messages. + *** @(RSL1k4)@ An explicit test for idempotency of publishes with library-generated ids shall exist that simulates an error response to a successful publish of a batch of messages, expects an automatic retry by the library, and verifies that the net outcome is that the batch is published only once. + *** @(RSL1k5)@ An explicit test for idempotency of publishes with client-supplied ids shall exist that involves multiple explicit publish requests for a given message and verifies that the net outcome is that the message is published only once. +@@ -269,7 +271,14 @@ h3(#rest-channel). Channel + ** @(RSL6a)@ All messages received will be decoded automatically based on the @encoding@ field and the payloads will be converted into the format they were originally sent using i.e. binary, string, or JSON + *** @(RSL6a1)@ A set of tests must exist to ensure that the client library provides data encoding & decoding interoperability with other client libraries. The tests must use the "set of predefined interoperability message fixtures":https://github.com/ably/ably-common/blob/master/test-resources/messages-encoding.json to 1) publish a raw message to the REST API using the JSON transport and subscribe to the message using Realtime to ensure the @data@ attribute matches the fixture; 2) publish a message using the REST client library and retrieve the raw message using the history REST API using the JSON transport ensuring the @data@ matches the fixture; 3) perform the client library operation using both @JSON@ and @MsgPack@ transports. For reference, see the "Ruby":https://github.com/ably/ably-ruby/pull/94 and "iOS":https://github.com/ably/ably-cocoa/pull/459 implementations + *** @(RSL6a2)@ A set of tests must exist to ensure that the client library provides interoperability for the @extras@ field which is a JSON-encodable object (ie a value that represents a JSON @object@ value and supports serialization to and from JSON text). The test, at a minimum, should publish a message with an @extras@ object such as @{"push":[{"title":"Testing"}]}@ and ensure it is received with an equivalent JSON-encodable object +-** @(RSL6b)@ If, for example, incompatible encryption details are provided or invalid Base64 is detected in the message payload, an error message will be sent to the logger, but the message will still be delivered with last successful decoding and the @encoding@ field. For example, if a message had a decoding of "utf-8/cipher+aes-128-cbc/base64", and the payload was successfully Base64 decoded but the payload could not be encrypted because the @CipherParam@ details were not configured, the message would be delivered with a binary payload and an @encoding@ with the value "utf-8/cipher+aes-128-cbc" ++** @(RSL6b)@ If, for example, incompatible encryption details are provided or invalid Base64 is detected in the message payload, an error message will be sent to the logger, but the message will still be delivered with last successful decoding and the @encoding@ field. For example, if a message had a decoding of "utf-8/cipher+aes-128-cbc/base64", and the payload was successfully Base64 decoded but the payload could not be decrypted because the @CipherParam@ details were not configured, the message would be delivered with a binary payload and an @encoding@ with the value "utf-8/cipher+aes-128-cbc". Additional steps need to be taken if decoding failed on "vcdiff" encoding; see "RTL18":#RTL18 ++* @(RSL7)@ @Channel#setOptions@ takes a @ChannelOptions@ object and sets or updates the stored channel options, then indicates success ++ ++h3(#plugins). Plugins ++* @(PC1)@ Specific client library features that are not commonly used may be supplied as independent libraries, as plugins, in order to avoid excessively bloating the client library. Although such plugins are packaged as independent libraries, they are still considered logically to be part of the client library code and, as such, may be tightly coupled with the client library implementation. The client library can be assumed to be aware of the plugin specific type and capabilities, and such plugins may by design be coupled to a specific version of the client library. ++* @(PC2)@ No generic plugin interface is specified, and therefore there is no common API exposed by all plugins. However, for type-safety, the opaque interface @Plugin@ should be used in strongly-typed languages as the type of the @ClientOptions.plugins@ collection as per "TO3o":#TO3o. ++* @(PC3)@ A plugin provided with the @PluginType@ enum key value of @vcdiff@ should be capable of decoding "vcdiff"-encoded messages. It must implement the the @VCDiffDecoder@ interface and the client library must be able to use it by casting it to this interface. ++** @(PC3a)@ The base argument of the @VCDiffDecoder.decode@ method should receive the stored base payload of the last message on a channel as specified by "RTL19":#RTL19. If the base payload is a string it should be encoded to binary using UTF-8 before being passed as base argument of the @VCDiffDecoder.decode@ method. + + h3(#rest-presence). Presence + * @(RSP1)@ Presence object is associated with a single channel and is accessible through @Channel#presence@ +@@ -456,9 +465,10 @@ h3(#realtime-channels). Channels + * @(RTS1)@ @Channels@ is a collection of @Channel@ objects accessible through @Realtime#channels@ + * @(RTS2)@ Methods should exist to check if a channel exists or iterate through the existing channels + * @(RTS3)@ @Channels#get@ function: +-** @(RTS3a)@ Creates a new @Channel@ object for the specified channel if none exists, or returns the existing channel. @ChannelOptions@ can be specified when instancing a new @Channel@ ++** @(RTS3a)@ Creates a new @Channel@ object for the specified channel if none exists, or returns the existing channel. @ChannelOptions@ can be provided in an optional second argument + ** @(RTS3b)@ If options are provided, the options are set on the @Channel@ when creating a new @Channel@ +-** @(RTS3c)@ Accessing an existing @Channel@ with options in the form @Channels#get(channel, options)@ will update the options on the channel and then return the existing @Channel@ object ++** @(RTS3c)@ Accessing an existing @Channel@ with options in the form @Channels#get(channel, options)@ will update the options on the channel and then return the existing @Channel@ object. (Note that this is soft-deprecated and may be removed in a future release, so should not be implemented in new client libraries. The supported way to update a set of @ChannelOptions@ is @Channel#setOptions@) ++*** @(RTS3c1)@ If a new set of @ChannelOptions@ is supplied to @Channels#get@ that would trigger a reattachment of the channel if supplied to @Channel#setOptions@ per "@RTL16a@":#RTL16a, it must raise an error, informing the user that they must use @Channel#setOptions@ instead + * @(RTS4)@ @Channels#release@ function: + ** @(RTS4a)@ Detaches the channel and then releases the channel resource i.e. it's deleted and can then be garbage collected + +@@ -490,6 +500,13 @@ h3(#realtime-channel). Channel + ** @(RTL4f)@ Once an @ATTACH@ @ProtocolMessage@ is sent, if an @ATTACHED@ @ProtocolMessage@ is not received within the "default realtime request timeout":#defaults, 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)@ If the language permits, a callback can be provided that is called when the channel is attached successfully or the attach fails and the @ErrorInfo@ error is passed as an argument to the callback + ** @(RTL4e)@ If the user does not have sufficient permissions to attach to the channel, the channel will transition to @FAILED@ and set the @Channel#errorReason@ ++** @(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 ++*** @(RTL4j1)@ A 'clean attach' is an attach attempt where the channel has either not previously been attached or has been explicitly detached since the last time it was attached. Note that this is not purely a function of the immediate previous channel state. An example implementation would be to set the flag from an @attachResume@ private boolean variable on the channel, that starts out set to @false@, is set to @true@ when the channel moves to the @ATTACHED@ state, and set to @false@ when the channel moves to the @DETACHING@ or @FAILED@ states. ++*** @(RTL4j2)@ The client library can test that the flag is being correctly encoded (and that @RTL4k@ channel params are correctly included) by publishing a message on a channel, then having another two clients attach to that channel both specifying a @rewind@ channel param of @"1"@, one of which has the @ATTACH_RESUME@ flag forcibly set, other doesn't. The client without the flag set should receive the previously-published message once the attach succeeds; the one with that flag set should not ++** @(RTL4k)@ If the user has specified a non-empty @params@ object in the @ChannelOptions@ ("@TB2c@":#TB2c), it must be included in a @params@ field of the @ATTACH@ @ProtocolMessage@ ++*** @(RTL4k1)@ If any channel parameters are requested (which may be through the @params@ field of the @ATTACH@ message or some other way opaque to the client library), the @ATTACHED@ (and any subsequent @ATTACHED@ s) will include a @params@ property (also a @Dict@) containing the subset of those params that the server has recognised and validated. This should be exposed as a read-only @params@ field of the @Channel@ (or a @getParams()@ method where that is more idiomatic). An @ATTACHED@ message with no @params@ property must be treated as equivalent to a @params@ of @{}@ (that is, @Channel.params@ should be set to the empty dict) ++** @(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 @Channel@ (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)@ @Channel#detach@ function: + ** @(RTL5a)@ If the channel state is @INITIALIZED@ or @DETACHED@ nothing is done + ** @(RTL5i)@ If the channel is in a pending state @DETACHING@ or @ATTACHING@, do the detach operation after the completion of the pending request +@@ -557,6 +574,19 @@ h3(#realtime-channel). Channel + ** @(RTL13b)@ If the attempt to re-attach fails, or if the channel was already in the @ATTACHING@ state, the channel will transition to the @SUSPENDED@ state and the error will be emitted in the @ChannelStateChange@ event. An attempt to re-attach the channel automatically will then be made after the period defined in @ClientOptions#channelRetryTimeout@. When re-attaching the channel, the channel will transition to the @ATTACHING@ state. If that request to attach fails i.e. it times out or a @DETACHED@ message is received, then the process described here in @RTL13b@ will be repeated, indefinitely + ** @(RTL13c)@ If the connection is no longer @CONNECTED@, then the automatic attempts to re-attach the channel described in "RTL13b":#RTL13b must be cancelled as any implicit channel state changes subsequently will be covered by "RTL3":#RTL3 + * @(RTL14)@ If an @ERROR ProtocolMessage@ is received for this channel (the channel attribute matches this channel's name), then the channel should immediately transition to the FAILED state, and the @Channel.errorReason@ should be set ++* @(RTL16)@ @Channel#setOptions@ takes a @ChannelOptions@ object and sets or updates the stored channel options. ++** @(RTL16a)@ If the user has provided either @ChannelOptions.params@ or @ChannelOptions.modes@ and the channel is in either the @attached@ or @attaching@ state, @Channel#setOptions@ sends an @ATTACH@ message to the server with the params & modes encoded per "@RTL4@":#RTL4, and indicates success once the server has replied with an @ATTACHED@ (or indicates failure if the channel becomes detached or failed before that happens, as with @Channel#attach@); else it indicates success immediately ++* @(RTL17)@ No messages should be passed to subscribers if the channel is in any state other than @ATTACHED@. ++* @(RTL18)@ Given "vcdiff"-encoded deltas are applied to the previous message published on a channel, when a "vcdiff" encoding fails to be decoded, it makes it impossible for a client to apply subsequent deltas received from that point of failure forward. As such, the client must automatically execute the following recovery procedure in lieu of "RTL7e":#"RTL7e": ++** @(RTL18a)@ Log error with code 40018 ++** @(RTL18b)@ Discard the message ++** @(RTL18c)@ Send an @ATTACH@ @ProtocolMessage@ with the @channelSerial@ set to the previous message to the message for which "vcdiff" decoding failed to the server, transitioning the channel state to @ATTACHING@, and waiting for a confirmation @ATTACHED@, as per @RTL4c@ and @RTL4f@. @ChannelStateChange.reason@ should be set to @ErrorInfo@ object with with code 40018. ++* @(RTL19)@ The data payload of the last message on each channel must be stored at all times since it will be needed to decode any subsequent message that has a "vcdiff" encoding step. The stored value is the "base payload" of the most recent message; this is the @data@ member of the message, in string or binary form, once all application-level encoding steps have been applied. The base payload is derived initially by processing a non-delta message; the processing and bookkeeping rules are as follows: ++** @(RTL19a)@ When processing any message (whether a delta or a full message), if the message @encoding@ string ends in @base64@, the message @data@ should be base64-decoded (and the @encoding@ string modified accordingly per "RSL6":#RSL6). ++** @(RTL19b)@ In the case of a non-delta message, the resulting @data@ value is stored as the base payload. ++** @(RTL19c)@ In the case of a delta message with a @vcdiff@ @encoding@ step, the @vcdiff@ decoder must be used to decode the base payload of the of delta message, applying that delta to the stored base payload. The direct result of that vcdiff delta application, before performing any further decoding steps, is stored as the updated base payload. ++* @(RTL20)@ The @id@ of the last received message on each channel must be stored along with the base payload. When processing a delta message (i.e. one whose @encoding@ contains @vcdiff@ step) the stored last message @id@ must be compared against the delta reference @id@, indicated in the @Message.extras.delta.from@ field of the delta message. If the delta reference @id@ of the received delta message does not equal the stored @id@ corresponding to the base payload, the message decoding must fail. The recovery procedure from "RTL18":#RTL18 must be executed. ++* @(RTL21)@ The messages in the @messages@ array of a @ProtocolMessage@ should each be decoded in ascending order of their index in the array. + + h3(#realtime-presence). Presence + +@@ -1096,7 +1126,7 @@ h4. Message + ** @(TM2g)@ @name@ string + ** @(TM2d)@ @data@ string, buffer or JSON-encodable object or array + ** @(TM2e)@ @encoding@ string +-** @(TM2i)@ @extras@ JSON-encodable object, used to contain any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays. The extras field is provided to contain message metadata and/or ancillary payloads in support of specific functionality, e.g. push. Each of these supported extensions is documented separately; for 1.1 the only supported extension is @push@, via the @extras.push@ member. The processing of any other members is undefined ++** @(TM2i)@ @extras@ JSON-encodable object, used to contain any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays. The @extras@ field is provided to contain message metadata and/or ancillary payloads in support of specific functionality, e.g. push. Each of these supported extensions is documented separately; for 1.1 the only supported extension is @push@, via the @extras.push@ member; 1.2 adds the @delta@ extension which is of type @DeltaExtras@. The processing of any other members is undefined + ** @(TM2f)@ @timestamp@ time in milliseconds since epoch. If a message received from Ably does not contain a @timestamp@, it should be set to the @timestamp@ of the encapsulating @ProtocolMessage@ + * @(TM3)@ @fromEncoded@ and @fromEncodedArray@ are alternative constructors that take an (already deserialized) @Message@-like object (or array of such objects), and optionally a @channelOptions@, and return a @Message@ (or array of such @Messages@) that's decoded and decrypted as specified in @RSL6@, using the cipher in the @channelOptions@ if the message is encrypted, with any residual transforms (ones that the library cannot decode or decrypt) left in the @encoding@ property per @RSL6b@. This is intended for users receiving messages other than from a REST or Realtime channel (for example, from a queue), to avoid them having to parse the @encoding@ string themselves. + +@@ -1126,6 +1156,7 @@ h4. ProtocolMessage + ** @(TR3c)@ 2: @RESUMED@ + ** @(TR3d)@ 3: @HAS_LOCAL_PRESENCE@ + ** @(TR3e)@ 4: @TRANSIENT@ ++** @(TR3f)@ 5: @ATTACH_RESUME@ + ** @(TR3q)@ 16: @PRESENCE@ + ** @(TR3r)@ 17: @PUBLISH@ + ** @(TR3s)@ 18: @SUBSCRIBE@ +@@ -1141,11 +1172,12 @@ h4. ProtocolMessage + ** @(TR4o)@ @connectionDetails@ @ConnectionDetails@ object - provides details on the constraints or defaults for the connection such as max message size, client ID or connection state TTL + ** @(TR4g)@ @count@ integer + ** @(TR4h)@ @error@ @ErrorInfo@ object +-** @(TR4i)@ @flags@ integer. Contains one or more of the following bit flags: @HAS_PRESENCE: 1@, @HAS_BACKLOG: 2@, @RESUMED: 4@ ++** @(TR4i)@ @flags@ integer. Contains one or more of the bit flags specified in @TR3@ + ** @(TR4j)@ @msgSerial@ long + ** @(TR4k)@ @messages@ Array of @Message@ objects + ** @(TR4l)@ @presence@ Array of @PresenceMessage@ objects + ** @(TR4m)@ @timestamp@ time in milliseconds since epoch ++** @(TR4q)@ @params@ @Dict@ key-value pairs + + h4. PaginatedResult + +@@ -1230,7 +1262,7 @@ h4. Stats + + h4. ErrorInfo + +-* @(TI1)@ Provides a generic Ably @ErrorInfo@ object that contains Ably @code@, @statusCode@ (analogous to HTTP status code), @message@ and @cause@ attributes ++* @(TI1)@ Provides a generic Ably @ErrorInfo@ object that contains Ably @code@, @statusCode@ (analogous to HTTP status code), @message@, optional @cause@, optional @href@, and optional @requestId@ (@RSC7c@) attributes + * @(TI2)@ Errors returned from the Ably server are compatible with the @ErrorInfo@ structure and should result in errors that inherit from @ErrorInfo@ + * @(TI3)@ "Ably-common":https://github.com/ably/ably-common should be included as a submodule so that "consistent error codes":https://github.com/ably/ably-common/blob/master/protocol/errors.json can be used + * @(TI4)@ Ably may additionally include a @href@ attribute with a string value. This is included for REST responses to provide a URL for customers to find more help on the error code +@@ -1326,6 +1358,8 @@ h4. ClientOptions + **** @(TO3l8e)@ The size of a @null@ or omitted property is zero + *** @(TO3l9)@ @maxFrameSize@ integer - default 524288 (512KiB). The maximum size of a single POST body or "WebSocket":/concepts/websockets frame. This is mostly only relevant for `Rest#request` (e.g. for batch publishes), since publishes will hit the @maxMessageSize@ limit before this + *** @(TO3l10)@ @fallbackRetryTimeout@ integer - default 600000 (10 minutes). (After a failed request to the default endpoint, followed by a successful request to a fallback endpoint), the period in milliseconds before HTTP requests are retried against the default endpoint ++** @(TO3o)@ @plugins@ @Dict@ A map between a @PluginType@ and a @Plugin@ object. The client library might downcast a @Plugin@ to particular plugin type. ++** @(TO3p)@ @addRequestIds@ boolean - defaults to false. If true, @RSC7c@ applies + + h4(#token-params). TokenParams + * @(TK1)@ A class providing parameters of a token request. These params are used when invoking @Auth#authorize@, @Auth#requestToken@ and @Auth#createTokenRequest@ +@@ -1354,6 +1388,8 @@ h4. ChannelOptions + ** @(TB2b)@ @cipher@, which is either: + *** @(TB2b1)@ A @CipherParams@ instance, or + *** @(TB2b2)@ an options hash (or language equivalent) consisting of any subset of @CipherParams@ fields that includes a @key@. In this case, the client library should call @getDefaultParams@, passing it the options hash, to obtain a @CipherParams@ instance ++** @(TB2c)@ @params@ (for realtime client libraries only) a @Dict@ of key/value pairs ++** @(TB2d)@ @modes@ (for realtime client libraries only) an array of @ChannelMode@ s, where a @ChannelMode@ is a member of an enum containing the names of those children of "@TR3@":#TR3 whose value is ≥16 (or see the IDL below) + * @(TB3)@ The client lib may optionally provide an alternative constructor @withCipherKey@ for ChannelOptions that takes a @key@ only. (This must be differentiated from the normal constructor such that it is clear that the value being passed in is a key). (This is intended for languages where requiring a hash map is unidiomatic) + + h4. CipherParams +@@ -1484,6 +1520,7 @@ class ClientOptions: + tlsPort: Int default 443 // TO3k5 + useBinaryProtocol: Bool default true // TO3f + transportParams: [String: Stringifiable]? // RTC1f ++ addRequestIds: Bool default false // TO3p + // configurable retry and failure defaults + disconnectedRetryTimeout: Duration default 15s // TO3l1 + suspendedRetryTimeout: Duration default 30s // RTN14d, TO3l2 +@@ -1494,6 +1531,7 @@ class ClientOptions: + httpMaxRetryDuration: Duration default 15s // TO3l6 + maxMessageSize: Int default 65536 // TO3l8 + maxFrameSize: Int default 524288 // TO3l8 ++ plugins: Dict // TO3o + + class AuthOptions: // RSA8e + authCallback: ((TokenParams) -> io (String | TokenDetails | TokenRequest | JsonObject))? // RSA4a, RSA4, TO3j5, AO2b +@@ -1557,6 +1595,8 @@ class RestChannel: + publish(Message, params?: Dict) => io // RSL1 + publish([Message], params?: Dict) => io // RSL1 + publish(name: String?, data: Data?) => io // RSL1 ++ setOptions(options: ChannelOptions) => io // RSL7 - note asynchronous return value for ++ // compatibility with RealtimeChannel#setOptions; not required for REST-only libraries + + // Only on platforms that support receiving notifications: + push: PushChannel // RSH4 +@@ -1568,6 +1608,8 @@ class RealtimeChannel: + presence: RealtimePresence // RTL9 + properties: ChannelProperties // CP1, RTL15 + push: PushChannel ++ modes: readonly [ChannelMode] // RTL4m ++ params: readonly Dict // RTL4k1 + attach() => io // RTL4d + detach() => io // RTL5e + history( +@@ -1585,6 +1627,7 @@ class RealtimeChannel: + unsubscribe() // RTL8a, RTE5 + unsubscribe((Message) ->) // RTL8a + unsubscribe(String, (Message) ->) // RTL8a ++ setOptions(options: ChannelOptions) => io // RTL16 + + class PushChannel: + subscribeDevice() => io // RSH7a +@@ -1606,6 +1649,12 @@ enum ChannelEvent: + embeds ChannelState + UPDATE // RTL2g + ++enum ChannelMode: // TB2d ++ PRESENCE ++ PUBLISH ++ SUBSCRIBE ++ PRESENCE_SUBSCRIBE ++ + class ChannelStateChange: + current: ChannelState // RTL2a, RTL2b + event: ChannelEvent // TH5 +@@ -1616,6 +1665,8 @@ class ChannelStateChange: + class ChannelOptions: + +withCipherKey(key: Binary | String)? -> ChannelOptions // TB3 + cipher: (CipherParams | Params)? // RSL5a, TB2b ++ params?: Dict // TB2c ++ modes?: [ChannelMode] // TB2d + + class CipherParams: + algorithm: String default "AES" // TZ2a +@@ -1721,12 +1772,13 @@ class ProtocolMessage: + connectionSerial: Int? // RTN10c, TR4f + count: Int? // TR4g + error: ErrorInfo? // RTN15c2, TR4h +- flags: .HAS_PRESENCE & .HAS_BACKLOG & .RESUMED ? // RTP1, TR3, TR4i, RTL2f ++ flags: Int? // TR4i; bitfield containing zero or more boolean flags specified in TR3 + id: String? // TR4b + messages: [Message]? // TR4k + msgSerial: Int? // RTN7b, TR4j + presence: [PresenceMessage]? // TR4l + timestamp: Time? // TR4m ++ params: Dict? // TR4q, RTL4k + + enum ProtocolMessageAction: + HEARTBEAT // TR2 +@@ -1897,6 +1949,7 @@ class ErrorInfo: + message: String // TI1 + cause: ErrorInfo? // TI1 + statusCode: Int // TI1 ++ requestId: String? // RSC7c + + class EventEmitter: + on((Data...) ->) // RTE4 +@@ -1923,6 +1976,20 @@ class HttpPaginatedResponse // RSC19b + errorCode: Int // HP6 + errorMessage: String // HP7 + headers: Dict // HP8 ++ ++class Plugin // PC2 ++ // Empty class/interface. Plugins are not expected to share any common interface. ++ // An opaque base interface type for plugins is defined for type-safety in statically-typed languages. ++ ++enum PluginType ++ "vcdiff" ++ ++class VCDiffDecoder ++ decode([byte] delta, [byte] base) -> [byte] ++ ++class DeltaExtras ++ from: String // the id of the message the delta was generated from ++ format: String //the delta format. Only vcdiff is supported as at API version 1.2 + ``` + + h2(#old-specs). Old specs diff --git a/content/client-lib-development-guide/versions/v1.1/features.textile b/content/client-lib-development-guide/versions/v1.1/features.textile new file mode 100644 index 0000000000..322a2f34d6 --- /dev/null +++ b/content/client-lib-development-guide/versions/v1.1/features.textile @@ -0,0 +1,1933 @@ +--- +title: Features spec +section: client-lib-development-guide +index: 1 +anchor_specs: true +languages: + - none +jump_to: + General: + - Test guidelines + REST client library: + - RestClient + - Auth#rest-auth + - Channels#rest-channels + - Channel#rest-channel + - Presence#rest-presence + - Encryption#rest-encryption + - Forwards compatibility#rest-compatibility + Realtime client library: + - RealtimeClient + - Connection#realtime-connection + - Channels#realtime-channels + - Channel#realtime-channel + - Presence#realtime-presence + - EventEmitter#eventemitter + - Forwards compatibility#realtime-compatibility + - State conditions and operations#state-conditions-and-operations + Push notifications: + - Push notifications#push-notifications + - Push channels#push-channels + - Activation state machine#activation-state-machine + Types: + - Data types#types + - Options#options + - Push notifications#types-push + Interface Definition: + - Complete API IDL#idl + Previous version: + - Old specs +--- + +This document outlines the complete feature set of both the REST and Realtime client libraries. It is expected that every client library developer refers to this document to ensure that their client library provides the same API and features as the existing Ably client libraries. In addition to this, it is essential that there is test coverage over all of the features described below. As an example, see the Ruby library "test specification and coverage":https://github.com/ably/ably-ruby/blob/master/SPEC.md generated from the test suite. + +We recommend you use the "IDL (Interface Definition Language)":#idl and refer to other existing libraries that adhere to this spec as a reference when reviewing how the API has been implemented. + +The key words "must", "must not", "required", "shall", "shall not", "should", "should not", "recommended", "may", and "optional" (whether lowercased or uppercased) in this document are to be interpreted as described in "RFC 2119":https://tools.ietf.org/html/rfc2119 . + +__Please note we maintain a separate Google Sheet that keeps track of which features are implemented and matching test coverage for each client library. If you intend to work on an Ably client library, please "contact us":https://www.ably.io/contact for access to this Google Sheet as it is useful as a reference and also needs to be kept up to date__ + +h2(#test-guidelines). Test guidelines + +* @(G1)@ Every test should be executed using all supported protocols (i.e. JSON and "MessagePack":http://msgpack.org/ if supported). This includes both sending & receiving data +* @(G2)@ All tests by default are run against a special Ably sandbox environment. This environment allows apps to be provisioned without any authentication that can then be used for client library testing. Bear in mind that all apps created in the sandbox environment are automatically deleted after 60 minutes and have low limits to prevent abuse. Apps are configured by sending a @POST@ request to @https://sandbox-rest.ably.io/apps@ with a JSON body that specifies the keys and their associated capabilities, channel namespace rules and any presence fixture data that is required; see "ably-common test-app-setup.json":https://github.com/ably/ably-common/blob/master/test-resources/test-app-setup.json. Presence fixture data is necessary for the REST library presence tests as there is no way to register presence on a channel in the REST library +* @(G3)@ Testing statistics can be tricky due to timing issues and slow test suites as a result of sending requests to generate statistics. As such, we provide a special stats endpoint in our sandbox environment that allows stats to be injected into our metrics system so that stats tests can make predictable assertions. To create stats you must send an authenticated @POST@ request to the stats JSON to @https://sandbox-rest.ably.io/stats@ with the stats data you wish to create. See the "Javascript stats fixture":https://github.com/ably/ably-js/blob/4e65d4e13eb8750a375b9511e4dd059092c0e481/spec/rest/stats.test.js#L8-L51 and "setup helper":https://github.com/ably/ably-js/blob/4e65d4e13eb8750a375b9511e4dd059092c0e481/spec/common/modules/testapp_manager.js#L158-L182 as an example +* @(G4)@ This spec defines API version 1.1. A client library must identify to Ably the version of the spec it uses in all requests and connections, per "RSC7a":#RSC7a and "RTN2f":#RTN2f. The spec it uses is defined as the latest API version for which the library implements all spec items relating to the wire protocol + +h2(#rest). REST client library + +h3(#restclient). RestClient + +* @(RSC1)@ The constructor accepts a set of "@ClientOptions@":#options or, in languages that support overloaded constructors, a string which may be a token string or an API key. +** @(RSC1a)@ If a single string argument is supplied when constructing the library then the library must determine whether this is a key or a token by checking for the presence of the ':' (colon) delimiter present in an API key. Any other string must be treated as a token string. +** @(RSC1b)@ If invalid arguments are provided such as no API key, no token and no means to create a token, then this will result in an error with error code 40106 and an informative message. +** @(RSC1c)@ Tests must exist that in each overloaded library constructor the library correctly determines an API key to be a key, and each type of token string is determined to be a token. +* @(RSC2)@ The logger by default outputs to @STDOUT@ (or other logging medium as appropriate to the platform) and the log level is set to warning +* @(RSC3)@ The log level can be changed +* @(RSC4)@ A custom logger can be provided in the constructor +* @(RSC5)@ @RestClient#auth@ attribute provides access to the @Auth@ object that was instanced with the @ClientOptions@ provided in the @RestClient@ constructor +* @(RSC6)@ @RestClient#stats@ function: +** @(RSC6a)@ Returns a @PaginatedResult@ page containing @Stats@ objects in the @PaginatedResult#items@ attribute returned from the stats request +** @(RSC6b)@ Supports the following params: +*** @(RSC6b1)@ @start@ and @end@ are timestamp fields represented as milliseconds since epoch, or where suitable to the language, Date or Time objects. @start@ must be equal to or less than @end@ and is unaffected by the request direction +*** @(RSC6b2)@ @direction@ backwards or forwards; if omitted the direction defaults to the REST API default (backwards) +*** @(RSC6b3)@ @limit@ supports up to 1,000 items; if omitted the limit defaults to the REST API default (100) +*** @(RSC6b4)@ @unit@ is the period for which the stats will be aggregated by, values supported are @minute@, @hour@, @day@ or @month@; if omitted the unit defaults to the REST API default (@minute@) +* @(RSC16)@ @RestClient#time@ function sends a get request to @rest.ably.io/time@ and returns the server time in milliseconds since epoch or as a Date/Time object where suitable +* @(RSC7)@ Sends REST requests over HTTP and HTTPS to the REST endpoint @rest.ably.io@ +** @(RSC7a)@ The header @X-Ably-Version: 1.1@ must be included in all REST requests to the Ably endpoint +** @(RSC7b)@ The header @X-Ably-Lib: [lib][.optional variant]?-[version]@ should be included in all REST requests to the Ably endpoint where @[lib]@ is the name of the library such as @js@ for @ably-js@, @[.optional variant]@ is an optional library variant, such as @laravel@ for the @php@ library, which is always delimited with a period such as @php.laravel@, and where @[version]@ is the full client library version using "Semver":http://semver.org/ such as @1.0.2@. For example, the 1.0.0 version of the Javascript library would use the header @X-Ably-Lib: js-1.0.0@ +*** @(RSC7b1)@ When it is not possible to send the @X-Ably-Lib@ header, such as for @JSONP@ requests, the library version should be sent as a query param such as @lib=js-1.0.0@ +* @(RSC18)@ If @ClientOptions#tls@ is true, then all communication is over HTTPS. If false, all communication is over HTTP however "Basic Auth":https://en.wikipedia.org/wiki/Basic_access_authentication over HTTP will result in an error as private keys cannot be submitted over an insecure connection. See @Auth@ below +* @(RSC8)@ Supports two protocols: +** @(RSC8a)@ "MessagePack":http://msgpack.org/ binary protocol (this is the default for environments having a suitable level or support for binary data) +** @(RSC8b)@ JSON text protocol (used when @useBinaryProtocol@ option is false) +* @(RSC9)@ Uses @Auth@ to establish what authentication scheme to use, how to authenticate, and automatic issuing of tokens when necessary +* @(RSC10)@ If a REST request responds with a token error (401 HTTP status code and an Ably error value @40140 <= code < 40150@), then the Auth class is responsible for reissuing a token and the request should be reattempted, see "RSA4a":#RSA4a and "RSA4b":#RSA4b +* @(RSC11)@ Requests are sent to the default endpoint @rest.ably.io@. However, if the @restHost@ option is set, the client will send requests to the specified host. If @environment@ option is configured and is not "production", the environment name is prefixed to the default host endpoint and the @restHost@ is set accordingly. For example, if the @environment@ is set to sandbox, then the @restHost@ endpoint will become @sandbox-rest.ably.io@. See "TO3k2":#TO3k2 for constraints. +* @(RSC12)@ REST endpoint host is configurable in the Client constructor with the option @restHost@ +* @(RSC13)@ The client library must use the connection and request timeouts specified in the @ClientOptions@, falling back to the defaults described in @ClientOptions@ below +* @(RSC15)@ Host Fallback +** @(RSC15b)@ The fallback behavior described below only applies when the default @rest.ably.io@ endpoint is being used and has not been overriden (see "RSC11":#RSC11), @ClientOptions#fallbackHostsUseDefault@ is @true@, or an array of @ClientOptions#fallbackHosts@ is provided. If host fallback is not supported, failing HTTP requests that would have "qualified for a retry against a fallback host (see RSC15d)":#RSC15d, will instead result in an error immediately +** @(RSC15e)@ The primary host is by default @rest.ably.io@ (unless overriden in @ClientOptions#environment@ or @ClientOptions#restHost@), which, through DNS, is automatically routed to the client's closest datacenter. New HTTP requests (except where @RSC15f@ applies and a cached fallback host is in effect) are first attempted against the primary host. +** @(RSC15a)@ In the case of an error necessitating use of an alternative host (see "RSC15d":#RSC15d), try fallback hosts (with a matching Host header as this is necessary when fallbacks are proxied through a CDN) in random order, continuing to try further hosts if "qualifying errors":#RSC15d occur, failing when all have been tried or the configured @httpMaxRetryCount@ has been reached (see "TO3l@":#TO3l5). This ensures that a client library is able to work around routing or other problems for the user's closest datacenter. For example, if a @POST@ request to @rest.ably.io@ fails because the default endpoint is unreachable or unserviceable, then the @POST@ request should be retried again against the fallback hosts in attempt to find an alternate healthy datacenter to service the request. The five default fallback hosts are @[a-e].ably-realtime.com@. If an array of custom fallback hosts are provided in @ClientOptions#fallbackHosts@, then they will be used instead. If an empty array of fallback hosts is provided, then fallback host functionality is disabled +** @(RSC15d)@ Errors that necessitate use of an alternative host include: host unresolvable or unreachable, request timeout, or a response but with an applicable HTTP status code in the range @500 <= code <= 504@. Resending requests that have failed for other failure conditions will not fix the problem and will simply increase the load on other datacenters unnecessarily +** @(RSC15f)@ Once/if a given fallback host succeeds, the client should store that successful fallback host for @ClientOptions.fallbackRetryTimeout@. Future HTTP requests during that period should use that host. If during this period a "qualifying errors":#RSC15d occurs on that host, or after @fallbackRetryTimeout@ has expired, it should be un-stored, and the fallback sequence begun again from scratch, starting with the default primary host (@rest.ably.io@ or @ClientOptions#restHost@) or, in the case of an existing fallback realtime connection as per (RTN17e), with the current fallback realtime host. +* @(RSC17)@ When instancing the library, if a @clientId@ attribute is set in @ClientOptions@, then the @Auth#clientId@ attribute will contain the provided @clientId@ +* @(RSC19)@ @RestClient#request@ function is provided as a convenience for customers who wish to use bleeding edge REST API functionality that is either not documented or is not included in the API for our client libraries. The REST client library provides a function to issue HTTP requests to the Ably endpoints with all the built in functionality of the library such as authentication, paging, fallback hosts, MsgPack and JSON support etc. The function: +** @(RSC19a)@ Method signature is @request(string method, string path, Dict params?, JsonObject | JsonArray body?, Dict headers?) -> HttpPaginatedResponse@ with arguments: @method@ is a valid "HTTP verb":https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html (must support @"GET"@, @"POST"@, and @"PUT"@, should support @"PATCH"@ and @"DELETE", may support others); @path@ is the path component of the URL such as @"/channels"@; @params@ and @headers@ are optional arguments containing pairs of key value strings (multi-valued headers are not supported) that will result in query params and HTTP headers being added respectively in the request (the argument types can be idiomatic for the language such as @Object@ in the case of Javascript); @body@ is an optional @JsonObject@ or @JsonArray@ like object argument that can be easily serialized to MsgPack or JSON +** @(RSC19b)@ All requests will unconditionally use the default authentication mechanism configured for the REST client i.e. basic or token authentication (see "Auth":#rest-auth) +** @(RSC19c)@ The library will configure the @Accept@ and @Content-Type@ type headers to reflect whether the client is configured to use a binary or JSON based protocol (see "RSC8":#RSC8). All requests are encoded and decoded into Json or MsgPack as appropriate automatically by the library. Binary @body@ payloads are not supported at this time +** @(RSC19d)@ @request@ method returns an @HttpPaginatedResponse@ object that inherits from the @PaginatedResult@ object to provide details on the response plus paging support where applicable. See "HP1":#HP1 for more details +** @(RSC19e)@ If the HTTP network request fails for reasons such as a timeout (after the underlying fallback host attempts have failed where applicable, see "RSC15":#RSC15), then the @request@ method should indicate an error in an idiomatic way for the platform +* @(RSC20)@ Unexpected internal library exception handling: +** @(RSC20a)@ The library must make every attempt to handle unexpected internal exceptions as gracefully as possible. For the avoidance of doubt, unexpected internal exceptions do not include request timeouts, invalid argument values, invalid responses from third parties or exceptions in code run in callbacks registered by applications. However, any unexpected failures or unhandled exceptions in our own code that typically could trigger a crash or raise an exception in our customer's code, is considered an unexpected internal exception +** @(RSC20b)@ The library may optionally report unexpected internal exceptions to the Ably exception reporting service. Exception reporting, when supported, is enabled by default, but can be disabled using the @logExceptionReportingUrl@ @ClientOption@ documented in "@TO3m@":#TO3m. When enabled, the client library must: +*** @(RSC20b1)@ At startup log a message with the equivalent of @info@ log level with the message "Ably client library exception reporting enabled. Unhandled failures will be automatically submitted to errors.ably.io to help improve our service. To find out more about this feature, see https://help.ably.io/exceptions". The @info@ log level is preferred as customers can hide this entry by configuring the client library log level to @warning@, yet will, by default, see this notice when using a client library with exception reporting enabled +*** @(RSC20b2)@ Any unhandled internal exceptions should automatically submit bug reports to the @logExceptionReportingUrl@ using the "@Sentry API@":https://docs.sentry.io/clientdev/ either via a raw HTTP request or by using an embedded "Sentry exception reporting client library":https://docs.sentry.io/clients/. A message at @info@ log level should be logged if the request succeeds or fails i.e. a failure to submit an exception should not be logged as a failure / error. Where possible, the exception GUID returned from the Sentry API should be logged so that the specific error can be tracked +** @(RSC20c)@ Exceptions reported must additionally include the following tags: @ably-lib@ with the value defined in "@RSC7b@":RSC7b, @ably-version@ with the value defined in "@RSC7a@":RSC7a, @appId@ if known from either the token or API key currently being used. +** @(RSC20d)@ All personally identifiable information, as much as is practicable, must be redacted or stripped completely before being submitted to Ably. Our intent is only to capture necessary information to debug issues in our own code +** @(RSC20e)@ Failures to log exceptions to the @errors.ably.io@ endpoint must be handled gracefully. This includes for example DNS failures, TCP/HTTP requests rejected, slow requests and internal failure errors. Additionally, as specified in @RSC20b2@, a failure to log an exception is logged with log level @info@ i.e. an exception reporting failure is not consider a client library @error@ or @warning@ +** @(RSC20f)@ Any errors emitted by the library as a result of an internal failure must contain a status code @500@, an error code in the range @51000@ to @51999@ and a suitable error message. The error code must match one of "our common error codes":https://github.com/ably/ably-common/blob/master/protocol/errors.json + +h3(#rest-auth). Auth + +* @(RSA1)@ "Basic Auth":https://en.wikipedia.org/wiki/Basic_access_authentication connects over HTTPS by default. Any attempt to use Basic Auth over HTTP without TLS will result in an error +* @(RSA11)@ When using Basic Auth, the API key is Base64 encoded and included in an @Authorization@ header, as specified in "RFC7235":https://tools.ietf.org/html/rfc7235. The API key follows the format @"KEY_NAME:KEY_SECRET"@ so when authenticating using "Basic Auth":https://en.wikipedia.org/wiki/Basic_access_authentication, the key name can be used as the username and the key secret as the password +* @(RSA2)@ "Basic Auth":https://en.wikipedia.org/wiki/Basic_access_authentication is the default authentication scheme if an API key exists +* @(RSA3)@ Token Auth: +** @(RSA3a)@ Can be used over HTTP or HTTPs +** @(RSA3b)@ For REST requests, the token string is optionally Base64-encoded and used in the @Authorization: Bearer@ header +** @(RSA3c)@ For Realtime "websocket connections":/concepts/websockets, the querystring param @accessToken@ is appended to the URL endpoint +** @(RSA3d)@ A test must exist that each type of token string is correctly passed in requests (ie according to @(RSA3b)@ and @(RSA3c)@) +* @(RSA4)@ Token Auth is used if @useTokenAuth@ is set to true, or if @useTokenAuth@ is unspecified and any one of @authUrl@, @authCallback@, @token@, or @TokenDetails@ is provided +** @(RSA4a)@ When a @token@ or @tokenDetails@ is used to instance the library, and no means to renew the token is provided (either an API key, @authCallback@ or @authUrl@): +*** @(RSA4a1)@ At instantiation time, a message at @info@ log level with error code @40171@ should be logged indicating that no means has been provided to renew the supplied token, including an associated url per @TI5@ +*** @(RSA4a2)@ if the server responds with a token error (401 HTTP status code and an Ably error value @40140 <= code < 40150@), then the client library should indicate an error with error code @40171@, not retry the request and, in the case of the realtime library, transition the connection to the @FAILED@ state +** @(RSA4b)@ When the client does have a means to renew the token automatically, and the server has responded with a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@) or the client library has optionally detected the current token has expired (see "RSA4b1":#RSA4b1), then the client should automatically make a single attempt to reissue the token and resend the request using the new token. If the token creation failed or the subsequent request with the new token failed due to a token error, then the request should result in an error (in the case of a realtime library, follow @RSA4c@) +*** @(RSA4b1)@ Client libraries can optionally save a round-trip request to the Ably service for expired tokens by detecting when a token has expired when all of the following applies: the current token is a @TokenDetails@ object with an @expires@ attribute; the library has previously queried the time from the Ably service and persisted the local clock offset according to "RSA10k":#RSA10k; the @expires@ time has passed based on the Ably service time and not the local clock (which is not guaranteed to be accurate) +** @(RSA4c)@ If an attempt by the realtime client library to authenticate is made using the @authUrl@ or @authCallback@, and the request to @authUrl@ fails (unless @RSA4d@ applies), the callback @authCallback@ results in an error (unless "RSA4d":#RSA4d applies), an attempt to exchange a @TokenRequest@ for a @TokenDetails@ results in an error (unless "RSA4d":#RSA4d applies), the provided token is in an invalid format (as defined in "RSA4e":#RSA4e), or the attempt times out after "@realtimeRequestTimeout@":#DF1b, then: +*** @(RSA4c1)@An @ErrorInfo@ with @code@ @80019@, @statusCode@ 401, and @cause@ set to the underlying cause should be emitted with the state change if there is one (per @RSA4c2/3@) and set as the connection @errorReason@ +*** @(RSA4c2)@If the connection is @CONNECTING@, then the connection attempt should be treated as unsuccessful, and as such the connection should transition to the @DISCONNECTED@ or @SUSPENDED@ state as defined in "RTN14":#RTN14 and "RTN15":#RTN15 +*** @(RSA4c3)@If the connection is @CONNECTED@, then the connection should remain @CONNECTED@ +** @(RSA4d)@ If a request by a realtime client to an @authUrl@ results in an HTTP 403 response, or any of an @authUrl@ request, an @authCallback@, or a request to Ably to exchange a @TokenRequest@ for a @TokenDetails@ result in an @ErrorInfo@ with @statusCode@ 403, then the client library should transition to the @FAILED@ state, with an @ErrorInfo@ (with @code@ @80019@, @statusCode@ 403, and @cause@ set to the underlying cause) emitted with the state change and set as the connection @errorReason@ +** @(RSA4e)@ If in the course of a REST request (or explicit call to @requestToken@) an attempt to authenticate using @authUrl@ or @authCallback@ fails due to a timeout, network error, a token in an invalid format (per "RSA4f":#RSA4f), or some other auth error condition other than an explicit @ErrorInfo@ from Ably, the request should result in an error with @code@ 40170, @statusCode@ 401, and a suitable error message +** @(RSA4f)@ The following conditions imply that the token is in an invalid format: the @authUrl@ response content type is not one of @text/plain@, @application/json@ or @application/jwt@; the object passed by @authCallback@ is neither a @String@, @JsonObject@, @TokenRequest@ object, nor @TokenDetails@ object; the token string or the JSON stringified @JsonObject@, @TokenRequest@ or @TokenDetails@ is greater than 128KiB. +** @(RSA4g)@ If multiple @authOptions@ are used to initialize the library, the preference ordering among them is identical to @Auth#authorize@, defined in @RSA10e@ +* @(RSA14)@ If Token Auth is selected, yet a token is not provided and there is no means to generate a token, then this will result in an error. For example, if only the option @useTokenAuth@ is specified, and a @key@ is not provided, then the client library is unable to authenticate or issue a token +* @(RSA15)@ If Token Auth is selected and @clientId@ has been set in the @ClientOptions@ when the library was instanced: +** @(RSA15a)@ Any @clientId@ provided in @ClientOptions@ must match any non wildcard (@'*'@) @clientId@ value in @TokenDetails@ or @connectionDetails@ of the @CONNECTED@ @ProtocolMessage@, where applicable +** @(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 +* @(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 @clientId@ is provided in @ClientOptions@ and @RSA4@ indicates that basic auth is to be used, then: +*** @(RSA7e1)@ For realtime clients, the connect request should include the @clientId@ as a querystring parameter, @clientId@ +*** @(RSA7e2)@ For REST clients, all requests should include an @X-Ably-ClientId@ header with value set to the @clientId@, Base64 encoded +** @(RSA7a)@ A client is considered to be identified if a @clientId@ is implicit in either the connection or the authentication scheme; that is, is present in the current authentication token (with the exception of the wildcard @clientId@ @'*'@), is set by a header per @RSA7e2@, or is specified when initiating a realtime connection per @RSA7e1@. The following applies to identified clients: +*** @(RSA7a1)@ All operations (such as message publishing or presence) carried out by an identified client will have an implicit @clientId@. The Ably service automatically updates the @clientId@ attribute (when empty) for all @Message@ and @PresenceMessage@ messages received from that client. Client libraries should therefore not explicitly set the @clientId@ field on messages published from an identified client +*** @(RSA7a4)@ When a @clientId@ value is provided in both @ClientOptions#clientId@ and @ClientOptions#defaultTokenParams@, the @ClientOptions#clientId@ takes precedence and is used for all Auth operations +** @(RSA12)@ @Auth#clientId@ attribute is @null@ when: +*** @(RSA12a)@ The @clientId@ attribute of a @TokenRequest@ or @TokenDetails@ used for authentication is @null@, or @ConnectionDetails#clientId@ is @null@ following a connection to Ably. In this case, the @null@ value indicates that a @clientId@ identity may not be assumed by this client i.e. the client is anonymous for all operations +*** @(RSA12b)@ The client was instanced without assigning a value for @ClientOptions#clientId@ (@null@), and the client has not yet authenticated or connected to Ably. In this case, the @null@ value indicates that the client has not yet been able to confirm its identity, and therefore may change and become identified following later authentication or establishment of a connection with Ably +** @(RSA7b)@ @Auth#clientId@ is not @null@ when: +*** @(RSA7b1)@ A @clientId@ is provided in the @ClientOptions@. @clientId@ should be a string +*** @(RSA7b2)@ Token authentication is being used, and the @TokenRequest@ or @TokenDetails@ object, used for authentication, has a @clientId@ value that is not @null@ +*** @(RSA7b3)@ Following a realtime connection being established, if the @CONNECTED@ @ProtocolMessages@ contains a @clientId@ that is not @null@. @clientId@ is an attribute of @ProtocolMessage#connectionDetails@ within a @CONNECTED@ @ProtocolMessage@ +*** @(RSA7b4)@ When a wildcard string @'*'@ is present in the @TokenRequest@, @TokenDetails@, or @ProtocolMessage#connectionDetails@ object, then the client does not have an identity but is allowed to assume an identity when performing operations with Ably. As such, @Auth#clientId@ should contain the string value @'*'@ indicating that the current client is allowed to perform operations on behalf of any @clientId@ +** @(RSA7c)@ A @clientId@ provided in the @ClientOptions@ when instancing the library must be either @null@ or a string, and cannot contain only a wildcard @'*'@ string value as that client ID value is reserved +* @(RSA8)@ @Auth#requestToken@ function: +** @(RSA8e)@ Method signature is @requestToken(TokenParams, AuthOptions)@. @TokenParams@ and @AuthOptions@ are optional. When @TokenParams@ or @AuthOptions@ are provided, the values of each attribute are not merged with the configured client library defaults, but rather are used instead of the stored values (even when @null@). If the object arguments are omitted, the client library configured defaults are used +** @(RSA8a)@ Implicitly creates a @TokenRequest@ if required, and requests a token from Ably if required. Returns a @TokenDetails@ object +** @(RSA8b)@ Supports all @TokenParams@ in the function arguments, which override defaults for @Client@ @Auth@ +** @(RSA8c)@ When @authUrl@ option is set, it will query the provided URL to obtain a @TokenRequest@ or the token itself (either a token string or a @TokenDetails@). The query is performed using the given URL using the HTTP method in @authMethod@, headers (from @authHeaders@) and supplementary params (from @authParams@). The token retrieved is assumed by the library to be a token string if the response has @Content-Type@ @"text/plain"@ or @"application/jwt"@, or taken to be a @TokenRequest@ or @TokenDetails@ object if the response has @Content-Type@ @"application/json"@. @authMethod@ can be either @GET@ or @POST@, or if not specified, will default to @GET@. It can be quite difficult to add test coverage for these scenarios - as such, we have developed a simple echo server that can be used in your tests, see the "ably-js authUrl echo tests":https://github.com/ably/ably-js/commit/e77b2c6c197bc71f3d27084fe54524724a00bc92 +*** @(RSA8c1)@ @TokenParams@ and any configured @authParams@ and @authHeaders@ are always sent to the @authUrl@ as follows: +**** @(RSA8c1a)@ When the @authMethod@ is @GET@ or unspecified, the @TokenParams@ and @authParams@ are merged and appended to the URL as query string params, and the @authHeaders@ are sent as HTTP headers +**** @(RSA8c1b)@ When the @authMethod@ is @POST@, the @TokenParams@ and @authParams@ are merged and sent form-encoded in the body of the @POST@ request, and the @authHeaders@ are sent as HTTP headers +**** @(RSA8c1c)@ If the given @authUrl@ includes any querystring params, they should be preserved. In the @GET@ case, @authParams@/@tokenParams@ should be merged with them. If a name conflict occurs, @authParams@/@tokenParams@ should take precedence +*** @(RSA8c2)@ @TokenParams@ take precedence over any configured @authParams@ when a name conflict occurs +*** @(RSA8c3)@ Specifying @authParams@ or @authHeaders@ as part of @AuthOptions@ replaces any configured @authParams@ or @authHeaders@ specified in @ClientOptions@ respectively. As the provided key/value pairs are not merged with the @ClientOptions@ configured key/value pairs, this enables a developer to delete @authParams@ or @authHeaders@ where necessary by providing an entire new set of key/value pairs +** @(RSA8d)@ When @authCallback@ option is set, it will invoke the callback, passing in the @TokenParams@, and expects either a token string, a @TokenDetails@ object or a @TokenRequest@ object to be returned, which will in turn be used to request a token from Ably +** @(RSA8f)@ A test should exist for the following: +*** @(RSA8f1)@ Request a token with a @null@ value @clientId@, authenticate a client with the token, publish a message without an explicit @clientId@, and ensure the message published does not have a @clientId@. Check that @Auth#clientId@ is @null@ +*** @(RSA8f2)@ Request a token with a @null@ value @clientId@, authenticate a client with the token, publish a message with an explicit @clientId@ value, and ensure that the message is rejected +*** @(RSA8f3)@ Request a token with a wildcard @'*'@ value @clientId@, authenticate a client with the token, publish a message without an explicit @clientId@, and ensure the message published does not have a @clientId@. Check that @Auth#clientId@ is a string with value @'*'@ +*** @(RSA8f4)@ Request a token with a wildcard @'*'@ value @clientId@, authenticate a client with the token, publish a message with an explicit @clientId@ value, and ensure that the message published has the provided @clientId@ +** @(RSA8g)@ Tests must exist to verify both the @authCallback@ and @authURL@ mechanisms where the returned token string value is a JWT token string and an Ably token string. +* @(RSA9)@ @Auth#createTokenRequest@ function: +** @(RSA9h)@ Method signature is @createTokenRequest(TokenParams, AuthOptions)@. @TokenParams@ and @AuthOptions@ are optional.When @TokenParams@ or @AuthOptions@ are provided, the values of each attribute are not merged with the configured client library defaults, but rather are used instead of the stored values (even when @null@). If the object arguments are omitted, the client library configured defaults are used +** @(RSA9a)@ Returns a signed @TokenRequest@ object that can be used to obtain a token from Ably. This is useful for servers that can create a @TokenRequest@ signed with the API key without communicating with Ably directly. The @TokenRequest@ can then be passed to a designated client that is then responsible for communicating with Ably and requesting a token for authentication from that @TokenRequest@ +** @(RSA9c)@ Generates a unique 16+ character @nonce@ if none is provided; the nonce is used to protect against replay attacks +** @(RSA9d)@ Generates a @timestamp@ from current time if not provided, will retrieve the server time if @queryTime@ is true +** @(RSA9e)@ TTL is optional and specified in milliseconds +** @(RSA9f)@ Capability JSON text can be provided that specifies the rights of the token in terms of the channel(s) authorized and the permitted operations on each +** @(RSA9g)@ A valid HMAC is created using the key secret (using the key from the passed-in @AuthOptions@ if supplied) to sign the @TokenRequest@ so that it can be used by any client to request a token without having or exchanging any secrets +** @(RSA9i)@ Adheres to all requirements in @RSA8@ relating to @TokenParams@ +* @(RSA10)@ @Auth#authorize@ function: +** @(RSA10a)@ Instructs the library to create a token immediately and ensures Token Auth is used for all future requests. See "RTC8":#RTC8 for re-authentication behavior when called for a realtime client +** @(RSA10j)@ Method signature is @authorize(TokenParams, AuthOptions)@. @TokenParams@ and @AuthOptions@ are optional. When the arguments are present, even if empty, the @TokenParams@ and @AuthOptions@ supersede any previously client library configured @TokenParams@ and @AuthOptions@. For example, if a client is initialized with @TokenParams#ttl@ configured with a custom value, and a @TokenParams@ object is passed in as an argument to @#authorize@ with a @null@ or missing value for @ttl@, then the @ttl@ used for every subsequent authorization will be @null@ +** @(RSA10b)@ Supports all @AuthOptions@ and @TokenParams@ in the function arguments +** @(RSA10k)@ If the @AuthOption@ argument's @queryTime@ attribute is true, it will obtain the server time once and persist the offset from the local clock. All future token requests generated directly or indirectly via a call to @authorize@ will not obtain the server time, but instead use the local clock offset to calculate the server time. The client library itself MAY internally discard the cached local clock offset in situations in which it may have been invalidated, such as if there is a local change to the date, time, or timezone, of the client device. For clarity however, there is no requirement for this cache invalidation to be available to consumers of the client library API. +** @(RSA10e)@ If the @authOptions@ contains a way of obtaining a token (an @authCallback@, @authUrl@, or @key@), that should be used to obtain a new token, as per @requestToken@ (@RSA8@). If it contains a token (@token@ or @tokenDetails@), that should be used as-is. If it contains both a token and a way of obtaining a token, the token should be used, with the way of obtaining a token being stored per @RSA10g@ for when the token expires. (Ordering of preference within those groups is not defined and is left up to individual implementations) +** @(RSA10f)@ Returns a @TokenDetails@ object that contains the token string + token metadata +** @(RSA10g)@ Stores the @AuthOptions@ and @TokenParams@ arguments as defaults for subsequent authorizations with the exception of the attributes @TokenParams#timestamp@ and @AuthOptions#queryTime@ +** @(RSA10h)@ Will use the value from @Auth#clientId@ by default, if not @null@ +** @(RSA10i)@ Adheres to all requirements in @RSA8@ relating to @TokenParams@, @authCallback@ and @authUrl@ +** @(RSA10l)@ Has an alias method @RestClient#authorise@ and @RealtimeClient#authorise@ that will log a deprecation warning stating that this alias method will be removed in @v1.0@ and the user should instead use @authorize@ +* @(RSA16)@ @Auth#tokenDetails@: +** @(RSA16a)@ Holds a @TokenDetails@ representing the token currently in use by the library, if any; +** @(RSA16b)@ If the library is provided with a token without the corresponding @TokenDetails@, then this holds a @TokenDetails@ instance in which only the @token@ attribute is populated with that token string +** @(RSA16c)@ Is set with the current token (if applicable) on instantiation and each time it is replaced, whether the result of an explicit @Auth#authorize@ operation, or a library-initiated renewal resulting from expiry or a token error response +** @(RSA16d)@ Is empty if there is no current token, including after a previous token has been determined to be invalid or expired, or if the library is using basic auth + +h3(#rest-channels). Channels + +* @(RSN1)@ @Channels@ is a collection of @Channel@ objects accessible through @Rest#channels@ +* @(RSN2)@ Methods should exist to check if a channel exists or iterate through the existing channels +* @(RSN3)@ @Channels#get@ function: +** @(RSN3a)@ Creates a new @Channel@ object for the specified channel if none exists, or returns the existing channel. @ChannelOptions@ can be specified when instancing a new @Channel@ +** @(RSN3b)@ If options are provided, the options are set on the @Channel@ +** @(RSN3c)@ Accessing an existing @Channel@ with options in the form @Channels#get(channel, options)@ will update the options on the channel and then return the existing @Channel@ object +* @(RSN4)@ @Channels#release@ function: +** @(RSN4a)@ Releases the channel resource i.e. it's deleted and can then be garbage collected + +h3(#rest-channel). Channel + +* @(RSL1)@ @Channel#publish@ function: +** @(RSL1a)@ Expects either a @Message@ object, an array of @Message@ objects, or a @name@ string and @data@ payload +** @(RSL1b)@ When @name@ and @data@ (or a @Message@) is provided, a single message is published to Ably +** @(RSL1c)@ When an array of @Message@ objects is provided, a single request is made to Ably +** @(RSL1d)@ Indicates an error if the message was not successfully published to Ably +** @(RSL1e)@ Allows @name@ and/or @data@ to be @null@. If any of the values are @null@, that property is not sent to Ably, e.g. a payload with a @null@ value for @data@ would be sent as @{"name":"click"}@ +** @(RSL1f)@ Unidentified clients using "Basic Auth":https://en.wikipedia.org/wiki/Basic_access_authentication (i.e. any @clientId@ is permitted as no @clientId@ specified): +*** @(RSL1f1)@ When a @Message@ with a @clientId@ value is published, Ably will accept and publish that message with the provided @clientId@. A test should assert via the history API that the @clientId@ of the published @Message@ is populated +** @(RSL1g)@ Identified clients with a @clientId@ (as a result of either an explicitly configured @clientId@ in @ClientOptions@, or implicitly through Token Auth): +*** @(RSL1g1)@ When publishing a @Message@ with the @clientId@ attribute set to @null@: +**** @(RSL1g1a)@ It is unnecessary for the client to set the @clientId@ of the @Message@ before publishing +**** @(RSL1g1b)@ Ably will assign the identified @clientId@ upon receiving the @Message@. A test should assert via the history API that the @clientId@ value is the identified @clientId@ for the @Message@ when received +*** @(RSL1g2)@ When publishing a @Message@ with the @clientId@ attribute value set to the identified client's @clientId@, Ably will accept the message and publish it. A test should assert that the @clientId@ value is populated for the @Message@ when received +*** @(RSL1g3)@ When publishing a @Message@ with a different @clientId@ attribute value to the identified client's @clientId@, the client library should reject the message, and indicate an error. The connection and channel remain available for further operations +*** @(RSL1g4)@ When publishing a message with an explicit @clientId@ that is incompatible with the identified client's @clientId@ (either inferred or explicitly configured in the token or @ClientOptions@), the library will reject the message immediately and indicate an error +** @(RSL1h)@ The @publish(name, data)@ form should not take any additional arguments. If a client library has supported additional arguments to the @(name, data)@ form (e.g. separate arguments for @clientId@ and @extras@, or a single @attributes@ argument) in any 1.x version, it should continue to do so until version 2.0. +** @(RSL1i)@ If the total size of the message or (if publishing an array) messages, calculated per "TO3l8":#TO3l8, exceeds the @maxMessageSize@, then the client library should reject the publish and indicate an error with code 40009 +** @(RSL1j)@ When @Message@ objects are provided, any valid @Message@ attribute (that is, an attribute specified in "TM2":#TM2) that is supplied by the caller must be included in the encoded message. (This does not mean it must be included _unaltered_; for example the @data@ and @encoding@ will be subject to processing per "RSL4":#RSL4) +** @(RSL1k)@ Idempotent publishing via REST is supported by populating the @id@ attribute of @Message@ instances passed to @publish()@: +*** @(RSL1k1)@ Idempotent publishing via library-generated @Message@ @id@s is supported if @idempotentRestPublishing@ (see "TO3n":#TO3n) is enabled and one or more @Message@ instances are passed to @publish()@ and all @Message@s have an empty @id@ attribute. The library generates a base @id@ string by base64-encoding a sequence of at least 9 bytes obtained from a source of randomness. Each individual @Message@ in the set of messages to be published is assigned a unique @id@ of the form <base id>:<serial> (where @serial@ is the zero-based index into the set). +*** @(RSL1k2)@ Idempotent publishing via client-supplied @Message@ @id@s is supported where a single @Message@ is passed to @publish()@ and it contains a non-empty @id@. The @id@ is preserved on sending the message. +*** @(RSL1k3)@ If more than one @Message@ is passed to @publish()@ and one or more of those messages contains a non-empty @id@ attribute, then all message ids (present or absent) are preserved on sending the batch of messages. +*** @(RSL1k4)@ An explicit test for idempotency of publishes with library-generated ids shall exist that simulates an error response to a successful publish of a batch of messages, expects an automatic retry by the library, and verifies that the net outcome is that the batch is published only once. +*** @(RSL1k5)@ An explicit test for idempotency of publishes with client-supplied ids shall exist that involves multiple explicit publish requests for a given message and verifies that the net outcome is that the message is published only once. +** @(RSL1l)@ The @publish(Message)@ and @publish(Message[])@ forms of the method should take an extra @Dict@ argument. These parameters should be encoded using normal querystring-encoding and sent as part of the query string of the REST publish. (@Stringifiable@ is defined in @RTC1f@) +*** @(RSL1l1)@ Publish params can be tested by publishing with a @_forceNack=true@ parameter, which will result in the publish being rejected with a @40099@ error code +* @(RSL2)@ @Channel#history@ function: +** @(RSL2a)@ Returns a @PaginatedResult@ page containing the first page of messages in the @PaginatedResult#items@ attribute returned from the history request +** @(RSL2b)@ Supports the following params: +*** @(RSL2b1)@ @start@ and @end@ are timestamp fields represented as milliseconds since epoch, or where suitable to the language, Time objects. @start@ must be equal to or less than @end@ and is unaffected by the request direction +*** @(RSL2b2)@ @direction@ backwards or forwards; if omitted the direction defaults to the REST API default (backwards) +*** @(RSL2b3)@ @limit@ supports up to 1,000 items; if omitted the direction defaults to the REST API default (100) +* @(RSL3)@ @Channel#presence@ attribute contains a @Presence@ object for this channel +* @(RSL4)@ Message encoding +** @(RSL4a)@ Payloads must be binary, strings, or objects capable of JSON representation, or can be empty (omitted). Any other data type should not be permitted and result in an error +** @(RSL4b)@ If a message is encoded, the @encoding@ attribute represents the encoding(s) applied in right to left format i.e. "utf-8/base64" indicates that the original payload has "utf-8" encoding and has subsequently been encoded in Base64 format +** @(RSL4c)@ When using MessagePack Message encoding +*** @(RSL4c1)@ a binary Message payload is encoded as MessagePack binary type +*** @(RSL4c2)@ a string Message payload is encoded as MessagePack string type +*** @(RSL4c3)@ a JSON Message payload is stringified either as a JSON Object or Array and encoded as MessagePack string type and the @encoding@ attribute is set to "json" +*** @(RSL4c4)@ All messages received will deliver payloads in the format they were sent in i.e. binary, string, or a structured type containing the parsed JSON +** @(RSL4d)@ When using JSON Message encoding +*** @(RSL4d1)@ a binary Message payload is encoded as Base64 and represented as a JSON string the @encoding@ attribute is set to "base64" +*** @(RSL4d2)@ a string Message payload is represented as a JSON string +*** @(RSL4d3)@ a JSON Message payload is stringified either as a JSON Object or Array and represented as a JSON string and the @encoding@ attribute is set to "json" +*** @(RSL4d4)@ All messages received will be decoded based on the @encoding@ field and deliver payloads in the format they were sent in i.e. binary, string, or a structured type containing the parsed JSON +* @(RSL5)@ Message payload encryption +** @(RSL5a)@ When a @Channel@ is instanced with a (non-null) @cipher@ channelOption, message payloads will be automatically encrypted when sent to Ably and decrypted when received on this channel, using the @cipher@ configuration +** @(RSL5b)@ AES 256 and 128 CBC encryption must be supported +** @(RSL5c)@ Tests must exist that encrypt and decrypt the following fixture data for "AES 128":https://github.com/ably/ably-common/blob/master/test-resources/crypto-data-128.json and "AES 256":https://github.com/ably/ably-common/blob/master/test-resources/crypto-data-256.json to ensure the client library encryption is compatible across libraries +* @(RSL6)@ Message decoding +** @(RSL6a)@ All messages received will be decoded automatically based on the @encoding@ field and the payloads will be converted into the format they were originally sent using i.e. binary, string, or JSON +*** @(RSL6a1)@ A set of tests must exist to ensure that the client library provides data encoding & decoding interoperability with other client libraries. The tests must use the "set of predefined interoperability message fixtures":https://github.com/ably/ably-common/blob/master/test-resources/messages-encoding.json to 1) publish a raw message to the REST API using the JSON transport and subscribe to the message using Realtime to ensure the @data@ attribute matches the fixture; 2) publish a message using the REST client library and retrieve the raw message using the history REST API using the JSON transport ensuring the @data@ matches the fixture; 3) perform the client library operation using both @JSON@ and @MsgPack@ transports. For reference, see the "Ruby":https://github.com/ably/ably-ruby/pull/94 and "iOS":https://github.com/ably/ably-cocoa/pull/459 implementations +*** @(RSL6a2)@ A set of tests must exist to ensure that the client library provides interoperability for the @extras@ field which is a JSON-encodable object (ie a value that represents a JSON @object@ value and supports serialization to and from JSON text). The test, at a minimum, should publish a message with an @extras@ object such as @{"push":[{"title":"Testing"}]}@ and ensure it is received with an equivalent JSON-encodable object +** @(RSL6b)@ If, for example, incompatible encryption details are provided or invalid Base64 is detected in the message payload, an error message will be sent to the logger, but the message will still be delivered with last successful decoding and the @encoding@ field. For example, if a message had a decoding of "utf-8/cipher+aes-128-cbc/base64", and the payload was successfully Base64 decoded but the payload could not be encrypted because the @CipherParam@ details were not configured, the message would be delivered with a binary payload and an @encoding@ with the value "utf-8/cipher+aes-128-cbc" + +h3(#rest-presence). Presence +* @(RSP1)@ Presence object is associated with a single channel and is accessible through @Channel#presence@ +* @(RSP2)@ There is no way to register a member as present on a channel via the REST API +* @(RSP3)@ @Presence#get@ function: +** @(RSP3a)@ Returns a @PaginatedResult@ page containing the first page of members present in the @PaginatedResult#items@ attribute returned from the presence request. Each member is represented as a @PresenceMessage@. Supports the following params: +*** @(RSP3a1)@ @limit@ supports up to 1,000 items; if unspecified it defaults to the REST API default (100) +*** @(RSP3a2)@ @clientId@ filters members by the provided @clientId@ +*** @(RSP3a3)@ @connectionId@ filters members by the provided @connectionId@ +* @(RSP4)@ @Presence#history@ function: +** @(RSP4a)@ Returns a @PaginatedResult@ page containing the first page of messages in the @PaginatedResult#items@ attribute returned from the presence request +** @(RSP4b)@ Supports the following params: +*** @(RSP4b1)@ @start@ and @end@ are timestamp fields represented as milliseconds since epoch, or where appropriate to the language, Date/Time objects. @start@ must be equal to or less than @end@ and is unaffected by the request direction +*** @(RSP4b2)@ @direction@ backwards or forwards; if unspecified defaults to the REST API default (backwards) +*** @(RSP4b3)@ @limit@ supports up to 1,000 items; if unspecified defaults to the REST API default (100) +* @(RSP5)@ Presence Messages retrieved are decoded in the same way that messages are decoded + +h3(#rest-encryption). Encryption +* @(RSE1)@ @Crypto::getDefaultParams@ function: +** @(RSE1a)@ Returns a complete @CipherParams@ instance, using the default values for any field not supplied +** @(RSE1b)@ Takes a hashmap (or language equivalent) consisting of any subset of @CipherParams@ fields that includes a @key@ +** @(RSE1c)@ The @key@ must be either a binary (e.g. a byte array, depending on the language), or a base64-encoded string. If the key is a string, the function should base64-decode it into a binary. Since the conversion to base64 is not under Ably control, this should be done leniently -- in particular, it should work with base64url (RFC 4648 s.5, which uses @-@ and @_@ instead of @+@ and @/@) as well as base64 (RFC 4648 s.4) +** @(RSE1d)@ Calculates a @keyLength@ from the key (its size in bits). +** @(RSE1e)@ Checks that the provided options are valid and self-consistent as best it can, raises an exception if not. At a minimum, this should include checking the calculated @keyLength@ is a valid key length for the encryption algorithm (for example, 128 or 256 for @AES@) +* @(RSE2)@ @Crypto::generateRandomKey@ function +** @(RSE2a)@ Takes an optional @keyLength@ parameter, which is the length in bits of the key to be generated. If unspecified, this is equal to the default @keyLength@ of the default algorithm: for @AES@, 256 bits. +** @(RSE2b)@ Returns (or calls back with, if the language cryptographic randomness primitives are blocking or async) the key as a binary (e.g. a byte array, depending on the language) + +h3(#rest-compatibility). Forwards compatibility +* @(RSF1)@ The library must apply the "robustness principle":https://en.wikipedia.org/wiki/Robustness_principle in its processing of requests and responses with the Ably system. In particular, deserialization of Messages and related types, and associated enums, must be tolerant to unrecognised attributes or enum values. Such unrecognised values must be ignored. + +h2(#realtime). Realtime client library features + +The Ably Realtime client libraries establish and maintain a persistent connection to Ably and provide methods to publish and subscribe to messages over a low latency realtime connection. + +The Realtime library is a super-set of the REST library and as such all Realtime libraries provide the functionality available in the REST library in addition to Realtime-specific features. + +The threading and/or asynchronous model for each realtime library will vary by language and it is therefore up to the developer to decide on the best approach for each given client library. For example, Node.js and Ruby (EventMachine) use a similar callback single threaded evented approach that ensures all public methods are non-blocking. Java and .NET use a threaded model whereby the @Connection@ runs in its own thread. Go makes extensive use of goroutines and channels. + +h3(#realtimeclient). RealtimeClient + +* @(RTC1)@ Supports all the same @ClientOptions@ as the @RestClient@ in addition to: +** @(RTC1a)@ @echoMessages@ boolean is true by default. If false, it prevents messages originating from this connection being echoed back on the same connection +** @(RTC1b)@ @autoConnect@ boolean is true by default. If true, as soon as the client library is instanced, it will connect to Ably. If false, the client library will wait for an explicit @Connection#connect@ to be called before connecting +** @(RTC1c)@ @recover@ string, when set, will attempt to recover the connection state of a previous connection +** @(RTC1d)@ @realtimeHost@ string, when set, will modify the realtime endpoint host used by this client library +** @(RTC1e)@ @environment@ string, when set, will modify both the REST and realtime endpoint hosts by prefixing the environment to the default endpoint host with a hyphen delimiter. For example, a @RealtimeClient@ with an @environment@ of "sandbox", would use "sandbox-rest.ably.io" as the @restHost@ and @sandbox-realtime.ably.io@ as the @realtimeHost@. See "TO3k3":#TO3k3 for constraints. +** @(RTC1f)@ @transportParams@ map or equivalent, additional parameters to be sent in the querystring when initiating a realtime connection. Keys are @Strings@, values are @Stringifiable@ (a value that can be coerced to a string in order to be sent as a querystring parameter. Supported values should be at least strings, numbers, and booleans, with booleans stringified as @true@ and @false@. If this is unidiomatic to the language, the implementer may consider this as equivalent to @String@). +*** @(RTC1f1)@ If a key in @transportParams@ is one the library sends by default (for example, @v@ or @heartbeats@), the value in @transportParams@ takes precedence. +* @(RTC2)@ @RealtimeClient#connection@ attribute provides access to the underlying @Connection@ object +* @(RTC3)@ @RealtimeClient#channels@ attribute provides access to the underlying @Channels@ object +* @(RTC4)@ @RealtimeClient#auth@ attribute provides access to the @Auth@ object that was instanced with the @ClientOptions@ provided in the @RealtimeClient@ constructor +** @(RTC4a)@ Unlike the stateless REST client library, the @Auth#clientId@ is populated when the connection is established. The @CONNECTED@ @ProtocolMessage@ contains the confirmed @clientId@ for this connected client i.e. the client is considered identified. See "@RSA7b@":#RSA7b and "@RSA12@":#RSA12 for further info +* @(RTC5)@ @RealtimeClient#stats@ function: +** @(RTC5a)@ Proxy to @RestClient#stats@ presented with an async or threaded interface as appropriate +** @(RTC5b)@ Accepts all the same params as @RestClient#stats@ and provides all the same functionality +* @(RTC6)@ @RealtimeClient#time@ function: +** @(RTC6a)@ Proxy to @RestClient#time@ presented with an async or threaded interface as appropriate +* @(RTC7)@ The client library must use the configured timeouts specified in the @ClientOptions@, falling back to the "client library defaults":#defaults and defaults described in @ClientOptions@ below +* @(RTC8)@ For a realtime client, @Auth#authorize@ instructs the library to obtain a token using the provided @tokenParams@ and @authOptions@ and upgrade the current connection to use that token; or if not currently connected, to connect with the token. +** @(RTC8a)@ If the connection is in the @CONNECTED@ state and @auth#authorize@ is called or Ably requests a re-authentication (see "RTN22":#RTN22), the client must obtain a new token, then send an @AUTH@ @ProtocolMessage@ to Ably with an @auth@ attribute containing an @AuthDetails@ object with the token string +*** @(RTC8a1)@ If the authentication token change is successful, then Ably will send a new @CONNECTED@ @ProtocolMessage@. The @connectionDetails@ provided in the @CONNECTED@ @ProtocolMessage@ must override any existing defaults, see "RTN21":#RTN21. The @Connection@ should emit an @UPDATE@ event per @RTN24@. A test should exist that performs an upgrade of capabilities without any loss of continuity or connectivity during the upgrade process. Another test should exist where the capabilities are downgraded resulting in Ably sending an @ERROR@ @ProtocolMessage@ with a @channel@ property, causing the channel to enter the @FAILED@ state. That test must assert that the channel becomes failed soon after the token update and the reason is included in the channel state change event +*** @(RTC8a2)@ If the authentication token change fails, then Ably will send an @ERROR@ @ProtocolMessage@ triggering the connection to transition to the @FAILED@ state. A test should exist for a token change that fails (such as sending a new token with an incompatible @clientId@) +*** @(RTC8a3)@ The @authorize@ call should be indicated as completed with the new token or error only once realtime has responded to the @AUTH@ with either a @CONNECTED@ or @ERROR@ respectively. +*** @(RTC8a4)@ Tests must exist that verify the inband reauthorization mechanism described in @RTC8a@ succeeds in the cases of an Ably token string and a JWT token string. +** @(RTC8b)@ If the connection is in the @CONNECTING@ state when @auth#authorize@ is called, all current connection attempts should be halted, and after obtaining a new token the library should immediately initiate a connection attempt using the new token +*** @(RTC8b1)@ The @authorize@ call should be indicated as completed with the new token once the connection has moved to the @CONNECTED@ state, or with an error if the connection instead moves to the @FAILED@, @SUSPENDED@, or @CLOSED@ states +** @(RTC8c)@ If the connection is in the @DISCONNECTED@, @SUSPENDED@, @FAILED@, or @CLOSED@ state when @auth#authorize@ is called, after obtaining a token the library should move to the @CONNECTING@ state and initiate a connection attempt using the new token, and @RTC8b1@ applies. +* @(RTC9)@ @RealtimeClient#request@ is a wrapper around @RestClient#request@ (see "RSC19":#RSC19) delivered in an idiomatic way for the realtime library, e.g. in the case of Ruby, with an evented async callback interface +* @(RTC10)@ The client library should never register any listeners for internal use with the public @EventEmitter@ interfaces (such as @Connection#on@) or message/event subscription interfaces (such as @Channel#subscribe@) in such a way that a user of the library calling @Connection#off()@ or @Channel#unsubscribe()@ to remove all listeners would result in the library not working as expected +* @(RTC11)@ Unexpected internal exceptions, as defined in "@RSC20@":RSC20, must be handled as gracefully as possible and reported to Ably's error reporting service when enabled. The aim when handling unexpected exceptions should be to ensure that no invalid or inconsistent state can potentially be left after handling the exception; depending on circumstances the remedial action could include failing the transport, failing the connection, rejecting a message, reinitialising the library completely, etc. + +h3(#realtime-connection). Connection + +* @(RTN1)@ @Connection@ connects to the Ably service using a "websocket":/concepts/websockets connection. The "ably-js library":https://github.ably.io/ably/ably-js supports additional transports such as Comet and XHR streaming; however non-browser client libraries typically use only a websocket transport +* @(RTN2)@ The default host used for realtime "websocket":/concepts/websockets connections is @realtime.ably.io@, and the following query string params should be used when opening a new connection: +** @(RTN2a)@ @format@ should be @msgpack@ (default) or @json@ +** @(RTN2b)@ @echo@ should be @true@ by default; @false@ will prevent messages published by the client being echoed back +** @(RTN2d)@ @clientId@ contains the provided @clientId@ option of @ClientOptions@, unless @clientId@ is @null@ +** @(RTN2e)@ Depending on the authentication scheme, either @accessToken@ contains the token string, or @key@ contains the API key +** @(RTN2f)@ API version param @v@ should be the API version per "G4":#G4 +** @(RTN2g)@ Library and version param @lib@ should include the header value described in "RSC7b":#RSC7b. For example, the 1.0.0 version of the Javascript library would use the param @lib=js-1.0.0@ +* @(RTN3)@ If connection option @autoConnect@ is true, a connection is initiated immediately; otherwise a connection is only initiated following an explicit call to @connect()@ +* @(RTN4)@ The @Connection@ implements @EventEmitter@ and emits @ConnectionEvent@ events, where a @ConnectionEvent@ is either a @ConnectionState@ or @UPDATE@, and a @ConnectionState@ is either @INITIALIZED@, @CONNECTING@, @CONNECTED@, @DISCONNECTED@, @SUSPENDED@, @CLOSING@, @CLOSED@, or @FAILED@ +** @(RTN4a)@ It emits a @ConnectionState@ @ConnectionEvent@ for every connection state change +** @(RTN4h)@ It emits an @UPDATE@ @ConnectionEvent@ for changes to connection conditions for which the @ConnectionState@ (e.g. @CONNECTED@) does not change. (The library must never emit a @ConnectionState@ @ConnectionEvent@ for a state equal to the previous state) +** @(RTN4b)@ A new connection will emit the following events in order when connecting: @CONNECTING@, then @CONNECTED@ +** @(RTN4c)@ A connection will emit the following events when closing the connection: @CLOSING@, then @CLOSED@ +** @(RTN4d)@ @Connection#state@ attribute is the current state of the connection +** @(RTN4e)@ A @ConnectionStateChange@ object is emitted as the first argument for every @ConnectionEvent@ (including both @RTN4a@ connection state changes and @RTL4h@ @UPDATE@ events) +** @(RTN4f)@ The @ConnectionStateChange@ object may contain a @reason@ consisting of an @ErrorInfo@ object with details of the error that has occurred for the @Connection@. Any state change triggered by a @ProtocolMessage@ that contains an @error@ member should populate the @reason@ with that error in the corresponding state change event +** @(RTN4i)@ Optionally, for backwards compatibility with 0.8 libraries, the @Connection@ @EventEmitter@ can provide an overloaded method that supports @on(ConnectionState)@, but must issue a deprecation warning +* @(RTN5)@ A test should exist that instances many (50+) clients simultaneously and performs a few basic operations such as attaching to a channel, publishing a message, and expecting all of those messages to arrive on all clients to ensure that there are no concurrency issues with the client library +* @(RTN6)@ A @Connection@ is successful and considered @CONNECTED@ once the "websocket":/concepts/websockets connection is open and the initial @CONNECTED@ @ProtocolMessage@ has been received +* @(RTN21)@ If the @CONNECTED@ @ProtocolMessage@ contains a @connectionDetails@ property, the attributes within @ConnectionDetails@ will be used as the defaults for this client library, overriding any configured options at the time the @CONNECTED@ @ProtocolMessage@ is received +* @(RTN7)@ @ACK@ and @NACK@: +** @(RTN7a)@ All @ProtocolMessage@ @Presence@ and @Message@ objects sent to Ably expect either an @ACK@ or @NACK@ from Ably to confirm successful receipt and acceptance or failure respectively. For clarity, it is unnecessary to fail the publish operation of a message using a timer. Instead the client library can rely on: the realtime system will send an @ACK@ or @NACK@ when connected; the client library will fail all awaiting messages once @SUSPENDED@ (see "RTN7c":#RTN7c); upon reconnecting, the client will resend all message awaiting a response, and the realtime system in turn will respond with an @ACK@ or @NACK@ (see "RTN19a":#RTN19a) +** @(RTN7b)@ Every @ProtocolMessage@ sent must contain a unique serially incrementing @msgSerial@ integer value starting at zero. The @msgSerial@ along with the @count@ for incoming @ACK@ and @NACK@ @ProtocolMessages@ indicates which messages succeeded or failed to be delivered +** @(RTN7c)@ If a connection enters the @SUSPENDED@, @CLOSED@ or @FAILED@ state, or if the connection state is lost, and an @ACK@ or @NACK@ has not yet been received for a message, the client should consider the delivery of those messages as failed +* @(RTN22)@ Ably can request that a connected client re-authenticates by sending the client an @AUTH@ @ProtocolMessage@. The client must then immediately start a new authentication process as described in "RTC8":#RTC8 +** @(RTN22a)@ Ably reserves the right to forcibly disconnect a client that does not re-authenticate within an acceptable period of time, or at any time the token is deemed no longer valid. A client is forcibly disconnected following a @DISCONNECTED@ message containing an error code in the range @40140 <= code < 40150@. This will in effect force the client to re-authenticate and resume the connection immediately, see "RTN15h":#RTN15h +* @(RTN8)@ @Connection#id@ attribute: +** @(RTN8a)@ Is unset until connected +** @(RTN8b)@ Is a unique string provided by Ably. You should have a test to ensure multiple connected clients have unique connection IDs +* @(RTN9)@ @Connection#key@ attribute: +** @(RTN9a)@ Is unset until connected +** @(RTN9b)@ Is a unique private connection key provided by Ably that is used to reconnect and retain connection state following an unexpected disconnection. You should have a test to ensure multiple connected clients have unique connection keys +* @(RTN10)@ @Connection#serial@ attribute: +** @(RTN10a)@ Is unset until connected, and is then set from the @connectionSerial@ attribute of the @CONNECTED@ @ProtocolMessage@ +** @(RTN10b)@ Continues to be updated by every @ProtocolMessage@ received from Ably that contains a @connectionSerial@. A test should exist that checks that the serial is updated when a @ProtocolMessage@ is received with the value from that @ProtocolMessage@. It should not otherwise change; in particular, unlike the library-internal @msgSerial@, it should not change as a result of publishing a message or receiving an @ACK@. +* @(RTN11)@ @Connection#connect@ function: +** @(RTN11a)@ Explicitly connects to the Ably service if not already connected +** @(RTN11b)@ If the state is @CLOSING@, the client should make a new connection with a new transport instance and remove all references to the old one. In particular, it should make sure that, when the @CLOSED@ @ProtocolMessage@ arrives for the old connection, it doesn't affect the new one. +** @(RTN11c)@ If the state is @DISCONNECTED@ or @SUSPENDED@, aborts the retry process described in "RTN14d":#RTN14d and "RTN14e":#RTN14e and immediately tries to reconnect. +** @(RTN11d)@ If the state is @CLOSED@ or @FAILED@, transitions all the channels to @INITIALIZED@ and unsets their @Channel.errorReason@, unsets the @Connection.errorReason@, clears all connection state (including in particular @Connection.recoveryKey@), and resets the @msgSerial@ to @0@ +* @(RTN12)@ @Connection#close@ function: +** @(RTN12f)@ If the connection state is @CONNECTING@, moves immediately to @CLOSING@. If the connection attempt succeeds, ie. a @CONNECTED@ @ProtocolMessage@ arrives from Ably, then do as specified in "RTN12a":#RTN12a. If it doesn't succeed, move to @CLOSED@. +** @(RTN12a)@ If the connection state is @CONNECTED@, sends a @CLOSE@ @ProtocolMessage@ to the server, transitions the state to @CLOSING@ and waits for a @CLOSED@ @ProtocolMessage@ to be received +** @(RTN12b)@ If the @CLOSED@ @ProtocolMessage@ is not received within the "default realtime request timeout":#defaults, the transport will be disconnected and the connection will automatically transition to the @CLOSED@ state +** @(RTN12c)@ If the transport is abruptly closed following a @CLOSE@ @ProtocolMessage@ being sent, then the connection will automatically transition to the @CLOSED@ state +** @(RTN12d)@ If the connection state is @DISCONNECTED@ or @SUSPENDED@, aborts the retry process described in "RTN14d":#RTN14d and "RTN14e":#RTN14e and transitions the connection immediately to the @CLOSED@ state +* @(RTN13)@ @Connection#ping@ function: +** @(RTN13d)@ If the connection state is @CONNECTING@ or @DISCONNECTED@, do the operation once the connection state is @CONNECTED@ +** @(RTN13a)@ Will send a @ProtocolMessage@ with action @HEARTBEAT@ the Ably service when connected and expects a @HEARTBEAT@ message in response. If the client library language supports callbacks, then the callback will be called with the response time or error +** @(RTN13b)@ Will indicate an error if in, or has transitioned to, the @INITIALIZED@, @SUSPENDED@, @CLOSING@, @CLOSED@ or @FAILED@ state +** @(RTN13c)@ Will fail if a @HEARTBEAT@ @ProtocolMessage@ is not received within the "default realtime request timeout":#defaults +** @(RTN13e)@ The @ProtocolMessage@ sent should include an @id@ property, with value a random string. If so, only a @HEARTBEAT@ response which includes an @id@ property with the same value should be considered a response to that ping, in order to disambiguate from normal heartbeats and other pings. +* @(RTN14)@ @Connection@ opening failures: +** @(RTN14a)@ If an API key is invalid, then the connection will transition to the @FAILED@ state and the @Connection#errorReason@ will be set on the @Connection@ object as well as the emitted @ConnectionStateChange@ +** @(RTN14b)@ If a connection request fails due to an @ERROR@ @ProtocolMessage@ being received by the client containing a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@) and an empty @channel@ attribute, then if the token is renewable, a single attempt to create a new token should be made and a new connection attempt initiated using the newly created token. If the attempt to create a new token fails, or the subsequent connection attempt fails due to another token error, then the connection will transition to the @DISCONNECTED@ state, and the @Connection#errorReason@ should be set. (If no means to renew the token is provided, @RSA4a@ applies) +** @(RTN14g)@ If an @ERROR@ @ProtocolMessage@ with an empty @channel@ attribute is received for any reason other than "RTN14b":#RTN14b, then the connection will transition to the @FAILED@ state and the server will terminate the connection. Additionally the @Connection#errorReason@ must be set with the error from the @ERROR@ @ProtocolMessage@ +** @(RTN14c)@ A new connection attempt will fail if not connected within the "default realtime request timeout":#defaults +** @(RTN14d)@ If a connection attempt fails for any recoverable reason (i.e. a network failure, a timeout such as "RTN14c":#RTN14c, or a disconnected response, other than a token failure "RTN14b":#RTN14b), the @Connection#state@ will transition to @DISCONNECTED@, the @Connection#errorReason@ will be updated, a @ConnectionStateChange@ with the @reason@ will be emitted, and new connection attempts will periodically be made until the maximum time in that state threshold is reached. The @retryIn@ attribute of the @ConnectionStateChange@ object will contain the time in milliseconds until the next connection attempt. See the @disconnectedRetryTimeout@ of @ClientOptions@ below. Each time a new connection attempt is made the state will transition to @CONNECTING@ and then to @CONNECTED@ if successful, or @DISCONNECTED@ if unsuccessful and the "default @connectionStateTtl@":#defaults has not been exceeded. Fallback hosts are used for new connection attempts in accordance with "RTN17":#RTN17. +** @(RTN14e)@ Once the connection state has been in the @DISCONNECTED@ state for more than the "default @connectionStateTtl@":#defaults, the state will change to @SUSPENDED@ and be emitted with the @reason@, and the @Connection#errorReason@ will be updated. In this state, a new connection attempt will be made periodically as specified within @suspendedRetryTimeout@ of @ClientOptions@ +** @(RTN14f)@ The connection will remain in the @SUSPENDED@ state indefinitely, whilst periodically attempting to reestablish a connection +* @(RTN15)@ @Connection@ failures once @CONNECTED@: +** @(RTN15h)@ If a @DISCONNECTED@ message is received from Ably, then that transport will subsequently be closed by Ably +*** @(RTN15h1)@ If the @DISCONNECTED@ message contains a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@) and the library does not have a means to renew the token, the connection will transition to the @FAILED@ state and the @Connection#errorReason@ will be set +*** @(RTN15h2)@ If the @DISCONNECTED@ message contains a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@) and the library has the means to renew the token, a single attempt to create a new token should be made and a new connection attempt initiated using the new token. If the token creation fails or the next connection attempt fails due to a token error, the connection will transition to the @DISCONNECTED@ state and the @Connection#errorReason@ will be set. +** @(RTN15i)@ If an @ERROR@ @ProtocolMessage@ is received, this indicates a fatal error in the connection. The server will close the transport immediately after. The client should transition to the @FAILED@ state triggering all attached channels to transition to the @FAILED@ state as well. Additionally the @Connection#errorReason@ should be set with the error received from Ably +** @(RTN15a)@ If a @Connection@ transport is disconnected unexpectedly or because a token has expired, then the @Connection@ manager will immediately attempt to reconnect and restore the connection state. Connection state recovery is provided by the Ably service and ensures that whilst the client is disconnected, all events are queued and channel state is retained on the Ably servers. When a new connection is made with the correct connection recovery key, the client is able to catch up by receiving the queued @ProtocolMessages@ from Ably. +** @(RTN15g)@ Connection state is only maintained server-side for a brief period, given by the @connectionStateTtl@ in the @connectionDetails@, see "CD2f":#CD2f. If a client has been disconnected for longer than the @connectionStateTtl@, it should not attempt to resume. Instead, it should clear the local connection state, and any connection attempts should be made as for a fresh connection +*** @(RTN15g1)@ This check should be made before each connection attempt. It is generally not sufficient to merely clear the connection state when moving to @SUSPENDED@ state (though that may be done too), since the device may have been sleeping / suspended, in which case it may have been many hours since it was last actually connected, even though, having been in the @CONNECTED@ state when it was put to sleep, it has only moved out of that state very recently (after waking up and noticing it's no longer connected) +*** @(RTN15g2)@ Another consequence of that is that the measure of whether the client been disconnected for too long (for the purpose of this check) cannot just be whether the client left the @CONNECTED@ state more than @connectionStateTtl@ ago. Instead, it should be whether the difference between the current time and the last activity time is greater than the sum of the @connectionStateTtl@ and the @maxIdleInterval@, where the last activity time is the time of the last known actual sign of activity from Ably per "RTN23a":#RTN23a +*** @(RTN15g3)@ When a connection attempt succeeds after the connection state has been cleared in this way, channels that were previously @ATTACHED@, @ATTACHING@, or @SUSPENDED@ must be automatically reattached, just as if the connection was a resume attempt which failed per "RTN15c3":#RTN15c3 +** @(RTN15b)@ In order for a connection to be resumed and connection state to be recovered, the client must have received a @CONNECTED@ ProtocolMessage which will include a private connection key. To resume that connection, the library reconnects to the "websocket":/concepts/websockets endpoint with two additional querystring params: +*** @(RTN15b1)@ @resume@ is the @ProtocolMessage#connectionKey@ from the most recent @CONNECTED@ @ProtocolMessage@ received +*** @(RTN15b2)@ @connectionSerial@ is the most recent @ProtocolMessage#connectionSerial@ (from any message, not just a @CONNECTED@) received from Ably (equivalently, the @Connection#serial@) +** @(RTN15c)@ The system's response to a resume request will be one of the following: +**** @(RTN15c1)@ @CONNECTED@ @ProtocolMessage@ with the same @connectionId@ as the current client, and no @error@. In this case, the server is indicating that the resume succeeded, all channels are still attached, and all backlog messages are available. The client should not change the state of attached channels, and immediately process any queued messages for that channel +**** @(RTN15c2)@ @CONNECTED@ @ProtocolMessage@ with the same @connectionId@ as the current client, and an @error@. In this case, the server is indicating that the resume succeeded but with a non-fatal error, all channels are still attached, and some backlog messages may be unavailable. The @ErrorInfo@ received should be set as the @reason@ in the @CONNECTED@ event, and the @Connection#errorReason@ should be set. The client should not change the state of attached channels, and immediately process any queued messages for that channel. Any channels that are not resumed in full may receive an @ATTACHED@ @ProtocolMessage@ with an @error@, see "RTL12":#RTL12 +**** @(RTN15c3)@ @CONNECTED@ @ProtocolMessage@ with a new @connectionId@, and an error in @error@. In this case, a new connection has been established, the resume was unsuccessful, the channels are no longer attached, and the error indicates the cause of the unsuccessful resume. The @ErrorInfo@ should be set as the @reason@ in the @CONNECTED@ event, and the @Connection#errorReason@ should be set. The client library should initiate an attach for channels that are in the @SUSPENDED@ state. For all channels in the @ATTACHING@ or @ATTACHED@ state, the client library should fail any previously queued messages for that channel and initiate a new attach i.e. a new @ATTACH@ @ProtocolMessage@ must be sent for each channel. Finally, the internal @msgSerial@ counter is reset so that the first message published to Ably will contain a @msgSerial@ value of @0@ +**** @(RTN15c5)@ @ERROR@ @ProtocolMessage@ indicating a failure to authenticate as a result of a token error (see "RTN15h":#RTN15h). The transport will be closed by the server. The spec described in "RTN15h":#RTN15h must be followed for a connection being resumed with a token error +**** @(RTN15c4)@ Any other @ERROR@ @ProtocolMessage@ indicating a fatal error in the connection. The server will close the transport immediately after. The client should transition to the @FAILED@ state triggering all attached channels to transition to the @FAILED@ state as well. Additionally the @Connection#errorReason@ will be set should be set with the error received from Ably +** @(RTN15f)@ @ACK@ and @NACK@ responses for published messages can only ever be received on the transport connection on which those messages were sent. Therefore, once a transport drops, the client library must either fail the publish attempt, or re-attempt by re-sending the messages on a new transport if the resume was successful (i.e. the @CONNECTED@ response includes the expected @connectionId@) +** @(RTN15d)@ Client libraries should have test coverage to ensure connection state recovery is working as expected by forcibly disconnecting a client and checking that messages published on channels are delivered once the connection is resumed +** @(RTN15e)@ When a connection is resumed, the @Connection#key@ may change and will be provided in the first @CONNECTED@ @ProtocolMessage#connectionDetails@ when the connection is established. The client library must update the @Connection#key@ value with the new @connectionKey@ value every time +* @(RTN20)@ When the client library can subscribe to the Operating System events for network/internet connectivity changes: +** @(RTN20a)@ When @CONNECTED@, @CONNECTING@ or @DISCONNECTING@, if the operating system indicates that the underlying internet connection is no longer available, then the client library should immediately transition the state to @DISCONNECTED@ with emit a state change with an appropriate @reason@. This state change will automatically trigger the client library to attempt to reconnect, see @RTN15@ above +** @(RTN20b)@ When @DISCONNECTED@ or @SUSPENDED@, if the operating system indicates that the underlying internet connection is now available, the client library should immediately attempt to connect +* @(RTN16)@ @Connection@ recovery: +** @(RTN16a)@ Connection recovery follows the resume spec "RTN15c":#RTN15c in respect to the expected response from the server. However, connection recovery is different in that the library has no state at the time of connection and recovers the connection based as a result of @recover@ key being explicitly provided to the Realtime library when instanced. Once a connection is recovered, all channels must be explicitly attached by the developer +** @(RTN16b)@ @Connection#recoveryKey@ is an attribute composed of the @connectionKey@, and the latest @connectionSerial@ received on the connection, and the current @msgSerial@ +** @(RTN16c)@ @Connection#recoveryKey@ becomes @Null@ when a connection is explicitly @CLOSED@ or @CLOSED@ by the server, as connection state is not retained for connections closed intentionally. The @Connection#key@ and @Connection#id@ is set to @Null@ +** @(RTN16d)@ When a connection is successfully recovered, the @Connection#id@ will be identical to the @id@ of the connection that was recovered, and @Connection#key@ will always be updated to the @ConnectionDetails#connectionKey@ provided in the first @CONNECTED@ @ProtocolMessage@ +** @(RTN16e)@ If the @recover@ option is missing or no longer valid when connecting to Ably, the client will connect anyway, but emit a @ConnectionStateChange@ with a @reason@, and will additionally set the @Connection#errorReason@ with an @ErrorInfo@ object describing the failure +** @(RTN16f)@ The @msgSerial@ component of the @recoveryKey@, unlike the other two components, is not sent to Ably, but rather is used to set the library internal @msgSerial@. (If the recover fails, the counter should be reset to 0 per "RTN15c3":#RTN15c3 ) +* @(RTN17)@ Host Fallback +** @(RTN17b)@ The fallback behavior described below only applies when the default @realtime.ably.io@ endpoint is being used and has not been overriden (see "RTC1d":#RTC1d and "RTC1e":#RTC1e), @ClientOptions#fallbackHostsUseDefault@ is @true@, or an array of @ClientOptions#fallbackHosts@ is provided. +** @(RTN17a)@ By default, every connection attempt is first attempted to the default primary host @realtime.ably.io@ (unless overriden in @ClientOptions#realtimeHost@), which, through DNS, is automatically routed to the client's closest datacenter. The client library must always prefer the default endpoint (closest datacenter), even if a previous connection attempt to that endpoint has failed. (That is, @RSC15f@ does not apply) +** @(RTN17c)@ In the case of an error necessitating use of an alternative host (see "RTN17d":#RTN17d), the @Connection@ manager should first check if an internet connection is available by issuing a @GET@ request to @https://internet-up.ably-realtime.com/is-the-internet-up.txt@. If the request succeeds and the text "yes" is included in the body, then the client library can assume it has a viable internet connection and should then immediately retry the connection against all fallback hosts to find an alternative healthy datacenter. The five default fallback hosts are @[a-e].ably-realtime.com@ and should be attempted in random order. The connection requests must include a matching Host header as this is necessary when fallbacks are proxied through a CDN. See "RSC15a":#RSC15a for details on how custom fallback hosts are specified and used +** @(RTN17d)@ Errors that necessitate use of an alternative host include: host unresolvable or unreachable, connection timeout, or a @DISCONNECTED@ response with an @error.statusCode@ in the range @500 <= code <= 504@ or HTTP response status code in the range @500 <= code <= 504@. Attempting to reconnect to a fallback host for other failure conditions will not fix the problem and will simply increase the load on other data-centers unnecessarily +** @(RTN17e)@ If the realtime client is connected to a fallback host endpoint, then for the duration that the transport is connected to that host, all HTTP requests, such as history or token requests, should be first attempted to the same datacenter the realtime connection is established with i.e. the same fallback host must be used as the default HTTP request host. If however the HTTP request against that fallback host fails, then the normal fallback host behavior should be followed attempting the request against another fallback host as described in "RSC15":#RSC15 +* @(RTN19)@ Transport state side effects - when a transport is upgraded or disconnected for any reason: +** @(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. The Ably service is responsible for keeping track of messages, ignoring duplicates and responding with suitable @ACK@/@NACK@ messages +** @(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 +* @(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@, that transport should be disconnected. This requirement is not mandatory; in deciding whether to implement, client library developers should take into account whether the transport in question is susceptible to undetected dropped connections. 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":/concepts/websockets ping frames) to satisfy this. So for example, for "websocket transports":/concepts/websockets, if the client is able to observe websocket pings, then it should send @heartbeats=false@. If not, it should send @heartbeats=true@. +* @(RTN24)@ A connected client may receive a @CONNECTED@ @ProtocolMessage@ from Ably at any point (though is typically triggered by a reauth, see @RTC8a@). The @connectionDetails@ in the @ProtocolMessage@ must override any stored details, see @RTN21@. The @Connection@ should emit an @UPDATE@ event with a @ConnectionStateChange@ object, which should have both @previous@ and @current@ attributes set to @CONNECTED@, and the @reason@ attribute set to to the @error@ member of the @CONNECTED@ @ProtocolMessage@ (if any). (Note that @UPDATE@ should be the only event emitted: in particular, the library must not emit an @CONNECTED@ event if the client was already connected, see @RTN4h@). + +h3(#realtime-channels). Channels + +* @(RTS1)@ @Channels@ is a collection of @Channel@ objects accessible through @Realtime#channels@ +* @(RTS2)@ Methods should exist to check if a channel exists or iterate through the existing channels +* @(RTS3)@ @Channels#get@ function: +** @(RTS3a)@ Creates a new @Channel@ object for the specified channel if none exists, or returns the existing channel. @ChannelOptions@ can be specified when instancing a new @Channel@ +** @(RTS3b)@ If options are provided, the options are set on the @Channel@ when creating a new @Channel@ +** @(RTS3c)@ Accessing an existing @Channel@ with options in the form @Channels#get(channel, options)@ will update the options on the channel and then return the existing @Channel@ object +* @(RTS4)@ @Channels#release@ function: +** @(RTS4a)@ Detaches the channel and then releases the channel resource i.e. it's deleted and can then be garbage collected + +h3(#realtime-channel). Channel + +* @(RTL1)@ As soon as a @Channel@ becomes attached, all incoming messages and presence messages (where 'incoming' is defined as 'received from Ably over the realtime transport') are processed and emitted where applicable. @PRESENCE@ and @SYNC@ messages are passed to the @Presence@ object ensuring it maintains a map of current members on a channel in realtime +* @(RTL2)@ The @Channel@ implements @EventEmitter@ and emits @ChannelEvent@ events, where a @ChannelEvent@ is either a @ChannelState@ or @UPDATE@, and a @ChannelState@ is either @INITIALIZED@, @ATTACHING@, @ATTACHED@, @DETACHING@, @DETACHED@, @SUSPENDED@ and @FAILED@ +** @(RTL2a)@ It emits a @ChannelState@ @ChannelEvent@ for every channel state change +** @(RTL2g)@ It emits an @UPDATE@ @ChannelEvent@ for changes to channel conditions for which the @ChannelState@ (e.g. @ATTACHED@) does not change. (The library must never emit a @ChannelState@ @ChannelEvent@ for a state equal to the previous state) +** @(RTL2b)@ @Channel#state@ attribute is the current state of the channel +** @(RTL2d)@ A @ChannelStateChange@ object is emitted as the first argument for every @ChannelEvent@ (including both @RTL2a@ state changes and @RTL2g@ @UPDATE@ events). It may optionally contain a @reason@ consisting of an @ErrorInfo@ object; any state change triggered by a @ProtocolMessage@ that contains an @error@ member should populate the @reason@ with that error in the corresponding state change event +** @(RTL2f)@ When a channel @ATTACHED@ @ProtocolMessage@ is received, the @ProtocolMessage@ may contain a @RESUMED@ bit flag indicating that the channel has been resumed. The corresponding @ChannelStateChange@ (either @ATTACHED@ per @RTL2a@, or @UPDATE@ per @RTL12@) will contain a @resumed@ boolean attribute with value @true@ if the bit flag @RESUMED@ was included. When @resumed@ is @true@, this indicates that the channel attach resumed the channel state from an existing connection and there has been no loss of message continuity. In all other cases, @resumed@ is false. A test should exist to ensure that @resumed@ is always false when a channel first becomes @ATTACHED@, it is @true@ when the channel is @ATTACHED@ following a successful "connection recovery":#RTN16, and is @false@ when the channel is @ATTACHED@ following a failed "connection recovery":#RTN16 +** @(RTL2h)@ Optionally, for backwards compatibility with 0.8 libraries, the @Channel@ @EventEmitter@ can provide an overloaded method that supports @on(ChannelState)@, but must issue a deprecation warning +* @(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 @Channel#errorReason@ +** @(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, then a @SUSPENDED@ channel will initiate an attach operation and transition to @ATTACHING@. If the attach operation times out, the channel should return to the @SUSPENDED@ state (see "RTL4f":#RTL4f) +* @(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)@ @Channel#attach@ function: +** @(RTL4a)@ If already @ATTACHED@ nothing is done +** @(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 @CLOSED@, @CLOSING@, @SUSPENDED@ or @FAILED@, the @attach@ request results in an error +** @(RTL4i)@ If the connection state is @INITIALIZED@, @CONNECTING@ or @DISCONNECTED@, do the operation once the connection state is @CONNECTED@ +** @(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 +** @(RTL4f)@ Once an @ATTACH@ @ProtocolMessage@ is sent, if an @ATTACHED@ @ProtocolMessage@ is not received within the "default realtime request timeout":#defaults, 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)@ If the language permits, a callback can be provided that is called when the channel is attached successfully or the attach fails and the @ErrorInfo@ error is passed as an argument to the callback +** @(RTL4e)@ If the user does not have sufficient permissions to attach to the channel, the channel will transition to @FAILED@ and set the @Channel#errorReason@ +* @(RTL5)@ @Channel#detach@ function: +** @(RTL5a)@ If the channel state is @INITIALIZED@ or @DETACHED@ nothing is done +** @(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 +** @(RTL5h)@ If the connection state is @INITIALIZED@, @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 +** @(RTL5f)@ Once a @DETACH@ @ProtocolMessage@ is sent, if a @DETACHED@ @ProtocolMessage@ is not received within the "default realtime request timeout":#defaults, the detach request should be treated as though it has failed and the channel will return to its previous 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)@ @Channel#publish@ function: +** @(RTL6a)@ Messages are encoded in the same way as the REST @Channel#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 +** @(RTL6b)@ An optional callback can be provided to the @#publish@ method that is called when the message is successfully delivered or upon failure with the appropriate @ErrorInfo@ error. A test should exist to publish lots of messages on a few connections to ensure all message success callbacks are called for all messages published +** @(RTL6i)@ Expects either a @Message@ object, an array of @Message@ objects, or a @name@ string and @data@ payload: +*** @(RTL6i1)@ When @name@ and @data@ (or a @Message@) is provided, a single @ProtocolMessage@ containing one @Message@ is published to Ably +*** @(RTL6i2)@ When an array of @Message@ objects is provided, a single @ProtocolMessage@ is used to publish all @Message@ objects in the array. +*** @(RTL6i3)@ Allows @name@ and or @data@ to be @null@. If any of the values are @null@, then key is not sent to Ably i.e. a payload with a @null@ value for @data@ would be sent as follows @{ "name": "click" }@ +** @(RTL6c)@ Connection and channel state conditions: +*** @(RTL6c1)@ If the connection is @CONNECTED@ and the channel is @INITIALIZED@, @ATTACHED@, @DETACHED@, @ATTACHING@, or @DETACHING@ then the messages are published immediately +*** @(RTL6c2)@ If the connection is @INITIALIZED@, @CONNECTING@ or @DISCONNECTED@, and @ClientOptions#queueMessages@ has not been explicitly set to false, then the message will be queued and delivered as soon as the connection is @CONNECTED@ and the channel is in a state in which publishing is permitted per @RTL6c1@ +*** @(RTL6c4)@ If the connection is @SUSPENDED@, @CLOSING@, @CLOSED@, or @FAILED@, or the channel is @SUSPENDED@ or @FAILED@, the operation will result in an error +*** @(RTL6c5)@ A publish should not trigger an implicit attach (in contrast to earlier version of this spec) +** @(RTL6d)@ The protocol permits @Message@ s that have been queued to be sent in a single @ProtocolMessage@ , by bundling them into the @ProtocolMessage#messages@ or @ProtocolMessage#presence@ array. In general, the client library SHOULD NOT do this. If it does, it MUST conform to all of the following constraints: +*** @(RTL6d1)@ The resulting @ProtocolMessage@ must not exceed the @maxMessageSize@ +*** @(RTL6d2)@ Messages can only be bundled together if they have the same @clientId@ value (or both have no @clientId@ set). (Note that this constraint only applies to what the client library can autonomously do as part of queuing messages, not to what the user can do by publishing an array of @Messages@. It exists because if any @Message@ in a @ProtocolMessage@ has an invalid @clientId@, the entire @ProtocolMessage@ is rejected. This is fine if the user has deliberately published the @Messages@ together – they requested atomicity – but not if the client library has bundled them without the user's knowledge) +*** @(RTL6d3)@ Messages can only be bundled together if they are for the same @channel@ +*** @(RTL6d4)@ Messages can only be bundled together if they are of the same type (that is, @Message@ versus @PresenceMessage@) +*** @(RTL6d5)@ Only contiguous messages in the queue can be bundled together. For example, if the user publishes three messages, A, B, and C, of which A and C could be bundled together under @RTL6d1-4@ but B could not, then no bundling should occur +*** @(RTL6d6)@ The order of messages in the resulting @ProtocolMessage@ Messages must match the publish order. For example, if the user publishes @Message@ D, then the @Message@ array [E, F], then @Message@ G, the final @messages@ array should be [D, E, F, G] +*** @(RTL6d7)@ Messages must not be bundled if any have had had their @Message.id@ property set +** @(RTL6e)@ Unidentified clients using "Basic Auth":https://en.wikipedia.org/wiki/Basic_access_authentication (i.e. any @clientId@ is permitted as no @clientId@ specified): +*** @(RTL6e1)@ When a @Message@ with a @clientId@ value is published, Ably will accept and publish that message with the provided @clientId@. A test should assert that the @clientId@ of the published @Message@ is populated +** @(RTL6g)@ Identified clients with a @clientId@ (as a result of either an explicitly configured @clientId@ in @ClientOptions@, or implicitly through Token Auth): +*** @(RTL6g1)@ When publishing a @Message@ with the @clientId@ attribute set to @null@: +**** @(RTL6g1a)@ It is unnecessary for the client to set the @clientId@ of the @Message@ before publishing +**** @(RTL6g1b)@ Ably will assign a @clientId@ upon receiving the @Message@. A test should assert that the @clientId@ value is populated for the @Message@ when received +*** @(RTL6g2)@ When publishing a @Message@ with the @clientId@ attribute value set to the identified client's @clientId@, Ably will accept the message and publish it. A test should assert that the @clientId@ value is populated for the @Message@ when received +*** @(RTL6g3)@ When publishing a @Message@ with a different @clientId@ attribute value from the identified client's @clientId@, the client library should reject that publish operation immediately. The message should not be sent to Ably and it should result in an error, typically in the form of an error callback. The connection and channel must remain available for further operations +*** @(RTL6g4)@ When using Token Auth, unless a @clientId@ has been provided in @ClientOptions@ or inferred following authentication, the client library is unidentified and will not be constrained when publishing messages with any explicit @clientId@. If a @Message@ with a @clientId@ value is published before the @clientId@ is configured or inferred following authentication, the client library should not reject any explicit @clientId@ specified in a message. A test should instance a library without an explicit @clientId@ and an @authCallback@ that returns a @tokenDetails@ object with a @clientId@, then publish a message with the same @clientId@ before authentication, and ensure that the message is published following authentication and received back with the @clientId@ intact. A further test should follow the same sequence of events, but should instead use an incompatible @clientId@ in the message, expecting that the message is rejected by the Ably service and the message error should contain the server error message, and the connection and channel should remain available for further operations +** @(RTL6h)@ The @publish(name, data)@ form should not take any arguments other than those two. If a client library has supported additional arguments to the @(name, data)@ form (e.g. separate arguments for @clientId@ and @extras@, or a single @attributes@ argument) in any 1.x version, it should continue to do so until version 2.0. +** @(RTL6f)@ @Message#connectionId@ should match the current @Connection#id@ for all published messages, a test should exist to ensure the @connectionId@ for received messages matches that of the publisher +* @(RTL7)@ @Channel#subscribe@ function: +** @(RTL7a)@ Subscribe with no arguments subscribes a listener to all messages +** @(RTL7b)@ Subscribe with a single name argument subscribes a listener to only messages whose @name@ member matches the string name +** @(RTL7c)@ Implicitly attaches the @Channel@ if the channel is in the @INITIALIZED@ state. The optional callback, if provided, is called according to "@RTL4d@":#RTL4d based on the implicit attach operation. The listener will always be registered regardless of the implicit attach result +** @(RTL7d)@ Messages delivered are automatically decoded based on the @encoding@ attribute; see REST @Channel@ encoding features. Tests should exist to publish and subscribe to encoded messages using the "AES 128":https://github.com/ably/ably-common/blob/master/test-resources/crypto-data-128.json and "AES 256":https://github.com/ably/ably-common/blob/master/test-resources/crypto-data-256.json fixture test data +** @(RTL7e)@ If a message cannot be decoded or decrypted successfully, it should be delivered to the listener with the @encoding@ attribute set indicating the residual encoding state, and an error should be logged +** @(RTL7f)@ A test should exist ensuring published messages are not echoed back to the subscriber when @echoMessages@ is set to false in the @Realtime@ library constructor +* @(RTL8)@ @Channel#unsubscribe@ function: +** @(RTL8a)@ Unsubscribe with no arguments unsubscribes the provided listener to all messages if subscribed +** @(RTL8b)@ Unsubscribe with a single name argument unsubscribes the provided listener if previously subscribed with a name-specific subscription +* @(RTL9)@ @Channel#presence@ attribute: +** @(RTL9a)@ Returns the @Presence@ object for this channel +* @(RTL10)@ @Channel#history@ function: +** @(RTL10a)@ Supports all the same params as REST @Channel#history@ +** @(RTL10b)@ Additionally supports the param @untilAttach@, which if true, will only retrieve messages prior to the moment that the channel was attached or emitted an @UPDATE@ indicating loss of continuity. This bound is specified by passing the querystring param @fromSerial@ with the @Channel#properties.attachSerial@ assigned to the channel in the @ATTACHED@ @ProtocolMessage@ (see "RTL15a":#RTL15a). If the @untilAttach@ param is specified when the channel is not attached, it results in an error +** @(RTL10c)@ Returns a @PaginatedResult@ page containing the first page of messages in the @PaginatedResult#items@ attribute returned from the history request +** @(RTL10d)@ A test should exist that publishes messages from one client, and upon confirmation of message delivery, a history request should be made on another client to ensure all messages are available +* @(RTL12)@ An attached channel may receive an additional @ATTACHED@ @ProtocolMessage@ from Ably at any point. (This is typically triggered following a transport being upgraded or resumed to indicate a partial loss of message continuity on that channel, in which case the @ProtocolMessage@ will have a @resumed@ flag set to false). If and only if the @resumed@ flag is false, this should result in the channel emitting an @UPDATE@ event with a @ChannelStateChange@ object. The @ChannelStateChange@ object should have both @previous@ and @current@ attributes set to @attached@, the @reason@ attribute set to to the @error@ member of the @ATTACHED@ @ProtocolMessage@ (if any), and the @resumed@ attribute set per the @RESUMED@ bitflag of the @ATTACHED@ @ProtocolMessage@. (Note that @UPDATE@ should be the only event emitted: in particular, the library must not emit an @ATTACHED@ event if the channel was already attached, see @RTL2g@). +* @(RTL15)@ @Channel#properties@ attribute is a @ChannelProperties@ object representing properties of the channel state. @properties@ is a publicly accessible member of the channel, but it is an experimental and unstable API. It has the following attributes: +** @(RTL15a)@ @attachSerial@ is unset when the channel is instanced, and is updated with the @channelSerial@ from each @ATTACHED@ @ProtocolMessage@ received from Ably with a matching @channel@ attribute. The @attachSerial@ value is used for @untilAttach@ queries, see "RTL10b":#RTL10b and "RTP12b":#RTP12b. +* @(RTL13)@ If the channel receives a server initiated @DETACHED@ message when it is in the @ATTACHING@, @ATTACHED@ or @SUSPENDED@ state (i.e. the client has not explicitly requested a detach putting the channel into the @DETACHING@ state), then the following applies: +** @(RTL13a)@ If the channel is in the @ATTACHED@ or @SUSPENDED@ states, an attempt to reattach the channel should be made immediately by sending a new @ATTACH@ message and the channel should transition to the @ATTACHING@ state with the error emitted in the @ChannelStateChange@ event. +** @(RTL13b)@ If the attempt to re-attach fails, or if the channel was already in the @ATTACHING@ state, the channel will transition to the @SUSPENDED@ state and the error will be emitted in the @ChannelStateChange@ event. An attempt to re-attach the channel automatically will then be made after the period defined in @ClientOptions#channelRetryTimeout@. When re-attaching the channel, the channel will transition to the @ATTACHING@ state. If that request to attach fails i.e. it times out or a @DETACHED@ message is received, then the process described here in @RTL13b@ will be repeated, indefinitely +** @(RTL13c)@ If the connection is no longer @CONNECTED@, then the automatic attempts to re-attach the channel described in "RTL13b":#RTL13b must be cancelled as any implicit channel state changes subsequently will be covered by "RTL3":#RTL3 +* @(RTL14)@ If an @ERROR ProtocolMessage@ is received for this channel (the channel attribute matches this channel's name), then the channel should immediately transition to the FAILED state, and the @Channel.errorReason@ should be set + +h3(#realtime-presence). Presence + +* @(RTP1)@ When a channel @ATTACHED@ @ProtocolMessage@ is received, the @ProtocolMessage@ may contain a @HAS_PRESENCE@ bit flag indicating that there are currently members present on the channel, see "TR3":#TR3 . 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@) +** @(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) +*** @(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 @Presence@ 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. +* @(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 @:@ +** @(RTP18c)@ a @SYNC@ may also be sent with no @channelSerial@ attribute. In this case, the sync data is entirely contained within that @ProtocolMessage@ +* @(RTP19)@ If the @PresenceMap@ has existing members when a @SYNC@ is started, the client library must ensure that members no longer present on the channel are removed from the local @PresenceMap@ once the sync is complete. In order to do this, the client library must keep track of any members that have not been added or updated in the @PresenceMap@ during the sync process. Note that a member can be added or updated when received in a @SYNC@ message or when received in a @PRESENCE@ message during the sync process. Once the sync is complete, the members in the @PresenceMap@ that have not been added or updated should be removed from the @PresenceMap@ and a @LEAVE@ event should be published for each. The @PresenceMessage@ published should contain the original attributes of the presence member with the @action@ set to @LEAVE@, @PresenceMessage#id@ set to @null@, and the @timestamp@ set to the current time. This behavior should be tested as follows: @ENTER@ presence on a channel, wait for @SYNC@ to complete, inject a member directly into the local @PresenceMap@ so that it only exists locally and not on the server, send a @SYNC@ message with the @channel@ attribute populated with the current channel which will trigger a server initiated @SYNC@. A @LEAVE@ event should then be published for the injected member, and checking the @PresenceMap@ should reveal that the member was removed and the valid member entered for this connection is still present +** @(RTP19a)@ If the @PresenceMap@ has existing members when an @ATTACHED@ message is received without a @HAS_PRESENCE@ flag, the client library should emit a @LEAVE@ event for each existing member, and the @PresenceMessage@ published should contain the original attributes of the presence member with the @action@ set to @LEAVE@, @PresenceMessage#id@ set to @null@, and the @timestamp@ set to the current time. Once complete, all members in the @PresenceMap@ should be removed as there are no members present on the channel +* @(RTP17)@ The Presence object should also keep a second @PresenceMap@ containing only members that match the current @connectionId@. Any incoming presence message that satisfies @RTP17b@ should be applied to this object in the same way as for the normal @PresenceMap@. This object should be private and is used to maintain a list of members that need to be automatically re-entered by the @Presence@ object when required to by @RTP17c@. +** @(RTP17a)@ All members belonging to the current connection are published as a @PresenceMessage@ on the @Channel@ by the server irrespective of whether the client has permission to subscribe or the @Channel@ is configured to publish presence events. A test should exist that attaches to a @Channel@ with a @presence@ capability and without a @subscribe@ capability. It should then enter the @Channel@ and ensure that the member entered from the current connection is present in the internal and public presence set available via "@Presence#get@":#RTP11 +** @(RTP17b)@ The events that should be applied to the @RTP17@ presence map are: any @ENTER@, @PRESENT@ or @UPDATE@ event with a @connectionId@ that matches the current client's @connectionId@; any @LEAVE@ event with a @connectionId@ that matches the current client's @connectionId@ and is not a 'synthesized leave' (an event that has a connectionId which is not an initial substring of its id, per "@RTP2b1@":#RTP2b1 ) +** @(RTP17c)@ The Presence object should perform automatic re-entry in the following situations: +*** @(RTP17c1)@ After a @SYNC@ operation has completed, per "@RTP18b@":#RTP18b +*** @(RTP17c2)@ When an @ATTACHED@ message is received with no "@HAS_PRESENCE@":#TR3a flag (so no @SYNC@ is expected as the server does not believe anyone is currently present) +** @(RTP17d)@ Automatic re-entry consists of, for each member of the @RTP17@ internal @PresenceMap@ whose @memberKey@ is not also a member of the normal @PresenceMap@, publishing a @PresenceMessage@ with an @ENTER@ action using the @clientId@ and @data@ attributes from that member, and removing that member from the internal @PresenceMap@ +** @(RTP17e)@ If the publish attempt fails for an automatic presence @ENTER@ (for example, by Ably rejecting it with a @NACK@), an @UPDATE@ event should be emitted on the channel with @resumed@ set to true and @reason@ set to an @ErrorInfo@ object with @code@ @91004@, a @message@ indicating that an automatic re-enter has failed and indicating the @clientId@, and @cause@ set to the the reason for the enter failure. The error should also be logged at @warn@ level or higher. +* @(RTP4)@ Ensure a test exists that enters 250 members using @Presence#enterClient@ on a single connection, and checks for @PRESENT@ events to be emitted on another connection for each member, and once sync is complete, all 250 members should be present in a @Presence#get@ request +* @(RTP5)@ Channel state change side effects: +** @(RTP5a)@ If the channel enters the @DETACHED@ or @FAILED@ state then all queued presence messages will fail immediately, and the @PresenceMap@ and "internal PresenceMap (see RTP17)":#RTP17 is cleared. The latter ensures members are not automatically re-entered if the @Channel@ later becomes attached. Since channels in the @DETACHED@ and @FAILED@ states will not receive any presence updates from Ably, presence events (specifically @LEAVE@) should not be emitted when the @PresenceMap@ is cleared as each presence member's state is unknown +** @(RTP5f)@ If the channel enters the @SUSPENDED@ state then all queued presence messages will fail immediately, and the @PresenceMap@ is maintained. This ensures that if the channel later becomes @ATTACHED@, it will only publish presence events for the changes in the @PresenceMap@ that have occurred whilst the client was disconnected. A test should exist for a channel that is in the @SUSPENDED@ state containing presence members to transition to the @ATTACHED@ state, and following the @SYNC@ process after attaching, any members present before and after the sync should not emit presence events, all other changes should be reflected in the @PresenceMap@ and should emit presence events on the channel +** @(RTP5b)@ If a channel enters the @ATTACHED@ state then all queued presence messages will be sent immediately. A presence @SYNC@ may be initiated per "@RTP1@":#RTP1 +* @(RTP16)@ Connection state conditions: +** @(RTP16a)@ If the connection is @CONNECTED@ and the channel is @ATTACHED@ then all presence messages are published immediately +** @(RTP16b)@ If the connection is @INITIALIZED@, @CONNECTING@ or @DISCONNECTED@ or the channel is @ATTACHING@ or @INITIALIZED@, and @ClientOptions#queueMessages@ has not been explicitly set to false, then all presence messages will be queued and delivered as soon as the connection state returns to @CONNECTED@ and the channel is @ATTACHED@ +** @(RTP16c)@ Else publishing presence messages will result in an error +* @(RTP6)@ @Presence#subscribe@ function: +** @(RTP6a)@ Subscribe with no arguments subscribes a listener to all presence messages +** @(RTP6b)@ Subscribe with a single action argument - such as @ENTER@, @LEAVE@, @UPDATE@ or @PRESENT@ - subscribes a listener to receive only presence messages with that action +** @(RTP6c)@ Implicitly attaches the @Channel@ if the channel is in the @INITIALIZED@ state. The optional callback, if provided, is called according to "@RTL4d@":#RTL4d based on the implicit attach operation. The listener will always be registered regardless of the implicit attach result +* @(RTP7)@ @Presence#unsubscribe@ function: +** @(RTP7a)@ Unsubscribe with no arguments unsubscribes the listener if previously subscribed with an action-specific subscription +** @(RTP7b)@ Unsubscribe with a single action argument unsubscribes the provided listener to all presence messages for that action +* @(RTP8)@ @Presence#enter@ function: +** @(RTP8a)@ Enters the current client into this channel, optionally with the data and/or extras provided +** @(RTP8b)@ Optionally a callback can be provided that is called for both success or failure to enter +** @(RTP8c)@ A @PRESENCE ProtocolMessage@ with a @PresenceMessage@ with the action @ENTER@ is sent to the Ably service. The @clientId@ attribute of the @PresenceMessage@ must not be present. Entering without an explicit @PresenceMessage#clientId@, implicitly uses the @clientId@ for the current connection +** @(RTP8d)@ Implicitly attaches the @Channel@ if the channel is in the @INITIALIZED@ state. However, if the channel is in the @DETACHED@ or @FAILED@ state, the @enter@ request results in an error +** @(RTP8e)@ Optional data and/or extras can be included when entering a channel that will be encoded and decoded as with normal messages. A test should exist to ensure data and extras used with enter are encoded & decoded correctly. Also, when data and/or extras is provided when entering, but neither data nor extras are provided when leaving, the data attribute should be emitted in the @LEAVE@ event for this client +** @(RTP8f)@ If the client library is authenticated but unidentified (i.e. @clientId@ is a wildcard @'*'@ or client is anonymous), the @enter@ request results in an error immediately +** @(RTP8g)@ If the channel is @DETACHED@ or @FAILED@, the @enter@ request results in an error immediately +** @(RTP8h)@ If the Ably service determines that the client does not have required presence permission, a @NACK@ is sent to the client resulting in an error +** @(RTP8i)@ If the Ably service determines that the client is unidentified, a @NACK@ is sent to the client resulting in an error +* @(RTP9)@ @Presence#update@ function: +** @(RTP9a)@ Updates the data and/or extras for the present member with an updated value or empty value (eg @null@) +** @(RTP9b)@ If the client was not already entered, it enters this client into this channel +** @(RTP9c)@ Optionally a callback can be provided that is called for both success or failure to update +** @(RTP9d)@ A @PRESENCE ProtocolMessage@ with a @PresenceMessage@ with the action @UPDATE@ is sent to the Ably service. The @clientId@ attribute of the @PresenceMessage@ must not be present. Updating without an explicit @PresenceMessage#clientId@, implicitly uses the @clientId@ for the current connection +** @(RTP9e)@ In all other ways, this method is identical to @Presence#enter@ and should have matching tests +* @(RTP10)@ @Presence#leave@ function: +** @(RTP10a)@ Leaves this client from the channel and the data and/or extras will be updated with the values provided. If the language permits the data argument to be omitted, then the previously set data value will be sent as a convenience +** @(RTP10b)@ Optionally a callback can be provided that is called for both success or failure to leave +** @(RTP10c)@ A @PRESENCE ProtocolMessage@ with a @PresenceMessage@ with the action @LEAVE@ is sent to the Ably service. The @clientId@ attribute of the @PresenceMessage@ must not be present. Leaving without an explicit @PresenceMessage#clientId@, implicitly uses the @clientId@ for the current connection +** @(RTP10d)@ If the client is not currently @ENTERED@, Ably will respond with an @ACK@ and the request will succeed (i.e. the outcome of asking to @LEAVE@ when not present vs being present is the same) +** @(RTP10e)@ In all other ways, this method is identical to @Presence#enter@ and should have matching tests +* @(RTP11)@ @Presence#get@ function: +** @(RTP11a)@ Returns the list of current members on the channel in a callback. By default, will wait for the @SYNC@ to be completed, see "RTP11c1":#RTP11c1 +** @(RTP11b)@ Implicitly attaches the @Channel@ if the channel is in the @INITIALIZED@ state. However, if the channel is in or enters the @DETACHED@ or @FAILED@ state before the operation succeeds, it will result in an error +** @(RTP11d)@ If the @Channel@ is in the @SUSPENDED@ state then the @get@ function will by default, or if @waitForSync@ is set to @true@, result in an error with @code@ @91005@ and a @message@ stating that the presence state is out of sync due to the channel being in a @SUSPENDED@ state. If however the @get@ function is called with @waitForSync@ set to @false@, then it immediately returns the members currently stored in the @PresenceMap@ giving developers access to the members that were present at the time the channel became @SUSPENDED@ +** @(RTP11c)@ An optional set of params can be provided: +*** @(RTP11c1)@ @waitForSync@ (default @true@). When @true@, method will wait until @SYNC@ is complete before returning a list of members. When @false@, known set of presence members is returned immediately, which may be incomplete if the @SYNC@ is not finished +*** @(RTP11c2)@ @clientId@ filters members by the provided @clientId@ +*** @(RTP11c3)@ @connectionId@ filters members by the provided @connectionId@ +* @(RTP12)@ @Presence#history@ function: +** @(RTP12a)@ Supports all the same params as REST @Presence#history@ +** @(RTP12b)@ Additionally supports the param @untilAttach@, which if true, will only retrieve messages prior to the moment that the channel was attached or emitted an @UPDATE@ indicating loss of continuity. This bound is specified by passing the querystring param @fromSerial@ with the @Channel#properties.attachSerial@ assigned to the channel in the @ATTACHED@ @ProtocolMessage@ (see "RTL15a":#RTL15a). If the @untilAttach@ param is specified when the channel is not attached, it will result in an error +** @(RTP12c)@ Returns a @PaginatedResult@ page containing the first page of messages in the @PaginatedResult#items@ attribute returned from the history request +** @(RTP12d)@ A test should exist that registers presence with a few clients, and upon confirmation of entering the channel for all clients, a presence history request should be made using another client to ensure all presence events are available +* @(RTP13)@ @Presence#syncComplete@ attribute is @true@ if the initial @SYNC@ operation has completed for the members present on the channel +* @(RTP14)@ @Presence#enterClient@ function: +** @(RTP14a)@ Enters into presence on a channel on behalf of another @clientId@. This allows a single client with suitable permissions to register presence on behalf of any number of clients using a single connection +** @(RTP14b)@ Optionally a callback can be provided that is called for both success or failure to enter +** @(RTP14c)@ Data can optionally be provided when entering and will follow the normal encoding & decoding rules +** @(RTP14d)@ A test should exist that registers a number of members each with a different @clientId@ on a presence channel, and then a @Presence#get@ should be used to verify that all members are present as expected +* @(RTP15)@ @Presence#enterClient@ @Presence#updateClient@ and @Presence#leaveClient@ function: +** @(RTP15a)@ Performs an enter, update or leave for given @clientId@. These methods apply if the Realtime library was not initialized with a specific @clientId@. This allows a single client with suitable permissions to update presence on behalf of any number of clients using a single connection. Otherwise these are functionality equivalent to the corresponding @enter@, @update@ and @leave@ methods, and equivalent test coverage should be provided +** @(RTP15b)@ Tests should use @enterClient@, @updateClient@ and @leaveClient@ for many members from one @Realtime@ client and check that the operations are reflected in the presence map and the expected events are emitted on a separate client +** @(RTP15c)@ Tests should also ensure that using these methods has no side effects on a client that has entered normally using @Presence#enter@ +** @(RTP15d)@ A callback can be provided that will be called upon success or failure +** @(RTP15e)@ Implicitly attaches the @Channel@ if the channel is in the @INITIALIZED@ state. However, if the channel is in or enters the @DETACHED@ or @FAILED@ state before the operation succeeds, it will result in an error +** @(RTP15f)@ If the client is identified and has a valid @clientId@, and the @clientId@ argument does not match the client's @clientId@, then it should indicate an error. The connection and channel remain available for further operations + +h3(#eventemitter). EventEmitter mixin / interface + +* @(RTE1)@ @EventEmitter@ is a generic interface for event registration and delivery used in a number of the types in the Realtime client library. For example, the @Connection@ object emits events for connection state using the @EventEmitter@ pattern +* @(RTE2)@ Where objects provide @subscribe@ or @unsubscribe@ methods, they should follow the specification for the @EventEmitter#on@ and @EventEmitter#off@ methods respectively +* @(RTE3)@ @EventEmitter#on@ registers the provided listener for either all events when no @event@ argument is provided, or for only a single named event when an @event@ argument is provided. If @on@ is called more than once with the same listener and @event@, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using @on@, and an event is emitted once, the listener would be invoked twice +* @(RTE4)@ @EventEmitter#once@ registers the provided listener for either the first event that is emitted when no @event@ argument is provided, or for only the first occurrence of a single named event when an @event@ argument is provided. If @once@ is called more than once with the same listener, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using @once@, and an event is emitted once, the listener would be invoked twice. However, all subsequent events emitted would not invoke the listener as @once@ ensures that each registration is only invoked once +* @(RTE5)@ @EventEmitter#off@ deregisters a listener. If called with a specific event and a listener, it removes all registrations that match both the given listener and the given event; if called only with a listener, it removes all registrations matching the given listener, regardless of whether they are associated with an event or not; if called with no arguments, it removes all registrations, for all events and listeners +* @(RTE6)@ @EventEmitter#emit@ emits an event, calling registered listeners with the given event name and any other given arguments. If an exception is raised in any of the listeners, the exception is caught by the @EventEmitter@ and the exception is logged to the Ably logger. Tests must exist to ensure exceptions raised in client code do not propagate and inhibit other event processing within the client library +** @(RTE6a)@ The set of listeners called by @emit@ must not change over the course of the @emit@. That is: If a listener being called by @emit@ registers another listener, that second listener should not be called by that invocation of @emit@ (even if it would have been called had it already been present); and if a listener being called by @emit@ removes other listeners, but those other listeners would otherwise have been called during that @emit@ invocation, they should still be called. Tests should exist for both adding and removing. See "https://goo.gl/OVTtjO":https://goo.gl/OVTtjO + +h3(#realtime-compatibility). Forwards compatibility +* @(RTF1)@ The library must apply the "robustness principle":https://en.wikipedia.org/wiki/Robustness_principle in its processing of requests and responses with the Ably system. In particular, deserialization of ProtocolMessages and related types, and associated enums, must be tolerant to unrecognised attributes or enum values. Such unrecognised values must be ignored. + +h2(#state-conditions-and-operations). State conditions and operations + +h3(#connection-states-operations). @Connection.state@ effects on realtime operations + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
InitializedConnectingConnectedDisconnectedSuspendedClosingClosedFailed
@connect@"RTN11a":#RTN11aNo-opNo-op"RTN11c":#RTN11c"RTN11c":#RTN11c"RTN11b":#RTN11b"RTN11a":#RTN11a"RTN11d":#RTN11d
@close@No-op"RTN12f":#RTN12f"RTN12a":#RTN12a"RTN12d":#RTN12d"RTN12d":#RTN12dNo-opNo-opNo-op
@ping@"RTN13b":#RTN13b"RTN13c":#RTN13c"RTN13a":#RTN13a"RTN13c":#RTN13c"RTN13b":#RTN13b"RTN13b":#RTN13b"RTN13b":#RTN13b"RTN13b":#RTN13b
Channel @attach@"RTL4h":#RTL4h"RTL4h":#RTL4h"See channel states table":#channel-states-operations"RTL4h":#RTL4h"RTL4b":#RTL4b"RTL4b":#RTL4b"RTL4b":#RTL4b"RTL4b":#RTL4b
Channel @detach@"RTL5h":#RTL5h"RTL5h":#RTL5h"See channel states table":#channel-states-operations"RTL5h":#RTL5h"See channel states table":#channel-states-operations"RTL5g":#RTL5g"See channel states table":#channel-states-operations"RTL5g":#RTL5g
Channel @publish@"RTL6c2":#RTL6c2"RTL6c2":#RTL6c2"See channel states table":#channel-states-operations"RTL6c2":#RTL6c2"RTL6c4":#RTL6c4"RTL6c4":#RTL6c4"RTL6c4":#RTL6c4"RTL6c4":#RTL6c4
Presence ops."RTP16b":#RTP16b"RTP16b":#RTP16b"See channel states table":#channel-states-operations"RTP16b":#RTP16b"RTP16c":#RTP16c"RTP16c":#RTP16c"RTP16c":#RTP16c"RTP16c":#RTP16c
+ +h3(#channel-states-operations). @RealtimeChannel.state@ effects on channel operations + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
InitializedAttachingAttachedSuspendedDetachingDetachedFailed
@attach@"RTL4c":#RTL4c"RTL4h":#RTL4h"RTL4a":#RTL4a"RTL4c":#RTL4c"RTL4h":#RTL4h"RTL4c":#RTL4c"RTL4g":#RTL4g
@detach@"RTL5h":#RTL5h"RTL5i":#RTL5i"RTL5d":#RTL5d"RTL5j":#RTL5j"RTL5i":#RTL5i"RTL5a":#RTL5a"RTL5b":#RTL5b
@publish@"RTL6c2":#RTL6c2"RTL6c2":#RTL6c2"RTL6c1":#RTL6c1"RTL6c4":#RTL6c4"RTL6c4":#RTL6c4"RTL6c4":#RTL6c4"RTL6c3":#RTL6c3
Presence ops."RTP16b":#RTP16b"RTP16b":#RTP16b"RTP16a":#RTP16a"RTP16c":#RTP16c"RTP16c":#RTP16c"RTP16c":#RTP16c"RTP16c":#RTP16c
+ +h2(#push-notifications). Push notifications + +* @(RSH1)@ @Push#admin@ object provides the following interface: +** @(RSH1a)@ @#publish(recipient, data)@ performs an HTTP request to "/push/publish":/rest-api#push-publish. Empty values for @recipient@ or @data@ should be immediately rejected. An end-to-end push notification test can be made using the special test-only @ablyChannel@ recipient. Additionally, tests should exist with valid and invalid recipient details +** @(RSH1b)@ @#deviceRegistrations@ provides access to the admin @PushDeviceRegistrations@ object with the following methods: +*** @(RSH1b1)@ @#get(deviceId)@ performs a request to "/push/deviceRegistrations/:deviceId":/rest-api#get-device-registration and returns a @DeviceDetails@ object if the @deviceId@ is found or results in a not found error if the device cannot be found. If the client has been "activated as a push target device":#activation-state-machine, and the specified @deviceId@ is that of the present client, then this request must include "push device authentication":#push-device-authentication. +*** @(RSH1b2)@ @#list(params)@ performs a request to "/push/deviceRegistrations":/rest-api#list-device-registrations and returns a paginated result with @DeviceDetails@ objects filtered by the provided params, as supported by the REST API. A test should exist filtering by @deviceId@ and separately by @clientId@, and then controlling the pagination with the @limit@ attribute +*** @(RSH1b3)@ @#save(device)@ issues a @PUT@ request to "/push/deviceRegistrations/:deviceId":/rest-api#update-device-registration using the @DeviceDetails@ object (and optionally a JSON-encodable object) argument. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation. If the client has been "activated as a push target device":#activation-state-machine, and the specified @deviceId@ is that of the present client, then this request must include "push device authentication":#push-device-authentication. +*** @(RSH1b4)@ @#remove(deviceId)@ issues a @DELETE@ request to "/push/deviceRegistrations/:deviceId":/rest-api#delete-device-registration and deletes the registered device specified by @deviceId@. A test should exist that deletes a device and succeeds, and then also deletes a device that does not exist but still succeeds +*** @(RSH1b5)@ @#removeWhere(params)@ issues a @DELETE@ request to "/push/deviceRegistrations":/rest-api#delete-device-registrations and deletes the registered devices matching the provided @params@. A test should exist that deletes devices by @clientId@ and by @deviceId@ separately, then additionally issues a delete for devices with no matching params and checks the operation still succeeds. If the client has been "activated as a push target device":#activation-state-machine, and the specified @deviceId@ is that of the present client, then this request must include "push device authentication":#push-device-authentication. +** @(RSH1c)@ @#channelSubscriptions@ provides access to the admin @ChannelSubscriptions@ object with the following methods: +*** @(RSH1c1)@ @#list(params)@ performs a request to "/push/channelSubscriptions":/rest-api#list-channel-subscriptions and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, as supported by the REST API. A test should exist filtering by @channel@ and @deviceId@ and/or @clientId@, and then controlling the pagination with the @limit@ attribute +*** @(RSH1c2)@ @#listChannels(params)@ performs a request to "/push/channels":/rest-api#list-channels and returns a paginated result with @String@ objects filtered by the provided params, as supported by the REST API. A test should exist using the @limit@ attribute and pagination +*** @(RSH1c3)@ @#save(pushChannelSubscription)@ issues a @POST@ request to "/push/channelSubscriptions":/rest-api#post-channel-subscription using the @PushChannelSubscription@ object (and optionally a JSON-encodable object) argument. If the client has been "activated as a push target device":#activation-state-machine, and the specified @PushChannelSubscription@ contains a @deviceId@ matching that of the present client, then this request must include "push device authentication":#push-device-authentication. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation +*** @(RSH1c4)@ @#remove(push_channel_subscription)@ issues a @DELETE@ request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription and deletes the channel subscription using the attributes as params to the @DELETE@ request. If the client has been "activated as a push target device":#activation-state-machine, and the specified @PushChannelSubscription@ contains a @deviceId@ matching that of the present client, then this request must include "push device authentication":#push-device-authentication. A test should exist that deletes a @clientId@ and @deviceId@ channel subscription separately and both succeed, and then also deletes a subscription that does not exist but still succeeds +*** @(RSH1c5)@ @#removeWhere(params)@ issues a @DELETE@ request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription and deletes the matching channel subscriptions provided in @params@. A test should exist that deletes channel subscriptions by @clientId@ and by @deviceId@ separately, then additionally issues a delete for subscriptions with no matching params and checks the operation still succeeds. +* @(RSH2)@ The following should only apply to platforms that support receiving push notifications: +** @(RSH2a)@ @Push#activate@ sends a @CalledActivate@ event to "the state machine":#RSH3. +** @(RSH2b)@ @Push#deactivate@ sends a @CalledDeactivate@ event to "the state machine":#RSH3. +** @(RSH2c)@ (Moved to "@(RSH8g)@":#RSH8g ). +** @(RSH2d)@ (Moved to "@(RSH8h)@":#RSH8h ). +** @(RSH2e)@ (Moved to "@(RSH8i)@":#RSH8i ). + +h3(#activation-state-machine). Activation State Machine + +* @(RSH3)@ In platforms that support receiving push notifications, in order to connect the device's push features with Ably's, the library must perform the process described in the following abstract state machine. While this process should be implemented in whatever way better fits the concrete platform, it should be taken into account that its lifetime is that of the _app_ that runs it, which outlives that of the @Rest@ instance or (typically) the process running the app. This typically forces some kind of on-disk storage to which the state machine's state must be persisted, so that it can be recovered later by new instances and processes running the app triggered by external events. +** @(RSH3a)@ State @NotActivated@ (the initial one). +*** @(RSH3a1)@ On event @CalledDeactivate@: +**** @(RSH3a1a)@ Makes @Push#deactivate@ return or call its callback with no error. +**** @(RSH3a1b)@ Transitions to @NotActivated@. +*** @(RSH3a2)@ On event @CalledActivate@: +**** @(RSH3a2a)@ If the local device has @deviceIdentityToken@, performs a validation of the local DeviceDetails via the following steps. "@(RSH3a2b)@":#RSH3a2b onwards then don't apply. +***** @(RSH3a2a1)@ Checks the compatibilty of the present client with the existing registration: if the @LocalDevice@ has a non-empty @clientId@, and the present identified client has a different (non-null) @clientId@, then a @SyncRegistrationFailed@ event should be fired containing an error with @code@ 61002, and skips to "@(RSH3a2a4)@":#RSH3a2a4. +***** @(RSH3a2a2)@ If a custom @registerCallback@ was provided to @Push#activate@, pass it the local @DeviceDetails@. +***** @(RSH3a2a3)@ Otherwise, makes an asynchronous HTTP PUT request to @/push/deviceRegistrations/:deviceId@ using the local @DeviceDetails@ with the push details as body. When the registration validation request is complete, a @RegistrationSynced@ or @SyncRegistrationFailed@ event should be fired. +***** @(RSH3a2a4)@ Transitions to @WaitingForRegistrationSync@. +**** @(RSH3a2b)@ If the local device does not have @id@ and @deviceSecret@, both are generated locally. The @id@ must be a "ulid":https://github.com/ulid/spec or similar globally-unique identifier. The @deviceSecret@ must be created using secure random data with sufficient entropy to generate a digest of at least 32 bytes (eg using sha256) and encoding that digest with base64. The local @DeviceDetails@ is updated with the resulting @deviceId@ and @deviceSecret@. +**** @(RSH3a2c)@ If the local device has the necessary push details (registration token, etc.), sends a @GotPushDeviceDetails@ event. +**** @(RSH3a2d)@ If the local device does not have the necessary push details, it initiates a request to the underlying platform (or otherwise generates them) +**** @(RSH3a2e)@ Transitions to @WaitingForPushDeviceDetails@. +*** @(RSH3a3)@ On event @GotPushDeviceDetails@: +**** @(RSH3a3a)@ Transitions to @NotActivated@. (This consumes the event; "@(RSH3a2)@":#RSH3a2 produces it again once @Push#activate@ is called.) +** @(RSH3b)@ State @WaitingForPushDeviceDetails@: +*** @(RSH3b1)@ On event @CalledActivate@: +**** @(RSH3b1a)@ Transitions to @WaitingForPushDeviceDetails@. +*** @(RSH3b2)@ On event @CalledDeactivate@: +**** @(RSH3b2a)@ Makes @Push#deactivate@ return or call its callback with no error. +**** @(RSH3b2b)@ Transitions to @NotActivated@. +*** @(RSH3b3)@ On event @GotPushDeviceDetails@: +**** @(RSH3b3a)@ If a custom @registerCallback@ was provided to @Push#activate@, pass it the local @DeviceDetails@ updated with the push details. +**** @(RSH3b3b)@ Otherwise, make an asynchronous HTTP @POST@ request to "/push/deviceRegistrations":/rest-api/#post-device-registration using the local @DeviceDetails@ updated with the push details as body. +**** @(RSH3b3c)@ Either way, when the registration is done, a @GotDeviceRegistration@ or @GettingDeviceRegistrationFailed@ event should be fired. +**** @(RSH3b3d)@ Transitions to @WaitingForDeviceRegistration@. +*** @(RSH3b4)@ On event @GettingPushDeviceDetailsFailed@: +**** @(RSH3b4a)@ Makes @Push#activate@ return or call its callback with the error. +**** @(RSH3b4b)@ Transitions to @NotActivated@. +** @(RSH3c)@ State @WaitingForDeviceRegistration@: +*** @(RSH3c1)@ On event @CalledActivate@: +**** @(RSH3c1a)@ Transitions to @WaitingForDeviceRegistration@. +*** @(RSH3c2)@ On event @GotDeviceRegistration@: +**** @(RSH3c2a)@ Updates the local @DeviceDetails@ with it. +**** @(RSH3c2b)@ Makes @Push#activate@ return or call its callback with no error. +**** @(RSH3c2c)@ Transitions to @WaitingForNewPushDeviceDetails@. +*** @(RSH3c3)@ On event @GettingDeviceRegistrationFailed@: +**** @(RSH3c3a)@ Makes @Push#activate@ return or call its callback with the error. +**** @(RSH3c3b)@ Transitions to @NotActivated@. +** @(RSH3d)@ State @WaitingForNewPushDeviceDetails@: +*** @(RSH3d1)@ On event @CalledActivate@: +**** @(RSH3d1a)@ Makes @Push#activate@ return or call its callback with no error. +**** @(RSH3d1b)@ Transitions to @WaitingForNewPushDeviceDetails@. +*** @(RSH3d2)@ On event @CalledDeactivate@: +**** @(RSH3d2a)@ If a custom @deregisterCallback@ was provided to @Push#deactivate@, pass it the local @DeviceDetails@ 's id. +**** @(RSH3d2b)@ Otherwise, make an asynchronous DELETE HTTP request to "/push/deviceRegistrations":/rest-api/#delete-device-registration using the local @DeviceDetails@ 's ID. This operation requires "push device authentication":#push-device-authentication. +**** @(RSH3d2c)@ Either way, when the registration is done, a @Deregistered@ or @DeregistrationFailed@ event should be fired. +**** @(RSH3d2d)@ Transitions to @WaitingForDeregistration@. +*** @(RSH3d3)@ On event @GotPushDeviceDetails@ (note that this will only happen on platforms whose push device details, after first set, can change, e. g. FCM's registration token refresh): +**** @(RSH3d3a)@ If a custom @registerCallback@ was provided to @Push#activate@, pass it the local @DeviceDetails@ updated with the push details. +**** @(RSH3d3b)@ Otherwise, make an asynchronous PATCH HTTP request to "/push/deviceRegistrations/:deviceId":/rest-api/#update-device-registration using the local @DeviceDetails@ 's push details as body (but only the changed fields, as described in "the REST endpoint documentation":/rest-api/#update-device-registration). This operation requires "push device authentication":#push-device-authentication. +**** @(RSH3d3c)@ Either way, when the registration is done, a @RegistrationSynced@ or @SyncRegistrationFailed@ event should be fired. +**** @(RSH3d3d)@ Transitions to @WaitingForRegistrationSync@. +** @(RSH3e)@ State @WaitingForRegistrationSync@: +*** @(RSH3e1)@ On event @CalledActivate@, unless the machine is in state @WaitingForRegistrationSync@ as a result of a @CalledActivate@ event: +**** @(RSH3e1a)@ Makes @Push#activate@ return or call its callback with no error. +**** @(RSH3e1b)@ Transitions to @WaitingForRegistrationSync@. +*** @(RSH3e2)@ On event @RegistrationSynced@: +**** @(RSH3e2b)@ If the machine is in state @WaitingForRegistrationSync@ as a result of a @CalledActivate@ event, make @Push#activate@ return or call its callback with no error. +**** @(RSH3e2a)@ Transitions to @WaitingForNewPushDeviceDetails@. +*** @(RSH3e3)@ On event @SyncRegistrationFailed@: +**** @(RSH3e3c)@ If the machine is in state @WaitingForRegistrationSync@ as a result of a @CalledActivate@ event, make @Push#activate@ return or call its callback with the error. +**** @(RSH3e3a)@ Otherwise, calls the @updateFailedCallback@ provided to @Push#activate@ with the error. +**** @(RSH3e3b)@ Transitions to @AfterRegistrationSyncFailed@. +** @(RSH3f)@ State @AfterRegistrationSyncFailed@: +*** @(RSH3f1)@ On events @CalledActivate@ or @GotPushDeviceDetails@: +**** @(RSH3f1a)@ Does the same as "RSH3a2a":#RSH3a2a. +*** @(RSH3f2)@ On events @CalledDeactivate@: +**** @(RSH3f2a)@ Does the same as "RSH3d2":#RSH3d2. +** @(RSH3g)@ State @WaitingForDeregistration@: +*** @(RSH3g1)@ On event @CalledDeactivate@: +**** @(RSH3g1a)@ Transitions to @WaitingForDeregistration@. +*** @(RSH3g2)@ On event @Deregistered@: +**** @(RSH3g2a)@ Clears all local @DeviceDetails@. +**** @(RSH3g2b)@ Makes @Push#deactivate@ return or call its callback with no error. +**** @(RSH3g2c)@ Transitions to @NotActivated@. +*** @(RSH3g3)@ On event @DeregistrationFailed@: +**** @(RSH3g3a)@ Makes @Push#deactivate@ return or call its callback with the error. +**** @(RSH3g3b)@ Transitions to the previous state, which is either @WaitingForNewPushDeviceDetails@ or @AfterRegistrationSyncFailed@ (so, in purity, @WaitingForDeregistration@ are two separate states, one for each previous state). +* @(RSH4)@ When an event is fired and a transition from the current state is not defined for such event, the event is put into a queue. Then, whenever a transition happens, an event is dequeued from the queue. If a transition from the new current state is defined for the dequeued event, such transition happens. If not, the event is put back in its place in the queue. E. g. we're @WaitingForDeregistration@, and an event @CalledActivate@ happens. This event will be put in the queue, since there's no transition defined for it. Then, an event @Deregistered@ arrives, causing a transition to @NotActivated@. Now we peek the next item on the queue: @CalledActivate@. Because @NotActivated@ transitions on @CalledActivate@, the event is consumed and the machine transitions. +* @(RSH5)@ Event handling is atomic and sequential: while an event is being handled, the next one should be handled only after the current one has caused a state transition or has been put into the pending events queue. + +h3(#push-device-authentication). Push device authentication + +* @(RSH6)@ In platforms that support receiving push notifications, and have undergone push registration, are capable of authenticating themselves to the Ably server in order that certain push admin operations can be authorized. +** @(RSH6a)@ If a device has completed activation and has a @deviceIdentityToken@ then push device authentication is performed for a request by adding an @X-Ably-DeviceIdentityToken@ request header whose value is the @deviceIdentityToken@. +** @(RSH6b)@ If a device has not completed but has a @deviceSecret@ then push device authentication is performed for a request by adding an @X-Ably-DeviceSecret@ request header whose value is the @deviceSecret@. + +h3(#push-channels). Push channels + +* @(RSH7)@ In platforms that support receiving push notifications, @RestChannel@ and @RealtimeChannel@ have an additional @push@ field, as an object with the following interface: +** @(RSH7a)@ @#subscribeDevice()@ +*** @(RSH7a1)@ Fails if the @LocalDevice@ doesn't have an @deviceIdentityToken@, ie. it isn't registered yet. +*** @(RSH7a2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @id@ and the channel name. +*** @(RSH7a3)@ The request must include "push device authentication":#push-device-authentication +** @(RSH7b)@ @#subscribeClient()@ +*** @(RSH7b1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. +*** @(RSH7b2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @clientId@ and the channel name. +** @(RSH7c)@ @#unsubscribeDevice()@ +*** @(RSH7c1)@ Fails if the @LocalDevice@ doesn't have a @deviceIdentityToken@, ie. it isn't registered yet. +*** @(RSH7c2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @id@ and the channel name. +*** @(RSH7c3)@ The request must include "push device authentication":#push-device-authentication +** @(RSH7d)@ @#unsubscribeClient()@ +*** @(RSH7d1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. +*** @(RSH7d2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @clientId@ and the channel name. +** @(RSH7e)@ @#listSubscriptions(params)@ performs a GET request to "/push/channelSubscriptions":/rest-api#list-channel-subscriptions and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, the channel name, the device ID, and the client ID if it exists, as supported by the REST API. A @concatFilters@ param needs to be set to @true@ as well. + +h3(#local-device). LocalDevice + +* @(RSH8)@ In platforms that support receiving push notifications, the @device@ method on the @Rest@ or @Realtime@ interfaces returns an instance of @LocalDevice@ that represents the current state of the device in respect of it being a target for push notifications. +** @(RSH8a)@ The @LocalDevice@ is initialised when first required, either as a result of a call to @Rest#device@ or @Realtime#device@, or as a result of an operation involving the Activation State Machine. The @LocalDevice@ @id@, @clientId@, @deviceSecret@ and @deviceIdentityToken@ attributes are populated, together with any @recipient@-related attributes, to the extent that they exist, from the persisted state. +** @(RSH8b)@ The @LocalDevice@ @id@ and @deviceSecret@ attributes are generated, and persisted as part of the @LocalDevice@ state, when required by step "@(RSH3a2b)@":#RSH3a2b in the Activation State Machine. At that time, the @clientId@ attribute is also initialised, if the client is identified according to "@(RSA7)@":#RSA7. +** @(RSH8c)@ Following successful registration of a @LocalDevice@, following the procedure in "@(RSH3c2a)@":#RSH3c2a, the now known @deviceIdentityToken@ is set and persisted. +** @(RSH8d)@ If the @LocalDevice* is created by an unidentified client (see "@(RSA7)@":#RSA7 ) and therefore has no @clientId@ set, but the client subsequently becomes identified (as a result of "@(RSA7b2)@":#RSA7b2 or "@(RSA7b3)@":#RSA7b3 ), then the @LocalDevice@ @clientId@ is set and persisted. +** @(RSH8e)@ If the @LocalDevice@ @clientId@ becomes set as a result of "@(RSH8d)@":#RSH8d, and the @LocalDevice@ is already registered (ie the @deviceIdentityToken@ is set), and the ActivationStateMachine is in any state other than @NotActivated@, then a @GotPushDeviceDetails@ event is sent to "the state machine":#RSH3 once the effects of "@(RSH8d)@":#RSH8d are visible, ie. once @LocalDevice@ @clientId@ is set. +** @(RSH8f)@ If the @LocalDevice@ is created by an unidentified client (see "@(RSA7)@":#RSA7 ) and therefore has no @clientId@ set, but on receipt of a registration response (see "@(RSH3c2)@":#RSH3c2 ) the registered device has a non-empty @clientId@, then the @LocalDevice@ @clientId@ is set with that @clientId@. +** @(RSH8g)@ Whenever any change arises of the push transport details for local device (eg an FCM registration token update triggered by the platform), a @GotPushDeviceDetails@ event is sent to "the state machine":#RSH3. +** @(RSH8h)@ If an attempt to obtain the push transport details for local device (eg an FCM registration token) fails, a @GettingPushDeviceDetailsFailed@ event containing the indicated error is sent to "the state machine":#RSH3. +** @(RSH8i)@ Each time the library is instanced, if the LocalDevice has push device details (eg an APNS deviceToken), and if the platform supports it, it must verify the validity of those details (eg by requesting a token from the platform and comparing that with the already-known token). If as a result there are updated details, then an update to the Ably server is triggered by sending a @GotPushDeviceDetails@ event to "the state machine":#RSH3. + +h2. Types + +h3(#types). Data types + +h4. Message + +* @(TM1)@ A @Message@ represents an individual message to be sent or received via the Ably Realtime service. See the "Ruby Message documentation":http://www.rubydoc.info/gems/ably/Ably/Models/Message, but bear in mind the attributes following underscore naming in Ruby +* @(TM2)@ Attributes available in a @Message@, see the "Ruby Message documentation":http://www.rubydoc.info/gems/ably/Ably/Models/Message for an explanation of each attribute: +** @(TM2a)@ @id@ string - unique ID for this message. This attribute is always populated for messages received over REST. For messages received over Realtime, if the message does not contain an @id@, it should be set to @protocolMsgId:index@, where @protocolMsgId@ is the id of the @ProtocolMessage@ encapsulating it, and @index@ is the index of the message inside the @messages@ array of the @ProtocolMessage@ +** @(TM2b)@ @clientId@ string +** @(TM2c)@ @connectionId@ string. If a message received from Ably does not contain a @connectionId@, it should be set to the @connectionId@ of the encapsulating @ProtocolMessage@ +** @(TM2h)@ @connectionKey@ string (note this is only ever populated by a publishing client when "publishing on behalf of another client":/rest/channels#publish-on-behalf, the @connectionKey@ will never be populated for messages received. A simple test for this attribute over REST is to populate this with an invalid @connectionKey@ when publishing and expecting a suitable error) +** @(TM2g)@ @name@ string +** @(TM2d)@ @data@ string, buffer or JSON-encodable object or array +** @(TM2e)@ @encoding@ string +** @(TM2i)@ @extras@ JSON-encodable object, used to contain any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays. The extras field is provided to contain message metadata and/or ancillary payloads in support of specific functionality, e.g. push. Each of these supported extensions is documented separately; for 1.1 the only supported extension is @push@, via the @extras.push@ member. The processing of any other members is undefined +** @(TM2f)@ @timestamp@ time in milliseconds since epoch. If a message received from Ably does not contain a @timestamp@, it should be set to the @timestamp@ of the encapsulating @ProtocolMessage@ +* @(TM3)@ @fromEncoded@ and @fromEncodedArray@ are alternative constructors that take an (already deserialized) @Message@-like object (or array of such objects), and optionally a @channelOptions@, and return a @Message@ (or array of such @Messages@) that's decoded and decrypted as specified in @RSL6@, using the cipher in the @channelOptions@ if the message is encrypted, with any residual transforms (ones that the library cannot decode or decrypt) left in the @encoding@ property per @RSL6b@. This is intended for users receiving messages other than from a REST or Realtime channel (for example, from a queue), to avoid them having to parse the @encoding@ string themselves. + +h4. PresenceMessage + +* @(TP1)@ A @PresenceMessage@ represents an individual presence message to be sent or received via the Ably Realtime service. See the "Ruby PresenceMessage documentation":http://www.rubydoc.info/gems/ably/Ably/Models/PresenceMessage, but bear in mind the attributes following underscore naming in Ruby +* @(TP2)@ @PresenceMessage@ @Action@ enum has the following values in order from zero: @ABSENT@, @PRESENT@, @ENTER@, @LEAVE@, @UPDATE@ +* @(TP3)@ Attributes available in a @PresenceMessage@, see the "Ruby PresenceMessage documentation":http://www.rubydoc.info/gems/ably/Ably/Models/PresenceMessage for an explanation of each attribute: +** @(TP3a)@ @id@ string - unique ID for this presence message. This attribute is always populated for presence messages received over REST. For presence messages received over Realtime, if the presence message does not contain an @id@, it should be set to @protocolMsgId:index@, where @protocolMsgId@ is the id of the @ProtocolMessage@ encapsulating it, and @index@ is the index of the presence message inside the @presence@ array of the @ProtocolMessage@ +** @(TP3b)@ @action@ enum +** @(TP3c)@ @clientId@ string +** @(TP3d)@ @connectionId@ string. If a presence message received from Ably does not contain a @connectionId@, it should be set to the @connectionId@ of the encapsulating @ProtocolMessage@ +** @(TP3e)@ @data@ string, buffer or JSON-encodable object or array +** @(TP3f)@ @encoding@ string +** @(TP3i)@ @extras@ JSON-encodable object, used to contain any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays. The @extras@ field is provided to contain message metadata and/or ancillary payloads in support of specific functionality. For 1.1 no specific functionality is specified for @extras@ in presence messages; the processing of all members is undefined +** @(TP3g)@ @timestamp@ time in milliseconds since epoch. If a presence message received from Ably does not contain a @timestamp@, it should be set to the @timestamp@ of the encapsulating @ProtocolMessage@ +** @(TP3h)@ @memberKey@ string function that combines the @connectionId@ and @clientId@ ensuring multiple connected clients with the same clientId are uniquely identifiable +* @(TP4)@ @fromEncoded@ and @fromEncodedArray@ are alternative constructors that take an (already deserialized) @PresenceMessage@-like object (or array of such objects), and optionally a @channelOptions@, and return a @PresenceMessage@ (or array of such @PresenceMessages@) that's decoded and decrypted as specified in @RSL6@, using the cipher in the @channelOptions@ if the message is encrypted, with any residual transforms (ones that the library cannot decode or decrypt) left in the @encoding@ property per @RSL6b@. This is intended for users receiving messages other than from a REST or Realtime channel (for example, from a queue), to avoid them having to parse the @encoding@ string themselves. + +h4. ProtocolMessage + +* @(TR1)@ A @ProtocolMessage@ represents the type used to send and receive messages over the Realtime protocol. A ProtocolMessage always relates either to the connection or to a single channel only, but can contain multiple individual Messages or PresenceMessages. See the "Ruby ProtocolMessage documentation":http://www.rubydoc.info/gems/ably/Ably/Models/ProtocolMessage, but bear in mind the attributes following underscore naming in Ruby +* @(TR2)@ @ProtocolMessage@ @Action@ enum has the following values in order from zero: @HEARTBEAT@, @ACK@, @NACK@, @CONNECT@, @CONNECTED@, @DISCONNECT@, @DISCONNECTED@, @CLOSE@, @CLOSED@, @ERROR@, @ATTACH@, @ATTACHED@, @DETACH@, @DETACHED@, @PRESENCE@, @MESSAGE@, @SYNC@, @AUTH@ +* @(TR3)@ @ProtocolMessage@ @Flag@ enum has the following values, where a flag with value @n@ is defined to be set if the bitwise AND of the @flags@ field with @2ⁿ@ is nonzero +** @(TR3a)@ 0: @HAS_PRESENCE@ +** @(TR3b)@ 1: @HAS_BACKLOG@ +** @(TR3c)@ 2: @RESUMED@ +** @(TR3d)@ 3: @HAS_LOCAL_PRESENCE@ +** @(TR3e)@ 4: @TRANSIENT@ +** @(TR3q)@ 16: @PRESENCE@ +** @(TR3r)@ 17: @PUBLISH@ +** @(TR3s)@ 18: @SUBSCRIBE@ +** @(TR3t)@ 19: @PRESENCE_SUBSCRIBE@ +* @(TR4)@ Attributes available in a @ProtocolMessage@, see the "Ruby ProtocolMessage documentation":http://www.rubydoc.info/gems/ably/Ably/Models/ProtocolMessage for an explanation of each attribute: +** @(TR4a)@ @action@ enum +** @(TR4n)@ @id@ string, which will generally be of the form @connectionId:msgSerial@ +** @(TR4p)@ @auth@ AuthDetails object used for auth, see "RTC8":#RTC8 +** @(TR4b)@ @channel@ string +** @(TR4c)@ @channelSerial@ string +** @(TR4d)@ @connectionId@ string +** @(TR4f)@ @connectionSerial@ long +** @(TR4o)@ @connectionDetails@ @ConnectionDetails@ object - provides details on the constraints or defaults for the connection such as max message size, client ID or connection state TTL +** @(TR4g)@ @count@ integer +** @(TR4h)@ @error@ @ErrorInfo@ object +** @(TR4i)@ @flags@ integer. Contains one or more of the following bit flags: @HAS_PRESENCE: 1@, @HAS_BACKLOG: 2@, @RESUMED: 4@ +** @(TR4j)@ @msgSerial@ long +** @(TR4k)@ @messages@ Array of @Message@ objects +** @(TR4l)@ @presence@ Array of @PresenceMessage@ objects +** @(TR4m)@ @timestamp@ time in milliseconds since epoch + +h4. PaginatedResult + +* @(TG1)@ A @PaginatedResult@ is a type that represents a page of results from a "paginated query":/rest-api/#pagination. The response is accompanied by metadata that indicates the relative queries available +* @(TG2)@ @PaginatedResult@ wraps all message and presence history, stats and REST presence requests. Instancing this type should not result in an error if paging headers are not returned from the REST API +* @(TG3)@ @PaginatedResult#items@ attribute contains a page of results (for example an Array of @Message@ objects for a channel history request) +* @(TG4)@ @PaginatedResult#next@ function returns a new @PaginatedResult@ loaded with the next page of results. If there are no further pages, then @null@ is returned +* @(TG5)@ @PaginatedResult#first@ function returns a new @PaginatedResult@ for the first page of results +* @(TG6)@ @PaginatedResult#hasNext@ function returns @true@ if there are further pages +* @(TG7)@ @PaginatedResult#isLast@ function returns @true@ if this page is the last page i.e. @!hasNext@ + +h4. HttpPaginatedResponse // RSC19b + +* @(HP1)@ A @HttpPaginatedResponse@ is a type that represents the response from an HTTP request containing an empty or JSON-encodable object response. "Paginated queries":/rest-api/#pagination are supported +* @(HP2)@ @HttpPaginatedResponse@ inherits from @PaginatedResult@ and overrides @next@ and @first@ so that a new @HttpPaginatedResponse@ is returned +* @(HP3)@ @items@ attribute is overriden so that an array of @JsonObject@ objects are returned if the response is a JSON array. If the response is a single JSON object then @items@ returns an array with one @JsonObject@. If the response is empty, then @items@ returns an empty array. +* @(HP4)@ @statusCode@ is an integer attribute with the HTTP status code for the response +* @(HP5)@ @success@ is a boolean attribute which is @true@ when the HTTP status code indicates success i.e. @200 <= statusCode < 300@ +* @(HP6)@ @errorCode@ is an integer attribute populated with the error code if the @X-Ably-Errorcode@ HTTP header is sent in the response +* @(HP7)@ @errorMessage@ is a string attribute populated with the error code if the @X-Ably-Errormessage@ HTTP header is sent in the response +* @(HP8)@ @headers@ is an Array of key value pairs for each response header + +h4. TokenRequest + +* @(TE1)@ @TokenRequest@ is a type containing the token request details sent to the "REST requestToken endpoint":/rest-api/#request-token +* @(TE2)@ String attributes @keyName@, @clientId@, @nonce@ and @mac@ +* @(TE3)@ @capability@ is a string attribute containing capabilities JSON stringified +* @(TE5)@ @timestamp@ long - The timestamp (in milliseconds since the epoch) of this request. Timestamps, in conjunction with the @nonce@, are used to prevent requests from being replayed +* @(TE4)@ @ttl@ attribute represents time to live (expiry) of this token in milliseconds +* @(TE6)@ @TokenRequest::fromJson@ static factory method that accepts either a @JsonObject@ or a string (which should be parsed as a JSON string), and returns a new @TokenRequest@. Statically typed languages (that expect the @authCallback@ to result in a typed @TokenRequest@ object, rather than, say, a hashmap) must implement this; others may at their discretion. + +h4. TokenDetails + +* @(TD1)@ @TokenDetails@ is a type containing the token request response from the "REST requestToken endpoint":/rest-api/#request-token +* @(TD2)@ @TokenDetails#token@ attribute contains the token string +* @(TD3)@ @TokenDetails#expires@ attribute contains the expiry time in milliseconds. Where idiomatic in the language, this can be a Date/Time object +* @(TD4)@ @TokenDetails#issued@ attribute contains the time the token was issued in milliseconds. Where idiomatic in the language, this can be a Date/Time object +* @(TD5)@ @TokenDetails#capability@ attribute contains the capability JSON stringified +* @(TD6)@ @TokenDetails#clientId@ attribute contains the @clientId@ assigned to the token. If @clientId@ is @null@ or omitted, then the token is prohibited from assuming a @clientId@ in any operations, however if @clientId@ is a wildcard string @'*'@, then the token is permitted to assume any @clientId@. Any other string value for @clientId@ implies that the @clientId@ is both enforced and assumed for all operations for this token +* @(TD7)@ @TokenDetails::fromJson@ static factory method that accepts either a @JsonObject@ or a string (which should be parsed as a JSON string), and returns a new @TokenDetails@. Statically typed languages (that expect the @authCallback@ to result in a typed @TokenDetails@ object, rather than, say, a hashmap) must implement this; others may at their discretion. + +h4. Token string + +* @(TN1)@ There is no @TokenString@ type but in this specification "token string" refers to a string containing a token for authentication with Ably. With the exception of "RSC1a":#RSC1a and "RSA4f":#RSA4f, token strings are handled opaquely by the library, and any use of token strings in the API and library must support any type of token string. +* @(TN2)@ A token string (referred to in this specification as an "Ably token string") may be obtained as the @TokenDetails#token@ attribute of a @TokenDetails@ obtained in response to a request to the "REST requestToken endpoint":/rest-api/#request-token +* @(TN3)@ A token string (referred to in this specification as a "JWT token string") may be a "JSON Web Token":https://tools.ietf.org/html/rfc7519 satisfying "the Ably requirements for JWTs":https://www.ably.io/documentation/core-features/authentication#ably-jwt. + +h4. AuthDetails + +* @(AD1)@ @AuthDetails@ is a type used with an @AUTH@ protocol message to send authentication details +* @(AD2)@ @AuthDetails#accessToken@ attribute contains the token string + +h4. Stats + +* @(TS1)@ @Stats@ is a type encapsulating a statistics datapoint retrieved from the "REST stats endpoint":/rest-api/#stats. See "example statistics in JSON format":/general/statistics/ +* @(TS2)@ All stats values default to zero when no underlying JSON value exists. We send sparse JSON to stats requests omitting fields where the value is zero to reduce bandwidth and optimize JSON parsing, however the API exposed to developers ensures that all properties of the @Stats@ object such as @stats.all.messages.count@ will always return an @Integer@ value i.e. all attributes are non-nullable +* @(TS3)@ See the "Ruby Stats type documentation":http://www.rubydoc.info/gems/ably/Ably/Models/Stats for a list of attributes and their types for the @Stats@ object +* @(TS4)@ @Stats.ConnectionTypes@ - see the "Ruby ConnectionTypes documentation":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/ConnectionTypes +* @(TS5)@ @Stats.MessageCount@ - see the "Ruby MessageCount documentation":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/MessageCount +* @(TS6)@ @Stats.MessageTypes@ - see the "Ruby MessageTypes documentation":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/MessageTypes +* @(TS7)@ @Stats.MessageTraffic@ - see the "Ruby MessageTraffic documentation":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/MessageTraffic +* @(TS8)@ @Stats.RequestCount@ - see the "Ruby RequestCount documentation":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/RequestCount +* @(TS9)@ @Stats.ResourceCount@ - see the "Ruby ResourceCount documentation":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/ResourceCount +* @(TS10)@ @Stats.PushStats@ - see the "Ruby PushStats documentation":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/PushStats +* @(TS11)@ @Stats.XchgMessages@ - see the "Ruby XchgMessages documentation":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/PushStats +* @(TS12)@ The attributes of a @Stats@ object consist of: +** @(TS12a)@ @intervalId@ (property present in the JSON) - a @String@ +** @(TS12b)@ @intervalTime@ (optional) - a language-idiomatic @Time@ object, parsed from the @intervalId@ +** @(TS12c)@ @unit@ (property present in the JSON) - a @String@ or (if idiomatic) an enumerable supporting the types @minute@, @hour@, @day@, and @month@. This must be from the @unit@ property of the JSON, not calculated from the @intervalId@ +** @(TS12d)@ @intervalGranularity@ (deprecated) - an alias for @unit@, only for 1.x versions of client libraries that had such a property in a previous 1.x version, to avoid breaking compatibility. As above, this must be from the @unit@ property of the JSON, not calculated from the @intervalId@ +** @(TS12e)@ @all@ (property present in the JSON) - a @MessageTypes@ object +** @(TS12f)@ @inbound@ (property present in the JSON) - a @MessageTraffic@ object +** @(TS12g)@ @outbound@ (property present in the JSON) - a @MessageTraffic@ object +** @(TS12h)@ @persisted@ (property present in the JSON) - a @MessageTypes@ object +** @(TS12i)@ @connections@ (property present in the JSON) - a @ConnectionTypes@ object +** @(TS12j)@ @channels@ (property present in the JSON) - a @ResourceCount@ object +** @(TS12k)@ @apiRequests@ (property present in the JSON) - a @RequestCount@ object +** @(TS12l)@ @tokenRequests@ (property present in the JSON) - a @RequestCount@ object +** @(TS12m)@ @push@ (property present in the JSON) - a @PushStats@ object +** @(TS12n)@ @xchgProducer@ (property present in the JSON) - an @XchgMessages@ object +** @(TS12o)@ @xchgConsumer@ (property present in the JSON) - an @XchgMessages@ object + +h4. ErrorInfo + +* @(TI1)@ Provides a generic Ably @ErrorInfo@ object that contains Ably @code@, @statusCode@ (analogous to HTTP status code), @message@ and @cause@ attributes +* @(TI2)@ Errors returned from the Ably server are compatible with the @ErrorInfo@ structure and should result in errors that inherit from @ErrorInfo@ +* @(TI3)@ "Ably-common":https://github.com/ably/ably-common should be included as a submodule so that "consistent error codes":https://github.com/ably/ably-common/blob/master/protocol/errors.json can be used +* @(TI4)@ Ably may additionally include a @href@ attribute with a string value. This is included for REST responses to provide a URL for customers to find more help on the error code +* @(TI5)@ Log entries generated from errors, where possible, should include a URL to help developers understand the error and resolve the issue. If the URL is not already present within the contents of the @message@ attribute, then it should be included in the log entry as follows: "See @[URL]@". If the @href@ attribute is present, it should be used as the URL. If the @href@ attribute is not present, and the @code@ attribute is present, then the URL should be constructed as follows "https://help.ably.io/error/@[CODE]@". If neither the @href@ or @code@ attributes are present, then an additional URL should not be included in the log entry. + +h4. ConnectionStateChange + +* @(TA1)@ Whenever the connection state changes, a @ConnectionStateChange@ object is emitted on the @Connection@ object +* @(TA2)@ The @ConnectionStateChange@ object contains the current state in attribute @current@, the previous state in attribute @previous@, and when the client is not connected and a connection attempt will be made automatically by the library, the amount of time in milliseconds until the next retry in the attribute @retryIn@ +* @(TA5)@ The @ConnectionStateChange@ object contains the @event@ that generated the connection state change +* @(TA3)@ If the connection state change includes error information, then the @reason@ attribute will contain an @ErrorInfo@ object describing the reason for the error + +h4. ChannelStateChange + +* @(TH1)@ Whenever the channel state changes, a @ChannelStateChange@ object is emitted on the @Channel@ object +* @(TH2)@ The @ChannelStateChange@ object contains the current state in attribute @current@, the previous state in attribute @previous@ +* @(TH5)@ The @ConnectionStateChange@ object contains the @event@ that generated the channel state change +* @(TH3)@ If the channel state change includes error information, then the @reason@ attribute will contain an @ErrorInfo@ object describing the reason for the error +* @(TH4)@ The @ChannelStateChange@ object contains an attribute @resumed@ which in combination with an @ATTACHED@ state, indicates whether the channel attach successfully resumed its state following the connection being resumed or recovered. If @resumed@ is true, then the attribute indicates that the attach within Ably successfully recovered the state for the channel, and as such there is no loss of message continuity. In all other cases, @resumed@ is false, and may be accompanied with a "channel state change error reason":#TH3 + +h4. Capability - *API not defined yet* +* @(TC1)@ This type represents a capability for a key or token +* @(TC2)@ For now a string representation of the JSON will suffice wherever @capability@ is used + +h4. ConnectionDetails + +* @(CD1)@ Connection details are optionally passed to the client library in the @CONNECTED@ @ProtocolMessage#connectionDetails@ attribute to inform the client about any constraints it should adhere to, and provide additional metadata about the connection. For example, if a request is made to publish a message that exceeds the @maxMessageSize@, the client library can reject the message immediately, without communicating with the Ably service +* @(CD2)@ Attributes available in @ConnectionDetails@: +** @(CD2a)@ @clientId@ contains the client ID assigned to the token. If @clientId@ is @null@ or omitted, then the client is prohibited from assuming a @clientId@ in any operations, however if @clientId@ is a wildcard string @'*'@, then the client is permitted to assume any @clientId@. Any other string value for @clientId@ implies that the @clientId@ is both enforced and assumed for all operations from this client +** @(CD2b)@ @connectionKey@ is the connection secret key string that is used to resume a connection and its state. +** @(CD2c)@ @maxMessageSize@ overrides the default @maxMessageSize@ ("TO3l8":#TO3l8) +** @(CD2d)@ @maxFrameSize@ overrides the default @maxFrameSize@ ("TO3l8":#TO3l8) +** @(CD2e)@ @maxInboundRate@ is the maximum allowable number of requests per second from a client or Ably. In the case of a realtime connection, this restriction applies to the number of @ProtocolMessage@ objects sent, whereas in the case of REST, it is the total number of REST requests per second +** @(CD2f)@ @connectionStateTtl@ is the duration that Ably will persist the connection state when a Realtime client is abruptly disconnected +** @(CD2g)@ @serverId@ string is a unique identifier for the front-end server that the client has connected to. This server ID is only used for the purposes of debugging +** @(CD2h)@ @maxIdleInterval@ is the maximum length of time in milliseconds that the server will allow no activity to occur in the server->client direction. After such a period of inactivity, the server will send a @HEARTBEAT@ or transport-level ping to the client. If the value is 0, the server will allow arbitrarily-long levels of inactivity. + +h4. ChannelProperties +* @(CP1)@ properties of a channel and its state +* @(CP2)@ The attributes of @ChannelProperties@ consist of: +** @(CP2a)@ @attachSerial@ string - contains the last @channelSerial@ received in an @ATTACHED@ @ProtocolMesage@ for the channel, see "RTL15a":#RTL15a + +h3(#options). Option types + +h4. ClientOptions +* @(TO1)@ Ably library options used when instancing a REST or Realtime client library +* @(TO2)@ Note: @ClientOptions@ does not currently define a default for max message size or request size. This will be added in the future to ensure that REST requests does not exceed the limits before the request is made to the server. In the case of realtime, the connection constraints will be sent to the client in the initial @CONNECTED@ @ProtocolMessage@ +* @(TO3)@ The attributes of @ClientOptions@ consist of: +** @(TO3a)@ @clientId@ string - the id of the client represented by this instance +** @(TO3b)@ @logLevel@ - controls the level of verbosity of log messages from the library. The implementation of this is likely to vary by platform +** @(TO3c)@ @logHandler@ - allows the client to intercept log messages and handle them in a client-specific way. The implementation of this is likely to vary by platform +** @(TO3m)@ @logExceptionReportingUrl@ - defaults to a string value for an Ably error reporting DSN (Data Source Name), which is typically a URL in the format https://[KEY]:[SECRET]@errors.ably.io/[ID]. When set to @null@, @false@ or an empty string (depending on what is idiomatic for the platform), exception reporting is disabled +** @(TO3d)@ @tls@ boolean - defaults to true. If false, will not use TLS for all connections +** @(TO3e)@ @autoConnect@ boolean - defaults to true. If false, suppresses the automatic initiation of a connection when the library is instanced +** @(TO3f)@ @useBinaryProtocol@ boolean - defaults to true. If false, forces the library to use the JSON encoding for REST and Realtime operations, instead of the default binary msgpack encoding +** @(TO3g)@ @queueMessages@ boolean - defaults to true. If false, suppresses the default queueing of messages when connection states that anticipate imminent connection (connecting and disconnected). Instead, publish and presence state changes will fail immediately if not in the connected state +** @(TO3h)@ @echoMessages@ boolean - defaults to true. If false, suppresses messages originating from this connection being echoed back on the same connection +** @(TO3i)@ @recover@ string - A connection recovery string, specified with the intention of inheriting the state of an earlier connection +** @(TO3n)@ @idempotentRestPublishing@ boolean - defaults to false for clients with version < 1.2, otherwise true. If true, "RSL1k":#RSL1k1 applies +** @(TO3j)@ Auth option attributes: +*** @(TO3j1)@ @key@ string - Full Ably key string as obtained from dashboard +*** @(TO3j2)@ @token@ string | @TokenDetails@ | @TokenRequest@ - An authentication token issued for this application, either in the form of a token string, a @TokenDetails@ object, or a @TokenRequest@ object +*** @(TO3j3)@ @tokenDetails@ @TokenDetails@ - An authentication token issued for this application +*** @(TO3j4)@ @useTokenAuth@ boolean - When true, token authentication will always be used by the client. If @clientId@ is unspecified, then the token issued will inherently be anonymous i.e. it will contain an empty @clientId@ +*** @(TO3j5)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key. If a JSON-encodable object is provided in the callback, then the library will determine if it's a @TokenDetails@ (if it contains a @token@ key) or @TokenRequest@ (no @token@ key) and use the @fromJson@ method ("TD7":#TD7, "TE6":#TE6) to construct an object +*** @(TO3j6)@ @authUrl@ string - A URL to query to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token request or token from another entity, so tokens can be renewed without the client requiring a key +*** @(TO3j7)@ @authMethod@ - The HTTP verb to be used when a request is made by the library to the @authUrl@. Defaults to @GET@, supports @GET@ and @POST@ +*** @(TO3j8)@ @authHeaders@ - Headers to be included in any request made by the library to the @authUrl@ +*** @(TO3j9)@ @authParams@ - Additional params to be included in any request made by the library to the @authUrl@, either as query params added to the URL in the case of @GET@, or form-encoded in the body in the case of @POST@ +*** @(TO3j10)@ @queryTime@ - If true, the library will when issuing a token request query the Ably system for the current time instead of relying on a locally-available time of day +*** @(TO3j11)@ @defaultTokenParams@ - When a "TokenParams":#token-params object is provided, it will override the client library defaults described in "TokenParams":#token-params +** @(TO3k)@ Development environment attributes: +*** @(TO3k1)@ @environment@ string - for development environments only; allows a non-default Ably environment to be used such as @sandbox@ +*** @(TO3k2)@ @restHost@ string - for development environments only; allows a non-default Ably REST host to be specified. It is never valid to provide both a @restHost@ and @environment@ value +*** @(TO3k3)@ @realtimeHost@ string - for development environments only; allows a non-default Ably Realtime host to be specified. It is never valid to provide both a @realtimeHost@ and @environment@ value +*** @(TO3k6)@ @fallbackHosts@ string array - optionally allows one or more fallback hosts to be used instead of the default fallback hosts. If an empty array is specified, then fallback host functionality is disabled +*** @(TO3k7)@ @fallbackHostsUseDefault@ boolean - optionally allows the default fallback hosts @[a-e].ably-realtime.com@ to be used when @environment@ is not production or a custom realtime or REST host endpoint is being used. It is never valid to configure @fallbackHost@ and set @fallbackHostsUseDefault@ to @true@ +*** @(TO3k4)@ @port@ integer - for development environments only; allows a non-default Ably non-TLS port to be specified +*** @(TO3k5)@ @tlsPort@ integer - for development environments only; allows a non-default Ably TLS port to be specified +** @(TO3l)@ The follow attributes, if set, are used to change the default behavior of the library: +*** @(TO3l1)@ @disconnectedRetryTimeout@ integer - default 15,000 (15s). When the connection enters the @DISCONNECTED@ state, after this delay in milliseconds, if the state is still @DISCONNECTED@, the client library will attempt to reconnect automatically +*** @(TO3l2)@ @suspendedRetryTimeout@ integer - default 30,000 (30s). When the connection enters the @SUSPENDED@ state, after this delay in milliseconds, if the state is still @SUSPENDED@, the client library will attempt to reconnect automatically +*** @(TO3l7)@ @channelRetryTimeout@ integer - default 15,000 (15s). When a channel becomes @SUSPENDED@ following a server initiated @DETACHED@, after this delay in milliseconds, if the channel is still @SUSPENDED@ and the connection is @CONNECTED@, the client library will attempt to re-attach the channel automatically +*** @(TO3l3)@ @httpOpenTimeout@ integer - default 4,000 (4s). Timeout for opening the connection, available in the client library if supported by the transport +*** @(TO3l4)@ @httpRequestTimeout@ integer - default 10,000 (10s). Timeout for any single HTTP request and response +*** @(TO3l5)@ @httpMaxRetryCount@ integer - default 3. Max number of fallback hosts to use as a fallback when an HTTP request to the primary host is unreachable or indicates that it is unserviceable +*** @(TO3l6)@ @httpMaxRetryDuration@ integer - default 15,000 (15s). Max elapsed time in which fallback host retries for HTTP requests will be attempted i.e. if the first default host attempt takes 7s, and then the subsequent fallback retry attempt takes 10s, no further fallback host attempts will be made as the total elapsed time of 17s exceeds the default 15s limit +*** @(TO3l8)@ @maxMessageSize@ integer - default 65536 (64KiB). The maximum size of messages that can be published in one go. That is, the size of the @ProtocolMessage.messages@ or @ProtocolMessage.presence@ array for a realtime publish or presence action, or the message or array of messages being published for a REST publish. For realtime publishes, the default will be overridden by the @maxMessageSize@ in the @connectionDetails@, see "CD2c":#CD2c +**** @(TO3l8a)@ The size is defined as the sum over all messages being published of the message @name@, @data@, @clientId@, and @extras@ +**** @(TO3l8b)@ The size of an @Object@ or @Array@ @data@ property is its string length after being JSON-stringified +**** @(TO3l8c)@ The size of a @binary@ @data@ property is its size in bytes (of the actual binary, not the base64 representation, regardless of whether the binary protocol is in use) +**** @(TO3l8d)@ The size of the @extras@ property is the string length of its JSON representation +**** @(TO3l8e)@ The size of a @null@ or omitted property is zero +*** @(TO3l9)@ @maxFrameSize@ integer - default 524288 (512KiB). The maximum size of a single POST body or "WebSocket":/concepts/websockets frame. This is mostly only relevant for `Rest#request` (e.g. for batch publishes), since publishes will hit the @maxMessageSize@ limit before this +*** @(TO3l10)@ @fallbackRetryTimeout@ integer - default 600000 (10 minutes). (After a failed request to the default endpoint, followed by a successful request to a fallback endpoint), the period in milliseconds before HTTP requests are retried against the default endpoint + +h4(#token-params). TokenParams +* @(TK1)@ A class providing parameters of a token request. These params are used when invoking @Auth#authorize@, @Auth#requestToken@ and @Auth#createTokenRequest@ +* @(TK2)@ The attributes of @TokenParams@ consist of: +** @(TK2a)@ @ttl@ long - Requested time to live for the token in milliseconds. When omitted, Ably will default to a TTL of 60 minutes +** @(TK2b)@ @capability@ string - Capability requirements JSON stringified for the token. When omitted, Ably will default to the capabilities of the underlying key +** @(TK2c)@ @clientId@ string - A @clientId@ string to associate with this token. If @clientId@ is @null@ or omitted, then the token is prohibited from assuming a @clientId@ in any operations, however if @clientId@ is a wildcard string @'*'@, then the token is permitted to assume any @clientId@. Any other string value for @clientId@ implies that the @clientId@ is both enforced and assumed for all operations for this token +** @(TK2d)@ @timestamp@ long - The timestamp (in milliseconds since the epoch) of this request. Timestamps, in conjunction with the @nonce@, are used to prevent requests from being replayed. @timestamp@ is a "one-time" value, and is valid in a request, but is not validly a member of any default token params such as @ClientOptions#defaultTokenParams@ + +h4. AuthOptions +* @(AO1)@ A class providing configurable authentication options used when authenticating or issuing tokens explicitly. These options are used when invoking @Auth#authorize@, @Auth#requestToken@, @Auth#createTokenRequest@ and @Auth#authorize@ +* @(AO2)@ The attributes of @AuthOptions@ consist of: +** @(AO2a)@ @key@ string - Full Ably key string, as obtained from dashboard, used when signing token requests locally +** @(AO2b)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key. If a JSON-encodable object is provided in the callback, then the library will determine if it's a @TokenDetails@ (if it contains a @token@ key) or @TokenRequest@ (no @token@ key) and use the @fromJson@ method ("TD7":#TD7, "TE6":#TE6) to construct an object +** @(AO2h)@ @token@ string - An authentication token string issued for this application +** @(AO2i)@ @tokenDetails@ @TokenDetails@ - An authentication token (token string plus associated details, per @TD1@) issued for this application +** @(AO2c)@ @authUrl@ string - A URL to query to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token request or token from another entity, so tokens can be renewed without the client requiring a key +** @(AO2d)@ @authMethod@ - The HTTP verb to be used when a request is made by the library to the @authUrl@. Defaults to @GET@, supports @GET@ and @POST@ +** @(AO2e)@ @authHeaders@ - Headers to be included in any request made by the library to the @authUrl@ +** @(AO2f)@ @authParams@ - Additional params to be included in any request made by the library to the @authUrl@, either as query params in the case of @GET@, or form-encoded in the body in the case of @POST@ +** @(AO2g)@ @queryTime@ - If true, the library will when issuing a token request query the Ably system for the current time instead of relying on a locally-available time of day + +h4. ChannelOptions +* @(TB1)@ options provided when instancing a channel +* @(TB2)@ The attributes of @ChannelOptions@ consist of: +** @(TB2b)@ @cipher@, which is either: +*** @(TB2b1)@ A @CipherParams@ instance, or +*** @(TB2b2)@ an options hash (or language equivalent) consisting of any subset of @CipherParams@ fields that includes a @key@. In this case, the client library should call @getDefaultParams@, passing it the options hash, to obtain a @CipherParams@ instance +* @(TB3)@ The client lib may optionally provide an alternative constructor @withCipherKey@ for ChannelOptions that takes a @key@ only. (This must be differentiated from the normal constructor such that it is clear that the value being passed in is a key). (This is intended for languages where requiring a hash map is unidiomatic) + +h4. CipherParams +* @(TZ1)@ params to configure encryption for a channel +* @(TZ2)@ The attributes of @CipherParams@ consist of anything necessary to implement the supported algorithms, in addition to the following standardised attributes: +** @(TZ2a)@ @algorithm@ string - Default is @AES@. Optionally specify the algorithm to use for encryption, currently only @AES@ is supported +** @(TZ2b)@ @keyLength@ integer - the length in bits of the @key@ +** @(TZ2d)@ @key@ binary - private key used to encrypt and decrypt payloads +** @(TZ2c)@ @mode@ string - Default is @CBC@. Optionally specify cipher mode, currently only @CBC@ is supported + +h3(#types-push). Push notifications + +h4. PushChannelSubscription +* @(PCS1)@ details of a push subscription to a channel, consisting of the following attributes: +* @(PCS2)@ deviceId string - (optional, populated for subscriptions made for a specific device registration) +* @(PCS3)@ clientId string - (optional, populated for subscriptions made for a specific @clientId@) +* @(PCS4)@ channel string - the channel name associated with this subscription + +h4. DeviceDetails +* @(PCD1)@ details of a registered device, consisting of the following attributes: +* @(PCD2)@ id string - the id of the device registration +* @(PCD3)@ clientId string - (optional, populated for device registrations associated with a @clientId@) +* @(PCD4)@ formFactor - the device formfactor, one of @phone@, @tablet@, @desktop@, @tv@, @watch@, @car@, @embedded@, @other@ +* @(PCD5)@ metadata - a map of string key/value pairs containing any other registered metadata associated with the device registration +* @(PCD6)@ platform - the device platform, one of @android@, @ios@, @browser@ +* @(PCD7)@ push DevicePushDetails - details of the push registration for this device + +h4. DevicePushDetails +* @(PCP1)@ details of the push registration for a given device, consisting of the following attributes: +* @(PCP2)@ errorReason ErrorInfo - (optional) any error information associated with the registratration +* @(PCP3)@ recipient - a map of string key/value pairs containing details of the push transport and address +* @(PCP4)@ state - the state of the push registration, one of @Active@, @Failing@, @Failed@ + +h3(#defaults). Client Library defaults + +The following default values are configured for the client library: + +* @(DF1)@ Realtime defaults: +** @(DF1a)@ @connectionStateTtl@ integer - default 120s. The duration that Ably will persist the connection state when a Realtime client is abruptly disconnected. When the client is in the @DISCONNECTED@ state, once this TTL has passed, the client should transition the state to the @SUSPENDED@ state signifying that the state is now lost i.e. channels need to be re-attached manually. Note that this default is overriden by @connectionStateTtl@, if specified in the @ConnectionDetails@ of the @CONNECTED@ @ProtocolMessage@ +** @(DF1b)@ @realtimeRequestTimeout@ - default 10s. When a realtime client library is establishing a connection with Ably, or sending a @HEARTBEAT@, @CONNECT@, @ATTACH@, @DETACH@ or @CLOSE@ @ProtocolMessage@ to Ably, this is the amount of time that the client library will wait before considering that request as failed and triggering a suitable failure condition + +h2(#idl). Interface Definition + +The following bespoke IDL (Interface Definition Language) describes the types and classes present in the REST and Realtime client libraries. + +Please note the following conventions: + +* Types are capitalized +* Attribute and method names are lowercase +* Attributes are denoted as @attributeName: Type@ +* Instance methods are denoted as @methodName(argName: Type, argName: Type) -> ReturnType@ (@ReturnType@ may be omitted) +* Callback functions / closures are denoted as @(argName: Type, argName: Type) -> ReturnType@ (@ReturnType@ is usually omitted) +* Values produced by I/O (e.g. a request) are prefixed with @=> io@. In some platforms (JS) those values are passed as arguments to a callback argument, instead of being returned. I/O always can fail, but how do they fail is undefined in the spec, so it's also undefined here +* Enums are denoted as @.A | .B | .C@ +* @Type?@ denotes a nullable type +* @[Type: Othertype]@ denotes a map, hash or equivalent, or (if idiomatic) a list of 2-tuples. +* @Type default value@ denotes that the thing being annotated with @Type@ has @value@ as default. @Type api-default value@ denotes that the Ably server API uses those defaults and therefore it is unnecessary for the client library to send these default values to the API +* Class fields (as opposed to instance fields) are prefixed with a @+@ +* @Duration@ and @Time@ types are typically represented as milliseconds since the epoch. Where needed, a more idiomatic language specific duration may be used such as @seconds@ or @Time@ respectively for Ruby +* @Data@ is a message payload type, see "RSL4a":#RSL4a for a list of supported payload types +* @Stringifiable@ is a type used for unknown configuration parameters that need to be coerced to strings when used, see "RTC1f":#RTC1f for definition +* @JSONObject@ and @JSONArray@ denote any type or interface in the target language that represents an "RFC4627":https://www.ietf.org/rfc/rfc4627.txt @object@ or @array@ value respectively. Such types serialize to, and may be deserialized from, the corresponding JSON text. In the specification text above, values of these types are collectively referred to as "JSON-encodable". + +```[python] +class Rest: + constructor(keyStr: String) // RSC1 + constructor(tokenStr: String) // RSC1 + constructor(ClientOptions) // RSC1 + auth: Auth // RSC5 + push: Push + device() => io LocalDevice + channels: Channels // RSN1 + request( + String method, + String path, + Dict params?, + JsonObject | JsonArray body?, + Dict headers + ) => io HttpPaginatedResponse // RSC19 + stats( + start: Time, // RSC6b1 + end: Time api-default now(), // RSC6b1 + direction: .Backwards | .Forwards api-default .Backwards, // RSC6b2 + limit: int api-default 100, // RSC6b3 + unit: .Minute | .Hour | .Day | .Month api-default .Minute // RSC6b4 + ) => io PaginatedResult // RSC6a + time() => io Time // RSC16 + +class Realtime: + constructor(keyStr: String) // RSC1 + constructor(tokenStr: String) // RSC1 + constructor(ClientOptions) // RSC1 + auth: Auth // RTC4 + push: Push + device() => io LocalDevice + channels: Channels // RTC3, RTS1 + clientId: String? // proxy for RSA7 + connection: Connection // RTC2 + request( + String method, + String path, + Dict params?, + JsonObject | JsonArray body?, + Dict headers? + ) => io HttpPaginatedResponse // RTC9 + stats: // Same as Rest.stats, RTC5a + close() // proxy for RTN12 + connect() // proxy for RTN11 + time() => io Time // RTC6a + +class ClientOptions: + embeds AuthOptions // TO3j + autoConnect: Bool default true // RTC1b, TO3e + clientId: String? // RSC17, RSA4, RSA15, TO3a + defaultTokenParams: TokenParams? // TO3j11 + echoMessages: Bool default true // RTC1a, TO3h + environment: String? // RSC15b, TO3k1 + logHandler: // platform specific - TO3c + logLevel: // platform specific - TO3b + logExceptionReportingUrl: String default "[library specific]" // TO3c + port: Int default 80 // TO3k4 + queueMessages: Bool default true // RTP16b, TO3g + restHost: String default "rest.ably.io" // RSC12, TO3k2 + realtimeHost: String default "realtime.ably.io" // RTC1d, TO3k3 + fallbackHosts: String[] default nil // RSC15b, RSC15a, TO3k6 + recover: String? // RTC1c, TO3i + tls: Bool default true // RSC18, TO3d + tlsPort: Int default 443 // TO3k5 + useBinaryProtocol: Bool default true // TO3f + transportParams: [String: Stringifiable]? // RTC1f + // configurable retry and failure defaults + disconnectedRetryTimeout: Duration default 15s // TO3l1 + suspendedRetryTimeout: Duration default 30s // RTN14d, TO3l2 + channelRetryTimeout: Duration default 15s // RTL13b, TO3l7 + httpOpenTimeout: Duration default 4s // TO3l3 + httpRequestTimeout: Duration default 10s // TO3l4 + httpMaxRetryCount: Int default 3 // TO3l5 + httpMaxRetryDuration: Duration default 15s // TO3l6 + maxMessageSize: Int default 65536 // TO3l8 + maxFrameSize: Int default 524288 // TO3l8 + +class AuthOptions: // RSA8e + authCallback: ((TokenParams) -> io (String | TokenDetails | TokenRequest | JsonObject))? // RSA4a, RSA4, TO3j5, AO2b + authHeaders: [String: Stringifiable]? // RSA8c3, TO3j8, AO2e + authMethod: .GET | .POST default .GET // RSA8c, TO3j7, AO2d + authParams: [String: Stringifiable]? // RSA8c3, RSA8c1, TO3j9, AO2f + authUrl: String? // RSA4a, RSA4, RSA8c, TO3j6, AO2c + key: String? // RSA11, RSA14, TO3j1, AO2a + queryTime: Bool default false // RSA9d, TO3j10, AO2a + token: String? | TokenDetails? | TokenRequest? // RSA4a, RSA4, TO3j2 + tokenDetails: TokenDetails? // RSA4a, RSA4, TO3j3 + useTokenAuth: Bool? // RSA4, RSA14, TO3j4 + +class TokenParams: // RSAA8e + capability: String api-default '{"*":["*"]}' // RSA9f, TK2b + clientId: String? // TK2c + nonce: String? // RSA9c, Tk2d + timestamp: Time? // RSA9d, Tk2d + ttl: Duration api-default 60min // RSA9e, TK2a + +class Auth: + clientId: String? // RSA7, RSC17, RSA12 + authorize(TokenParams?, AuthOptions?) => io TokenDetails // RSA10 + createTokenRequest(TokenParams?, AuthOptions?) => io TokenRequest // RSA9 + requestToken(TokenParams?, AuthOptions?) => io TokenDetails // RSA8e + +class TokenDetails: + +fromJson(String | JsonObject) -> TokenDetails// TD7 + capability: String // TD5 + clientId: String? // TD6 + expires: Time // TD3 + issued: Time // TD4 + token: String // TD2 + +class TokenRequest: + +fromJson(String | JsonObject) -> TokenRequest // TE6 + capability: String // TE3 + clientId: String? // TE2 + keyName: String // TE2 + mac: String // TE2 + nonce: String // TE2 + timestamp: Time? // TE5 + ttl: Duration? api-default 60min // TE4 + +class Channels: + exists(String) -> Bool // RSN2, RTS2 + get(String) -> ChannelType // RSN3a, RTS3a + get(String, ChannelOptions) -> ChannelType // RSN3c, RTS3c + iterate() -> Iterator // RSN2, RTS2 + release(String) // RSN4, RTS4 + +class RestChannel: + name: String? + presence: RestPresence // RSL3 + history( + start: Time, // RSL2b1 + end: Time api-default now(), // RSL2b1 + direction: .Backwards | .Forwards api-default .Backwards, // RSL2b2 + limit: int api-default 100 // RSL2b3 + ) => io PaginatedResult // RSL2a + publish(Message, params?: Dict) => io // RSL1 + publish([Message], params?: Dict) => io // RSL1 + publish(name: String?, data: Data?) => io // RSL1 + + // Only on platforms that support receiving notifications: + push: PushChannel // RSH4 + +class RealtimeChannel: + embeds EventEmitter // RTL2a, RTL2d, RTL2e + errorReason: ErrorInfo? // RTL4e + state: ChannelState // RTL2b + presence: RealtimePresence // RTL9 + properties: ChannelProperties // CP1, RTL15 + push: PushChannel + attach() => io // RTL4d + detach() => io // RTL5e + history( + start: Time, // RTL10a + end: Time api-default now(), // RTL10a + direction: .Backwards | .Forwards api-default .Backwards, // RTL10a + limit: int api-default 100, // RTL10a + untilAttach: Bool default false // RTL10b + ) => io PaginatedResult // RSL2a + publish(Message) => io // RTL6i + publish([Message]) => io // RTL6i + publish(name: String?, data: Data?) => io // RTL6i + subscribe((Message) ->) => io // RTL7a + subscribe(String, (Message) ->) => io // RTL7b + unsubscribe() // RTL8a, RTE5 + unsubscribe((Message) ->) // RTL8a + unsubscribe(String, (Message) ->) // RTL8a + +class PushChannel: + subscribeDevice() => io // RSH7a + subscribeClient() => io // RSH7b + unsubscribeDevice() => io // RSH7c + unsubscribeClient() => io // RSH7d + listSubscriptions() => io PaginatedResult // RSH7e + +enum ChannelState: + INITIALIZED + ATTACHING + ATTACHED + DETACHING + DETACHED + SUSPENDED + FAILED + +enum ChannelEvent: + embeds ChannelState + UPDATE // RTL2g + +class ChannelStateChange: + current: ChannelState // RTL2a, RTL2b + event: ChannelEvent // TH5 + previous: ChannelState // RTL2a, RTL2b + reason: ErrorInfo? // RTL2e, TH3 + resumed: Boolean // RTL2f, TH4 + +class ChannelOptions: + +withCipherKey(key: Binary | String)? -> ChannelOptions // TB3 + cipher: (CipherParams | Params)? // RSL5a, TB2b + +class CipherParams: + algorithm: String default "AES" // TZ2a + key: Binary // TZ2d + keyLength: Int // TZ2b + mode: String default "CBC" // TZ2c + +class Crypto: + +getDefaultParams(Params) -> CipherParams // RSE1 + +generateRandomKey(keyLength: Int?) => io Binary // RSE2 + +class RestPresence: + get( + limit: int api-default 100, // RSP3a + clientId: String?, // RSP3a2 + connectionId: String?, // RSP3a3 + ) => io PaginatedResult // RSPa + history( + start: Time, // RSP4b1 + end: Time api-default now(), // RSP4b1 + direction: .Backwards | .Forwards api-default .Backwards, // RSP4b2 + limit: int api-default 100, // RSP4b3 + ) => io PaginatedResult // RSP4a + +class RealtimePresence: + syncComplete: Bool // RTP13 + get( + waitForSync: Bool default true, // RTP11c1 + clientId: String?, // RTP11c2 + connectionId: String?, // RTP11c3 + ) => io [PresenceMessage] // RTP11 + history( + start: Time, // RTP12a + end: Time, // RTP12a + direction: .Backwards | .Forwards api-default .Backwards, // RTP12a + limit: int api-default 100, // RTP12a + untilAttach: Bool default false // RTP12b + ) => io PaginatedResult // RTP12c + subscribe((PresenceMessage) ->) => io // RTP6a + subscribe(PresenceAction, (PresenceMessage) ->) => io // RTP6b + unsubscribe() // RTP7a, RTE5 + unsubscribe((PresenceMessage) ->) // RTP7a + unsubscribe(PresenceAction, (PresenceMessage) ->) // RTP7b + // presence state modifiers + enter(Data?, extras?: JsonObject) => io // RTP8 + update(Data?, extras?: JsonObject) => io // RTP9 + leave(Data?, extras?: JsonObject) => io // RTP10 + enterClient(clientId: String, Data?, extras?: JsonObject) => io // RTP4, RTP14, RTP15 + updateClient(clientId: String, Data?, extras?: JsonObject) => io // RTP15 + leaveClient(clientId: String, Data?, extras?: JsonObject) => io // RTP15 + +enum PresenceAction: + ABSENT // TP2 + PRESENT // TP2 + ENTER // TP2 + LEAVE // TP2 + UPDATE // TP2 + +class ConnectionDetails: + clientId: String? // RSA12a, CD2a + connectionKey: String // RTN15e, CD2b + connectionStateTtl: Duration // CD2f, RTN14e, DF1a + maxFrameSize: Int // CD2d + maxInboundRate: Int // CD2e + maxMessageSize: Int // CD2c + serverId: String // CD2g + maxIdleInterval: Duration // CD2h + +class Message: + constructor(name: String?, data: Data?) // TM2 + constructor(name: String?, data: Data?, clientId: String?) // TM2 + +fromEncoded(JsonObject, ChannelOptions?) -> Message // TM3 + +fromEncodedArray(JsonArray, ChannelOptions?) -> [Message] // TM3 + clientId: String? // RSL1g1, TM2b + connectionId: String? // TM2c + data: Data? // TM2d + encoding: String? // TM2e + extras: JsonObject? // TM2i + id: String // TM2a + name: String? // TM2g + timestamp: Time // TM2f + +class PresenceMessage + +fromEncoded(JsonObject, ChannelOptions?) -> PresenceMessage // TP4 + +fromEncodedArray(JsonArray, ChannelOptions?) -> [PresenceMessage] // TP4 + action: PresenceAction // TP3b + clientId: String // TP3c + connectionId: String // TP3d + data: Data? // TP3e + encoding: String? // TP3f + extras: JsonObject? // TP3i + id: String // TP3a + timestamp: Time // TP3g + memberKey() -> String // TP3h + +class ProtocolMessage: + action: ProtocolMessageAction // TR2, TR4a + auth: AuthDetails? // + channel: String? // TR4b + channelSerial: String? // TR4c + connectionDetails: ConnectionDetails? // RSA7b3, RTN19, TR4o + connectionId: String? // RTN15c1, TR4d + connectionSerial: Int? // RTN10c, TR4f + count: Int? // TR4g + error: ErrorInfo? // RTN15c2, TR4h + flags: .HAS_PRESENCE & .HAS_BACKLOG & .RESUMED ? // RTP1, TR3, TR4i, RTL2f + id: String? // TR4b + messages: [Message]? // TR4k + msgSerial: Int? // RTN7b, TR4j + presence: [PresenceMessage]? // TR4l + timestamp: Time? // TR4m + +enum ProtocolMessageAction: + HEARTBEAT // TR2 + ACK // TR2 + NACK // TR2 + CONNECT // TR2 + CONNECTED // TR2 + DISCONNECT // TR2 + DISCONNECTED // TR2 + CLOSE // TR2 + CLOSED // TR2 + ERROR // TR2 + ATTACH // TR2 + ATTACHED // TR2 + DETACH // TR2 + DETACHED // TR2 + PRESENCE // TR2 + MESSAGE // TR2 + SYNC // TR2 + AUTH // TR2 + +class AuthDetails: // RTC8 + accessToken: String // AD2 + +class Connection: + embeds EventEmitter // RTN4a, RTN4e, RTN4g + errorReason: ErrorInfo? // RTN14a + id: String? // RTN8 + key: String? // RTN9 + recoveryKey: String? // RTN16b, RTN16c + serial: Int // RTN10 + state: ConnectionState // RTN4d + close() // RTN12 + connect() // RTC1b, RTN3, RTN11 + ping() => io // RTN13 + +enum ConnectionState: + INITIALIZED + CONNECTING + CONNECTED + DISCONNECTED + SUSPENDED + CLOSING + CLOSED + FAILED + +enum ConnectionEvent: + embeds ConnectionState + UPDATE // RTN4h + +class ConnectionStateChange: + current: ConnectionState // TA2 + event: ConnectionEvent // TA5 + previous: ConnectionState // TA2 + reason: ErrorInfo? // RTN4f, TA3 + retryIn: Duration? // RTN14d, TA2 + +class Stats: + intervalId: String // TS12a + intervalTime: Time // TS12b (calculated clientside) + unit: Stats.IntervalGranularity // TS12c + intervalGranularity: Stats.IntervalGranularity? // TS12d (deprecated) + all: Stats.MessageTypes // TS12e + inbound: Stats.MessageTraffic // TS12f + outbound: Stats.MessageTraffic // TS12g + persisted: Stats.MessageTypes // TS12h + connections: Stats.ConnectionTypes // TS12i + channels: Stats.ResourceCount // TS12j + apiRequests: Stats.RequestCount // TS12k + tokenRequests: Stats.RequestCount // TS12l + push: Stats.PushStats // TS12m + xchgProducer: Stats.XchgMessages // TS12n + xchgConsumer: Stats.XchgMessages // TS12o + +enum StatsIntervalGranularity: + MINUTE + HOUR + DAY + MONTH + +class DeviceDetails: + id: String + clientId: String? + formFactor: DeviceFormFactor + metadata: JsonObject + platform: DevicePlatform + push: DevicePushDetails + deviceSecret: String? + +class DevicePushDetails: + errorReason: ErrorInfo? + recipient: JsonObject + state: .Active | .Failing | .Failed + +class LocalDevice extends DeviceDetails: + deviceIdentityToken: String + +class Push: + admin: PushAdmin // RSH1 + + // Only on platforms that support receiving notifications: + + activate( + registerCallback: ((ErrorInfo?, DeviceDetails?) -> io String)?, + // Only on platforms that, after first set, can update later its push + // device details: + updateFailedCallback: ((ErrorInfo) ->) + ) => io ErrorInfo? // RSH2a + deactivate( + deregisterCallback: ((ErrorInfo?, deviceId: String?) -> io)? + ) => io ErrorInfo? // RSH2b + +class PushAdmin: + publish(recipient: JsonObject, data: JsonObject) => io // RSH1a + deviceRegistrations: PushDeviceRegistrations // RSH1b + channelSubscriptions: PushChannelSubscriptions // RSH1c + +class JsonObject: + // Platform-dependent, typically a Dict-like object + +class PushDeviceRegistrations: + get(DeviceDetails) => io DeviceDetails // RSH1b1 + get(deviceId: String) => io DeviceDetails // RSH1b1 + list(params: Dict) => io PaginatedResult // RSH1b2 + save(DeviceDetails) => io DeviceDetails // RSH1b3 + remove(DeviceDetails) => io // RSH1b4 + remove(deviceId: String) => io // RSH1b4 + removeWhere(params: Dict) => io // RSH1b5 + +class PushChannelSubscriptions: + list(params: Dict) => io PaginatedResult // RSH1c1 + listChannels(params: Dict?) => io PaginatedResult // RSH1c2 + save(PushChannelSubscription) => io PushChannelSubscription // RSH1c3 + remove(PushChannelSubscription) => io // RSH1c4 + removeWhere(params: Dict) => io // RSH1c5 + +enum DevicePushTransportType: + "fcm" // PTT1 + "gcm" // PTT1 + "apns" // PTT1 + "web" // PTT1 + +enum DevicePlatform: + "android" // PPT1 + "ios" // PPT1 + "browser" // PPT1 + +enum DeviceFormFactor: + "phone" // PDT1 + "tablet" // PDT1 + "desktop" // PDT1 + "tv" // PDT1 + "watch" // PDT1 + "car" // PDT1 + "embedded" // PDT1 + "other" // PDT1 + +class PushChannelSubscription: + +forDevice(channel: String, deviceId: String) => PushChannelSubscription + +forClientId(channel: String, clientId: String) => PushChannelSubscription + deviceId: String? // PCS2, PCS5, PCS6 + clientId: String? // PCS3, PCS6 + channel: String // PCS4 + +class ErrorInfo: + code: Int // TI1 + href: String? // TI4 + message: String // TI1 + cause: ErrorInfo? // TI1 + statusCode: Int // TI1 + +class EventEmitter: + on((Data...) ->) // RTE4 + on(Event, (Data...) ->) // RTE4 + once((Data...) ->) // RTE4 + once(Event, (Data...) ->) // RTE4 + off() // RTE5 + off((Data...) ->) // RTE5 + off(Event, (Data...) ->) // RTE5 + emit(Event, Data...) // RTE6 + +class PaginatedResult: + items: [T] // TG3 + first() => io PaginatedResult // TG5 + hasNext() -> Bool // TG6 + isLast() -> Bool // TG7 + next() => io PaginatedResult? // TG4 + +class HttpPaginatedResponse // RSC19b + embeds PaginatedResult + items: [JsonObject] // HP3 + statusCode: Int // HP4 + success: Bool // HP5 + errorCode: Int // HP6 + errorMessage: String // HP7 + headers: Dict // HP8 +``` + +h2(#old-specs). Old specs + +Use the version navigation to view older versions. References to diffs for each version are maintained below: + +* v1.0 deprecated in Jan 2019. "View 1.0 → 1.1 changes":https://github.com/ably/docs/blob/master/content/client-lib-development-guide/versions/features-1-0__1-1.diff +* v0.8 deprecated in Jan 2017. "View 0.8 → 1.0 changes":https://github.com/ably/docs/blob/master/content/client-lib-development-guide/versions/features-0-8__1-0.diff diff --git a/content/code/sse/eventstream.code b/content/code/sse/eventstream.code index cee934586e..e6ff30fab9 100644 --- a/content/code/sse/eventstream.code +++ b/content/code/sse/eventstream.code @@ -1,7 +1,7 @@ [--- Javascript ---] var channel = "{{RANDOM_CHANNEL_NAME}}"; var apiKey = "{{API_KEY}}"; -var url = "https://realtime.ably.io/event-stream?v=1.1&key=" + apiKey + "&channels=" + channel; +var url = "https://realtime.ably.io/event-stream?v=1.2&key=" + apiKey + "&channels=" + channel; var xhttp = new XMLHttpRequest(); diff --git a/content/code/sse/sse.code b/content/code/sse/sse.code index 9787c27dda..ad6b06a5af 100644 --- a/content/code/sse/sse.code +++ b/content/code/sse/sse.code @@ -1,7 +1,7 @@ [--- Javascript ---] var channel = "{{RANDOM_CHANNEL_NAME}}"; var apiKey = "{{API_KEY}}"; -var url = "https://realtime.ably.io/sse?v=1.1&key=" + apiKey + "&channels=" + channel; +var url = "https://realtime.ably.io/sse?v=1.2&key=" + apiKey + "&channels=" + channel; var eventSource = new EventSource(url); eventSource.onopen = function() { diff --git a/content/general/events/ifttt.textile b/content/general/events/ifttt.textile index f6a84280af..717f1fd2a5 100644 --- a/content/general/events/ifttt.textile +++ b/content/general/events/ifttt.textile @@ -65,7 +65,7 @@ x-ably-envelope-source: channel.message x-ably-message-encoding: json x-ably-message-id: {UNIQUE_ABLY_MESSAGE_ID} x-ably-message-timestamp: {TIMESTAMP_ORIGINAL_MESSAGE_WAS_SENT} -x-ably-version: 1.0 +x-ably-version: 1.2 content-length: 18 connection: keep-alive ``` diff --git a/content/partials/general/events/_batched_event_headers.textile b/content/partials/general/events/_batched_event_headers.textile index c48e82b084..f417b21e9a 100644 --- a/content/partials/general/events/_batched_event_headers.textile +++ b/content/partials/general/events/_batched_event_headers.textile @@ -3,4 +3,4 @@ Batched events will have the following headers: - content-type := the type of the payload. This can be either @application/json@, @text/plain@, or @application/octet-stream@, depending on if it's @JSON@, @text@, or @binary@ respectively - x-ably-envelope-appid := the "app ID":https://support.ably.io/support/solutions/articles/3000063083 which the message came from - content-type := the type of the payload. This will be @application/json@ or @application/x-msgpack@ -- x-ably-version := the version of Reactor Event. At present this should be @1.0@, though older Events will be @0.8@ +- x-ably-version := the version of Reactor Event. At present this should be @1.2@ diff --git a/content/partials/general/events/_enveloped_event_headers.textile b/content/partials/general/events/_enveloped_event_headers.textile index 4e470ad8d9..1115128719 100644 --- a/content/partials/general/events/_enveloped_event_headers.textile +++ b/content/partials/general/events/_enveloped_event_headers.textile @@ -1,6 +1,6 @@ Enveloped events will have the following headers: - content-type := the type of the payload. This can be either @application/json@, @text/plain@, or @application/octet-stream@, depending on if it's @JSON@, @text@, or @binary@ respectively -- x-ably-version := the version of Reactor Event. At present this should be @1.0@, though older Events will be @0.8@ +- x-ably-version := the version of Reactor Event. At present this should be @1.2@ - x-ably-envelope-appid := the "app ID":https://support.ably.io/support/solutions/articles/3000063083 which the message came from - content-type := the type of the payload. This will be @application/json@ or @application/x-msgpack@ for "enveloped":#envelope messages diff --git a/content/partials/general/events/_non_enveloped_event_headers.textile b/content/partials/general/events/_non_enveloped_event_headers.textile index 87f71ceac7..52ce76ac6a 100644 --- a/content/partials/general/events/_non_enveloped_event_headers.textile +++ b/content/partials/general/events/_non_enveloped_event_headers.textile @@ -1,7 +1,7 @@ Non-enveloped events have quite a few headers, in order to provide context to the data sent in the payload. These are: - content-type := the type of the payload. This can be either @application/json@, @text/plain@, or @application/octet-stream@, depending on if it's @JSON@, @text@, or @binary@ respectively -- x-ably-version := the version of Reactor Event. At present this should be @1.0@, though older Events will be @0.8@ +- x-ably-version := the version of Reactor Event. At present this should be @1.2@ - x-ably-envelope-appid := the "app ID":https://support.ably.io/support/solutions/articles/3000063083 which the message came from - x-ably-envelope-channel := the Ably channel which the message came from - x-ably-envelope-rule-id := the Ably Reactor Rule ID which was activated to send this message diff --git a/content/realtime/channel-params.textile b/content/realtime/channel-params.textile index 75cf596cc1..19e473054a 100644 --- a/content/realtime/channel-params.textile +++ b/content/realtime/channel-params.textile @@ -57,7 +57,7 @@ h3. SSE example For example, to specify the @rewind@ channel param with the value @"1"@ using a querystring parameter, where it will apply to all channels: ```[javascript] -var querystring = 'v=1.1&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; +var querystring = 'v=1.2&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); ``` @@ -67,7 +67,7 @@ Or to specify the same parameter but only applying to one channel of two, using var channelOne = encodeURIComponent('[?rewind=1]channel1'); var channelTwo = 'channel2'; var channels = channelOne + ',' + channelTwo; - var querystring = 'v=1.1&key={{API_KEY}}&channels=' + channels'; + var querystring = 'v=1.2&key={{API_KEY}}&channels=' + channels'; var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); ``` @@ -98,7 +98,7 @@ h3(#rewind-example-ably). Rewind example with an Ably client library To subscribe to a channel, getting the most recent message if available: ```[jsall] - // only with ably-js v1.2 or later; currently in beta + // only with ably-js v1.2 or later const realtime = new Ably.Realtime('{{API_KEY}}'); realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', { rewind: '1' @@ -117,7 +117,7 @@ h3(#rewind-example-sse). Rewind example with SSE To subscribe to a channel, getting the most recent message if available: ```[javascript] - var querystring = 'v=1.1&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; + var querystring = 'v=1.2&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); ``` @@ -139,3 +139,109 @@ h3(#rewind-examples-mqtt). Rewind example with MQTT ... }); ``` + +h2(#delta). Delta parameter + +The @delta@ channel parameter allows subscribers to expresse their desire to receive deltas (diffs) between the previous and current message instead of the current message in full on a given channel. The effects of this parameter are invisible to message senders and only affect subscribers that specify it - i.e. the sender sends messages in full and Ably computes and sends the message deltas to any subscriber which subscribed using this parameter. + +Note that requesting deltas does not guarantee that every message received will be a delta, as the server may chose to send some messages in full depending on system load and other factors. Therefore a subscriber using the @delta@ parameter should be prepared to handle a random mix of delta and full messages. + +The @delta@ parameter can take only the value @vcdiff@. VCDIFF is the format of the deltas. It is an open format specified in "RFC 3284":https://tools.ietf.org/html/rfc3284. + +Ably provides a JavaScript "delta codec library":https://github.com/ably/delta-codec-js to help you avoid writing boilerplate VCDIFF handling code. Some examples follow. + +h3(#delta-example-sse). Delta example with enveloped SSE + +```[jsall] +const key = '{{API_KEY}}'; +const channel = 'sample-app-sse'; +const url = `https://realtime.ably.io/event-stream?channels=${channel}&v=1.2&key=${key}&delta=vcdiff`; +const eventSource = new EventSource(url); +const channelDecoder = new DeltaCodec.CheckedVcdiffDecoder(); + +eventSource.onmessage = (event) => { + /* event.data is JSON-encoded Ably Message (see https://www.ably.io/documentation/realtime/types#message) */ + const message = JSON.parse(event.data); + const { id, extras } = message; + let { data } = message; + + try { + if (extras && extras.delta) { + data = channelDecoder.applyBase64Delta(data, id, extras.delta.from).asUtf8String(); + } else { + channelDecoder.setBase(data, id); + } + } catch(e) { + /* Delta decoder error */ + console.log(e); + } + + /* Process decoded data */ + console.log(data); +}; +``` + +h3(#delta-example-unenv-sse). Delta example with unenveloped SSE + +```[jsall] +const key = '{{API_KEY}}'; +const channel = 'sample-app-sse'; +const url = `https://realtime.ably.io/event-stream?channels=${channel}&v=1.2&key=${key}&delta=vcdiff&enveloped=false`; +const eventSource = new EventSource(url); +const channelDecoder = new DeltaCodec.VcdiffDecoder(); + +eventSource.onmessage = (event) => { + let data = event.data; + + try { + if (DeltaCodec.VcdiffDecoder.isBase64Delta(data)) { + data = channelDecoder.applyBase64Delta(data).asUtf8String(); + } else { + channelDecoder.setBase(data); + } + } catch(e) { + /* Delta decoder error */ + console.log(e); + } + + /* Process decoded data */ + console.log(data); +}; +``` + +h3(#delta-example-mqtt). Delta example with MQTT + +```[jsall] +const mqtt = require('mqtt'); +const { VcdiffDecoder } = require('./lib'); + +const options = { + keepalive: 30, + username: 'FIRST_HALF_OF_API_KEY', + password: 'SECOND_HALF_OF_API_KEY', + port: 8883 +}; +const client = mqtt.connect('mqtts:mqtt.ably.io', options); +const channelName = 'sample-app-mqtt'; +const channelDecoder = new VcdiffDecoder(); + +client.on('message', (_, payload) => { + let data = payload; + + try { + if (VcdiffDecoder.isDelta(data)) { + data = channelDecoder.applyDelta(data).asUint8Array(); + } else { + channelDecoder.setBase(data); + } + } catch(e) { + /* Delta decoder error */ + console.log(e); + } + + /* Process decoded data */ + console.log(data); +}); + +client.subscribe(`[?delta=vcdiff]${channelName}`); +``` diff --git a/content/realtime/usage.textile b/content/realtime/usage.textile index 2c61850c7e..bbcfd3aa4b 100644 --- a/content/realtime/usage.textile +++ b/content/realtime/usage.textile @@ -100,13 +100,13 @@ blang[java]. For Java applications: ```[java] - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' ``` For Android apps: ```[java] - compile 'io.ably:ably-android:1.0.0' + compile 'io.ably:ably-android:1.2.0' ``` In the above example a specific version of the library is referenced, however we recommend you check which is the latest stable version and always use that. Follow links to get the latest stable release for "Java":https://bintray.com/ably-io/ably/ably-java and "Android":https://bintray.com/ably-io/ably/ably-android. diff --git a/content/realtime/versions/v1.1/channel-params.textile b/content/realtime/versions/v1.1/channel-params.textile new file mode 100644 index 0000000000..75cf596cc1 --- /dev/null +++ b/content/realtime/versions/v1.1/channel-params.textile @@ -0,0 +1,141 @@ +--- +title: Channel Parameters +section: realtime +index: 32 +jump_to: + Help with: + - Overview#overview + - Using params with v1.1 or earlier Ably libraries#using-params-with-lib-v11 + - Using params with non-Ably transports#using-params-with-other-transports + - Examples#examples + Parameters: + - rewind#rewind +--- + +h2(#overview). Overview + +Channel parameters are a general mechanism by which a client can express properties of a channel, or of its attachment to a channel. A set of channel parameters is simply a set of key/value pairs, where both keys and values are strings; the keys correspond to specific features that Ably defines. + +The methods provided for specifying channel parameters, and the currently available features, are outlined below. + +h2(#supported-params). Currently supported channel params + +- rewind := Used to request that an attachment start from some number of messages or point in time in the past. See "rewind":#rewind for more information +- delta := **In an experimental state**. Used to request that data payloads should be sent as deltas to the previous payload. "Contact us":https://www.ably.io/contact for more information and supported values + +h2(#using-params-with-lib-v11). Using channel params with v1.1 or earlier Ably client libraries + +The current Ably libraries, at version 1.1, do not expose an API for expressing channel parameters. This means that it is necessary to specify parameters in a way that is opaque to the library. + +A set of params is expressed by including a query string, using standard URL query syntax and encoding, within the qualifier part of a channel name. The qualifier part is in square brackets at the start of the channel name. + +For example, to specify the parameter @foo@ with value @bar@ on channel @baz@, the qualified channel name would be @[?foo=bar]baz@. If the channel name already has a qualifier, such as @[meta]log@, then the query string follows the existing qualifier, as in @[meta?foo=bar]log@. + +Using this syntax with the Ably library means that channel parameters are specified for the lifetime of the @Channel@ instance; in order to reference the same channel, but with different channel parameters, it is necessary to get a new @Channel@ instance, using a qualified name that includes the new channel parameters. + +h3. Example + +For example, to specify the @rewind@ channel param with the value @"1"@: + +```[javascript] + const realtime = new Ably.Realtime('{{API_KEY}}'); + const channel = realtime.channels.get('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); +``` + +h2(#using-params-with-other-transports). Using params with non-Ably transports + +It is possible to interact with Ably channels using transports that do not involve using an Ably library; for example using SSE without any library, or using a supported non-Ably protocol such as MQTT. In these cases, it is also necessary to use a qualified channel name. + +A set of params is expressed by including a query string, using standard URL query syntax and encoding, within the qualifier part of a channel name. The qualifier part is in square brackets at the start of the channel name. + +For example, to specify the parameter @foo@ with value @bar@ on channel @baz@, the qualified channel name would be @[?foo=bar]baz@. If the channel name already has a qualifier, such as @[meta]log@, then the query string follows the existing qualifier, as in @[meta?foo=bar]log@. + +In an SSE connection, it is also possible to specify channel parameters as a query string in the connection URL, instead of as a qualifier on an individual channel name. In this case, the given channel parameters apply to all channel attachments associated with that connection. + +h3. SSE example + +For example, to specify the @rewind@ channel param with the value @"1"@ using a querystring parameter, where it will apply to all channels: + +```[javascript] +var querystring = 'v=1.1&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; +var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); +``` + +Or to specify the same parameter but only applying to one channel of two, using a qualified channel name: + +```[javascript] + var channelOne = encodeURIComponent('[?rewind=1]channel1'); + var channelTwo = 'channel2'; + var channels = channelOne + ',' + channelTwo; + var querystring = 'v=1.1&key={{API_KEY}}&channels=' + channels'; + var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); +``` + +h2(#rewind). Rewind parameter + +The @rewind@ channel parameter relates to the initial attachment of a connection to a channel, and expresses the intent to attach to the channel at a position, or a point in time, in the past (that is, effectively "rewinding" the channel for the purposes of the present attachment). + +A @rewind@ parameter can express a channel position in terms of a number of messages, or a time interval. + +A @rewind@ value that is simply a number @n@ (eg @rewind=1@) is a request to attach to the channel at a position @n@ messages before the present position. If that attachment is successful, and one or more messages exist on the channel prior to the present position, then those messages will be delivered to the subscriber immediately after the attachment has completed, and before any subsequent messages that arise in real time. + +If fewer than the requested number of messages exists on the channel (including the case that there are no prior messages), then the available messages are sent; this does not constitute an error. + +A @rewind@ value can also be a string that is a time interval specifier. Supported specifier values express an integral number of seconds (eg @15s@) or minutes (eg @2m@). If that attachment is successful, and one or more messages exist on the channel in the given time interval prior to the present time, then those messages will be delivered to the subscriber immediately after the attachment has completed, and before any subsequent messages that arise in real time. + +If you wish to use a time interval rewind but additionally specify a limit on the number of messages to be returned, you can use the @rewindLimit@ channel param. For example, to request up to 10 messages in a window 5m before the present time, specify a channel parameter string of @rewind=5m&rewindLimit=10@. If fewer than the requested number of messages exists on the channel in that interval (including the case that there are no messages), then the available messages are sent; this does not constitute an error. + +At most 100 messages will be sent in a rewind request. If the number of messages within the specified interval is greater than that limit, then only the most recent messages up to that limit are sent. The attachment succeeds, but truncation of the message backlog is indicated as a non-fatal error in the attachment response. + +By default, a maximum of two minutes of channel history is available when attaching. This means that a rewind time specifier of greater than two minutes will only be able to rewind by two minutes. If a channel has persistence enabled, then it is possible to rewind back in time by up to the persistence TTL on the channel. + +The channel position expressed by a @rewind@ parameter has an effect only on an initial channel attachment. Any subsequent reattachment of the same channel on the same connection, in order to resume the connection, will attempt to resume with continuity from the point at which the connection dropped. (There are a few exceptions to this: in particular, client libraries earlier than v1.2 that have been disconnected for over two minutes, and all clients when using "@recover@ mode":/realtime/connection#connection-state-recovery ; in both cases the previous attachment state is not preserved). + +Any @rewind@ parameter value that cannot be parsed either as a number or a time specifier represents an error, and any attachment request will fail with an error. + +h3(#rewind-example-ably). Rewind example with an Ably client library + +To subscribe to a channel, getting the most recent message if available: + +```[jsall] + // only with ably-js v1.2 or later; currently in beta + const realtime = new Ably.Realtime('{{API_KEY}}'); + realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', { + rewind: '1' + }).subscribe(msg => console.log("Received message: ", msg)); +``` + +```[jsall] + // with ably-js v1.1 or below + const realtime = new Ably.Realtime('{{API_KEY}}'); + const channel = realtime.channels.get('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); + channel.subscribe(msg => console.log("Received message: ", msg)); +``` + +h3(#rewind-example-sse). Rewind example with SSE + +To subscribe to a channel, getting the most recent message if available: + +```[javascript] + var querystring = 'v=1.1&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; + var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); +``` + +h3(#rewind-examples-mqtt). Rewind example with MQTT + +```[nodejs] + var mqtt = require('mqtt'); + var options = { + keepalive: 30, + username: 'FIRST_HALF_OF_API_KEY', + password: 'SECOND_HALF_OF_API_KEY', + port: 8883 + }; + var client = mqtt.connect('mqtts:mqtt.ably.io', options); + client.on('connect', () => { + client.subscribe('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); + }); + client.on('message', (topic, message) => { + ... + }); +``` diff --git a/content/rest-api/index.textile b/content/rest-api/index.textile index cedf73e65c..2ac5af0838 100644 --- a/content/rest-api/index.textile +++ b/content/rest-api/index.textile @@ -147,14 +147,14 @@ By default, all requests receive the latest version of the API, which is current When we make backwards-incompatible API changes to the API, we release new versions. Therefore, we encourage you to explicitly request the version you are interfacing with in all requests using one of the following mechanisms: -1. Include a @X-Ably-Version@ header. Example: +1. Include the @X-Ably-Version@ header. Example: bc[sh]. curl https://rest.ably.io/time \ - -H "X-Ably-Version: 1.0" + -H "X-Ably-Version: 1.2" 2. Include the version query string param @v@. Example: -bc[sh]. curl https://rest.ably.io/time?v=1.0 +bc[sh]. curl https://rest.ably.io/time?v=1.2 h3(#pagination). Pagination diff --git a/content/rest/usage.textile b/content/rest/usage.textile index 9278b630e6..4e065de8ec 100644 --- a/content/rest/usage.textile +++ b/content/rest/usage.textile @@ -126,13 +126,13 @@ blang[java]. For Java applications: ```[java] - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' ``` For Android apps: ```[java] - compile 'io.ably:ably-android:1.0.0' + compile 'io.ably:ably-android:1.2.0' ``` In the above example a specific version of the library is referenced, however we recommend you check which is the latest stable version and always use that. Follow links to get the latest stable release for "Java":https://bintray.com/ably-io/ably/ably-java and "Android":https://bintray.com/ably-io/ably/ably-android. diff --git a/content/root/quick-start-guide.textile b/content/root/quick-start-guide.textile index fed7a14a53..f1503b19e3 100644 --- a/content/root/quick-start-guide.textile +++ b/content/root/quick-start-guide.textile @@ -62,13 +62,13 @@ blang[java]. For Java applications: ```[java] - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' ``` For Android apps: ```[java] - compile 'io.ably:ably-android:1.0.0' + compile 'io.ably:ably-android:1.2.0' ``` In the above example a specific version of the library is referenced, however we recommend you check which is the latest stable version and always use that. Follow links to get the latest stable release for "Java":https://bintray.com/ably-io/ably/ably-java and "Android":https://bintray.com/ably-io/ably/ably-android. diff --git a/content/sse/index.textile b/content/sse/index.textile index 28005c6331..8dd36120ed 100644 --- a/content/sse/index.textile +++ b/content/sse/index.textile @@ -25,7 +25,7 @@ SSE is incredibly simple to get started with. The code sample below provides an ```[javascript](code-editor:sse/sse) var key ='{{API_KEY}}'; -var url ='https://realtime.ably.io/event-stream?channels=myChannel&v=1.1&key=' + key; +var url ='https://realtime.ably.io/event-stream?channels=myChannel&v=1.2&key=' + key; var eventSource = new EventSource(url); eventSource.onmessage = function(event) { @@ -112,7 +112,7 @@ The @/event-stream@ endpoint will give an SSE response if the @Accept@ header is h5. Request parameters - channels := **mandatory**. One or more channel names, separated by commas (or the @separator@ if specified). Non-url-safe characters should be URL-encoded (for example, @?channels=foo%3Fbar@ will subscribe to the channel @foo?bar@). Alias: @channel@. -- v := **mandatory**. The version of the api you are requesting. The current (and only) version of the API is 1.1, so only @v=1.1@ is accepted. +- v := **mandatory**. The version of the api you are requesting. The current version of the API is 1.2, so @v=1.2@ is recommended. - separator := **optional**. A separator, to enable easy subscriptions to channels with commas in their name. For example, @?separator=|&channel=fo,o|ba,r@ will subscribe to the two channels @fo,o@ and @ba,r@. - key := **optional**. An Ably API key to use, if using basic auth. - accessToken := **optional** An Ably auth token to use, if using token auth. @@ -131,7 +131,7 @@ h5. Code example ```[javascript](code-editor:sse/sse) var apiKey = '{{API_KEY}}'; -var url = 'https://realtime.ably.io/event-stream?channels=myChannel&v=1.1&key=' + apikey; +var url = 'https://realtime.ably.io/event-stream?channels=myChannel&v=1.2&key=' + apikey; var eventSource = new EventSource(url); eventSource.onmessage = function(event) { @@ -145,7 +145,7 @@ import json import sseclient api_key='{{API_KEY}}' -url = "https://realtime.ably.io/sse?channels=myChannel&v=1.1&key=%s" % (api_key) +url = "https://realtime.ably.io/sse?channels=myChannel&v=1.2&key=%s" % (api_key) def with_urllib3(url): import urllib3 @@ -160,7 +160,7 @@ for event in client.events(): ``` ```[curl] -curl "https://rest.ably.io/sse?channel=example&v=1.1" \ +curl "https://rest.ably.io/sse?channel=example&v=1.2" \ --user "{{API_KEY}}" ⏎ id: cbfKayrzgAXDWM:1556806691343-0 @@ -196,7 +196,7 @@ When available, we recommend using an SSE library as opposed to the raw HTTP str h5. Request parameters - channels := **mandatory**. One or more channel names, separated by commas (or the @separator@ if specified). Non-url-safe characters should be URL-encoded (for example, @?channels=foo%3Fbar@ will subscribe to the channel @foo?bar@). Alias: @channel@. -- v := **mandatory**. The version of the api you are requesting. The current (and only) version of the API is 1.1, so only @v=1.1@ is accepted. +- v := **mandatory**. The version of the api you are requesting. The current version of the API is 1.2, so @v=1.2@ is recommended. - separator := **optional**. A separator, to enable easy subscriptions to channels with commas in their name. For example, @?separator=|&channel=fo,o|ba,r@ will subscribe to the two channels @fo,o@ and @ba,r@. - key := **optional**. An Ably API key to use, if using basic auth. - accessToken := **optional** An Ably auth token to use, if using token auth. @@ -233,7 +233,7 @@ Note that failures on opening the connection (for example, invalid authenticatio h5. Code example ```[curl] -curl "https://rest.ably.io/event-stream?channel=example&v=1.1" \ +curl "https://rest.ably.io/event-stream?channel=example&v=1.2" \ --user "{{API_KEY}}" { @@ -263,7 +263,7 @@ curl "https://rest.ably.io/event-stream?channel=example&v=1.1" \ ```[nodejs] const request = require('request'); const apiKey = '{{API_KEY}}'; -const url = 'https://realtime.ably.io/event-stream?channels=myChannel&v=1.1&key=' + apikey; +const url = 'https://realtime.ably.io/event-stream?channels=myChannel&v=1.2&key=' + apikey; request .get(url) diff --git a/content/tutorials/channel-rewind.textile b/content/tutorials/channel-rewind.textile index a964ce5105..054c556267 100644 --- a/content/tutorials/channel-rewind.textile +++ b/content/tutorials/channel-rewind.textile @@ -165,7 +165,7 @@ Let us now look at consuming the same data stream with the rewind parameter, but subscribeOpenWeather($('select#city-id').val()); function subscribeOpenWeather(id) { var channelWeather = "[product:ably-openweathermap/weather]weather:" + id; - var URL = `https://realtime.ably.io/sse?v=1.1&key=${apiKey}&channels=${encodeURIComponent(channelWeather)}&rewind=1`; + var URL = `https://realtime.ably.io/sse?v=1.2&key=${apiKey}&channels=${encodeURIComponent(channelWeather)}&rewind=1`; eventSource = new EventSource(URL); eventSource.onmessage = function (msg) { var message = JSON.parse(msg.data); diff --git a/content/tutorials/encryption.textile b/content/tutorials/encryption.textile index 828b0bfdce..c028671151 100644 --- a/content/tutorials/encryption.textile +++ b/content/tutorials/encryption.textile @@ -63,7 +63,7 @@ blang[java]. } dependencies { - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' } ``` @@ -149,7 +149,7 @@ blang[nodejs]. { ... "dependencies": { - "ably": ">=1.1" + "ably": ">=1.2" ... } } diff --git a/content/tutorials/history.textile b/content/tutorials/history.textile index 3b2234cf24..2a2acf73ba 100644 --- a/content/tutorials/history.textile +++ b/content/tutorials/history.textile @@ -80,7 +80,7 @@ blang[java]. } dependencies { - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' } ``` @@ -119,7 +119,7 @@ blang[android]. ... dependencies { ... - compile 'io.ably:ably-android:1.0.0' + compile 'io.ably:ably-android:1.2.0' } ``` diff --git a/content/tutorials/publish-subscribe.textile b/content/tutorials/publish-subscribe.textile index 3c82930c94..16d3001cfe 100644 --- a/content/tutorials/publish-subscribe.textile +++ b/content/tutorials/publish-subscribe.textile @@ -61,7 +61,7 @@ blang[java]. } dependencies { - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' } ``` @@ -101,7 +101,7 @@ blang[android]. ... dependencies { ... - compile 'io.ably:ably-android:1.0.0' + compile 'io.ably:ably-android:1.2.0' } ``` diff --git a/content/tutorials/queue-amqp-neutrino-profanity.textile b/content/tutorials/queue-amqp-neutrino-profanity.textile index 08ef8ef83c..609d36512b 100644 --- a/content/tutorials/queue-amqp-neutrino-profanity.textile +++ b/content/tutorials/queue-amqp-neutrino-profanity.textile @@ -87,7 +87,7 @@ First we'll add the @express@ NPM module and create a @package.json@: "start": "node ./server.js" }, "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "express": "~4.0" } } @@ -250,7 +250,7 @@ We're now ready to start consuming messages from the queue. There are "plenty of ```[json] "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "amqplib": "^0.4", "express": ">=4.14.0" } @@ -355,7 +355,7 @@ Now that you have an API key you can use to communicate with the Neutrino API, y ```[json] "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "amqplib": "^0.4", "express": ">=4.14.0", "request": ">=2.79.0" diff --git a/content/tutorials/queue-amqp-wolfram-alpha.textile b/content/tutorials/queue-amqp-wolfram-alpha.textile index 57b74eb016..677be037e1 100644 --- a/content/tutorials/queue-amqp-wolfram-alpha.textile +++ b/content/tutorials/queue-amqp-wolfram-alpha.textile @@ -87,7 +87,7 @@ First we'll add the @express@ NPM module and create a @package.json@: "start": "node ./server.js" }, "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "express": "~4.0" } } @@ -251,7 +251,7 @@ We're now ready to start consuming messages from the queue. There are "plenty of ```[json] "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "amqplib": "^0.4", "express": ">=4.14.0" } @@ -357,7 +357,7 @@ Now that you have an AppID you can use to communicate with the Wolfram Alpha API ```[json] "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "amqplib": "^0.4", "express": ">=4.14.0", "request": ">=2.79.0" diff --git a/content/tutorials/queue-stomp-neutrino-profanity.textile b/content/tutorials/queue-stomp-neutrino-profanity.textile index 1f7f6d719f..4434a66bdf 100644 --- a/content/tutorials/queue-stomp-neutrino-profanity.textile +++ b/content/tutorials/queue-stomp-neutrino-profanity.textile @@ -87,7 +87,7 @@ First we'll add the @express@ NPM module and create a @package.json@: "start": "node ./server.js" }, "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "express": "~4.0" } } @@ -250,7 +250,7 @@ We're now ready to start consuming messages from the queue. There are "plenty of ```[json] "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "amqplib": "^0.4", "express": ">=4.14.0" } @@ -371,7 +371,7 @@ Now that you have an API key you can use to communicate with the Neutrino API, y ```[json] "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "express": ">=4.14.0", "request": ">=2.79.0", "stompit": "^0.24" diff --git a/content/tutorials/reactor-event-azure.textile b/content/tutorials/reactor-event-azure.textile index 78d2a6830e..825fe62ae4 100644 --- a/content/tutorials/reactor-event-azure.textile +++ b/content/tutorials/reactor-event-azure.textile @@ -178,7 +178,7 @@ Create a folder on your local machine, then navigate to that folder from your co "version": "1.0.0", "description": "Ably Reactor Function Tutorial", "dependencies": { - "ably": "^1.0.11", + "ably": ">=1.2", "compromise": "^11.2.1", "fuzzyset": "0.0.4" } diff --git a/content/tutorials/reactor-event-google.textile b/content/tutorials/reactor-event-google.textile index 25ebb68ac8..09e1b8889a 100644 --- a/content/tutorials/reactor-event-google.textile +++ b/content/tutorials/reactor-event-google.textile @@ -86,7 +86,7 @@ Create a folder on your local machine, then navigate to that folder from your co "version": "1.0.0", "description": "Ably Reactor Function Tutorial", "dependencies": { - "ably": "^1.0.11", + "ably": ">=1.2", "compromise": "^11.2.1", "fuzzyset": "0.0.4" } diff --git a/content/tutorials/sse-and-http-streaming.textile b/content/tutorials/sse-and-http-streaming.textile index 6fb25035b2..66ec22635e 100644 --- a/content/tutorials/sse-and-http-streaming.textile +++ b/content/tutorials/sse-and-http-streaming.textile @@ -130,7 +130,7 @@ blang[javascript]. throw 'Cannot proceed without api key'; } - const url = `https://realtime.ably.io/sse?v=1.1&key=${apiKey}&channels=${channels}` + const url = `https://realtime.ably.io/sse?v=1.2&key=${apiKey}&channels=${channels}` const eventSource = new EventSource(url); eventSource.onopen = function() { addEvent(`Connected and subscribed to channels: ${channels.split(',').join(', ')}`); @@ -179,7 +179,7 @@ blang[python]. print("Please replace your API key") exit() channels = 'date-channel,time-channel' - url = 'https://realtime.ably.io/sse?v=1.1&key=%s&channels=%s' % (apiKey, channels) + url = 'https://realtime.ably.io/sse?v=1.2&key=%s&channels=%s' % (apiKey, channels) def with_urllib3(url): """Get a streaming response for the given event feed using urllib3.""" @@ -202,7 +202,7 @@ blang[python]. h3(#with-sse). With Event Stream -For the Event Stream endpoint, just replace the URL with @https://realtime.ably.io/event-stream?v=1.1&key=${apiKey}&channels=${channels}@. The rest of the code is exactly the same as that of SSE. +For the Event Stream endpoint, just replace the URL with @https://realtime.ably.io/event-stream?v=1.2&key=${apiKey}&channels=${channels}@. The rest of the code is exactly the same as that of SSE. bq. The @/event-stream@ endpoint will give an SSE response if the @Accept@ header is set to @text/event-stream@. The @/sse@ endpoint is provided as an easier way of forcing an SSE response. diff --git a/content/tutorials/token-authentication.textile b/content/tutorials/token-authentication.textile index 721bcb73db..47fd9870bc 100644 --- a/content/tutorials/token-authentication.textile +++ b/content/tutorials/token-authentication.textile @@ -55,7 +55,7 @@ blang[java]. } dependencies { - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' } ``` @@ -299,7 +299,7 @@ blang[nodejs,android]. "start": "node ./server.js" }, "dependencies": { - "ably": ">=0.8", + "ably": ">=1.2", "express": "~4.0" } } @@ -637,7 +637,7 @@ blang[android]. ... dependencies { ... - compile 'io.ably:ably-android:1.0.0' + compile 'io.ably:ably-android:1.2.0' } ``` @@ -1360,7 +1360,7 @@ blang[nodejs]. { ... "dependencies": { - "ably": ">=0.8", + "ably": ">=1.2", "express": "~4.0", "cookie-parser": "~1.4" } diff --git a/data/jsbins.yaml b/data/jsbins.yaml index d019b9dc9d..5c14ebb605 100644 --- a/data/jsbins.yaml +++ b/data/jsbins.yaml @@ -29,7 +29,6 @@ jsbin_hash: OtnWoay3QvApAKwR4sW+nqmwHTw=: ebizix R1DMwbF6puNE2KAN8YmbN3adxn0=: oqasav "+7edQ0/+RVdXbSaumOltjdMtYW8=": udenus - kFsT2eCL2NRWubzQ8OC3BMNZFYg=: ozecar J6B4SjIgGXie5DHoSF30sGPzb64=: akutul dqNcZL3zwOeS3PmMAzJ54wVDkNg=: utizit fDq9P84O672WgwAsIhYmPxnPLRA=: azifaq @@ -40,9 +39,10 @@ jsbin_hash: f9UaJsgoEvwrF4XhUjmJeUAZCnE=: imecoq KLOsf4s5KuRyHZaVRl+L6X9hsnA=: omihuk abZx53u8UDccCug5pAKKuGT5euI=: asasev - 0mral5B3E8UfMEPBq8MsCmeOGzc=: ipojey Q0XIw74KKfK3Zv34fqKSck4ip5U=: ivihud Sf9QPfpwxOvNcKy+KTQKXHq8dbc=: umikub + 6voT8sjZ4GnNONoP3sW/Uaq2GkE=: aqalul + 5SY9KnghzlKVnF/NlXih5jySnug=: utulow jsbin_id: adapters/pusher-pub-sub: eBxLJU1YqdX+JW9JQY70S4iQfmU= adapters/pubnub-pub-sub: pzHKfCW4/o2wDnCfbg1m0Pkl7v8= @@ -81,8 +81,8 @@ jsbin_id: website-examples/simple-chat: rnwPhTS4IMwf/LJyYfbrJnK4Axc= api-streamer/tfl: J6B4SjIgGXie5DHoSF30sGPzb64= rest/batch-publish: f9UaJsgoEvwrF4XhUjmJeUAZCnE= - sse/sse: 0mral5B3E8UfMEPBq8MsCmeOGzc= - sse/eventstream: kFsT2eCL2NRWubzQ8OC3BMNZFYg= + sse/sse: 5SY9KnghzlKVnF/NlXih5jySnug= + sse/eventstream: 6voT8sjZ4GnNONoP3sW/Uaq2GkE= rest/batch-presence: cHvxfaSCnHR6qzdutJZ/TZWK0FE= realtime/rewind: fDq9P84O672WgwAsIhYmPxnPLRA= authentication/jwt-token: Q0XIw74KKfK3Zv34fqKSck4ip5U= diff --git a/docs.gemspec b/docs.gemspec index dd1e8bee6f..6e9e0bfced 100644 --- a/docs.gemspec +++ b/docs.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = 'ably-docs' - spec.version = '1.0.0' + spec.version = '1.2.0' spec.authors = ["Matthew O'Riordan"] spec.email = ['support@ably.io'] spec.description = %q{Documentation repository Gem for Ably} diff --git a/lib/helpers/versions_helper.rb b/lib/helpers/versions_helper.rb index 51e66a7491..17de96ca6c 100644 --- a/lib/helpers/versions_helper.rb +++ b/lib/helpers/versions_helper.rb @@ -1,5 +1,5 @@ module VersionsHelper - CURRENT_VERSION = '1.1' unless defined?(CURRENT_VERSION) + CURRENT_VERSION = '1.2' unless defined?(CURRENT_VERSION) VERSIONED_FOLDERS = %w( client-lib-development-guide