Features spec
This document outlines the complete feature set of both the REST and Realtime client libraries. It is expected that every client library developer refers to this document to ensure that their client library provides the same API and features as the existing Ably client libraries. In addition to this, it is essential that there is test coverage over all of the features described below. As an example, see the Ruby library test specification and coverage generated from the test suite.
We recommend you use the IDL (Interface Definition Language) and refer to other existing libraries that adhere to this spec as a reference when reviewing how the API has been implemented.
The key words “must”, “must not”, “required”, “shall”, “shall not”, “should”, “should not”, “recommended”, “may”, and “optional” (whether lowercased or uppercased) in this document are to be interpreted as described in RFC 2119 .
Please note we maintain a separate Google Sheet that keeps track of which features are implemented and matching test coverage for each client library. If you intend to work on an Ably client library, please contact us for access to this Google Sheet as it is useful as a reference and also needs to be kept up to date
Test guidelines
(G1)
Every test should be executed using all supported protocols (i.e. JSON and MessagePack if supported). This includes both sending & receiving data(G2)
All tests by default are run against a special Ably sandbox environment. This environment allows apps to be provisioned without any authentication that can then be used for client library testing. Bear in mind that all apps created in the sandbox environment are automatically deleted after 60 minutes and have low limits to prevent abuse. Apps are configured by sending aPOST
request tohttps://sandbox-rest.ably.io/apps
with a JSON body that specifies the keys and their associated capabilities, channel namespace rules and any presence fixture data that is required; see ably-common test-app-setup.json. Presence fixture data is necessary for the REST library presence tests as there is no way to register presence on a channel in the REST library(G3)
Testing statistics can be tricky due to timing issues and slow test suites as a result of sending requests to generate statistics. As such, we provide a special stats endpoint in our sandbox environment that allows stats to be injected into our metrics system so that stats tests can make predictable assertions. To create stats you must send an authenticatedPOST
request to the stats JSON tohttps://sandbox-rest.ably.io/stats
with the stats data you wish to create. See the JavaScript stats fixture and setup helper as an example(G4)
This spec defines API version 1.1. A client library must identify to Ably the version of the spec it uses in all requests and connections, per RSC7a and RTN2f. The spec it uses is defined as the latest API version for which the library implements all spec items relating to the wire protocol
REST client library
RestClient
(RSC1)
The constructor accepts a set ofClientOptions
or, in languages that support overloaded constructors, a string which may be a token string or an API key.(RSC1a)
If a single string argument is supplied when constructing the library then the library must determine whether this is a key or a token by checking for the presence of the ‘:’ (colon) delimiter present in an API key. Any other string must be treated as a token string.(RSC1b)
If invalid arguments are provided such as no API key, no token and no means to create a token, then this will result in an error with error code 40106 and an informative message.(RSC1c)
Tests must exist that in each overloaded library constructor the library correctly determines an API key to be a key, and each type of token string is determined to be a token.
(RSC2)
The logger by default outputs toSTDOUT
(or other logging medium as appropriate to the platform) and the log level is set to warning(RSC3)
The log level can be changed(RSC4)
A custom logger can be provided in the constructor(RSC5)
RestClient#auth
attribute provides access to theAuth
object that was instantiated with theClientOptions
provided in theRestClient
constructor(RSC6)
RestClient#stats
function:(RSC6a)
Returns aPaginatedResult
page containingStats
objects in thePaginatedResult#items
attribute returned from the stats request(RSC6b)
Supports the following params:***
(RSC6b1)
start
andend
are timestamp fields represented as milliseconds since epoch, or where suitable to the language, Date or Time objects.start
must be equal to or less thanend
and is unaffected by the request direction***
(RSC6b2)
direction
backwards or forwards; if omitted the direction defaults to the REST API default (backwards)***
(RSC6b3)
limit
supports up to 1,000 items; if omitted the limit defaults to the REST API default (100)***
(RSC6b4)
unit
is the period for which the stats will be aggregated by, values supported areminute
,hour
,day
ormonth
; if omitted the unit defaults to the REST API default (minute
)
(RSC16)
RestClient#time
function sends a get request torest.ably.io/time
and returns the server time in milliseconds since epoch or as a Date/Time object where suitable(RSC7)
Sends REST requests over HTTP and HTTPS to the REST endpointrest.ably.io
(RSC7a)
The headerX-Ably-Version: 1.1
must be included in all REST requests to the Ably endpoint(RSC7b)
The headerX-Ably-Lib: [lib][.optional variant]?-[version]
should be included in all REST requests to the Ably endpoint where[lib]
is the name of the library such asjs
forably-js
,[.optional variant]
is an optional library variant, such aslaravel
for thephp
library, which is always delimited with a period such asphp.laravel
, and where[version]
is the full client library version using Semver such as1.0.2
. For example, the 1.0.0 version of the JavaScript library would use the headerX-Ably-Lib: js-1.0.0
***
(RSC7b1)
When it is not possible to send theX-Ably-Lib
header, such as forJSONP
requests, the library version should be sent as a query param such aslib=js-1.0.0
(RSC18)
IfClientOptions#tls
is true, then all communication is over HTTPS. If false, all communication is over HTTP however Basic Auth over HTTP will result in an error as private keys cannot be submitted over an insecure connection. SeeAuth
below(RSC8)
Supports two protocols:(RSC8a)
MessagePack binary protocol (this is the default for environments having a suitable level or support for binary data)(RSC8b)
JSON text protocol (used whenuseBinaryProtocol
option is false)
(RSC9)
UsesAuth
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 value40140 <= code < 40150
), then the Auth class is responsible for reissuing a token and the request should be reattempted, see RSA4a and RSA4b(RSC11)
Requests are sent to the default endpointrest.ably.io
. However, if therestHost
option is set, the client will send requests to the specified host. Ifenvironment
option is configured and is not production”, the environment name is prefixed to the default host endpoint and therestHost
is set accordingly. For example, if theenvironment
is set to sandbox, then therestHost
endpoint will becomesandbox-rest.ably.io
. See “TO3k2 for constraints.(RSC12)
REST endpoint host is configurable in the Client constructor with the optionrestHost
(RSC13)
The client library must use the connection and request timeouts specified in theClientOptions
, falling back to the defaults described inClientOptions
below(RSC15)
Host Fallback(RSC15b)
The fallback behavior described below only applies when the defaultrest.ably.io
endpoint is being used and has not been overriden (see RSC11),ClientOptions#fallbackHostsUseDefault
istrue
, or an array ofClientOptions#fallbackHosts
is provided. If host fallback is not supported, failing HTTP requests that would have qualified for a retry against a fallback host, will instead result in an error immediately(RSC15e)
The primary host is by defaultrest.ably.io
(unless overriden inClientOptions#environment
orClientOptions#restHost
), which, through DNS, is automatically routed to the client’s closest datacenter. New HTTP requests (except whereRSC15f
applies and a cached fallback host is in effect) are first attempted against the primary host.(RSC15a)
In the case of an error necessitating use of an alternative host (see RSC15d), 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 occur, failing when all have been tried or the configuredhttpMaxRetryCount
has been reached (see TO3l<code>). This ensures that a client library is able to work around routing or other problems for the user’s closest datacenter. For example, if a </code>POSTrequest to
rest.ably.iofails because the default endpoint is unreachable or unserviceable, then the
POSTrequest should be retried again against the fallback hosts in attempt to find an alternate healthy datacenter to service the request. The five default fallback hosts are
[a-e].ably-realtime.com. If an array of custom fallback hosts are provided in
ClientOptions#[email protected], 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 range500 <= code <= 504
. Resending requests that have failed for other failure conditions will not fix the problem and will simply increase the load on other datacenters unnecessarily(RSC15f)
Once/if a given fallback host succeeds, the client should store that successful fallback host forClientOptions.fallbackRetryTimeout
. Future HTTP requests during that period should use that host. If during this period a qualifying errors occurs on that host, or afterfallbackRetryTimeout
has expired, it should be un-stored, and the fallback sequence begun again from scratch, starting with the default primary host (rest.ably.io
orClientOptions#restHost
) or, in the case of an existing fallback realtime connection as per (RTN17e), with the current fallback realtime host.
(RSC17)
When instancing the library, if aclientId
attribute is set inClientOptions
, then theAuth#clientId
attribute will contain the providedclientId
(RSC19)
RestClient#request
function is provided as a convenience for customers who wish to use 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 isrequest(string method, string path, Dict<String, String> params?, JsonObject | JsonArray body?, Dict<String, String> headers?) -> HttpPaginatedResponse
with arguments:method
is a valid HTTP verb (must support"GET
,OST
, andUT
, should supportATCH
andELETE", may support others);
pathis the path component of the URL such as
“/channels”;
paramsand
headersare 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
Objectin the case of JavaScript);
bodyis an optional
JsonObjector
[email protected] 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)(RSC19c)
The library will configure theAccept
andContent-Type
type headers to reflect whether the client is configured to use a binary or JSON based protocol (see RSC8). All requests are encoded and decoded into Json or MsgPack as appropriate automatically by the library. Binarybody
payloads are not supported at this time(RSC19d)
request
method returns anHttpPaginatedResponse
object that inherits from thePaginatedResult
object to provide details on the response plus paging support where applicable. See 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), then therequest
method should indicate an error in an idiomatic way for the platform
(RSC20)
Unexpected internal library exception handling:(RSC20a)
The library must make every attempt to handle unexpected internal exceptions as gracefully as possible. For the avoidance of doubt, unexpected internal exceptions do not include request timeouts, invalid argument values, invalid responses from third parties or exceptions in code run in callbacks registered by applications. However, any unexpected failures or unhandled exceptions in our own code that typically could trigger a crash or raise an exception in our customer’s code, is considered an unexpected internal exception(RSC20b)
The library may optionally report unexpected internal exceptions to the Ably exception reporting service. Exception reporting, when supported, is enabled by default, but can be disabled using thelogExceptionReportingUrl
ClientOption
documented inTO3m
. When enabled, the client library must:***
(RSC20b1)
At startup log a message with the equivalent ofinfo
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”. Theinfo
log level is preferred as customers can hide this entry by configuring the client library log level towarning
, 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 thelogExceptionReportingUrl
using theSentry API
either via a raw HTTP request or by using an embedded Sentry exception reporting client library. A message atinfo
log level should be logged if the request succeeds or fails i.e. a failure to submit an exception should not be logged as a failure / error. Where possible, the exception GUID returned from the Sentry API should be logged so that the specific error can be tracked(RSC20c)
Exceptions reported must additionally include the following tags:ably-lib
with the value defined in RSC7b,ably-version
with the value defined in 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 theerrors.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 inRSC20b2
, a failure to log an exception is logged with log levelinfo
i.e. an exception reporting failure is not consider a client libraryerror
orwarning
(RSC20f)
Any errors emitted by the library as a result of an internal failure must contain a status code500
, an error code in the range51000
to51999
and a suitable error message. The error code must match one of our common error codes
Auth
(RSA1)
Basic Auth connects over HTTPS by default. Any attempt to use Basic Auth over HTTP without TLS will result in an error(RSA11)
When using Basic Auth, the API key is Base64 encoded and included in anAuthorization
header, as specified in RFC7235. The API key follows the format"KEY_NAME:KEY_SECRET"
so when authenticating using Basic Auth, the key name can be used as the username and the key secret as the password(RSA2)
Basic Auth is the default authentication scheme if an API key exists(RSA3)
Token Auth:(RSA3a)
Can be used over HTTP or HTTPs(RSA3b)
For REST requests, the token string is optionally Base64-encoded and used in theAuthorization: Bearer
header(RSA3c)
For Realtime websocket connections, the querystring paramaccessToken
is appended to the URL endpoint(RSA3d)
A test must exist that each type of token string is correctly passed in requests (ie according to(RSA3b)
and(RSA3c)
)
(RSA4)
Token Auth is used ifuseTokenAuth
is set to true, or ifuseTokenAuth
is unspecified and any one ofauthUrl
,authCallback
,token
, orTokenDetails
is provided(RSA4a)
When atoken
ortokenDetails
is used to instantiate the library, and no means to renew the token is provided (either an API key,authCallback
orauthUrl
):***
(RSA4a1)
At instantiation time, a message atinfo
log level with error code40171
should be logged indicating that no means has been provided to renew the supplied token, including an associated url perTI5
***
(RSA4a2)
if the server responds with a token error (401 HTTP status code and an Ably error value40140 <= code < 40150
), then the client library should indicate an error with error code40171
, not retry the request and, in the case of the realtime library, transition the connection to theFAILED
state(RSA4b)
When the client does have a means to renew the token automatically, and the server has responded with a token error (statusCode
value of 401 and errorcode
value in the range40140 <= code < 40150
) or the client library has optionally detected the current token has expired (see RSA4b1), then the client should automatically make a single attempt to reissue the token and resend the request using the new token. If the token creation failed or the subsequent request with the new token failed due to a token error, then the request should result in an error (in the case of a realtime library, followRSA4c
)***
(RSA4b1)
Client libraries can optionally save a round-trip request to the Ably service for expired tokens by detecting when a token has expired when all of the following applies: the current token is aTokenDetails
object with anexpires
attribute; the library has previously queried the time from the Ably service and persisted the local clock offset according to RSA10k; theexpires
time has passed based on the Ably service time and not the local clock (which is not guaranteed to be accurate)(RSA4c)
If an attempt by the realtime client library to authenticate is made using theauthUrl
orauthCallback
, and the request toauthUrl
fails (unlessRSA4d
applies), the callbackauthCallback
results in an error (unless RSA4d applies), an attempt to exchange aTokenRequest
for aTokenDetails
results in an error (unless RSA4d applies), the provided token is in an invalid format (as defined in RSA4e), or the attempt times out afterrealtimeRequestTimeout
, then:***
(RSA4c1)
AnErrorInfo
withcode
80019
,statusCode
401, andcause
set to the underlying cause should be emitted with the state change if there is one (perRSA4c2/3
) and set as the connectionerrorReason
***
(RSA4c2)
If the connection isCONNECTING
, then the connection attempt should be treated as unsuccessful, and as such the connection should transition to theDISCONNECTED
orSUSPENDED
state as defined in RTN14 and RTN15***
(RSA4c3)
If the connection isCONNECTED
, then the connection should remainCONNECTED
(RSA4d)
If a request by a realtime client to anauthUrl
results in an HTTP 403 response, or any of anauthUrl
request, anauthCallback
, or a request to Ably to exchange aTokenRequest
for aTokenDetails
result in anErrorInfo
withstatusCode
403, then the client library should transition to theFAILED
state, with anErrorInfo
(withcode
80019
,statusCode
403, andcause
set to the underlying cause) emitted with the state change and set as the connectionerrorReason
(RSA4e)
If in the course of a REST request (or explicit call torequestToken
) an attempt to authenticate usingauthUrl
orauthCallback
fails due to a timeout, network error, a token in an invalid format (per RSA4f), or some other auth error condition other than an explicitErrorInfo
from Ably, the request should result in an error withcode
40170,statusCode
401, and a suitable error message(RSA4f)
The following conditions imply that the token is in an invalid format: theauthUrl
response content type is not one oftext/plain
,application/json
orapplication/jwt
; the object passed byauthCallback
is neither aString
,JsonObject
,TokenRequest
object, norTokenDetails
object; the token string or the JSON stringifiedJsonObject
,TokenRequest
orTokenDetails
is greater than 128KiB.(RSA4g)
If multipleauthOptions
are used to initialize the library, the preference ordering among them is identical toAuth#authorize
, defined inRSA10e
(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 optionuseTokenAuth
is specified, and akey
is not provided, then the client library is unable to authenticate or issue a token(RSA15)
If Token Auth is selected andclientId
has been set in theClientOptions
when the library was instantiated:(RSA15a)
AnyclientId
provided inClientOptions
must match any non wildcard ('*'
)clientId
value inTokenDetails
orconnectionDetails
of theCONNECTED
ProtocolMessage
, where applicable(RSA15b)
If the clientId fromTokenDetails
orconnectionDetails
contains only a wildcard string ‘*’, then the client is permitted to be either unidentified (i.e. authorised to act on behalf of any clientId) or identified by providing aclientId
when communicating with Ably(RSA15c)
Following an auth request which uses aTokenDetails
orTokenRequest
object that contains an incompatibleclientId
, the library should in the case of Realtime transition the connection state toFAILED
, and in the case of REST result in an appropriate error response
(RSA5)
TTL for new tokens is specified in milliseconds. If the user-providedtokenParams
does not specify a TTL, the TTL field should be omitted from thetokenRequest
, and Ably will supply a token with a TTL of 60 minutes. See TK2a(RSA6)
Thecapability
for new tokens is JSON stringified. If If the user-providedtokenParams
does not specify capabilities, thecapability
field should be omitted from thetokenRequest
, and Ably will supply a token with the capabilities of the underlying key. See TK2b(RSA7)
clientId
and authenticated clients:(RSA7d)
IfclientId
is provided inClientOptions
andRSA4
indicates that token auth is to be used, theclientId
field in theTokenParams
(TK2c
) should be set to thatclientId
when requesting a token(RSA7e)
IfclientId
is provided inClientOptions
andRSA4
indicates that basic auth is to be used, then:***
(RSA7e1)
For realtime clients, the connect request should include theclientId
as a querystring parameter,clientId
***
(RSA7e2)
For REST clients, all requests should include anX-Ably-ClientId
header with value set to theclientId
, Base64 encoded(RSA7a)
A client is considered to be identified if aclientId
is implicit in either the connection or the authentication scheme; that is, is present in the current authentication token (with the exception of the wildcardclientId
'*'
), is set by a header perRSA7e2
, or is specified when initiating a realtime connection perRSA7e1
. The following applies to identified clients:***
(RSA7a1)
All operations (such as message publishing or presence) carried out by an identified client will have an implicitclientId
. The Ably service automatically updates theclientId
attribute (when empty) for allMessage
andPresenceMessage
messages received from that client. Client libraries should therefore not explicitly set theclientId
field on messages published from an identified client***
(RSA7a4)
When aclientId
value is provided in bothClientOptions#clientId
andClientOptions#defaultTokenParams
, theClientOptions#clientId
takes precedence and is used for all Auth operations(RSA12)
Auth#clientId
attribute isnull
when:***
(RSA12a)
TheclientId
attribute of aTokenRequest
orTokenDetails
used for authentication isnull
, orConnectionDetails#clientId
isnull
following a connection to Ably. In this case, thenull
value indicates that aclientId
identity may not be assumed by this client i.e. the client is anonymous for all operations***
(RSA12b)
The client was instantiated without assigning a value forClientOptions#clientId
(null
), and the client has not yet authenticated or connected to Ably. In this case, thenull
value indicates that the client has not yet been able to confirm its identity, and therefore may change and become identified following later authentication or establishment of a connection with Ably(RSA7b)
Auth#clientId
is notnull
when:***
(RSA7b1)
AclientId
is provided in theClientOptions
.clientId
should be a string***
(RSA7b2)
Token authentication is being used, and theTokenRequest
orTokenDetails
object, used for authentication, has aclientId
value that is notnull
***
(RSA7b3)
Following a realtime connection being established, if theCONNECTED
ProtocolMessages
contains aclientId
that is notnull
.clientId
is an attribute ofProtocolMessage#connectionDetails
within aCONNECTED
ProtocolMessage
***
(RSA7b4)
When a wildcard string'*'
is present in theTokenRequest
,TokenDetails
, orProtocolMessage#connectionDetails
object, then the client does not have an identity but is allowed to assume an identity when performing operations with Ably. As such,Auth#clientId
should contain the string value'*'
indicating that the current client is allowed to perform operations on behalf of anyclientId
(RSA7c)
AclientId
provided in theClientOptions
when instancing the library must be eithernull
or a string, and cannot contain only a wildcard'*'
string value as that client ID value is reserved
(RSA8)
Auth#requestToken
function:(RSA8e)
Method signature isrequestToken(TokenParams, AuthOptions)
.TokenParams
andAuthOptions
are optional. WhenTokenParams
orAuthOptions
are provided, the values of each attribute are not merged with the configured client library defaults, but rather are used instead of the stored values (even whennull
). If the object arguments are omitted, the client library configured defaults are used(RSA8a)
Implicitly creates aTokenRequest
if required, and requests a token from Ably if required. Returns aTokenDetails
object(RSA8b)
Supports allTokenParams
in the function arguments, which override defaults forClient
Auth
(RSA8c)
WhenauthUrl
option is set, it will query the provided URL to obtain aTokenRequest
or the token itself (either a token string or aTokenDetails
). The query is performed using the given URL using the HTTP method inauthMethod
, headers (fromauthHeaders
) and supplementary params (fromauthParams
). The token retrieved is assumed by the library to be a token string if the response hasContent-Type
"text/plain
orpplication/jwt
, or taken to be aTokenRequest
orTokenDetails
object if the response hasContent-Type
pplication/json"
.authMethod
can be eitherGET
orPOST
, or if not specified, will default toGET
. It can be quite difficult to add test coverage for these scenarios – as such, we have developed a simple echo server that can be used in your tests, see the ably-js authUrl echo tests***
(RSA8c1)
TokenParams
and any configuredauthParams
andauthHeaders
are always sent to theauthUrl
as follows:(RSA8c1a)
When theauthMethod
isGET
or unspecified, theTokenParams
andauthParams
are merged and appended to the URL as query string params, and theauthHeaders
are sent as HTTP headers(RSA8c1b)
When theauthMethod
isPOST
, theTokenParams
andauthParams
are merged and sent form-encoded in the body of thePOST
request, and theauthHeaders
are sent as HTTP headers(RSA8c1c)
If the givenauthUrl
includes any querystring params, they should be preserved. In theGET
case,authParams
/tokenParams
should be merged with them. If a name conflict occurs,authParams
/tokenParams
should take precedence***
(RSA8c2)
TokenParams
take precedence over any configuredauthParams
when a name conflict occurs***
(RSA8c3)
SpecifyingauthParams
orauthHeaders
as part ofAuthOptions
replaces any configuredauthParams
orauthHeaders
specified inClientOptions
respectively. As the provided key/value pairs are not merged with theClientOptions
configured key/value pairs, this enables a developer to deleteauthParams
orauthHeaders
where necessary by providing an entire new set of key/value pairs(RSA8d)
WhenauthCallback
option is set, it will invoke the callback, passing in theTokenParams
, and expects either a token string, aTokenDetails
object or aTokenRequest
object to be returned, which will in turn be used to request a token from Ably(RSA8f)
A test should exist for the following:***
(RSA8f1)
Request a token with anull
valueclientId
, authenticate a client with the token, publish a message without an explicitclientId
, and ensure the message published does not have aclientId
. Check thatAuth#clientId
isnull
***
(RSA8f2)
Request a token with anull
valueclientId
, authenticate a client with the token, publish a message with an explicitclientId
value, and ensure that the message is rejected***
(RSA8f3)
Request a token with a wildcard'*'
valueclientId
, authenticate a client with the token, publish a message without an explicitclientId
, and ensure the message published does not have aclientId
. Check thatAuth#clientId
is a string with value'*'
***
(RSA8f4)
Request a token with a wildcard'*'
valueclientId
, authenticate a client with the token, publish a message with an explicitclientId
value, and ensure that the message published has the providedclientId
(RSA8g)
Tests must exist to verify both theauthCallback
andauthURL
mechanisms where the returned token string value is a JWT token string and an Ably token string.
(RSA9)
Auth#createTokenRequest
function:(RSA9h)
Method signature iscreateTokenRequest(TokenParams, AuthOptions)
.TokenParams
andAuthOptions
are optional.WhenTokenParams
orAuthOptions
are provided, the values of each attribute are not merged with the configured client library defaults, but rather are used instead of the stored values (even whennull
). If the object arguments are omitted, the client library configured defaults are used(RSA9a)
Returns a signedTokenRequest
object that can be used to obtain a token from Ably. This is useful for servers that can create aTokenRequest
signed with the API key without communicating with Ably directly. TheTokenRequest
can then be passed to a designated client that is then responsible for communicating with Ably and requesting a token for authentication from thatTokenRequest
(RSA9c)
Generates a unique 16+ characternonce
if none is provided; the nonce is used to protect against replay attacks(RSA9d)
Generates atimestamp
from current time if not provided, will retrieve the server time ifqueryTime
is true(RSA9e)
TTL is optional and specified in milliseconds(RSA9f)
Capability JSON text can be provided that specifies the rights of the token in terms of the channel(s) authorized and the permitted operations on each(RSA9g)
A valid HMAC is created using the key secret (using the key from the passed-inAuthOptions
if supplied) to sign theTokenRequest
so that it can be used by any client to request a token without having or exchanging any secrets(RSA9i)
Adheres to all requirements inRSA8
relating toTokenParams
(RSA10)
Auth#authorize
function:(RSA10a)
Instructs the library to create a token immediately and ensures Token Auth is used for all future requests. See RTC8 for re-authentication behavior when called for a realtime client(RSA10j)
Method signature isauthorize(TokenParams, AuthOptions)
.TokenParams
andAuthOptions
are optional. When the arguments are present, even if empty, theTokenParams
andAuthOptions
supersede any previously client library configuredTokenParams
andAuthOptions
. For example, if a client is initialized withTokenParams#ttl
configured with a custom value, and aTokenParams
object is passed in as an argument to#authorize
with anull
or missing value forttl
, then thettl
used for every subsequent authorization will benull
(RSA10b)
Supports allAuthOptions
andTokenParams
in the function arguments(RSA10k)
If theAuthOption
argument’squeryTime
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 toauthorize
will not obtain the server time, but instead use the local clock offset to calculate the server time. The client library itself MAY internally discard the cached local clock offset in situations in which it may have been invalidated, such as if there is a local change to the date, time, or timezone, of the client device. For clarity however, there is no requirement for this cache invalidation to be available to consumers of the client library API.(RSA10e)
If theauthOptions
contains a way of obtaining a token (anauthCallback
,authUrl
, orkey
), that should be used to obtain a new token, as perrequestToken
(RSA8
). If it contains a token (token
ortokenDetails
), 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 perRSA10g
for when the token expires. (Ordering of preference within those groups is not defined and is left up to individual implementations)(RSA10f)
Returns aTokenDetails
object that contains the token string + token metadata(RSA10g)
Stores theAuthOptions
andTokenParams
arguments as defaults for subsequent authorizations with the exception of the attributesTokenParams#timestamp
andAuthOptions#queryTime
(RSA10h)
Will use the value fromAuth#clientId
by default, if notnull
(RSA10i)
Adheres to all requirements inRSA8
relating toTokenParams
,authCallback
andauthUrl
(RSA10l)
Has an alias methodRestClient#authorise
andRealtimeClient#authorise
that will log a deprecation warning stating that this alias method will be removed inv1.0
and the user should instead useauthorize
(RSA16)
Auth#tokenDetails
:(RSA16a)
Holds aTokenDetails
representing the token currently in use by the library, if any;(RSA16b)
If the library is provided with a token without the correspondingTokenDetails
, then this holds aTokenDetails
instance in which only thetoken
attribute is populated with that token string(RSA16c)
Is set with the current token (if applicable) on instantiation and each time it is replaced, whether the result of an explicitAuth#authorize
operation, or a library-initiated renewal resulting from expiry or a token error response(RSA16d)
Is empty if there is no current token, including after a previous token has been determined to be invalid or expired, or if the library is using basic auth
Channels
(RSN1)
Channels
is a collection ofChannel
objects accessible throughRest#channels
(RSN2)
Methods should exist to check if a channel exists or iterate through the existing channels(RSN3)
Channels#get
function:(RSN3a)
Creates a newChannel
object for the specified channel if none exists, or returns the existing channel.ChannelOptions
can be specified when instancing a newChannel
(RSN3b)
If options are provided, the options are set on theChannel
(RSN3c)
Accessing an existingChannel
with options in the formChannels#get(channel, options)
will update the options on the channel and then return the existingChannel
object
(RSN4)
Channels#release
function:(RSN4a)
Releases the channel resource i.e. it’s deleted and can then be garbage collected
Channel
(RSL1)
Channel#publish
function:(RSL1a)
Expects either aMessage
object, an array ofMessage
objects, or aname
string anddata
payload(RSL1b)
Whenname
anddata
(or aMessage
) is provided, a single message is published to Ably(RSL1c)
When an array ofMessage
objects is provided, a single request is made to Ably(RSL1d)
Indicates an error if the message was not successfully published to Ably(RSL1e)
Allowsname
and/ordata
to benull
. If any of the values arenull
, that property is not sent to Ably, e.g. a payload with anull
value fordata
would be sent as{"name":"click"}
(RSL1f)
Unidentified clients using Basic Auth (i.e. anyclientId
is permitted as noclientId
specified):***
(RSL1f1)
When aMessage
with aclientId
value is published, Ably will accept and publish that message with the providedclientId
. A test should assert via the history API that theclientId
of the publishedMessage
is populated(RSL1g)
Identified clients with aclientId
(as a result of either an explicitly configuredclientId
inClientOptions
, or implicitly through Token Auth):***
(RSL1g1)
When publishing aMessage
with theclientId
attribute set tonull
:(RSL1g1a)
It is unnecessary for the client to set theclientId
of theMessage
before publishing(RSL1g1b)
Ably will assign the identifiedclientId
upon receiving theMessage
. A test should assert via the history API that theclientId
value is the identifiedclientId
for theMessage
when received***
(RSL1g2)
When publishing aMessage
with theclientId
attribute value set to the identified client’sclientId
, Ably will accept the message and publish it. A test should assert that theclientId
value is populated for theMessage
when received***
(RSL1g3)
When publishing aMessage
with a differentclientId
attribute value to the identified client’sclientId
, 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 explicitclientId
that is incompatible with the identified client’sclientId
(either inferred or explicitly configured in the token orClientOptions
), the library will reject the message immediately and indicate an error(RSL1h)
Thepublish(name, data)
form should not take any additional arguments. If a client library has supported additional arguments to the(name, data)
form (e.g. separate arguments forclientId
andextras
, or a singleattributes
argument) in any 1.x version, it should continue to do so until version 2.0.(RSL1i)
If the total size of the message or (if publishing an array) messages, calculated per TO3l8, exceeds themaxMessageSize
, then the client library should reject the publish and indicate an error with code 40009(RSL1j)
WhenMessage
objects are provided, any validMessage
attribute (that is, an attribute specified in TM2) that is supplied by the caller must be included in the encoded message. (This does not mean it must be included unaltered; for example thedata
andencoding
will be subject to processing per RSL4)(RSL1k)
Idempotent publishing via REST is supported by populating theid
attribute ofMessage
instances passed topublish()
:***
(RSL1k1)
Idempotent publishing via library-generatedMessage
id
s is supported ifidempotentRestPublishing
(see TO3n) is enabled and one or moreMessage
instances are passed topublish()
and allMessage
s have an emptyid
attribute. The library generates a baseid
string by base64-encoding a sequence of at least 9 bytes obtained from a source of randomness. Each individualMessage
in the set of messages to be published is assigned a uniqueid
of the form <base id>:<serial> (whereserial
is the zero-based index into the set).***
(RSL1k2)
Idempotent publishing via client-suppliedMessage
id
s is supported where a singleMessage
is passed topublish()
and it contains a non-emptyid
. Theid
is preserved on sending the message.***
(RSL1k3)
If more than oneMessage
is passed topublish()
and one or more of those messages contains a non-emptyid
attribute, then all message ids (present or absent) are preserved on sending the batch of messages.***
(RSL1k4)
An explicit test for idempotency of publishes with library-generated ids shall exist that simulates an error response to a successful publish of a batch of messages, expects an automatic retry by the library, and verifies that the net outcome is that the batch is published only once.***
(RSL1k5)
An explicit test for idempotency of publishes with client-supplied ids shall exist that involves multiple explicit publish requests for a given message and verifies that the net outcome is that the message is published only once.(RSL1l)
Thepublish(Message)
andpublish(Message[])
forms of the method should take an extraDict<String, Stringifiable>
argument. These parameters should be encoded using normal querystring-encoding and sent as part of the query string of the REST publish. (Stringifiable
is defined inRTC1f
)***
(RSL1l1)
Publish params can be tested by publishing with a_forceNack=true
parameter, which will result in the publish being rejected with a40099
error code
(RSL2)
Channel#history
function:(RSL2a)
Returns aPaginatedResult
page containing the first page of messages in thePaginatedResult#items
attribute returned from the history request(RSL2b)
Supports the following params:***
(RSL2b1)
start
andend
are timestamp fields represented as milliseconds since epoch, or where suitable to the language, Time objects.start
must be equal to or less thanend
and is unaffected by the request direction***
(RSL2b2)
direction
backwards or forwards; if omitted the direction defaults to the REST API default (backwards)***
(RSL2b3)
limit
supports up to 1,000 items; if omitted the direction defaults to the REST API default (100)
(RSL3)
Channel#presence
attribute contains aPresence
object for this channel(RSL4)
Message encoding(RSL4a)
Payloads must be binary, strings, or objects capable of JSON representation, or can be empty (omitted). Any other data type should not be permitted and result in an error(RSL4b)
If a message is encoded, theencoding
attribute represents the encoding(s) applied in right to left format i.e. “utf-8/base64″ indicates that the original payload has “utf-8″ encoding and has subsequently been encoded in Base64 format(RSL4c)
When using MessagePack Message encoding***
(RSL4c1)
a binary Message payload is encoded as MessagePack binary type***
(RSL4c2)
a string Message payload is encoded as MessagePack string type***
(RSL4c3)
a JSON Message payload is stringified either as a JSON Object or Array and encoded as MessagePack string type and theencoding
attribute is set to “json”***
(RSL4c4)
All messages received will deliver payloads in the format they were sent in i.e. binary, string, or a structured type containing the parsed JSON(RSL4d)
When using JSON Message encoding***
(RSL4d1)
a binary Message payload is encoded as Base64 and represented as a JSON string theencoding
attribute is set to “base64″***
(RSL4d2)
a string Message payload is represented as a JSON string***
(RSL4d3)
a JSON Message payload is stringified either as a JSON Object or Array and represented as a JSON string and theencoding
attribute is set to “json”***
(RSL4d4)
All messages received will be decoded based on theencoding
field and deliver payloads in the format they were sent in i.e. binary, string, or a structured type containing the parsed JSON
(RSL5)
Message payload encryption(RSL5a)
When aChannel
is instantiated with a (non-null)cipher
channelOption, message payloads will be automatically encrypted when sent to Ably and decrypted when received on this channel, using thecipher
configuration(RSL5b)
AES 256 and 128 CBC encryption must be supported(RSL5c)
Tests must exist that encrypt and decrypt the following fixture data for AES 128 and AES 256 to ensure the client library encryption is compatible across libraries
(RSL6)
Message decoding(RSL6a)
All messages received will be decoded automatically based on theencoding
field and the payloads will be converted into the format they were originally sent using i.e. binary, string, or JSON***
(RSL6a1)
A set of tests must exist to ensure that the client library provides data encoding & decoding interoperability with other client libraries. The tests must use the set of predefined interoperability message fixtures to 1) publish a raw message to the REST API using the JSON transport and subscribe to the message using Realtime to ensure thedata
attribute matches the fixture; 2) publish a message using the REST client library and retrieve the raw message using the history REST API using the JSON transport ensuring thedata
matches the fixture; 3) perform the client library operation using bothJSON
andMsgPack
transports. For reference, see the Ruby and iOS implementations***
(RSL6a2)
A set of tests must exist to ensure that the client library provides interoperability for theextras
field which is a JSON-encodable object (ie a value that represents a JSONobject
value and supports serialization to and from JSON text). The test, at a minimum, should publish a message with anextras
object such as{<a href="[">push</a>{"title":"Testing"}]}
and ensure it is received with an equivalent JSON-encodable object(RSL6b)
If, for example, incompatible encryption details are provided or invalid Base64 is detected in the message payload, an error message will be sent to the logger, but the message will still be delivered with last successful decoding and theencoding
field. For example, if a message had a decoding of “utf-8/cipher+aes-128-cbc/base64″, and the payload was successfully Base64 decoded but the payload could not be encrypted because theCipherParam
details were not configured, the message would be delivered with a binary payload and anencoding
with the value “utf-8/cipher+aes-128-cbc”
Presence
(RSP1)
Presence object is associated with a single channel and is accessible throughChannel#presence
(RSP2)
There is no way to register a member as present on a channel via the REST API(RSP3)
Presence#get
function:(RSP3a)
Returns aPaginatedResult
page containing the first page of members present in thePaginatedResult#items
attribute returned from the presence request. Each member is represented as aPresenceMessage
. Supports the following params:***
(RSP3a1)
limit
supports up to 1,000 items; if unspecified it defaults to the REST API default (100)***
(RSP3a2)
clientId
filters members by the providedclientId
***
(RSP3a3)
connectionId
filters members by the providedconnectionId
(RSP4)
Presence#history
function:(RSP4a)
Returns aPaginatedResult
page containing the first page of messages in thePaginatedResult#items
attribute returned from the presence request(RSP4b)
Supports the following params:***
(RSP4b1)
start
andend
are timestamp fields represented as milliseconds since epoch, or where appropriate to the language, Date/Time objects.start
must be equal to or less thanend
and is unaffected by the request direction***
(RSP4b2)
direction
backwards or forwards; if unspecified defaults to the REST API default (backwards)***
(RSP4b3)
limit
supports up to 1,000 items; if unspecified defaults to the REST API default (100)
(RSP5)
Presence Messages retrieved are decoded in the same way that messages are decoded
Encryption
(RSE1)
Crypto::getDefaultParams
function:(RSE1a)
Returns a completeCipherParams
instance, using the default values for any field not supplied(RSE1b)
Takes a hashmap (or language equivalent) consisting of any subset ofCipherParams
fields that includes akey
(RSE1c)
Thekey
must be either a binary (e.g. a byte array, depending on the language), or a base64-encoded string. If the key is a string, the function should base64-decode it into a binary. Since the conversion to base64 is not under Ably control, this should be done leniently — in particular, it should work with base64url (RFC 4648 s.5, which uses-
and_
instead of+
and/
) as well as base64 (RFC 4648 s.4)(RSE1d)
Calculates akeyLength
from the key (its size in bits).(RSE1e)
Checks that the provided options are valid and self-consistent as best it can, raises an exception if not. At a minimum, this should include checking the calculatedkeyLength
is a valid key length for the encryption algorithm (for example, 128 or 256 forAES
)
(RSE2)
Crypto::generateRandomKey
function(RSE2a)
Takes an optionalkeyLength
parameter, which is the length in bits of the key to be generated. If unspecified, this is equal to the defaultkeyLength
of the default algorithm: forAES
, 256 bits.(RSE2b)
Returns (or calls back with, if the language cryptographic randomness primitives are blocking or async) the key as a binary (e.g. a byte array, depending on the language)
Forwards compatibility
(RSF1)
The library must apply the robustness principle in its processing of requests and responses with the Ably system. In particular, deserialization of Messages and related types, and associated enums, must be tolerant to unrecognised attributes or enum values. Such unrecognised values must be ignored.
Realtime client library features
The Ably Realtime client libraries establish and maintain a persistent connection to Ably and provide methods to publish and subscribe to messages over a low latency realtime connection.
The Realtime library is a super-set of the REST library and as such all Realtime libraries provide the functionality available in the REST library in addition to Realtime-specific features.
The threading and/or asynchronous model for each realtime library will vary by language and it is therefore up to the developer to decide on the best approach for each given client library. For example, Node.js and Ruby (EventMachine) use a similar callback single threaded evented approach that ensures all public methods are non-blocking. Java and .NET use a threaded model whereby the Connection
runs in its own thread. Go makes extensive use of goroutines and channels.
RealtimeClient
(RTC1)
Supports all the sameClientOptions
as theRestClient
in addition to:(RTC1a)
echoMessages
boolean is true by default. If false, it prevents messages originating from this connection being echoed back on the same connection(RTC1b)
autoConnect
boolean is true by default. If true, as soon as the client library is instantiated, it will connect to Ably. If false, the client library will wait for an explicitConnection#connect
to be called before connecting(RTC1c)
recover
string, when set, will attempt to recover the connection state of a previous connection(RTC1d)
realtimeHost
string, when set, will modify the realtime endpoint host used by this client library(RTC1e)
environment
string, when set, will modify both the REST and realtime endpoint hosts by prefixing the environment to the default endpoint host with a hyphen delimiter. For example, aRealtimeClient
with anenvironment
of “sandbox”, would use “sandbox-rest.ably.io” as therestHost
andsandbox-realtime.ably.io
as therealtimeHost
. See TO3k3 for constraints.(RTC1f)
transportParams
map or equivalent, additional parameters to be sent in the querystring when initiating a realtime connection. Keys areStrings
, values areStringifiable
(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 astrue
andfalse
. If this is unidiomatic to the language, the implementer may consider this as equivalent toString
).***
(RTC1f1)
If a key intransportParams
is one the library sends by default (for example,v
orheartbeats
), the value intransportParams
takes precedence.
(RTC2)
RealtimeClient#connection
attribute provides access to the underlyingConnection
object(RTC3)
RealtimeClient#channels
attribute provides access to the underlyingChannels
object(RTC4)
RealtimeClient#auth
attribute provides access to theAuth
object that was instantiated with theClientOptions
provided in theRealtimeClient
constructor(RTC5)
RealtimeClient#stats
function:(RTC5a)
Proxy toRestClient#stats
presented with an async or threaded interface as appropriate(RTC5b)
Accepts all the same params asRestClient#stats
and provides all the same functionality
(RTC6)
RealtimeClient#time
function:(RTC6a)
Proxy toRestClient#time
presented with an async or threaded interface as appropriate
(RTC7)
The client library must use the configured timeouts specified in theClientOptions
, falling back to the client library defaults and defaults described inClientOptions
below(RTC8)
For a realtime client,Auth#authorize
instructs the library to obtain a token using the providedtokenParams
andauthOptions
and upgrade the current connection to use that token; or if not currently connected, to connect with the token.(RTC8a)
If the connection is in theCONNECTED
state andauth#authorize
is called or Ably requests a re-authentication (see RTN22), the client must obtain a new token, then send anAUTH
ProtocolMessage
to Ably with anauth
attribute containing anAuthDetails
object with the token string***
(RTC8a1)
If the authentication token change is successful, then Ably will send a newCONNECTED
ProtocolMessage
. TheconnectionDetails
provided in theCONNECTED
ProtocolMessage
must override any existing defaults, see RTN21. TheConnection
should emit anUPDATE
event perRTN24
. A test should exist that performs an upgrade of capabilities without any loss of continuity or connectivity during the upgrade process. Another test should exist where the capabilities are downgraded resulting in Ably sending anERROR
ProtocolMessage
with achannel
property, causing the channel to enter theFAILED
state. That test must assert that the channel becomes failed soon after the token update and the reason is included in the channel state change event***
(RTC8a2)
If the authentication token change fails, then Ably will send anERROR
ProtocolMessage
triggering the connection to transition to theFAILED
state. A test should exist for a token change that fails (such as sending a new token with an incompatibleclientId
)***
(RTC8a3)
Theauthorize
call should be indicated as completed with the new token or error only once realtime has responded to theAUTH
with either aCONNECTED
orERROR
respectively.***
(RTC8a4)
Tests must exist that verify the inband reauthorization mechanism described inRTC8a
succeeds in the cases of an Ably token string and a JWT token string.(RTC8b)
If the connection is in theCONNECTING
state whenauth#authorize
is called, all current connection attempts should be halted, and after obtaining a new token the library should immediately initiate a connection attempt using the new token***
(RTC8b1)
Theauthorize
call should be indicated as completed with the new token once the connection has moved to theCONNECTED
state, or with an error if the connection instead moves to theFAILED
,SUSPENDED
, orCLOSED
states(RTC8c)
If the connection is in theDISCONNECTED
,SUSPENDED
,FAILED
, orCLOSED
state whenauth#authorize
is called, after obtaining a token the library should move to theCONNECTING
state and initiate a connection attempt using the new token, andRTC8b1
applies.
(RTC9)
RealtimeClient#request
is a wrapper aroundRestClient#request
(see 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 publicEventEmitter
interfaces (such asConnection#on
) or message/event subscription interfaces (such asChannel#subscribe
) in such a way that a user of the library callingConnection#off()
orChannel#unsubscribe()
to remove all listeners would result in the library not working as expected(RTC11)
Unexpected internal exceptions, as defined in 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.
Connection
(RTN1)
Connection
connects to the Ably service using a websocket connection. The ably-js library supports additional transports such as Comet and XHR streaming; however non-browser client libraries typically use only a websocket transport(RTN2)
The default host used for realtime websocket connections isrealtime.ably.io
, and the following query string params should be used when opening a new connection:(RTN2a)
format
should bemsgpack
(default) orjson
(RTN2b)
echo
should betrue
by default;false
will prevent messages published by the client being echoed back(RTN2d)
clientId
contains the providedclientId
option ofClientOptions
, unlessclientId
isnull
(RTN2e)
Depending on the authentication scheme, eitheraccessToken
contains the token string, orkey
contains the API key(RTN2f)
API version paramv
should be the API version per G4(RTN2g)
Library and version paramlib
should include the header value described in RSC7b. For example, the 1.0.0 version of the JavaScript library would use the paramlib=js-1.0.0
(RTN3)
If connection optionautoConnect
is true, a connection is initiated immediately; otherwise a connection is only initiated following an explicit call toconnect()
(RTN4)
TheConnection
implementsEventEmitter
and emitsConnectionEvent
events, where aConnectionEvent
is either aConnectionState
orUPDATE
, and aConnectionState
is eitherINITIALIZED
,CONNECTING
,CONNECTED
,DISCONNECTED
,SUSPENDED
,CLOSING
,CLOSED
, orFAILED
(RTN4a)
It emits aConnectionState
ConnectionEvent
for every connection state change(RTN4h)
It emits anUPDATE
ConnectionEvent
for changes to connection conditions for which theConnectionState
(e.g.CONNECTED
) does not change. (The library must never emit aConnectionState
ConnectionEvent
for a state equal to the previous state)(RTN4b)
A new connection will emit the following events in order when connecting:CONNECTING
, thenCONNECTED
(RTN4c)
A connection will emit the following events when closing the connection:CLOSING
, thenCLOSED
(RTN4d)
Connection#state
attribute is the current state of the connection(RTN4e)
AConnectionStateChange
object is emitted as the first argument for everyConnectionEvent
(including bothRTN4a
connection state changes andRTL4h
UPDATE
events)(RTN4f)
TheConnectionStateChange
object may contain areason
consisting of anErrorInfo
object with details of the error that has occurred for theConnection
. Any state change triggered by aProtocolMessage
that contains anerror
member should populate thereason
with that error in the corresponding state change event(RTN4i)
Optionally, for backwards compatibility with 0.8 libraries, theConnection
EventEmitter
can provide an overloaded method that supportson(ConnectionState)
, but must issue a deprecation warning
(RTN5)
A test should exist that instances many (50+) clients simultaneously and performs a few basic operations such as attaching to a channel, publishing a message, and expecting all of those messages to arrive on all clients to ensure that there are no concurrency issues with the client library(RTN6)
AConnection
is successful and consideredCONNECTED
once the websocket connection is open and the initialCONNECTED
ProtocolMessage
has been received(RTN21)
If theCONNECTED
ProtocolMessage
contains aconnectionDetails
property, the attributes withinConnectionDetails
will be used as the defaults for this client library, overriding any configured options at the time theCONNECTED
ProtocolMessage
is received(RTN7)
ACK
andNACK
:(RTN7a)
AllProtocolMessage
Presence
andMessage
objects sent to Ably expect either anACK
orNACK
from Ably to confirm successful receipt and acceptance or failure respectively. For clarity, it is unnecessary to fail the publish operation of a message using a timer. Instead the client library can rely on: the realtime system will send anACK
orNACK
when connected; the client library will fail all awaiting messages onceSUSPENDED
(see RTN7c); upon reconnecting, the client will resend all message awaiting a response, and the realtime system in turn will respond with anACK
orNACK
(see RTN19a)(RTN7b)
EveryProtocolMessage
sent must contain a unique serially incrementingmsgSerial
integer value starting at zero. ThemsgSerial
along with thecount
for incomingACK
andNACK
ProtocolMessages
indicates which messages succeeded or failed to be delivered(RTN7c)
If a connection enters theSUSPENDED
,CLOSED
orFAILED
state, or if the connection state is lost, and anACK
orNACK
has not yet been received for a message, the client should consider the delivery of those messages as failed
(RTN22)
Ably can request that a connected client re-authenticates by sending the client anAUTH
ProtocolMessage
. The client must then immediately start a new authentication process as described in RTC8(RTN22a)
Ably reserves the right to forcibly disconnect a client that does not re-authenticate within an acceptable period of time, or at any time the token is deemed no longer valid. A client is forcibly disconnected following aDISCONNECTED
message containing an error code in the range40140 <= code < 40150
. This will in effect force the client to re-authenticate and resume the connection immediately, see RTN15h
(RTN8)
Connection#id
attribute:(RTN8a)
Is unset until connected(RTN8b)
Is a unique string provided by Ably. You should have a test to ensure multiple connected clients have unique connection IDs
(RTN9)
Connection#key
attribute:(RTN9a)
Is unset until connected(RTN9b)
Is a unique private connection key provided by Ably that is used to reconnect and retain connection state following an unexpected disconnection. You should have a test to ensure multiple connected clients have unique connection keys
(RTN10)
Connection#serial
attribute:(RTN10a)
Is unset until connected, and is then set from theconnectionSerial
attribute of theCONNECTED
ProtocolMessage
(RTN10b)
Continues to be updated by everyProtocolMessage
received from Ably that contains aconnectionSerial
. A test should exist that checks that the serial is updated when aProtocolMessage
is received with the value from thatProtocolMessage
. It should not otherwise change; in particular, unlike the library-internalmsgSerial
, it should not change as a result of publishing a message or receiving anACK
.
(RTN11)
Connection#connect
function:(RTN11a)
Explicitly connects to the Ably service if not already connected(RTN11b)
If the state isCLOSING
, the client should make a new connection with a new transport instance and remove all references to the old one. In particular, it should make sure that, when theCLOSED
ProtocolMessage
arrives for the old connection, it doesn’t affect the new one.(RTN11c)
If the state isDISCONNECTED
orSUSPENDED
, aborts the retry process described in RTN14d and RTN14e and immediately tries to reconnect.(RTN11d)
If the state isCLOSED
orFAILED
, transitions all the channels toINITIALIZED
and unsets theirChannel.errorReason
, unsets theConnection.errorReason
, clears all connection state (including in particularConnection.recoveryKey
), and resets themsgSerial
to0
(RTN12)
Connection#close
function:(RTN12f)
If the connection state isCONNECTING
, moves immediately toCLOSING
. If the connection attempt succeeds, ie. aCONNECTED
ProtocolMessage
arrives from Ably, then do as specified in RTN12a. If it doesn’t succeed, move toCLOSED
.(RTN12a)
If the connection state isCONNECTED
, sends aCLOSE
ProtocolMessage
to the server, transitions the state toCLOSING
and waits for aCLOSED
ProtocolMessage
to be received(RTN12b)
If theCLOSED
ProtocolMessage
is not received within the default realtime request timeout, the transport will be disconnected and the connection will automatically transition to theCLOSED
state(RTN12c)
If the transport is abruptly closed following aCLOSE
ProtocolMessage
being sent, then the connection will automatically transition to theCLOSED
state(RTN12d)
If the connection state isDISCONNECTED
orSUSPENDED
, aborts the retry process described in RTN14d and RTN14e and transitions the connection immediately to theCLOSED
state
(RTN13)
Connection#ping
function:(RTN13d)
If the connection state isCONNECTING
orDISCONNECTED
, do the operation once the connection state isCONNECTED
(RTN13a)
Will send aProtocolMessage
with actionHEARTBEAT
the Ably service when connected and expects aHEARTBEAT
message in response. If the client library language supports callbacks, then the callback will be called with the response time or error(RTN13b)
Will indicate an error if in, or has transitioned to, theINITIALIZED
,SUSPENDED
,CLOSING
,CLOSED
orFAILED
state(RTN13c)
Will fail if aHEARTBEAT
ProtocolMessage
is not received within the default realtime request timeout(RTN13e)
TheProtocolMessage
sent should include anid
property, with value a random string. If so, only aHEARTBEAT
response which includes anid
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 theFAILED
state and theConnection#errorReason
will be set on theConnection
object as well as the emittedConnectionStateChange
(RTN14b)
If a connection request fails due to anERROR
ProtocolMessage
being received by the client containing a token error (statusCode
value of 401 and errorcode
value in the range40140 <= code < 40150
) and an emptychannel
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 theDISCONNECTED
state, and theConnection#errorReason
should be set. (If no means to renew the token is provided,RSA4a
applies)(RTN14g)
If anERROR
ProtocolMessage
with an emptychannel
attribute is received for any reason other than RTN14b, then the connection will transition to theFAILED
state and the server will terminate the connection. Additionally theConnection#errorReason
must be set with the error from theERROR
ProtocolMessage
(RTN14c)
A new connection attempt will fail if not connected within the default realtime request timeout(RTN14d)
If a connection attempt fails for any recoverable reason (i.e. a network failure, a timeout such as RTN14c, or a disconnected response, other than a token failure RTN14b), theConnection#state
will transition toDISCONNECTED
, theConnection#errorReason
will be updated, aConnectionStateChange
with thereason
will be emitted, and new connection attempts will periodically be made until the maximum time in that state threshold is reached. TheretryIn
attribute of theConnectionStateChange
object will contain the time in milliseconds until the next connection attempt. See thedisconnectedRetryTimeout
ofClientOptions
below. Each time a new connection attempt is made the state will transition toCONNECTING
and then toCONNECTED
if successful, orDISCONNECTED
if unsuccessful and the defaultconnectionStateTtl
has not been exceeded. Fallback hosts are used for new connection attempts in accordance with RTN17.(RTN14e)
Once the connection state has been in theDISCONNECTED
state for more than the defaultconnectionStateTtl
, the state will change toSUSPENDED
and be emitted with thereason
, and theConnection#errorReason
will be updated. In this state, a new connection attempt will be made periodically as specified withinsuspendedRetryTimeout
ofClientOptions
(RTN14f)
The connection will remain in theSUSPENDED
state indefinitely, whilst periodically attempting to reestablish a connection
(RTN15)
Connection
failures onceCONNECTED
:(RTN15h)
If aDISCONNECTED
message is received from Ably, then that transport will subsequently be closed by Ably***
(RTN15h1)
If theDISCONNECTED
message contains a token error (statusCode
value of 401 and errorcode
value in the range40140 <= code < 40150
) and the library does not have a means to renew the token, the connection will transition to theFAILED
state and theConnection#errorReason
will be set***
(RTN15h2)
If theDISCONNECTED
message contains a token error (statusCode
value of 401 and errorcode
value in the range40140 <= code < 40150
) and the library has the means to renew the token, a single attempt to create a new token should be made and a new connection attempt initiated using the new token. If the token creation fails or the next connection attempt fails due to a token error, the connection will transition to theDISCONNECTED
state and theConnection#errorReason
will be set.(RTN15i)
If anERROR
ProtocolMessage
is received, this indicates a fatal error in the connection. The server will close the transport immediately after. The client should transition to theFAILED
state triggering all attached channels to transition to theFAILED
state as well. Additionally theConnection#errorReason
should be set with the error received from Ably(RTN15a)
If aConnection
transport is disconnected unexpectedly or because a token has expired, then theConnection
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 queuedProtocolMessages
from Ably.(RTN15g)
Connection state is only maintained server-side for a brief period, given by theconnectionStateTtl
in theconnectionDetails
, see CD2f. If a client has been disconnected for longer than theconnectionStateTtl
, 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 toSUSPENDED
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 theCONNECTED
state when it was put to sleep, it has only moved out of that state very recently (after waking up and noticing it’s no longer connected)***
(RTN15g2)
Another consequence of that is that the measure of whether the client been disconnected for too long (for the purpose of this check) cannot just be whether the client left theCONNECTED
state more thanconnectionStateTtl
ago. Instead, it should be whether the difference between the current time and the last activity time is greater than the sum of theconnectionStateTtl
and themaxIdleInterval
, where the last activity time is the time of the last known actual sign of activity from Ably per RTN23a***
(RTN15g3)
When a connection attempt succeeds after the connection state has been cleared in this way, channels that were previouslyATTACHED
,ATTACHING
, orSUSPENDED
must be automatically reattached, just as if the connection was a resume attempt which failed per RTN15c3(RTN15b)
In order for a connection to be resumed and connection state to be recovered, the client must have received aCONNECTED
ProtocolMessage which will include a private connection key. To resume that connection, the library reconnects to the websocket endpoint with two additional querystring params:***
(RTN15b1)
resume
is theProtocolMessage#connectionKey
from the most recentCONNECTED
ProtocolMessage
received***
(RTN15b2)
connectionSerial
is the most recentProtocolMessage#connectionSerial
(from any message, not just aCONNECTED
) received from Ably (equivalently, theConnection#serial
)(RTN15c)
The system’s response to a resume request will be one of the following:(RTN15c1)
CONNECTED
ProtocolMessage
with the sameconnectionId
as the current client, and noerror
. 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 sameconnectionId
as the current client, and anerror
. 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. TheErrorInfo
received should be set as thereason
in theCONNECTED
event, and theConnection#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 anATTACHED
ProtocolMessage
with anerror
, see RTL12(RTN15c3)
CONNECTED
ProtocolMessage
with a newconnectionId
, and an error inerror
. 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. TheErrorInfo
should be set as thereason
in theCONNECTED
event, and theConnection#errorReason
should be set. The client library should initiate an attach for channels that are in theSUSPENDED
state. For all channels in theATTACHING
orATTACHED
state, the client library should fail any previously queued messages for that channel and initiate a new attach i.e. a newATTACH
ProtocolMessage
must be sent for each channel. Finally, the internalmsgSerial
counter is reset so that the first message published to Ably will contain amsgSerial
value of0
(RTN15c5)
ERROR
ProtocolMessage
indicating a failure to authenticate as a result of a token error (see RTN15h). The transport will be closed by the server. The spec described in RTN15h must be followed for a connection being resumed with a token error(RTN15c4)
Any otherERROR
ProtocolMessage
indicating a fatal error in the connection. The server will close the transport immediately after. The client should transition to theFAILED
state triggering all attached channels to transition to theFAILED
state as well. Additionally theConnection#errorReason
will be set should be set with the error received from Ably(RTN15f)
ACK
andNACK
responses for published messages can only ever be received on the transport connection on which those messages were sent. Therefore, once a transport drops, the client library must either fail the publish attempt, or re-attempt by re-sending the messages on a new transport if the resume was successful (i.e. theCONNECTED
response includes the expectedconnectionId
)(RTN15d)
Client libraries should have test coverage to ensure connection state recovery is working as expected by forcibly disconnecting a client and checking that messages published on channels are delivered once the connection is resumed(RTN15e)
When a connection is resumed, theConnection#key
may change and will be provided in the firstCONNECTED
ProtocolMessage#connectionDetails
when the connection is established. The client library must update theConnection#key
value with the newconnectionKey
value every time
(RTN20)
When the client library can subscribe to the Operating System events for network/internet connectivity changes:(RTN20a)
WhenCONNECTED
,CONNECTING
orDISCONNECTING
, if the operating system indicates that the underlying internet connection is no longer available, then the client library should immediately transition the state toDISCONNECTED
with emit a state change with an appropriatereason
. This state change will automatically trigger the client library to attempt to reconnect, seeRTN15
above(RTN20b)
WhenDISCONNECTED
orSUSPENDED
, 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 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 ofrecover
key being explicitly provided to the Realtime library when instantiated. Once a connection is recovered, all channels must be explicitly attached by the developer(RTN16b)
Connection#recoveryKey
is an attribute composed of theconnectionKey
, and the latestconnectionSerial
received on the connection, and the currentmsgSerial
(RTN16c)
Connection#recoveryKey
becomesNull
when a connection is explicitlyCLOSED
orCLOSED
by the server, as connection state is not retained for connections closed intentionally. TheConnection#key
andConnection#id
is set toNull
(RTN16d)
When a connection is successfully recovered, theConnection#id
will be identical to theid
of the connection that was recovered, andConnection#key
will always be updated to theConnectionDetails#connectionKey
provided in the firstCONNECTED
ProtocolMessage
(RTN16e)
If therecover
option is missing or no longer valid when connecting to Ably, the client will connect anyway, but emit aConnectionStateChange
with areason
, and will additionally set theConnection#errorReason
with anErrorInfo
object describing the failure(RTN16f)
ThemsgSerial
component of therecoveryKey
, unlike the other two components, is not sent to Ably, but rather is used to set the library internalmsgSerial
. (If the recover fails, the counter should be reset to 0 per RTN15c3 )
(RTN17)
Host Fallback(RTN17b)
The fallback behavior described below only applies when the defaultrealtime.ably.io
endpoint is being used and has not been overriden (see RTC1d and RTC1e),ClientOptions#fallbackHostsUseDefault
istrue
, or an array ofClientOptions#fallbackHosts
is provided.(RTN17a)
By default, every connection attempt is first attempted to the default primary hostrealtime.ably.io
(unless overriden inClientOptions#realtimeHost
), which, through DNS, is automatically routed to the client’s closest datacenter. The client library must always prefer the default endpoint (closest datacenter), even if a previous connection attempt to that endpoint has failed. (That is,RSC15f
does not apply)(RTN17c)
In the case of an error necessitating use of an alternative host (see RTN17d), theConnection
manager should first check if an internet connection is available by issuing aGET
request tohttps://internet-up.ably-realtime.com/is-the-internet-up.txt
. If the request succeeds and the text “yes” is included in the body, then the client library can assume it has a viable internet connection and should then immediately retry the connection against all fallback hosts to find an alternative healthy datacenter. The five default fallback hosts are[a-e].ably-realtime.com
and should be attempted in random order. The connection requests must include a matching Host header as this is necessary when fallbacks are proxied through a CDN. See RSC15a 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 aDISCONNECTED
response with anerror.statusCode
in the range500 <= code <= 504
or HTTP response status code in the range500 <= code <= 504
. Attempting to reconnect to a fallback host for other failure conditions will not fix the problem and will simply increase the load on other data-centers unnecessarily(RTN17e)
If the realtime client is connected to a fallback host endpoint, then for the duration that the transport is connected to that host, all HTTP requests, such as history or token requests, should be first attempted to the same datacenter the realtime connection is established with i.e. the same fallback host must be used as the default HTTP request host. If however the HTTP request against that fallback host fails, then the normal fallback host behavior should be followed attempting the request against another fallback host as described in RSC15
(RTN19)
Transport state side effects – when a transport is upgraded or disconnected for any reason:(RTN19a)
AnyProtocolMessage
that is awaiting anACK
/NACK
on the old transport will not receive theACK
/NACK
on the new transport. The client library must therefore resend anyProtocolMessage
that is awaiting aACK
/NACK
to Ably in order to receive the expectedACK
/NACK
for that message. The Ably service is responsible for keeping track of messages, ignoring duplicates and responding with suitableACK
/NACK
messages(RTN19b)
If there are any pending channels i.e. in theATTACHING
orDETACHING
state, the respectiveATTACH
orDETACH
message should be resent to Ably
(RTN23)
Heartbeats(RTN23a)
If a transport does not receive any indication of activity on a transport for a period greater than the sum of themaxIdleInterval
(which will be sent in theconnectionDetails
of the most recentCONNECTED
message received on that transport) and therealtimeRequestTimeout
, 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, seeRTN23b
) 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 aheartbeats
param in the querystring, with valuetrue
orfalse
. If the value is true, the server will use Ably protocol messages (for example, a message with aHEARTBEAT
action) to satisfy themaxIdleInterval
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 sendheartbeats=false
. If not, it should sendheartbeats=true
.
(RTN24)
A connected client may receive aCONNECTED
ProtocolMessage
from Ably at any point (though is typically triggered by a reauth, seeRTC8a
). TheconnectionDetails
in theProtocolMessage
must override any stored details, seeRTN21
. TheConnection
should emit anUPDATE
event with aConnectionStateChange
object, which should have bothprevious
andcurrent
attributes set toCONNECTED
, and thereason
attribute set to to theerror
member of theCONNECTED
ProtocolMessage
(if any). (Note thatUPDATE
should be the only event emitted: in particular, the library must not emit anCONNECTED
event if the client was already connected, seeRTN4h
).
Channels
(RTS1)
Channels
is a collection ofChannel
objects accessible throughRealtime#channels
(RTS2)
Methods should exist to check if a channel exists or iterate through the existing channels(RTS3)
Channels#get
function:(RTS3a)
Creates a newChannel
object for the specified channel if none exists, or returns the existing channel.ChannelOptions
can be specified when instancing a newChannel
(RTS3b)
If options are provided, the options are set on theChannel
when creating a newChannel
(RTS3c)
Accessing an existingChannel
with options in the formChannels#get(channel, options)
will update the options on the channel and then return the existingChannel
object
(RTS4)
Channels#release
function:(RTS4a)
Detaches the channel and then releases the channel resource i.e. it’s deleted and can then be garbage collected
Channel
(RTL1)
As soon as aChannel
becomes attached, all incoming messages and presence messages (where ‘incoming’ is defined as ‘received from Ably over the realtime transport’) are processed and emitted where applicable.PRESENCE
andSYNC
messages are passed to thePresence
object ensuring it maintains a map of current members on a channel in realtime(RTL2)
TheChannel
implementsEventEmitter
and emitsChannelEvent
events, where aChannelEvent
is either aChannelState
orUPDATE
, and aChannelState
is eitherINITIALIZED
,ATTACHING
,ATTACHED
,DETACHING
,DETACHED
,SUSPENDED
andFAILED
(RTL2a)
It emits aChannelState
ChannelEvent
for every channel state change(RTL2g)
It emits anUPDATE
ChannelEvent
for changes to channel conditions for which theChannelState
(e.g.ATTACHED
) does not change. (The library must never emit aChannelState
ChannelEvent
for a state equal to the previous state)(RTL2b)
Channel#state
attribute is the current state of the channel(RTL2d)
AChannelStateChange
object is emitted as the first argument for everyChannelEvent
(including bothRTL2a
state changes andRTL2g
UPDATE
events). It may optionally contain areason
consisting of anErrorInfo
object; any state change triggered by aProtocolMessage
that contains anerror
member should populate thereason
with that error in the corresponding state change event(RTL2f)
When a channelATTACHED
ProtocolMessage
is received, theProtocolMessage
may contain aRESUMED
bit flag indicating that the channel has been resumed. The correspondingChannelStateChange
(eitherATTACHED
perRTL2a
, orUPDATE
perRTL12
) will contain aresumed
boolean attribute with valuetrue
if the bit flagRESUMED
was included. Whenresumed
istrue
, 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 thatresumed
is always false when a channel first becomesATTACHED
, it istrue
when the channel isATTACHED
following a successful connection recovery, and isfalse
when the channel isATTACHED
following a failed connection recovery(RTL2h)
Optionally, for backwards compatibility with 0.8 libraries, theChannel
EventEmitter
can provide an overloaded method that supportson(ChannelState)
, but must issue a deprecation warning
(RTL3)
Connection state change side effects:(RTL3e)
If the connection state enters theDISCONNECTED
state, it will have no effect on the channel states.(RTL3a)
If the connection state enters theFAILED
state, then anATTACHING
orATTACHED
channel state will transition toFAILED
and set theChannel#errorReason
(RTL3b)
If the connection state enters theCLOSED
state, then anATTACHING
orATTACHED
channel state will transition toDETACHED
(RTL3c)
If the connection state enters theSUSPENDED
state, then anATTACHING
orATTACHED
channel state will transition toSUSPENDED
(RTL3d)
If the connection state enters theCONNECTED
state, then aSUSPENDED
channel will initiate an attach operation and transition toATTACHING
. If the attach operation times out, the channel should return to theSUSPENDED
state (see RTL4f)
(RTL11)
If a channel enters theDETACHED
,SUSPENDED
orFAILED
state, then all presence actions that are still queued for send on that channel per RTP16b should be deleted from the queue, and any callback passed to the corresponding presence method invocation should be called with anErrorInfo
indicating the failure(RTL11a)
For clarity, any messages awaiting anACK
orNACK
are unaffected by channel state changes i.e. a channel that becomes detached following an explicit request to detach may still receive anACK
orNACK
for messages published on that channel later
(RTL4)
Channel#attach
function:(RTL4a)
If alreadyATTACHED
nothing is done(RTL4h)
If the channel is in a pending stateDETACHING
orATTACHING
, do the attach operation after the completion of the pending request(RTL4g)
If the channel is in theFAILED
state, theattach
request sets itserrorReason
tonull
, and proceeds with a channel attach described in RTL4b, RTL4i and RTL4c(RTL4b)
If the connection state isCLOSED
,CLOSING
,SUSPENDED
orFAILED
, theattach
request results in an error(RTL4i)
If the connection state isINITIALIZED
,CONNECTING
orDISCONNECTED
, do the operation once the connection state isCONNECTED
(RTL4c)
Otherwise anATTACH
ProtocolMessage is sent to the server, the state transitions toATTACHING
and the channel becomesATTACHED
when the confirmationATTACHED
ProtocolMessage is received(RTL4f)
Once anATTACH
ProtocolMessage
is sent, if anATTACHED
ProtocolMessage
is not received within the default realtime request timeout, the attach request should be treated as though it has failed and the channel should transition to theSUSPENDED
state. The channel will then be subsequently automatically re-attached as described in RTL13(RTL4d)
If the language permits, a callback can be provided that is called when the channel is attached successfully or the attach fails and theErrorInfo
error is passed as an argument to the callback(RTL4e)
If the user does not have sufficient permissions to attach to the channel, the channel will transition toFAILED
and set theChannel#errorReason
(RTL5)
Channel#detach
function:(RTL5a)
If the channel state isINITIALIZED
orDETACHED
nothing is done(RTL5i)
If the channel is in a pending stateDETACHING
orATTACHING
, do the detach operation after the completion of the pending request(RTL5b)
If the channel state isFAILED
, thedetach
request results in an error(RTL5j)
If the channel state isSUSPENDED
, thedetach
request transitions the channel immediately to theDETACHED
state(RTL5g)
If the connection state isCLOSING
orFAILED
, thedetach
request results in an error(RTL5h)
If the connection state isINITIALIZED
,CONNECTING
orDISCONNECTED
, do the detach operation once the connection state isCONNECTED
(RTL5d)
Otherwise aDETACH
ProtocolMessage is sent to the server, the state transitions toDETACHING
and the channel becomesDETACHED
when the confirmationDETACHED
ProtocolMessage is received(RTL5f)
Once aDETACH
ProtocolMessage
is sent, if aDETACHED
ProtocolMessage
is not received within the default realtime request timeout, 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 theErrorInfo
error is passed as an argument to the callback
(RTL6)
Channel#publish
function:(RTL6a)
Messages are encoded in the same way as the RESTChannel#publish
method, and RSL1g (size limit) applies similarly***
(RTL6a1)
RSL1k (idempotentRestPublishing
option), RSL1j1 (idempotent publishing test), and RSL1l (publish(Message, params)
form) do not apply to realtime publishes(RTL6b)
An optional callback can be provided to the#publish
method that is called when the message is successfully delivered or upon failure with the appropriateErrorInfo
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 aMessage
object, an array ofMessage
objects, or aname
string anddata
payload:***
(RTL6i1)
Whenname
anddata
(or aMessage
) is provided, a singleProtocolMessage
containing oneMessage
is published to Ably***
(RTL6i2)
When an array ofMessage
objects is provided, a singleProtocolMessage
is used to publish allMessage
objects in the array.***
(RTL6i3)
Allowsname
and ordata
to benull
. If any of the values arenull
, then key is not sent to Ably i.e. a payload with anull
value fordata
would be sent as follows{ "name": "click" }
(RTL6c)
Connection and channel state conditions:***
(RTL6c1)
If the connection isCONNECTED
and the channel isINITIALIZED
,ATTACHED
,DETACHED
,ATTACHING
, orDETACHING
then the messages are published immediately***
(RTL6c2)
If the connection isINITIALIZED
,CONNECTING
orDISCONNECTED
, andClientOptions#queueMessages
has not been explicitly set to false, then the message will be queued and delivered as soon as the connection isCONNECTED
and the channel is in a state in which publishing is permitted perRTL6c1
***
(RTL6c4)
If the connection isSUSPENDED
,CLOSING
,CLOSED
, orFAILED
, or the channel isSUSPENDED
orFAILED
, the operation will result in an error***
(RTL6c5)
A publish should not trigger an implicit attach (in contrast to earlier version of this spec)(RTL6d)
The protocol permitsMessage
s that have been queued to be sent in a singleProtocolMessage
, by bundling them into theProtocolMessage#messages
orProtocolMessage#presence
array. In general, the client library SHOULD NOT do this. If it does, it MUST conform to all of the following constraints:***
(RTL6d1)
The resultingProtocolMessage
must not exceed themaxMessageSize
***
(RTL6d2)
Messages can only be bundled together if they have the sameclientId
value (or both have noclientId
set). (Note that this constraint only applies to what the client library can autonomously do as part of queuing messages, not to what the user can do by publishing an array ofMessages
. It exists because if anyMessage
in aProtocolMessage
has an invalidclientId
, the entireProtocolMessage
is rejected. This is fine if the user has deliberately published theMessages
together – they requested atomicity – but not if the client library has bundled them without the user’s knowledge)***
(RTL6d3)
Messages can only be bundled together if they are for the samechannel
***
(RTL6d4)
Messages can only be bundled together if they are of the same type (that is,Message
versusPresenceMessage
)***
(RTL6d5)
Only contiguous messages in the queue can be bundled together. For example, if the user publishes three messages, A, B, and C, of which A and C could be bundled together underRTL6d1-4
but B could not, then no bundling should occur***
(RTL6d6)
The order of messages in the resultingProtocolMessage
Messages must match the publish order. For example, if the user publishesMessage
D, then theMessage
array [E, F], thenMessage
G, the finalmessages
array should be [D, E, F, G]***
(RTL6d7)
Messages must not be bundled if any have had had theirMessage.id
property set(RTL6e)
Unidentified clients using Basic Auth (i.e. anyclientId
is permitted as noclientId
specified):***
(RTL6e1)
When aMessage
with aclientId
value is published, Ably will accept and publish that message with the providedclientId
. A test should assert that theclientId
of the publishedMessage
is populated(RTL6g)
Identified clients with aclientId
(as a result of either an explicitly configuredclientId
inClientOptions
, or implicitly through Token Auth):***
(RTL6g1)
When publishing aMessage
with theclientId
attribute set tonull
:(RTL6g1a)
It is unnecessary for the client to set theclientId
of theMessage
before publishing(RTL6g1b)
Ably will assign aclientId
upon receiving theMessage
. A test should assert that theclientId
value is populated for theMessage
when received***
(RTL6g2)
When publishing aMessage
with theclientId
attribute value set to the identified client’sclientId
, Ably will accept the message and publish it. A test should assert that theclientId
value is populated for theMessage
when received***
(RTL6g3)
When publishing aMessage
with a differentclientId
attribute value from the identified client’sclientId
, the client library should reject that publish operation immediately. The message should not be sent to Ably and it should result in an error, typically in the form of an error callback. The connection and channel must remain available for further operations***
(RTL6g4)
When using Token Auth, unless aclientId
has been provided inClientOptions
or inferred following authentication, the client library is unidentified and will not be constrained when publishing messages with any explicitclientId
. If aMessage
with aclientId
value is published before theclientId
is configured or inferred following authentication, the client library should not reject any explicitclientId
specified in a message. A test should instance a library without an explicitclientId
and anauthCallback
that returns atokenDetails
object with aclientId
, then publish a message with the sameclientId
before authentication, and ensure that the message is published following authentication and received back with theclientId
intact. A further test should follow the same sequence of events, but should instead use an incompatibleclientId
in the message, expecting that the message is rejected by the Ably service and the message error should contain the server error message, and the connection and channel should remain available for further operations(RTL6h)
Thepublish(name, data)
form should not take any arguments other than those two. If a client library has supported additional arguments to the(name, data)
form (e.g. separate arguments forclientId
andextras
, or a singleattributes
argument) in any 1.x version, it should continue to do so until version 2.0.(RTL6f)
Message#connectionId
should match the currentConnection#id
for all published messages, a test should exist to ensure theconnectionId
for received messages matches that of the publisher
(RTL7)
Channel#subscribe
function:(RTL7a)
Subscribe with no arguments subscribes a listener to all messages(RTL7b)
Subscribe with a single name argument subscribes a listener to only messages whosename
member matches the string name(RTL7c)
Implicitly attaches theChannel
if the channel is in theINITIALIZED
state. The optional callback, if provided, is called according toRTL4d
based on the implicit attach operation. The listener will always be registered regardless of the implicit attach result(RTL7d)
Messages delivered are automatically decoded based on theencoding
attribute; see RESTChannel
encoding features. Tests should exist to publish and subscribe to encoded messages using the AES 128 and AES 256 fixture test data(RTL7e)
If a message cannot be decoded or decrypted successfully, it should be delivered to the listener with theencoding
attribute set indicating the residual encoding state, and an error should be logged(RTL7f)
A test should exist ensuring published messages are not echoed back to the subscriber whenechoMessages
is set to false in theRealtime
library constructor
(RTL8)
Channel#unsubscribe
function:(RTL8a)
Unsubscribe with no arguments unsubscribes the provided listener to all messages if subscribed(RTL8b)
Unsubscribe with a single name argument unsubscribes the provided listener if previously subscribed with a name-specific subscription
(RTL9)
Channel#presence
attribute:(RTL9a)
Returns thePresence
object for this channel
(RTL10)
Channel#history
function:(RTL10a)
Supports all the same params as RESTChannel#history
(RTL10b)
Additionally supports the paramuntilAttach
, which if true, will only retrieve messages prior to the moment that the channel was attached or emitted anUPDATE
indicating loss of continuity. This bound is specified by passing the querystring paramfromSerial
with theChannel#properties.attachSerial
assigned to the channel in theATTACHED
ProtocolMessage
(see RTL15a). If theuntilAttach
param is specified when the channel is not attached, it results in an error(RTL10c)
Returns aPaginatedResult
page containing the first page of messages in thePaginatedResult#items
attribute returned from the history request(RTL10d)
A test should exist that publishes messages from one client, and upon confirmation of message delivery, a history request should be made on another client to ensure all messages are available
(RTL12)
An attached channel may receive an additionalATTACHED
ProtocolMessage
from Ably at any point. (This is typically triggered following a transport being upgraded or resumed to indicate a partial loss of message continuity on that channel, in which case theProtocolMessage
will have aresumed
flag set to false). If and only if theresumed
flag is false, this should result in the channel emitting anUPDATE
event with aChannelStateChange
object. TheChannelStateChange
object should have bothprevious
andcurrent
attributes set toattached
, thereason
attribute set to to theerror
member of theATTACHED
ProtocolMessage
(if any), and theresumed
attribute set per theRESUMED
bitflag of theATTACHED
ProtocolMessage
. (Note thatUPDATE
should be the only event emitted: in particular, the library must not emit anATTACHED
event if the channel was already attached, seeRTL2g
).(RTL15)
Channel#properties
attribute is aChannelProperties
object representing properties of the channel state.properties
is a publicly accessible member of the channel, but it is an experimental and unstable API. It has the following attributes:(RTL13)
If the channel receives a server initiatedDETACHED
message when it is in theATTACHING
,ATTACHED
orSUSPENDED
state (i.e. the client has not explicitly requested a detach putting the channel into theDETACHING
state), then the following applies:(RTL13a)
If the channel is in theATTACHED
orSUSPENDED
states, an attempt to reattach the channel should be made immediately by sending a newATTACH
message and the channel should transition to theATTACHING
state with the error emitted in theChannelStateChange
event.(RTL13b)
If the attempt to re-attach fails, or if the channel was already in theATTACHING
state, the channel will transition to theSUSPENDED
state and the error will be emitted in theChannelStateChange
event. An attempt to re-attach the channel automatically will then be made after the period defined inClientOptions#channelRetryTimeout
. When re-attaching the channel, the channel will transition to theATTACHING
state. If that request to attach fails i.e. it times out or aDETACHED
message is received, then the process described here inRTL13b
will be repeated, indefinitely(RTL13c)
If the connection is no longerCONNECTED
, then the automatic attempts to re-attach the channel described in RTL13b must be cancelled as any implicit channel state changes subsequently will be covered by RTL3
(RTL14)
If anERROR ProtocolMessage
is received for this channel (the channel attribute matches this channel’s name), then the channel should immediately transition to the FAILED state, and theChannel.errorReason
should be set
Presence
(RTP1)
When a channelATTACHED
ProtocolMessage
is received, theProtocolMessage
may contain aHAS_PRESENCE
bit flag indicating that there are currently members present on the channel, see TR3 . If the flag is 1, the server will shortly perform aSYNC
operation as described in RTP18 . If that flag is 0 or there is noflags
field, the presence map should be considered in sync immediately with no members present on the channel(RTP2)
APresenceMap
should be used to maintain a list of members present on a channel. Broadly, this is is a map of memberKeys to presence messages, all withPRESENT
actions (during a sync there may also be ones with anABSENT
action, see RTP2f).(RTP2a)
All incoming presence messages must be compared for newness with the matching member already in thePresenceMap
, if one exists, where “matching” means they share the samememberKey
(or equivalently, they share bothconnectionId
andclientId
)(RTP2b)
To compare for newness:***
(RTP2b1)
If either presence message has aconnectionId
which is not an initial substring of itsid
, compare them bytimestamp
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 anid
that does not correspond to itsconnectionId
, as it wasn’t actually published by that connection)***
(RTP2b2)
Else split theid
of both presence messages (which will be of the formconnid:msgSerial:index
, e.g.aaaaaa:0:0
) on the separator:
, and parse the latter two as integers. Compare them first bymsgSerial
numerically, then (ifmsgSerial
is equal) byindex
numerically, larger being newer in both cases(RTP2c)
As there are no guarantees that during aSYNC
operation presence events will arrive in order, all presence messages from aSYNC
must also be compared for newness in the same way as they would from aPRESENCE
(RTP2d)
When a presence message with an action ofENTER
,UPDATE
, orPRESENT
arrives, it should be added to the presence map with the action set toPRESENT
(RTP2e)
If aSYNC
is not in progress, then when a presence message with an action ofLEAVE
arrives, thatmemberKey
should be deleted from the presence map, if present(RTP2f)
If aSYNC
is in progress, then when a presence message with an action ofLEAVE
arrives, it should be stored in the presence map with the action set toABSENT
. When theSYNC
completes, anyABSENT
members should be deleted from the presence map. (This is because in aSYNC
, we might receive aLEAVE
before the correspondingENTER
).(RTP2g)
Any incoming presence message that passes the newness check should be emitted on thePresence
object, with an event name set to its original action. Note: this action may not be the same one that it will have when stored in the presence map. For example: an incoming presence message with anENTER
action will be emitted as anenter
event, and the emitted presence message will have its action set toENTER
. However, it will be stored in the presence map with aPRESENT
action.
(RTP18)
The realtime system reserves the right to initiate a sync of the presence members at any point once a channel is attached. A server initiated sync provides Ably with a means to send a complete list of members present on the channel at any point(RTP18a)
The client library determines that a new sync has started whenever aSYNC
ProtocolMessage
is received with achannel
attribute and a new sync sequence identifier in thechannelSerial
attribute. ThechannelSerial
is used as the sync cursor and is a two-part identifier<sync sequence id>:<cursor value>
. If a new sequence identifier is sent from Ably, then the client library must consider that to be the start of a new sync sequence and any previous in-flight sync should be discarded(RTP18b)
The sync operation for that sequence identifier has completed once the cursor is empty; that is, when thechannelSerial
looks like<sync sequence id>:
(RTP18c)
aSYNC
may also be sent with nochannelSerial
attribute. In this case, the sync data is entirely contained within thatProtocolMessage
(RTP19)
If thePresenceMap
has existing members when aSYNC
is started, the client library must ensure that members no longer present on the channel are removed from the localPresenceMap
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 thePresenceMap
during the sync process. Note that a member can be added or updated when received in aSYNC
message or when received in aPRESENCE
message during the sync process. Once the sync is complete, the members in thePresenceMap
that have not been added or updated should be removed from thePresenceMap
and aLEAVE
event should be published for each. ThePresenceMessage
published should contain the original attributes of the presence member with theaction
set toLEAVE
,PresenceMessage#id
set tonull
, and thetimestamp
set to the current time. This behavior should be tested as follows:ENTER
presence on a channel, wait forSYNC
to complete, inject a member directly into the localPresenceMap
so that it only exists locally and not on the server, send aSYNC
message with thechannel
attribute populated with the current channel which will trigger a server initiatedSYNC
. ALEAVE
event should then be published for the injected member, and checking thePresenceMap
should reveal that the member was removed and the valid member entered for this connection is still present(RTP19a)
If thePresenceMap
has existing members when anATTACHED
message is received without aHAS_PRESENCE
flag, the client library should emit aLEAVE
event for each existing member, and thePresenceMessage
published should contain the original attributes of the presence member with theaction
set toLEAVE
,PresenceMessage#id
set tonull
, and thetimestamp
set to the current time. Once complete, all members in thePresenceMap
should be removed as there are no members present on the channel
(RTP17)
The Presence object should also keep a secondPresenceMap
containing only members that match the currentconnectionId
. Any incoming presence message that satisfiesRTP17b
should be applied to this object in the same way as for the normalPresenceMap
. This object should be private and is used to maintain a list of members that need to be automatically re-entered by thePresence
object when required to byRTP17c
.(RTP17a)
All members belonging to the current connection are published as aPresenceMessage
on theChannel
by the server irrespective of whether the client has permission to subscribe or theChannel
is configured to publish presence events. A test should exist that attaches to aChannel
with apresence
capability and without asubscribe
capability. It should then enter theChannel
and ensure that the member entered from the current connection is present in the internal and public presence set available viaPresence#get
(RTP17b)
The events that should be applied to theRTP17
presence map are: anyENTER
,PRESENT
orUPDATE
event with aconnectionId
that matches the current client’sconnectionId
; anyLEAVE
event with aconnectionId
that matches the current client’sconnectionId
and is not a ‘synthesized leave’ (an event that has a connectionId which is not an initial substring of its id, perRTP2b1
)(RTP17c)
The Presence object should perform automatic re-entry in the following situations:***
(RTP17c1)
After aSYNC
operation has completed, perRTP18b
***
(RTP17c2)
When anATTACHED
message is received with noHAS_PRESENCE
flag (so noSYNC
is expected as the server does not believe anyone is currently present)(RTP17d)
Automatic re-entry consists of, for each member of theRTP17
internalPresenceMap
whosememberKey
is not also a member of the normalPresenceMap
, publishing aPresenceMessage
with anENTER
action using theclientId
anddata
attributes from that member, and removing that member from the internalPresenceMap
(RTP17e)
If the publish attempt fails for an automatic presenceENTER
(for example, by Ably rejecting it with aNACK
), anUPDATE
event should be emitted on the channel withresumed
set to true andreason
set to anErrorInfo
object withcode
91004
, amessage
indicating that an automatic re-enter has failed and indicating theclientId
, andcause
set to the the reason for the enter failure. The error should also be logged atwarn
level or higher.
(RTP4)
Ensure a test exists that enters 250 members usingPresence#enterClient
on a single connection, and checks forPRESENT
events to be emitted on another connection for each member, and once sync is complete, all 250 members should be present in aPresence#get
request(RTP5)
Channel state change side effects:(RTP5a)
If the channel enters theDETACHED
orFAILED
state then all queued presence messages will fail immediately, and thePresenceMap
and internal PresenceMap is cleared. The latter ensures members are not automatically re-entered if theChannel
later becomes attached. Since channels in theDETACHED
andFAILED
states will not receive any presence updates from Ably, presence events (specificallyLEAVE
) should not be emitted when thePresenceMap
is cleared as each presence member’s state is unknown(RTP5f)
If the channel enters theSUSPENDED
state then all queued presence messages will fail immediately, and thePresenceMap
is maintained. This ensures that if the channel later becomesATTACHED
, it will only publish presence events for the changes in thePresenceMap
that have occurred whilst the client was disconnected. A test should exist for a channel that is in theSUSPENDED
state containing presence members to transition to theATTACHED
state, and following theSYNC
process after attaching, any members present before and after the sync should not emit presence events, all other changes should be reflected in thePresenceMap
and should emit presence events on the channel(RTP5b)
If a channel enters theATTACHED
state then all queued presence messages will be sent immediately. A presenceSYNC
may be initiated perRTP1
(RTP16)
Connection state conditions:(RTP16a)
If the connection isCONNECTED
and the channel isATTACHED
then all presence messages are published immediately(RTP16b)
If the connection isINITIALIZED
,CONNECTING
orDISCONNECTED
or the channel isATTACHING
orINITIALIZED
, andClientOptions#queueMessages
has not been explicitly set to false, then all presence messages will be queued and delivered as soon as the connection state returns toCONNECTED
and the channel isATTACHED
(RTP16c)
Else publishing presence messages will result in an error
(RTP6)
Presence#subscribe
function:(RTP6a)
Subscribe with no arguments subscribes a listener to all presence messages(RTP6b)
Subscribe with a single action argument – such asENTER
,LEAVE
,UPDATE
orPRESENT
– subscribes a listener to receive only presence messages with that action(RTP6c)
Implicitly attaches theChannel
if the channel is in theINITIALIZED
state. The optional callback, if provided, is called according toRTL4d
based on the implicit attach operation. The listener will always be registered regardless of the implicit attach result
(RTP7)
Presence#unsubscribe
function:(RTP7a)
Unsubscribe with no arguments unsubscribes the listener if previously subscribed with an action-specific subscription(RTP7b)
Unsubscribe with a single action argument unsubscribes the provided listener to all presence messages for that action
(RTP8)
Presence#enter
function:(RTP8a)
Enters the current client into this channel, optionally with the data and/or extras provided(RTP8b)
Optionally a callback can be provided that is called for both success or failure to enter(RTP8c)
APRESENCE ProtocolMessage
with aPresenceMessage
with the actionENTER
is sent to the Ably service. TheclientId
attribute of thePresenceMessage
must not be present. Entering without an explicitPresenceMessage#clientId
, implicitly uses theclientId
for the current connection(RTP8d)
Implicitly attaches theChannel
if the channel is in theINITIALIZED
state. However, if the channel is in theDETACHED
orFAILED
state, theenter
request results in an error(RTP8e)
Optional data and/or extras can be included when entering a channel that will be encoded and decoded as with normal messages. A test should exist to ensure data and extras used with enter are encoded & decoded correctly. Also, when data and/or extras is provided when entering, but neither data nor extras are provided when leaving, the data attribute should be emitted in theLEAVE
event for this client(RTP8f)
If the client library is authenticated but unidentified (i.e.clientId
is a wildcard'*'
or client is anonymous), theenter
request results in an error immediately(RTP8g)
If the channel isDETACHED
orFAILED
, theenter
request results in an error immediately(RTP8h)
If the Ably service determines that the client does not have required presence permission, aNACK
is sent to the client resulting in an error(RTP8i)
If the Ably service determines that the client is unidentified, aNACK
is sent to the client resulting in an error
(RTP9)
Presence#update
function:(RTP9a)
Updates the data and/or extras for the present member with an updated value or empty value (egnull
)(RTP9b)
If the client was not already entered, it enters this client into this channel(RTP9c)
Optionally a callback can be provided that is called for both success or failure to update(RTP9d)
APRESENCE ProtocolMessage
with aPresenceMessage
with the actionUPDATE
is sent to the Ably service. TheclientId
attribute of thePresenceMessage
must not be present. Updating without an explicitPresenceMessage#clientId
, implicitly uses theclientId
for the current connection(RTP9e)
In all other ways, this method is identical toPresence#enter
and should have matching tests
(RTP10)
Presence#leave
function:(RTP10a)
Leaves this client from the channel and the data and/or extras will be updated with the values provided. If the language permits the data argument to be omitted, then the previously set data value will be sent as a convenience(RTP10b)
Optionally a callback can be provided that is called for both success or failure to leave(RTP10c)
APRESENCE ProtocolMessage
with aPresenceMessage
with the actionLEAVE
is sent to the Ably service. TheclientId
attribute of thePresenceMessage
must not be present. Leaving without an explicitPresenceMessage#clientId
, implicitly uses theclientId
for the current connection(RTP10d)
If the client is not currentlyENTERED
, Ably will respond with anACK
and the request will succeed (i.e. the outcome of asking toLEAVE
when not present vs being present is the same)(RTP10e)
In all other ways, this method is identical toPresence#enter
and should have matching tests
(RTP11)
Presence#get
function:(RTP11a)
Returns the list of current members on the channel in a callback. By default, will wait for theSYNC
to be completed, see RTP11c1(RTP11b)
Implicitly attaches theChannel
if the channel is in theINITIALIZED
state. However, if the channel is in or enters theDETACHED
orFAILED
state before the operation succeeds, it will result in an error(RTP11d)
If theChannel
is in theSUSPENDED
state then theget
function will by default, or ifwaitForSync
is set totrue
, result in an error withcode
91005
and amessage
stating that the presence state is out of sync due to the channel being in aSUSPENDED
state. If however theget
function is called withwaitForSync
set tofalse
, then it immediately returns the members currently stored in thePresenceMap
giving developers access to the members that were present at the time the channel becameSUSPENDED
(RTP11c)
An optional set of params can be provided:***
(RTP11c1)
waitForSync
(defaulttrue
). Whentrue
, method will wait untilSYNC
is complete before returning a list of members. Whenfalse
, known set of presence members is returned immediately, which may be incomplete if theSYNC
is not finished***
(RTP11c2)
clientId
filters members by the providedclientId
***
(RTP11c3)
connectionId
filters members by the providedconnectionId
(RTP12)
Presence#history
function:(RTP12a)
Supports all the same params as RESTPresence#history
(RTP12b)
Additionally supports the paramuntilAttach
, which if true, will only retrieve messages prior to the moment that the channel was attached or emitted anUPDATE
indicating loss of continuity. This bound is specified by passing the querystring paramfromSerial
with theChannel#properties.attachSerial
assigned to the channel in theATTACHED
ProtocolMessage
(see RTL15a). If theuntilAttach
param is specified when the channel is not attached, it will result in an error(RTP12c)
Returns aPaginatedResult
page containing the first page of messages in thePaginatedResult#items
attribute returned from the history request(RTP12d)
A test should exist that registers presence with a few clients, and upon confirmation of entering the channel for all clients, a presence history request should be made using another client to ensure all presence events are available
(RTP13)
Presence#syncComplete
attribute istrue
if the initialSYNC
operation has completed for the members present on the channel(RTP14)
Presence#enterClient
function:(RTP14a)
Enters into presence on a channel on behalf of anotherclientId
. This allows a single client with suitable permissions to register presence on behalf of any number of clients using a single connection(RTP14b)
Optionally a callback can be provided that is called for both success or failure to enter(RTP14c)
Data can optionally be provided when entering and will follow the normal encoding & decoding rules(RTP14d)
A test should exist that registers a number of members each with a differentclientId
on a presence channel, and then aPresence#get
should be used to verify that all members are present as expected
(RTP15)
Presence#enterClient
Presence#updateClient
andPresence#leaveClient
function:(RTP15a)
Performs an enter, update or leave for givenclientId
. These methods apply if the Realtime library was not initialized with a specificclientId
. This allows a single client with suitable permissions to update presence on behalf of any number of clients using a single connection. Otherwise these are functionality equivalent to the correspondingenter
,update
andleave
methods, and equivalent test coverage should be provided(RTP15b)
Tests should useenterClient
,updateClient
andleaveClient
for many members from oneRealtime
client and check that the operations are reflected in the presence map and the expected events are emitted on a separate client(RTP15c)
Tests should also ensure that using these methods has no side effects on a client that has entered normally usingPresence#enter
(RTP15d)
A callback can be provided that will be called upon success or failure(RTP15e)
Implicitly attaches theChannel
if the channel is in theINITIALIZED
state. However, if the channel is in or enters theDETACHED
orFAILED
state before the operation succeeds, it will result in an error(RTP15f)
If the client is identified and has a validclientId
, and theclientId
argument does not match the client’sclientId
, then it should indicate an error. The connection and channel remain available for further operations
EventEmitter mixin / interface
(RTE1)
EventEmitter
is a generic interface for event registration and delivery used in a number of the types in the Realtime client library. For example, theConnection
object emits events for connection state using theEventEmitter
pattern(RTE2)
Where objects providesubscribe
orunsubscribe
methods, they should follow the specification for theEventEmitter#on
andEventEmitter#off
methods respectively(RTE3)
EventEmitter#on
registers the provided listener for either all events when noevent
argument is provided, or for only a single named event when anevent
argument is provided. Ifon
is called more than once with the same listener andevent
, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice usingon
, and an event is emitted once, the listener would be invoked twice(RTE4)
EventEmitter#once
registers the provided listener for either the first event that is emitted when noevent
argument is provided, or for only the first occurrence of a single named event when anevent
argument is provided. Ifonce
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 usingonce
, and an event is emitted once, the listener would be invoked twice. However, all subsequent events emitted would not invoke the listener asonce
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 theEventEmitter
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 byemit
must not change over the course of theemit
. That is: If a listener being called byemit
registers another listener, that second listener should not be called by that invocation ofemit
(even if it would have been called had it already been present); and if a listener being called byemit
removes other listeners, but those other listeners would otherwise have been called during thatemit
invocation, they should still be called. Tests should exist for both adding and removing. See https://goo.gl/OVTtjO
Forwards compatibility
(RTF1)
The library must apply the robustness principle in its processing of requests and responses with the Ably system. In particular, deserialization of ProtocolMessages and related types, and associated enums, must be tolerant to unrecognised attributes or enum values. Such unrecognised values must be ignored.
State conditions and operations
Connection.state
effects on realtime operations
<table>
Initialized Connecting Connected Disconnected Suspended Closing Closed Failed
<tr>
connect
<tr>
close
<tr>
ping
<tr>
Channel attach
<tr>
Channel detach
<tr>
Channel publish
<tr> Presence ops.
RTP16bRTP16bSee channel states tableRTP16bRTP16cRTP16cRTP16cRTP16cRealtimeChannel.state
effects on channel operations
<table>
Initialized Attaching Attached Suspended Detaching Detached Failed
<tr>
attach
<tr>
detach
<tr>
publish
<tr> Presence ops.
RTP16bRTP16bRTP16aRTP16cRTP16cRTP16cRTP16cPush notifications
(RSH1)
Push#admin
object provides the following interface:(RSH1a)
#publish(recipient, data)
performs an HTTP request to /push/publish. Empty values forrecipient
ordata
should be immediately rejected. An end-to-end push notification test can be made using the special test-onlyablyChannel
recipient. Additionally, tests should exist with valid and invalid recipient details(RSH1b)
#deviceRegistrations
provides access to the adminPushDeviceRegistrations
object with the following methods:***
(RSH1b1)
#get(deviceId)
performs a request to /push/deviceRegistrations/:deviceId and returns aDeviceDetails
object if thedeviceId
is found or results in a not found error if the device cannot be found. If the client has been activated as a push target device, and the specifieddeviceId
is that of the present client, then this request must include push device authentication.***
(RSH1b2)
#list(params)
performs a request to /push/deviceRegistrations and returns a paginated result withDeviceDetails
objects filtered by the provided params, as supported by the REST API. A test should exist filtering bydeviceId
and separately byclientId
, and then controlling the pagination with thelimit
attribute***
(RSH1b3)
#save(device)
issues aPUT
request to /push/deviceRegistrations/:deviceId using theDeviceDetails
object (and optionally a JSON-encodable object) argument. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation. If the client has been activated as a push target device, and the specifieddeviceId
is that of the present client, then this request must include push device authentication.***
(RSH1b4)
#remove(deviceId)
issues aDELETE
request to /push/deviceRegistrations/:deviceId and deletes the registered device specified bydeviceId
. A test should exist that deletes a device and succeeds, and then also deletes a device that does not exist but still succeeds***
(RSH1b5)
#removeWhere(params)
issues aDELETE
request to /push/deviceRegistrations and deletes the registered devices matching the providedparams
. A test should exist that deletes devices byclientId
and bydeviceId
separately, then additionally issues a delete for devices with no matching params and checks the operation still succeeds. If the client has been activated as a push target device, and the specifieddeviceId
is that of the present client, then this request must include push device authentication.(RSH1c)
#channelSubscriptions
provides access to the adminChannelSubscriptions
object with the following methods:***
(RSH1c1)
#list(params)
performs a request to /push/channelSubscriptions and returns a paginated result withPushChannelSubscription
objects filtered by the provided params, as supported by the REST API. A test should exist filtering bychannel
anddeviceId
and/orclientId
, and then controlling the pagination with thelimit
attribute***
(RSH1c2)
#listChannels(params)
performs a request to /push/channels and returns a paginated result withString
objects filtered by the provided params, as supported by the REST API. A test should exist using thelimit
attribute and pagination***
(RSH1c3)
#save(pushChannelSubscription)
issues aPOST
request to /push/channelSubscriptions using thePushChannelSubscription
object (and optionally a JSON-encodable object) argument. If the client has been activated as a push target device, and the specifiedPushChannelSubscription
contains adeviceId
matching that of the present client, then this request must include push device authentication. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation***
(RSH1c4)
#remove(push_channel_subscription)
issues aDELETE
request to /push/channelSubscriptions and deletes the channel subscription using the attributes as params to theDELETE
request. If the client has been activated as a push target device, and the specifiedPushChannelSubscription
contains adeviceId
matching that of the present client, then this request must include push device authentication. A test should exist that deletes aclientId
anddeviceId
channel subscription separately and both succeed, and then also deletes a subscription that does not exist but still succeeds***
(RSH1c5)
#removeWhere(params)
issues aDELETE
request to /push/channelSubscriptions and deletes the matching channel subscriptions provided inparams
. A test should exist that deletes channel subscriptions byclientId
and bydeviceId
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 aCalledActivate
event to the state machine.(RSH2b)
Push#deactivate
sends aCalledDeactivate
event to the state machine.(RSH2c)
(Moved to(RSH8g)
).(RSH2d)
(Moved to(RSH8h)
).(RSH2e)
(Moved to(RSH8i)
).
Activation State Machine
(RSH3)
In platforms that support receiving push notifications, in order to connect the device’s push features with Ably’s, the library must perform the process described in the following abstract state machine. While this process should be implemented in whatever way better fits the concrete platform, it should be taken into account that its lifetime is that of the app that runs it, which outlives that of theRest
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)
StateNotActivated
(the initial one).***
(RSH3a1)
On eventCalledDeactivate
:(RSH3a1a)
MakesPush#deactivate
return or call its callback with no error.(RSH3a1b)
Transitions toNotActivated
.***
(RSH3a2)
On eventCalledActivate
:(RSH3a2a)
If the local device hasdeviceIdentityToken
, performs a validation of the local DeviceDetails via the following steps.(RSH3a2b)
onwards then don’t apply. *(RSH3a2a1)
Checks the compatibilty of the present client with the existing registration: if theLocalDevice
has a non-emptyclientId
, and the present identified client has a different (non-null)clientId
, then aSyncRegistrationFailed
event should be fired containing an error withcode
61002, and skips to(RSH3a2a4)
. *(RSH3a2a2)
If a customregisterCallback
was provided toPush#activate
, pass it the localDeviceDetails
. *(RSH3a2a3)
Otherwise, makes an asynchronous HTTP PUT request to/push/deviceRegistrations/:deviceId
using the localDeviceDetails
with the push details as body. When the registration validation request is complete, aRegistrationSynced
orSyncRegistrationFailed
event should be fired. *(RSH3a2a4)
Transitions toWaitingForRegistrationSync
.(RSH3a2b)
If the local device does not haveid
anddeviceSecret
, both are generated locally. Theid
must be a ulid or similar globally-unique identifier. ThedeviceSecret
must be created using secure random data with sufficient entropy to generate a digest of at least 32 bytes (eg using sha256) and encoding that digest with base64. The localDeviceDetails
is updated with the resultingdeviceId
anddeviceSecret
.(RSH3a2c)
If the local device has the necessary push details (registration token, etc.), sends aGotPushDeviceDetails
event.(RSH3a2d)
If the local device does not have the necessary push details, it initiates a request to the underlying platform (or otherwise generates them)(RSH3a2e)
Transitions toWaitingForPushDeviceDetails
.***
(RSH3a3)
On eventGotPushDeviceDetails
:(RSH3a3a)
Transitions toNotActivated
. (This consumes the event;(RSH3a2)
produces it again oncePush#activate
is called.)(RSH3b)
StateWaitingForPushDeviceDetails
:***
(RSH3b1)
On eventCalledActivate
:(RSH3b1a)
Transitions toWaitingForPushDeviceDetails
.***
(RSH3b2)
On eventCalledDeactivate
:(RSH3b2a)
MakesPush#deactivate
return or call its callback with no error.(RSH3b2b)
Transitions toNotActivated
.***
(RSH3b3)
On eventGotPushDeviceDetails
:(RSH3b3a)
If a customregisterCallback
was provided toPush#activate
, pass it the localDeviceDetails
updated with the push details.(RSH3b3b)
Otherwise, make an asynchronous HTTPPOST
request to /push/deviceRegistrations using the localDeviceDetails
updated with the push details as body.(RSH3b3c)
Either way, when the registration is done, aGotDeviceRegistration
orGettingDeviceRegistrationFailed
event should be fired.(RSH3b3d)
Transitions toWaitingForDeviceRegistration
.***
(RSH3b4)
On eventGettingPushDeviceDetailsFailed
:(RSH3b4a)
MakesPush#activate
return or call its callback with the error.(RSH3b4b)
Transitions toNotActivated
.(RSH3c)
StateWaitingForDeviceRegistration
:***
(RSH3c1)
On eventCalledActivate
:(RSH3c1a)
Transitions toWaitingForDeviceRegistration
.***
(RSH3c2)
On eventGotDeviceRegistration
:(RSH3c2a)
Updates the localDeviceDetails
with it.(RSH3c2b)
MakesPush#activate
return or call its callback with no error.(RSH3c2c)
Transitions toWaitingForNewPushDeviceDetails
.***
(RSH3c3)
On eventGettingDeviceRegistrationFailed
:(RSH3c3a)
MakesPush#activate
return or call its callback with the error.(RSH3c3b)
Transitions toNotActivated
.(RSH3d)
StateWaitingForNewPushDeviceDetails
:***
(RSH3d1)
On eventCalledActivate
:(RSH3d1a)
MakesPush#activate
return or call its callback with no error.(RSH3d1b)
Transitions toWaitingForNewPushDeviceDetails
.***
(RSH3d2)
On eventCalledDeactivate
:(RSH3d2a)
If a customderegisterCallback
was provided toPush#deactivate
, pass it the localDeviceDetails
‘s id.(RSH3d2b)
Otherwise, make an asynchronous DELETE HTTP request to /push/deviceRegistrations using the localDeviceDetails
‘s ID. This operation requires push device authentication.(RSH3d2c)
Either way, when the registration is done, aDeregistered
orDeregistrationFailed
event should be fired.(RSH3d2d)
Transitions toWaitingForDeregistration
.***
(RSH3d3)
On eventGotPushDeviceDetails
(note that this will only happen on platforms whose push device details, after first set, can change, e. g. FCM‘s registration token refresh):(RSH3d3a)
If a customregisterCallback
was provided toPush#activate
, pass it the localDeviceDetails
updated with the push details.(RSH3d3b)
Otherwise, make an asynchronous PATCH HTTP request to /push/deviceRegistrations/:deviceId using the localDeviceDetails
‘s push details as body (but only the changed fields, as described in the REST endpoint documentation). This operation requires push device authentication.(RSH3d3c)
Either way, when the registration is done, aRegistrationSynced
orSyncRegistrationFailed
event should be fired.(RSH3d3d)
Transitions toWaitingForRegistrationSync
.(RSH3e)
StateWaitingForRegistrationSync
:***
(RSH3e1)
On eventCalledActivate
, unless the machine is in stateWaitingForRegistrationSync
as a result of aCalledActivate
event:(RSH3e1a)
MakesPush#activate
return or call its callback with no error.(RSH3e1b)
Transitions toWaitingForRegistrationSync
.***
(RSH3e2)
On eventRegistrationSynced
:(RSH3e2b)
If the machine is in stateWaitingForRegistrationSync
as a result of aCalledActivate
event, makePush#activate
return or call its callback with no error.(RSH3e2a)
Transitions toWaitingForNewPushDeviceDetails
.***
(RSH3e3)
On eventSyncRegistrationFailed
:(RSH3e3c)
If the machine is in stateWaitingForRegistrationSync
as a result of aCalledActivate
event, makePush#activate
return or call its callback with the error.(RSH3e3a)
Otherwise, calls theupdateFailedCallback
provided toPush#activate
with the error.(RSH3e3b)
Transitions toAfterRegistrationSyncFailed
.(RSH3f)
StateAfterRegistrationSyncFailed
:***
(RSH3f1)
On eventsCalledActivate
orGotPushDeviceDetails
:(RSH3f1a)
Does the same as RSH3a2a.***
(RSH3f2)
On eventsCalledDeactivate
:(RSH3f2a)
Does the same as RSH3d2.(RSH3g)
StateWaitingForDeregistration
:***
(RSH3g1)
On eventCalledDeactivate
:(RSH3g1a)
Transitions toWaitingForDeregistration
.***
(RSH3g2)
On eventDeregistered
:(RSH3g2a)
Clears all localDeviceDetails
.(RSH3g2b)
MakesPush#deactivate
return or call its callback with no error.(RSH3g2c)
Transitions toNotActivated
.***
(RSH3g3)
On eventDeregistrationFailed
:(RSH3g3a)
MakesPush#deactivate
return or call its callback with the error.(RSH3g3b)
Transitions to the previous state, which is eitherWaitingForNewPushDeviceDetails
orAfterRegistrationSyncFailed
(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’reWaitingForDeregistration
, and an eventCalledActivate
happens. This event will be put in the queue, since there’s no transition defined for it. Then, an eventDeregistered
arrives, causing a transition toNotActivated
. Now we peek the next item on the queue:CalledActivate
. BecauseNotActivated
transitions onCalledActivate
, 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.
Push device authentication
(RSH6)
In platforms that support receiving push notifications, and have undergone push registration, are capable of authenticating themselves to the Ably server in order that certain push admin operations can be authorized.(RSH6a)
If a device has completed activation and has adeviceIdentityToken
then push device authentication is performed for a request by adding anX-Ably-DeviceToken
request header whose value is thedeviceIdentityToken
. This header has always beenX-Ably-DeviceToken
, but has previously been mistakenly documented asX-Ably-DeviceIdentityToken
in the hope of renaming it to avoid confusion with APNs device token. It was never renamed.(RSH6b)
If a device has not completed but has adeviceSecret
then push device authentication is performed for a request by adding anX-Ably-DeviceSecret
request header whose value is thedeviceSecret
.
Push channels
(RSH7)
In platforms that support receiving push notifications,RestChannel
andRealtimeChannel
have an additionalpush
field, as an object with the following interface:(RSH7a)
#subscribeDevice()
***
(RSH7a1)
Fails if theLocalDevice
doesn’t have andeviceIdentityToken
, ie. it isn’t registered yet.***
(RSH7a2)
Performs a POST request to /push/channelSubscriptions with the device’sid
and the channel name.***
(RSH7a3)
The request must include push device authentication(RSH7b)
#subscribeClient()
***
(RSH7b1)
Fails if theLocalDevice
doesn’t have aclientId
.***
(RSH7b2)
Performs a POST request to /push/channelSubscriptions with the device’sclientId
and the channel name.(RSH7c)
#unsubscribeDevice()
***
(RSH7c1)
Fails if theLocalDevice
doesn’t have adeviceIdentityToken
, ie. it isn’t registered yet.***
(RSH7c2)
Performs a DELETE request to /push/channelSubscriptions with the device’sid
and the channel name.***
(RSH7c3)
The request must include push device authentication(RSH7d)
#unsubscribeClient()
***
(RSH7d1)
Fails if theLocalDevice
doesn’t have aclientId
.***
(RSH7d2)
Performs a DELETE request to /push/channelSubscriptions with the device’sclientId
and the channel name.(RSH7e)
#listSubscriptions(params)
performs a GET request to /push/channelSubscriptions and returns a paginated result withPushChannelSubscription
objects filtered by the provided params, the channel name, the device ID, and the client ID if it exists, as supported by the REST API. AconcatFilters
param needs to be set totrue
as well.
LocalDevice
(RSH8)
In platforms that support receiving push notifications, thedevice
method on theRest
orRealtime
interfaces returns an instance ofLocalDevice
that represents the current state of the device in respect of it being a target for push notifications.(RSH8a)
TheLocalDevice
is initialised when first required, either as a result of a call toRest#device
orRealtime#device
, or as a result of an operation involving the Activation State Machine. TheLocalDevice
id
,clientId
,deviceSecret
anddeviceIdentityToken
attributes are populated, together with anyrecipient
-related attributes, to the extent that they exist, from the persisted state.(RSH8b)
TheLocalDevice
id
anddeviceSecret
attributes are generated, and persisted as part of theLocalDevice
state, when required by step(RSH3a2b)
in the Activation State Machine. At that time, theclientId
attribute is also initialised, if the client is identified according to(RSA7)
.(RSH8c)
Following successful registration of aLocalDevice
, following the procedure in(RSH3c2a)
, the now knowndeviceIdentityToken
is set and persisted.(RSH8d)
If theLocalDevice* is created by an unidentified client (see "
(RSA7)":#RSA7 ) and therefore has no
clientIdset, but the client subsequently becomes identified (as a result of "
(RSA7b2)":#RSA7b2 or "
(RSA7b3)":#RSA7b3 ), then the
LocalDevice(RSH8e)
If theLocalDevice
clientId
becomes set as a result of(RSH8d)
, and theLocalDevice
is already registered (ie thedeviceIdentityToken
is set), and the ActivationStateMachine is in any state other thanNotActivated
, then aGotPushDeviceDetails
event is sent to the state machine once the effects of(RSH8d)
are visible, ie. onceLocalDevice
clientId
is set.(RSH8f)
If theLocalDevice
is created by an unidentified client (see(RSA7)
) and therefore has noclientId
set, but on receipt of a registration response (see(RSH3c2)
) the registered device has a non-emptyclientId
, then theLocalDevice
clientId
is set with thatclientId
.(RSH8g)
Whenever any change arises of the push transport details for local device (eg an FCM registration token update triggered by the platform), aGotPushDeviceDetails
event is sent to the state machine.(RSH8h)
If an attempt to obtain the push transport details for local device (eg an FCM registration token) fails, aGettingPushDeviceDetailsFailed
event containing the indicated error is sent to the state machine.(RSH8i)
Each time the library is instantiated, if the LocalDevice has push device details (eg an APNS deviceToken), and if the platform supports it, it must verify the validity of those details (eg by requesting a token from the platform and comparing that with the already-known token). If as a result there are updated details, then an update to the Ably server is triggered by sending aGotPushDeviceDetails
event to the state machine.
Types
Data types
Message
(TM1)
AMessage
represents an individual message to be sent or received via the Ably Realtime service. See the Ruby Message documentation, but bear in mind the attributes following underscore naming in Ruby(TM2)
Attributes available in aMessage
, see the Ruby Message documentation for an explanation of each attribute:(TM2a)
id
string – unique ID for this message. This attribute is always populated for messages received over REST. For messages received over Realtime, if the message does not contain anid
, it should be set toprotocolMsgId:index
, whereprotocolMsgId
is the id of theProtocolMessage
encapsulating it, andindex
is the index of the message inside themessages
array of theProtocolMessage
(TM2b)
clientId
string(TM2c)
connectionId
string. If a message received from Ably does not contain aconnectionId
, it should be set to theconnectionId
of the encapsulatingProtocolMessage
(TM2h)
connectionKey
string (note this is only ever populated by a publishing client when publishing on behalf of another client, theconnectionKey
will never be populated for messages received. A simple test for this attribute over REST is to populate this with an invalidconnectionKey
when publishing and expecting a suitable error)(TM2g)
name
string(TM2d)
data
string, buffer or JSON-encodable object or array(TM2e)
encoding
string(TM2i)
extras
JSON-encodable object, used to contain any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays. The extras field is provided to contain message metadata and/or ancillary payloads in support of specific functionality, e.g. push. Each of these supported extensions is documented separately; for 1.1 the only supported extension ispush
, via theextras.push
member. The processing of any other members is undefined(TM2f)
timestamp
time in milliseconds since epoch. If a message received from Ably does not contain atimestamp
, it should be set to thetimestamp
of the encapsulatingProtocolMessage
(TM3)
fromEncoded
andfromEncodedArray
are alternative constructors that take an (already deserialized)Message
-like object (or array of such objects), and optionally achannelOptions
, and return aMessage
(or array of suchMessages
) that’s decoded and decrypted as specified inRSL6
, using the cipher in thechannelOptions
if the message is encrypted, with any residual transforms (ones that the library cannot decode or decrypt) left in theencoding
property perRSL6b
. This is intended for users receiving messages other than from a REST or Realtime channel (for example, from a queue), to avoid them having to parse theencoding
string themselves.
PresenceMessage
(TP1)
APresenceMessage
represents an individual presence message to be sent or received via the Ably Realtime service. See the Ruby PresenceMessage documentation, 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 aPresenceMessage
, see the Ruby PresenceMessage documentation for an explanation of each attribute:(TP3a)
id
string – unique ID for this presence message. This attribute is always populated for presence messages received over REST. For presence messages received over Realtime, if the presence message does not contain anid
, it should be set toprotocolMsgId:index
, whereprotocolMsgId
is the id of theProtocolMessage
encapsulating it, andindex
is the index of the presence message inside thepresence
array of theProtocolMessage
(TP3b)
action
enum(TP3c)
clientId
string(TP3d)
connectionId
string. If a presence message received from Ably does not contain aconnectionId
, it should be set to theconnectionId
of the encapsulatingProtocolMessage
(TP3e)
data
string, buffer or JSON-encodable object or array(TP3f)
encoding
string(TP3i)
extras
JSON-encodable object, used to contain any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays. Theextras
field is provided to contain message metadata and/or ancillary payloads in support of specific functionality. For 1.1 no specific functionality is specified forextras
in presence messages; the processing of all members is undefined(TP3g)
timestamp
time in milliseconds since epoch. If a presence message received from Ably does not contain atimestamp
, it should be set to thetimestamp
of the encapsulatingProtocolMessage
(TP3h)
memberKey
string function that combines theconnectionId
andclientId
ensuring multiple connected clients with the same clientId are uniquely identifiable
(TP4)
fromEncoded
andfromEncodedArray
are alternative constructors that take an (already deserialized)PresenceMessage
-like object (or array of such objects), and optionally achannelOptions
, and return aPresenceMessage
(or array of suchPresenceMessages
) that’s decoded and decrypted as specified inRSL6
, using the cipher in thechannelOptions
if the message is encrypted, with any residual transforms (ones that the library cannot decode or decrypt) left in theencoding
property perRSL6b
. This is intended for users receiving messages other than from a REST or Realtime channel (for example, from a queue), to avoid them having to parse theencoding
string themselves.
ProtocolMessage
(TR1)
AProtocolMessage
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, but bear in mind the attributes following underscore naming in Ruby(TR2)
ProtocolMessage
Action
enum has the following values in order from zero:HEARTBEAT
,ACK
,NACK
,CONNECT
,CONNECTED
,DISCONNECT
,DISCONNECTED
,CLOSE
,CLOSED
,ERROR
,ATTACH
,ATTACHED
,DETACH
,DETACHED
,PRESENCE
,MESSAGE
,SYNC
,AUTH
(TR3)
ProtocolMessage
Flag
enum has the following values, where a flag with valuen
is defined to be set if the bitwise AND of theflags
field with2ⁿ
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 aProtocolMessage
, see the Ruby ProtocolMessage documentation for an explanation of each attribute:(TR4a)
action
enum(TR4n)
id
string, which will generally be of the formconnectionId:msgSerial
(TR4p)
auth
AuthDetails object used for auth, see RTC8(TR4b)
channel
string(TR4c)
channelSerial
string(TR4d)
connectionId
string(TR4f)
connectionSerial
long(TR4o)
connectionDetails
ConnectionDetails
object – provides details on the constraints or defaults for the connection such as max message size, client ID or connection state TTL(TR4g)
count
integer(TR4h)
error
ErrorInfo
object(TR4i)
flags
integer. Contains one or more of the following bit flags:HAS_PRESENCE: 1
,HAS_BACKLOG: 2
,RESUMED: 4
(TR4j)
msgSerial
long(TR4k)
messages
Array ofMessage
objects(TR4l)
presence
Array ofPresenceMessage
objects(TR4m)
timestamp
time in milliseconds since epoch
PaginatedResult
(TG1)
APaginatedResult
is a type that represents a page of results from a paginated query. The response is accompanied by metadata that indicates the relative queries available(TG2)
PaginatedResult
wraps all message and presence history, stats and REST presence requests. Instancing this type should not result in an error if paging headers are not returned from the REST API(TG3)
PaginatedResult#items
attribute contains a page of results (for example an Array ofMessage
objects for a channel history request)(TG4)
PaginatedResult#next
function returns a newPaginatedResult
loaded with the next page of results. If there are no further pages, thennull
is returned(TG5)
PaginatedResult#first
function returns a newPaginatedResult
for the first page of results(TG6)
PaginatedResult#hasNext
function returnstrue
if there are further pages(TG7)
PaginatedResult#isLast
function returnstrue
if this page is the last page i.e.!hasNext
HttpPaginatedResponse // RSC19b
(HP1)
AHttpPaginatedResponse
is a type that represents the response from an HTTP request containing an empty or JSON-encodable object response. Paginated queries are supported(HP2)
HttpPaginatedResponse
inherits fromPaginatedResult
and overridesnext
andfirst
so that a newHttpPaginatedResponse
is returned(HP3)
items
attribute is overriden so that an array ofJsonObject
objects are returned if the response is a JSON array. If the response is a single JSON object thenitems
returns an array with oneJsonObject
. If the response is empty, thenitems
returns an empty array.(HP4)
statusCode
is an integer attribute with the HTTP status code for the response(HP5)
success
is a boolean attribute which istrue
when the HTTP status code indicates success i.e.200 <= statusCode < 300
(HP6)
errorCode
is an integer attribute populated with the error code if theX-Ably-Errorcode
HTTP header is sent in the response(HP7)
errorMessage
is a string attribute populated with the error code if theX-Ably-Errormessage
HTTP header is sent in the response(HP8)
headers
is an Array of key value pairs for each response header
TokenRequest
(TE1)
TokenRequest
is a type containing the token request details sent to the REST requestToken endpoint(TE2)
String attributeskeyName
,clientId
,nonce
andmac
(TE3)
capability
is a string attribute containing capabilities JSON stringified(TE5)
timestamp
long – The timestamp (in milliseconds since the epoch) of this request. Timestamps, in conjunction with thenonce
, are used to prevent requests from being replayed(TE4)
ttl
attribute represents time to live (expiry) of this token in milliseconds(TE6)
TokenRequest::fromJson
static factory method that accepts either aJsonObject
or a string (which should be parsed as a JSON string), and returns a newTokenRequest
. Statically typed languages (that expect theauthCallback
to result in a typedTokenRequest
object, rather than, say, a hashmap) must implement this; others may at their discretion.
TokenDetails
(TD1)
TokenDetails
is a type containing the token request response from the REST requestToken endpoint(TD2)
TokenDetails#token
attribute contains the token string(TD3)
TokenDetails#expires
attribute contains the expiry time in milliseconds. Where idiomatic in the language, this can be a Date/Time object(TD4)
TokenDetails#issued
attribute contains the time the token was issued in milliseconds. Where idiomatic in the language, this can be a Date/Time object(TD5)
TokenDetails#capability
attribute contains the capability JSON stringified(TD6)
TokenDetails#clientId
attribute contains theclientId
assigned to the token. IfclientId
isnull
or omitted, then the token is prohibited from assuming aclientId
in any operations, however ifclientId
is a wildcard string'*'
, then the token is permitted to assume anyclientId
. Any other string value forclientId
implies that theclientId
is both enforced and assumed for all operations for this token(TD7)
TokenDetails::fromJson
static factory method that accepts either aJsonObject
or a string (which should be parsed as a JSON string), and returns a newTokenDetails
. Statically typed languages (that expect theauthCallback
to result in a typedTokenDetails
object, rather than, say, a hashmap) must implement this; others may at their discretion.
Token string
(TN1)
There is noTokenString
type but in this specification “token string” refers to a string containing a token for authentication with Ably. With the exception of RSC1a and RSA4f, token strings are handled opaquely by the library, and any use of token strings in the API and library must support any type of token string.(TN2)
A token string (referred to in this specification as an “Ably token string”) may be obtained as theTokenDetails#token
attribute of aTokenDetails
obtained in response to a request to the REST requestToken endpoint(TN3)
A token string (referred to in this specification as a JWT token string”) may be a “JSON Web Token satisfying the Ably requirements for JWTs.
AuthDetails
(AD1)
AuthDetails
is a type used with anAUTH
protocol message to send authentication details(AD2)
AuthDetails#accessToken
attribute contains the token string
Stats
(TS1)
Stats
is a type encapsulating a statistics datapoint retrieved from the REST stats endpoint. See example statistics in JSON format(TS2)
All stats values default to zero when no underlying JSON value exists. We send sparse JSON to stats requests omitting fields where the value is zero to reduce bandwidth and optimize JSON parsing, however the API exposed to developers ensures that all properties of theStats
object such asstats.all.messages.count
will always return anInteger
value i.e. all attributes are non-nullable(TS3)
See the Ruby Stats type documentation for a list of attributes and their types for theStats
object(TS4)
Stats.ConnectionTypes
– see the Ruby ConnectionTypes documentation(TS5)
Stats.MessageCount
– see the Ruby MessageCount documentation(TS6)
Stats.MessageTypes
– see the Ruby MessageTypes documentation(TS7)
Stats.MessageTraffic
– see the Ruby MessageTraffic documentation(TS8)
Stats.RequestCount
– see the Ruby RequestCount documentation(TS9)
Stats.ResourceCount
– see the Ruby ResourceCount documentation(TS10)
Stats.PushStats
– see the Ruby PushStats documentation(TS11)
Stats.XchgMessages
– see the Ruby XchgMessages documentation(TS12)
The attributes of aStats
object consist of:(TS12a)
intervalId
(property present in the JSON) – aString
(TS12b)
intervalTime
(optional) – a language-idiomaticTime
object, parsed from theintervalId
(TS12c)
unit
(property present in the JSON) – aString
or (if idiomatic) an enumerable supporting the typesminute
,hour
,day
, andmonth
. This must be from theunit
property of the JSON, not calculated from theintervalId
(TS12d)
intervalGranularity
(deprecated) – an alias forunit
, only for 1.x versions of client libraries that had such a property in a previous 1.x version, to avoid breaking compatibility. As above, this must be from theunit
property of the JSON, not calculated from theintervalId
(TS12e)
all
(property present in the JSON) – aMessageTypes
object(TS12f)
inbound
(property present in the JSON) – aMessageTraffic
object(TS12g)
outbound
(property present in the JSON) – aMessageTraffic
object(TS12h)
persisted
(property present in the JSON) – aMessageTypes
object(TS12i)
connections
(property present in the JSON) – aConnectionTypes
object(TS12j)
channels
(property present in the JSON) – aResourceCount
object(TS12k)
apiRequests
(property present in the JSON) – aRequestCount
object(TS12l)
tokenRequests
(property present in the JSON) – aRequestCount
object(TS12m)
push
(property present in the JSON) – aPushStats
object(TS12n)
xchgProducer
(property present in the JSON) – anXchgMessages
object(TS12o)
xchgConsumer
(property present in the JSON) – anXchgMessages
object
ErrorInfo
(TI1)
Provides a generic AblyErrorInfo
object that contains Ablycode
,statusCode
(analogous to HTTP status code),message
andcause
attributes(TI2)
Errors returned from the Ably server are compatible with theErrorInfo
structure and should result in errors that inherit fromErrorInfo
(TI3)
Ably-common should be included as a submodule so that consistent error codes can be used(TI4)
Ably may additionally include ahref
attribute with a string value. This is included for REST responses to provide a URL for customers to find more help on the error code(TI5)
Log entries generated from errors, where possible, should include a URL to help developers understand the error and resolve the issue. If the URL is not already present within the contents of themessage
attribute, then it should be included in the log entry as follows: “See[URL]
“. If thehref
attribute is present, it should be used as the URL. If thehref
attribute is not present, and thecode
attribute is present, then the URL should be constructed as follows “https://help.ably.io/error/[CODE]
“. If neither thehref
orcode
attributes are present, then an additional URL should not be included in the log entry.
ConnectionStateChange
(TA1)
Whenever the connection state changes, aConnectionStateChange
object is emitted on theConnection
object(TA2)
TheConnectionStateChange
object contains the current state in attributecurrent
, the previous state in attributeprevious
, and when the client is not connected and a connection attempt will be made automatically by the library, the amount of time in milliseconds until the next retry in the attributeretryIn
(TA5)
TheConnectionStateChange
object contains theevent
that generated the connection state change(TA3)
If the connection state change includes error information, then thereason
attribute will contain anErrorInfo
object describing the reason for the error
ChannelStateChange
(TH1)
Whenever the channel state changes, aChannelStateChange
object is emitted on theChannel
object(TH2)
TheChannelStateChange
object contains the current state in attributecurrent
, the previous state in attributeprevious
(TH5)
TheConnectionStateChange
object contains theevent
that generated the channel state change(TH3)
If the channel state change includes error information, then thereason
attribute will contain anErrorInfo
object describing the reason for the error(TH4)
TheChannelStateChange
object contains an attributeresumed
which in combination with anATTACHED
state, indicates whether the channel attach successfully resumed its state following the connection being resumed or recovered. Ifresumed
is true, then the attribute indicates that the attach within Ably successfully recovered the state for the channel, and as such there is no loss of message continuity. In all other cases,resumed
is false, and may be accompanied with a channel state change error reason
Capability – API not defined yet
(TC1)
This type represents a capability for a key or token(TC2)
For now a string representation of the JSON will suffice wherevercapability
is used
ConnectionDetails
(CD1)
Connection details are optionally passed to the client library in theCONNECTED
ProtocolMessage#connectionDetails
attribute to inform the client about any constraints it should adhere to, and provide additional metadata about the connection. For example, if a request is made to publish a message that exceeds themaxMessageSize
, the client library can reject the message immediately, without communicating with the Ably service(CD2)
Attributes available inConnectionDetails
:(CD2a)
clientId
contains the client ID assigned to the token. IfclientId
isnull
or omitted, then the client is prohibited from assuming aclientId
in any operations, however ifclientId
is a wildcard string'*'
, then the client is permitted to assume anyclientId
. Any other string value forclientId
implies that theclientId
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 defaultmaxMessageSize
(TO3l8)(CD2d)
maxFrameSize
overrides the defaultmaxFrameSize
(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 ofProtocolMessage
objects sent, whereas in the case of REST, it is the total number of REST requests per second(CD2f)
connectionStateTtl
is the duration that Ably will persist the connection state when a Realtime client is abruptly disconnected(CD2g)
serverId
string is a unique identifier for the front-end server that the client has connected to. This server ID is only used for the purposes of debugging(CD2h)
maxIdleInterval
is the maximum length of time in milliseconds that the server will allow no activity to occur in the server→client direction. After such a period of inactivity, the server will send aHEARTBEAT
or transport-level ping to the client. If the value is 0, the server will allow arbitrarily-long levels of inactivity.
ChannelProperties
(CP1)
properties of a channel and its state(CP2)
The attributes ofChannelProperties
consist of:(CP2a)
attachSerial
string – contains the lastchannelSerial
received in anATTACHED
ProtocolMesage
for the channel, see RTL15a
Option types
ClientOptions
(TO1)
Ably library options used when instancing a REST or Realtime client library(TO2)
Note:ClientOptions
does not currently define a default for max message size or request size. This will be added in the future to ensure that REST requests does not exceed the limits before the request is made to the server. In the case of realtime, the connection constraints will be sent to the client in the initialCONNECTED
ProtocolMessage
(TO3)
The attributes ofClientOptions
consist of:(TO3a)
clientId
string – the id of the client represented by this instance(TO3b)
logLevel
– controls the level of verbosity of log messages from the library. The implementation of this is likely to vary by platform(TO3c)
logHandler
– allows the client to intercept log messages and handle them in a client-specific way. The implementation of this is likely to vary by platform(TO3m)
logExceptionReportingUrl
– defaults to a string value for an Ably error reporting DSN (Data Source Name), which is typically a URL in the formathttps://[KEY]:[SECRET]<code>errors.ably.io/[ID]
. When set to </code>null,
[email protected] 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 instantiated(TO3f)
useBinaryProtocol
boolean – defaults to true. If false, forces the library to use the JSON encoding for REST and Realtime operations, instead of the default binary msgpack encoding(TO3g)
queueMessages
boolean – defaults to true. If false, suppresses the default queueing of messages when connection states that anticipate imminent connection (connecting and disconnected). Instead, publish and presence state changes will fail immediately if not in the connected state(TO3h)
echoMessages
boolean – defaults to true. If false, suppresses messages originating from this connection being echoed back on the same connection(TO3i)
recover
string – A connection recovery string, specified with the intention of inheriting the state of an earlier connection(TO3n)
idempotentRestPublishing
boolean – defaults to false for clients with version < 1.2, otherwise true. If true, RSL1k applies(TO3j)
Auth option attributes:***
(TO3j1)
key
string – Full Ably key string as obtained from dashboard***
(TO3j2)
token
string |TokenDetails
|TokenRequest
– An authentication token issued for this application, either in the form of a token string, aTokenDetails
object, or aTokenRequest
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. IfclientId
is unspecified, then the token issued will inherently be anonymous i.e. it will contain an emptyclientId
***
(TO3j5)
authCallback
– A callback to call to obtain a signedTokenRequest
,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 aTokenDetails
(if it contains atoken
key) orTokenRequest
(notoken
key) and use thefromJson
method (TD7, TE6) to construct an object***
(TO3j6)
authUrl
string – A URL to query to obtain a signedTokenRequest
,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 theauthUrl
. Defaults toGET
, supportsGET
andPOST
***
(TO3j8)
authHeaders
– Headers to be included in any request made by the library to theauthUrl
***
(TO3j9)
authParams
– Additional params to be included in any request made by the library to theauthUrl
, either as query params added to the URL in the case ofGET
, or form-encoded in the body in the case ofPOST
***
(TO3j10)
queryTime
– If true, the library will when issuing a token request query the Ably system for the current time instead of relying on a locally-available time of day***
(TO3j11)
defaultTokenParams
– When a TokenParams object is provided, it will override the client library defaults described in TokenParams(TO3k)
Development environment attributes:***
(TO3k1)
environment
string – for development environments only; allows a non-default Ably environment to be used such assandbox
***
(TO3k2)
restHost
string – for development environments only; allows a non-default Ably REST host to be specified. It is never valid to provide both arestHost
andenvironment
value***
(TO3k3)
realtimeHost
string – for development environments only; allows a non-default Ably Realtime host to be specified. It is never valid to provide both arealtimeHost
andenvironment
value***
(TO3k6)
fallbackHosts
string array – optionally allows one or more fallback hosts to be used instead of the default fallback hosts. If an empty array is specified, then fallback host functionality is disabled***
(TO3k7)
fallbackHostsUseDefault
boolean – optionally allows the default fallback hosts[a-e].ably-realtime.com
to be used whenenvironment
is not production or a custom realtime or REST host endpoint is being used. It is never valid to configurefallbackHost
and setfallbackHostsUseDefault
totrue
***
(TO3k4)
port
integer – for development environments only; allows a non-default Ably non-TLS port to be specified***
(TO3k5)
tlsPort
integer – for development environments only; allows a non-default Ably TLS port to be specified(TO3l)
The follow attributes, if set, are used to change the default behavior of the library:***
(TO3l1)
disconnectedRetryTimeout
integer – default 15,000 (15s). When the connection enters theDISCONNECTED
state, after this delay in milliseconds, if the state is stillDISCONNECTED
, the client library will attempt to reconnect automatically***
(TO3l2)
suspendedRetryTimeout
integer – default 30,000 (30s). When the connection enters theSUSPENDED
state, after this delay in milliseconds, if the state is stillSUSPENDED
, the client library will attempt to reconnect automatically***
(TO3l7)
channelRetryTimeout
integer – default 15,000 (15s). When a channel becomesSUSPENDED
following a server initiatedDETACHED
, after this delay in milliseconds, if the channel is stillSUSPENDED
and the connection isCONNECTED
, the client library will attempt to re-attach the channel automatically***
(TO3l3)
httpOpenTimeout
integer – default 4,000 (4s). Timeout for opening the connection, available in the client library if supported by the transport***
(TO3l4)
httpRequestTimeout
integer – default 10,000 (10s). Timeout for any single HTTP request and response***
(TO3l5)
httpMaxRetryCount
integer – default 3. Max number of fallback hosts to use as a fallback when an HTTP request to the primary host is unreachable or indicates that it is unserviceable***
(TO3l6)
httpMaxRetryDuration
integer – default 15,000 (15s). Max elapsed time in which fallback host retries for HTTP requests will be attempted i.e. if the first default host attempt takes 7s, and then the subsequent fallback retry attempt takes 10s, no further fallback host attempts will be made as the total elapsed time of 17s exceeds the default 15s limit***
(TO3l8)
maxMessageSize
integer – default 65536 (64KiB). The maximum size of messages that can be published in one go. That is, the size of theProtocolMessage.messages
orProtocolMessage.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 themaxMessageSize
in theconnectionDetails
, see CD2c(TO3l8a)
The size is defined as the sum over all messages being published of the messagename
,data
,clientId
, andextras
(TO3l8b)
The size of anObject
orArray
data
property is its string length after being JSON-stringified(TO3l8c)
The size of abinary
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 theextras
property is the string length of its JSON representation(TO3l8e)
The size of anull
or omitted property is zero***
(TO3l9)
maxFrameSize
integer – default 524288 (512KiB). 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 themaxMessageSize
limit before this***
(TO3l10)
fallbackRetryTimeout
integer – default 600000 (10 minutes). (After a failed request to the default endpoint, followed by a successful request to a fallback endpoint), the period in milliseconds before HTTP requests are retried against the default endpoint
TokenParams
(TK1)
A class providing parameters of a token request. These params are used when invokingAuth#authorize
,Auth#requestToken
andAuth#createTokenRequest
(TK2)
The attributes ofTokenParams
consist of:(TK2a)
ttl
long – Requested time to live for the token in milliseconds. When omitted, Ably will default to a TTL of 60 minutes(TK2b)
capability
string – Capability requirements JSON stringified for the token. When omitted, Ably will default to the capabilities of the underlying key(TK2c)
clientId
string – AclientId
string to associate with this token. IfclientId
isnull
or omitted, then the token is prohibited from assuming aclientId
in any operations, however ifclientId
is a wildcard string'*'
, then the token is permitted to assume anyclientId
. Any other string value forclientId
implies that theclientId
is both enforced and assumed for all operations for this token(TK2d)
timestamp
long – The timestamp (in milliseconds since the epoch) of this request. Timestamps, in conjunction with thenonce
, are used to prevent requests from being replayed.timestamp
is a “one-time” value, and is valid in a request, but is not validly a member of any default token params such asClientOptions#defaultTokenParams
AuthOptions
(AO1)
A class providing configurable authentication options used when authenticating or issuing tokens explicitly. These options are used when invokingAuth#authorize
,Auth#requestToken
,Auth#createTokenRequest
andAuth#authorize
(AO2)
The attributes ofAuthOptions
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 signedTokenRequest
,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 aTokenDetails
(if it contains atoken
key) orTokenRequest
(notoken
key) and use thefromJson
method (TD7, 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, perTD1
) issued for this application(AO2c)
authUrl
string – A URL to query to obtain a signedTokenRequest
,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 theauthUrl
. Defaults toGET
, supportsGET
andPOST
(AO2e)
authHeaders
– Headers to be included in any request made by the library to theauthUrl
(AO2f)
authParams
– Additional params to be included in any request made by the library to theauthUrl
, either as query params in the case ofGET
, or form-encoded in the body in the case ofPOST
(AO2g)
queryTime
– If true, the library will when issuing a token request query the Ably system for the current time instead of relying on a locally-available time of day
ChannelOptions
(TB1)
options provided when instancing a channel(TB2)
The attributes ofChannelOptions
consist of:(TB2b)
cipher
, which is either:***
(TB2b1)
ACipherParams
instance, or***
(TB2b2)
an options hash (or language equivalent) consisting of any subset ofCipherParams
fields that includes akey
. In this case, the client library should callgetDefaultParams
, passing it the options hash, to obtain aCipherParams
instance
(TB3)
The client lib may optionally provide an alternative constructorwithCipherKey
for ChannelOptions that takes akey
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)
CipherParams
(TZ1)
params to configure encryption for a channel(TZ2)
The attributes ofCipherParams
consist of anything necessary to implement the supported algorithms, in addition to the following standardised attributes:(TZ2a)
algorithm
string – Default isAES
. Optionally specify the algorithm to use for encryption, currently onlyAES
is supported(TZ2b)
keyLength
integer – the length in bits of thekey
(TZ2d)
key
binary – private key used to encrypt and decrypt payloads(TZ2c)
mode
string – Default isCBC
. Optionally specify cipher mode, currently onlyCBC
is supported
Push notifications
PushChannelSubscription
(PCS1)
details of a push subscription to a channel, consisting of the following attributes:(PCS2)
deviceId string – (optional, populated for subscriptions made for a specific device registration)(PCS3)
clientId string – (optional, populated for subscriptions made for a specificclientId
)(PCS4)
channel string – the channel name associated with this subscription
DeviceDetails
(PCD1)
details of a registered device, consisting of the following attributes:(PCD2)
id string – the id of the device registration(PCD3)
clientId string – (optional, populated for device registrations associated with aclientId
)(PCD4)
formFactor – the device formfactor, one ofphone
,tablet
,desktop
,tv
,watch
,car
,embedded
,other
(PCD5)
metadata – a map of string key/value pairs containing any other registered metadata associated with the device registration(PCD6)
platform – the device platform, one ofandroid
,ios
,browser
(PCD7)
push DevicePushDetails – details of the push registration for this device
DevicePushDetails
(PCP1)
details of the push registration for a given device, consisting of the following attributes:(PCP2)
errorReason ErrorInfo – (optional) any error information associated with the registration(PCP3)
recipient – a map of string key/value pairs containing details of the push transport and address(PCP4)
state – the state of the push registration, one ofActive
,Failing
,Failed
Client Library defaults
The following default values are configured for the client library:
(DF1)
Realtime defaults:(DF1a)
connectionStateTtl
integer – default 120s. The duration that Ably will persist the connection state when a Realtime client is abruptly disconnected. When the client is in theDISCONNECTED
state, once this TTL has passed, the client should transition the state to theSUSPENDED
state signifying that the state is now lost i.e. channels need to be re-attached manually. Note that this default is overriden byconnectionStateTtl
, if specified in theConnectionDetails
of theCONNECTED
ProtocolMessage
(DF1b)
realtimeRequestTimeout
– default 10s. When a realtime client library is establishing a connection with Ably, or sending aHEARTBEAT
,CONNECT
,ATTACH
,DETACH
orCLOSE
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
Interface Definition
The following bespoke IDL (Interface Definition Language) describes the types and classes present in the REST and Realtime client libraries.
Please note the following conventions:
- Types are capitalized
- Attribute and method names are lowercase
- Attributes are denoted as
attributeName: Type
- Instance methods are denoted as
methodName(argName: Type, argName: Type) -> ReturnType
(ReturnType
may be omitted) - Callback functions / closures are denoted as
(argName: Type, argName: Type) -> ReturnType
(ReturnType
is usually omitted) - Values produced by I/O (e.g. a request) are prefixed with
=> io
. In some platforms (JS) those values are passed as arguments to a callback argument, instead of being returned. I/O always can fail, but how do they fail is undefined in the spec, so it’s also undefined here - Enums are denoted as
.A | .B | .C
Type?
denotes a nullable type[Type: Othertype]
denotes a map, hash or equivalent, or (if idiomatic) a list of 2-tuples.Type default value
denotes that the thing being annotated withType
hasvalue
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
andTime
types are typically represented as milliseconds since the epoch. Where needed, a more idiomatic language specific duration may be used such asseconds
orTime
respectively for RubyData
is a message payload type, see RSL4a for a list of supported payload typesStringifiable
is a type used for unknown configuration parameters that need to be coerced to strings when used, see RTC1f for definitionJSONObject
andJSONArray
denote any type or interface in the target language that represents an RFC4627object
orarray
value respectively. Such types serialize to, and may be deserialized from, the corresponding JSON text. In the specification text above, values of these types are collectively referred to as “JSON-encodable”.
class Rest:
constructor(keyStr: String) // RSC1
constructor(tokenStr: String) // RSC1
constructor(ClientOptions) // RSC1
auth: Auth // RSC5
push: Push
device() => io LocalDevice
channels: Channels<RestChannel> // RSN1
request(
String method,
String path,
Dict<String, String> params?,
JsonObject | JsonArray body?,
Dict<String, String> headers
) => io HttpPaginatedResponse // RSC19
stats(
start: Time, // RSC6b1
end: Time api-default now(), // RSC6b1
direction: .Backwards | .Forwards api-default .Backwards, // RSC6b2
limit: int api-default 100, // RSC6b3
unit: .Minute | .Hour | .Day | .Month api-default .Minute // RSC6b4
) => io PaginatedResult<Stats> // RSC6a
time() => io Time // RSC16
class Realtime:
constructor(keyStr: String) // RSC1
constructor(tokenStr: String) // RSC1
constructor(ClientOptions) // RSC1
auth: Auth // RTC4
push: Push
device() => io LocalDevice
channels: Channels<RealtimeChannel> // RTC3, RTS1
clientId: String? // proxy for RSA7
connection: Connection // RTC2
request(
String method,
String path,
Dict<String, String> params?,
JsonObject | JsonArray body?,
Dict<String, String> headers?
) => io HttpPaginatedResponse // RTC9
stats: // Same as Rest.stats, RTC5a
close() // proxy for RTN12
connect() // proxy for RTN11
time() => io Time // RTC6a
class ClientOptions:
embeds AuthOptions // TO3j
autoConnect: Bool default true // RTC1b, TO3e
clientId: String? // RSC17, RSA4, RSA15, TO3a
defaultTokenParams: TokenParams? // TO3j11
echoMessages: Bool default true // RTC1a, TO3h
environment: String? // RSC15b, TO3k1
logHandler: // platform specific - TO3c
logLevel: // platform specific - TO3b
logExceptionReportingUrl: String default "[library specific]" // TO3c
port: Int default 80 // TO3k4
queueMessages: Bool default true // RTP16b, TO3g
restHost: String default "rest.ably.io" // RSC12, TO3k2
realtimeHost: String default "realtime.ably.io" // RTC1d, TO3k3
fallbackHosts: String[] default nil // RSC15b, RSC15a, TO3k6
recover: String? // RTC1c, TO3i
tls: Bool default true // RSC18, TO3d
tlsPort: Int default 443 // TO3k5
useBinaryProtocol: Bool default true // TO3f
transportParams: [String: Stringifiable]? // RTC1f
// configurable retry and failure defaults
disconnectedRetryTimeout: Duration default 15s // TO3l1
suspendedRetryTimeout: Duration default 30s // RTN14d, TO3l2
channelRetryTimeout: Duration default 15s // RTL13b, TO3l7
httpOpenTimeout: Duration default 4s // TO3l3
httpRequestTimeout: Duration default 10s // TO3l4
httpMaxRetryCount: Int default 3 // TO3l5
httpMaxRetryDuration: Duration default 15s // TO3l6
maxMessageSize: Int default 65536 // TO3l8
maxFrameSize: Int default 524288 // TO3l8
idempotentRestPublishing: bool default false // RSL1k1, RTL6a1, TO3n
class AuthOptions: // RSA8e
authCallback: ((TokenParams) -> io (String | TokenDetails | TokenRequest | JsonObject))? // RSA4a, RSA4, TO3j5, AO2b
authHeaders: [String: Stringifiable]? // RSA8c3, TO3j8, AO2e
authMethod: .GET | .POST default .GET // RSA8c, TO3j7, AO2d
authParams: [String: Stringifiable]? // RSA8c3, RSA8c1, TO3j9, AO2f
authUrl: String? // RSA4a, RSA4, RSA8c, TO3j6, AO2c
key: String? // RSA11, RSA14, TO3j1, AO2a
queryTime: Bool default false // RSA9d, TO3j10, AO2a
token: String? | TokenDetails? | TokenRequest? // RSA4a, RSA4, TO3j2
tokenDetails: TokenDetails? // RSA4a, RSA4, TO3j3
useTokenAuth: Bool? // RSA4, RSA14, TO3j4
class TokenParams: // RSAA8e
capability: String api-default '{"*":["*"]}' // RSA9f, TK2b
clientId: String? // TK2c
nonce: String? // RSA9c, Tk2d
timestamp: Time? // RSA9d, Tk2d
ttl: Duration api-default 60min // RSA9e, TK2a
class Auth:
clientId: String? // RSA7, RSC17, RSA12
authorize(TokenParams?, AuthOptions?) => io TokenDetails // RSA10
createTokenRequest(TokenParams?, AuthOptions?) => io TokenRequest // RSA9
requestToken(TokenParams?, AuthOptions?) => io TokenDetails // RSA8e
class TokenDetails:
+fromJson(String | JsonObject) -> TokenDetails// TD7
capability: String // TD5
clientId: String? // TD6
expires: Time // TD3
issued: Time // TD4
token: String // TD2
class TokenRequest:
+fromJson(String | JsonObject) -> TokenRequest // TE6
capability: String // TE3
clientId: String? // TE2
keyName: String // TE2
mac: String // TE2
nonce: String // TE2
timestamp: Time? // TE5
ttl: Duration? api-default 60min // TE4
class Channels<ChannelType>:
exists(String) -> Bool // RSN2, RTS2
get(String) -> ChannelType // RSN3a, RTS3a
get(String, ChannelOptions) -> ChannelType // RSN3c, RTS3c
iterate() -> Iterator<ChannelType> // RSN2, RTS2
release(String) // RSN4, RTS4
class RestChannel:
name: String?
presence: RestPresence // RSL3
history(
start: Time, // RSL2b1
end: Time api-default now(), // RSL2b1
direction: .Backwards | .Forwards api-default .Backwards, // RSL2b2
limit: int api-default 100 // RSL2b3
) => io PaginatedResult<Message> // RSL2a
publish(Message, params?: Dict<String, Stringifiable>) => io // RSL1
publish([Message], params?: Dict<String, Stringifiable>) => io // RSL1
publish(name: String?, data: Data?) => io // RSL1
// Only on platforms that support receiving notifications:
push: PushChannel // RSH4
class RealtimeChannel:
embeds EventEmitter<ChannelEvent, ChannelStateChange?> // RTL2a, RTL2d, RTL2e
errorReason: ErrorInfo? // RTL4e
state: ChannelState // RTL2b
presence: RealtimePresence // RTL9
properties: ChannelProperties // CP1, RTL15
push: PushChannel
attach() => io // RTL4d
detach() => io // RTL5e
history(
start: Time, // RTL10a
end: Time api-default now(), // RTL10a
direction: .Backwards | .Forwards api-default .Backwards, // RTL10a
limit: int api-default 100, // RTL10a
untilAttach: Bool default false // RTL10b
) => io PaginatedResult<Message> // RSL2a
publish(Message) => io // RTL6i
publish([Message]) => io // RTL6i
publish(name: String?, data: Data?) => io // RTL6i
subscribe((Message) ->) => io // RTL7a
subscribe(String, (Message) ->) => io // RTL7b
unsubscribe() // RTL8a, RTE5
unsubscribe((Message) ->) // RTL8a
unsubscribe(String, (Message) ->) // RTL8a
class PushChannel:
subscribeDevice() => io // RSH7a
subscribeClient() => io // RSH7b
unsubscribeDevice() => io // RSH7c
unsubscribeClient() => io // RSH7d
listSubscriptions() => io PaginatedResult<PushChannelSubscription> // RSH7e
enum ChannelState:
INITIALIZED
ATTACHING
ATTACHED
DETACHING
DETACHED
SUSPENDED
FAILED
enum ChannelEvent:
embeds ChannelState
UPDATE // RTL2g
class ChannelStateChange:
current: ChannelState // RTL2a, RTL2b
event: ChannelEvent // TH5
previous: ChannelState // RTL2a, RTL2b
reason: ErrorInfo? // RTL2e, TH3
resumed: Boolean // RTL2f, TH4
class ChannelOptions:
+withCipherKey(key: Binary | String)? -> ChannelOptions // TB3
cipher: (CipherParams | Params)? // RSL5a, TB2b
class CipherParams:
algorithm: String default "AES" // TZ2a
key: Binary // TZ2d
keyLength: Int // TZ2b
mode: String default "CBC" // TZ2c
class Crypto:
+getDefaultParams(Params) -> CipherParams // RSE1
+generateRandomKey(keyLength: Int?) => io Binary // RSE2
class RestPresence:
get(
limit: int api-default 100, // RSP3a
clientId: String?, // RSP3a2
connectionId: String?, // RSP3a3
) => io PaginatedResult<PresenceMessage> // RSPa
history(
start: Time, // RSP4b1
end: Time api-default now(), // RSP4b1
direction: .Backwards | .Forwards api-default .Backwards, // RSP4b2
limit: int api-default 100, // RSP4b3
) => io PaginatedResult<PresenceMessage> // RSP4a
class RealtimePresence:
syncComplete: Bool // RTP13
get(
waitForSync: Bool default true, // RTP11c1
clientId: String?, // RTP11c2
connectionId: String?, // RTP11c3
) => io [PresenceMessage] // RTP11
history(
start: Time, // RTP12a
end: Time, // RTP12a
direction: .Backwards | .Forwards api-default .Backwards, // RTP12a
limit: int api-default 100, // RTP12a
untilAttach: Bool default false // RTP12b
) => io PaginatedResult<PresenceMessage> // RTP12c
subscribe((PresenceMessage) ->) => io // RTP6a
subscribe(PresenceAction, (PresenceMessage) ->) => io // RTP6b
unsubscribe() // RTP7a, RTE5
unsubscribe((PresenceMessage) ->) // RTP7a
unsubscribe(PresenceAction, (PresenceMessage) ->) // RTP7b
// presence state modifiers
enter(Data?, extras?: JsonObject) => io // RTP8
update(Data?, extras?: JsonObject) => io // RTP9
leave(Data?, extras?: JsonObject) => io // RTP10
enterClient(clientId: String, Data?, extras?: JsonObject) => io // RTP4, RTP14, RTP15
updateClient(clientId: String, Data?, extras?: JsonObject) => io // RTP15
leaveClient(clientId: String, Data?, extras?: JsonObject) => io // RTP15
enum PresenceAction:
ABSENT // TP2
PRESENT // TP2
ENTER // TP2
LEAVE // TP2
UPDATE // TP2
class ConnectionDetails:
clientId: String? // RSA12a, CD2a
connectionKey: String // RTN15e, CD2b
connectionStateTtl: Duration // CD2f, RTN14e, DF1a
maxFrameSize: Int // CD2d
maxInboundRate: Int // CD2e
maxMessageSize: Int // CD2c
serverId: String // CD2g
maxIdleInterval: Duration // CD2h
class Message:
constructor(name: String?, data: Data?) // TM2
constructor(name: String?, data: Data?, clientId: String?) // TM2
+fromEncoded(JsonObject, ChannelOptions?) -> Message // TM3
+fromEncodedArray(JsonArray, ChannelOptions?) -> [Message] // TM3
clientId: String? // RSL1g1, TM2b
connectionId: String? // TM2c
data: Data? // TM2d
encoding: String? // TM2e
extras: JsonObject? // TM2i
id: String // TM2a
name: String? // TM2g
timestamp: Time // TM2f
class PresenceMessage
+fromEncoded(JsonObject, ChannelOptions?) -> PresenceMessage // TP4
+fromEncodedArray(JsonArray, ChannelOptions?) -> [PresenceMessage] // TP4
action: PresenceAction // TP3b
clientId: String // TP3c
connectionId: String // TP3d
data: Data? // TP3e
encoding: String? // TP3f
extras: JsonObject? // TP3i
id: String // TP3a
timestamp: Time // TP3g
memberKey() -> String // TP3h
class ProtocolMessage:
action: ProtocolMessageAction // TR2, TR4a
auth: AuthDetails? //
channel: String? // TR4b
channelSerial: String? // TR4c
connectionDetails: ConnectionDetails? // RSA7b3, RTN19, TR4o
connectionId: String? // RTN15c1, TR4d
connectionSerial: Int? // RTN10c, TR4f
count: Int? // TR4g
error: ErrorInfo? // RTN15c2, TR4h
flags: .HAS_PRESENCE & .HAS_BACKLOG & .RESUMED ? // RTP1, TR3, TR4i, RTL2f
id: String? // TR4b
messages: [Message]? // TR4k
msgSerial: Int? // RTN7b, TR4j
presence: [PresenceMessage]? // TR4l
timestamp: Time? // TR4m
enum ProtocolMessageAction:
HEARTBEAT // TR2
ACK // TR2
NACK // TR2
CONNECT // TR2
CONNECTED // TR2
DISCONNECT // TR2
DISCONNECTED // TR2
CLOSE // TR2
CLOSED // TR2
ERROR // TR2
ATTACH // TR2
ATTACHED // TR2
DETACH // TR2
DETACHED // TR2
PRESENCE // TR2
MESSAGE // TR2
SYNC // TR2
AUTH // TR2
class AuthDetails: // RTC8
accessToken: String // AD2
class Connection:
embeds EventEmitter<ConnectionEvent, ConnectionStateChange> // RTN4a, RTN4e, RTN4g
errorReason: ErrorInfo? // RTN14a
id: String? // RTN8
key: String? // RTN9
recoveryKey: String? // RTN16b, RTN16c
serial: Int // RTN10
state: ConnectionState // RTN4d
close() // RTN12
connect() // RTC1b, RTN3, RTN11
ping() => io // RTN13
enum ConnectionState:
INITIALIZED
CONNECTING
CONNECTED
DISCONNECTED
SUSPENDED
CLOSING
CLOSED
FAILED
enum ConnectionEvent:
embeds ConnectionState
UPDATE // RTN4h
class ConnectionStateChange:
current: ConnectionState // TA2
event: ConnectionEvent // TA5
previous: ConnectionState // TA2
reason: ErrorInfo? // RTN4f, TA3
retryIn: Duration? // RTN14d, TA2
class Stats:
intervalId: String // TS12a
intervalTime: Time // TS12b (calculated clientside)
unit: Stats.IntervalGranularity // TS12c
intervalGranularity: Stats.IntervalGranularity? // TS12d (deprecated)
all: Stats.MessageTypes // TS12e
inbound: Stats.MessageTraffic // TS12f
outbound: Stats.MessageTraffic // TS12g
persisted: Stats.MessageTypes // TS12h
connections: Stats.ConnectionTypes // TS12i
channels: Stats.ResourceCount // TS12j
apiRequests: Stats.RequestCount // TS12k
tokenRequests: Stats.RequestCount // TS12l
push: Stats.PushStats // TS12m
xchgProducer: Stats.XchgMessages // TS12n
xchgConsumer: Stats.XchgMessages // TS12o
enum StatsIntervalGranularity:
MINUTE
HOUR
DAY
MONTH
class DeviceDetails:
id: String
clientId: String?
formFactor: DeviceFormFactor
metadata: JsonObject
platform: DevicePlatform
push: DevicePushDetails
deviceSecret: String?
class DevicePushDetails:
errorReason: ErrorInfo?
recipient: JsonObject
state: .Active | .Failing | .Failed
class LocalDevice extends DeviceDetails:
deviceIdentityToken: String
class Push:
admin: PushAdmin // RSH1
// Only on platforms that support receiving notifications:
activate(
registerCallback: ((ErrorInfo?, DeviceDetails?) -> io String)?,
// Only on platforms that, after first set, can update later its push
// device details:
updateFailedCallback: ((ErrorInfo) ->)
) => io ErrorInfo? // RSH2a
deactivate(
deregisterCallback: ((ErrorInfo?, deviceId: String?) -> io)?
) => io ErrorInfo? // RSH2b
class PushAdmin:
publish(recipient: JsonObject, data: JsonObject) => io // RSH1a
deviceRegistrations: PushDeviceRegistrations // RSH1b
channelSubscriptions: PushChannelSubscriptions // RSH1c
class JsonObject:
// Platform-dependent, typically a Dict-like object
class PushDeviceRegistrations:
get(DeviceDetails) => io DeviceDetails // RSH1b1
get(deviceId: String) => io DeviceDetails // RSH1b1
list(params: Dict<String, String>) => io PaginatedResult<DeviceDetails> // RSH1b2
save(DeviceDetails) => io DeviceDetails // RSH1b3
remove(DeviceDetails) => io // RSH1b4
remove(deviceId: String) => io // RSH1b4
removeWhere(params: Dict<String, String>) => io // RSH1b5
class PushChannelSubscriptions:
list(params: Dict<String, String>) => io PaginatedResult<PushChannelSubscription> // RSH1c1
listChannels(params: Dict<String, String>?) => io PaginatedResult<String> // RSH1c2
save(PushChannelSubscription) => io PushChannelSubscription // RSH1c3
remove(PushChannelSubscription) => io // RSH1c4
removeWhere(params: Dict<String, String>) => io // RSH1c5
enum DevicePushTransportType:
"fcm" // PTT1
"gcm" // PTT1
"apns" // PTT1
"web" // PTT1
enum DevicePlatform:
"android" // PPT1
"ios" // PPT1
"browser" // PPT1
enum DeviceFormFactor:
"phone" // PDT1
"tablet" // PDT1
"desktop" // PDT1
"tv" // PDT1
"watch" // PDT1
"car" // PDT1
"embedded" // PDT1
"other" // PDT1
class PushChannelSubscription:
+forDevice(channel: String, deviceId: String) => PushChannelSubscription
+forClientId(channel: String, clientId: String) => PushChannelSubscription
deviceId: String? // PCS2, PCS5, PCS6
clientId: String? // PCS3, PCS6
channel: String // PCS4
class ErrorInfo:
code: Int // TI1
href: String? // TI4
message: String // TI1
cause: ErrorInfo? // TI1
statusCode: Int // TI1
class EventEmitter<Event, Data>:
on((Data...) ->) // RTE4
on(Event, (Data...) ->) // RTE4
once((Data...) ->) // RTE4
once(Event, (Data...) ->) // RTE4
off() // RTE5
off((Data...) ->) // RTE5
off(Event, (Data...) ->) // RTE5
emit(Event, Data...) // RTE6
class PaginatedResult<T>:
items: [T] // TG3
first() => io PaginatedResult<T> // TG5
hasNext() -> Bool // TG6
isLast() -> Bool // TG7
next() => io PaginatedResult<T>? // TG4
class HttpPaginatedResponse // RSC19b
embeds PaginatedResult<JsonObject>
items: [JsonObject] // HP3
statusCode: Int // HP4
success: Bool // HP5
errorCode: Int // HP6
errorMessage: String // HP7
headers: Dict<String, String> // HP8
CopyCopied!
Old specs
Use the version navigation to view older versions. References to diffs for each version are maintained below:
- v1.0 deprecated in Jan 2019. View 1.0 → 1.1 changes
- v0.8 deprecated in Jan 2017. View 0.8 → 1.0 changes