diff --git a/Gemfile.lock b/Gemfile.lock index 02d4b78e61..14851c4c8e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,7 +8,7 @@ GEM clipboard (1.1.1) coderay (1.1.1) colored (1.2) - cri (2.9.1) + cri (2.10.1) colored (~> 1.2) domain_name (0.5.20170404) unf (>= 0.0.5, < 1.0.0) @@ -58,7 +58,7 @@ GEM byebug (~> 9.0) pry (~> 0.10) rack (2.0.3) - rake (12.0.0) + rake (12.3.0) rb-fsevent (0.10.2) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) @@ -103,4 +103,4 @@ RUBY VERSION ruby 2.4.1p111 BUNDLED WITH - 1.15.3 + 1.16.0 diff --git a/Procfile b/Procfile deleted file mode 100644 index f485c5890b..0000000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: sh boot.sh diff --git a/README.md b/README.md index 51de4100c9..a9effc4539 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,13 @@ If you would prefer to use Docker, then you can set up the env as follows: Running on Heroku ---- -This repo will automatically run on Heroku, but relies on the following buildpacks: +This repo will automatically run on Heroku, but relies on the following buildpacks (see https://github.com/ably-forks/heroku-buildpack-nanoc): $ heroku buildpacks === ably-docs Buildpack URLs - 1. heroku/ruby - 2. https://github.com/ably-forks/heroku-buildpack-nanoc.git + 1. https://github.com/heroku/heroku-buildpack-ruby + 2. https://github.com/ably-forks/heroku-buildpack-nanoc + 3. https://github.com/heroku/heroku-buildpack-static Pull requests ---- diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 7f486e7b24..f74c149497 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -39,7 +39,7 @@ $(function() { if (!idTag.length) { if (console.error) { console.error('Hash tag target #' + aid + ' is missing. Could not scroll'); } } else { - $('body').animate({ scrollTop: idTag.offset().top - stickNavHeight }, 'fast', function() { + $('html, body').animate({ scrollTop: (Math.round(idTag.offset().top) - stickNavHeight) + "px" }, 'fast', function() { setTimeout(function() { navIsScrolling = false; }, 750); }); var newUrl = document.location.href.replace(/#[^#]*$/,'') + '#' + aid; diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index ae9ae3ccaa..d33a1f09b7 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -82,7 +82,7 @@ h3(#restclient). RestClient * @(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)@ By default, every new HTTP request is first attempted to the default primary host @rest.ably.io@ (unless overriden in @ClientOptions#restHost@), which, through DNS, is automatically routed to the client's closest data center. The client library must always prefer the default endpoint (closest data center), even if a previous request to that endpoint has failed -** @(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 "@TO3l5@":#TO3l5). This ensures that a client library is able to work around routing or other problems for the user's closest data center. 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 data center 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 +** @(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 data center. 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 data center 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 data-centers unnecessarily * @(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: @@ -91,6 +91,15 @@ h3(#restclient). RestClient ** @(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, and where appropriate, report these failures to Ably's error tracking service. 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)@ Exception reporting 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 displayed to the user 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 @@ -101,14 +110,17 @@ h3(#rest-auth). Auth ** @(RSA3a)@ Can be used over HTTP or HTTPs ** @(RSA3b)@ For REST requests, the token string is Base64 encoded and used in the @Authorization: Bearer@ header ** @(RSA3c)@ For Realtime connections, the querystring param @accessToken@ is appended to the URL endpoint -* @(RSA4)@ Token Auth is used if @useTokenAuth@ is set to true, or if @useTokenAuth@ is unspecified and any one of the following conditions are met: a @clientId@ is specified; @authUrl@ or @authCallback@ is provided; an explicit @token@ or @TokenDetails@ is provided +* @(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@), 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, 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 token has expired or the server has responded with a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@), 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 -** @(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@ applies), an attempt to exchange a @TokenRequest@ for a @TokenDetails@ results in an error (unless @RSA4d@ applies), the provided token is in an invalid format, or the attempt times out after "@realtimeRequestTimeout@":#DF1b, then: -*** @(RSA4c1)@An @ErrorInfo@ with @code@ @80019@ and description of the underlying failure should be emitted with the state change, in the @errorReason@ and/or in the callback as appropriate +** @(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":#RSA4 applies), an attempt to exchange a @TokenRequest@ for a @TokenDetails@ results in an error (unless "RSA4d":#RSA4 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 the connection @errorReason@ should be set to the @ErrorInfo@ (or where there is none, as for a 403 @authUrl@ response with no body, an @ErrorInfo@ with @code@ @40300@ and an appropriate message) +** @(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 "RSA4e":#RSA4e), 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 neither @text/plain@ nor @application/json@; the object passed by @authCallback@ is neither a @String@, @JsonObject@, @TokenRequest@ object, nor @TokenDetails@ object; the text token string is greater than 384 bytes; the JSON stringified @JsonObject@, @TokenRequest@ or @TokenDetails@ is greater than 128kb. +** @(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 @@ -117,9 +129,12 @@ h3(#rest-auth). Auth * @(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: -** @(RSA7a)@ If a @clientId@ is provided in the @ClientOptions@, or is present in the current authentication token, then the client is considered to be identified (it has a @clientId@ that is implicit in all operations). Note that an authentication token @clientId@ wildcard value of @'*'@ is the exception where the client is unidentified and any @clientId@ is permitted in operations with Ably. The following applies to identified clients: -*** @(RSA7a1)@ All operations (such as message publishing or presence) will have an implicit @clientId@. The Ably service automatically updates the @clientId@ attribute (when empty) for all @Message@ and @PresenceMessage@ messages received from that identified client, and any messages then published from the Ably service will have the @clientId@ attribute populated. It is therefore expected that Ably client libraries do not explicitly set the @clientId@ field on messages published when @clientId@ is implicit in the connection or authentication scheme -*** @(RSA7a2)@ If @clientId@ is provided in @ClientOptions@, and an API key is provided along with no other means to generate a token, the client library will authenticate with Ably and obtain a token using the provided @clientId@ ensuring the token is restricted to operations for that @clientId@ +** @(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 @@ -162,7 +177,7 @@ h3(#rest-auth). Auth ** @(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)@ Adheres to the implementation of @requestToken@ when issuing new tokens +** @(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@ @@ -189,7 +204,6 @@ h3(#rest-channel). Channel ** @(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@, then key is not sent to Ably i.e. a payload with a @null@ value for @data@ would be sent as follows @{ "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): -** @(RSL1g)@ If the total size of the message or (if publishing an array) messages, calculated per "TO3l7":#TO3l7, exceeds the @maxMessageSize@, then the client library should reject the publish and indicate an error *** @(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@: @@ -199,6 +213,7 @@ h3(#rest-channel). Channel *** @(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)@ Where the library language permits, the @Channel#publish(name, data)@ method should provide an optional argument that allows the @clientId@ value to be specified such as @Channel#publish('event', 'data', { clientId: 'John' })@, and/or the @extras@ field to be specified such as @Channel#publish('event', 'data', { extras: { push: { 'key': 'value' } })@ +** @(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 * @(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: @@ -273,10 +288,11 @@ h3(#realtimeclient). RealtimeClient ** @(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 hypen 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 +** @(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 futher 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 @@ -293,6 +309,7 @@ h3(#realtimeclient). RealtimeClient ** @(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 @@ -305,9 +322,9 @@ h3(#realtime-connection). Connection ** @(RTN2f)@ API version param @v@ should be @1.0@ ** @(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 -** @(RTN4a)@ It emits a @ConnectionState@ event (one of @INITIALIZED@, @CONNECTING@, @CONNECTED@, @DISCONNECTED@, @SUSPENDED@, @CLOSING@, @CLOSED@, @FAILED@) for every connection state change -** @(RTN4h)@ It can emit an @UPDATE@ event (this is the only @ConnectionEvent@ which is not a @ConnectionState@). This is used for changes to connection conditions for which the @ConnectionState@ (e.g. @CONNECTED@) does not change. (The library must never emit a @ConnectionState@ event for a state equal to the previous state). +* @(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 @@ -364,14 +381,14 @@ h3(#realtime-connection). Connection ** @(RTN15a)@ If a @Connection@ transport is disconnected unexpectedly or if a token expires, 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 serverside 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)@ Anothing 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 +*** @(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 library reconnects to the websocket endpoint with two additional querystring params: *** @(RTN15b1)@ @resume@ is the private connection key assigned to the connection when the first @CONNECTED@ @ProtocolMessage@ was received *** @(RTN15b2)@ @connectionSerial@ is the most recent @ProtocolMessage#connectionSerial@ received from Ably or @Connection#serial@ which should be identical ** @(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 +**** @(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 @@ -417,9 +434,9 @@ h3(#realtime-channels). Channels h3(#realtime-channel). Channel * @(RTL1)@ As soon as a @Channel@ becomes attached, all incoming messages and presence messages 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 -** @(RTL2a)@ It emits a @ChannelState@ event (one of @INITIALIZED@, @ATTACHING@, @ATTACHED@, @DETACHING@, @DETACHED@, @SUSPENDED@ and @FAILED@) for every channel state change -** @(RTL2g)@ It can emit an @UPDATE@ event (this is the only @ChannelEvent@ which is not a @ChannelState@). This is used for changes to channel conditions for which the @ChannelState@ (e.g. @ATTACHED@) does not change. (The library must never emit a @ChannelState@ for a state equal to the previous state). +* @(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 @@ -603,6 +620,7 @@ h3(#eventemitter). EventEmitter mixin / interface * @(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 h2(#state-conditions-and-operations). State conditions and operations @@ -1076,7 +1094,7 @@ h4. TokenDetails h4. AuthDetails -* @(AD1)@ @AuthDetails@ is a type used with an @AUTH@ protocol messages to send authentication details +* @(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 @@ -1093,9 +1111,11 @@ h4. Stats h4. ErrorInfo -* @(TI1)@ Provides a generic Ably @ErrorInfo@ object that contains Ably @code@, @statusCode@ (analogous to HTTP status code) and @message@ attributes +* @(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)@ When errors are logged that contain a @code@, the following text should be included in all log entries: "See https://help.ably.io/error/@[CODE]@" h4. ConnectionStateChange @@ -1122,7 +1142,7 @@ h4. ConnectionDetails * @(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@ ("TO3l7":#TO3l7) +** @(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 @@ -1143,6 +1163,7 @@ h4. ClientOptions ** @(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 @@ -1151,10 +1172,10 @@ h4. ClientOptions ** @(TO3i)@ @recover@ string - A connection recovery string, specified with the intention of inheriting the state of an earlier connection ** @(TO3j)@ Auth option attributes: *** @(TO3j1)@ @key@ string - Full Ably key string as obtained from dashboard -*** @(TO3j2)@ @token@ string - An authentication token string issued for this application +*** @(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 +*** @(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@ @@ -1177,13 +1198,13 @@ h4. ClientOptions *** @(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 -*** @(TO3l7)@ @maxMessageSize@ integer - default 65536 (64kB). 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 -**** @(TO3l7a)@ The size is defined as the sum over all messages being published of the message @name@, @data@, @clientId@, and @extras@ -**** @(TO3l7b)@ The size of an @Object@ or @Array@ @data@ property is its string length after being JSON-stringified -**** @(TO3l7c)@ 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) -**** @(TO3l7d)@ The size of the @extras@ property is the string length of its JSON representation -**** @(TO3l7e)@ The size of a @null@ or omitted property is zero -*** @(TO3l8)@ @maxFrameSize@ integer - default 524288 (512kB). The maximum size of a single POST body or WebSocket frame. This is mostly only relevant for `Rest#request` (e.g. for batch publishes), since publishes will hit the @maxMessageSize@ limit before this +*** @(TO3l8)@ @maxMessageSize@ integer - default 65536 (64kB). 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 (512kB). The maximum size of a single POST body or WebSocket frame. This is mostly only relevant for `Rest#request` (e.g. for batch publishes), since publishes will hit the @maxMessageSize@ limit before this 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@ @@ -1197,7 +1218,9 @@ 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 +** @(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@ @@ -1229,7 +1252,7 @@ h3(#defaults). Client Library defaults The following default values are configured for the client library: * @(DF1)@ Realtime defaults: -** @(DF1a)@ @connectionStateTtl@ integer - default 60s. 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@ +** @(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 @@ -1309,6 +1332,7 @@ class ClientOptions: 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 @@ -1327,18 +1351,18 @@ class ClientOptions: httpRequestTimeout: Duration default 10s // TO3l4 httpMaxRetryCount: Int default 3 // TO3l5 httpMaxRetryDuration: Duration default 15s // TO3l6 - maxMessageSize: Int default 65536 // TO3l7 + maxMessageSize: Int default 65536 // TO3l8 maxFrameSize: Int default 524288 // TO3l8 class AuthOptions: // RSA8e - authCallback: (() -> io (String | TokenDetails | TokenRequest))? // RSA4a, RSA4, TO3j5, AO2b + authCallback: (() -> 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? // RSA4a, RSA4, TO3j2 + token: String? | TokenDetails? | TokenRequest? // RSA4a, RSA4, TO3j2 tokenDetails: TokenDetails? // RSA4a, RSA4, TO3j3 useTokenAuth: Bool? // RSA4, RSA14, TO3j4 @@ -1721,7 +1745,9 @@ class PushChannelSubscription: class ErrorInfo: code: Int // TI1 + href: String? // TI4 message: String // TI1 + cause: ErrorInfo? // TI1 statusCode: Int // TI1 class EventEmitter: diff --git a/content/client-lib-development-guide/versions/features-1-0__1-1.diff b/content/client-lib-development-guide/versions/features-1-0__1-1.diff new file mode 100644 index 0000000000..94ae41239f --- /dev/null +++ b/content/client-lib-development-guide/versions/features-1-0__1-1.diff @@ -0,0 +1,728 @@ +diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile +index 3c72399..d2a2618 100644 +--- a/content/client-lib-development-guide/features.textile ++++ b/content/client-lib-development-guide/features.textile +@@ -14,6 +14,7 @@ jump_to: + - Channels#rest-channels + - Channel#rest-channel + - Presence#rest-presence ++ - Encryption#rest-encryption + Realtime client library: + - RealtimeClient + - Connection#realtime-connection +@@ -22,9 +23,13 @@ jump_to: + - Presence#realtime-presence + - EventEmitter#eventemitter + - State conditions and operations#state-conditions-and-operations ++ Push notifications: ++ - Push notifications#push-notifications ++ - Activation state machine#activation-state-machine + Types: + - Data types#types + - Options#options ++ - Push notifications#types-push + Interface Definition: + - Complete API IDL#idl + Previous version: +@@ -70,22 +75,31 @@ h3(#restclient). RestClient + ** @(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 "RSC14c":#RSC14c and "RSC14d":#RSC14d ++* @(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)@ By default, every new HTTP request is first attempted to the default primary host @rest.ably.io@ (unless overriden in @ClientOptions#restHost@), which, through DNS, is automatically routed to the client's closest data center. The client library must always prefer the default endpoint (closest data center), even if a previous request to that endpoint has failed +-** @(RSC15a)@ In the case of an error necessitating use of an alternative host (see "RSC15d":#RSC15d), try fallback hosts 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 "@TO3l5@":#TO3l5). This ensures that a client library is able to work around routing or other problems for the user's closest data center. 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 data center 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 ++** @(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 data center. 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 data center 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 data-centers unnecessarily + * @(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, Hash params?, JsonObject | JsonArray body?, Hash 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"@, 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 ++** @(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"@, 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, and where appropriate, report these failures to Ably's error tracking service. 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)@ Exception reporting 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 displayed to the user 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 + +@@ -99,10 +113,14 @@ h3(#rest-auth). Auth + * @(RSA4)@ Token Auth is used if @useTokenAuth@ is set to true, or if @useTokenAuth@ is unspecified and any one of the following conditions are met: a @clientId@ is specified; @authUrl@ or @authCallback@ is provided; an explicit @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@), 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, 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 token has expired or the server has responded with a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@), 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 +-** @(RSA4c)@ If an attempt by the realtime client library to authenticate is made using the @authUrl@ or @authCallback@, and the request to @authUrl@ fails, the callback @authCallback@ results in an error, the provided token is in an invalid format, or the attempt times out after "@realtimeRequestTimeout@":#DF1b, then: +-*** @(RSA4c1)@An @ErrorInfo@ with @code@ @80019@ and description of the underlying failure should be emitted with the state change, in the @errorReason@ and/or in the callback as appropriate ++** @(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":#RSA4 applies), an attempt to exchange a @TokenRequest@ for a @TokenDetails@ results in an error (unless "RSA4d":#RSA4 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 "RSA4e":#RSA4e), 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 neither @text/plain@ nor @application/json@; the object passed by @authCallback@ is neither a @String@, @JsonObject@, @TokenRequest@ object, nor @TokenDetails@ object; the text token string is greater than 384 bytes; the JSON stringified @JsonObject@, @TokenRequest@ or @TokenDetails@ is greater than 128kb. ++** @(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 +@@ -132,6 +150,7 @@ h3(#rest-auth). Auth + *** @(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 +@@ -155,7 +174,7 @@ h3(#rest-auth). Auth + ** @(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)@ Adheres to the implementation of @requestToken@ when issuing new tokens ++** @(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@ +@@ -178,10 +197,11 @@ h3(#rest-channel). Channel + * @(RSL1)@ @Channel#publish@ function: + ** @(RSL1a)@ Expects either an array of @Message@ objects or a @name@ string and @data@ payload + ** @(RSL1b)@ When @name@ and @data@ 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. When publishing multiple messages, this approach is more efficient. However, a yet to be implemented feature should limit the total number of messages bundled in a single POST based on the default max request size, and would reject the publish and indicate an error if any single message exceeds that limit ++** @(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@, then key is not sent to Ably i.e. a payload with a @null@ value for @data@ would be sent as follows @{ "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): ++** @(RSL1g)@ 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 + *** @(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@: +@@ -264,10 +284,12 @@ h3(#realtimeclient). RealtimeClient + ** @(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 hypen 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 ++** @(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 futher 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 +@@ -284,6 +306,7 @@ h3(#realtimeclient). RealtimeClient + ** @(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 + +@@ -343,7 +366,7 @@ h3(#realtime-connection). Connection + ** @(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 @FAILED@ state and the @Connection#errorReason@ should be set ++** @(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 or timeout such as "RTN14c":#RTN14c 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 +@@ -352,13 +375,17 @@ h3(#realtime-connection). Connection + * @(RTN15)@ @Connection@ failures once @CONNECTED@: + ** @(RTN15h)@ If a @DISCONNECTED@ message is received from Ably, then that transport will subsequently be closed by Ably. If the @DISCONNECTED@ message contains a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@), if the token is renewable, 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, the next connection attempt fails due to a token error, or the token was not renewable, the connection will transition to the @FAILED@ 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 if a token expires, 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. Connection state is only maintained for a brief period, up to two minutes, so if a client is disconnected for a longer period connection state cannot be resumed ++** @(RTN15a)@ If a @Connection@ transport is disconnected unexpectedly or if a token expires, 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 serverside 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)@ Anothing 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 library reconnects to the websocket endpoint with two additional querystring params: + *** @(RTN15b1)@ @resume@ is the private connection key assigned to the connection when the first @CONNECTED@ @ProtocolMessage@ was received + *** @(RTN15b2)@ @connectionSerial@ is the most recent @ProtocolMessage#connectionSerial@ received from Ably or @Connection#serial@ which should be identical + ** @(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 ++**** @(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 +@@ -370,22 +397,23 @@ h3(#realtime-connection). Connection + ** @(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 connection key and latest serial received on the connection ++** @(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 data center. The client library must always prefer the default endpoint (closest data center), even if a previous connection attempt to that endpoint has failed +-** @(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 data center. The five default fallback hosts are @[a-e].ably-realtime.com@ and should be attempted in random order. See "RSC15a":#RSC15a for details on how custom fallback hosts are specified and used ++** @(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 data center. 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 response but with an "error body @statusCode@":/rest-api#error-response 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 data center 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 +-** @(RTN19b)@ If a @SYNC@ is underway, ensure the client library adheres to @RTP3@ ++** @(RTN19c)@ If a @SYNC@ is underway, ensure the client library adheres to @RTP3@ + * @(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 count. ++** @(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 ping frames) to satisfy this. So for example, for websocket transports, 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 @RTL4h@). + +@@ -439,18 +467,20 @@ h3(#realtime-channel). Channel + ** @(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 ++** @(RTL6a)@ Messages are encoded in the same way as the REST @Channel#publish@ method, and "RSL1g":#RSL1g (size limit) applies similarly + ** @(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 an array of @Message@ objects or a @name@ string and @data@ payload: + *** @(RTL6i1)@ When @name@ and @data@ 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. However, a yet to be implemented feature should limit the total number of messages bundled in a single ProtocolMessage based on the default max message size ++*** @(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 @ATTACHED@ then the messages are published immediately +-*** @(RTL6c2)@ If the connection is @INITIALIZED@, @CONNECTING@ or @DISCONNECTED@ or the channel is @INITIALIZED@, @ATTACHING@ or @ATTACHED@, and @ClientOptions#queueMessages@ has not been explicitly set to false, then the message will be queued and delivered as soon as the connection state enters the @CONNECTED@ state and the channel is @ATTACHED@ +-*** @(RTL6c4)@ If the connection is @SUSPENDED@, @CLOSING@, @CLOSED@, or @FAILED@, or the channel is @DETACHING@, @DETACHED@, @SUSPENDED@ or @FAILED@, the operation will result in an error +-*** @(RTL6c3)@ Implicitly attaches the @Channel@ if the channel is in the @INITIALIZED@ state. However, if the channel is in enters to the @DETACHED@ or @FAILED@ state before the operation succeeds, it will result in an error +-** @(RTL6d)@ Messages are delivered using a single @ProtocolMessage@ where possible by bundling in all messages for that channel into the @ProtocolMessage#messages@ array. However, a yet to be implemented feature should limit the total number of messages bundled per @ProtocolMessage@ based on the default max message size, and would reject the publish and indicate an error if any single message exceeds that limit ++*** @(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)@ Messages for a single channel that have been queued may be sent in a single @ProtocolMessage@ by bundling them into the @ProtocolMessage#messages@ array, subject to the following constraints: ++*** @(RTL6d1)@ The resulting @ProtocolMesssage@ must not exceed the @maxMessageSize@ ++*** @(RTL6d2)@ Messages with differing @clientId@ values must not be bundled together + ** @(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): +@@ -494,8 +524,8 @@ h3(#realtime-presence). Presence + * @(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 ++*** @(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 +@@ -507,7 +537,7 @@ h3(#realtime-presence). Presence + ** @(RTP18b)@ The 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 ++** @(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 is also responsible for keeping a separate copy of an object logically equivalent to the @PresenceMap@ containing only members that match the current @connectionId@. Any @ENTER@, @PRESENT@, @UPDATE@ or @LEAVE@ event that matches the current @connectionId@ should be applied to this object in the same way it is done for the @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 a @Channel@ becomes @ATTACHED@ with some or all continuity lost. + ** @(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 + * @(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 +@@ -526,7 +556,7 @@ h3(#realtime-presence). Presence + * @(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. However, if the channel is in or enters the @FAILED@ state before the operation succeeds, it will result in the listener not being registered and an error being indicated, typically to the optional callback where the language permits ++** @(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 +@@ -587,6 +617,7 @@ h3(#eventemitter). EventEmitter mixin / interface + * @(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 + + h2(#state-conditions-and-operations). State conditions and operations + +@@ -859,6 +890,100 @@ h3(#channel-states-operations). @RealtimeChannel.state@ effects on channel opera + + + ++h2(#push-notifications). Push notifications ++ ++* @(RSH1)@ @Push#admin@ object provides the following interface: ++** @(RHS1a)@ @#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 ++** @(RHS1b)@ @#deviceRegistrations@ provides access to the admin @PushDeviceRegistrations@ object with the following methods: ++*** @(RHS1b1)@ @#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 ++*** @(RHS1b2)@ @#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 ++*** @(RHS1b3)@ @#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 ++*** @(RHS1b4)@ @#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 ++*** @(RHS1b5)@ @#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. ++** @(RHS1c)@ @#channelSubscriptions@ provides access to the admin @ChannelSubscriptions@ object with the following methods: ++*** @(RHS1c1)@ @#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 ++*** @(RHS1c2)@ @#listChannels(params)@ performs a request to "/push/channels":/rest-api#list-channels and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, as supported by the REST API. A test should exist using the @limit@ attribute and pagination ++*** @(RHS1c3)@ @#save(pushChannelSubscription)@ issues a @POST@ (this will change to @PUT@ soon) request to "/push/channelSubscriptions":/rest-api#put-channel-subscription using the @PushChannelSubscription@ 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 ++*** @(RHS1c4)@ @#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. 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 ++*** @(RHS1c5)@ @#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)@ Whenever, from the platform's APIs, details for sending push notifications to the local device (e. g. GCM's registration token) is received, a @GotPushDeviceDetails@ event is sent to "the state machine":#RSH3. ++ ++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 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 @id@ and @updateToken@, enqueues a @CalledActivate@ event and transitions to @WaitingForNewPushDeviceDetails@. ++**** @(RSH3a2b)@ If the local device has the necessary push details (registration token, etc.), sends a @GotPushDeviceDetails@ event. ++**** @(RSH3a2c)@ Transitions to @WaitingForPushDeviceDetails@. ++** @(RSH3b)@ State @WaitingForPushDeviceDetails@: ++*** @(RSH3b1)@ On event @CalledActivate@: ++**** @(RSH3b1a)@ Transitions ot @CalledActivate@. ++*** @(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 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 @GotUpdateToken@ or @GettingUpdateTokenFailed@ event should be fired. ++**** @(RSH3b3d)@ Transitions to @WaitingForUpdateToken@. ++** @(RSH3c)@ State @WaitingForUpdateToken@: ++*** @(RSH3c1)@ On event @CalledActivate@: ++**** @(RSH3c1a)@ Transitions to @WaitingForUpdateToken@. ++*** @(RSH3c2)@ On event @GotUpdateToken@: ++**** @(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 @GettingUpdateTokenFailed@: ++**** @(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. ++**** @(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. GCM'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 PUT HTTP request to "/push/deviceRegistrations/:deviceId":/rest-api/#update-device-registration using the local @DeviceDetails@ 's push details as body and its @updateToken@ as authorization token. ++**** @(RSH3d3c)@ Either way, when the registration is done, a @RegistrationUpdated@ or @UpdatingRegistrationFailed@ event should be fired. ++**** @(RSH3d3d)@ Transitions to @WaitingForRegistrationUpdate@. ++** @(RSH3e)@ State @WaitingForRegistrationUpdate@: ++*** @(RSH3e1)@ On event @CalledActivate@: ++**** @(RSH3e1a)@ Makes @Push#activate@ return or call its callback with no error. ++**** @(RSH3e1b)@ Transitions to @WaitingForNewPushDeviceDetails@. ++*** @(RSH3e2)@ On event @RegistrationUpdated@: ++**** @(RSH3e2a)@ Transitions to @WaitingForNewPushDeviceDetails@. ++*** @(RSH3e3)@ On event @UpdatingRegistrationFailed@: ++**** @(RSH3e3a)@ Calls the @updateFailedCallback@ provided to @Push#activate@ with the error. ++**** @(RSH3e3b)@ Transitions to @AfterRegistrationUpdateFailed@. ++** @(RSH3f)@ State @AfterRegistrationUpdateFailed@: ++*** @(RSH3f1)@ On events @CalledActivate@ or @GotPushDeviceDetails@: ++**** @(RSH3f1a)@ Does the same as "RSH3d3":#RSH3d3. ++*** @(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)@ Removes the @updateToken@ from the 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 @AfterRegistrationUpdateFailed@ (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. ++ + h2. Types + + h3(#types). Data types +@@ -867,7 +992,7 @@ 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. If a message received from Ably 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@ ++** @(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-messages#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) +@@ -883,7 +1008,7 @@ 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 message. If a presence message received from Ably 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 @presence@ array of the @ProtocolMessage@ ++** @(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@ +@@ -897,7 +1022,16 @@ 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 in order from zero: @HAS_PRESENCE@, @HAS_BACKLOG@ and @RESUMED@ ++* @(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@ +@@ -974,9 +1108,11 @@ h4. Stats + + h4. ErrorInfo + +-* @(TI1)@ Provides a generic Ably @ErrorInfo@ object that contains Ably @code@, @statusCode@ (analogous to HTTP status code) and @message@ attributes ++* @(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@ +-* @(TE3)@ "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 ++* @(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)@ When errors are logged that contain a @code@, the following text should be included in all log entries: "See https://help.ably.io/error/@[CODE]@" + + h4. ConnectionStateChange + +@@ -1003,8 +1139,8 @@ h4. ConnectionDetails + * @(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@ is the maximum individual message size in bytes +-** @(CD2d)@ @maxFrameSize@ is the maximum size for a single frame of data sent to Ably. This restriction applies to a @ProtocolMessage@ sent over a realtime connection, or the total body size for a REST request ++** @(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 +@@ -1024,6 +1160,7 @@ h4. ClientOptions + ** @(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 +@@ -1032,10 +1169,10 @@ h4. ClientOptions + ** @(TO3i)@ @recover@ string - A connection recovery string, specified with the intention of inheriting the state of an earlier connection + ** @(TO3j)@ Auth option attributes: + *** @(TO3j1)@ @key@ string - Full Ably key string as obtained from dashboard +-*** @(TO3j2)@ @token@ string - An authentication token string issued for this application ++*** @(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 ++*** @(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@ +@@ -1055,9 +1192,16 @@ h4. ClientOptions + *** @(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 15,000 (15s). Timeout for any single HTTP request and response ++*** @(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 10,000 (10s). Max elapsed time in which fallback host retries for HTTP requests will be attempted i.e. if the first default host attempt takes 5s, and then the subsequent fallback retry attempt takes 7s, no further fallback host attempts will be made as the total elapsed time of 12s exceeds the default 10s limit ++*** @(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 (64kB). 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 (512kB). The maximum size of a single POST body or WebSocket frame. This is mostly only relevant for `Rest#request` (e.g. for batch publishes), since publishes will hit the @maxMessageSize@ limit before this + + 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@ +@@ -1071,7 +1215,9 @@ 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 ++** @(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@ +@@ -1083,7 +1229,7 @@ h4. ChannelOptions + * @(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 ++*** @(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 +@@ -1094,6 +1240,10 @@ h4. CipherParams + ** @(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 ++ ++TODO ++ + h3(#defaults). Client Library defaults + + The following default values are configured for the client library: +@@ -1116,10 +1266,12 @@ Please note the following conventions: + * 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 + + ```[python] + class Rest: +@@ -1127,8 +1279,16 @@ class Rest: + constructor(tokenStr: String) // RSC1 + constructor(ClientOptions) // RSC1 + auth: Auth // RSC5 ++ push: Push ++ device() => io LocalDevice + channels: Channels // RSN1 +- request(String method, String path, Hash params?, JsonObject | JsonArray body?, Hash headers?) => io HttpPaginatedResponse // RSC19 ++ 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 +@@ -1143,10 +1303,18 @@ class Realtime: + 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, Hash params?, JsonObject | JsonArray body?, Hash headers?) => io HttpPaginatedResponse // RTC9 ++ 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 +@@ -1161,6 +1329,7 @@ class ClientOptions: + 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 +@@ -1170,25 +1339,28 @@ class ClientOptions: + 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 // TO311 +- suspendedRetryTimeout: Duration default 30s // RTN14d, TO312 ++ disconnectedRetryTimeout: Duration default 15s // TO3l1 ++ suspendedRetryTimeout: Duration default 30s // RTN14d, TO3l2 + channelRetryTimeout: Duration default 15s // RTL13b, TO3l7 +- httpOpenTimeout: Duration default 4s // TO313 +- httpRequestTimeout: Duration default 15s // TO314 +- httpMaxRetryCount: Int default 3 // TO315 +- httpMaxRetryDuration: Duration default 10s // TO315 ++ 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: (() -> io (String | TokenDetails | TokenRequest))? // RSC14c, RSA4, TO3j5, AO2b +- authHeaders: [(String, String)]? // RSA8c3, TO3j8, AO2e ++ authCallback: (() -> 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: [String]]? // RSA8c3, RSA8c1, TO3j9, AO2f +- authUrl: String? // RSC14c, RSA4, RSA8c, TO3j6, AO2c +- key: String? // RSC14a, RSA14, TO3j1, AO2a ++ 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? // RSC14c, RSA4, TO3j2 +- tokenDetails: TokenDetails? // RSC14c, RSA4, TO3j3 ++ token: String? | TokenDetails? | TokenRequest? // RSA4a, RSA4, TO3j2 ++ tokenDetails: TokenDetails? // RSA4a, RSA4, TO3j3 + useTokenAuth: Bool? // RSA4, RSA14, TO3j4 + + class TokenParams: // RSAA8e +@@ -1232,6 +1404,7 @@ class Channels: + class RestChannel: + name: String? + presence: RestPresence // RSL3 ++ push: PushChannel + history( + start: Time, // RSL2b1 + end: Time api-default now(), // RSL2b1 +@@ -1247,6 +1420,7 @@ class RealtimeChannel: + state: ChannelState // RTL2b + presence: RealtimePresence // RTL9 + properties: ChannelProperties // CP1, RTL15 ++ push: PushChannel + attach() => io // RTL4d + detach() => io // RTL5e + history( +@@ -1264,6 +1438,13 @@ class RealtimeChannel: + unsubscribe((Message) ->) // RTL8a + unsubscribe(String, (Message) ->) // RTL8a + ++class PushChannel: ++ subscribeDevice() => io ++ subscribeClientId() => io ++ unsubscribeDevice() => io ++ unsubscribeClientId() => io ++ listSubscriptions() => io PaginatedResult ++ + enum ChannelState: + INITIALIZED + ATTACHING +@@ -1473,9 +1654,97 @@ enum StatsIntervalGranularity: + DAY + MONTH + ++class DeviceDetails: ++ id: String ++ clientId: String? ++ formFactor: DeviceFormFactor ++ metadata: JsonObject ++ platform: DevicePlatform ++ push: DevicePushDetails ++ updateToken: String ++ ++class DevicePushDetails: ++ errorReason: ErrorInfo? ++ recipient: JsonObject ++ state: .Active | .Failing | .Failed ++ ++class LocalDevice extends DeviceDetails: ++ setUpdateToken(String) ++ reissueUpdateToken() => io ++ ++class Push: ++ admin: PushAdmin ++ ++ // 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? // RSH3 ++ deactivate( ++ deregisterCallback: ((ErrorInfo?, deviceId: String?) -> io)? ++ ) => io ErrorInfo? ++ ++class PushAdmin: ++ deviceRegistrations: PushDeviceRegistrations ++ channelSubscriptions: PushChannelSubscriptions ++ publish(recipient: JsonObject, data: JsonObject) => io ++ ++class JsonObject: ++ // Platform-dependent, typically a Dict-like object ++ ++class PushDeviceRegistrations: ++ get(DeviceDetails) => io DeviceDetails ++ get(deviceId: String) => io DeviceDetails ++ list(params: Dict) => io PaginatedResult ++ save(DeviceDetails) => io DeviceDetails ++ remove(DeviceDetails) => io ++ remove(deviceId: String) => io ++ removeWhere(params: Dict) => io ++ ++class PushChannelSubscriptions: ++ get(PushChannelSubscription) => io PushChannelSubscription ++ list(params: Dict) => io PaginatedResult ++ listChannels(params: Dict?) => io PaginatedResult ++ save(PushChannelSubscription) => io PushChannelSubscription ++ remove(PushChannelSubscription) => io ++ removeWhere(params: Dict) => io ++ ++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: +@@ -1502,11 +1771,11 @@ class HttpPaginatedResponse // RSC19b + success: Bool // HP5 + errorCode: Int // HP6 + errorMessage: String // HP7 +- headers: Hash // HP8 ++ 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: + +-* v0.8 deprecated in Jan 2017. "View 0.8 → 1.0 changes":https://github.com/ably/docs/blob/source/content/client-lib-development-guide/versions/features-0-8__1-0.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/rest-api/index.textile b/content/rest-api/index.textile index 9f189649d3..08668052dc 100644 --- a/content/rest-api/index.textile +++ b/content/rest-api/index.textile @@ -1019,10 +1019,10 @@ bc[sh]. curl -X POST https://rest.ably.io/push/publish \ -H "Content-Type: application/json" \ --data \ ' - { +{ "recipient": { "clientId": "myClientId" - } + }, "notification": { "title": "Hello from Ably!", "body": "Example push notification from Ably." @@ -1031,8 +1031,7 @@ bc[sh]. curl -X POST https://rest.ably.io/push/publish \ "foo": "bar", "baz": "qux" } -} -' +}' The @recipient@ field supports delivering either to devices "registered to Ably":#post-device-registration by device ID, by their associated client ID, or directly to devices using the underlying notifications service (GCM, APNs, etc.), thus bypassing registrations to Ably altogether. diff --git a/content/root/how-ably-works.textile b/content/root/how-ably-works.textile index 9ff185ebb1..86907b1871 100644 --- a/content/root/how-ably-works.textile +++ b/content/root/how-ably-works.textile @@ -149,7 +149,7 @@ Most of our client libraries use a WebSocket to establish a realtime connection However, client libraries such as our "Javascript browser library":https://github.com/ably/ably-js are designed to choose the best transport available based on the browser and connection available. By supporting additional transports with the ability to fallback to the lowest common denominator, Ably ensures that practically every browser in use today is able to establish a realtime connection to Ably. The following transports are currently supported by our "Javascript browser library":https://github.com/ably/ably-js in order of best to worst performing: -* "WebSockets":https://en.wikipedia.org/wiki/WebSocket ("supported by 88% of browsers globally as of Nov 2015":http://caniuse.com/#feat=websockets) +* "WebSockets":https://en.wikipedia.org/wiki/WebSocket ("supported by 94% of browsers globally as of Dec 2017":http://caniuse.com/#feat=websockets) * XHR streaming * XDR streaming * XHR polling diff --git a/content/tutorials/publish-subscribe.textile b/content/tutorials/publish-subscribe.textile index 347479a084..25ca866096 100644 --- a/content/tutorials/publish-subscribe.textile +++ b/content/tutorials/publish-subscribe.textile @@ -6,7 +6,7 @@ index: 10 The Ably Realtime service organizes the message traffic within applications into named channels. Channels are the “unit” of message distribution; clients attach to any number of channels to subscribe to messages, and every message published to a channel is broadcasted to all subscribers. This scalable and resilient messaging pattern is commonly called pub/sub. -As you can publish a message to any channel, channels provide a means for you to filter data by topic or enforce access control for your subscribers. Only subscribers for those channesl will receive those messages. +As you can publish a message to any channel, channels provide a means for you to filter data by topic or enforce access control for your subscribers. Only subscribers for those channels will receive those messages. Messages published can contain string, JSON object, JSON array or binary data payloads. @@ -119,7 +119,7 @@ blang[javascript]. ``` - "See this step in Github":https://github.com/ably/tutorials/commit/843128d + "See this step in Github":https://github.com/ably/tutorials/commit/publish-subscribe-javascript-install-ably blang[nodejs]. To start using Ably you first need to install the NPM module. The NPM module can be installed as follows: @@ -379,7 +379,7 @@ blang[javascript]. }); ``` - "See this step in Github":https://github.com/ably/tutorials/commit/de53537 + "See this step in Github":https://github.com/ably/tutorials/commit/publish-subscribe-javascript-subscribe blang[nodejs]. ```[nodejs] @@ -499,7 +499,7 @@ blang[javascript]. channel.publish("update", { "team": "Man United" }); ``` - "See this step in Github":https://github.com/ably/tutorials/commit/140583e + "See this step in Github":https://github.com/ably/tutorials/commit/publish-subscribe-javascript blang[nodejs]. ```[nodejs] diff --git a/content/types/_auth_options.textile b/content/types/_auth_options.textile index 5fdf788ceb..8efacd4b25 100644 --- a/content/types/_auth_options.textile +++ b/content/types/_auth_options.textile @@ -19,9 +19,9 @@ h4. ruby: Attributes Python: Attributes -- authCallbackAuthCallbackauth_callback:auth_callback := A functionfunction with the form @function(tokenParams, callback(err, tokenOrTokenRequest))@@TokenCallback@ instancecallable (eg a lambda)proc / lambda (called synchronously in REST and Realtime but does not block EventMachine in the latter) which is called when a new token is required. The role of the callback is to either generate a signed "@TokenRequest@":/realtime/types#token-request which may then be submitted automatically by the library to the "Ably REST API @requestToken@":/rest-api#request-token; or to provide a valid token in as a "@TokenDetails@":/realtime/types#token-details object. See "an authentication callback example":<%= JsBins.url_for('authentication/auth-callback') %> or "our authentication documentation":/rest/authentication for details of the token request format and associated API calls.
__Type: @Callable@@TokenCallback@@Proc@@Func>@__ +- authCallbackAuthCallbackauth_callback:auth_callback := A functionfunction with the form @function(tokenParams, callback(err, tokenOrTokenRequest))@@TokenCallback@ instancecallable (eg a lambda)proc / lambda (called synchronously in REST and Realtime but does not block EventMachine in the latter) which is called when a new token is required. The role of the callback is to either generate a signed "@TokenRequest@":/realtime/types#token-request which may then be submitted automatically by the library to the "Ably REST API @requestToken@":/rest-api#request-token; or to provide a valid token in as a "@TokenDetails@":/realtime/types#token-details object. See "an authentication callback example":<%= JsBins.url_for('authentication/auth-callback') %> or "our authentication documentation":/rest/authentication for details of the token request format and associated API calls. Please note that the JSON stringified version of @TokenDetails@ or @TokenRequest@ must be less than 128kb.
__Type: @Callable@@TokenCallback@@Proc@@Func>@__ -- authUrlAuthUrl:auth_urlauth_url := A URL that the library may use to obtain a token string (in plain text format), or a signed "@TokenRequest@":/realtime/types#token-request or "@TokenDetails@":/realtime/types#token-details (in JSON format). For example, this can be used by a client to obtain signed token requests from an application server.
__Type: @String@@Uri@@NSURL@__ +- authUrlAuthUrl:auth_urlauth_url := A URL that the library may use to obtain a token string (in plain text format), or a signed "@TokenRequest@":/realtime/types#token-request or "@TokenDetails@":/realtime/types#token-details (in JSON format). For example, this can be used by a client to obtain signed token requests from an application server. Please note that the JSON stringified version of @TokenDetails@ or @TokenRequest@ must be less than 128kb.
__Type: @String@@Uri@@NSURL@__ - authMethodAuthMethod:auth_methodauth_method := _@GET@@:get@_ The HTTP verb to use for the request, either @GET@@:get@ or @POST@@:post@
__Type: @String@@Symbol@@HttpMethod@__ diff --git a/content/types/_client_options.textile b/content/types/_client_options.textile index 9f63fef884..c2f2fe1af4 100644 --- a/content/types/_client_options.textile +++ b/content/types/_client_options.textile @@ -18,7 +18,7 @@ h4. - keyKey:key := The full key string, as obtained from the "application dashboard":http://support.ably.io/solution/articles/3000030053-how-do-i-access-my-app-dashboard. Use this option if you wish to use Basic authentication, or wish to be able to issue tokens without needing to defer to a separate entity to sign token requests. Read more about "Basic authentication":/general/authentication#basic-authentication
__Type: @String@__ -- tokenToken:token := An authenticated token string that is most commonly obtained from the @token@@Token@ property of a "@TokenDetails@":/realtime/types#token-details component of a token request response. Use this option if you wish to use Token authentication. Read more about "Token authentication":/general/authentication#token-authentication
__Type: @String@__ +- tokenToken:token := An authenticated token. This can either be a "@TokenDetails@":/realtime/types#token-details object, a "@TokenRequest@":/realtime/types#token-request object, or token string obtained from the @token@>@Token@ property of a "@TokenDetails@":/realtime/types#token-details component of a token request response. Use this option if you wish to use Token authentication. Read more about "Token authentication":/general/authentication#token-authentication
__Type: @String@, @TokenDetails@ or @TokenRequest@__ - tokenDetailsTokenDetailstoken_details:token_details := An authenticated "@TokenDetails@":/realtime/types/#token-details object that is most commonly obtained from of a token request response. Use this option if you wish to use Token authentication. Read more about "Token authentication":/general/authentication#token-authentication
__Type: @TokenDetails@__ @@ -26,9 +26,9 @@ h4. - clientIdClientIdclient_id:client_id := A client ID, used for identifying this client when publishing messages or for presence purposes. The @clientId@@client_id@@ClientId@ can be any non-empty string. This option is primarily intended to be used in situations where the library is instanced with a key; note that a @clientId@@client_id@@ClientId@ may also be implicit in a token used to instance the library; an error will be raised if a @clientId@@client_id@ specified here conflicts with the @clientId@@client_id@@ClientId@ implicit in the token. "Find out more about client identities":/how-ably-works#client-identity
__Type: @String@__ -- authCallbackAuthCallbackauth_callback:auth_callback := A functionfunction with the form @function(tokenParams, callback(err, tokenOrTokenRequest))@@TokenCallback@ instancecallable (eg a lambda)proc / lambda (called synchronously in REST and Realtime but does not block EventMachine in the latter) which is called when a new token is required. The role of the callback is to either generate a signed "@TokenRequest@":/realtime/types#token-request which may then be submitted automatically by the library to the "Ably REST API @requestToken@":/rest-api#request-token; or to provide a valid token in as a "@TokenDetails@":/realtime/types#token-details object. See "an authentication callback example":<%= JsBins.url_for('authentication/auth-callback') %> or "our authentication documentation":/rest/authentication for details of the token request format and associated API calls.
__Type: @Callable@@TokenCallback@@Proc@@Func>@__ +- authCallbackAuthCallbackauth_callback:auth_callback := A functionfunction with the form @function(tokenParams, callback(err, tokenOrTokenRequest))@@TokenCallback@ instancecallable (eg a lambda)proc / lambda (called synchronously in REST and Realtime but does not block EventMachine in the latter) which is called when a new token is required. The role of the callback is to either generate a signed "@TokenRequest@":/realtime/types#token-request which may then be submitted automatically by the library to the "Ably REST API @requestToken@":/rest-api#request-token; or to provide a valid token in as a "@TokenDetails@":/realtime/types#token-details object. See "an authentication callback example":<%= JsBins.url_for('authentication/auth-callback') %> or "our authentication documentation":/rest/authentication for details of the token request format and associated API calls. Please note that the JSON stringified version of @TokenDetails@ or @TokenRequest@ must be less than 128kb.
__Type: @Callable@@TokenCallback@@Proc@@Func>@__ -- authUrlAuthUrlauth_url:auth_url := A URL that the library may use to obtain a token string (in plain text format), or a signed "@TokenRequest@":/realtime/types#token-request or "@TokenDetails@":/realtime/types#token-details (in JSON format). For example, this can be used by a client to obtain signed token requests from an application server.
__Type: @String@@Uri@__ +- authUrlAuthUrlauth_url:auth_url := A URL that the library may use to obtain a token string (in plain text format), or a signed "@TokenRequest@":/realtime/types#token-request or "@TokenDetails@":/realtime/types#token-details (in JSON format). For example, this can be used by a client to obtain signed token requests from an application server. Please note that the JSON stringified version of @TokenDetails@ or @TokenRequest@ must be less than 128kb.
__Type: @String@@Uri@__ - authMethodAuthMethodauth_method:auth_method := _@GET@@:get@_ The HTTP verb to use for the request, either @GET@@:get@ or @POST@@:post@
__Type: @String@@Symbol@@HttpMethod@__