diff --git a/.gitignore b/.gitignore index c2f574e92e..bf76cf1838 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ tmp output -crash.log +/crash.log .sass* -config/jsbin_config.yaml -package-lock.json +/config/jsbin_config.yaml +/package-lock.json *.DS_Store tags diff --git a/app/assets/images/realtime/delta-messages.png b/app/assets/images/realtime/delta-messages.png new file mode 100644 index 0000000000..3069f3f53e Binary files /dev/null and b/app/assets/images/realtime/delta-messages.png differ diff --git a/content/code/realtime/channel-deltas-size.code b/content/code/realtime/channel-deltas-size.code new file mode 100644 index 0000000000..ec18328b5f --- /dev/null +++ b/content/code/realtime/channel-deltas-size.code @@ -0,0 +1,152 @@ +[--- Javascript ---] +var receivedLogNotDeltas = document.getElementById('received-not-deltas'); +var receivedLogDeltas = document.getElementById('received-deltas'); +var totalWithoutDeltas = document.getElementById('no-delta'); +var totalWithDeltas = document.getElementById('delta'); + +var clientOptions = { + key: '{{API_KEY}}', + plugins: { + vcdiff: { + decode: decodeAndCountSize + } + } + }; + +var deltaChannelOptions = { + params: { + delta: 'vcdiff', + rewind: 10 + } + }; + +var ably = new Ably.Realtime(clientOptions); + +var deltaChannel = ably.channels.get('[product:cttransit/gtfsr]vehicle:all', deltaChannelOptions); + +var channel = ably.channels.get('[product:cttransit/gtfsr]vehicle:all'); + + +/* Subscribe to a channel normally */ +channel.subscribe(function(message, err) { + var size = getMessageSize(message); + totalWithoutDeltas.innerHTML = parseInt(totalWithoutDeltas.innerHTML, 10) + size; + receivedLogNotDeltas.insertAdjacentHTML('afterbegin', '
A simple example demonstrating how using deltas compares to not using deltas.
+This example by default is subscribed to the CTtransit bus source, found on the Ably Hub. If you want to test this on one of your own channels, you can replace the API key with one of your own.
+ +Cumulative size of messages sent without deltas: 0 Bytes
+Cumulative size of messages sent with deltas: 0 Bytes
+In this example, we demonstrate the simplest way to subscribe to deltas on a channel. See our deltas documentation for more details. + +
In this example, we demonstrate the simplest way to subscribe to a message using rewind in libraries older than v1.2. See our Rewind documentation for more details. +
In this example, we demonstrate the simplest way to subscribe to a message using rewind in libraries older than v1.2. See our Rewind documentation for more details.
+
+
+Whilst Basic Authentication is simple, we recommend it to be only used on the server-side as it suffers from a number of problems:
+
+* the secret is passed directly by the client to Ably, so it is not permitted for connections that are not over TLS (HTTPS or non-encrypted realtime connections) to prevent the key secret being intercepted
+* all of the configured capabilities of the key are implicitly possible in any request, and clients that legitimately obtain this key may then abuse the rights for that key
+* clients are permitted to use any client ID in all operations with Ably. As such, a client ID in messages and presence cannot be trusted as any client using Basic Authentication can masquerade with any client ID
+
+h2(#token-authentication). Token Authentication
+
+Client-side devices should generally be considered untrusted, and as such, it is important that you minimize the impact of any credentials being compromised on those devices. Token authentication achieves this by having a trusted device, such as one of your own servers, possessing an API key "configured via the dashboard":https://support.ably.io/support/solutions/articles/3000030502-setting-up-and-managing-api-keys. It can then use the API key to distribute time-limited "tokens":#tokens with limited sets of "access rights or capabilities":#capabilities-explained, or with "specific identities (@clientId@@ClientId@)":#identified-clients to untrusted clients.
+
+Different token-issuing mechanisms can be used with Ably; the default is to use "Ably Tokens":#tokens which you request from Ably based on an Ably "TokenRequest":/realtime/authentication#token-request that you sign and issue from your servers to clients; or a "JSON Web Token":https://jwt.io (JWT) which you generate on your servers and sign using your private API key. Token Authentication, in most cases, is the recommended strategy on the client-side as it provides more fine-grained access control and limits the risk of exposed or compromised credentials.
+
+Any of the following will lead to the library to use token authentication:
+
+* an "@authUrl@@AuthUrl@":/realtime/types#client-options or "@authCallback@@AuthCallback@":/realtime/types#client-options is provided that returns an Ably-compatible token or an Ably "@TokenRequest@;":/realtime/types#token-request
+* "@useTokenAuth@@UseTokenAuth@":/realtime/types#client-options is true;
+* a "@clientId@@ClientId@":/realtime/types#client-options is provided (only for pre-1.1 client libraries);
+* a "@token@@Token@":/realtime/types#client-options or "@tokenDetails@@TokenDetails@":/realtime/types#client-options property is provided
+
+The last of those (providing a literal @token@@Token@ or @tokenDetails@@TokenDetails@) is mostly only used for testing: since tokens are short-lived, in production you almost always want to use an authentication method that allows the client library to renew the token automatically when the previous one expires.
+
+Please note that when setting up a mechanism to automatically renew tokens, an @authURL@ might be more relevant and recommended to be used with the web based clients as they can easily utilize cookies and other web-only features. However, in case of non-web clients, @authCallback@ is the recommended strategy.
+
+Token authentication is typically done in one of four ways:
+
+h3(#token-request-process). Ably TokenRequest is created by your servers and passed to clients
+
+p(tip#timetip). Token requests include a timestamp. So you should ensure that the machine on which you are running your auth server has an accurate clock, e.g. by using an "NTP daemon":https://en.wikipedia.org/wiki/Ntpd . If you are not able to control your machine's clock, you may wish to use the "@queryTime@ auth option":/rest/types#auth-options to avoid "'Timestamp not current' errors":https://support.ably.io/support/solutions/articles/3000068941-40104-timestamp-not-current .
+
+Using our REST or Realtime client libraries, an Ably "@TokenRequest@ is generated from your servers":/realtime/authentication#create-token-request and handed to the client-side client library. The client-side client library then uses that "Ably @TokenRequest@":/realtime/types#token-request to "request an Ably Token":/realtime/authentication#request-token from Ably and subsequently authenticate using that "Ably Token":#ably-tokens. This is the recommended approach for authentication as: an Ably "@TokenRequest@":/realtime/types#token-request can be generated securely by your servers without communicating with Ably; your secret API key is never shared with Ably or your clients. Ably "@TokenRequests@":/realtime/types#token-request cannot be tampered with due to being signed, must be used soon after creation and can only be used once.
+
+minimize. View example of creating an Ably TokenRequest
+ bc[javascript](code-editor:authentication/create-token-request). var ably = new Ably.Rest({ key: '{{API_KEY}}' });
+ ably.auth.createTokenRequest({ clientId: 'client@example.com' }, null, function(err, tokenRequest) {
+ /* tokenRequest => {
+ "capability": "{\"*\":[\"*\"]}",
+ "clientId": "client@example.com",
+ "keyName": "{{API_KEY_NAME}}",
+ "nonce": "5576521221082658",
+ "timestamp": {{MS_SINCE_EPOCH}},
+ "mac": "GZRgXssZDCegRV....EXAMPLE"
+ } */
+ });
+
+
+
+
+
+h3(#token-process). Ably Token is issued by your servers and passed to clients
+
+Using our REST or Realtime client libraries, an "Ably Token is requested by your servers":/realtime/authentication#request-token from Ably and then handed to the client-side client library. The client-side client library then uses that "Ably Token":#tokens to authenticate with Ably. This is an alternative approach for authentication that allows you to issue "Ably Tokens":#tokens directly as opposed to providing Ably "@TokenRequests@":/realtime/types#token-request from your servers. The advantage for clients is it saves one round trip request as they do not need to request an "Ably Token":#tokens themselves. The disadvantage is that your servers must communicate with Ably each time an "Ably Token":#tokens is required.
+
+minimize. View an example of issuing an Ably Token
+ bc[javascript](code-editor:authentication/request-token). var ably = new Ably.Rest({ key: '{{API_KEY}}' });
+ ably.requestToken({ clientId: 'client@example.com' }, function(err, token) {
+ /* token => {
+ "token": "xVLyHw.Dtxd9tuz....EXAMPLE",
+ "capability": "{\"*\":[\"*\"]}"
+ "clientId": "client@example.com",
+ "expires": 1449745287315,
+ "keyName": "{{API_KEY_NAME}}",
+ "issued": 1449741687315,
+ } */
+ });
+
+
+
+
+
+h3(#ably-jwt-process). Ably JWT is created by your servers and passed to clients
+
+p(tip). In most scenarios, we would recommend you use one of the many "JWT libraries available":https://jwt.io/ when constructing your JWT.
+
+It is possible to use a "JWT":https://jwt.io as a form of token for authentication with Ably, so long as it is structured appropriately, in what will be referred to as an "*Ably JWT*":#ably-jwt. It is possible for an "Ably JWT":/core-features/authentication#ably-jwt to contain claims indicating its clientId, capabilities and expiry - in an analogous way to an "Ably Token":#tokens - and it is signed with the applicable "Ably API key's secret part":https://support.ably.io/support/solutions/articles/3000030054.
+
+This is similar to signing an Ably "@TokenRequest@":/realtime/authentication#request-token, but the client does not need then to request an "Ably Token":#ably-tokens, instead being able to use the "Ably JWT":/core-features/authentication#ably-jwt as a token in itself. "Any compliant third-party JWT library":https://jwt.io/ may be used to create the JWT without requiring the token to be issued by Ably. This can be useful for situations where an Ably client library is not available, such as an embedded device connecting to Ably via "MQTT":/mqtt.
+
+Similarly to with the "TokenRequest flow":#timetip , you should ensure that your auth server has an accurate clock, as the JWT includes absolute "issued at" and "expires at" timestamps.
+
+An example of creating an "Ably JWT":/core-features/authentication#ably-jwt manually can be seen below, with *SECRET* being the "secret part of your API key":https://support.ably.io/support/solutions/articles/3000030054. In most scenarios however, we would recommend you use one of the many "JWT libraries available for simplicity":https://jwt.io/:
+
+minimize. View example of creating an Ably JWT
+ ```[javascript](code-editor:authentication/jwt-token)
+ var header = {
+ "typ":"JWT",
+ "alg":"HS256",
+ "kid": "{{API_KEY_NAME}}"
+ }
+ var currentTime = Math.round(Date.now()/1000);
+ var claims = {
+ "iat": currentTime, /* current time in seconds */
+ "exp": currentTime + 3600, /* time of expiration in seconds */
+ "x-ably-capability": "{\"*\":[\"*\"]}"
+ }
+ var base64Header = btoa(header);
+ var base64Claims = btoa(claims);
+ /* Apply the hash specified in the header */
+ var signature = hash((base64Header + "." + base64Claims), {{API_KEY_SECRET}});
+ var ablyJwt = base64Header + "." + base64Claims + "." + signature;
+ ```
+
+ *Note:* At present Ably does not support asymmetric signatures based on a keypair belonging to a third party. If this is something you'd be interested in, please "get in touch":https://www.ably.io/contact.
+
+
+
+
+
+h4(#jwt-embed-process). Ably-compatible token is embedded in a External JWT from your server and passed to clients
+
+If a system has an existing "JWT":https://jwt.io/ scheme, it can be useful to embed an Ably-compatible token as a claim within it. The External JWT and embedded Ably-compatible token need to meet the following requirements:
+
+* The embedded token is an "Ably JWT":#ably-jwt-process, or an "Ably Token":#ably-tokens
+* The embedded token is included under the @x-ably-token@ key in the "JOSE Header":https://tools.ietf.org/html/rfc7519
+* OR (if using JWS) the embedded token is included using the @x-ably-token@ Claim in the payload
+* The expiry time of the embedded token must not be earlier than the outer JWT’s expiry time (@exp@ Claim). Ably will reject any JWT if it is unencrypted and its @exp@ Claim is later than the expiry of the enclosed token. This helps to ensure that tokens are renewed prior to expiry
+
+minimize. View example of issuing an Ably-compatible token inside the of header of a JWT
+ bc[javascript]. var ably = new Ably.Rest({ key: '{{API_KEY}}' });
+ ably.auth.requestToken({ clientId: 'client@example.com' }, function(err, tokenDetails) {
+ var header = {
+ "typ":"JWT",
+ "alg":"HS256",
+ "x-ably-token": tokenDetails.token
+ }
+ var claims = {
+ "exp": currentTime + 3600
+ }
+ var base64Header = btoa(header);
+ var base64Claims = btoa(claims);
+ /* Apply the hash specified in the header */
+ var signature = hash((base64Header + "." + base64Claims), SECRET);
+ var jwt = base64Header + "." + base64Claims + "." + signature;
+ /* Send jwt to client */
+ });
+
+ *Note:* The authenticity of the JWT *will not* be checked, due to Ably not having access to your SECRET key.
+
+
+
+
+
+h2(#selecting-auth). Selecting an authentication mechanism
+
+<%= partial partial_version('core-features/_authentication_comparison') %>
+
+h2(#capabilities-explained). Capabilities and Token Security explained
+
+"API keys":https://support.ably.io/solution/articles/3000030054-what-is-an-app-api-key, like "Ably-compatible tokens":#tokens, have a set of capabilities assigned to them that specify which "operations":#capability-operations (such as subscribe or publish) can be performed on which channels. However, unlike tokens, API keys are long-lived, secret and typically not shared with un-trusted clients.
+
+API keys and their capabilities are "configured using the dashboard":https://support.ably.io/support/solutions/articles/3000030502-setting-up-and-managing-api-keys, they cannot be added or removed programmatically. Ably-compatible tokens on the other hand are designed to be shared with un-trusted clients, are short-lived, and significantly, they are configured and issued programmatically. See "selecting an authentication scheme":#selecting-auth to understand why token authentication, in most cases, is the preferred authentication scheme.
+
+h3(#capabilities-key). Capabilities with API keys
+
+An "Ably API key":https://support.ably.io/solution/articles/3000030054 can have single set of permissions, applied to any number of channels or "queues":/general/queues. To create a key with certain permissions, simply go to create a new API key and "select the desired permissions":https://support.ably.io/support/solutions/articles/3000030502.
+
+You can also choose whether to restrict the API key to only channels, only "queues":/general/queues, or to match a set of channel/queue names. If you've chosen to restrict the API key to *selected channels and queues*, you can use a comma separated list of resources the API key can access, making use of "wildcards":#wildcards to provide access to areas of your app. It is worth noting an API key will provide the same permissions to all resources it has access to.
+
+h3(#capabilities-token). Capabilities with Tokens
+
+"Ably Tokens":#tokens are issued from an existing "API key":https://support.ably.io/solution/articles/3000030054-what-is-an-app-api-key, and their capabilities can, at most, match the capabilities of the issuing API key. "Ably JWTs":#ably-jwt have a similar restriction on capabilities, correlating to the API key they've been signed with. If an API key must be shared with a third party, then it is recommended that "the principle of least privilege":http://en.wikipedia.org/wiki/Principle_of_least_privilege is considered, assigning only the capabilities needed by that third party. Thus, any Ably requests authenticated using that API key or Ably-compatible tokens associated with that API key, will be restricted to the capabilities assigned to the API key.
+
+minimize. View how capabilities are determined for Ably Tokens
+ * If no capability is specified in the Ably "@TokenRequest@":/realtime/types#token-request, then the "Ably Token":#ably-tokens will be given the full set of capabilities assigned to the issuing key; "see example":#capabilities-explained-all.
+ * If a set of capabilities are requested, then the "Ably Token":#ably-tokens will be assigned the intersection of the requested capability and the capability of the issuing key, "see example":#capabilities-explained-intersection.
+ * If a set of capabilities are requested, and the intersection between those and the API key's capabilities is empty (ie they are entirely incompatible), then the "Ably Token":#ably-tokens request will result in an error, "see example":#capabilities-explained-error.
+
+minimize. View how capabilities are determined for Ably JWTs
+ * The capabilities granted to an "Ably JWT":#ably-jwt will be the intersection of the capabilities within the "Ably JWT":#ably-jwt with the capabilities of the associated API key;
+ * If the set of capabilities within the "Ably JWT":#ably-jwt have no intersection with the capabilities of the API key, then an error will instead be returned, "see example":#capabilities-explained-error.
+
+See "capability operations":#capability-operations below for the complete set of supported operations on a channel.
+
+h3(#wildcards). Resource names and wildcards
+
+Capabilities are a map from resources to a list of "operations":#capability-operations. Each resource can match a single channel e.g. @channel@, or multiple channels using wildcards (@*@). Wildcards can only replace whole segments (segments are delimited by @:@) of the resource name. A wildcard at the end of the name can replace arbitrarily many segments. For example:
+
+* A resource of @*@ will match any channel
+* A resource of @namespace:*@ will match any channel in the @namespace@ namespace, including @namespace:channel@, and @namespace:channel:other@
+* A resource of @foo:*:baz@ will match @foo:bar:baz@, but not @foo:bar:bam:baz@
+* A resource of @foo:*@ will match @foo:bar@, @foo:bar:bam@, @foo:bar:bam:baz@ etc., as the wildcard as at the end
+* A resource of @foo*@ (without a colon!) will only match the single channel literally called @foo*@, which probably isn't what you want
+
+A resource can also be a queue, in which case it will start with @[queue]@, e.g. @[queue]appid-queuename@. (This is unambiguous as channel names may not begin with a @[@). Similar wildcard rules apply, e.g. @[queue]*@ will match all queues.
+
+A resource can also be a metachannel, in which case it will start with @[meta]@, e.g. @[meta]metaname@. (This is unambiguous as channel names may not begin with a @[@). Similar wildcard rules apply, e.g. @[meta]*@ will match all metachannels.
+
+You can also have a resource name of @[*]*@, which will match all queues, all metachannels, and all channels.
+
+Wildcards are also supported for "operations":#capability-operations, by requesting an operations list of @['*']@.
+
+h3(#capabilities-example-key). Capabilities example for API key
+
+In order to define which capabilities an API key will have, simply select the appropriate capability boxes when "setting up your API key":https://support.ably.io/support/solutions/articles/3000030502-setting-up-and-managing-api-keys. The structure to define channels and namespaces is structurally the same as defined in the above "wildcards":#wildcards section.
+
+h3(#capabilities-example). Capabilities example in code for tokens
+
+If you want to see some live code examples of how capabilities work, take a look at our "capabilities example":<%= JsBins.url_for('authentication/capabilities') %>.
+
+h4(#capabilities-explained-all)(minimize=View capabilities example). Ably Token request without capabilities example
+
+Given an API key exists with the following capabilities:
+
+```[json]
+{
+ "chat": ["publish", "subscribe", "presence"],
+ "status": ["subscribe"]
+}
+```
+
+If an "Ably Token":#ably-tokens is requested without requiring any capabilities:
+
+```[javascript]
+auth.requestToken(tokenCallback)
+```
+
+Then the request for an "Ably Token":#ably-tokens is treated as requesting all capabilities, i.e. @{"[*]*":["*"]}@), and all capabilities of the API key are assigned to the "Ably Token":#ably-tokens. The capabilities for the issued "Ably Token":#ably-tokens would be as follows:
+
+```[json]
+{
+ "chat": ["publish", "subscribe", "presence"],
+ "status": ["subscribe"]
+}
+```
+
+h4(#capabilities-explained-intersection)(minimize=View intersected capabilities example). Ably Token is requested with intersection of capabilities example
+
+Given an API key exists with the following capabilities:
+
+```[json]
+{
+ "chat:*": ["publish", "subscribe", "presence"],
+ "status": ["subscribe", "history"],
+ "alerts": ["subscribe"]
+}
+```
+
+And an "Ably Token":#ably-tokens is requested with the following explicit capabilities:
+
+```[javascript]
+auth.requestToken({ capability: {
+ "chat:bob": ["subscribe"], // only "subscribe" intersects
+ "status": ["*"], // "*"" intersects with "subscribe"
+ "secret": ["publish", "subscribe"] // key does not have access to "secret" channel
+}}, tokenCallback)
+```
+
+Then Ably will intersect the API key's capabilities and the requested capabilities ie Ably will satisfy the requested "Ably Token's":#ably-tokens capabilities as far as possible based on the capability of the issuing API key. The capabilities for the issued "Ably Token":#ably-tokens would be as follows:
+
+```[json]
+{
+ "chat:bob": ["subscribe"],
+ "status": ["subscribe", "history"]
+}
+```
+
+h4(#capabilities-explained-error)(minimize=View incompatible capabilities example). Ably Token is requested with incompatible capabilities
+
+Given an API key exists with the following capabilities:
+
+```[json]
+{
+ "chat": ["*"]
+}
+```
+
+And an "Ably Token":#ably-tokens is requested with the following explicit capabilities:
+
+```[javascript]
+auth.requestToken({ capability: {
+ "status": ["*"]
+}}, tokenCallback)
+```
+
+Then Ably will be unable to issue an "Ably Token":#ably-tokens because the intersection of the requested capabilities and the API key's capabilities is empty – they are entirely incompatible. In the example above, @requestToken@ will call the callback with an error.
+
+See a working "capabilities example":<%= JsBins.url_for('authentication/capabilities') %>.
+
+h3(#capability-operations). Capability operations
+
+<%= partial partial_version('core-features/_authentication_capabilities') %>
+
+h3(#identified-clients). Understanding Identified clients
+
+When a client is authenticated and connected to Ably, they are considered to be an *authenticated client*. However, whilst an *authenticated client* has a verifiable means to authenticate with Ably, they do not necessarily have an identity. When a client is assigned a trusted identity (ie a @client ID@), then they are considered to be an *identified client* and for all operations they perform with the Ably service, their @client ID@ field will be automatically populated and can be trusted by other clients.
+
+For example, assuming you were building a chat application and wanted to allow clients to publish messages and be present on a channel. If each client is assigned a trusted identity by your server, such as a unique email address or UUID, then all other subscribed clients can trust any messages or presence events they receive in the channel as being from that client. No other clients are permitted to assume a @client ID@ that they are not assigned in their Ably-compatible token, that is they are unable to masquerade as another @client ID@.
+
+In Ably a client can be identified with a @client ID@ in two ways:
+
+* if the client is authenticated with an Ably-compatible token that is issued for that @client ID@;
+* if the client claims that @client ID@ (as part of "@ClientOptions@":/realtime/usage#client-options in the "constructor":/realtime/usage) and is authenticated with an Ably-compatible token that is issued for a "wildcard @client ID@":https://support.ably.io/solution/articles/3000048586 (a special token privilege that allows any client identity to be assumed)
+
+We encourage customers to always issue Ably-compatible tokens to clients so that they authenticate using the short-lived token and do not have access to a customer's private API keys. Since the customer can then control the @client ID@ that may be used by any of its clients, all other clients can rely on the validity of the @client ID@ in published messages and of members present in presence channels.
+
+The following Javascript example demonstrates how to issue an "Ably Token":#ably-tokens with an explicit @client ID@ that, when used by a client, will then be considered an *identified client*.
+
+```[javascript](code-editor:realtime/auth-client-id)
+ var realtime = new Ably.Rest({ key: '{{API_KEY}}' });
+ realtime.auth.createTokenRequest({ clientId: 'Bob' }, function(err, tokenRequest) {
+ /* ... issue the TokenRequest to a client ... */
+ })
+```
+
+h1. Authentication API Reference
+
+inline-toc.
+ Token Types:
+ - TokenDetails#ably-tokens
+ - Ably JWT#ably-jwt
+ Objects:
+ - Auth object#auth-object
+
+h2(#tokens). Token Types
+
+In the documentation, references to Ably-compatible tokens typically refer either to an Ably Token, or an "Ably JWT":#ably-jwt. For Ably Tokens, this can either be referring to the @TokenDetails@ object that contain the @token@ string or the token string itself. @TokenDetails@ objects are obtained when "requesting an Ably Token":/realtime/authentication#request-token from the Ably service and contain not only the @token@ string in the @token@ attribute, but also contain attributes describing the properties of the Ably Token. For "Ably JWT":#ably-jwt, this will be simply referring to a JWT which has been signed by an Ably private API key.
+
+h3(#ably-tokens). TokenDetails type
+
+<%= partial partial_version('types/_token_details') %>
+
+h3(#ably-jwt). Ably JWT
+
+An Ably JWT is not strictly an Ably construct, rather it is a "JWT":https://jwt.io/ which has been constructed to be compatible with Ably. The JWT must adhere to the following to ensure compatibility:
+
+* *The JOSE header must include:*
+** @kid@ - Key name, such that an API key of @{{API_KEY}}@ will have key name @{{API_KEY_NAME}}@
+* *The JWT claim set must include:*
+** @iat@ - time of issue in seconds
+** @exp@ - expiry time in seconds
+* *The JWT claim set may include:*
+** @x-ably-capability@ - JSON text encoding of the "capability":https://www.ably.io/documentation/core-features/authentication#tokens
+** @x-ably-clientId@ - client ID
+
+Arbitrary additional claims and headers are supported (apart from those prefixed with @x-ably-@ which are reserved for future use).
+
+The Ably JWT must be signed with the secret part of your "Ably API key":https://support.ably.io/support/solutions/articles/3000030054, using one of the following signature algorithms (as defined in "JWA":https://tools.ietf.org/html/rfc7518):
+
+* *HS256* - HMAC using the SHA-256 hash algorithm
+* *HS384* - HMAC using the SHA-384 hash algorithm
+
+We recommend you use one of the many "JWT libraries available for simplicity":https://jwt.io/ when creating your JWTs.
+
+minimize. View example of creating an Ably JWT
+ ```[javascript](code-editor:authentication/jwt-token)
+ var header = {
+ "typ":"JWT",
+ "alg":"HS256",
+ "kid": "{{API_KEY_NAME}}"
+ };
+ var currentTime = Math.round(Date.now()/1000);
+ var claims = {
+ "iat": currentTime, /* current time in seconds */
+ "exp": currentTime + 3600, /* time of expiration in seconds */
+ "x-ably-capability": "{\"*\":[\"*\"]}"
+ };
+ var base64Header = btoa(header);
+ var base64Claims = btoa(claims);
+ /* Apply the hash specified in the header */
+ var signature = hash((base64Header + "." + base64Claims), {{API_KEY_SECRET}});
+ var ablyJwt = base64Header + "." + base64Claims + "." + signature;
+ ```
+
+ *Note:* At present Ably does not support asymmetric signatures based on a keypair belonging to a third party. If this is something you'd be interested in, please "get in touch":https://www.ably.io/contact.
+
+h2(#auth-object). Auth object
+
+The principal use-case for the @Auth@ object is to create Ably "@TokenRequest@":/realtime/authentication#token-request objects with "createTokenRequest":/realtime/authentication#create-token-request or obtain "Ably Tokens":#ably-tokens from Ably with "requestToken":#request-token, and then issue them to other "less trusted" clients. Typically, your servers should be the only devices to have a "private API key":https://support.ably.io/solution/articles/3000030054, and this private API key is used to securely sign Ably "@TokenRequest@":/realtime/authentication#token-request objects or request "Ably Tokens":#ably-tokens from Ably. Clients are then issued with these short-lived "Ably Tokens":#ably-tokens or Ably "@TokenRequest@":/realtime/authentication#token-request objects, and the libraries can then use these to authenticate with Ably. If you adopt this model, your private API key is never shared with clients directly.
+
+A subsidiary use-case for the @Auth@ object is to preemptively trigger renewal of a token or to acquire a new token with a revised set of capabilities by explicitly calling "@authorize@@Authorize@":/realtime/authentication#authorize.
+
+Descriptions of this object exist in both the "Realtime":/realtime/authentication#auth-options and "REST":/rest/authentication#auth-options libraries.
diff --git a/content/core-features/versions/v1.1/channels.textile b/content/core-features/versions/v1.1/channels.textile
new file mode 100644
index 0000000000..b8b94ae9fd
--- /dev/null
+++ b/content/core-features/versions/v1.1/channels.textile
@@ -0,0 +1,31 @@
+---
+title: Channels
+section: core-features
+index: 23
+---
+
+Ably aggregates all it's data into named units of distribution, referred to as "channels". Channels offer a way to implement the Publish-Subscribe (Pub/Sub) architectural pattern, which is a popular pattern used for realtime data delivery.
+
+The Publish-Subscribe messaging pattern lets any number of publishers publish data to a channel, which could be subscribed to by any number of subscribers. The key thing to note about Pub/Sub is that publishers and subscribers are completely decoupled as explained "further down this page":/channels#understanding-decoupled-clients. Once subscribed, the subscribers no longer have to poll the server or data provider to check if there is any new data that they need to be aware of; instead, they will be notified of it as it becomes available.
+
+h2(#understanding-pubsub). Understanding Pub/Sub with an example
+
+To understand Pub/Sub in more detail, let's consider an example of location tracking of a vehicle in realtime. In this case, the vehicle whose location is to be tracked acts as a publisher, while the user intending to receive the location updates acts as a subscriber. This scenario is illustrated below.
+
+
+
+
+
+As you can see, in order to accomplish this scenario, the client would subscribe to the location channel, to receive updates continuously being published by the vehicle to the same channel. Since the location tracking needs to be live, one would use the realtime library to implement this. Ably's Realtime library uses the "WebSocket transport protocol":/concepts/websockets under the hood; thus the connection remains open for the whole duration that the app is running for.
+
+h2(#understanding-decoupled-clients). Understanding de-coupling of Pub/Sub clients
+
+Ably's Data Stream Network supports the Publish-Subscribe messaging pattern via the concept of channels as explained in the previous sections. With Ably, you are able to use any number of devices and languages with one another. The various clients of Ably's Data Stream Network can be quite diverse too, as shown in the illustration below. This means that while a publisher might be a sensor working with "MQTT":/concepts/mqtt, the subscriber could be a web browser working with JavaScript.
+
+
+
+
+
+Ably is responsible for routing the right message to the right client in real time ("typically within 60ms globally":https://status.ably.com/status).
+
+Read the "Realtime Library":/realtime documentation to learn how you can implement Pub/Sub in your applications, or you can have a look at our "REST Library":/rest if you wish to do discrete operations like publishing data periodically, on channels. Further, you can also jump into the "Pub/Sub tutorial":https://www.ably.io/tutorials/publish-subscribe#lang-javascript or check out a "video":https://www.youtube.com/watch?v=_70uOFiBeo8&t=0s&list=PLv7MaB8onr7krKzzwswsLYFsB_KmgEu44&index=2 to see it in action.
\ No newline at end of file
diff --git a/content/core-features/versions/v1.1/history.textile b/content/core-features/versions/v1.1/history.textile
new file mode 100644
index 0000000000..311f0ce534
--- /dev/null
+++ b/content/core-features/versions/v1.1/history.textile
@@ -0,0 +1,19 @@
+---
+title: History
+section: core-features
+index: 21
+---
+
+By default, all messages sent on Ably will be stored for 2 minutes on our servers. This allows for clients who disconnect for less than 2 minutes to recover any messages they might have missed, through our History API. The recovering client will receive these messages in the original order they were sent, and this is applicable to both "regular channel messages":/realtime/messages and "presence messages":/realtime/presence.
+
+
+
+
+
+However, if your use case requires longer retention of messages, i.e. longer than the default two minutes, you can enable "persisted history": using both the "Realtime":/realtime and the "REST":/rest libraries of Ably. If "persisted history is enabled":#persisted-history for a channel, its messages will "typically be stored for 24 - 72 hours on disk":https://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for.
+
+h2(#persisted-history). Enabling persistent history
+
+Every message that is persisted to or retrieved from disk counts as an extra message towards your monthly quota. For example, for a channel that has persistence enabled, if a message is published, two messages will be deducted from your monthly quota. If the message is later retrieved from history, another message will be deducted from your monthly quota.
+
+To enable history on a channel, it is necessary to add a channel rule in the settings of your "application dashboard":https://support.ably.io/solution/articles/3000030053-how-do-i-access-my-app-dashboard. See the "documentation on channel rules":https://support.ably.io/solution/articles/3000030057-what-are-channel-rules-and-how-can-i-use-them-in-my-app for further information on what they are and how to configure them.
\ No newline at end of file
diff --git a/content/core-features/versions/v1.1/presence.textile b/content/core-features/versions/v1.1/presence.textile
new file mode 100644
index 0000000000..dbdc74f9d2
--- /dev/null
+++ b/content/core-features/versions/v1.1/presence.textile
@@ -0,0 +1,10 @@
+---
+title: Presence
+section: core-features
+index: 27
+---
+ Ably's presence feature allows clients or devices to announce their presence on a channel. Other devices or services may then subscribe to these presence events (such as entering, updating their state, or leaving the channel) in real time using our "realtime SDKs":/realtime, or via the "Reactor service":https://www.ably.io/reactor. You can also request a list of clients or devices that are online/offline on a channel at a particular point in time via the "REST API":/rest-api#presence.
+
+
+
+ Furthermore, if persistence is enabled on the presence channel, you can also retrieve "presence history":/rest/history#presence-history for the channel, i.e, static data about historical presence states of your clients/devices. This operation also can be done using both Ably's "Realtime":/realtime and "REST":/rest libraries.
diff --git a/content/core-features/versions/v1.1/pubsub.textile b/content/core-features/versions/v1.1/pubsub.textile
new file mode 100644
index 0000000000..4394be227e
--- /dev/null
+++ b/content/core-features/versions/v1.1/pubsub.textile
@@ -0,0 +1,21 @@
+---
+title: Pub/Sub
+section: core-features
+index: 23
+---
+
+Pub/Sub is shorthand for the Publish/Subscribe architectural pattern, which is a popular pattern used for realtime data delivery. This messaging pattern lets any number of publishers publish data, ideally to a data channel/topic, which could be subscribed to by any number of subscribers. The important thing to note about Pub/Sub is that publishers and subscribers are completely decoupled. In addition, once subscribed, the subscribers no longer have to poll the server or data provider to check if there’s any new data that they need to be aware of; instead, they will be notified of it as it becomes available.
+
+
+
+
+
+For instance, consider a location tracking application. The subscriber will continue to receive updates in real time for as long as the connection remains connected, and the client is subscribed for updates.
+
+
+
+
+
+As seen in the illustration above, you can implement Pub/Sub easily by using Ably as the intermediary realtime messaging platform. Your publishers and subscribers can attach to named "channels":/realtime/channels and Ably is responsible for routing the right message to the right client in real time ("typically within 60ms globally":https://status.ably.com/status).
+
+Read our "Realtime Library":/realtime documentation to learn how you can implement Pub/Sub in your applications. You can also jump into our "Pub/Sub tutorial":https://www.ably.io/tutorials/publish-subscribe#lang-javascript or check out a "quick bit video":https://www.youtube.com/watch?v=_70uOFiBeo8&t=0s&list=PLv7MaB8onr7krKzzwswsLYFsB_KmgEu44&index=2 to see it in action.
\ No newline at end of file
diff --git a/content/general/events/ifttt.textile b/content/general/events/ifttt.textile
index f6a84280af..717f1fd2a5 100644
--- a/content/general/events/ifttt.textile
+++ b/content/general/events/ifttt.textile
@@ -65,7 +65,7 @@ x-ably-envelope-source: channel.message
x-ably-message-encoding: json
x-ably-message-id: {UNIQUE_ABLY_MESSAGE_ID}
x-ably-message-timestamp: {TIMESTAMP_ORIGINAL_MESSAGE_WAS_SENT}
-x-ably-version: 1.0
+x-ably-version: 1.2
content-length: 18
connection: keep-alive
```
diff --git a/content/general/versions/v1.1/channel-rules-namespaces.textile b/content/general/versions/v1.1/channel-rules-namespaces.textile
new file mode 100644
index 0000000000..472e31cfea
--- /dev/null
+++ b/content/general/versions/v1.1/channel-rules-namespaces.textile
@@ -0,0 +1,13 @@
+---
+title: Channel Rules and Namespaces
+index: 15
+---
+
+Apps can have one or more channel rules with configurable settings that will be applied to matching channels created in the app. If no channel rule is found when a channel is created, the default channel rule for the app is applied.
+
+Channel rules allow settings such as whether messages are persisted or TLS required to be configured. The name in each configured channel rule will match any channel with that name or any channel in that namespace. For example, a channel rule with the configured namespace of "rss" will match the channel name "rss" as well as the channel name "rss:news" which is a channel within the "rss" namespace.
+
+Each channel rule has the following configurable settings:
+* **Persisted messages** - If enabled, all messages within this namespace will be persisted. You can access stored messages via the History API. The number of hours a message is stored is configurable, "find out more about message persistence":http://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for
+* **Require authentication** - if enabled, only "authorized clients":/general/authorization with a clientId will be permitted to subscribe to matching channels. Anonymous clients will not be permitted to join the channel.
+* **Require TLS** - if enabled, only clients who have connected to Ably over TLS will be allowed to join the matching channel.
diff --git a/content/general/versions/v1.1/events.textile b/content/general/versions/v1.1/events.textile
new file mode 100644
index 0000000000..b20f62766f
--- /dev/null
+++ b/content/general/versions/v1.1/events.textile
@@ -0,0 +1,179 @@
+---
+title: Reactor Events
+section: general
+index: 20
+languages:
+ - none
+jump_to:
+ Help with:
+ - Available integrations#integrations
+ - Configuring a Webhook#configure
+ - Sources#sources
+ - Single vs Batched requests#batching
+ - Envelopes#envelope
+ - Payload encoding#encoding
+ - Webhook Security#security
+ - Examples#examples
+---
+
+Reactor Events allow you to configure rules that react to "messages being published":/realtime/messages or "presence events emitted":/realtime/presence (such as members entering or leaving) on "channels":/realtime/channels. These rules can notify HTTP endpoints, serverless functions or other services for each event as they arise, or in batches.
+
+p(tip). Reactor Events are "rate limited":#transport and are suitable for low to medium volumes of updates. If you expect a high volume of events and messages (averaging more than 25 per second), then you should consider using our "message queues":/general/queues or "firehose":/general/firehose as they are more suitable for higher volumes.
+
+Subscribing to events and messages on-demand is often best done using our "realtime client libraries":/realtime or by subscribing to Ably using any of the "realtime protocols we support":https://www.ably.io/adapters. However, when a persistent subscription is required to push data into third party systems, the Reactor is designed for this use case and is available as *Reactor Events* (for HTTP requests, serverless functions, etc), "Reactor Queues":/general/queues (data is pushed into our own hosted message queues that you can subscribe to), or "Reactor Firehose":/general/firehose (stream events into third party systems like Kafka and AWS Kinesis).
+
+If you want to be notified as events arise, trigger serverless functions, or invoke an HTTP request to an endpoint, then Reactor Events is the right choice. For example, if you want to send a welcome message to someone when they become present on a chat channel, you can use Reactor Events to trigger a serverless function immediately after they enter with using "channel lifecycles":#sources, which in turn can publish a welcome message back to that user on the chat channel.
+
+In addition, various existing systems, such as Azure Functions, Google Functions, and AWS Lambda rely on HTTP events. Reactor Events will allow for simple integration with said systems.
+
+
+
+
+
+You can "configure events":https://support.ably.io/support/solutions/articles/3000074406 from the "Reactor tab in your app":https://support.ably.io/solution/articles/3000074406 on a per app basis which can apply to one or more channels in that app. Reactor Events can be filtered by channel naming using a regular expression, for example @^click_.*_mouse$@. This would match the string @click_@ followed by a string followed by @_mouse@, for example, @click_left_mouse@.
+
+h3(#integrations). Available integrations
+
+At present, in addition to support for any custom HTTP endpoint, we have ready-made integrations with the following services:
+
+* "AWS Lambda Functions":/general/events/aws-lambda
+* "Azure Functions":/general/events/azure
+* "Google Cloud Functions":/general/events/google-functions
+* "IFTTT":/general/events/ifttt
+* "Cloudflare Workers":/general/events/cloudflare
+* "Zapier":/general/events/zapier
+
+h2(#configure). Configuring a webhook
+
+Webhooks are configured from the Reactor tab in your "app dashboard":https://support.ably.io/support/solutions/articles/3000030053. The following fields are shared between each webhook:
+
+- URL := The URL of the endpoint where messages will be sent
+- Custom headers := Optionally allows you to provide a set of headers that will be included in all HTTP POST requests. You must use format @name:value@ for each header you add, for example, @X-Custom-Header:foo@
+- "Source":#sources := Choose which of @Message@, @Presence@, or @Channel Lifecycle@ events on channels should activate this Reactor Event Rule. @Channel Lifecycle@ events are only available in "Batch Request":#batching mode
+- "Request Mode":#batching := This will either be in @Single Request@ mode or @Batch Request@ mode. "Single Request":#batching will send each event as separately to the endpoint specified by the Rule. "Batch Request":#batching will roll up multiple events in the same request
+- Channel filter := An optional filter, which allows the Rule to be applied to a restricted set of channels. This can be specified as a regular expression, allowing for swathes of channels to be used
+- "Encoding":#encoding := The encoding to be used by this Rule. This can be either JSON or "MsgPack":http://msgpack.org. Encoding only applies to "enveloped":#envelope and "batched":#batching messages
+
+
+
+
+h2(#payloads). Payload types
+
+Ably currently supports three types of data that can be delivered via our Firehose:
+
+* **Messages** - messages are streamed as soon as they are published on a channel
+* **Presence events** - when clients enter, update their data, or leave channels, the presence event is streamed
+* **Channel lifecycle events** - when a channel is created (following the first client attaching to this channel) or discarded (when there are no more clients attached to the channel), the lifecycle event is streamed
+
+h2(#streaming). Streaming server support
+
+We can support the following streaming servers:
+
+* Amazon Kinesis
+* Apache Spark ("get in touch":https://www.ably.io/contact)
+* Apache Storm ("get in touch":https://www.ably.io/contact)
+* Google DataFlow ("get in touch":https://www.ably.io/contact)
+
+h2(#queue). Queue server support
+
+We can support the following queueing servers:
+
+* Amazon SQS
+* RabbitMQ
+* ActiveMQ ("get in touch":https://www.ably.io/contact)
+* Apache Kafka ("get in touch":https://www.ably.io/contact)
+
+h2(#getting-started). Getting started
+
+Reactor Firehose is offered exclusively to our "Enterprise customers":https://www.ably.io/pricing/enterprise. "Get in touch":https://www.ably.io/contact if you would like to discuss setting up a Firehose to your servers.
diff --git a/content/general/versions/v1.1/push.textile b/content/general/versions/v1.1/push.textile
new file mode 100644
index 0000000000..9314f232c2
--- /dev/null
+++ b/content/general/versions/v1.1/push.textile
@@ -0,0 +1,64 @@
+---
+title: Push Notifications
+section: general
+index: 55
+api_separator:
+jump_to:
+ Help with:
+ - Delivering push notifications#deliver
+ - Activating and subscribing a device#activate-device
+ - Managing devices and subscriptions#admin
+ - Platform support#platform-support
+ - Tutorials#tutorials
+---
+
+<%= partial partial_version('general/push/_push_intro') %>
+
+We no longer support "Google Cloud Messaging":https://developers.google.com/cloud-messaging/; this is deprecated by Google, in favour of FCM, and the service will soon be terminated altogether.
+
+h3(#download). Downloading a client library with push support
+
+The following Ably client library SDKs provide support for activation and receiving of native push notifications:
+
+* "Android SDK":https://github.com/ably/ably-java
+* "iOS Objective-C and Swift SDK":https://github.com/ably/ably-cocoa
+* Experimental "W3C Push API":https://www.w3.org/TR/push-api/ compatible browser push notification support in the "Javascript SDK":https://github.com/ably/ably-js. "Get in touch":https://www.ably.io/contact for access to this experimental feature
+
+All Ably client library SDKs, that adhere to the "v1.1 specification support":/client-lib-development-guide/features/, support "push publishing":/general/push/publish and "push admin":/general/push/admin functionality.
+
+"See the list of client libraries available for download":https://www.ably.io/download
+
+h3(#features). Key features
+
+* General Availability Support for Android and iOS
+* Experimental support for Chrome, Firefox and Opera. Apple's Safari Notifications planned.
+* Custom notification formats and badges for iOS and Android. Browsers receive the notification in a Web Worker which is in turn responsible for presenting a visual notification to the user (this is an experimental feature).
+* Both visual notifications and data payloads can be sent to mobile devices.
+* Any number of mobile and browser devices can be registered on pub/sub channels. Each time a message is published with a push notification payload, Ably will ensure that all registered devices receive the push notification in near realtime.
+* Scale to millions of devices simultaneously by leveraging Ably's global platform.
+* User-centric device registration allowing devices to be grouped by user ("@clientId@":/realtime/authentication#identified-clients)
+* Filters can be applied to notifications such as client ID, connection ID or device type, or alternatively you can push message directly to devices or users via our API.
+* Realtime metrics for your delivered and undelivered push notifications.
+
+h2(#tutorials). Tutorials
+
+If you wish to see step by step instructions to set up, send and receive push notifications on your mobile devices, you can checkout our "tutorials for iOS and Android, with both direct device registration and registration via server":/tutorials#tut-push-notifications examples.
+
+h2(#smart-notifications). Smart Notifications (Not yet released)
+
+Ably's Smart Notifications will offer a less intrusive and more effective way to notify your users with native iOS, Android and browser notifications. Instead of delivering "dumb" push notifications to your users when you wish to get their attention, Ably allows you to deliver messages based on a user's connection state, their active device and their current context within your application. This means you can send less but more effective notifications to your users. "Find out more":https://blog.ably.io/smart-notifications-the-next-evolution-of-messaging-6dcb24bf857.
+
+"Get in touch":/https://www.ably.io/contact to find out more.
+
+h3(#how-it-works). How do Smart Notifications work?
+
+Each Ably "pub/sub channel":/core-features/channels can have one or more devices registered to it, and every device registered can be assigned a client identifier (i.e. they are an "identified client in the Ably system":/realtime/authentication/#identified-clients).
+
+When a message is published on a "pub/sub channel":/core-features/channels, a smart notification payload and rules can optionally be included with the message. When the Ably service receives a message with a smart notification payload, it will do one of two things:
+
+* If the notification is configured as a "dumb" push notification, it will deliver the push notification to the relevant devices associated with the pub/sub channel. This is a common pattern with other vendors and it assumes that if the registered device app is not active, a visual push notification will be displayed, and if the app is open and active, the visual push notification will be suppressed. In the case of a data push notification, it will be always be delivered and no visual indicator is shown to the user.
+* If the notification is configured as a "smart" push notification, then Ably will process the rules for each smart notification in a user-centric way, such that the user is notified in the most effective way on the most relevant device based on their current context. For example, if a user has your mobile app installed but also uses your web app during office hours, and a notification is sent whilst the user is currently using the web app, then a visual notification can be shown only on the open web app, and the unnecessary duplicate push notification to their device can be suppressed. If however, after some configured time, the user has not responded to the message in the web app, a follow up native mobile push notification could be triggered automatically by Ably.
+
+"Find out why we believe smart notifications will be the next evolution of messaging":https://blog.ably.io/smart-notifications-the-next-evolution-of-messaging-6dcb24bf857.
+
+"Get in touch if you'd like to be notified when Smart Notifications go into beta":https://www.ably.io/contact
diff --git a/content/general/versions/v1.1/push/activate-subscribe.textile b/content/general/versions/v1.1/push/activate-subscribe.textile
new file mode 100644
index 0000000000..c8dab31333
--- /dev/null
+++ b/content/general/versions/v1.1/push/activate-subscribe.textile
@@ -0,0 +1,456 @@
+---
+title: Push Notifications - Device activation and subscription
+section: general
+index: 44
+hide_from_nav: true
+api_separator:
+ Tutorials:
+ - Push Tutorials:/tutorials#tut-push-notifications
+languages:
+ - android
+ - swift
+ - objc
+jump_to:
+ Help with:
+ - Prerequisites for push#prerequisites
+ - Platform installation#platform-install
+ - Activating push on your device#device-activation
+ - Subscribing to push notifications#subscribing
+---
+
+Every device that will receive push notifications must activate itself with the local operating system or framework, and hook into the push notification services that the underlying platform provides. This functionality is platform-specific and can also vary considerably across not just platforms, but also across the push services that operate on those platforms such as GCM and FCM, both of which are available on the Android platform.
+
+The Ably client libraries aim to abstract away this complexity and platform-specific behaviour by providing a consistent API for device activation, maintenance of the device registration, and for subscription to Ably channels for receiving push notifications.
+
+
+
+
+
+The client libraries also provide a set of admin functionality that is typically used server-side (in a trusted environment) to manage all devices and push notification delivery and subscriptions. You can find out more in the "push admin documentation":/general/push/admin.
+
+In this section, we will run you through all of the features that a push notification device has available to it.
+
+h2(#prerequisites). Prerequisites
+
+Before you can configure your devices to receive push notifications, you must first enable push in your Ably app by adding the third party push service credentials and/or certificates to your app push dashboard. These credentials are then used by Ably to authenticate with the respective third party push service (such as APNs) and delivery all queued notifications.
+
+If you have not already done so, you can sign up for a free account with "Apple's Push Notification service":https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html and Google's "Firebase Cloud Messaging service":https://firebase.google.com/docs/cloud-messaging/.
+
+h2(#platform-install). Platform installation
+
+p(tip). Whilst platform installation is platform-specific, all subsequent Ably API calls are generally portable. Be sure to choose a language above that you wish to see the documentation and code examples in.
+
+Before you can activate your push device or receive push notifications, you must first plug in Ably to the underlying OS or platform. Once Ably is plugged in, all subsequent API interactions you have will be with the Ably Realtime library API which is as consistent as possible across all platforms. By providing a consistent API interface across all platforms, we aim to ensure implementation is simpler and more predictable regardless of the platform you are integrating Ably with.
+
+Installation, however, is platform-specific and as such, instructions for each platform and service are provided below:
+
+h3. Install Ably for Google Firebase Cloud Messaging on Android
+
+As with any Firebase-enabled app, you need to include one or more services within your app to handle interactions with Firebase. You need to have a service that extends "FirebaseMessagingService":https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessagingService (and overrides "@onMessageReceived()@":https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessagingService) in order to handle incoming push messages; the code that handles incoming push messages does not need to interact with Ably in any way.
+
+The second requirement is that your app integrates with Firebase to handle registration token notifications, and these notifications do need to be passed to Ably; the registration token is used by Ably's servers to be able to push messages to each specific device. You can be notified of token updates either by overriding "@onNewToken()@":https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessagingService in your derived "@FirebaseMessagingService@":https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessagingService, or by overriding "@onTokenRefresh()@":https://firebase.google.com/docs/reference/android/com/google/firebase/iid/FirebaseInstanceIdService in a service that derives from "@FirebaseInstanceIdService@":https://firebase.google.com/docs/reference/android/com/google/firebase/iid/FirebaseInstanceIdService.
+
+Registration tokens must be notified to the Ably library by calling @AblyFirebaseInstanceIdService.onNewRegistrationToken()@. If deriving from "@FirebaseInstanceIdService@":https://firebase.google.com/docs/reference/android/com/google/firebase/iid/FirebaseInstanceIdService directly, for example, your class should end up looking more or less like this:
+
+```[android]
+public class MyRegistrationTokenService extends FirebaseInstanceIdService {
+ @Override
+ public void onTokenRefresh() {
+ // Get updated InstanceID token.
+ String token = FirebaseInstanceId.getInstance().getToken();
+ // Notify Ably
+ AblyFirebaseInstanceIdService.onNewRegistrationToken(this, token);
+ }
+}
+```
+
+h3. Install Ably for Apple Push Notifications on iOS
+
+If you haven't yet, you should first "set up APNs":https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html in your app.
+
+After setting up APNs, you should have a couple of methods in your @UIApplicationDelegate@: "@application(_:didRegisterForRemoteNotificationsWithDeviceToken:)@":https://developer.apple.com/reference/uikit/uiapplicationdelegate/1622958-application"@application:didRegisterForRemoteNotificationsWithDeviceToken:@":https://developer.apple.com/reference/uikit/uiapplicationdelegate/1622958-application?language=objc and "@application(_:didFailToRegisterForRemoteNotificationsWithError:)@":https://developer.apple.com/reference/uikit/uiapplicationdelegate/1622962-application"@application:didFailToRegisterForRemoteNotificationsWithError:@":https://developer.apple.com/reference/uikit/uiapplicationdelegate/1622962-application?language=objc. @ARTPush@ has two corresponding methods that you should call from yours, passing to them also an @ARTRest@ or @ARTRealtime@ instance, configured with the authentication setup and other options you need.
+
+```[objc]
+// In your UIApplicationDelegate class:
+- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
+ [ARTPush didRegisterForRemoteNotificationsWithDeviceToken:deviceToken realtime:[self getAblyRealtime]];
+}
+
+- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
+ [ARTPush didFailToRegisterForRemoteNotificationsWithError:error realtime:[self getAblyRealtime]];
+}
+
+- (ARTRealtime *)getAblyRealtime {
+ ARTClientOptions *options = [[ARTClientOptions alloc] init];
+ // Set up options; API key or auth URL, etc.
+ return [[ARTRealtime alloc] initWithOptions: options];
+}
+```
+```[swift]
+// In your UIApplicationDelegate class:
+func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
+ ARTPush.didRegisterForRemoteNotifications(withDeviceToken: deviceToken, realtime: self.getAblyRealtime())
+}
+
+func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
+ ARTPush.didFailToRegisterForRemoteNotificationsWithError(error, realtime: self.getAblyRealtime())
+}
+
+func getAblyRealtime() -> ARTRealtime {
+ var options = ARTClientOptions()
+ // Set up options; API key or auth URL, etc.
+ return ARTRealtime(options: options)
+}
+```
+
+h2(#device-activation). Activating push on your device
+
+Activating a device for push notifications and registering it with Ably is commonly performed entirely from the device. However, it is possible to separate the concerns such that activation with the underlying platform is performed on the device, and registration of that activated device with Ably is performed using your own servers. This latter pattern is more commonly used when you want to minimize the capabilities assigned to an untrusted device. "Find out how to register the device from your servers":#activation-from-server
+
+
+
+
+
+In the following example, we will both activate the device with the underlying platform and register the device with Ably from the device itself.
+
+h3. Activate the device for push with @push.activate@
+
+If you want to start receiving push notifications from Ably (e.g. from your main activityyour @UIApplicationDelegate@), you need to first call "@AblyRealtime.push.activate@@ARTRealtime.push.activate@":#activate which will *register the device for push* by doing the following on your behalf:
+
+* Ensure the Ably client is authenticated
+* Generate a unique identifier for this device and store this in local storage
+* Activate the device for push notifications with the underlying OS or platform and obtain a unique identifier for the device as a push recipient. For example, in FCM this is described as a "@registration token@":https://firebase.google.com/docs/cloud-messaging/android/client#sample-register, and in APNs this is described as a "@device token@":https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html
+* Register the local device with Ably using the device's unique identifier, platform-specific details such as form factor and OS, and the push recipient details to receive push notifications. This in turn ensures Ably can reach this device and deliver push notifications
+* Store the "@deviceIdentityToken@":#local-device from the response from Ably in local storage so that subsequent requests to Ably to update push recipient details are authenticated as being from the device in question.
+
+Please note that the effects of calling "@activate@":#activate outlives the current process. Once called, the device will stay registered even after the application is closed, and up until "@deactivate@":#deactivate is called. "@activate@":#activate is idempotent: calling it again when device is already activated has the sole effect of calling its callback.
+
+```[android]
+AblyRealtime ably = getAblyRealtime();
+ably.setAndroidContext(context)
+ably.push.activate();
+```
+```[objc]
+ARTRealtime *ably = [self getAblyRealtime];
+[ably.push activate];
+```
+```[swift]
+let ably = self.getAblyRealtime()
+ably.push.activate()
+```
+
+Please bear in mind that in order for the client to register itself automatically with Ably, it needs to be authenticated and have "the required @push-subscribe@ capability":#required-capabilities. If you would prefer to delegate registration of the push device to your own servers and not allow devices to register themselves directly with Ably, then see the section "how to register devices from your server":#activation-from-server. You can also check "our recommendations":#server-vs-direct-registration for choosing either registration strategy.
+
+h3. Register for callback from @activate@
+
+Once "@activate@":#activate is called, the aforementioned activation and registration process kicks off in the background. Once completed, a callback will be invoked if registered. We recommend you set up this callback so that you will be notified when push activation has succeeded or failed. Once the device has successfully been activated, you can then start subscribing for push notifications on channels and receiving push notifications via Ably.
+
+When the activation process is completed, Ably will send a broadcast through the application's "@LocalBroadcastManager@":https://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager. You should listen for a broadcast with action @io.ably.broadcast.PUSH_ACTIVATE@call your @(void)didActivateAblyPush:(nullable ARTErrorInfo *)error@@didActivateAblyPush(error: ARTErrorInfo?)@ method from your @ARTPushRegistererDelegate@ implementation as follows:
+
+```[android]
+LocalBroadcastManager.getInstance(context.getApplicationContext()).registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ErrorInfo error = IntentUtils.getErrorInfo(intent);
+ if (error != null) {
+ // Handle error
+ return;
+ }
+ // Subscribe to channels / listen for push etc.
+ }
+}, new IntentFilter("io.ably.broadcast.PUSH_ACTIVATE"));
+
+ably.push.activate(context);
+```
+```[objc]
+// Add the activate method from 'ARTPushRegistererDelegate' to your 'UIApplicationDelegate' class:
+- (void)didActivateAblyPush:(nullable ARTErrorInfo *)error {
+ if (error) {
+ // Handle error
+ return;
+ }
+ // Subscribe to channels / listen for push etc.
+}
+
+// Call activate, which will call the delegate method when done:
+[ably.push activate];
+```
+```[swift]
+// Add the activate method from 'ARTPushRegistererDelegate' to your 'UIApplicationDelegate' class:
+func didActivateAblyPush(_ error: ARTErrorInfo?) {
+ if let error = error {
+ // Handle error
+ return
+ }
+ // Subscribe to channels / listen for push etc.
+}
+
+// Call activate, which will call the delegate method when done:
+ably.push.activate()
+```
+
+h2(#subscribing). Subscribe for push notifications
+
+Before you subscribe to a channel for push, make sure its "channel namespace is configured to explicitly enable push notifications":https://support.ably.io/solution/articles/3000030057-what-are-channel-rules-and-how-can-i-use-them-in-my-app. By default, push notifications on channels are disabled.
+
+There are two ways a device can be subscribed to a channel: directly "by its device ID":#subscribing-device-id, or indirectly "by its associated client ID":#subscribing-client-id.
+
+h3(#subscribing-device-id). Subscribing by device ID
+
+A **device ID** uniquely identifies a device within Ably's services and is assigned automatically at the time the device is activated.
+
+If your client "has the push-subscribe capabilities":#push-capabilities, you can do the following:
+
+```[objc]
+[[realtime.channels get:@"pushenabled:foo"].push subscribeDevice:^(ARTErrorInfo *error) {
+ // Check error.
+}];
+```
+```[swift]
+realtime.channels.get("pushenabled:foo").push.subscribeDevice { error
+ // Check error.
+}
+```
+```[android]
+realtime.channels.get("pushenabled:foo").push.subscribeDevice(context);
+
+// or
+
+realtime.channels.get("pushenabled:foo").push.subscribeDeviceAsync(context, new CompletionListener() {
+ @Override
+ public void onSuccess() {}
+
+ @Override
+ public void onError(ErrorInfo errorInfo) {
+ // Handle error.
+ }
+});
+```
+
+If your client doesn't have the @push-subscribe@ permissions, you should communicate the device ID to your server so that it can subscribe on the device's behalf. You can find your unique device ID at "@ARTRealtime.device.id@@AblyRealtime.device().id@":#device-details. The server must then "use the push admin API":/general/push/admin to subscribe the device.
+
+h3(#subscribing-client-id). Subscribing by client ID
+
+When a device is registered, it can be associated with a "client ID":/realtime/authentication/#identified-clients. "@AblyRealtime.push.activate@@ARTRealtime.push.activate@":#activate takes the client ID from the @AblyRealtime@ instance.
+
+You can subscribe all devices associated with a client ID to a channel in a single operation; that is, create a subscription by client ID. New device registrations associated to that client ID will also be subscribed to the channel, and if a device registration is no longer associated with that client ID, it will also stop being subscribed to the channel (unless it's also "subscribed directly by device ID":#subscribing-device-id).
+
+To subscribe your @AblyRealtime@ instance's client ID to a channel:
+
+```[objc]
+[[realtime.channels get:@"pushenabled:foo"].push subscribeClient:^(ARTErrorInfo *error) {
+ // Check error.
+}];
+```
+```[swift]
+realtime.channels.get("pushenabled:foo").push.subscribeClient { error
+ // Check error.
+}
+```
+```[android]
+realtime.channels.get("pushenabled:foo").push.subscribeClient();
+
+// or
+
+realtime.channels.get("pushenabled:foo").push.subscribeClientAsync(new CompletionListener() {
+ @Override
+ public void onSuccess() {}
+
+ @Override
+ public void onError(ErrorInfo errorInfo) {
+ // Handle error.
+ }
+});
+```
+
+Alternatively, if you want to subscribe a different client ID not currently associated with the currently authenticated realtime instance, you can "use the admin API":/general/push/admin.
+
+h2(#push-capabilities). Push capabilities
+
+These are the "capabilities":/core-features/authentication/#capabilities-explained necessary to perform push operations:
+
+* @push-subscribe@: Register and deregister the local device, and subscribe and unsubscribe the local device to channels for push notifications.
+* @push-admin@: Register, update and deregister any device registration, and subscribe and unsubscribe to channels for push notifications. Publish push notification using the @POST /push/publish@ endpoint (@AblyRealtime.push.admin.publish@ method).
+
+Typically, client devices subscribing for push will either have @push-subscribe@ privileges or "delegate operations to a server":#activation-from-server with @push-admin@ privileges.
+
+h2(#activation-from-server). Activating devices from your server
+
+The default for @AblyRealtime.push.activate@ is to register the device with Ably directly from the device, but you can instead delegate that to your server. Don't forget to register the device using the "push admin API":/general/push/admin in your server.
+
+
+
+
+
+
+blang[objc,swift].
+ For this, your @UIApplicationDelegate@ must implement these optional methods from @ARTPushRegistererDelegate@:
+
+ ```[objc]
+ - (void)ablyPushCustomRegister:(ARTErrorInfo *)error
+ deviceDetails:(ARTDeviceDetails *)deviceDetails
+ callback:(void (^)(ARTDeviceIdentityTokenDetails * _Nullable, ARTErrorInfo * _Nullable))callback {
+ if (error) {
+ // Handle error.
+ callback(nil, error);
+ return;
+ }
+
+ [self registerThroughYourServer:deviceDetails callback:callback];
+ }
+
+ - (void)ablyPushCustomDeregister:(ARTErrorInfo *)error d
+ deviceId:(ARTDeviceId *)deviceId
+ callback:(void (^)(ARTErrorInfo * _Nullable))callback {
+ if (error) {
+ // Handle error.
+ callback(nil, error);
+ return;
+ }
+
+ [self unregisterThroughYourServer:deviceDetails callback:callback];
+ }
+ ```
+ ```[swift]
+ func ablyPushCustomRegister(_ error: ARTErrorInfo?, deviceDetails: ARTDeviceDetails, callback: @escaping (ARTDeviceIdentityTokenDetails?, ARTErrorInfo?) -> Void) {
+ if let e = error {
+ // Handle error.
+ callback(nil, e)
+ return
+ }
+
+ self.registerThroughYourServer(deviceDetails: deviceDetails, callback: callback)
+ }
+
+ func ablyPushCustomDeregister(_ error: ARTErrorInfo?, deviceId: String, callback: ((ARTErrorInfo?) -> Void)? = nil) {
+ if let e = error {
+ // Handle error.
+ callback(nil, e)
+ return
+ }
+
+ self.unregisterThroughYourServer(deviceDetails: deviceDetails, callback: callback)
+ }
+ ```
+
+blang[android].
+ For this, you need to communicate back and forth with the Ably library via the application's "@LocalBroadcastManager@":https://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.
+
+ First, make sure you pass @true@ as the @useCustomRegisterer@ parameter to "@activate@":#activate (and for "@deactivate@":#deactivate).
+
+ ```[android]
+ ably.push.activate(context, true);
+ ably.push.deactivate(context, true);
+ ```
+
+ The Ably library will then broadcast an @io.ably.broadcast.PUSH_REGISTER_DEVICE@ action when it needs you to register from your server, and @io.ably.broadcast.PUSH_DEREGISTER_DEVICE@ when it needs you to deregister. You must configure a listener to those actions in your application's @AndroidManifest.xml@, and from it answer back with a @PUSH_DEVICE_REGISTERED@ or @PUSH_DEVICE_DEREGISTERED@, like this:
+
+ ```[xml]
+
+
+
+
+h2(#channel-broadcast). Channel-based broadcasting
+
+The model for delivering push notifications to devices over channels is intentionally very similar to how normal messages are delivered to realtime subscribers using Ably's "pub/sub channels":/core-features/channels. For example, a normal message published on an Ably channel is broadcast immediately to all subscribers of that channel. When broadcasting push notifications on channels, however, the process is the same with the exception that the subscribers (devices receiving push notifications) are registered in advance using our API and the message itself must contain an *extra push notification payload* that specifies the optional visual format and optional data payload of the native push notification.
+
+Therefore, the process for delivering push notifications to devices using channel-based broadcasting is as follows:
+
+# Subscribe one or more devices to one or more channels
+# Publish a message on those channels with a *push notification payload*
+
+Please note that a push notification published on a channel will only be delivered to a device if:
+
+* the *extra push notification* payload is included in the published message
+* a "channel rule":https://support.ably.io/solution/articles/3000030057-what-are-channel-rules-and-how-can-i-use-them-in-my-app is configured explicitly enabling push notifications on that channel
+* the device is subscribed to the channel
+* the push notification payload is compatible with the subscribed push notification device
+
+h3(#channel-broadcast-example). Channel-based push notification example
+
+Push notifications are sent as special payloads alongside "a normal Ably message":/realtime/messages in the @extras@ field. The @extras@ field is an object and must contain a @push@ attribute object with the push payload details.
+
+```[objc]
+ARTMessage *message = [[ARTMessage alloc] initWithName:@"example" data:@"rest data"];
+message.extras = @{
+ @"push": @{
+ @"notification": @{
+ @"title": @"Hello from Ably!",
+ @"body": @"Example push notification from Ably."
+ },
+ @"data": @{
+ @"foo": @"bar",
+ @"baz": @"qux"
+ }
+ }
+};
+[[rest.channels get:@"pushenabled:foo"] publish:@[message]];
+```
+```[swift]
+var message = ARTMessage(name: "example", data: "rest data")
+message.extras = [
+ "push": [
+ "notification": [
+ "title": "Hello from Ably!",
+ "body": "Example push notification from Ably."
+ ],
+ "data": [
+ "foo": "bar",
+ "baz": "qux"
+ ]
+ ]
+]
+rest.channels.get("pushenabled:foo").publish([message])
+```
+```[java]
+Message message = new Message("example", "rest data");
+message.extras = io.ably.lib.util.JsonUtils.object()
+ .add("push", io.ably.lib.util.JsonUtils.object()
+ .add("notification", io.ably.lib.util.JsonUtils.object()
+ .add("title", "Hello from Ably!")
+ .add("body", "Example push notification from Ably."))
+ .add("data", io.ably.lib.util.JsonUtils.object()
+ .add("foo", "bar")
+ .add("baz", "qux")));
+
+rest.channels.get("pushenabled:foo").publish(message);
+```
+```[ruby]
+extras = {
+ push: {
+ notification: {
+ title: 'Hello from Ably!',
+ body: 'Example push notification from Ably.'
+ },
+ data: {
+ foo: 'bar',
+ baz: 'qux'
+ }
+ }
+}
+
+channel = rest.channels.get('pushenabled:foo')
+channel.publish('example', 'data', extras: extras)
+```
+```[jsall]
+var extras = {
+ push: {
+ notification: {
+ title: 'Hello from Ably!',
+ body: 'Example push notification from Ably.'
+ },
+ data: {
+ foo: 'bar',
+ baz: 'qux'
+ }
+ }
+};
+
+var channel = rest.channels.get('pushenabled:foo');
+channel.publish({ name: 'example', data: 'data', extras: extras });
+```
+```[python]
+extras = {
+ 'push': {
+ 'notification': {
+ 'title': 'Hello from Ably!',
+ 'body': 'Example push notification from Ably.'
+ }
+ },
+}
+
+channel = rest.channels.get('pushenabled:foo')
+channel.publish({ 'name': 'example', 'data': 'data', 'extras': extras });
+```
+```[php]
+$msg = new Message();
+$msg->name = 'name';
+$msg->data = 'data';
+$msg->extras = [
+ 'push' => [
+ 'notification' => [
+ 'title' => 'Hello from Ably!',
+ 'body' => 'Example push notification from Ably.'
+ ]
+ ]
+];
+$channel = $rest->channels->get('pushenabled:foo');
+$channel->publish($msg);
+```
+
+h2(#direct-publishing). Direct publishing
+
+Ably provides an API that allows native push notifications to be delivered directly to:
+
+* Devices identified by their unique device ID
+* Devices identified by their assigned "@clientId@":/realtime/authentication#identified-clients
+* Devices identified by their native recipient attributes such as their unique @registrationToken@ in the case of GCM, @deviceToken@ in the case of APNS, or @targetUrl@ and @encryptionKey@ in the case of a Web device (*experimental*). This is particularly useful when migrating to Ably with existing push notification target devices.
+
+See the "push admin publish documentation":/general/push/admin#publish for the client library API details, and the "raw push publish REST API documentation":/rest-api#push-publish for information on the underlying direct publishing endpoint used by the client libraries.
+
+h3(#direct-publishing-device-id-example). Publish to a device ID example
+
+```[jsall]
+var recipient = {
+ deviceId: 'xxxxxxxxxxx'
+};
+var data = {
+ notification: {
+ title: 'Hello from Ably!',
+ body: 'Example push notification from Ably.'
+ }
+};
+
+rest.push.admin.publish(recipient, data);
+```
+```[ruby]
+recipient = {
+ device_id: 'xxxxxxxxxxx'
+}
+data = {
+ notification: {
+ title: 'Hello from Ably!',
+ body: 'Example push notification from Ably.'
+ }
+}
+
+rest.push.admin.publish(recipient, data)
+```
+```[objc]
+ARTPushRecipient *recipient = @{
+ @"deviceId": @"xxxxxxxxxxxxxx"
+};
+ARTJsonObject *data = @{
+ @"notification": @{
+ @"title": @"Hello from Ably!",
+ @"body": @"Example push notification from Ably."
+ },
+ @"data": @{
+ @"foo": @"bar",
+ @"baz": @"qux"
+ }
+};
+[rest.push.admin publish:recipient data:data callback:^(ARTErrorInfo *error)
+```
+```[java]
+Message message = new Message("example", "rest data");
+message.extras = io.ably.lib.util.JsonUtils.object()
+ .add("push", io.ably.lib.util.JsonUtils.object()
+ .add("notification", io.ably.lib.util.JsonUtils.object()
+ .add("title", "Hello from Ably!")
+ .add("body", "Example push notification from Ably."))
+ .add("data", io.ably.lib.util.JsonUtils.object()
+ .add("foo", "bar")
+ .add("baz", "qux")));
+
+rest.push.admin.publish(arrayOf(Param("deviceId", deviceId)), message);
+```
+```[swift]
+let recipient: [String: Any] = [
+ "deviceId": "xxxxxxxxxxxxxx"
+]
+let data: [String: Any] = [
+ "notification": [
+ "title": "Hello from Ably!",
+ "body": "Example push notification from Ably."
+ ],
+ "data": [
+ "foo": "bar",
+ "baz": "qux"
+ ]
+]
+rest.push.admin.publish(recipient, data: data)
+```
+```[python]
+recipient = {'deviceId': 'xxxxxxxxxxxx'}
+message = {
+ 'push': {
+ 'notification': {
+ 'title': 'Hello from Ably!',
+ 'body': 'Example push notification from Ably.'
+ }
+ }
+}
+
+rest.push.admin.publish(recipient, message)
+```
+```[php]
+$recipient = [ 'deviceId' => 'xxxxxxxxxxxx' ];
+$data = [ 'push' =>
+ [ 'notification' =>
+ [ 'title' => 'Hello from Ably!',
+ 'body' => 'Example push notification from Ably.'
+ ]
+ ]
+ ];
+$rest->push->admin->publish( $recipient, $data );
+```
+
+h3(#direct-publishing-client-id-example). Publish to a client ID example
+
+```[jsall]
+var recipient = {
+ clientId: 'bob'
+};
+var notification = {
+ notification: {
+ title: 'Hello from Ably!',
+ body: 'Example push notification from Ably.'
+ }
+};
+
+rest.push.admin.publish(recipient, notification);
+```
+```[ruby]
+recipient = {
+ client_id: 'bob'
+}
+notification = {
+ notification: {
+ title: 'Hello from Ably!',
+ body: 'Example push notification from Ably.'
+ }
+}
+
+rest.push.admin.publish(recipient, notification)
+```
+```[objc]
+ARTPushRecipient *recipient = @{
+ @"clientId": @"xxxxxxxxxxxxxx"
+};
+ARTJsonObject *data = @{
+ @"notification": @{
+ @"title": @"Hello from Ably!",
+ @"body": @"Example push notification from Ably."
+ },
+ @"data": @{
+ @"foo": @"bar",
+ @"baz": @"qux"
+ }
+};
+[rest.push.admin publish:recipient data:data callback:^(ARTErrorInfo *error)
+```
+```[swift]
+let recipient: [String: Any] = [
+ "clientId": "xxxxxxxxxxxxxx"
+]
+let data: [String: Any] = [
+ "notification": [
+ "title": "Hello from Ably!",
+ "body": "Example push notification from Ably."
+ ],
+ "data": [
+ "foo": "bar",
+ "baz": "qux"
+ ]
+]
+rest.push.admin.publish(recipient, data: data)
+```
+```[java]
+Message message = new Message("example", "rest data");
+message.extras = io.ably.lib.util.JsonUtils.object()
+ .add("push", io.ably.lib.util.JsonUtils.object()
+ .add("notification", io.ably.lib.util.JsonUtils.object()
+ .add("title", "Hello from Ably!")
+ .add("body", "Example push notification from Ably."))
+ .add("data", io.ably.lib.util.JsonUtils.object()
+ .add("foo", "bar")
+ .add("baz", "qux")));
+
+rest.push.admin.publish(arrayOf(Param("clientId", clientId)), message);
+```
+```[python]
+recipient = {'clientId': 'xxxxxxxxxxxx'}
+message = {
+ 'push': {
+ 'notification': {
+ 'title': 'Hello from Ably!',
+ 'body': 'Example push notification from Ably.'
+ }
+ }
+}
+
+rest.push.admin.publish(recipient, message)
+```
+```[php]
+$recipient = [ 'clientId' => 'xxxxxxxxxxx' ];
+$data = [ 'push' =>
+ [ 'notification' =>
+ [ 'title' => 'Hello from Ably!',
+ 'body' => 'Example push notification from Ably.'
+ ]
+ ]
+ ];
+$rest->push->admin->publish( $recipient, $data );
+```
+
+h3(#direct-publishing-client-id-example). Publish direct to a native recipient example
+
+```[ruby]
+recipient = {
+ transport_type: 'apns',
+ device_token: 'xxxxxxxxxx'
+}
+notification = {
+ notification: {
+ title: 'Hello from Ably!',
+ body: 'Example push notification from Ably.'
+ }
+}
+
+rest.push.admin.publish(recipient, notification)
+```
+```[jsall]
+var recipient = {
+ transportType: 'apns',
+ deviceToken: 'xxxxxxxxxx'
+};
+var notification = {
+ notification: {
+ title: 'Hello from Ably!',
+ body: 'Example push notification from Ably.'
+ }
+};
+
+rest.push.admin.publish(recipient, notification);
+```
+```[objc]
+ARTPushRecipient *recipient = @{
+ @"transportType": @"apns",
+ @"deviceToken": @"XXXXXXXX"
+};
+
+ARTJsonObject *data = @{
+ @"notification": @{
+ @"title": @"Hello from Ably!",
+ @"body": @"Example push notification from Ably."
+ },
+ @"data": @{
+ @"foo": @"bar",
+ @"baz": @"qux"
+ }
+};
+[rest.push.admin publish:recipient data:data callback:^(ARTErrorInfo *error)
+```
+```[swift]
+let recipient: [String: Any] = [
+ "transportType": "apns",
+ "deviceToken": "XXXXXXXX"
+]
+
+let data: [String: Any] = [
+ "notification": [
+ "title": "Hello from Ably!",
+ "body": "Example push notification from Ably."
+ ],
+ "data": [
+ "foo": "bar",
+ "baz": "qux"
+ ]
+]
+rest.push.admin.publish(recipient, data: data)
+```
+```[java]
+Message message = new Message("example", "rest data");
+message.extras = io.ably.lib.util.JsonUtils.object()
+ .add("push", io.ably.lib.util.JsonUtils.object()
+ .add("notification", io.ably.lib.util.JsonUtils.object()
+ .add("title", "Hello from Ably!")
+ .add("body", "Example push notification from Ably."))
+ .add("data", io.ably.lib.util.JsonUtils.object()
+ .add("foo", "bar")
+ .add("baz", "qux")));
+
+rest.push.admin.publish(arrayOf(Param("transportType", "apns"), Param("deviceToken", deviceToken)), message);
+```
+```[python]
+recipient = {'transportType': 'apns', 'deviceToken': 'XXXXXXX'}
+message = {
+ 'push': {
+ 'notification': {
+ 'title': 'Hello from Ably!',
+ 'body': 'Example push notification from Ably.'
+ }
+ }
+}
+
+rest.push.admin.publish(recipient, message)
+```
+```[php]
+$recipient = [ 'transportType' => 'apns', 'deviceToken' => 'XXXXXXX' ];
+$data = [ 'push' =>
+ [ 'notification' =>
+ [ 'title' => 'Hello from Ably!',
+ 'body' => 'Example push notification from Ably.'
+ ]
+ ]
+ ];
+$rest->push->admin->publish( $recipient, $data );
+```
+
+h2(#payload-structure). Push payload structure
+
+A push notification payload has a generic structure as follows:
+
+```[json]
+{
+ "notification": {
+ "title": | Ably field | +FCM | +APNs | +Web (*experimental*) | +
|---|---|---|---|
| @notification.title@ | +@notification.title@ | +@aps.alert.title@ | +@notification.title@ | +
| @notification.body@ | +@notification.body@ | +@aps.alert.body@ | +@notification.body@ | +
| @notification.icon@ | +@notification.icon@ | +Discarded. | +@notification.icon@ | +
| @notification.sound@ | +@notification.sound@ | +@aps.alert.sound@ | +@notification.sound@ | +
| @notification.collapseKey@ | +@collapse_key@ | +@aps.thread-id@ | +@notification.collapseKey@ | +
| @data@ | +@data@ | +Merged into root object. | +@data@ | +
+
+
+h3(#why). When should I use queues instead of pub/sub channels?
+
+**Queues** are more appropriate where:
+
+* "Work" needs to be distributed between your servers for each published message. For example, "work" could be to generate an image and attach it to an email when a message is published
+* Messages should be delivered to only one consumer regardless of how many consumers are listening for new messages
+* You need an architectural design to process realtime data that scales horizontally by simply adding more consumer "worker" servers
+* You want to consume realtime data from channels on your servers statelessly i.e. you do not want to keep track of which channels or clients are active or share state between your servers
+* You want a backlog of messages to build up if the consumers cannot process data quickly enough or if the consumers go offline
+* You can provision the queues you need in advance. For example, you may have one queue for chat messages and another for analytics events
+
+Please bear in mind that with the Ably platform all realtime data originates from pub/sub channels i.e. you never publish directly to a queue, you publish to a channel. If a queue rule exists that matches the channel name, then the message published will be automatically published into the designated queue. Therefore if you need to publish and consume data, you will have to publish data to channels over REST or Realtime protocols, and consume your data using an AMQP or STOMP client library.
+
+h2(#using). Using the Reactor Queues
+
+All Ably accounts have access to Reactor Queue functionality, however to get started you need to provision a physical queue and set up a queue rule to move data from channels into that queue.
+
+h3(#provisioning). Provisioning Reactor Queues
+
+Unlike "pub/sub channels":/realtime/channels that can exist in any datacenter and are provisioned on-demand by clients, queues need to be provisioned in advance and exist in one region.
+
+Queues are setup "within your app dashboard":https://support.ably.io/support/solutions/articles/3000030053-how-do-i-access-my-app-dashboard and you will need to configure:
+
+* A unique name for the queue. This name (along with the app ID prefixed automatically) will be used when consuming the queue from your queue client libraries.
+* The region that queue will be physically located in. Note that all queues exist across two datacenters in each region for high availability.
+* The TTL (time-to-live) for your messages. If the TTL period passes and a message has not been consumed from the queue, then the message is moved to the "dead letter queue":#deadletter
+* The max length for your queue which is the total number of messages that your queue can retain in memory and/or on disk. When the queue is considered full based on the max length, a message published to the queue will be accepted however the oldest message on that queue will be moved into the "dead letter queue":#deadletter to make room for the new message
+
+Please note that the total number of queues, TTL and max length for each queue is a limited based on your account type. "Find out more about account and package limits":https://support.ably.io/solution/articles/3000053845-do-you-have-any-connection-message-rate-or-other-limits-on-accounts.
+
+**"Follow step-by-step instructions to provision a queue now »":https://support.ably.io/solution/articles/3000062188-how-can-i-provision-a-new-message-queue**
+
+
+h3(#setup). Setting up queue rules
+
+Once you have provisioned a physical queue, you need to set up one or more queue rules to republish messages, presence events or channel events from pub/sub channels into a queue. Queue rules can either be used to publish to internal queues (hosted by Ably) or external external streams or queues (such as Kinesis, Kafka, RabbitMQ). Publishing to external streams or queues is part of our "Ably Reactor Firehose servers":https://www.ably.io/reactor which is only available to Enterprise customers.
+
+Queues rules are setup in the "Reactor tab":https://support.ably.io/support/solutions/articles/3000062196-how-can-i-set-up-a-queue-rule found "within your app dashboard":https://support.ably.io/support/solutions/articles/3000030053-how-do-i-access-my-app-dashboard. For internal queue rules you set up you will need to configure:
+
+* The **source** for the realtime data which is either:
+** **Messages** - messages are enqueued as soon as they are published on a channel;
+** **Presence events** - when clients enter, update their data, or leave channels, the presence event is enqueued; or
+** **Channel lifecycle events** - when a channel is opened (following the first client attaching to this channel) or closed (when there are no more clients attached to the channel), the lifecycle event is enqueued
+* An optional **channel filter** that allows you to filter which channels produce messages or events for your queues. Regular expressions are supported such as @^click_.*_mouse$@
+* The **encoding** for your message which is either JSON (the default text format) or "MsgPack":http://msgpack.org (a binary format)
+* Whether messages published to the queue are wrapped in an **envelope** or not. The default envelope that wraps all messages published to queues provides additional metadata such as the @channel@, @appId@, @site@, and @ruleId@. Non-enveloped messages contain only the payload (@data@ element of the message) and some metadata is provided in the message headers. "See examples of enveloped and non-enveloped messages":#enveloped.
+
+**"Follow step-by-step instructions to set up a queue rule now »":https://support.ably.io/solution/articles/3000062196**
+
+h3(#dashboard-stats). Queue dashboards and stats
+
+Provisioned queues are visible "in your app dashboard":https://support.ably.io/support/solutions/articles/3000030053-how-do-i-access-my-app-dashboard and provide near-realtime stats for the current state of each queue. See an example screenshot below:
+
+
+
+
+
+Whilst the queue dashboard stats show the current state of your queue, your app and account dashboard provide up-to-date live and historical stats for all messages published to your queues. See an example screenshot from an "app dashboard":https://support.ably.io/support/solutions/articles/3000030053-how-do-i-access-my-app-dashboard below:
+
+
+
+
+
+h3(#testing-rules). Testing your queue rules
+
+Once your "Reactor Queue":https://www.ably.io/reactor is provisioned, and your "Queue rules":https://support.ably.io/support/solutions/articles/3000062196-how-can-i-set-up-a-queue-rule are configured, there are a number of ways we recommend customers can debug the configured rules and queues:
+
+h4(#testing-dashboard). Checking queue dashboard stats
+
+Use the "dev console":https://support.ably.io/solution/articles/3000062195-do-you-have-a-debugging-or-development-console-for-testing to generate messages or events that match your queue rule. You can confirm messages are being delivered if the "Messages ready" count in your queue dashboard increases (see above). Note that the messages ready count won't increase if you have a client consuming messages from this queue.
+
+h4(#testing-cli). Using a CLI to consume messages
+
+Install a command line tool for consuming messages using the AMQP protocol to check that messages published on channels (using the dev console or from any other source) are being pushed into the queues based on the queue rules.
+
+You can install "Node AMQP Consume CLI":https://www.npmjs.com/package/amqp-consume-cli with:
+
+bc[sh]. npm install amqp-consume-cli -g
+
+Then you need to go to your app dashboard to retrieve an API key that has access to the queues (your root key will typically have access to subscribe to all queues). Then check the server endpoint, vhost and queue name (which is always prefixed with a scope which is your appId) from the queue dashboard (see above) and issue a command such as:
+
+bc[sh]. amqp-consume --queue-name [Name] \
+ --host [Server endpoint host] --port [Server endpoint port] \
+ --ssl --vhost shared --creds [your API key]
+
+Whenever a message is published to the queue you are subscribing to, the @amqp-consume@ tool will output the message details such as:
+
+```[sh]
+Message received
+Attributes: { contentType: 'application/json',
+ headers: {},
+ deliveryMode: 1,
+ timestamp: 1485914937984 }
+Data: {
+ "source":"channel.message",
+ "appId":"ael724",
+ "channel":"foo",
+ "site":"eu-west-1-A",
+ "ruleId":"cOOo9g",
+ "messages":[
+ {
+ "id":"vjzxPR-XK3:3:0",
+ "name":"event",
+ "connectionId":"vjzxPR-XK3",
+ "timestamp":1485914937909,
+ "data":"payload"
+ }
+ ]
+}
+```
+
+_Please note that the @messages@ attribute is an @Array@ so that future envelope options may allow messages to be bundled into a single envelope ("Reactor Events":/general/events can batch messages). However, with the current queue rule design, an envelope will only ever contain one message._
+
+h3(#consume-messages). Consuming messages from queues
+
+Consuming messages from Ably Reactor Message Queues is mostly the same as consuming from any other queue supporting AMQP or STOMP protocols. However, there a few tips below to avoid common pitfalls.
+
+h4(#consume-amqp). Using AMQP
+
+The AMQP protocol provides a rich set of functionality to amongst other things bind to exchanges, provision queues and configure routing. This functionality exists so that queues can be dynamically provisioned by clients and messages can be routed to these queues as required.
+
+However, unlike our pub/sub channels, queues are pre-provisioned via our queue dashboards and all routing is handled by the queue rules. As such, when subscribing to messages from the provisioned queues, you must not attempt to bind to an exchange or declare a queue as these requests will be rejected. Instead, you should subscribe directly to the queue you wish to consume messages from.
+
+Take the following queue as an example:
+
+
+
+
+In order to subscribe to messages from this queue you will need:
+
+- The queue name := @UATwBQ:example-queue@ which is made up of your app ID and the name you assigned to your queue
+- The host := @us-east-1-a-queue.ably.io@
+- The port := @5671@ which is the TLS port you consume from. We only support TLS connections for security reasons
+- The vhost := @shared@
+- The username := the part before the @:@ of "an API key":https://support.ably.io/solution/articles/3000030502-setting-up-and-managing-api-keys that has access to queues. For example, the username for an API key such as @APPID.KEYID:SECRET@ would be @APPID.KEYID@.
+- The password := the part after the @:@ of "the API key":https://support.ably.io/solution/articles/3000030502-setting-up-and-managing-api-keys. For example, the password for an API key such as @APPID.KEYID:SECRET@ would be @SECRET@.
+
+A simple example of subscribing to this queue in Node.js can be seen below:
+
+```[nodejs]
+const url = 'amqps://APPID.KEYID:SECRET@us-east-1-a-queue.ably.io/shared'
+amqp.connect(url, (err, conn) => {
+ if (err) { return handleError(err) }
+
+ /* Opens a channel for communication. The word channel is overloaded
+ and this has nothing to do with pub/sub channels */
+ conn.createChannel((err, ch) => {
+ if (err) { return handleError(err) }
+
+ /* Wait for messages published to the Ably Reactor queue */
+ ch.consume('UATwBQ:example-queue', (item) => {
+ let decodedEnvelope = JSON.parse(item.content)
+
+ /* The envelope messages attribute will only contain one message. However,
+ in future versions, we may allow optional bundling of messages into a
+ single queue message and as such this attribute is an Array to support
+ that in future */
+ let messages = Ably.Realtime.Message.fromEncodedArray(decodedEnvelope.messages)
+ messages.forEach((message) => {
+ actionMessage(message)
+ })
+
+ /* ACK (success) so that message is removed from queue */
+ ch.ack(item)
+ })
+ })
+})
+```
+
+Please note:
+
+* In the example above, the queue rule has been configured to wrap each message in an envelope (the default setting). Therefore the first step is to parse the envelope JSON. See details on "enveloped messages":#enveloped below.
+* The @Message.fromEncodedArray@ method is used to decode the message(s) and return an array of "@Message@":/realtime/types#message objects. We strongly recommend you use this method if your client library supports it to ensure messages are decoded correctly and portable across all platforms.
+* Whilst the code above can handle multiple messages per envelope, we currently only support one message per envelope. The @messages@ attribute is an @Array@ so that in future we could optionally support message bundling.
+
+**"See our tutorials section for a few step-by-step examples using a Reactor Queue with AMQP »":/tutorials**
+
+h4(#consume-stomp). Using STOMP
+
+The STOMP protocol is a simple text-based protocol designed for working with message-oriented middleware. It provides an interoperable wire format that allows STOMP clients to talk with any message broker support the STOMP protocol and as such is a good fit for use with Ably Reactor Queues.
+
+Assuming the following queue has been set up, we'll show you a simple example of subscribing to a STOMP queue:
+
+
+
+
+In order to subscribe to messages from this queue you will need:
+
+- The queue name := @UATwBQ:example-queue@ which is made up of your app ID and the name you assigned to your queue
+- The host := @us-east-1-a-queue.ably.io@
+- The port := @61614@ which is the STOMP TLS port you consume from (the port in the screenshot above is for AMQP). We only support TLS connections for security reasons
+- The vhost := @shared@
+- The username := the part before the @:@ of "an API key":https://support.ably.io/solution/articles/3000030502-setting-up-and-managing-api-keys that has access to queues. For example, the username for an API key such as @APPID.KEYID:SECRET@ would be @APPID.KEYID@.
+- The password := the part after the @:@ of "the API key":https://support.ably.io/solution/articles/3000030502-setting-up-and-managing-api-keys. For example, the password for an API key such as @APPID.KEYID:SECRET@ would be @SECRET@.
+
+A simple example of subscribing to this queue in Node.js can be seen below:
+
+```[nodejs]
+const connectOptions = {
+ 'host': 'us-east-1-a-queue.ably.io',
+ 'port': 61614, /* STOMP TLS port */
+ 'ssl': true,
+ 'connectHeaders':{
+ 'host': 'shared',
+ 'login': 'APPID.KEYID',
+ 'passcode': 'SECRET'
+ }
+}
+
+Stompit.connect(connectOptions, (error, client) => {
+ if (err) { return handleError(err) }
+
+ const subscribeHeaders = {
+ /* To subscribe to an existing queue, /amq/queue prefix is required */
+ 'destination': '/amq/queue/UATwBQ:example-queue',
+ 'ack': 'client-individual' /* each message requires an ACK to confirm it has been processed */
+ }
+ /* Wait for messages published to the Ably Reactor queue */
+ client.subscribe(subscribeHeaders, (error, message) => {
+ if (err) { return handleError(err) }
+
+ /* STOMP is a text-based protocol so decode UTF-8 string */
+ message.readString('utf-8', (error, body) => {
+ if (err) { return handleError(err) }
+
+ let decodedEnvelope = JSON.parse(item.content)
+
+ /* The envelope messages attribute will only contain one message. However,
+ in future versions, we may allow optional bundling of messages into a
+ single queue message and as such this attribute is an Array to support
+ that in future */
+ let messages = Ably.Realtime.Message.fromEncodedArray(decodedEnvelope.messages)
+ messages.forEach((message) => {
+ actionMessage(message)
+ })
+
+ client.ack(message)
+ })
+ })
+})
+```
+
+Please note:
+
+* In the example above, the queue rule has been configured to wrap each message in an envelope (the default setting). Therefore the first step is to parse the envelope JSON. See details on "enveloped messages":#enveloped below.
+* The @Message.fromEncodedArray@ method is used to decode the message(s) and return an array of "@Message@":/realtime/types#message objects. We strongly recommend you use this method if your client library supports it to ensure messages are decoded correctly and portable across all platforms.
+* Whilst the code above can handle multiple messages per envelope, we currently only support one message per envelope. The @messages@ attribute is an @Array@ so that in future we could optionally support message bundling.
+
+**"See our tutorials section for step-by-step examples using a Reactor Queue with STOMP »":/tutorials**
+
+h4(#enveloped). Enveloped and non-enveloped message examples
+
+When you configure a queue rule, you are given the option to envelope messages, which is enabled by default. In most cases, we believe an enveloped message provides more flexibility as it contains additional metadata in a portable format that can be useful such as the @clientId@ of the publisher, or the @channel@ name the message originated from.
+
+However, where performance is a primary concern, you may choose not to envelope messages and instead have only the message payload (@data@ element) published. This has the advantage of requiring one less parsing step, however decoding of the raw payload in the published message will be your responsibility.
+
+Note that messages published to queues are by default encoded as JSON (a text format), however you can choose to have messages encoded with "MsgPack":msgpack.org (a binary format) in your queue rules.
+
+h5(#envelope-message). Enveloped message example
+
+**Headers**: @none@
+
+**Data**:
+
+```[json]
+{
+ "source": "channel.message",
+ "appId":"ael724",
+ "channel": "foo",
+ "site": "eu-west-1-A",
+ "ruleId": "cOOo9g",
+ "messages": [
+ {
+ "id": "vjzxPR-XK3:3:0",
+ "name": "event",
+ "connectionId": "vjzxPR-XK3",
+ "timestamp": 1485914937909,
+ "data": "textPayload"
+ }
+ ]
+}
+```
+
+_Please note that the @messages@ attribute is an @Array@ so that future envelope options may allow messages to be bundled into a single envelope ("Reactor Events":/general/events can batch messages). However, with the current queue rule design, an envelope will only ever contain one message._
+
+h5(#no-envelope-message). Non-enveloped message example
+
+**Headers**:
+* @X-ABLY-ENVELOPE-SOURCE@: @channel.message@
+* @X-ABLY-ENVELOPE-APPID@: @ael724@
+* @X-ABLY-ENVELOPE-CHANNEL@: @foo@
+* @X-ABLY-ENVELOPE-SITE@: @eu-west-1-A@
+* @X-ABLY-ENVELOPE-RULE-ID@: @wYge7g@
+* @X-ABLY-MESSAGE-ID@: @vjzxPR-XK3:3:0@
+* @X-ABLY-MESSAGE-TIMESTAMP@: @1485914937909@
+* @X-ABLY-MESSAGE-CONNECTION-ID@: @vjzxPR-XK3@
+
+**Data**:
+
+```[json]
+textPayload
+```
+
+h5(#envelope-presence). Enveloped presence message example
+
+**Headers**: @none@
+
+**Data**:
+
+```[json]
+{
+ "source": "channel.presence",
+ "appId":"ael724",
+ "channel": "foo",
+ "site": "eu-west-1-A",
+ "ruleId": "z8R85g",
+ "presence": [
+ {
+ "id": "vjzxPR-XK3:5:0",
+ "clientId": "bob",
+ "connectionId": "vjzxPR-XK3",
+ "timestamp": 1485916832961,
+ "action": "enter",
+ "data": "clientData"
+ }
+ ]
+}
+```
+
+_Please note that the @presence@ attribute is an @Array@ so that future envelope options may allow presence messages to be bundled into a single envelope ("Reactor Events":/general/events can batch messages). However, with the current queue rule design, an envelope will only ever contain one presence message._
+
+h5(#no-envelope-presence). Non-enveloped presence message example
+
+**Headers**:
+* @X-ABLY-ENVELOPE-SOURCE@: @channel.presence@
+* @X-ABLY-ENVELOPE-APPID@: @ael724@
+* @X-ABLY-ENVELOPE-CHANNEL@: @foo@
+* @X-ABLY-ENVELOPE-SITE@: @eu-west-1-A@
+* @X-ABLY-ENVELOPE-RULE-ID@: @wYge7g@
+* @X-ABLY-MESSAGE-ID@: @vjzxPR-XK3:5:0@
+* @X-ABLY-MESSAGE-TIMESTAMP@: @1485914937909@
+* @X-ABLY-MESSAGE-CONNECTION-ID@: @vjzxPR-XK3@
+* @X-ABLY-MESSAGE-CLIENT-ID@: @bob@
+* @X-ABLY-MESSAGE-ACTION@: @enter@
+
+**Data**:
+
+```[json]
+clientData
+```
+
+h4(#deadletter). Dead letter queues
+
+When you provision a queue, Ably automatically provisions a "special" dead letter queue at the same time. This dead letter queue holds messages that have failed to be processed correctly or expired. It is advisable to consume messages from the dead letter queue so that you can keep track of failed, expired or unprocessable messages. Messages are moved into your dead letter queue when:
+
+* The message is rejected (@basic.reject@ or @basic.nack@) with @requeue=false@;
+* The TTL for the message expires; or
+* The queue is full (max length limit is reached) and a new message is published to the queue. In this case, the oldest message in the queue is removed and placed in the dead letter queue to make room for the new message
+
+Please note that messages already in the dead letter queue that subsequently meet any of the above criteria are deleted i.e. if the TTL for a message in the dead letter queue passes, the message is deleted forever.
+
+A dead letter queue uses the reserved queue name @APPID:deadletter@ where @APPID@ is the app ID in which your queues are provisioned. You will have exactly one deadletter queue per app if you have one or more Reactor Queues, and this queue will appear in your queues dashboard. You can subscribe to a dead letter queue just like any other queue.
+
+h4(#download). Download a client library
+
+For a list of popular AMQP and STOMP client libraries you can use across a wide range of platforms, please see "our client library download page":https://www.ably.io/download.
+
+h2(#considerations). Queue considerations
+
+When using Reactor Queues, please bear in mind that:
+
+* Our message queues guarantee "at least once delivery":http://www.cloudcomputingpatterns.org/at_least_once_delivery/ using a message acknowledgement protocol ("exactly once is not practically achievable":http://bravenewgeek.com/you-cannot-have-exactly-once-delivery/)
+* Ably provides reliable ordering for you messages by channel. For example, if messages published in a single channel are republished to a queue, and there is only one consumer for that queue, then the consumer will receive the messages in the order they were published. However, if you have "more than one consumer, reliable ordering is not possible":http://stackoverflow.com/a/21363518/139607, equally if you have messages from multiple channels, reliable ordering is only supported per channel not across all channels.
+* Rate limits apply to queues depending on your account type. Please see "the complete list of account limits":https://support.ably.io/solution/articles/3000053845-do-you-have-any-connection-message-rate-or-other-limits-on-accounts.
+* There is a default TTL (time-to-live) applied to all messages that is configured when you provision your queue. If a message has not been consumed from a queue within this period, it will be moved to the deadletter queue. If the TTL of the deadletter queue passes, the message is discarded. See "account limits":https://support.ably.io/solution/articles/3000053845-do-you-have-any-connection-message-rate-or-other-limits-on-accounts.
+* There is a max message limit configured when you provision your queue. If the max message limit is reached for your queue, new messages will be moved to the deadletter queue. Once the deadletter queue reaches its max message limit, new messages will be discarded. See "account limits":https://support.ably.io/solution/articles/3000053845-do-you-have-any-connection-message-rate-or-other-limits-on-accounts.
+* With the AMQP protocol, it is possible to consume multiple queues from a single connection, and also to consume more than one message at a time. You will need to refer to your client library's documentation to enable these capabilities. See "this StackOverFlow answer":http://stackoverflow.com/a/17011833/139607 as a good starting point.
+* Unlike our Ably "pub/sub channels":/realtime/channels which are implicitly global and distributed, our message queues are provisioned in a single physical region. You can choose the region you want your queue to exist when provisioning your queue. Typically you will want to provision a queue closest to your servers to keep the latency as low as possible.
+* Each message published to the queue will count towards you monthly message quota. See "billing info for more details":#below.
+
+h3(#scalability-availability). Queue Scalability and High Availability
+
+Ably's Reactor Message Queue service is offered in two flavours, multi-tenanted and dedicated.
+
+Our multi-tenanted queue service is provided as part of the core platform to all customers. The queues are provided in a high availability configuration (your data is stored in at least two datacenters with automatic fail-over capabilities). Our multi-tenanted queue service is designed for low to medium volumes of messages and has a guideline limit of no more than 200 messages per second per account.
+
+For customers with more demanding requirements (up to millions of messages per second), Ably has two solutions for our "Enterprise customers":https://www.ably.io/pricing/enterprise:
+
+* Dedicated queue clusters that scale to millions of messages
+* "Ably Reactor Firehose":/general/firehose for streaming your realtime data directly into your own streaming or queueing service
+
+"Get in touch if you'd like to find out more about our Enterprise offering":https://www.ably.io/contact.
+
+h3(#billing). Billing info
+
+Each message published by a rule to a queue counts as one message towards your message quota. For example, if you publish a message on a channel that is in turn republished to a Reactor Queue, that will count as two messages. "Find out more about how messages are counted":https://support.ably.io/solution/articles/3000053844-how-does-ably-count-messages.
+
+h2(#next-steps). Next steps
+
+* "Follow one of our Reactor Queue step-by-step tutorials":/tutorials#reactor
+* "Download a client library":https://www.ably.io/download
+* "Provision a queue now":https://support.ably.io/solution/articles/3000062188-how-can-i-provision-a-new-message-queue and "set up a queue rule now":https://support.ably.io/solution/articles/3000062196-how-can-i-set-up-a-queue-rule
diff --git a/content/general/versions/v1.1/statistics.textile b/content/general/versions/v1.1/statistics.textile
new file mode 100644
index 0000000000..fa075aa7e4
--- /dev/null
+++ b/content/general/versions/v1.1/statistics.textile
@@ -0,0 +1,320 @@
+---
+title: Application Statistics
+index: 30
+---
+
+The Ably system can be queried to obtain usage statistics for a given application, and results are provided aggregated across all channels in use in the application in the specified period. Stats may be used to track usage against account quotas. The details on how to retrieve statistics are available in the "REST API documentation":/rest-api#stats, "Realtime client library statistics documentation":/realtime/statistics and "REST client library statistics documentation":/rest/statistics.
+
+Statistics returned from the API are sparse; this means that if a metric object such as a message count @{ count: [val], data: val }@ is empty or contains only zero values for all key value pairs, then the metric will be omitted completely from the JSON response. This reduces the size of the JSON significantly and thus improves performance.
+
+h3(#stats-example). Complete stats example containing all possible metrics
+
+Example request:
+
+bc[sh]. curl https://rest.ably.io/stats?unit=hour \
+ -u "{{API_KEY}}"
+
+Example response:
+
+```[json]
+[
+ {
+ "all": { // aggregates inbound and outbound messages
+ "messages": { // messages published on channels
+ "count": 22, // count of messages
+ "data": 308 // total data in bytes
+ },
+ "presence": { // presence events such as enter/leave
+ "count": 41, // count of presence events
+ "data": 2873 // total data in bytes for presence events
+ },
+ "all": { // aggregated messages and presence
+ "count": 63, // count of all
+ "data": 3181 // total bytes for all
+ }
+ },
+ "inbound": { // all inbound messages i.e. received by Ably from clients
+ "realtime": { // received over realtime socket connection
+ "messages": { // messages published on channels
+ "count": 0, // count of messages
+ "data": 0 // total data in bytes
+ },
+ "presence": { // presence events such as enter/leave
+ "count": 4, // count of presence events
+ "data": 676 // total data in bytes for presence events
+ },
+ "all": { // aggregated channel messages + presence
+ "count": 4, // count of all
+ "data": 676 // total bytes for all
+ }
+ },
+ "rest": { // received via the HTTP REST API
+ "messages": { // messages published on channels
+ "count": 5, // count of messages
+ "data": 70 // total data in bytes
+ },
+ "presence": { // presence events such as enter/leave
+ "count": 2, // count of presence events
+ "data": 20 // total data in bytes for presence events
+ },
+ "all": { // channel messages + presence
+ "count": 5, // count of all
+ "data": 70 // total bytes for all
+ }
+ },
+ "all": { // aggregates all inbound realtime and REST messages
+ "messages": { // messages published on channels
+ "count": 5, // count of messages
+ "data": 70 // total data in bytes
+ },
+ "presence": { // presence events such as enter/leave
+ "count": 6, // count of presence events
+ "data": 696 // total data in bytes for presence events
+ },
+ "all": { // aggregated channel messages + presence
+ "count": 11, // count of all
+ "data": 766 // total bytes for all
+ }
+ }
+ },
+ "outbound": { // all outbound messages i.e. sent from Ably to clients
+ "realtime": { // sent over realtime socket connection
+ "messages": { // messages published on channels
+ "count": 17, // count of messages
+ "data": 2873 // total data in bytes
+ },
+ "presence": { // presence events such as enter/leave
+ "count": 37, // count of presence events
+ "data": 2197 // total data in bytes for presence events
+ },
+ "all": { // aggregated channel messages + presence
+ "count": 54, // count of all
+ "data": 2435 // total bytes for all
+ }
+ },
+ "rest": { // retrieved using REST history API
+ "messages": { // messages on channels
+ "count": 2, // count of messages
+ "data": 20 // total data in bytes
+ },
+ "presence": { // presence events such as enter/leave
+ "count": 4, // count of presence events
+ "data": 40 // total data in bytes for presence events
+ },
+ "all": { // aggregated channel messages + presence
+ "count": 6, // count of all
+ "data": 60 // total bytes for all
+ }
+ },
+ "webhook": { // messages pushed to customer's servers via Webhooks
+ "messages": { // messages published on channels
+ "count": 1, // count of messages
+ "data": 10 // total data in bytes
+ },
+ "presence": { // presence events such as enter/leave
+ "count": 2, // count of presence events
+ "data": 20 // total data in bytes for presence events
+ },
+ "all": { // aggregated messages + presence
+ "count": 3, // count of all
+ "data": 30 // total bytes for all
+ }
+ },
+ "sharedQueue": { // messages sent to a Reactor Queue
+ "messages": { // messages published on channels
+ "count": 1, // count of messages
+ "data": 10 // total data in bytes
+ },
+ "presence": { // presence events such as enter/leave
+ "count": 2, // count of presence events
+ "data": 20 // total data in bytes for presence events
+ },
+ "all": { // aggregated messages + presence
+ "count": 3, // count of all
+ "data": 30 // total bytes for all
+ }
+ },
+ "externalQueue": { // messages sent to some external target using Reactor Firehose
+ "messages": { // messages published on channels
+ "count": 1, // count of messages
+ "data": 10 // total data in bytes
+ },
+ "presence": { // presence events such as enter/leave
+ "count": 2, // count of presence events
+ "data": 20 // total data in bytes for presence events
+ },
+ "all": { // aggregated messages + presence
+ "count": 3, // count of all
+ "data": 30 // total bytes for all
+ }
+ },
+ "httpEvent": { // times some per-message http trigger has been invoked, typically
+ // a serverless function on a service such as AWS Lambda, Google
+ // Cloud Functions, or Azure Functions
+ "messages": { // messages published on channels
+ "count": 1, // count of messages
+ "data": 10 // total data in bytes
+ },
+ "presence": { // presence events such as enter/leave
+ "count": 2, // count of presence events
+ "data": 20 // total data in bytes for presence events
+ },
+ "all": { // aggregated messages + presence
+ "count": 3, // count of all
+ "data": 30 // total bytes for all
+ }
+ },
+ "push": { // messages pushed to devices via a Push Notifications transport
+ // such as FCM or APNS
+ "messages": { // messages published on channels
+ "count": 1, // count of messages
+ "data": 10 // total data in bytes
+ },
+ "presence": { // presence events such as enter/leave
+ "count": 2, // count of presence events
+ "data": 20 // total data in bytes for presence events
+ },
+ "all": { // aggregated channel messages + presence
+ "count": 3, // count of all
+ "data": 30 // total bytes for all
+ }
+ },
+ "all": { // aggregates all outbound realtime, REST, Webhook, sharedQueue,
+ // externalQueue, httpEvent, and push messages
+ "messages": { // messages published on channels
+ "count": 30, // count of messages
+ "data": 268 // total data in bytes
+ },
+ "presence": { // presence events such as enter/leave
+ "count": 42, // count of presence events
+ "data": 2257 // total data in bytes for presence events
+ },
+ "all": { // aggregated channel messages + presence
+ "count": 63, // count of all
+ "data": 2525 // total bytes for all
+ }
+ }
+ },
+ "persisted": { // all message types persisted based on configured channel rules
+ "messages": { // messages persisted on channels
+ "count": 5, // count of messages
+ "data": 70 // total data in bytes
+ },
+ "presence": { // presence events persisted for states such as enter/leave
+ "count": 8, // count of presence events
+ "data": 676 // total data in bytes for presence events
+ },
+ "all": { // aggregated channel messages + presence persisted
+ "count": 13, // count of all
+ "data": 746 // total bytes for all
+ }
+ },
+ "connections": { // connection statistics for this time period
+ "plain": { // non-TLS un-encrypted connections
+ "peak": 4, // peak concurrent connections for this period
+ "min": 0, // minimum concurrent connections in this period
+ "mean": 2, // average concurrent connections in this period
+ "opened": 6, // count of new connections in this period
+ "refused": 0 // count of connections refused by Ably in this period
+ },
+ "tls": { // TLS encrypted connections
+ "peak": 2, // peak concurrent connections for this period
+ "min": 2, // minimum concurrent connections in this period
+ "mean": 2, // average concurrent connections in this period
+ "opened": 2, // count of new connections in this period
+ "refused": 0 // count of connections refused by Ably in this period
+ },
+ "all": { // aggregated summary of all connection types
+ "peak": 6, // peak concurrent connections for this period
+ "min": 2, // minimum concurrent connections in this period
+ "mean": 4, // average concurrent connections in this period
+ "opened": 8, // count of new connections in this period
+ "refused": 0 // count of connections refused by Ably in this period
+ }
+ },
+ "channels": { // channel statistics for this time period
+ "peak": 2, // peak number of channels active for this period
+ "min": 0, // min number of channels active for this period
+ "mean": 0, // average number of channels active for this period
+ "opened": 0, // total number of channels opened in this period
+ "refused": 0 // number of channel attach requests failed because of permissions
+ },
+ "apiRequests": { // API requests made via the REST API excluding tokens
+ "succeeded": 11, // successful requests
+ "failed": 0, // failed requests
+ "refused": 0 // requests refused as a result of exceeding account limits
+ },
+ "tokenRequests": { // token requests via the REST API
+ "succeeded": 9, // successful tokens issued
+ "failed": 0, // failed token request
+ "refused": 0 // requests refused due to permissions or rate limiting
+ },
+ "push": { // Detailed stats on push notifications, see
+ // https://www.ably.io/documentation/general/push for more details
+ "messages": 0,
+ "notifications": {
+ "invalid": 0,
+ "attempted": 0,
+ "successful": 0,
+ "failed": 0
+ },
+ "directPublishes": 0
+ },
+ "inProgress": "2015-03-16:10:57", // last sub-interval included in this statistic
+ "count": 116, // number of lower-level stats used to aggregate these results
+ "unit": "hour", // unit of time for these stats from the intervalId forwards
+ "intervalId": "2015-03-16:10" // time period for stats in format yyyy-mm-dd:hh:mm:ss
+ }
+]
+```
+
+h3. Sparse stats example containing present metrics
+
+Example request:
+
+bc[sh]. curl https://rest.ably.io/stats?unit=minute \
+ -u "{{API_KEY}}"
+
+Example response:
+
+```[json]
+[
+ {
+ "all": {
+ "messages": {
+ "count": 1,
+ "data": 50
+ }
+ "all": {
+ "count": 1,
+ "data": 50
+ }
+ },
+ "inbound": {
+ "rest": {
+ "messages": {
+ "count": 1,
+ "data": 50
+ },
+ "all": {
+ "count": 1,
+ "data": 50
+ }
+ },
+ "all": {
+ "messages": {
+ "count": 1,
+ "data": 50
+ },
+ "all": {
+ "count": 1,
+ "data": 50
+ }
+ }
+ },
+ "count": 0,
+ "unit": "minute",
+ "intervalId": "2015-03-26:01:11"
+ }
+]
+```
diff --git a/content/mqtt/index.textile b/content/mqtt/index.textile
index 4ff76badc7..8775ad4404 100644
--- a/content/mqtt/index.textile
+++ b/content/mqtt/index.textile
@@ -35,8 +35,8 @@ For example, in the NodeJS "MQTT package":https://www.npmjs.com/package/mqtt, yo
bc[nodejs]. {
var options = {
keepalive: 30,
- username: 'FIRST_HALF_OF_API_KEY',
- password: 'SECOND_HALF_OF_API_KEY',
+ username: '{{API_KEY_NAME}}', /* API key's name */
+ password: '{{API_KEY_SECRET}}', /* API key's secret */
port: 8883
};
var client = mqtt.connect('mqtts:mqtt.ably.io', options);
@@ -54,8 +54,8 @@ bc[nodejs]. {
var decoder = new encoding.TextDecoder();
var options = {
keepalive: 30,
- username: 'FIRST_HALF_OF_API_KEY',
- password: 'SECOND_HALF_OF_API_KEY',
+ username: '{{API_KEY_NAME}}', /* API key's name */
+ password: '{{API_KEY_SECRET}}', /* API key's secret */
port: 8883
};
var client = mqtt.connect('mqtts:mqtt.ably.io', options);
diff --git a/content/partials/general/events/_batched_event_headers.textile b/content/partials/general/events/_batched_event_headers.textile
index c48e82b084..f417b21e9a 100644
--- a/content/partials/general/events/_batched_event_headers.textile
+++ b/content/partials/general/events/_batched_event_headers.textile
@@ -3,4 +3,4 @@ Batched events will have the following headers:
- content-type := the type of the payload. This can be either @application/json@, @text/plain@, or @application/octet-stream@, depending on if it's @JSON@, @text@, or @binary@ respectively
- x-ably-envelope-appid := the "app ID":https://support.ably.io/support/solutions/articles/3000063083 which the message came from
- content-type := the type of the payload. This will be @application/json@ or @application/x-msgpack@
-- x-ably-version := the version of Reactor Event. At present this should be @1.0@, though older Events will be @0.8@
+- x-ably-version := the version of Reactor Event. At present this should be @1.2@
diff --git a/content/partials/general/events/_enveloped_event_headers.textile b/content/partials/general/events/_enveloped_event_headers.textile
index 4e470ad8d9..1115128719 100644
--- a/content/partials/general/events/_enveloped_event_headers.textile
+++ b/content/partials/general/events/_enveloped_event_headers.textile
@@ -1,6 +1,6 @@
Enveloped events will have the following headers:
- content-type := the type of the payload. This can be either @application/json@, @text/plain@, or @application/octet-stream@, depending on if it's @JSON@, @text@, or @binary@ respectively
-- x-ably-version := the version of Reactor Event. At present this should be @1.0@, though older Events will be @0.8@
+- x-ably-version := the version of Reactor Event. At present this should be @1.2@
- x-ably-envelope-appid := the "app ID":https://support.ably.io/support/solutions/articles/3000063083 which the message came from
- content-type := the type of the payload. This will be @application/json@ or @application/x-msgpack@ for "enveloped":#envelope messages
diff --git a/content/partials/general/events/_non_enveloped_event_headers.textile b/content/partials/general/events/_non_enveloped_event_headers.textile
index 87f71ceac7..52ce76ac6a 100644
--- a/content/partials/general/events/_non_enveloped_event_headers.textile
+++ b/content/partials/general/events/_non_enveloped_event_headers.textile
@@ -1,7 +1,7 @@
Non-enveloped events have quite a few headers, in order to provide context to the data sent in the payload. These are:
- content-type := the type of the payload. This can be either @application/json@, @text/plain@, or @application/octet-stream@, depending on if it's @JSON@, @text@, or @binary@ respectively
-- x-ably-version := the version of Reactor Event. At present this should be @1.0@, though older Events will be @0.8@
+- x-ably-version := the version of Reactor Event. At present this should be @1.2@
- x-ably-envelope-appid := the "app ID":https://support.ably.io/support/solutions/articles/3000063083 which the message came from
- x-ably-envelope-channel := the Ably channel which the message came from
- x-ably-envelope-rule-id := the Ably Reactor Rule ID which was activated to send this message
diff --git a/content/partials/types/_channel_details.textile b/content/partials/types/_channel_details.textile
index 28fdc40a96..a97d6e0fbd 100644
--- a/content/partials/types/_channel_details.textile
+++ b/content/partials/types/_channel_details.textile
@@ -5,7 +5,7 @@ h3(#channel-details). ChannelDetails
- channelId := the required name of the channel including any qualifier, if any
+
+
+As shown above, Ably provides two models for delivering push notifications to devices:
+
+h3(#direct-publishing). Direct publishing
+
+Ably provides a REST API that allows native push notifications to be delivered directly to:
+
+* Devices identified by their unique device ID
+* Devices identified by their assigned "@clientId@":/realtime/authentication#identified-clients
+* Devices identified by the recipient details of the native push transport such as their unique @registrationToken@ in the case of FCM, @deviceToken@ in the case of APNS, or @targetUrl@ and @encryptionKey@ in the case of a Web device (*experimental*). This means is particularly useful when migrating to Ably with existing push notification target devices.
+
+"Find out more about direct push notification publishing":/general/push/publish#direct-publishing
+
+h3(#channel-broadcasting). Channel-based broadcasting
+
+The model for delivering push notifications to devices over channels is intentionally very similar to how messages are normally delivered using Ably's "pub/sub channel":/core-features/channels. For example, a normal message published on an Ably channel is broadcast immediately to all realtime subscribers of that channel. When broadcasting push notifications on channels, however, the process is the same with the exception that the subscribers (devices receiving push notifications) are registered in advance using our API and the message itself must contain an *extra push notification payload* that specifies the optional visual format and optional data payload of the native push notification.
+
+"Find out more about channel-based push notification broadcasting":/general/push/publish#channel-broadcast
+
+h2(#activate-device). Activating a device and receiving notifications
+
+Every device that will receive push notifications must activate itself with the local operating system or framework, and hook into the push notification services that the underlying platform provides. This functionality is platform-specific and can also vary considerably across not just platforms, but also across the push services that operate on those platforms such as GCM and FCM, both of which are available on the Android platform.
+
+The Ably client libraries aim to abstract away this complexity and platform-specific behaviour by providing a consistent API for device activation, maintenance of the device registration, and for subscription to Ably channels for receiving push notifications.
+
+"Find out more about device activations and subscriptions":/general/push/activate-subscribe.
+
+h2(#admin). Managing devices and subscriptions
+
+Whilst the realtime client libraries provide APIs for a device to activate itself (via "@client.push@":/general/push/activate-subscribe) and subscribe for push notifications (via "@channel.push@":/general/push/activate-subscribe), those APIs are intentionally limited to actions pertaining to the device it is run on.
+
+A separate and distinct push admin API is additionally provided in our client libraries specifically designed for use by your servers to facilitate managing and delivering push notifications across all of your registered devices. This API, amongst other things, includes features to manage registered devices, channel subscriptions and deliver push notifications directly. Currently the "push admin API":/general/push/admin is available in our JavaScript, Ruby, Java/Android, PHP, Python, and iOS libraries. It is also available in our other libraries through the use of the "request":/rest/usage#request method, using the underlying "API":/rest-api directly.
+
+"Find out more about the push admin API":/general/push/admin.
+
+h2(#platform-support). Platform support
+
+Ably currently offers support for push notifications on the following platforms:
+
+- "Apple Push Notifications":https://developer.apple.com/notifications/ := supported on all mobile devices running iOS and desktop devices running OS X
+- "Firebase Cloud Messaging":https://firebase.google.com/docs/cloud-messaging/ := supported on all Android and iOS devices, although we use FCM exclusively for Android message delivery
+- Experimental "W3C Push API":https://www.w3.org/TR/push-api/ := experimental support for "modern W3C compliant browsers":https://caniuse.com/#feat=push-api (this does not include Apple's Safari browser). "Get in touch":https://www.ably.io/contact if you want to use this.
diff --git a/content/partials/versions/v1.1/realtime/_stats.textile b/content/partials/versions/v1.1/realtime/_stats.textile
new file mode 100644
index 0000000000..f0ae891b28
--- /dev/null
+++ b/content/partials/versions/v1.1/realtime/_stats.textile
@@ -0,0 +1,49 @@
+h4. Parameters
+
+- optionsquery := an optional objectHash@ARTStatsQuery@@StatsRequestParams@"@Param@":#param[] array containing the query parameters
+
+-
+
diff --git a/content/partials/versions/v1.1/types/_ably_exception.textile b/content/partials/versions/v1.1/types/_ably_exception.textile
new file mode 100644
index 0000000000..a40e6682ff
--- /dev/null
+++ b/content/partials/versions/v1.1/types/_ably_exception.textile
@@ -0,0 +1,8 @@
+An @AblyException@ is an exception encapsulating error information containing an Ably-specific error code and generic status code, where applicable.
+
+h4.
+ default: Properties
+ java: Members
+ ruby: Attributes
+
+- errorInfoErrorInfo := "@ErrorInfo":/realtime/types#error-info corresponding to this exception, where applicable
+
+
+Subscribing in delta mode is enabled for a given channel by specifying a @delta@ "channel parameter":/realtime/channels/channel-parameters/overview with the value @vcdiff@. This will cause delta messages to be generated by the server and sent to the client, and the library reconstitutes the original message payload. Messages on the channel are delivered to the subscriber's listener in the same way as with a normal subscription.
+
+h2(#delta-processing). Delta processing
+
+Deltas apply to the principal payload of a "@Message@":/realtime/messages#properties published via Ably, which is the @data@ member. Other elements of a message, such as @clientId@, @name@, or @extras@ are unchanged by use of deltas and are not compressed.
+
+Deltas are supported for realtime subscriptions only. Messages retrieved via "history":/realtime/history, and messages delivered to "Reactor integrations":https://www.ably.io/reactor, are not compressed.
+
+Delta compression via @vcdiff@ is supported for all payloads, whether string, binary, or JSON-encoded. The delta algorithm processes message payloads as opaque binaries and has no dependency on the structure of the payload - it does not process line-oriented diffs, for example. In principle, @vcdiff@ deltas can be applied to encrypted message payloads, but in practice this provides no benefit because there is no similarity between successive encrypted payloads even on identical or near-identical plaintext message payloads.
+
+Delta compression is a subscriber-specified option only - the publisher has no control over whether or not deltas are generated for any given message; the processing is performed if there is at least one subscriber on a channel that has requested a delta-mode subscription.
+
+There is no constraint on how many publishers or subscribers there are. If there are multiple publishers, then deltas can still be generated, and they will be determined based on the order of messages in the channel in question.
+
+Delta mode, when activated on a channel, is performed for all messages on a channel, and deltas are calculated strictly based on the message ordering in that channel. The effectiveness of delta mode is dependent on the level of similarity between successive payloads.
+
+If a delta is generated and it results in a difference that is not appreciably smaller than the original message, or is even larger than the original message (which can happen if successive messages are completely different), then the delta will not be sent and clients will receive the original, unprocessed message. Therefore, the sequence of messages that will be delivered to a client for any given channel will be a combination of regular messages and delta-compressed messages.
+
+A channel subscriber can experience a discontinuity in the sequence of messages it receives on a given channel for the following reasons:
+
+* The connection can drop, and there will be a discontinuity of the client is unable to reconnect within the two-minute window it is allowed to preserve connection continuity.
+* The outbound connection might have been rate-limited, which causes some messages to be dropped.
+* There might have been some internal error in the Ably system which leads to the server being unable to preserve continuity on the channel.
+
+In these cases, the service indicates the discontinuity to the client, together with the reason, and this is usually visible to the subscriber in a channel @UPDATE@ event.
+
+If a subscriber has a delta-mode subscription and the channel in question experiences a discontinuity, then a non-delta message will be delivered to the client as the first message after the discontinuity. This ensures that lost messages do not prevent the client from reconstituting messages from deltas.
+
+h2(#using-deltas). Using deltas
+
+h3(#using-deltas-ably). Via an Ably library
+
+The most common way to subscribe to Ably channels is via a realtime connection, using an Ably realtime library.
+
+For many libraries this requires no change on the part of the caller except to specify the @delta@ "channel parameter":/realtime/channels/channel-parameters/overview when subscribing to the channel. In some libraries, the @vcdiff@ delta decoding library is excluded from the default library distribution in order to avoid bloating the library. In these cases, it is also necessary to supply the delta decoder plugin when instancing the Ably library.
+
+```[javascript](code-editor:realtime/channel-deltas)
+ /* Make sure to include in your head */
+ var realtime = new Ably.Realtime({key: '{{API_KEY}}', plugins: {vcdiffDecoder: vcdiffDecoder}});
+ realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', {
+ delta: 'vcdiff'
+ }).subscribe(msg => console.log("Received message: ", msg));
+```
+
+```[nodejs]
+ var vcdiffPlugin = require('@ably/vcdiff-decoder');
+ var realtime = new Ably.Realtime({key: '{{API_KEY}}', plugins: {vcdiffDecoder: vcdiffDecoder}});
+ realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', {
+ delta: 'vcdiff'
+ }).subscribe(msg => console.log("Received message: ", msg));
+```
+
+```[java]
+ AblyRealtime ably = new AblyRealtime("{{API_KEY}}")
+ Channel channel = ably.channels.get("{{RANDOM_CHANNEL_NAME}}", new ChannelOptions{{params = Map.of("delta", "vcdiff")}});
+ channel.subscribe(new MessageListener() {
+ @Override
+ public void onMessage(Message message) {
+ System.out.println("Received `" + message.name + "` message with data: " + message.data);
+ }
+ });
+```
+
+```[swift]
+ let options = ARTClientOptions(key: key)
+ let client = ARTRealtime(options: options)
+ let channelOptions = ARTRealtimeChannelOptions()
+ channelOptions.params = [
+ "delta": "vcdiff"
+ ]
+
+ let channel = client.channels.get(channelName, options: channelOptions)
+```
+
+```[csharp]
+ var clientOptions = new ClientOptions();
+ clientOptions.Key = "{{API_KEY}}";
+ clientOptions.Environment = AblyEnvironment;
+ var ably = new AblyRealtime(clientOptions);
+
+ var channelParams = new ChannelParams();
+ channelParams.Add("delta", "vcdiff");
+ var channelOptions = new ChannelOptions();
+ channelOptions.Params = channelParams;
+ var channel = ably.Channels.Get("{{RANDOM_CHANNEL_NAME}}", channelOptions);
+
+ channel.Subscribe(message => {
+ Console.WriteLine(message.Data.ToString());
+ });
+```
+
+h3(#using-deltas-non-ably). Via a subscription that does not use an Ably library
+
+If subscribing to a channel in delta mode using "SSE":/sse or one of the protocol adaptors such as "MQTT":/mqtt, then you will need to decode any received delta messages yourself. There are decoder libraries available to do this for several platforms; see the "download":https://www.ably.io/download section for details.
+
+When subscribing without an Ably library, the channel @delta@ parameter must be specified using a "qualified channel name":/realtime/channels/channel-parameters/overview. In the case of SSE, it is also possible to specify channel parameters as regular query parameters on the connection URL.
+
+Some transports provide raw message payloads - that is, the content of the @data@ attribute of a @Message@ - without the accompanying metadata. That means that the recipient of the message does not have access to the @extras@ or @encoding@ attributes of the message that would ordinarily be used to decode delta message payloads.
+
+Examples of such transports are MQTT, and SSE in non-enveloped mode. In order to assist applications that use these transports, the @vcdiff@ decoder libraries can check for the @vcdiff@ header at the start of the message payload as an inexact method of determining whether or not the message is a regular message or a delta. Note that, in order to rely on that check, you need to know that that header will not be present in any valid (uncompressed) message in your app. No valid JSON value, for example, will match the @vcdiff@ header check, so it is safe to perform this sniffing on JSON message payloads.
+
+h4(#delta-example-sse). Delta example with SSE
+
+You can subscribe to messages in delta mode, using the SSE transport, as follows.
+
+```[javascript]
+var key = '{{API_KEY}}';
+var channel = 'sample-app-sse';
+var baseUrl = 'https://realtime.ably.io/event-stream';
+var urlParams = `?channels=${channel}&v=1.1&key=${key}&delta=vcdiff`;
+var url = baseUrl + urlParams;
+var eventSource = new EventSource(url);
+var channelDecoder = new DeltaCodec.CheckedVcdiffDecoder();
+
+eventSource.onmessage = function(event) {
+ /* event.data is JSON-encoded Ably Message
+ (see https://www.ably.io/documentation/realtime/types#message) */
+ var message = JSON.parse(event.data);
+ var { id, extras } = message;
+ var { data } = message;
+
+ try {
+ if (extras && extras.delta) {
+ data = channelDecoder.applyBase64Delta(data, id, extras.delta.from).asUtf8String();
+ } else {
+ channelDecoder.setBase(data, id);
+ }
+ } catch(e) {
+ /* Delta decoder error */
+ console.log(e);
+ }
+
+ /* Process decoded data */
+ console.log(data);
+};
+```
+
+h4(#delta-example-unenv-sse). Delta example with unenveloped SSE
+
+For more information on enveloped and uneveloped SSE, please see the "SSE API":/sse#sse
+
+```[javascript]
+ /* Make sure to include in your head */
+ var DeltaCodec = require('@ably/delta-codec');
+
+ var key = '{{API_KEY}}';
+ var channel = 'sample-app-sse';
+ var baseUrl = 'https://realtime.ably.io/event-stream';
+ var urlParams = `?channels=${channel}&v=1.1&key=${key}&delta=vcdiff&enveloped=false`;
+ var url = baseUrl + urlParams;
+ var eventSource = new EventSource(url);
+ var channelDecoder = new DeltaCodec.VcdiffDecoder();
+
+ eventSource.onmessage = function(event) {
+ var data = event.data;
+
+ try {
+ if (DeltaCodec.VcdiffDecoder.isBase64Delta(data)) {
+ data = channelDecoder.applyBase64Delta(data).asUtf8String();
+ } else {
+ channelDecoder.setBase(data);
+ }
+ } catch(e) {
+ /* Delta decoder error */
+ console.log(e);
+ }
+
+ /* Process decoded data */
+ console.log(data);
+ };
+```
+
+h4(#delta-example-mqtt). Delta example with MQTT
+
+```[nodejs]
+ var mqtt = require('mqtt');
+ var { VcdiffDecoder } = require('@ably/vcdiff-decoder');
+
+ var options = {
+ keepalive: 30,
+ username: '{{API_KEY_NAME}}', /* API key's name */
+ password: '{{API_KEY_SECRET}}', /* API key's secret */
+ port: 8883
+ };
+ var client = mqtt.connect('mqtts:mqtt.ably.io', options);
+ var channelName = 'sample-app-mqtt';
+ var channelDecoder = new VcdiffDecoder();
+
+ client.on('message', (_, payload) => {
+ var data = payload;
+
+ try {
+ if (VcdiffDecoder.isDelta(data)) {
+ data = channelDecoder.applyDelta(data).asUint8Array();
+ } else {
+ channelDecoder.setBase(data);
+ }
+ } catch(e) {
+ /* Delta decoder error */
+ console.log(e);
+ }
+
+ /* Process decoded data */
+ console.log(data);
+ });
+
+ client.subscribe(`[?delta=vcdiff]${channelName}`);
+```
+
+h1. API Reference
+
+inline-toc.
+ ChannelsOptions Details:
+ - ChannelOptions#channel-options
+
+h3(#channel-options).
+ default: ChannelOptions Object
+ objc,swift: ARTChannelOptions
+ java: io.ably.lib.types.ChannelOptions
+ csharp: IO.Ably.Realtime.ChannelOptions
+
+<%= partial partial_version('types/_channel_options') %>
diff --git a/content/realtime/channels/channel-parameters/overview.textile b/content/realtime/channels/channel-parameters/overview.textile
new file mode 100644
index 0000000000..992afc076b
--- /dev/null
+++ b/content/realtime/channels/channel-parameters/overview.textile
@@ -0,0 +1,188 @@
+---
+title: Channel Parameters
+section: realtime
+index: 4
+languages:
+ - javascript
+ - nodejs
+ - java
+ - swift
+ - csharp
+jump_to:
+ Help with:
+ - Overview#overview
+ - Supported channel parameters#supported-parameters
+ - Using channel parameters with Ably libraries#using-parameters-ably
+ - Using channel parameters outside of supported Ably libraries#using-parameters-non-ably
+ - Next steps#next-steps
+---
+
+h2(#overview). Overview
+
+Ably provides channel parameters as a means of customizing channel functionality. For example, you can request that a channel attachment start from some time in the past by using the "rewind parameter":#supported-parameters.
+
+The methods provided for specifying channel parameters are outlined below.
+
+h2(#supported-parameters). Supported channel parameters
+
+A set of channel parameters is a set of key/value pairs, where both keys and values are strings; the keys correspond to specific features that Ably defines:
+
+- rewind := Allows an attachment to a channel to start from a given number of messages or point in time in the past. See "rewind":/realtime/channels/channel-parameters/rewind for more information.
+- delta := Enables delta compression, a way for a client to subscribe to a channel so that message payloads sent contain only the difference (ie the delta) between the present message and the previous message on the channel. See "deltas":/realtime/channels/channel-parameters/deltas for more information.
+
+h2(#using-parameters-ably). Using channel parameters with Ably libraries
+
+You can specify channel parameters in the "@ChannelOptions@":#channel-options when obtaining a @Channel@. A collection of channel parameters is expressed as a map of string key/value pairs. The @ChannelOptions@ associated with a channel may also be updated by calling "setOptions":/realtime/channels#modifying-options. The parameters associated with a channel take effect when the channel is first attached; if the parameters are subsequently modified via a call to @setOptions@, then that call triggers an attach operation that applies the updated parameters, if successful.
+
+h3. Example
+
+For example, to specify the @rewind@ channel parameter with the value @"1"@:
+
+```[jsall]
+ var realtime = new Ably.Realtime('{{API_KEY}}');
+ var channelOpts = {params: {rewind: '1'}};
+ var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', channelOpts);
+```
+
+```[java]
+ final Map
+
+
+An Ably Realtime client library is responsible for:
+
+- Connection state management := Actively managing the "WebSocket":/concepts/websockets connection by reconnecting "automatically to an available datacenter":https://support.ably.io/solution/articles/3000044636-routing-around-network-and-dns-issues when a connection drops and restoring the connection state. Find out more about "the connection object and its state":/realtime/connection and "connection state recovery which provides message continuity over changing network conditions":https://support.ably.io/solution/articles/3000044639-connection-state-recovery.
+- Channel management := Providing "publish and subscribe":/realtime/channels capabilities over channels and actively managing them by queueing published messages when disconnected and "retrieving missed messages":https://support.ably.io/solution/articles/3000044639-connection-state-recovery once reconnected. The library proactively reattaches channels that become suspended due to long periods of disconnectedness.
+- Presence := Allowing a client to "register itself as present on a channel":/realtime/presence and actively ensuring all members present on a channel are kept in-sync locally. The library proactively restores presence state on suspended channels due to long periods of disconnectedness.
+- Data interoperabilty := Ensuring messages and their payloads (JSON, strings or binary data) are encoded and decoded in a uniform way to ensure interoperability between all supported platforms.
+- Encryption := "Encrypting payloads with the optional user-generated encryption key":/realtime/encryption ensuring payloads cannot be decrypted whilst in transit or by any party without the private key.
+
+h3(#realtime-vs-rest). When to use Realtime vs REST libraries
+
+The **Realtime library** is most commonly used client-side and is stateful, it establishes a connection to Ably for that client and maintains state for the life of the connection. Reasons to use the Realtime library are:
+
+* You are developing a mobile, desktop or web client that needs to subscribe to messages in real time.
+* You want to maintain a persistent connection to Ably, attach to one or more channels, and publish and subscribe to messages.
+* Your application needs to register its presence on a channel, or listen for others becoming present in real time.
+
+The **REST client library** is most commonly used server-side i.e. on your application servers, and is stateless. Reasons to use the REST library are:
+
+* Your application server is used to primarily issues tokens for clients and/or publish messages on channels.
+* Your application is mostly stateless i.e. you process a request or respond to an event, and then move onto the next request or event without any previous state carrying through.
+* Your prefer a synchronous request over an asynchronous request. Note not all REST libraries are synchronous, but where the platform offers a synchronous and asynchronous approach, the REST libraries are more often synchronous.
+
+h4(#other-libs). Other libraries and supported protocols to consider
+
+* If you want to consume realtime data from one or more of your servers, then we recommend you consider using our "Reactor Queues":/general/queues or "Reactor Firehose":/general/firehose. With the Reactor, you can consume realtime data in a robust, resilient and scalable way across multiple support protocols. "Find our more about the Ably Reactor":https://www.ably.io/reactor.
+* If you want realtime messages or presence events to trigger execution of code on your servers or in a server-less environment (such as AWS Lambda), then you should consider "Reactor Events":/general/events.
+* If you want to use another realtime protocol such as MQTT or perhaps even one of our competitors' protocols, you should review "the realtime protocols we support with our Protocol Adapters":http://www.ably.io/adapters
+
+h2(#docs). Diving into the documentation
+
+The Realtime Client Library API documentation is structured as follows:
+
+* "Constructor & usage examples":/realtime/usage
+* "Connection":/realtime/connection
+* "Channels":/realtime/channels
+* "Messages":/realtime/messages
+* "Presence":/realtime/presence
+* "Authentication":/realtime/authentication
+* "History":/realtime/history
+* "Encryption":/realtime/encryption
+* "Statistics":/realtime/statistics
+* "Types":/realtime/types
+
+h2(#tutorials). Step-by-step tutorials
+
+We have a number of tutorials in a wide range of languages to help walk you through some of the key features of our Ably client libraries.
+"Skip to Ably tutorials »":/tutorials
+
diff --git a/content/realtime/versions/v1.1/messages.textile b/content/realtime/versions/v1.1/messages.textile
new file mode 100644
index 0000000000..ed9613264c
--- /dev/null
+++ b/content/realtime/versions/v1.1/messages.textile
@@ -0,0 +1,505 @@
+---
+title: Messages
+section: realtime
+index: 31
+languages:
+ - javascript
+ - nodejs
+ - ruby
+ - java
+ - swift
+ - objc
+ - csharp
+api_separator:
+jump_to:
+ Help with:
+ - Getting started#getting-started
+ - Subscribing to messages#message-subscription
+ - Publishing messages#message-publish
+ - Retrieving message history#message-history
+ Message properties:
+ - name#name
+ - data#data
+ - id#id
+ - clientId#client-id
+ - connectionId#connection-id
+ - timestamp#timestamp
+ - encoding#encoding
+ Message methods:
+ - fromEncoded#message-from-encoded
+ - fromEncodedArray#message-from-encoded-array
+---
+
+The Ably Realtime service allows for clients to send information with @messages@, which contain data the client wishes to communicate. These messages are "published":#message-publish through "channels":/realtime/channels, which other users can "subscribe":#message-subscription to in order to receive them. This scalable and resilient messaging pattern is commonly called "pub/sub":https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern.
+
+h2(#getting-started). Getting started
+
+The Ably Realtime client library provides a straightforward API for "publishing":#message-publish and "subscribing":#message-subscription to messages on a "channel":/realtime/channels. If the "channel":/realtime/channels does not exist at the time the client is attached, a "channel":/realtime/channels will be created in the Ably system immediately.
+
+```[javascript](code-editor:realtime/channel-publish)
+ var realtime = new Ably.Realtime('{{API_KEY}}');
+ var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}');
+ channel.subscribe(function(message) {
+ alert('Received: ' + message.data);
+ });
+ channel.publish('example', 'message data');
+```
+
+```[nodejs](code-editor:realtime/channel-publish)
+ var Ably = require('ably');
+ var realtime = new Ably.Realtime('{{API_KEY}}');
+ var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}');
+ channel.subscribe(function(message) {
+ console.log("Received: " message.data);
+ });
+ channel.publish("example", "message data");
+```
+
+```[ruby]
+ realtime = Ably::Realtime.new('{{API_KEY}}')
+ channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}')
+ channel.subscribe do |message|
+ puts "Received: #{message.data}"
+ end
+ channel.publish 'example', 'message data'
+```
+
+```[java]
+ AblyRealtime realtime = new AblyRealtime("{{API_KEY}}");
+ Channel channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}");
+ channel.subscribe(new MessageListener() {
+ @Override
+ public void onMessage(Message message) {
+ System.out.println("New messages arrived. " + message.name);
+ }
+ });
+ channel.publish("example", "message data");
+```
+
+```[csharp]
+ AblyRealtime realtime = new AblyRealtime("{{API_KEY}}");
+ var channel = realtime.Channels.Get("{{RANDOM_CHANNEL_NAME}}");
+ channel.Subscribe(message => {
+ Console.WriteLine($"Message: {message.name}:{message.data} received")
+ });
+ channel.Publish("example", "message data");
+```
+
+```[objc]
+ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"];
+ARTRealtimeChannel *channel = [realtime.channels get:@"{{RANDOM_CHANNEL_NAME}}"];
+[channel subscribe:^(ARTMessage *message) {
+ NSLog(@"Received: %@", message.data);
+}];
+[channel publish:@"example" data:@"message data"];
+```
+
+```[swift]
+let realtime = ARTRealtime(key: "{{API_KEY}}")
+let channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}")
+channel.subscribe { message in
+ print("Received: \(message.data)")
+}
+channel.publish("example", data: "message data")
+```
+
+If you would prefer to just dive into code and see some examples of how to use messages, then we recommend you take a look at our "Realtime tutorials":/tutorials.
+
+h2(#messages). Messages
+
+Each message published has an optional event @name@ propertymemberattribute and a @data@ propertymemberattribute carrying the payload of the message. Various primitive and object types are defined, portable and supported in all clients, enabling clients to be interoperable despite being hosted in different languages or environments.
+
+The supported payload types are Strings, JSON objects and arrays, buffers containing arbitrary binary data, and Null objects. Client libraries detect the supplied message payload and encode the message appropriately.
+
+h3(#message-subscription). Subscribing to messages
+
+The @name@ propertymemberattribute of published messages does not affect the distribution of a channel message to clients but may be used as a (purely client-side) subscription filter, allowing a client to register a listener that only sees a subset of the messages received on the channel. When subscribing, a message listener can subscribe to see all messages on the channel or only a subset whose name matches a given @name@ string.
+
+The client can choose whether or not to receive messages that they themselves publish using "@ClientOptions#echoMessages@":/realtime/usage/#client-options"@ClientOptions#echo_messages@":/realtime/usage/#client-options.
+
+