From 0436070ca571092b4ac0730539ab3f095c6d79ab Mon Sep 17 00:00:00 2001 From: tomC Date: Thu, 16 Apr 2020 14:48:13 +0100 Subject: [PATCH 1/3] Reduce scope of Git ignore where specific files are being targeted relative to root. I had accidentally asked nanoc to compile the site from a sub-folder but it took me a while to work out that this had created a transitory crash.log file for me in that folder. It's easier for developers to spot this kind of mistake if they'll see a new file pop up in that scenario. --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From e8b9cd8df5f9b775b0e7b1da514d266841bf6a2b Mon Sep 17 00:00:00 2001 From: tomC Date: Thu, 16 Apr 2020 17:59:40 +0100 Subject: [PATCH 2/3] Updated docs to v1.2 - Update references to current version to be 1.2 - Include documentation on Deltas - Update gemspec version to 1.2.0 - Update jsbin examples - Created v1.1 versions of all versionable files --- content/code/sse/eventstream.code | 2 +- content/code/sse/sse.code | 2 +- .../versions/v1.1/authentication.textile | 413 +++++ .../versions/v1.1/channels.textile | 31 + .../versions/v1.1/history.textile | 19 + .../versions/v1.1/presence.textile | 10 + .../versions/v1.1/pubsub.textile | 21 + content/general/events/ifttt.textile | 2 +- .../v1.1/channel-rules-namespaces.textile | 13 + content/general/versions/v1.1/events.textile | 179 +++ .../versions/v1.1/events/aws-lambda.textile | 57 + .../versions/v1.1/events/azure.textile | 66 + .../versions/v1.1/events/cloudflare.textile | 66 + .../v1.1/events/google-functions.textile | 67 + .../versions/v1.1/events/ifttt.textile | 80 + .../versions/v1.1/events/zapier.textile | 64 + .../general/versions/v1.1/firehose.textile | 47 + content/general/versions/v1.1/push.textile | 64 + .../v1.1/push/activate-subscribe.textile | 456 ++++++ .../general/versions/v1.1/push/admin.textile | 142 ++ .../versions/v1.1/push/publish.textile | 614 ++++++++ content/general/versions/v1.1/queues.textile | 431 +++++ .../general/versions/v1.1/statistics.textile | 320 ++++ .../events/_batched_event_headers.textile | 2 +- .../events/_enveloped_event_headers.textile | 2 +- .../_non_enveloped_event_headers.textile | 2 +- .../partials/types/_channel_details.textile | 2 +- .../_authentication_capabilities.textile | 14 + .../_authentication_comparison.textile | 12 + .../events/_batched_event_headers.textile | 6 + .../general/events/_batched_events.textile | 109 ++ .../events/_enveloped_event_headers.textile | 6 + .../general/events/_enveloped_events.textile | 54 + .../events/_events_examples_intro.textile | 1 + .../_non_enveloped_event_headers.textile | 13 + .../events/_non_enveloped_events.textile | 36 + .../v1.1/general/push/_debugging_push.textile | 3 + .../v1.1/general/push/_push_intro.textile | 49 + .../versions/v1.1/realtime/_stats.textile | 49 + .../versions/v1.1/rest/_request.textile | 69 + .../versions/v1.1/rest/_stats.textile | 40 + .../v1.1/shared/_channel_enumeration.textile | 23 + .../v1.1/shared/_channel_metadata.textile | 10 + .../v1.1/shared/_channel_namespaces.textile | 11 + .../v1.1/shared/_presence_states.textile | 11 + .../v1.1/shared/_token_auth_methods.textile | 11 + .../_step-1-setup-free-account.textile | 11 + .../v1.1/types/_ably_exception.textile | 8 + .../versions/v1.1/types/_auth_options.textile | 28 + .../v1.1/types/_base_ably_exception.textile | 13 + .../v1.1/types/_channel_details.textile | 45 + .../v1.1/types/_channel_event.textile | 116 ++ .../v1.1/types/_channel_options.textile | 40 + .../v1.1/types/_channel_state.textile | 122 ++ .../v1.1/types/_channel_state_change.textile | 9 + .../types/_channel_state_listener.textile | 8 + .../v1.1/types/_cipher_params.textile | 16 + .../v1.1/types/_client_options.textile | 72 + .../v1.1/types/_completion_listener.textile | 11 + .../v1.1/types/_connection_event.textile | 143 ++ .../v1.1/types/_connection_state.textile | 135 ++ .../types/_connection_state_change.textile | 12 + .../types/_connection_state_listener.textile | 8 + .../versions/v1.1/types/_deferrable.textile | 30 + .../v1.1/types/_device_details.textile | 24 + .../versions/v1.1/types/_error_info.textile | 14 + .../types/_history_request_params.textile | 9 + .../types/_http_paginated_response.textile | 94 ++ .../types/_last_connection_details.textile | 14 + .../versions/v1.1/types/_local_device.textile | 8 + .../versions/v1.1/types/_message.textile | 60 + .../v1.1/types/_message_listener.textile | 8 + .../v1.1/types/_paginated_result.textile | 195 +++ .../versions/v1.1/types/_param.textile | 10 + .../v1.1/types/_presence_action.textile | 144 ++ .../v1.1/types/_presence_listener.textile | 8 + .../v1.1/types/_presence_message.textile | 66 + .../versions/v1.1/types/_push_admin.textile | 469 ++++++ .../versions/v1.1/types/_push_channel.textile | 71 + .../types/_push_channel_subscription.textile | 53 + .../versions/v1.1/types/_push_device.textile | 23 + .../types/_realtime_client_options.textile | 19 + .../_realtime_client_options_intro.textile | 12 + .../v1.1/types/_rest_client_options.textile | 3 + .../types/_rest_client_options_intro.textile | 11 + .../versions/v1.1/types/_stats.textile | 26 + .../v1.1/types/_stats_granularity.textile | 33 + .../v1.1/types/_stats_request_params.textile | 10 + .../v1.1/types/_token_details.textile | 48 + .../versions/v1.1/types/_token_params.textile | 33 + .../v1.1/types/_token_request.textile | 35 + content/realtime/channel-params.textile | 114 +- content/realtime/usage.textile | 4 +- .../versions/v1.1/authentication.textile | 600 +++++++ .../versions/v1.1/channel-metadata.textile | 127 ++ .../versions/v1.1/channel-params.textile | 141 ++ .../realtime/versions/v1.1/channels.textile | 1403 +++++++++++++++++ .../realtime/versions/v1.1/connection.textile | 726 +++++++++ .../realtime/versions/v1.1/encryption.textile | 294 ++++ .../realtime/versions/v1.1/history.textile | 502 ++++++ content/realtime/versions/v1.1/index.textile | 74 + .../realtime/versions/v1.1/messages.textile | 505 ++++++ .../realtime/versions/v1.1/presence.textile | 1199 ++++++++++++++ content/realtime/versions/v1.1/push.textile | 100 ++ .../realtime/versions/v1.1/statistics.textile | 132 ++ content/realtime/versions/v1.1/types.textile | 335 ++++ content/realtime/versions/v1.1/usage.textile | 428 +++++ content/rest-api/index.textile | 8 +- content/rest-api/versions/v1.1/beta.textile | 379 +++++ content/rest-api/versions/v1.1/index.textile | 1243 +++++++++++++++ .../versions/v1.1/token-request-spec.textile | 162 ++ content/rest/usage.textile | 4 +- .../rest/versions/v1.1/authentication.textile | 665 ++++++++ .../rest/versions/v1.1/channel-status.textile | 114 ++ content/rest/versions/v1.1/channels.textile | 598 +++++++ content/rest/versions/v1.1/encryption.textile | 327 ++++ content/rest/versions/v1.1/history.textile | 338 ++++ content/rest/versions/v1.1/index.textile | 76 + content/rest/versions/v1.1/messages.textile | 383 +++++ content/rest/versions/v1.1/presence.textile | 392 +++++ content/rest/versions/v1.1/push.textile | 84 + content/rest/versions/v1.1/statistics.textile | 166 ++ content/rest/versions/v1.1/types.textile | 230 +++ content/rest/versions/v1.1/usage.textile | 404 +++++ content/root/quick-start-guide.textile | 4 +- content/sse/index.textile | 16 +- content/sse/versions/v1.1/index.textile | 276 ++++ content/tutorials/channel-rewind.textile | 2 +- content/tutorials/encryption.textile | 4 +- content/tutorials/history.textile | 4 +- content/tutorials/publish-subscribe.textile | 4 +- .../queue-amqp-neutrino-profanity.textile | 6 +- .../queue-amqp-wolfram-alpha.textile | 6 +- .../queue-stomp-neutrino-profanity.textile | 6 +- content/tutorials/reactor-event-azure.textile | 2 +- .../tutorials/reactor-event-google.textile | 2 +- .../tutorials/sse-and-http-streaming.textile | 6 +- .../tutorials/token-authentication.textile | 8 +- data/jsbins.yaml | 8 +- docs.gemspec | 2 +- lib/helpers/versions_helper.rb | 3 +- 141 files changed, 18584 insertions(+), 60 deletions(-) create mode 100644 content/core-features/versions/v1.1/authentication.textile create mode 100644 content/core-features/versions/v1.1/channels.textile create mode 100644 content/core-features/versions/v1.1/history.textile create mode 100644 content/core-features/versions/v1.1/presence.textile create mode 100644 content/core-features/versions/v1.1/pubsub.textile create mode 100644 content/general/versions/v1.1/channel-rules-namespaces.textile create mode 100644 content/general/versions/v1.1/events.textile create mode 100644 content/general/versions/v1.1/events/aws-lambda.textile create mode 100644 content/general/versions/v1.1/events/azure.textile create mode 100644 content/general/versions/v1.1/events/cloudflare.textile create mode 100644 content/general/versions/v1.1/events/google-functions.textile create mode 100644 content/general/versions/v1.1/events/ifttt.textile create mode 100644 content/general/versions/v1.1/events/zapier.textile create mode 100644 content/general/versions/v1.1/firehose.textile create mode 100644 content/general/versions/v1.1/push.textile create mode 100644 content/general/versions/v1.1/push/activate-subscribe.textile create mode 100644 content/general/versions/v1.1/push/admin.textile create mode 100644 content/general/versions/v1.1/push/publish.textile create mode 100644 content/general/versions/v1.1/queues.textile create mode 100644 content/general/versions/v1.1/statistics.textile create mode 100644 content/partials/versions/v1.1/core-features/_authentication_capabilities.textile create mode 100644 content/partials/versions/v1.1/core-features/_authentication_comparison.textile create mode 100644 content/partials/versions/v1.1/general/events/_batched_event_headers.textile create mode 100644 content/partials/versions/v1.1/general/events/_batched_events.textile create mode 100644 content/partials/versions/v1.1/general/events/_enveloped_event_headers.textile create mode 100644 content/partials/versions/v1.1/general/events/_enveloped_events.textile create mode 100644 content/partials/versions/v1.1/general/events/_events_examples_intro.textile create mode 100644 content/partials/versions/v1.1/general/events/_non_enveloped_event_headers.textile create mode 100644 content/partials/versions/v1.1/general/events/_non_enveloped_events.textile create mode 100644 content/partials/versions/v1.1/general/push/_debugging_push.textile create mode 100644 content/partials/versions/v1.1/general/push/_push_intro.textile create mode 100644 content/partials/versions/v1.1/realtime/_stats.textile create mode 100644 content/partials/versions/v1.1/rest/_request.textile create mode 100644 content/partials/versions/v1.1/rest/_stats.textile create mode 100644 content/partials/versions/v1.1/shared/_channel_enumeration.textile create mode 100644 content/partials/versions/v1.1/shared/_channel_metadata.textile create mode 100644 content/partials/versions/v1.1/shared/_channel_namespaces.textile create mode 100644 content/partials/versions/v1.1/shared/_presence_states.textile create mode 100644 content/partials/versions/v1.1/shared/_token_auth_methods.textile create mode 100644 content/partials/versions/v1.1/shared/tutorials/_step-1-setup-free-account.textile create mode 100644 content/partials/versions/v1.1/types/_ably_exception.textile create mode 100644 content/partials/versions/v1.1/types/_auth_options.textile create mode 100644 content/partials/versions/v1.1/types/_base_ably_exception.textile create mode 100644 content/partials/versions/v1.1/types/_channel_details.textile create mode 100644 content/partials/versions/v1.1/types/_channel_event.textile create mode 100644 content/partials/versions/v1.1/types/_channel_options.textile create mode 100644 content/partials/versions/v1.1/types/_channel_state.textile create mode 100644 content/partials/versions/v1.1/types/_channel_state_change.textile create mode 100644 content/partials/versions/v1.1/types/_channel_state_listener.textile create mode 100644 content/partials/versions/v1.1/types/_cipher_params.textile create mode 100644 content/partials/versions/v1.1/types/_client_options.textile create mode 100644 content/partials/versions/v1.1/types/_completion_listener.textile create mode 100644 content/partials/versions/v1.1/types/_connection_event.textile create mode 100644 content/partials/versions/v1.1/types/_connection_state.textile create mode 100644 content/partials/versions/v1.1/types/_connection_state_change.textile create mode 100644 content/partials/versions/v1.1/types/_connection_state_listener.textile create mode 100644 content/partials/versions/v1.1/types/_deferrable.textile create mode 100644 content/partials/versions/v1.1/types/_device_details.textile create mode 100644 content/partials/versions/v1.1/types/_error_info.textile create mode 100644 content/partials/versions/v1.1/types/_history_request_params.textile create mode 100644 content/partials/versions/v1.1/types/_http_paginated_response.textile create mode 100644 content/partials/versions/v1.1/types/_last_connection_details.textile create mode 100644 content/partials/versions/v1.1/types/_local_device.textile create mode 100644 content/partials/versions/v1.1/types/_message.textile create mode 100644 content/partials/versions/v1.1/types/_message_listener.textile create mode 100644 content/partials/versions/v1.1/types/_paginated_result.textile create mode 100644 content/partials/versions/v1.1/types/_param.textile create mode 100644 content/partials/versions/v1.1/types/_presence_action.textile create mode 100644 content/partials/versions/v1.1/types/_presence_listener.textile create mode 100644 content/partials/versions/v1.1/types/_presence_message.textile create mode 100644 content/partials/versions/v1.1/types/_push_admin.textile create mode 100644 content/partials/versions/v1.1/types/_push_channel.textile create mode 100644 content/partials/versions/v1.1/types/_push_channel_subscription.textile create mode 100644 content/partials/versions/v1.1/types/_push_device.textile create mode 100644 content/partials/versions/v1.1/types/_realtime_client_options.textile create mode 100644 content/partials/versions/v1.1/types/_realtime_client_options_intro.textile create mode 100644 content/partials/versions/v1.1/types/_rest_client_options.textile create mode 100644 content/partials/versions/v1.1/types/_rest_client_options_intro.textile create mode 100644 content/partials/versions/v1.1/types/_stats.textile create mode 100644 content/partials/versions/v1.1/types/_stats_granularity.textile create mode 100644 content/partials/versions/v1.1/types/_stats_request_params.textile create mode 100644 content/partials/versions/v1.1/types/_token_details.textile create mode 100644 content/partials/versions/v1.1/types/_token_params.textile create mode 100644 content/partials/versions/v1.1/types/_token_request.textile create mode 100644 content/realtime/versions/v1.1/authentication.textile create mode 100644 content/realtime/versions/v1.1/channel-metadata.textile create mode 100644 content/realtime/versions/v1.1/channel-params.textile create mode 100644 content/realtime/versions/v1.1/channels.textile create mode 100644 content/realtime/versions/v1.1/connection.textile create mode 100644 content/realtime/versions/v1.1/encryption.textile create mode 100644 content/realtime/versions/v1.1/history.textile create mode 100644 content/realtime/versions/v1.1/index.textile create mode 100644 content/realtime/versions/v1.1/messages.textile create mode 100644 content/realtime/versions/v1.1/presence.textile create mode 100644 content/realtime/versions/v1.1/push.textile create mode 100644 content/realtime/versions/v1.1/statistics.textile create mode 100644 content/realtime/versions/v1.1/types.textile create mode 100644 content/realtime/versions/v1.1/usage.textile create mode 100644 content/rest-api/versions/v1.1/beta.textile create mode 100644 content/rest-api/versions/v1.1/index.textile create mode 100644 content/rest-api/versions/v1.1/token-request-spec.textile create mode 100644 content/rest/versions/v1.1/authentication.textile create mode 100644 content/rest/versions/v1.1/channel-status.textile create mode 100644 content/rest/versions/v1.1/channels.textile create mode 100644 content/rest/versions/v1.1/encryption.textile create mode 100644 content/rest/versions/v1.1/history.textile create mode 100644 content/rest/versions/v1.1/index.textile create mode 100644 content/rest/versions/v1.1/messages.textile create mode 100644 content/rest/versions/v1.1/presence.textile create mode 100644 content/rest/versions/v1.1/push.textile create mode 100644 content/rest/versions/v1.1/statistics.textile create mode 100644 content/rest/versions/v1.1/types.textile create mode 100644 content/rest/versions/v1.1/usage.textile create mode 100644 content/sse/versions/v1.1/index.textile diff --git a/content/code/sse/eventstream.code b/content/code/sse/eventstream.code index cee934586e..e6ff30fab9 100644 --- a/content/code/sse/eventstream.code +++ b/content/code/sse/eventstream.code @@ -1,7 +1,7 @@ [--- Javascript ---] var channel = "{{RANDOM_CHANNEL_NAME}}"; var apiKey = "{{API_KEY}}"; -var url = "https://realtime.ably.io/event-stream?v=1.1&key=" + apiKey + "&channels=" + channel; +var url = "https://realtime.ably.io/event-stream?v=1.2&key=" + apiKey + "&channels=" + channel; var xhttp = new XMLHttpRequest(); diff --git a/content/code/sse/sse.code b/content/code/sse/sse.code index 9787c27dda..ad6b06a5af 100644 --- a/content/code/sse/sse.code +++ b/content/code/sse/sse.code @@ -1,7 +1,7 @@ [--- Javascript ---] var channel = "{{RANDOM_CHANNEL_NAME}}"; var apiKey = "{{API_KEY}}"; -var url = "https://realtime.ably.io/sse?v=1.1&key=" + apiKey + "&channels=" + channel; +var url = "https://realtime.ably.io/sse?v=1.2&key=" + apiKey + "&channels=" + channel; var eventSource = new EventSource(url); eventSource.onopen = function() { diff --git a/content/core-features/versions/v1.1/authentication.textile b/content/core-features/versions/v1.1/authentication.textile new file mode 100644 index 0000000000..20033ade67 --- /dev/null +++ b/content/core-features/versions/v1.1/authentication.textile @@ -0,0 +1,413 @@ +--- +title: Auth and Security +section: core-features +index: 10 +languages: + - none +jump_to: + Authentication: + - Overview#title + - Tutorials & Examples#tutorials + - Basic authentication + - Token authentication + - Selecting an authentication mechanism#selecting-auth + - Capabilities explained + - Capability operations + - Understanding Identified clients#identified-clients + - Token Types#tokens + - Auth object +--- + +Both the "REST client library":/rest/authentication and the "Realtime client library":/realtime/authentication use common authentication mechanisms. The two schemes supported by Ably are "Basic Authentication":#basic-authentication, which uses your private "API key":https://support.ably.io/solution/articles/3000030054-what-is-an-app-api-key, and "Token Authentication":#token-authentication, which uses short-lived tokens for access which are periodically renewed. + +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. + +h3(#tutorials). Tutorials & Examples + +If you would like to just dive in and see an example of Ably authentication in code, then take a look at our "client-server authentication tutorials":/tutorials. + +h2(#basic-authentication). Basic Authentication + +Every application has one or more private API keys that you set up and "configure via the dashboard":https://support.ably.io/support/solutions/articles/3000030502-setting-up-and-managing-api-keys. These keys can be used to authenticate with Ably. It is recommended that private API keys are never shared with untrusted parties, and as such, should only be used by your trusted secure servers when authenticating with Ably. + +Basic authentication is the simplest way to authenticate with Ably. It is as simple as: + +bc[javascript](code-editor:authentication/basic-auth). var ably = new Ably.Realtime({ key: '{{API_KEY}}' }); + +h3. Process used by client libraries connecting with basic auth: + + + Basic authentication process diagram + + +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" + } */ + }); + + + Ably TokenRequest auth process diagram + + +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, + } */ + }); + + + Ably Token auth process diagram + + +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. + + + Ably JWT auth method + + +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. + + + Token embedded in a JWT auth method + + +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. + + + Pub/Sub in Ably + + +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. + + + Pub/Sub in Ably + + +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. + + + History representation + + +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. + + Presence representation + + 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. + + + Pub/Sub in Ably + + +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. + + + Pub/Sub in Ably + + +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. + + + Ably Reactor Events diagram + + +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 + +
+ +If the Rule is in the *Single Request* mode, it will also have the following options: + +- "Enveloped":#envelope := If the rule has the Enveloped option set, then data delivered by this Rule will be wrapped in an "Ably envelope":#envelope. Otherwise, the Rule will send the "raw payload":#no-envelope-examples + +
+ +If the Rule is in the *Batch Request* mode, it will have the following additional options: + +- Sign with key := Ably will optionally sign the data with the specified private key. This will be included as an HTTP header @X-Ably-Signature@ in every HTTP post request issued to your server. See "webhook security":#security for more details. + +*Note* that various integrations have restrictions on them which will mean some of these base options are either changed or absent. You can check specific details in each "integration's page":#integrations. + +h3(#sources). Sources + +Ably currently supports three types of Events: + +* "Messages":#messages - messages trigger function calls as soon as they are published on a channel +* "Presence events":#presence - presence events trigger function calls when clients enter, update their data, or leave channels +* "Channel lifecycle events":/realtime/channel-metadata – get notified 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). Currently this is *only* available for "batched":#batching messages + +h3(#batching). Single vs Batched requests + +If *Single request* is chosen for a Reactor Event Rule, then a @POST@ request will be sent to your specified endpoint/service each time an event occurs. Although this can be useful for some use-cases where the endpoint can only process one message per request, or needs the event as soon as it's available, it can result in the endpoint being overloaded with requests. To avoid this, it's possible to instead make use of *Batch request* instead, which will batch messages sent within a set timeframe together. + +h4(#single-request). Single request details + +Single request is best suited for scenarios where you're wanting a 1-to-1 relationship between sent messages and events being called. If you are making use of a serverless system which is expecting a single piece of data each time, and then intends to perform some transformation/event following that, then Single request will likely work well for you. If you're using a single server, which has the potential to be overloaded by requests, and can process multiple events per payload sent, Batch request will be a better choice. + +minimize. View single request transport details + h5(#single-rate-limits). Rate limits + + * Free accounts are limited to 15 Reactor invocations per second on single requests, whilst paid are limited to 30. + * Webhook requests are made with a default timeout of 15s. If the request fails or times out, Ably retries the request with exponential backoff (base delay 1s, backoff factor sqrt(2), up to a max of 60s) + * Multiple requests can be in-flight at once, up to the "max concurrency limit":https://support.ably.io/a/solutions/articles/3000053845#reactor-limits. If the number of in-flight requests exceeds the max concurrency limit, new messages coming in are placed in a short queue (length 10); if that queue length is exceeded, further messages are rejected + + h5(#single-failures). Failures and back off + + * If a request is rejected with @5xx@ or times out, it will be retried twice more, once after 4s, then if that fails, again after 20s + +h4(#batch-request). Batch request details + +Batch requests are useful for endpoints which have the potential to be overloaded by requests, or simply have no preference requirement for processing messages sent one-by-one. If you are using an endpoint which has either of these requirements (for example "IFTTT":/general/events/ifttt requires one event per request), you should use Single request. + +minimize. View batch request transport details + Webhook batched requests are typically published at most once per second per configured webhook. + + h5(#batch-rate-limits). Rate limits + + * For each configured webhook, up to one request per second will be made to the configured endpoint URL + * The first event that matches a configured webhook will trigger a webhook request immediately. Therefore, if you have a low volume of events you are listening to, in most cases your request should arrive in under a second from the time the event was generated + * webhook requests are made with a default timeout of 15s. If the request fails or times out, Ably retries the request with exponential backoff (base delay 1s, backoff factor sqrt(2), up to a max of 60s) + * Once a webhook request is triggered, all other events will be queued so that they can be delivered in a batch in the next request. The next webhook request will be issued within one second with the following caveats: + ** Only a limited number of http requests are in-flight at one time for each configured webhook. Therefore, if you want to be notified quickly, we recommend you accept requests quickly and defer any work to be done asynchronously + ** If there are more than 1,000 events queued for the next webhook, the oldest 1,000 events will be bundled into the next webhook and the remaining events will be delivered in the next webhook. Therefore, if your sustained rate of events is expected to be more than 1,000 per second or your servers are slow to respond, then it is possible a backlog will build up and you will not receive all events. "Get in touch if you need a higher sustained rate":https://www.ably.io/contact. + + h5(#batch-failures). Failures and back off + + * If the endpoint for any of the webhook requests respond with an HTTP status code that does not indicate success i.e. @200 - 209@, then Ably will retry that failed request + * Every retry is performed with an incrementing back off that is calculated as @delay = delay * sqrt(2)@ where delay is initially @1@. For example, if the initial webhook request fails, and subsequent for retries fail, the back off delays for each request would look as follows: @initial request > wait 1.4s > 1st retry > wait 2s > 2nd retry > wait 2.8s > 3rd retry > wait 4s > 4th retry > wait 5.6s > successful request@ + * The back off for consecutively failing requests will increase until it reaches 60s. All subsequent retries for failed requests will then be made every 60s until a request is successful + * The queue of events is retained for 5 minutes. If an event cannot be delivered within 5 minutes, then the events are discarded to prevent the queue from growing indefinitely + +h3(#envelope). Envelopes + +When you configure a Reactor Event rule using "single requests":#batching, 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, if you don't need anything besides the payload of each message, or the endpoint expects a very restricted data structure, 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. + +Check out examples of "enveloped":#envelope-examples and "non-enveloped":#no-envelope-examples examples down below. + +h3(#encoding). Payload Encoding + +The encoding of payloads sent is defined when setting up a Reactor Rule in the "Reactor tab of your app":https://support.ably.io/solution/articles/3000074406. This only applies to "enveloped":#envelope messages and their structure, non-enveloped messages will remain their original format. You can have the message sent in JSON format, or as a "MessagePack":https://msgpack.org payload. + +* "JSON":https://www.json.org (JavaScript Object Notation): An efficient data-interchange format which is fairly standard and provides simple text based encoding. +* "MessagePack":https://msgpack.org: An efficient binary serialization format that is similar to JSON, but smaller. This is notably useful for binary payloads, as a JSON envelope with a binary payload would need to have the payload base64-encoded + +h2(#security). Webhook security + +We encourage customers to use a secure HTTPS URL when configuring their Reactor Events. This will ensure that requests cannot be intercepted and all communication with your servers is secured with TLS. + +However, in addition, we optionally support a signature included as an HTTP header @X-Ably-Signature@ in "batched":#batching requests. The endpoint can use the chosen private API key to verify the authenticity of the webhook data. + +In order to verify the signature, you need to do the following: + +* start with the webhook request body. This will be a JSON string encoded with content-encoding @utf-8@; +* identify the key based on the @keyId@ indicated in the @X-Ably-Key@ header; +* calculate the HMAC of that request body with algorithm SHA-256 and the key being the corresponding @keyValue@ (the secret part of the key after the "@:@"); +* encode the resulting HMAC using RFC 3548 base 64; +* compare that result with the signature value indicated in the @X-Ably-Signature@ header + +h3(#example-signature). Webhook HMAC SHA-256 signature verification example + +If you choose to sign your webhook requests, we recommend you try the following first: + +# "Set up a free RequestBin HTTP endpoint test URL":https://requestbin.com/ +# "Configure a webhook":#configure with the URL set to the RequestBin endpoint, and ensure you have chosen to "batch":#batching messages and are using a key to sign each webhook request +# Trigger an event using the "Dev Console":https://support.ably.io/support/solutions/articles/3000062195 in your app dashboard which will generate a webhook. You should then confirm that the webhook has been received in your RequestBin +# Check that the @X-Ably-Signature@ header in your webhook request matches the HMAC SHA-256 you create using our "Javascript HMAC SHA-256 demo":<%= JsBins.url_for('reactor/webhook-hmac-sha-256') %> + +h2(#examples). Examples + +<%= partial partial_version('general/events/_events_examples_intro') %> + +h3(#batch-examples). Batched event payloads + +<%= partial partial_version('general/events/_batched_event_headers') %> + +<%= partial partial_version('general/events/_batched_events') %> + +h3(#envelope-examples). Enveloped event payloads + +<%= partial partial_version('general/events/_enveloped_event_headers') %> + +<%= partial partial_version('general/events/_enveloped_events') %> + +h3(#no-envelope-examples). Non-enveloped event payloads + +<%= partial partial_version('general/events/_non_enveloped_event_headers') %> + +<%= partial partial_version('general/events/_non_enveloped_events') %> diff --git a/content/general/versions/v1.1/events/aws-lambda.textile b/content/general/versions/v1.1/events/aws-lambda.textile new file mode 100644 index 0000000000..d323dcf338 --- /dev/null +++ b/content/general/versions/v1.1/events/aws-lambda.textile @@ -0,0 +1,57 @@ +--- +title: AWS Lambda Functions +section: general +index: 22 +languages: + - none +jump_to: + Help with: + - Fields#fields + - Restrictions#restrictions + - Examples#examples +--- + +As part of "Reactor Events":/general/events, it is possible to integrate with various systems. + +"AWS Lambda Functions":https://aws.amazon.com/lambda provide event-driven serverless compute functions which allow users to easily run code whenever events are sent to it. These can be very useful for integrating into various other AWS systems. + +h2(#tutorials). Tutorials & Examples + +If you would like to just dive in and see an example of this being implemented, then take a look at our "AWS Lambda tutorial":/tutorials/reactor-event-aws. + +For more details on the specifics of how Reactor Events and our integrations work, check out our "Reactor Events documentation":/general/events. + +h2(#fields). Rule fields + +- AWS Region := The region you chose for your "AWS Lambda function":https://docs.aws.amazon.com/general/latest/gr/rande.html#lambda_region +- Function Name := The name you gave your "AWS Lambda function":https://docs.aws.amazon.com/lambda/latest/dg/getting-started.html +- AWS Authentication Scheme := the "authentication scheme":https://support.ably.io/support/solutions/articles/3000071908 you wish to use for your function. Either @AWS Credentials@ or @ARN of an assumable role@ +- Qualifier := optional qualifier for your AWS Lambda function +- "Source":/general/events#sources := Choose which of @Message@ or @Presence@ events on channels should activate this Reactor Event Rule. +- 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 + +
+ +If the Rule is using *AWS Credentials* to authenticate, it will also have the following options: + +- AWS Credentials := a key:value pair which match your "AWS credentials":https://support.ably.io/support/solutions/articles/3000071908 + +
+ +If the Rule is using *ARN of an assumable role* to authenticate, it will also have the following options: + +- Assume Role ARN := your "ARN":https://support.ably.io/support/solutions/articles/3000071908 which will work for this AWS Lambda function + +h2(#restrictions). Restrictions + +At present, it is not possible to "batch":/general/event#batching messages to AWS Lambda Functions, nor can messages be "encoded":/general/events#encoding in anything besides JSON. + +h2(#examples). Examples + +h3(#envelope-examples). Enveloped event payloads + +<%= partial partial_version('general/events/_enveloped_events') %> + +h3(#no-envelope-examples). Non-enveloped event payloads + +<%= partial partial_version('general/events/_non_enveloped_events') %> diff --git a/content/general/versions/v1.1/events/azure.textile b/content/general/versions/v1.1/events/azure.textile new file mode 100644 index 0000000000..6a87d01e79 --- /dev/null +++ b/content/general/versions/v1.1/events/azure.textile @@ -0,0 +1,66 @@ +--- +title: Azure Functions +section: general +index: 23 +languages: + - none +jump_to: + Help with: + - Fields#fields + - Example#examples +--- + +As part of "Reactor Events":/general/events, it is possible to integrate with various systems. + +"Azure Functions":https://azure.microsoft.com/en-gb/services/functions provide event-driven serverless compute functions which allow users to easily run code whenever events are sent to it. + +h2(#tutorials). Tutorials & Examples + +If you would like to just dive in and see an example of this being implemented, then take a look at our "Azure Functions tutorial":/tutorials/reactor-event-azure. + +For more details on the specifics of how Reactor Events and our integrations work, check out our "Reactor Events documentation":/general/events. + +h2(#fields). Rule fields + +- Azure App ID := The App ID of your "Azure app":https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-azure-function#create-a-function-app +- Function Name := the name of your "Azure Function":https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-azure-function#create-a-function-app +- Headers := Optionally allows you to provide a set of headers that will be included in all HTTP POST requests. You must use format @"XCustom-Header-1:value1"@ for each header you add +- "Source":/general/events#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":/general/events#batching mode +- "Request Mode":/general/events#batching := This will either be in @Single Request@ mode or @Batch Request@ mode. "Single Request":/general/events#single-request will send each event as separately to the endpoint specified by the Rule. "Batch Request":/general/events#batch-request will send events triggered concurrently 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 := The encoding to be used by this Rule. This can be either JSON or "MsgPack":http://msgpack.org + +
+ +If the Rule is in the *Single Request* mode, it will also have the following options: + +- "Enveloped":/general/events#enveloping := If the rule has the Enveloped option set, then data delivered by this Rule will be wrapped in an "Ably envelope":/general/events#envelope-examples. Otherwise, the Rule will send the "raw payload":/general/events#no-envelope-examples + +
+ +If the Rule is in the *Batch Request* mode, it will have the following additional options: + +- Sign with key := Ably will optionally sign the data with the specified private key. This will be included as an HTTP header @X-Ably-Signature@ in every HTTP post request issued to your server. See "webhook security":/general/events#security for more details. + +h2(#examples). Examples + +<%= partial partial_version('general/events/_events_examples_intro') %> + +h3(#batch-examples). Batched event payloads + +<%= partial partial_version('general/events/_batched_event_headers') %> + +<%= partial partial_version('general/events/_batched_events') %> + +h3(#envelope-examples). Enveloped event payloads + +<%= partial partial_version('general/events/_enveloped_event_headers') %> + +<%= partial partial_version('general/events/_enveloped_events') %> + +h3(#no-envelope-examples). Non-enveloped event payloads + +<%= partial partial_version('general/events/_non_enveloped_event_headers') %> + +<%= partial partial_version('general/events/_non_enveloped_events') %> + diff --git a/content/general/versions/v1.1/events/cloudflare.textile b/content/general/versions/v1.1/events/cloudflare.textile new file mode 100644 index 0000000000..467c9a0c92 --- /dev/null +++ b/content/general/versions/v1.1/events/cloudflare.textile @@ -0,0 +1,66 @@ +--- +title: Cloudflare Workers +section: general +index: 26 +languages: + - none +jump_to: + Help with: + - Fields#fields + - Restrictions#restrictions + - Example#examples +--- + +As part of "Reactor Events":/general/events, it is possible to integrate with various systems. + +"Cloudflare Workers":https://www.cloudflare.com/en-gb/products/cloudflare-workers allow you to make use of Cloudflare’s Edge Network to distribute your normal JavaScript-based functions. + +h2(#tutorials). Tutorials & Examples + +If you would like to just dive in and see an example of this being implemented, then take a look at our "Cloudflare Worker tutorial":/tutorials/reactor-event-cloudflare. + +For more details on the specifics of how Reactor Events and our integrations work, check out our "Reactor Events documentation":/general/events. + +h2(#fields). Rule fields + +- URL := The URL of your Clouflare Worker +- Custom headers := Optionally allows you to provide a set of headers that will be included in all HTTP POST requests. You must use format @"XCustom-Header-1:value1"@ for each header you add +- "Source":/general/events#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":/general/events#batching mode +- "Request Mode":/general/events#batching := This will either be in @Single Request@ mode or @Batch Request@ mode. "Single Request":/general/events#single-request will send each event as separately to the endpoint specified by the Rule. "Batch Request":/general/events#batch-request will send events triggered concurrently 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 := The encoding to be used by this Rule. This can be either JSON or "MsgPack":http://msgpack.org + +
+ +If the Rule is in the *Single Request* mode, it will also have the following options: + +- "Enveloped":/general/events#enveloping := If the rule has the Enveloped option set, then data delivered by this Rule will be wrapped in an "Ably envelope":/general/events#envelope-examples. Otherwise, the Rule will send the "raw payload":/general/events#no-envelope-examples + +
+ +If the Rule is in the *Batch Request* mode, it will have the following additional options: + +- Sign with key := Ably will optionally sign the data with the specified private key. This will be included as an HTTP header @X-Ably-Signature@ in every HTTP post request issued to your server. See "webhook security":/general/events#security for more details. + +h2(#examples). Examples + +<%= partial partial_version('general/events/_events_examples_intro') %> + +h3(#batch-examples). Batched event payloads + +<%= partial partial_version('general/events/_batched_event_headers') %> + +<%= partial partial_version('general/events/_batched_events') %> + +h3(#envelope-examples). Enveloped event payloads + +<%= partial partial_version('general/events/_enveloped_event_headers') %> + +<%= partial partial_version('general/events/_enveloped_events') %> + +h3(#no-envelope-examples). Non-enveloped event payloads + +<%= partial partial_version('general/events/_non_enveloped_event_headers') %> + +<%= partial partial_version('general/events/_non_enveloped_events') %> + diff --git a/content/general/versions/v1.1/events/google-functions.textile b/content/general/versions/v1.1/events/google-functions.textile new file mode 100644 index 0000000000..bf193a7868 --- /dev/null +++ b/content/general/versions/v1.1/events/google-functions.textile @@ -0,0 +1,67 @@ +--- +title: Google Cloud Functions +section: general +index: 24 +languages: + - none +jump_to: + Help with: + - Fields#fields + - Restrictions#restrictions + - Example#examples +--- + +As part of "Reactor Events":/general/events, it is possible to integrate with various systems. + +"Google Cloud Functions":https://cloud.google.com/functions provide event-driven serverless compute functions which allow users to easily run code whenever events are sent to it. + +h2(#tutorials). Tutorials & Examples + +If you would like to just dive in and see an example of this being implemented, then take a look at our "Google Cloud Functions tutorial":/tutorials/reactor-event-google. + +For more details on the specifics of how Reactor Events and our integrations work, check out our "Reactor Events documentation":/general/events. + +h2(#fields). Rule fields + +- Region := the region in which "your Google Function is hosted":https://cloud.google.com/compute/docs/regions-zones. +- Project ID := the project ID for your "Google Cloud Project":https://cloud.google.com that was generated when you created your project +- Function := the name of your "Google Function":https://cloud.google.com/functions +- Headers := Optionally allows you to provide a set of headers that will be included in all HTTP POST requests. You must use format @"XCustom-Header-1:value1"@ for each header you add +- "Source":/general/events#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":/general/events#batching mode +- "Request Mode":/general/events#batching := This will either be in @Single Request@ mode or @Batch Request@ mode. "Single Request":/general/events#single-request will send each event as separately to the endpoint specified by the Rule. "Batch Request":/general/events#batch-request will send events triggered concurrently 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 := The encoding to be used by this Rule. This can be either JSON or "MsgPack":http://msgpack.org + +
+ +If the Rule is in the *Single Request* mode, it will also have the following options: + +- "Enveloped":/general/events#enveloping := If the rule has the Enveloped option set, then data delivered by this Rule will be wrapped in an "Ably envelope":/general/events#envelope-examples. Otherwise, the Rule will send the "raw payload":/general/events#no-envelope-examples + +
+ +If the Rule is in the *Batch Request* mode, it will have the following additional options: + +- Sign with key := Ably will optionally sign the data with the specified private key. This will be included as an HTTP header @X-Ably-Signature@ in every HTTP post request issued to your server. See "webhook security":/general/events#security for more details. + +h2(#examples). Examples + +<%= partial partial_version('general/events/_events_examples_intro') %> + +h3(#batch-examples). Batched event payloads + +<%= partial partial_version('general/events/_batched_event_headers') %> + +<%= partial partial_version('general/events/_batched_events') %> + +h3(#envelope-examples). Enveloped event payloads + +<%= partial partial_version('general/events/_enveloped_event_headers') %> + +<%= partial partial_version('general/events/_enveloped_events') %> + +h3(#no-envelope-examples). Non-enveloped event payloads + +<%= partial partial_version('general/events/_non_enveloped_event_headers') %> + +<%= partial partial_version('general/events/_non_enveloped_events') %> diff --git a/content/general/versions/v1.1/events/ifttt.textile b/content/general/versions/v1.1/events/ifttt.textile new file mode 100644 index 0000000000..b9478fa644 --- /dev/null +++ b/content/general/versions/v1.1/events/ifttt.textile @@ -0,0 +1,80 @@ +--- +title: IFTTT +section: general +index: 27 +languages: + - none +jump_to: + Help with: + - Fields#fields + - Restrictions#restrictions + - Example#example +--- + +As part of "Reactor Events":/general/events, it is possible to integrate with various systems via webhooks. + +"IFTTT":https://ifttt.com allows for simple conditional chains, helping to combine various services together. Ably can integrate into IFTTT through their "webhooks":https://ifttt.com/maker_webhooks functionality. + +h2(#tutorials). Tutorials & Examples + +If you would like to just dive in and see an example of this being implemented, then take a look at our "IFTTT tutorial":/tutorials/reactor-event-ifttt. + +For more details on the specifics of how Reactor Events and our integrations work, check out our "Reactor Events documentation":/general/events. + +h2(#fields). Rule fields + +- IFTTT Webhook key := Your IFTTT account's unique webhook key. This is obtained from going to their "Webhooks page":https://ifttt.com/maker_webhooks, then going to @Documentation@ +- Event name := The Event name is used to identify the IFTTT applet that receives the Reactor Event, make sure you write the same name when creating the IFTTT applet +- Headers := Optional headers to send along with any message +- Source := Choose which events on channels should activate this Reactor Event Rule. Currently IFTTT rules only support @message@ and @presence@ due to the "restrictions":#restrictions detailed below +- 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 := The encoding to be used by this Rule. This can only be @JSON@ for IFTTT due to the "restrictions":#restrictions detailed below + +h2(#restrictions). Restrictions + +IFTTT has limitations on the data it can process. All payloads are expected to be @JSON@, with keys of @value1@, @value2@, or @value3@. A payload of anything besides @JSON@, or any key:values beyond these cannot be processed. + +Due to this, we do not support "enveloping":/general/events#envelope, "batching":/general/events#batching, or MsgPack encoding for IFTTT. In addition, any communication which requires decoding on the other end, such as messages sent via "MQTT":/mqtt will not be able to pass data on to IFTTT. + +In addition, if you are wanting data to be passed through and processed by IFTTT, you will need to ensure relevant data matches the above structure. For example, the data of a message or presence message sent through IFTTT should conform to the following structure: + +```[json] +{ + "value1" :"data I want to send 1", + "value2" :"data I want to send 2", + "value3" :"data I want to send 3" +} +``` + +In the future we would like provide transformation capabilities, allowing for data sent on "channels":/realtime/channels to be re-structured prior to being sent to an endpoint. For IFTTT, this would allow for channel communication to not need to adhere to the IFTTT data structure expectations. If this would be of interest to you, please "get in touch":/contact. + +h2(#example). Example + +For a "message data":/realtime/messages#data or "presence message data":/realtime/presence#data of @{ "value1": "My first message", "value2": "My second message"}@, the following would be sent to your IFTTT endpoint: + +*Headers*: + +```[text] +host: https://maker.ifttt.com/trigger/{YOUR_EVENT}/with/key/{YOUR_IFTTT_KEY} +content-type: application/json +x-ably-envelope-appid: {YOUR_APP_ID} +x-ably-envelope-channel: {YOUR_CHANNEL} +x-ably-envelope-rule-id: {YOUR_RULE_ID} +x-ably-envelope-site: {ably-server-location} +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.1 +content-length: 18 +connection: keep-alive +``` + +*Payload*: + +```[json] +{ + "value1": "My first message", + "value2": "My second message" +} +``` diff --git a/content/general/versions/v1.1/events/zapier.textile b/content/general/versions/v1.1/events/zapier.textile new file mode 100644 index 0000000000..6000f09cfc --- /dev/null +++ b/content/general/versions/v1.1/events/zapier.textile @@ -0,0 +1,64 @@ +--- +title: Zapier +section: general +index: 25 +languages: + - none +jump_to: + Help with: + - Fields#fields + - Example#examples +--- + +As part of "Reactor Events":/general/events, it is possible to integrate with various systems via webhooks. + +"Zapier":https://zapier.com provides simple integrations into thousands of different services. It's possible to plug into this using their "webhooks":https://zapier.com/page/webhooks feature. + +h2(#tutorials). Tutorials & Examples + +If you would like to just dive in and see an example of this being implemented, then take a look at our "Zapier tutorial":/tutorials/reactor-event-zapier. + +For more details on the specifics of how Reactor Events and our integrations work, check out our "Reactor Events documentation":/general/events. + +h2(#fields). Rule fields + +- URL := The URL of your Zapier endpoint +- Custom headers := Optionally allows you to provide a set of headers that will be included in all HTTP POST requests. You must use format @"XCustom-Header-1:value1"@ for each header you add +- "Source":/general/events#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":/general/events#batching mode +- "Request Mode":/general/events#batching := This will either be in @Single Request@ mode or @Batch Request@ mode. "Single Request":/general/events#single-request will send each event as separately to the endpoint specified by the Rule. "Batch Request":/general/events#batch-request will send events triggered concurrently 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 := The encoding to be used by this Rule. This can be either JSON or "MsgPack":http://msgpack.org + +
+ +If the Rule is in the *Single Request* mode, it will also have the following options: + +- "Enveloped":/general/events#enveloping := If the rule has the Enveloped option set, then data delivered by this Rule will be wrapped in an "Ably envelope":/general/events#envelope-examples. Otherwise, the Rule will send the "raw payload":/general/events#no-envelope-examples + +
+ +If the Rule is in the *Batch Request* mode, it will have the following additional options: + +- Sign with key := Ably will optionally sign the data with the specified private key. This will be included as an HTTP header @X-Ably-Signature@ in every HTTP post request issued to your server. See "webhook security":/general/events#security for more details. + +h2(#examples). Examples + +<%= partial partial_version('general/events/_events_examples_intro') %> + +h3(#batch-examples). Batched event payloads + +<%= partial partial_version('general/events/_batched_event_headers') %> + +<%= partial partial_version('general/events/_batched_events') %> + +h3(#envelope-examples). Enveloped event payloads + +<%= partial partial_version('general/events/_enveloped_event_headers') %> + +<%= partial partial_version('general/events/_enveloped_events') %> + +h3(#no-envelope-examples). Non-enveloped event payloads + +<%= partial partial_version('general/events/_non_enveloped_event_headers') %> + +<%= partial partial_version('general/events/_non_enveloped_events') %> diff --git a/content/general/versions/v1.1/firehose.textile b/content/general/versions/v1.1/firehose.textile new file mode 100644 index 0000000000..0e8856871e --- /dev/null +++ b/content/general/versions/v1.1/firehose.textile @@ -0,0 +1,47 @@ +--- +title: Reactor Firehose +section: general +index: 26 +--- + +Our Firehose can stream your realtime data published within the Ably platform directly to another streaming or queueing service. For example, all messages published by any device on a channel could be immediately streamed to Amazon Kinesis allowing you to process this data in realtime. Firehose is offered as part of our "Ably Reactor service":https://www.ably.io/reactor but is only available to "Enterprise customers":https://www.ably.io/pricing/enterprise. + +Using configurable rules, you can stream any of messages, presence events, and "channel lifecycle events":/realtime/channel-metadata#lifecycle-events. Unlike our "channels":/realtime/channels which follow a "pub/sub pattern":https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern where each message is delivered to any number of subscribers, our Firehose operates on the basis that each message is delivered once to your streaming or queueing server. + +As each message is delivered once to your streaming or queueing server, this design is commonly used to process realtime data published by Ably asynchronously. For example, using workers consuming from your stream or queue, you could persist each message of a live chat to your own database, start publishing updates once a channel becomes active, or trigger an event if a device has submitted a location that indicates that it has reached its destination. Find out why we think streams and message queues help solve many of the challenges associated with consuming pub/sub data server-side in our article: "Message queues — the right way to process and work with realtime data on your servers":https://blog.ably.io/message-queues-the-right-way-to-process-and-work-with-realtime-data-on-your-servers-2d15985301f8. + +Please note that if you want to consume realtime data from a queue, you should take a look at our "Reactor Queues":/general/queues. They provide a straightforward and robust way to consume realtime from your worker servers without having to worry about queueing infrastructure. + + + Ably Reactor Firehose diagram + + +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. + + + Push Notifications process using Ably + + +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:​did​Fail​To​Register​For​Remote​Notifications​With​Error:​@":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 + + + Push Notifications direct device registration + + +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. + + + Push Notifications device registration via 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] + + + + + + + ``` + + ```[android] + public class MyAblyBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + AblyRealtime ably = getAblyRealtime(); + String action = intent.getAction(); + + if (action.equals("io.ably.broadcast.PUSH_REGISTER_DEVICE")) { + DeviceDetails device = ably.device(context); + boolean isNew = intent.getBooleanExtra("isNew", false); + + Intent response = new Intent("io.ably.broadcast.PUSH_DEVICE_REGISTERED"); + + try { + String deviceIdentityToken = registerThroughYourServer(device, isNew); + response.putExtra("deviceIdentityToken", deviceIdentityToken); + } catch(AblyException e) { + IntentUtils.addErrorInfo(intent, e.errorInfo); + } + + LocalBroadcastManager.getInstance(context.getApplicationContext()).sendBroadcast(intent); + } else if (action.equals("io.ably.broadcast.PUSH_DEREGISTER_DEVICE")) { + DeviceDetails device = ably.device(context); + + Intent response = new Intent("io.ably.broadcast.PUSH_DEVICE_DEREGISTERED"); + + try { + deregisterThroughYourServer(device.id); + } catch(AblyException e) { + IntentUtils.addErrorInfo(intent, e.errorInfo); + } + + LocalBroadcastManager.getInstance(context.getApplicationContext()).sendBroadcast(intent); + } + } + } + ``` + +h2(#server-vs-direct-registration). Direct registration vs. registration via your server + +Two options for registering devices gives you the flexibility to implement push notifications in the best way for each use case. + +Direct registration means less server side code as the device registers itself and attaches to a channel. However, this means you need to trust your devices to handle this securely and efficiently. Token authentication solves this. + +If you want to control all of your devices server side then registration via server is the way to go. The Ably Push Admin API lets you do this. + +<%= partial partial_version('general/push/_push_intro') %> + +h1. Push Device API reference + +inline-toc. + Methods: + - activate()#activate + - deactivate()#deactivate + Related types: + - DeviceDetails#device-details + - LocalDevice#localdevice + - PushChannel#push-channel + - PushChannelSubscription#push-channel-subscription + - PaginatedResult#paginated-result + +<%= partial partial_version('types/_push_device') %> + +h2(#related-types). Related types + +h3(#device-details). + default: DeviceDetails + ruby: Ably::Models::DeviceDetails + swift,objc: ARTDeviceDetails + +<%= partial partial_version('types/_device_details') %> + +h3(#local-device). + default: LocalDevice + ruby: Ably::Models::LocalDevice + +<%= partial partial_version('types/_local_device') %> + +h3(#push-channel). + default: PushChannel + ruby: Ably::Models::PushChannel + +<%= partial partial_version('types/_push_channel') %> + +h3(#push-channel-subscription). + default: PushChannelSubscription + ruby: Ably::Models::PushChannelSubscription + java,android: ChannelSubscription + swift,objc: ArtPushChannelSubscription + +<%= partial partial_version('types/_push_channel_subscription') %> + +h3(#paginated-result). + default: PaginatedResult + swift,objc: ARTPaginatedResult + ruby: Ably::Models::PaginatedResult + java,android: io.ably.lib.types.PaginatedResult + csharp: IO.Ably.PaginatedResult + +<%= partial partial_version('types/_paginated_result') %> diff --git a/content/general/versions/v1.1/push/admin.textile b/content/general/versions/v1.1/push/admin.textile new file mode 100644 index 0000000000..a5a6fd19fc --- /dev/null +++ b/content/general/versions/v1.1/push/admin.textile @@ -0,0 +1,142 @@ +--- +title: Push Notifications - Admin +section: general +index: 43 +hide_from_nav: true +api_separator: + Tutorials: + - Push Tutorials:/tutorials#tut-push-notifications +languages: + - javascript + - nodejs + - ruby + - php + - python + - swift + - objc + - java + - android +jump_to: + Help with: + - Push Admin API Overview + API reference: + - publish#publish + - DeviceRegistrations#device-registrations-object + - PushChannelSubscriptions#push-channel-subscriptions + Types: + - Related Types#related-types +--- + +The client libraries provide a Push Admin API that is intended to be used on a customer's servers to perform all the management tasks relating to registering devices, managing push device subscriptions and delivering push notifications directly to devices or devices associated with a client identifier. + +The push admin API is accessible via the @push@ attribute of the realtime client. For example to publish a push notification directly to a device, you would access the "@publish@":#publish method as follows: + +```[jsall] +var rest = new Ably.Rest({ key: apiKey }); +rest.push.admin.publish(recipient, data); +``` +```[ruby] +rest = Ably::Rest.new(key: api_key) +result = rest.push.admin.publish(recipient, data) +``` +```[python] +rest = AblyRest('{{API_KEY}}') +rest.push.admin.publish(recipient, data) +``` +```[php] +$rest = new Ably\AblyRest('{{API_KEY}}'); +$rest->push->admin->publish( $recipient, $data ); +``` +```[swift] +let rest = ARTRest(key: "{{API_KEY}}") +rest.push.admin.publish(recipient, data: ["data": ["foo": "bar"]]) +``` +```[java,android] +AblyRest rest = new AblyRest("{{API_KEY}}"); +rest.push.admin.publishAsync(recipient, data); +``` +```[go] +rest := ably.NewRestClient(ably.NewClientOptions("{{API_KEY}}")) +// Go does not have access to the push admin directly, so make use of the Request method +rest.Request('POST', '/push/publish', nil, { "recipient": recipient, "data:" data }, nil) +``` +```[csharp] +Rest rest = new AblyRest("{{API_KEY}}"); +// C# .NET does not have access to the push admin directly, so make use of the Request method +var body = JToken.Parse("{ \"recipient\": recipient, \"data\": data }"); +Rest.Request("POST", "/push/publish", null, body, null); +``` + +The push admin API offers three key features: + +* "@publish@":#publish for "direct publishing to devices and groups of identified devices":/general/push/publish#direct-publishing +* "@deviceRegistrations@":#device-registrations for registering, updating, listing and de-registering push devices +* "@channelSubscriptions@":#channel-subscriptions for subscribing, listing and unsubscribing individual devices or groups of identified devices to push notifications published on channels + +h2(#access-control). Push admin access control and device authentication + +Operations using the push admin API, as with all our other APIs, require specific permissions as part of the credentials of a client. The push admin API has two modes of authorization: + +* access using the "@push-admin@":/general/push/admin permission. A client whose credentials contain the @push-admin@ permission has full access to the push admin API, and can manage registrations and subscriptions for all devices; + +* access using the @push-subscribe@ permission. A client with @push-subscribe@ is a push target device, and it can manage its registration and any subscription for itself; it is not able to manage push registrations or channel subscriptions for any other device. The credentials presented, as well as containing the @push-subscribe@ permission, must also authenticate the device itself. + +Every push target device has a @deviceId@ and also has device credentials, which are used as the basis for device authentication. When using the push admin API, a device can authenticate itself in two ways: + +* by using an "Ably token":/core-features/authentication#token-authentication that contains its @deviceId@; + +* by using a normal "Ably key":/core-features/authentication#basic-authentication or "Ably Token":/core-features/authentication#token-authentication but additionally including a @deviceIdentityToken@ - a credential created at registration time that is used to assert the device's identity - in a header in the request. + +Management of device credentials is performed by the client library, so unless the push admin API is being accessed directly using HTTP, the client application does not need to worry about managing device credentials. + +<%= partial partial_version('general/push/_push_intro') %> + +h1. API reference + +inline-toc. + Methods: + - publish#publish + Properties: + - deviceRegistrations#device-registrations + - channelSubscriptions#channel-subscriptions + DeviceRegistrations: + - get#device-get-id + - list(params)#device-list + - save(DeviceDetails)#device-save + - remove#device-remove-id + - removeWhere(params)#device-remove-where + PushChannelSubscriptions: + - list(params)#push-channel-sub-list + - listChannels(params)#push-channel-sub-list-channels + - save(PushChannelSubscription)#push-channel-sub-save + - remove(PushChannelSubscription)#push-channel-sub-remove + - removeWhere(params)#push-channel-sub-remove-where + Related types: + - DeviceDetails#device-details + - PushChannelSubscription#push-channel-subscription + - PaginatedResult#paginated-result + +<%= partial partial_version('types/_push_admin') %> + +h2(#related-types). Related types + +h3(#device-details). + default: DeviceDetails + ruby: Ably::Models::DeviceDetails + +<%= partial partial_version('types/_device_details') %> + +h3(#push-channel-subscription). + default: PushChannelSubscription + ruby: Ably::Models::PushChannelSubscription + +<%= partial partial_version('types/_push_channel_subscription') %> + +h3(#paginated-result). + default: PaginatedResult + swift,objc: ARTPaginatedResult + ruby: Ably::Models::PaginatedResult + java: io.ably.lib.types.PaginatedResult + csharp: IO.Ably.PaginatedResult + +<%= partial partial_version('types/_paginated_result') %> diff --git a/content/general/versions/v1.1/push/publish.textile b/content/general/versions/v1.1/push/publish.textile new file mode 100644 index 0000000000..2d7f04441c --- /dev/null +++ b/content/general/versions/v1.1/push/publish.textile @@ -0,0 +1,614 @@ +--- +title: Push Notifications - Publishing Notifications +section: general +index: 42 +hide_from_nav: true +languages: + - javascript + - nodejs + - java + - swift + - objc + - ruby + - python + - php +jump_to: + Help with: + - Channel-based broadcasting#channel-broadcast + - Direct publishing#direct-publishing + - Push payload structure#payload-structure +--- + +Ably provides two models for delivering push notifications to devices. Channel-based broadcasting provides automatic fan-out capabilities for push notifications, and direct publishing provides a means to deliver individual notifications to devices. + + + Push Notifications in Ably + + + +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": , + "body": , + "icon": , + "sound": , + "collapseKey": + }, + "data": { + : , + ... + } +} +``` + +Depending on the transport (APNs, FCM, etc.), the following transformations are made automatically by Ably to make each field compatible with the target push notification transport: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Ably fieldFCMAPNsWeb (*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@
+ +So for example, a push payload in a message published to Ably as follows: + +```[json] +{ + "notification": { + "collapseKey": "chat" + } +} +``` + +would be delivered in raw format to FCM as: + +```[json] +{ + "collapse_key": "chat" +} +``` + +and would be delivered in raw format to APNs as: + +```[json] +{ + "aps.thread-id": "chat" +} +``` + +and would be delivered in raw format to a web push target (*experimental*) as: + +```[json] +{ + "notification": { + "collapseKey": "chat" + } +} +``` + +Additionally, you can set transport-specific attributes which will get merged into the root object resulting from generic mapping explained above only when pushing to the selected transport. This way, you can: + +* Override the generic value for that field. +* Add specific fields that are not supported in the generic structure. + +To do this, alongside @notification@ and @data@, add an object whose field is one of: + +* @fcm@, for "FCM":https://firebase.google.com/docs/cloud-messaging/concept-options. +* @apns@, for "APNs":https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html#//apple_ref/doc/uid/TP40008194-CH8-SW1. +* @web@, for Web Notifications (*experimental*). + +Here's an example of a push payload that overrides the default title for APNs iOS and sets the FCM Android-specific @color@ field: + +```[json] +{ + "notification": { + "title": "Hello from Ably!", + "body": "Example push notification from Ably." + }, + "data": { + "foo": "bar", + "baz": "qux" + }, + "apns": { + "aps": { + "alert": { + "title": "Hello to iOS from Ably!" + } + } + }, + "fcm": { + "notification": { + "color": "#d3d3d3" + } + } +} +``` diff --git a/content/general/versions/v1.1/queues.textile b/content/general/versions/v1.1/queues.textile new file mode 100644 index 0000000000..a799d630fd --- /dev/null +++ b/content/general/versions/v1.1/queues.textile @@ -0,0 +1,431 @@ +--- +title: Reactor Queues +section: general +index: 23 +jump_to: + Help with: + - What are Reactor Queues?#what + - Why use queues?#why + - Using the Reactor Queues#using + - Queue considerations#considerations + - Billing considerations#billing +--- + +Our Reactor Queues are traditional message queues that provide a reliable and straightforward mechanism for customers to consume, process, store, augment or reroute data from our realtime platform efficiently by your servers. Reactor Queues are offered as part of our "Ably Reactor service":https://www.ably.io/reactor which is available to all customers of the Ably platform. + +h2(#what). What are Reactor Queues? + +Ably's Reactor Queues provide an asynchronous machine-to-machine communication protocol that follows a "traditional message queueing pattern":http://soapatterns.org/design_patterns/asynchronous_queuing. At a high level, each machine participates in one or both roles: producers (Ably channels) publish messages (data) to a queue; consumers retrieve messages from the queue. The queue service is responsible for: storing published messages by placing them at the back of the queue; picking off the oldest messages from the front of the queue and handing them to a consumer; ensuring a "FIFO or 'first in, first out'":https://en.wikipedia.org/wiki/FIFO policy is followed; ensuring messages that are consumed successfully are only handed to one of the consumers. This messaging pattern provides decoupling (publishers can publish without waiting for consumers), scalability (adding more consumers increases throughput capacity) and resilience (messages are stored until a consumer has acknowledged the message has been processed successfully). + +"Ably's Realtime Core":https://www.ably.io/dsn provides "channels for realtime data distribution using the pub/sub messaging pattern":/realtime/channels. Unlike queues, pub/sub channels provide fan-out so that every message published on a channel is received by all devices subscribed for that data. When delivered with "our connection state recovery":https://support.ably.io/solution/articles/3000044639-connection-state-recovery, this pattern provides a decoupled, resilient and scalable means to publishing realtime data to any number of devices. + +No single pattern is better than the other, both have their merits and valid use cases. Take for example a delivery van driving through a city publishing its location periodically. Any number of customers waiting for their parcel can subscribe for updates and thus a pub/sub channel is well suited due to its inherent fan-out capability. However, emails may need to be triggered when the van is nearing its destination as well. A message queueing pattern is a better fit here as multiple worker servers can share the workload by consuming the location messages from the queue and performing work for each message without having to share any state. The message queue ensures that work is distributed evenly to the pool of servers, work is not duplicated (resulting for example in more than one email notification being sent) and the system is resilient to crashes or spikes in load (messages are stored until a consumer is ready to retrieve them). + +Ably combines both pub/sub and queueing functionality in "its platform":https://www.ably.io/dsn as seen in the diagram below: + + + Ably Reactor Message Queues diagram + + +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: + + + Queue dashboard example + + +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: + + + App stats example + + +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: + + Queue dashboard 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: + + Queue dashboard 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 := @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/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
__Type: @string@__ - region := in events relating to the activity of a channel in a specific region, this optionally identifies the region
__Type: @string@__ - isGlobalMaster := in events relating to the activity of a channel in a specific region, this optionally identifies whether or not that region is responsible for global coordination of the channel
__Type: @boolean@__ -- status := an optional "@ChannelStatus@":#channel-status instance
__Type: "ChannelStatus":/#channel-status__ +- status := an optional "@ChannelStatus@":#channel-status instance
__Type: "ChannelStatus":#channel-status__ minimize. See an example @ChannelDetails@ JSON object ```[json] diff --git a/content/partials/versions/v1.1/core-features/_authentication_capabilities.textile b/content/partials/versions/v1.1/core-features/_authentication_capabilities.textile new file mode 100644 index 0000000000..4f91299d9f --- /dev/null +++ b/content/partials/versions/v1.1/core-features/_authentication_capabilities.textile @@ -0,0 +1,14 @@ +The following capability operations are available for API keys and issued tokens. + +- subscribe := can subscribe to messages and presence state change messages on channels, and get the presence set of a channel +- publish := can publish messages to channels +- presence := can register presence on a channel (enter, update and leave) +- history := can retrieve message and presence state history on channels +- stats := can retrieve current and historical usage statistics for an app +- push-subscribe := can subscribe devices for push notifications +- push-admin := can manage device registrations and push subscriptions for all devices in an app +- channel-metadata := can get metadata for a channel, and enumerate channels + +See a working "capabilities example":<%= JsBins.url_for('authentication/capabilities') %>, read "understanding capabilities and token security":/core-features/authentication#capabilities-explained above to get a more thorough overview of how capabilities can be used to secure your application along with working examples. + +While most of these capabilities need to be enabled for the resource you're using them with, as described in "resource names and wildcards":/core-features/authentication#wildcards above, there are exceptions. The @stats@ permission only does anything when attached to the wildcard resource @'*'@ (or a resource that contains that as a subset, such as @'[*]*'@), since stats are app-wide. The @channel-metadata@ permission works both ways. When associated with a specific channel or set of channels it allows you to use the "Channel Status API":/rest/channel-status#channel-status to request the status of that channel. When associated with the wilcard resource @'*'@ it takes on an additional meaning: as well as allowing channel status requests for all channels, it also allows you to "enumerate all active channels":/rest/channel-status#enumeration-rest diff --git a/content/partials/versions/v1.1/core-features/_authentication_comparison.textile b/content/partials/versions/v1.1/core-features/_authentication_comparison.textile new file mode 100644 index 0000000000..c58e1bab26 --- /dev/null +++ b/content/partials/versions/v1.1/core-features/_authentication_comparison.textile @@ -0,0 +1,12 @@ +When deciding on which authentication method you will be using, it is recommended to bear in mind the "principle of least privilege":http://en.wikipedia.org/wiki/Principle_of_least_privilege: a client should ideally only possess the credentials and rights that it needs to accomplish what it wants; this way, if the credentials are compromised, the rights that can be abused by an attacker are minimized. + +The table below should be used as a rough guide as to what you should consider when choosing your authentication method. Many applications will most naturally use a mixed strategy: one or more trusted application servers will use basic authentication to access the service and issue tokens over HTTPS, whereas remote browsers and devices will use individually issued tokens.: + +minimize. View the authentication comparison table + |_. Scenario |_. "Basic":/core-features/authentication#basic-authentication |_. "Token":/core-features/authentication#token-authentication |_. Description | + | Your scripts may be exposed | No | Yes | If the script, program or system holding the key is exposed, for example on a user's device, you should not embed an "API key":https://support.ably.io/support/solutions/articles/3000030054-what-is-an-app-api-key and instead use "Token Authentication":/core-features/authentication#token-authentication. If the script is on a secure environment such as your own server, an "API key":https://support.ably.io/support/solutions/articles/3000030054-what-is-an-app-api-key with "Basic Authentication":/core-features/authentication#basic-authentication is fine. | + | Your connection may be insecure | No | Yes | If there is a risk of exposure of the client’s credentials, either directly or over an insecure, or insecurely proxied, connection, "Token Authentication":/core-features/authentication#token-authentication should be used. If you are sure the connection is secure and unmediated, "Basic Authentication":/core-features/authentication#basic-authentication is acceptable. | + | You have no server to control access | Yes | No | If you do not have your own server to perform authentication and provide "tokens":/core-features/authentication#tokens to users, you'll need to use "Basic Authentication":/core-features/authentication#basic-authentication. | + | You require fine-grained access control | No | Yes | If you need to provide "privileges":/core-features/authentication#capabilities-explained on a user-by-user basis, you'd be better using "Token Authentication":/core-features/authentication#token-authentication. If you only need a few access control groups, "Basic Authentication":/core-features/authentication#basic-authentication is reasonable. | + | Users need restricted periods of access | No | Yes | If you need users to only have access for a certain period of time, or the ability to revoke access, Token Authentication is needed. If users are able to always have access, Basic Authentication is acceptable. | + | Users need to identify themselves | Partial | Yes | If the user can be trusted to "identify":/core-features/authentication#identified-clients itself, "Basic Authentication":/core-features/authentication#basic-authentication is fine. If the user cannot be trusted however, "Token Authentication":/core-features/authentication#token-authentication is better as it allows for a trusted token distributor to identify the user instead. | \ No newline at end of file diff --git a/content/partials/versions/v1.1/general/events/_batched_event_headers.textile b/content/partials/versions/v1.1/general/events/_batched_event_headers.textile new file mode 100644 index 0000000000..d612a38362 --- /dev/null +++ b/content/partials/versions/v1.1/general/events/_batched_event_headers.textile @@ -0,0 +1,6 @@ +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.1@ diff --git a/content/partials/versions/v1.1/general/events/_batched_events.textile b/content/partials/versions/v1.1/general/events/_batched_events.textile new file mode 100644 index 0000000000..003fb9f7c3 --- /dev/null +++ b/content/partials/versions/v1.1/general/events/_batched_events.textile @@ -0,0 +1,109 @@ +Each batched message will have the following fields: + +- name := the event type, aka "@presence.message@", "@channel.message@", "@channel.closed@", etc +- webhookId := an internal unique ID for the configured webhook +- source := the source for the webhook, namely "@channel.message@", "@channel.presence@", "@channel.lifecycle@" +- timestamp := a timestamp represented as milliseconds since the epoch for the presence event +- data := an object containing the data of the event defined below in "JSONPath format":http://goessner.net/articles/JsonPath + +h4(#batch-example-message). Batched message events + +For @message@ events, @data@ will contain: + +- data.channelId := name of the channel that the presence event belongs to +- data.site := an internal site identifier indicating which primary datacenter the member is present in +- data.messages := an @Array@ of "@Message@":/rest/messages#properties + +minimize. View batched @message@ payload example + ```[json] + { + "items": [{ + "webhookId": "ABcDEf", + "source": "channel.lifecycle", + "timestamp": 1562124922426, + "serial": "a7bcdEFghIjklm123456789:4", + "name": "channel.message", + "data": { + "channelId": "channelName", + "site": "eu-west-1-A", + "messages": [{ + "id": "ABcDefgHIj:1:0", + "connectionId": "ABcDefgHIj", + "timestamp": 1123145678900, + "data": "some message data", + "name": "my message name" + }] + } + }] + } + ``` + +h4(#batch-example-presence). Batched presence events + +For @presence@ events, @data@ will contain: + +- data.channelId := name of the channel that the presence event belongs to +- data.site := an internal site identifier indicating which primary datacenter the member is present in +- data.presence := an @Array@ of "@Presence@":/realtime/presence#presence-message events + +minimize. View batched @presence@ payload example + ```[json] + { + "items": [{ + "webhookId": "ABcDEf", + "source": "channel.lifecycle", + "timestamp": 1562124922426, + "serial": "a7bcdEFghIjklm123456789:4", + "name": "presence.message", + "data": { + "channelId": "education", + "site": "eu-west-1-A", + "presence": [{ + "id": "ABcDefgHIj:1:0", + "connectionId": "ABcDefgHIj", + "timestamp": 1123145678900, + "clientId": "bob", + "data": "some message data", + "action": 4 + }] + } + }] + } + ``` + +h4(#batch-example-lifecycle). Batched channel lifecycle events + +For @channel lifecycle@ events, @data@ will contain: + +- data.channelId := name of the channel that the presence event belongs to +- data.status := a "@ChannelStatus@":/realtime/channel-metadata#channel-details object + +minimize. View batched @channel lifecycle@ payload example + ```[json] + { + "items": [{ + "webhookId": "ABcDEf", + "source": "channel.lifecycle", + "timestamp": 1562124922426, + "serial": "a7bcdEFghIjklm123456789:4", + "name": "channel.opened", + "data": { + "channelId": "channelName", + "name": "channelName", + "status": { + "isActive": true, + "occupancy": { + "metrics": { + "connections": 1, + "publishers": 1, + "subscribers": 1, + "presenceConnections": 1, + "presenceMembers": 0, + "presenceSubscribers": 1 + } + } + } + } + }] + } + ``` diff --git a/content/partials/versions/v1.1/general/events/_enveloped_event_headers.textile b/content/partials/versions/v1.1/general/events/_enveloped_event_headers.textile new file mode 100644 index 0000000000..8cc6de0333 --- /dev/null +++ b/content/partials/versions/v1.1/general/events/_enveloped_event_headers.textile @@ -0,0 +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.1@ +- 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/versions/v1.1/general/events/_enveloped_events.textile b/content/partials/versions/v1.1/general/events/_enveloped_events.textile new file mode 100644 index 0000000000..07e3ca9386 --- /dev/null +++ b/content/partials/versions/v1.1/general/events/_enveloped_events.textile @@ -0,0 +1,54 @@ +Each enveloped message will have the following fields: + +- source := the source for the webhook, namely "@channel.message@" or "@channel.presence@" +- appId := the Ably app this message came from +- channel := the Ably channel where the event occurred +- site := the Ably datacenter which sent the message +- timestamp := a timestamp represented as milliseconds since the epoch for the presence event + +In addition, it will contain another field which will contain the actual message, which is named according to the message type. + +h4(#envelope-example-message). Enveloped message events + +For @message@ events, there will be a @messages@ field, which will contain the "@Message@":/rest/messages#properties event. + +minimize. View enveloped @message@ payload example + ```[json] + { + "source": "channel.message", + "appId": "aBCdEf", + "channel": "channel-name", + "site": "eu-central-1-A", + "ruleId": "1-a2Bc", + "messages": [{ + "id": "ABcDefgHIj:1:0", + "connectionId": "ABcDefgHIj", + "timestamp": 1123145678900, + "data": "some message data", + "name": "my message name" + }] + } + ``` + +h4(#envelope-example-presence). Enveloped presence events + +For @presence@ events, there will be a @presence@ field, which will contain the "@Presence@":/realtime/presence#presence-message event. + +minimize. View enveloped @message@ payload example + ```[json] + { + "source": "channel.message", + "appId": "aBCdEf", + "channel": "channel-name", + "site": "eu-central-1-A", + "ruleId": "1-a2Bc", + "presence": [{ + "id": "abCdEFgHIJ:1:0", + "clientId": "bob", + "connectionId": "Ab1CDE2FGh", + "timestamp": 1582270137276, + "data": "some data in the presence object", + "action": 4 + }] + } + ``` \ No newline at end of file diff --git a/content/partials/versions/v1.1/general/events/_events_examples_intro.textile b/content/partials/versions/v1.1/general/events/_events_examples_intro.textile new file mode 100644 index 0000000000..eac29ca928 --- /dev/null +++ b/content/partials/versions/v1.1/general/events/_events_examples_intro.textile @@ -0,0 +1 @@ +Given the various potential combinations of @enveloped@, @batched@ and message sources, it can be good to know what to expect given certain combinations of rules. diff --git a/content/partials/versions/v1.1/general/events/_non_enveloped_event_headers.textile b/content/partials/versions/v1.1/general/events/_non_enveloped_event_headers.textile new file mode 100644 index 0000000000..f5dbceb7c9 --- /dev/null +++ b/content/partials/versions/v1.1/general/events/_non_enveloped_event_headers.textile @@ -0,0 +1,13 @@ +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.1@ +- 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 +- x-ably-envelope-site := the Ably datacenter which sent the message +- x-ably-envelope-source := the "source":#sources for the webhook, namely "@channel.message@" or "@channel.presence@" +- x-ably-message-client-id := the client ID of the connection which sent the event +- x-ably-message-connection-id := the connection ID responsible for the initial event +- x-ably-message-id := the message's unique ID +- x-ably-message-timestamp := the time the message was originally sent diff --git a/content/partials/versions/v1.1/general/events/_non_enveloped_events.textile b/content/partials/versions/v1.1/general/events/_non_enveloped_events.textile new file mode 100644 index 0000000000..84f4d47198 --- /dev/null +++ b/content/partials/versions/v1.1/general/events/_non_enveloped_events.textile @@ -0,0 +1,36 @@ +h4(#no-envelope-example-message). Non-enveloped message events + +For @message@ events, there will be the additional headers: + +- x-ably-message-name := The "name":/realtime/messages#name of the @Message@ + +The payload will contain the "data":/realtime/messages#data of the @Message@. + +For example, if you sent the following curl message, which sends a JSON message to the channel @my_channel@: + +```[curl] +curl -X POST https://rest.ably.io/channels/my_channel/messages \ + -u "{{API_KEY}}" \ + -H "Content-Type: application/json" \ + --data '{ "name": "publish", "data": "example" }' +``` + +The @x-ably-message-name@ header would be @publish@, and the payload would be @example@. + +h4(#no-envelope-example-presence). Non-enveloped presence events + +For @Presence@ events, there will be the additional headers: + +- x-ably-message-action := the action performed by the event (@update@, @enter@, @leave@) + +The payload will contain the "data":/realtime/presence#presence-message of the @Presence@ message. + +For example, if a "client enters":/realtime/presence#enter a channel's presence with the following code: + +```[jsall] +realtime = new Ably.Realtime({ key: '{{API_KEY}}', clientId: 'bob' }); +channel = realtime.channels.get('some_channel'); +channel.presence.enter('some data'); +``` + +Then the @x-ably-message-action@ would be @enter@, the @x-ably-message-client-id@ would be "bob", and the payload would be "some data". diff --git a/content/partials/versions/v1.1/general/push/_debugging_push.textile b/content/partials/versions/v1.1/general/push/_debugging_push.textile new file mode 100644 index 0000000000..87730d2d9e --- /dev/null +++ b/content/partials/versions/v1.1/general/push/_debugging_push.textile @@ -0,0 +1,3 @@ +h2(#debugging-push). Debugging Push Notifications + +If you’re having trouble receiving push notifications there could be a number of reasons. Check out our "support article":https://support.ably.io/a/solutions/articles/3000087496-i-m-not-receiving-push-notifications-on-my-device on how to properly debug push notifications with Ably. \ No newline at end of file diff --git a/content/partials/versions/v1.1/general/push/_push_intro.textile b/content/partials/versions/v1.1/general/push/_push_intro.textile new file mode 100644 index 0000000000..14a2274b86 --- /dev/null +++ b/content/partials/versions/v1.1/general/push/_push_intro.textile @@ -0,0 +1,49 @@ +Ably can deliver native push notifications to devices using, amongst others, "Apple's Push Notification":https://developer.apple.com/notifications/ service and Google's "Firebase Cloud Messaging":https://firebase.google.com/docs/cloud-messaging/ service. Native push notifications, unlike Ably's "channel based pub/sub messaging":/realtime/channels, do not require the device to maintain a connection to Ably, as the underlying platform or OS is responsible for maintaining its own battery-efficient transport to receive push notifications. Therefore, native push notifications are commonly used to display visual notifications to users or launch a background process for an app in a battery-efficient manner. + +h2(#deliver). Delivering push notifications + + + Push Notifications in Ably + + +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 + +-
callback
:= is a function of the form: @function(err, result)@ +-
&block
:= yields a @PaginatedResult@ object +-
callback
:= called with a "ARTPaginatedResult":#paginated-result<"ARTStats":/rest/types#stats> object or an error + +h4. @options@ parameters@ARTStatsQuery@ properties@StatsRequestParams@ properties + +The following options, as defined in the "REST @/stats@ API":/rest-api#stats endpoint, are permitted: + +- start:startStart := _beginning of time_ earliest @DateTimeOffset@ or @Time@ or time in milliseconds since the epoch for any stats retrieved
__Type: @Long@@Int or @Time@@DateTimeOffset@__ +- end:endEnd := _current time_ latest @DateTimeOffset@ or @Time@ or time in milliseconds since the epoch for any stats retrieved
__Type: @Long@@Int or @Time@@DateTimeOffset@__ +- direction:directionDirection := _backwards_ @:@@forwards@ or @:@@backwards@
__Type: @String@@Symbol@@Direction@ enum__ +- limit:limitLimit := _100_ maximum number of stats to retrieve up to 1,000
__Type: @Integer@__ +- unit:unitUnit := _minute_ @:@@minute@, @:@@hour@, @:@@day@ or @:@@month@. Based on the unit selected, the given start or end times are rounded down to the start of the relevant interval depending on the unit granularity of the query
__Type: @String@"@ARTStatsGranularity@":#stats-granularity@Symbol@@StatsIntervalGranularity@ enum__ + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @result@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "@Stats@":/realtime/types#stats objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + On failure to retrieve stats, @err@ contains an "@ErrorInfo@":#error-info object with an error response as defined in the "Ably REST API":/rest-api#common documentation. + +blang[java]. + h4. Returns + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@Stats@":/realtime/types#stats objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + Failure to retrieve the stats will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[csharp]. + h4. Returns + + Returns a @Task@ which needs to be awaited. + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@Stats@":/realtime/types#stats objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@NextAsync@":#paginated-result and "@FirstAsync@":#paginated-result methods. + + Failure to retrieve the stats will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the stats method. + + On success, the registered success callbacks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method yields a "PaginatedResult":#paginated-result that encapsulates an array of "@Stats@":/realtime/types#stats objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + Failure to retrieve the stats will trigger the errback callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object containing an error response as defined in the "Ably REST API":/rest-api#common documentation. diff --git a/content/partials/versions/v1.1/rest/_request.textile b/content/partials/versions/v1.1/rest/_request.textile new file mode 100644 index 0000000000..22f62e76a8 --- /dev/null +++ b/content/partials/versions/v1.1/rest/_request.textile @@ -0,0 +1,69 @@ +h6(#request). + default: request + go,csharp: Request + +bq(definition). + default: request(String method, String path, Object params, Object body, Object headers, callback("ErrorInfo":/rest/types#error-info err, "HttpPaginatedResponse":/rest/types#http-paginated-response results)) + ruby,php: "HttpPaginatedResponse":/rest/types#http-paginated-response request(String method, String path, Object params, Object body, Object headers) + python: publish(method=String, path=String, params=Object, body=Object, headers=Object) + java: "HttpPaginatedResponse":/rest/types#http-paginated-response request(String method, String path, Object params, Object body, Object headers) + csharp: Task<"HttpPaginatedResponse":/rest/types#http-paginated-response> Request(string method, string path, Dictionary requestParams, JToken body, Dictionary headers) + objc,swift: request(method: String, path: String, params: Object?, body: Object?, headers: Object?, callback: ("ARTHttpPaginatedResponse":/rest/types#http-paginated-response, ARTErrorInfo?) -> Void) + go: "HTTPPaginatedResponse":rest/types#http-paginated-response Request(method string, path string, params PaginateParams, body interface, headers http.Header) + +Makes a REST request to a provided path. This is provided as a convenience for developers who wish to use bleeding edge REST API functionality that is either not documented or is not yet included in the public API, without having to handle authentication, paging, fallback hosts, MsgPack and JSON support, etc. themselves. + +h4. Parameters + +- method := either @get@, @post@, @put@, @patch@ or @delete@.
__Type: Stringstring__ +- path := the path to query.
__Type: Stringstring__ +- params := (optional) any querystring parameters needed.
__Type: ObjectPaginateParamsDictionary__ +- body := (optional; for @post@, @put@ and @patch@ methods) the body of the request, as anything that can be serialized into JSON, such as an @Object@ or @Array@.a JToken.
__Type: SerializableinterfaceJToken__ +- headers := (optional) any headers needed. If provided, these will be mixed in with the default library headers.
__Type: Objecthttp.HeaderDictionary__ + +blang[jsall,objc,swift]. + h4. Callback result + + On successfully receiving a response from Ably, @results@ contains an "@HttpPaginatedResponse@@ARTHttpPaginatedResponse@":/rest/types#http-paginated-response containing the @statusCode@ of the response, a @success@ boolean (equivalent to whether the status code is between 200 and 299), @headers@, and an @items@ array containing the current page of results. It supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods, identically to "@PaginatedResult@":/rest/types#paginated-result. + + On failure to obtain a response, @err@ contains an "@ErrorInfo@":/rest/types#error-info object with an error response as defined in the "Ably REST API":/rest-api#common documentation. (Note that if a response is obtained, any response, even with a non-2xx status code, will result in an HTTP Paginated Response, not an @err@). + +blang[java,ruby,php,python]. + h4. Returns + + On successfully receiving a response from Ably, the returned "@HttpPaginatedResponse@":/rest/types#http-paginated-response contains a @status_code@@statusCode@ and a @success@ boolean, @headers@, and an @items@ array containing the current page of results. It supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods, identically to "@PaginatedResult@":/rest/types#paginated-result. + + Failure to obtain a response will raise an "@AblyException@":/realtime/types/#ably-exception. (Note that if a response is obtained, any response, even with a non-2xx status code, will result in an HTTP Paginated Response, not an exception). + +blang[csharp]. + h4. Returns + + The method is asynchronous and return a Task that has to be awaited to get the result. + + On successfully receiving a response from Ably, the returned "@HttpPaginatedResponse@":/rest/types#http-paginated-response containing the @StatusCode@ and a @Success@ boolean, @Headers@, and an @Items@ array containing the current page of results. "@HttpPaginatedResponse@":/rest/types#http-paginated-response supports pagination using "@NextAsync@":#paginated-result and "@FirstAsync@":#paginated-result methods. + + Failure to obtain a response will raise an "@AblyException@":/realtime/types/#ably-exception. (Note that if a response is obtained, any response, even with a non-2xx status code, will result in an HTTP Paginated Response, not an exception). + +h4. Example + +```[jsall] +rest.request( + 'get', + '/channels/someChannel/messages', + {limit: 1, direction: 'forwards'}, + null, + null, + function(err, response) { + if(err) { + console.log('An error occurred; err = ' + err.toString()); + } else { + console.log('Success! status code was ' + response.statusCode); + console.log(response.items.length + ' items returned'); + if(response.hasNext()) { + response.next(function(err, nextPage) { + console.log(nextPage.items.length + ' more items returned'); + }); + } + } + }); +``` diff --git a/content/partials/versions/v1.1/rest/_stats.textile b/content/partials/versions/v1.1/rest/_stats.textile new file mode 100644 index 0000000000..9c3b977b8b --- /dev/null +++ b/content/partials/versions/v1.1/rest/_stats.textile @@ -0,0 +1,40 @@ +h4. Parameters + +- optionsquery := an optional objectHash@ARTStatsQuery@@StatsRequestParams@"@Param@":#param[] array containing the query parameters + +-
callback
:= is a function of the form: @function(err, result)@ +-
&block
:= yields a @PaginatedResult@ object +-
callback
:= called with a "ARTPaginatedResult":#paginated-result<"ARTStats":/rest/types#stats> object or an error + +h4. @options@ parameters@ARTStatsQuery@ properties@StatsRequestParams@ properties + +The following options, as defined in the "REST @/stats@ API":/rest-api#stats endpoint, are permitted: + +- start:startStart := _beginning of time_ earliest @DateTimeOffset@ or @Time@ or time in milliseconds since the epoch for any stats retrieved
__Type: @Long@@Int or @Time@@DateTimeOffset@__ +- end:endEnd := _current time_ latest @DateTimeOffset@ or @Time@ or time in milliseconds since the epoch for any stats retrieved
__Type: @Long@@Int or @Time@@DateTimeOffset@__ +- direction:directionDirection := _backwards_ @:@@forwards@ or @:@@backwards@
__Type: @String@@Symbol@@Direction@ enum__ +- limit:limitLimit := _100_ maximum number of messages to retrieve up to 1,000
__Type: @Integer@__ +- unit:unitUnit := _minute_ @:@@minute@, @:@@hour@, @:@@day@ or @:@@month@. Based on the unit selected, the given start or end times are rounded down to the start of the relevant interval depending on the unit granularity of the query
__Type: @String@"@ARTStatsGranularity@":#stats-granularity@Symbol@@StatsIntervalGranularity@ enum__ + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @result@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "@Stats@":/rest/types#stats objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + On failure to retrieve stats, @err@ contains an "@ErrorInfo@":#error-info object with an error response as defined in the "Ably REST API":/rest-api#common documentation. + +blang[java,ruby,php]. + h4. Returns + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@Stats@":/rest/types#stats objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + Failure to retrieve the stats will raise an "@AblyException@":/rest/types#ably-exception + +blang[csharp]. + h4. Returns + + The method is asynchronous and return Task which needs to be awaited. + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates a list of "@Stats@":/rest/types#stats objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@NextAsync@":#paginated-result and "@FirstAsync@":#paginated-result methods. + + Failure to retrieve the stats will raise an "@AblyException@":/rest/types#ably-exception diff --git a/content/partials/versions/v1.1/shared/_channel_enumeration.textile b/content/partials/versions/v1.1/shared/_channel_enumeration.textile new file mode 100644 index 0000000000..6005ac363d --- /dev/null +++ b/content/partials/versions/v1.1/shared/_channel_enumeration.textile @@ -0,0 +1,23 @@ +This enumerates all active channels in the application. This is a paginated API following the same API conventions as other paginated APIs in the "Ably REST library":/rest. + +Example request: + +bc[sh]. curl https://rest.ably.io/channels \ + -u "{{API_KEY}}" + +This will return either a list of channel names, or a "ChannelDetails":/realtime/channel-metadata#channel-details object depending on what options you've specified. + +The following parameters are supported: + +- limit := _100_ optionally specifies the maximum number of results to return. A limit greater than 1000 is unsupported
__Type: @integer@__ +- prefix := optionally limits the query to only those channels whose name starts with the given prefix
__Type: @string@__ +- by := _value_ optionally specifies whether to return just channel names (@by=id@) or "ChannelDetails":realtime/channel-metadata#channel-details (@by=value@) + +The credentials presented with the request must include the @channel-metadata@ permission for the wildcard resource @'*'@. + +Client libraries currently do not provide a dedicated API to enumerate channels, but make this available using the "request":/rest/usage#request method. When using this, you can simply iterate through the "PaginatedResults":rest/types#paginated-result to enumerate through the results. + +@Enumeration@ is possible of all channels in an app, by repeated calls to the API, following the @next@ relative link on each successive call, until there is no @next@ relative link. However, the state of the app and the cluster itself can change during that enumeration. This API therefore has the following limitations: + +* channels that become active, or become inactive, between the first and last request in the sequence, might or might not appear in the result. The API guarantees that if a channel is continuously active from the time that the first request is made until the time that the last request completes, then it is guaranteed to be present in the result. Similarly, if a channel is continuously inactive between those times then it is guaranteed not to be present in the result; +* cluster state changes, in this first release of this API, may cause a pagination sequence to become invalid, in which case the request will respond with an error with code @40011@. In this case, to get a complete result, it is necessary to start the enumeration again from the beginning. Other API options to deal with this possibility will be provided in later versions of this API. Enumerations that are satisfiable in the first response page do not have this issue. diff --git a/content/partials/versions/v1.1/shared/_channel_metadata.textile b/content/partials/versions/v1.1/shared/_channel_metadata.textile new file mode 100644 index 0000000000..19408ea19d --- /dev/null +++ b/content/partials/versions/v1.1/shared/_channel_metadata.textile @@ -0,0 +1,10 @@ +This returns a "ChannelDetails":/realtime/channel-metadata#channel-details for the given channel, indicating global "occupancy":/rest/channel-status#occupancy. A side-effect of this request, in the current version of this API, is that it will cause the channel in question to become activated; therefore it is primarily intended to be used in conjunction with the "enumeration API":#enumeration-rest or in situations where the application has another means to know whether or not a given channel is active. + +Example request: + +bc[sh]. curl https://rest.ably.io/channels/ \ + -u "{{API_KEY}}" + +The credentials presented with the request must include the @channel-metadata@ permission for the channel in question. + +Client libraries currently do not support this API, but it is usable via the generic "request API":/rest/usage#request. diff --git a/content/partials/versions/v1.1/shared/_channel_namespaces.textile b/content/partials/versions/v1.1/shared/_channel_namespaces.textile new file mode 100644 index 0000000000..425cee798a --- /dev/null +++ b/content/partials/versions/v1.1/shared/_channel_namespaces.textile @@ -0,0 +1,11 @@ +One or more channel namespaces, or channel name prefixes, may be "configured for an app in your dashboard":https://support.ably.io/solution/articles/3000030053-how-do-i-access-my-app-dashboard. When a channel is created whose name is prefixed with one of the namespaces, the channel assumes certain configured attributes associated with that namespace. For example, a channel namespace named "@private@" would match channels named "@private@", "@private:chat@", "@private:chat:mike@". + +Namespace-prefixed channel names are delimited by a single colon @:@; the first component of the channel name (from the start up to and including the last character before the colon) is the namespace. A channel name may validly contain a colon even if the namespace component does not correspond to a namespace; also, a channel may contain multiple colons and only the component up to the first colon will be matched with a namespace. The only restriction on channel names is that a channel name may not start with a colon @:@, an open square bracket @[@ and it may not be empty. + +Namespaces are defined and configured via the "application dashboard settings":https://support.ably.io/solution/articles/3000030057-what-are-channel-rules-and-how-can-i-use-them-in-my-app. The namespace attributes that can be configured are: + +* **Persisted messages** - If enabled, all messages within this namespace will be stored according to the storage rules for your account. You can access stored messages via the "history API":/realtime/history +* **Require identification** - if enabled, clients will not be permitted to subscribe to matching channels unless they are both authenticated and identified (they have an assigned client ID). Anonymous clients are not permitted to join these channels. Find out more about "authenticated and identified clients":https://support.ably.io/solution/articles/3000038608-authenticated-clients +* **Require TLS** - if enabled, only clients who have connected to Ably over TLS will be allowed to join the channel + +Key or token capabilities can also specify access rights based on channel namespace, find out more about "authentication":/core-features/authentication diff --git a/content/partials/versions/v1.1/shared/_presence_states.textile b/content/partials/versions/v1.1/shared/_presence_states.textile new file mode 100644 index 0000000000..43794c0a1d --- /dev/null +++ b/content/partials/versions/v1.1/shared/_presence_states.textile @@ -0,0 +1,11 @@ +Whenever a member enters or leaves a channel, or updates "their member data":#member-data, a presence event is emitted to all presence subscribers on that channel. Subscribing to presence events makes it incredibly easy to build an app that shows, in real time, any changes to clients connected to Ably and present on a channel. + +The following presence events are emitted: + +- :enterPresenceAction.ENTERAction.ENTEREnterenter := A new member has entered the channel + +- :leavePresenceAction.LEAVEAction.LEAVELeaveleave := A member who was present has now left the channel. This may be a result of an explicit request to leave or implicitly when detaching from the channel. Alternatively, if a member's connection is abruptly disconnected and they do not resume their connection within a minute, Ably treats this as a leave event as the client is no longer present + +- :updatePresenceAction.UPDATEAction.UPDATEUpdateupdate := An already present member has updated their "member data":#member-data. Being notified of member data updates can be very useful, for example, it can be used to update the status of a user when they are typing a message + +- :presentPresenceAction.PRESENTAction.PRESENTPresentpresent := When subscribing to presence events on a channel that already has members present, this event is emitted for every member already present on the channel before the subscribe listener was registered diff --git a/content/partials/versions/v1.1/shared/_token_auth_methods.textile b/content/partials/versions/v1.1/shared/_token_auth_methods.textile new file mode 100644 index 0000000000..2a02cc6bd4 --- /dev/null +++ b/content/partials/versions/v1.1/shared/_token_auth_methods.textile @@ -0,0 +1,11 @@ +- authCallbackAuthCallbackauth_callback:auth_callback := A functionfunction with the form @function(tokenParams, callback(err, tokenOrTokenRequest))@@TokenCallback@ instancecallable (eg a lambda)proc / lambda (called synchronously in REST and Realtime but does not block EventMachine in the latter) which is called when a new token is required. The role of the callback is to obtain a fresh token, one of: an Ably Token string (in plain text format); a signed "@TokenRequest@":/realtime/types#token-request ; a "@TokenDetails@":/realtime/types#token-details (in JSON format); an "Ably JWT":/core-features/authentication#ably-jwt. See "an authentication callback example":<%= JsBins.url_for('authentication/auth-callback') %> or "our authentication documentation":/rest/authentication for details of the Ably TokenRequest format and associated API calls.
__Type: @Callable@@TokenCallback@@Proc@@Func>@__ + +- authUrlAuthUrl:auth_urlauth_url := A URL that the library may use to obtain a fresh token, one of: an Ably Token string (in plain text format); a signed "@TokenRequest@":/realtime/types#token-request ; a "@TokenDetails@":/realtime/types#token-details (in JSON format); an "Ably JWT":/core-features/authentication#ably-jwt. For example, this can be used by a client to obtain signed Ably TokenRequests from an application server.
__Type: @String@@Uri@@NSURL@__ + +- authMethodAuthMethodauth_method:auth_method := _@GET@@:get@_ The HTTP verb to use for the request, either @GET@@:get@ or @POST@@:post@
__Type: @String@@Symbol@@HttpMethod@__ + +- authHeadersAuthHeadersauth_headers:auth_headers := A set of key value pair headers to be added to any request made to the @authUrl@@AuthUrl@. Useful when an application requires these to be added to validate the request or implement the response. If the @authHeaders@ object contains an @authorization@ key, then @withCredentials@ will be set on the xhr request.
__Type: @Object@@Dict@@Hash@@Associative Array@@Param []@@Dictionary@__ + +- authParamsAuthParams:auth_paramsauth_params := A set of key value pair params to be added to any request made to the @authUrl@@AuthUrl@. When the @authMethod@@AuthMethod@ is @GET@, query params are added to the URL, whereas when @authMethod@@AuthMethod@ is @POST@, the params are sent as URL encoded form data. Useful when an application require these to be added to validate the request or implement the response.
__Type: @Object@@Hash@@Associative Array@@Param[]@@Dictionary@@NSArray@@[NSURLQueryItem]/Array@__ + +- tokenDetailsTokenDetailstoken_details:token_details := An authenticated "@TokenDetails@":/realtime/types/#token-details object (most commonly obtained from an Ably Token Request response). This option is mostly useful 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, such as @authUrl@@AuthUrl@@:auth_url@@auth_url@ or @authCallback@AuthCallback@auth_callback@@:auth_callback@. Use this option if you wish to use Token authentication. Read more about "Token authentication":/core-features/authentication#token-authentication
__Type: @TokenDetails@__ diff --git a/content/partials/versions/v1.1/shared/tutorials/_step-1-setup-free-account.textile b/content/partials/versions/v1.1/shared/tutorials/_step-1-setup-free-account.textile new file mode 100644 index 0000000000..590473568e --- /dev/null +++ b/content/partials/versions/v1.1/shared/tutorials/_step-1-setup-free-account.textile @@ -0,0 +1,11 @@ +h2(#setup-ably-account). Step 1 - Set up a free account with Ably + +In order to run these tutorials locally, you will need an "Ably API key":https://support.ably.io/support/solutions/articles/3000030502-setting-up-and-managing-api-keys. If you are not already signed up, you should "sign up now for a free Ably account":https://www.ably.io/signup. Once you have an Ably account: + +# "Log into your app dashboard":https://support.ably.io/support/solutions/articles/3000030053-how-do-i-access-my-app-dashboard +# Under "Your apps", click on "Manage app" for any app you wish to use for this tutorial, or create a new one with the "Create New App" button +# Click on the "API Keys" tab +# Copy the secret "API Key" value from your Root key and store it so that you can use it later in this tutorial + + Copy API Key screenshot + 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
__Type: @ErrorInfo@__ diff --git a/content/partials/versions/v1.1/types/_auth_options.textile b/content/partials/versions/v1.1/types/_auth_options.textile new file mode 100644 index 0000000000..67a50e4c4a --- /dev/null +++ b/content/partials/versions/v1.1/types/_auth_options.textile @@ -0,0 +1,28 @@ +blang[jsall]. + @AuthOptions@ is a plain Javascript object and is used when making "authentication":/realtime/authentication requests. If passed in, an @authOptions@ object will be used instead of (as opposed to supplementing or being merged with) the default values given when the library was instanced. The following attributes are supported: + +blang[ruby]. + @AuthOptions@ is a Hash object and is used when making "authentication":/realtime/authentication requests. These options will supplement or override the corresponding options given when the library was instanced. The following key symbol values can be added to the Hash: + +blang[python]. + @AuthOptions@ is a Dict and is used when making "authentication":/realtime/authentication requests. These options will supplement or override the corresponding options given when the library was instanced. The following key symbol values can be added to the Dict: + +blang[php]. + @AuthOptions@ is an Associative Array and is used when making "authentication":/realtime/authentication requests. These options will supplement or override the corresponding options given when the library was instanced. The following named keys and values can be added to the Associative Array: + +blang[java,swift,objc,go]. + @ART@@AuthOptions@ is used when making "authentication":/realtime/authentication requests. These options will supplement or override the corresponding options given when the library was instanced. + +h4. + default: Properties + java: Members + ruby: Attributes + Python: Attributes + +<%= partial partial_version('shared/_token_auth_methods') %> + +- keyKey:keykey := Optionally the "API key":https://support.ably.io/solution/articles/3000030054-what-is-an-app-api-key to use can be specified as a full key string; if not, the API key passed into "@ClientOptions@":#client-options when instancing the Realtime or REST library is used
__Type: @String@__ + +- queryTimeQueryTime:query_timequery_time := _false_ If true, the library will query the Ably servers for the current time when "issuing TokenRequests":/rest/authentication#token-authentication instead of relying on a locally-available time of day. Knowing the time accurately is needed to create valid signed Ably "TokenRequests":/rest/authentication#token-authentication, so this option is useful for library instances on auth servers where for some reason the server clock cannot be kept synchronized through normal means, such as an "NTP daemon":https://en.wikipedia.org/wiki/Ntpd . The server is queried for the current time once per client library instance (which stores the offset from the local clock), so if using this option you should avoid instancing a new version of the library for each request.
__Type: @Boolean@__ + +- tokenToken:token := An authenticated token. This can either be a "@TokenDetails@":/realtime/types#token-details object, a "@TokenRequest@":/realtime/types#token-request object, or token string (obtained from the @token@@Token@ property of a "@TokenDetails@":/realtime/types#token-details component of an Ably TokenRequest response, or a "JSON Web Token":https://tools.ietf.org/html/rfc7519 satisfying "the Ably requirements for JWTs":https://www.ably.io/documentation/core-features/authentication#ably-jwt). This option is mostly useful 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, such as @authUrl@AuthUrl:auth_urlauth_url or authCallbackAuthCallbackauth_callback:auth_callback. Read more about "Token authentication":/core-features/authentication#token-authentication
__Type: @String@, @TokenDetails@ or @TokenRequest@__ diff --git a/content/partials/versions/v1.1/types/_base_ably_exception.textile b/content/partials/versions/v1.1/types/_base_ably_exception.textile new file mode 100644 index 0000000000..976b014dc6 --- /dev/null +++ b/content/partials/versions/v1.1/types/_base_ably_exception.textile @@ -0,0 +1,13 @@ +A @BaseAblyException@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 + python: Attributes + +- codeCode := Ably error code (see "ably-common/protocol/errors.json":https://github.com/ably/ably-common/blob/master/protocol/errors.json)
__Type: @Integer@__ + +- statusCodestatus_codeStatusCode := HTTP Status Code corresponding to this error, where applicable
__Type: @Integer@__ + +- messageMessage := Additional message information, where available
__Type: @String@__ diff --git a/content/partials/versions/v1.1/types/_channel_details.textile b/content/partials/versions/v1.1/types/_channel_details.textile new file mode 100644 index 0000000000..a97d6e0fbd --- /dev/null +++ b/content/partials/versions/v1.1/types/_channel_details.textile @@ -0,0 +1,45 @@ +h3(#channel-details). ChannelDetails + +@ChannelDetails@ is an object returned when requesting or receiving "channel metadata":/realtime/channel-metadata. It contains information on the channel itself, along with the current state of the channel in the "ChannelStatus":#channel-status object. + +- channelId := the required name of the channel including any qualifier, if any
__Type: @string@__ +- region := in events relating to the activity of a channel in a specific region, this optionally identifies the region
__Type: @string@__ +- isGlobalMaster := in events relating to the activity of a channel in a specific region, this optionally identifies whether or not that region is responsible for global coordination of the channel
__Type: @boolean@__ +- status := an optional "@ChannelStatus@":#channel-status instance
__Type: "ChannelStatus":#channel-status__ + +minimize. See an example @ChannelDetails@ JSON object + ```[json] + { + "channelId": "foo", + "status": { + "isActive": true, + "occupancy": { + "metrics": { + "connections": 1, + "publishers": 1, + "subscribers": 1, + "presenceConnections": 1, + "presenceMembers": 0, + "presenceSubscribers": 1 + } + } + } + } + ``` + +h3(#channel-status). ChannelDetails.ChannelStatus + +@ChannelStatus@ is contained within the "@ChannelDetails@":#channel-details object, and optionally contains an "Occupancy":#occupancy object. + +- isActive := a required boolean value indicating whether the channel that is the subject of the event is active. For events indicating regional activity of a channel this indicates activity in that region, not global activity
__Type: @boolean@__ +- occupancy := an optional "@Occupancy@":#occupancy instance indicating the occupancy of the channel. For events indicating regional activity of a channel this indicates activity in that region, not global activity.
__Type: "Occupancy":#occupancy__ + +h3(#occupancy). ChannelDetails.ChannelStatus.Occupancy + +Occupancy is optionally contained within the above "@ChannelStatus@":#channel-status object, and contains metadata relating to the occupants of the channel. This is usually contained within the @occupancy@ attribute of the "@ChannelStatus@":#channel-status object. Membership categories include: + +- publishers := the number of connections attached to the channel that are authorised to publish
__Type: @integer@__ +- subscribers := the number of connections attached that are authorised to subscribe to messages
__Type: @integer@__ +- presenceSubscribers := the number of connections that are authorised to subscribe to presence messages
__Type: @integer@__ +- presenceConnections := the number of connections that are authorised to enter members into the presence channel
__Type: @integer@__ +- presenceMembers := the number of members currently entered into the presence channel
__Type: @integer@__ diff --git a/content/partials/versions/v1.1/types/_channel_event.textile b/content/partials/versions/v1.1/types/_channel_event.textile new file mode 100644 index 0000000000..2efbdd00d4 --- /dev/null +++ b/content/partials/versions/v1.1/types/_channel_event.textile @@ -0,0 +1,116 @@ +blang[jsall]. + @ChannelEvent@ is a String that can be emitted as an event on the @Channel@ object; either a "@ChannelState@":/realtime/channels#channel-states or an @update@ event. + + ```[javascript] + var ChannelEvents = [ + 'initialized', + 'attaching', + 'attached', + 'detaching', + 'detached', + 'failed', + 'suspended', + 'update' + ] + ``` + +blang[java]. + @io.ably.lib.realtime.ChannelEvent@ is an enum representing all the events that can be emitted be the @Channel@; either a "@ChannelState@":/realtime/channels#channel-states or an @update@ event. + + ```[java] + public enum ChannelEvent { + initialized, // 0 + attaching, // 1 + attached, // 2 + detaching, // 3 + detached, // 4 + failed // 5 + update // 6 + } + ``` + +blang[csharp]. + @IO.Ably.Realtime.ChannelEvent@ is an enum representing all the events that can be emitted be the @Channel@; either a "@ChannelState@":/realtime/channels#channel-states or an @Update@ event. + + ```[csharp] + public enum ChannelEvent + { + Initialized = 0, + Attaching = 1, + Attached = 2, + Detaching= 3, + Detached = 4, + Failed = 5, + Update = 6 + }; + ``` + +blang[ruby]. + @Ably::Realtime::Channel::EVENT@ is an enum-like value representing all the events that can be emitted be the @Channel@; either a "@ChannelState@":/realtime/channels#channel-states or an @:update@ event. @EVENT@ can be represented interchangeably as either symbols or constants. + + h4. Symbol states + + ```[ruby] + :initialized # => 0 + :attaching # => 1 + :attached # => 2 + :detaching # => 3 + :detached # => 4 + :failed # => 5 + :update # => 6 + ``` + + h4. Constant states + + ```[ruby] + Channel::EVENT.Initialized # => 0 + Channel::EVENT.Attaching # => 1 + Channel::EVENT.Attached # => 2 + Channel::EVENT.Detaching # => 3 + Channel::EVENT.Detached # => 4 + Channel::EVENT.Failed # => 5 + Channel::EVENT.Update # => 6 + ``` + +blang[objc,swift]. + @ARTChannelEvent@ is the enum emitted as the event in @ARTRealtimeChannel.on@; either a "@ChannelState@":/realtime/channels#channel-state or an @Update@ event. + + ```[objc] + typedef NS_ENUM(NSUInteger, ARTChannelEvent) { + ARTChannelEventInitialized, + ARTChannelEventAttaching, + ARTChannelEventAttached, + ARTChannelEventDetaching, + ARTChannelEventDetached, + ARTChannelEventFailed, + ARTChannelEventUpdate + }; + ``` + + ```[swift] + enum ARTChannelEvent : UInt { + case Initialized + case Attaching + case Attached + case Detaching + case Detached + case Failed + case Update + } + ``` + +blang[go]. + @ChannelEvent@ is a String that can be emitted as an event on the @Channel@ object; either a "@ChannelState@":/realtime/channels#channel-states or an @update@ event. + + ```[go] + const ( + StateChanInitialized = 256 + StateChanConnecting = 512 + StateChanConnected = 1024 + StateChanDisconnected = 2048 + StateChanSuspended = 4096 + StateChanClosing = 8192 + StateChanClosed = 16384 + StateChanFailed = 32768 + ) + ``` diff --git a/content/partials/versions/v1.1/types/_channel_options.textile b/content/partials/versions/v1.1/types/_channel_options.textile new file mode 100644 index 0000000000..2ff18322ef --- /dev/null +++ b/content/partials/versions/v1.1/types/_channel_options.textile @@ -0,0 +1,40 @@ +Currently the supported channel options are only used for "configuring encryption":/realtime/encryption. + +blang[jsall]. + @ChannelOptions@, a plain Javascript object, may optionally be specified when instancing a "@Channel@":/realtime/channels, and this may be used to specify channel-specific options. The following attributes can be defined on the object: + +blang[ruby]. + @ChannelOptions@, a Hash object, may optionally be specified when instancing a "@Channel@":/realtime/channels, and this may be used to specify channel-specific options. The following key symbol values can be added to the Hash: + +blang[php]. + @ChannelOptions@, an Associative Array, may optionally be specified when instancing a "@Channel@":/realtime/channels, and this may be used to specify channel-specific options. The following named keys and values can be added to the Associated Array: + +blang[java,swift,objc,go]. + @ART@@io.ably.lib.types.@@ChannelOptions@ may optionally be specified when instancing a "@Channel@":/realtime/channels, and this may be used to specify channel-specific options. + +blang[csharp]. + @IO.Ably.ChannelOptions@ may optionally be specified when instancing a "@Channel@":/realtime/channels, and this may be used to specify channel-specific options. + +h4. + default: Properties + java: Members + ruby: Attributes + +- cipher:cipherCipherParams := Requests encryption for this channel when not null, and specifies encryption-related parameters (such as algorithm, chaining mode, key length and key). See "an example":/realtime/encryption#getting-started
__Type: "@CipherParams@":/realtime/encryption#cipher-params or an options objecta @Param[]@ listan options hashan Associative Array containing at a minimum a @key@__ + +blang[java]. + h4. Static methods + + h6(#with-cipher-key). withCipherKey + + bq(definition). static ChannelOptions.withCipherKey(Byte[] or String key) + + A helper method to generate a @ChannelOptions@ for the simple case where you only specify a key. + + h4. Parameters + + - key := A binary @Byte[]@ array or a base64-encoded @String@. + + h4. Returns + + On success, the method returns a complete @ChannelOptions@ object. Failure will raise an "@AblyException@":/realtime/types#ably-exception. diff --git a/content/partials/versions/v1.1/types/_channel_state.textile b/content/partials/versions/v1.1/types/_channel_state.textile new file mode 100644 index 0000000000..83758812f9 --- /dev/null +++ b/content/partials/versions/v1.1/types/_channel_state.textile @@ -0,0 +1,122 @@ +blang[jsall]. + @ChannelState@ is a String with a value matching any of the "@Realtime Channel@ states":/realtime/channels#channel-states. + + ```[javascript] + var ChannelStates = [ + 'initialized', + 'attaching', + 'attached', + 'detaching', + 'detached', + 'failed', + 'suspended' + ] + ``` + +blang[java]. + @io.ably.lib.realtime.ChannelState@ is an enum representing all the "@Realtime Channel@ states":/realtime/channels#channel-states. + + ```[java] + public enum ChannelState { + initialized, // 0 + attaching, // 1 + attached, // 2 + detaching, // 3 + detached, // 4 + failed // 5 + } + ``` + +blang[csharp]. + @IO.Ably.Realtime.ChannelState@ is an enum representing all the "@Realtime Channel@ states":/realtime/channels#channel-states. + + ```[csharp] + public enum ChannelState + { + Initialized = 0, + Attaching = 1, + Attached = 2, + Detaching= 3, + Detached = 4, + Failed = 5 + }; + ``` + +blang[ruby]. + @Ably::Realtime::Channel::STATE@ is an enum-like value representing all the "@Realtime Channel@ states":/realtime/channels#channel-states. @STATE@ can be represented interchangeably as either symbols or constants. + + h4. Symbol states + + ```[ruby] + :initialized # => 0 + :attaching # => 1 + :attached # => 2 + :detaching # => 3 + :detached # => 4 + :failed # => 5 + ``` + + h4. Constant states + + ```[ruby] + Channel::STATE.Initialized # => 0 + Channel::STATE.Attaching # => 1 + Channel::STATE.Attached # => 2 + Channel::STATE.Detaching # => 3 + Channel::STATE.Detached # => 4 + Channel::STATE.Failed # => 5 + ``` + + h4. Example usage + + ```[ruby] + # Example with symbols + channel.on(:attached) { ... } + + # Example with constants + channel.on(Ably::Realtime::Channel::STATE.Attached) { ... } + + # Interchangeable + Ably::Realtime::Channel::STATE.Attached == :attached # => true + ``` + +blang[objc,swift]. + @ARTRealtimeChannelState@ is an enum representing all the "@Realtime Channel@ states":/realtime/channels#channel-states. + + ```[objc] + typedef NS_ENUM(NSUInteger, ARTRealtimeChannelState) { + ARTRealtimeChannelInitialized, + ARTRealtimeChannelAttaching, + ARTRealtimeChannelAttached, + ARTRealtimeChannelDetaching, + ARTRealtimeChannelDetached, + ARTRealtimeChannelFailed + }; + ``` + + ```[swift] + public enum ARTRealtimeChannelState : UInt { + case Initialized + case Attaching + case Attached + case Detaching + case Detached + case Failed + } + ``` + +blang[go]. + @ChannelState@ is a string representing all the "channel states":/realtime/channels#channel-states + + ```[go] + const ( + StateChanInitialized = 256 + StateChanConnecting = 512 + StateChanConnected = 1024 + StateChanDisconnected = 2048 + StateChanSuspended = 4096 + StateChanClosing = 8192 + StateChanClosed = 16384 + StateChanFailed = 32768 + ) + ``` diff --git a/content/partials/versions/v1.1/types/_channel_state_change.textile b/content/partials/versions/v1.1/types/_channel_state_change.textile new file mode 100644 index 0000000000..d3d50a88dc --- /dev/null +++ b/content/partials/versions/v1.1/types/_channel_state_change.textile @@ -0,0 +1,9 @@ +A @Ably::Models::ChannelStateChange@ChannelStateChangeARTRealtimeChannelStateChange is a type encapsulating state change information emitted by the "@Channel@":/realtime/channels object. See "@Channel#on@":/realtime/channels#on to register a listener for one or more events. + +h4. Attributes + +- currentCurrent := the new current state
__Type: "@ChannelState@Channel::STATE@":/realtime/types#channel-state__ +- previousPrevious := the previous state. (for the @update@ event, this will be equal to the @current@ state)
__Type: "@ChannelState@Channel::STATE@":/realtime/types#channel-state__ +- eventEvent := the event that triggered this state change
__Type: "@ChannelEvent@Channel::EVENT@":/realtime/types#channel-event__ +- reasonReason := an "@ErrorInfo@":#error-info containing any information relating to the transition
__Type: "@ErrorInfo@":/realtime/types#error-info__ +- resumedResumed := a boolean indicated whether message continuity on this channel is preserved, see "Nonfatal channel errors":/realtime/channels#nonfatal-errors for more info.
__Type: Boolean__ diff --git a/content/partials/versions/v1.1/types/_channel_state_listener.textile b/content/partials/versions/v1.1/types/_channel_state_listener.textile new file mode 100644 index 0000000000..6010e251dd --- /dev/null +++ b/content/partials/versions/v1.1/types/_channel_state_listener.textile @@ -0,0 +1,8 @@ +A @io.ably.lib.realtime.ChannelStateListener@ is an interface allowing a client to be notified of "channel state changes":/realtime/types/#channel-state-change for a "@Channel@":/realtime/channels + +```[java] + public interface ChannelStateListener { + // Called when the channel state changes + public void onChannelStateChanged(ChannelStateChange stateChange, ErrorInfo reason); + } +``` diff --git a/content/partials/versions/v1.1/types/_cipher_params.textile b/content/partials/versions/v1.1/types/_cipher_params.textile new file mode 100644 index 0000000000..575760980e --- /dev/null +++ b/content/partials/versions/v1.1/types/_cipher_params.textile @@ -0,0 +1,16 @@ +A @CipherParams@ contains configuration options for a channel cipher, including algorithm, mode, key length and key. Ably client libraries currently support AES with CBC, PKCS#7 with a default key length of 256 bits. All implementations also support AES128. + +Individual client libraries may support either instancing a @CipherParams@ directly, using "@Crypto.getDefaultParams()@":/realtime/encryption#get-default-params"@Crypto.GetDefaultParams()@":/realtime/encryption#get-default-params"@Crypto.get_default_params()@":/realtime/encryption#get-default-params, or generating one automatically when initializing a channel, as in "this example":/realtime/encryption#getting-started. + +h4. + default: Properties + java: Members + ruby: Attributes + +-
keyKey:key
:= A binary (@byte[]@@ArrayBuffer@ or @WordArray@@Buffer@byte array@NSData@) or base64-encoded @NS@@String@ containing the secret key used for encryption and decryption + +- algorithm:algorithmAlgorithm := _AES_ The name of the algorithm in the default system provider, or the lower-cased version of it; eg "aes" or "AES"
__Type: @String@__ +- key_length:key_lengthkeyLengthKeyLength := _256_ The key length in bits of the cipher, either 128 or 256
__Type: @Integer@__ +- mode:modeMode := _CBC_ The cipher mode
__Type: @String@@CipherMode@__ + +-
keySpec
:= A @KeySpec@ for the cipher key
__Type: @SecretKeySpec@__ diff --git a/content/partials/versions/v1.1/types/_client_options.textile b/content/partials/versions/v1.1/types/_client_options.textile new file mode 100644 index 0000000000..87f0c01031 --- /dev/null +++ b/content/partials/versions/v1.1/types/_client_options.textile @@ -0,0 +1,72 @@ +h4. + default: Properties + java: Members + ruby: Attributes + python: Keyword arguments + +- keyKey:key := The full key string, as obtained from the "application dashboard":http://support.ably.io/solution/articles/3000030053. Use this option if you wish to use Basic authentication, or wish to be able to issue Ably Tokens without needing to defer to a separate entity to sign Ably TokenRequests. Read more about "Basic authentication":/core-features/authentication#basic-authentication
__Type: @String@__ + +- tokenToken:token := An authenticated token. This can either be a "@TokenDetails@":/realtime/types#token-details object, a "@TokenRequest@":/realtime/types#token-request object, or token string (obtained from the @token@@Token@ property of a "@TokenDetails@":/realtime/types#token-details component of an Ably TokenRequest response, or a "JSON Web Token":https://tools.ietf.org/html/rfc7519 satisfying "the Ably requirements for JWTs":https://www.ably.io/documentation/core-features/authentication#ably-jwt). This option is mostly useful 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, such as @authUrl@@AuthUrl@:auth_urlauth_url or @authCallback@@AuthCallback@@auth_callback@@:auth_callback@. Read more about "Token authentication":/core-features/authentication#token-authentication
__Type: @String@, @TokenDetails@ or @TokenRequest@__ + +<%= partial partial_version('shared/_token_auth_methods') %> + +- tlsTls:tls := _true_ A boolean value, indicating whether or not a TLS ("SSL") secure connection should be used. An insecure connection cannot be used with Basic authentication as it would lead to a possible compromise of the private API key while in transit. "Find out more about TLS":https://support.ably.io/solution/articles/3000045208
__Type: @Boolean@__ + +- clientIdClientIdclient_id:client_id := A client ID, used for identifying this client when publishing messages or for presence purposes. The @clientId@@client_id@@ClientId@ can be any non-empty string. This option is primarily intended to be used in situations where the library is instanced with a key; note that a @clientId@@client_id@@ClientId@ may also be implicit in a token used to instance the library; an error will be raised if a @clientId@@client_id@ specified here conflicts with the @clientId@@client_id@@ClientId@ implicit in the token. "Find out more about client identities":/how-ably-works#client-identity
__Type: @String@__ + +- useTokenAuthUseTokenAuthuse_token_auth:use_token_auth := _false_ When true, forces "Token authentication":/core-features/authentication#token-authentication to be used by the library. Please note that if a @client_id@@clientId@ is not specified in the "@ClientOptions@":/realtime/types#client-options or "@TokenParams@":/realtime/types#token-params, then the Ably Token issued will be "anonymous":https://support.ably.io/solution/articles/3000038608.
__Type: @Boolean@__ + +- environmentEnvironment:environment := _nullNullNonenil_ Allows a "custom environment":https://support.ably.io/a/solutions/articles/3000077619, region or cluster to be used with the Ably service. Please "contact us":https://www.ably.io/contact if you require a custom environment. Note that once a custom environment is specified, the "fallback host functionality":https://support.ably.io/solution/articles/3000044636 is disabled by default.
__Type: @String@__ + +- idempotentRestPublishingIdempotentRestPublishing:idempotent_rest_publishing := _false_ When true, enables idempotent publishing by assigning a unique message ID client-side, allowing the Ably servers to discard automatic publish retries following a failure such as a network fault. We recommend you enable this by default. In version 1.2 onwards, idempotent publishing for retries will be enabled by default.
__Type: @Boolean@__ + +- fallbackHostsFallbackHostsfallback_hosts:fallback_hosts := _@[a.ably-realtime.com, b.ably-realtime.com, c.ably-realtime.com, d.ably-realtime.com, e.ably-realtime.com]@_ An array of fallback hosts to be used in the case of an error necessitating the use of an alternative host. +When a custom environment is specified, the "fallback host functionality":https://support.ably.io/solution/articles/3000044636 is disabled. If your customer success manager has provided you with a set of custom fallback hosts, please specify them here.
__Type: @String []@__ + +- fallbackHostsUseDefaultFallbackHostsUseDefaultfallback_hosts_use_default:fallback_hosts_use_default := _true_ When true, the client will use fallback hosts in the case of an error necessitating the use of an alternative host. When a custom environment is specified, by default the "fallback host functionality":https://support.ably.io/solution/articles/3000044636 is disabled. Before setting this to true, please check with your customer success manager if this is supported for your environment.
__Type: @Boolean@__ + +- transportParamsTransportParamstransport_params:transport_params := Optional. Can be used to pass in arbitrary connection parameters.
__Type: @Object@@Dict@@Hash@@Associative Array@@Param []@@Dictionary@__ + +blang[jsall]. + - log := Parameters to control the log output of the library. The supplied value must be an object that may contain one or both of the following entries: +
    +
  • @level@: a number controlling the verbosity of the output. Valid values are: 0 (no logs), 1 (errors only), 2 (errors plus connection and channel state changes), 3 (high-level debug output), and 4 (full debug output).
  • +
  • @handler@: a function to handle each line of log output. If @handler@ is not specified, @console.log@ is used.
  • +
+ Note that the log level and log handler have global scope in the library and will thus not act independently between library instances when multiple library instances exist concurrently.
__Type: @Object@__ + + - transports := An optional array of transports to use, in descending order of preference. In the browser environment the available transports are: @web_socket@, @xhr@, @jsonp@.The transports available in the Node.js client library are: @web_socket@, @xhr@, @comet@.
__Type: @String []@__ + +blang[java]. + - logLevel := _5_ A number controlling the verbosity of the output from 2 (maximum, verbose) to 6 (errors only). A special value of 99 will silence all logging. Note that the @logLevel@ is a static variable in the library and will thus not act independently between library instances when multiple library instances exist concurrently. See "the logging section of the java library README":https://github.com/ably/ably-java/#logging for more details.
__Type: @Integer@__ + + - logHandler := _@System.out PrintStream@_ A @LogHandler@ interface can be specified to handle each line of log output. If @logHandler@ is not specified, @System.out@ is used. Note that the @logHandler@ is a static variable in the library and will thus not act independently between library instances when multiple library instances exist concurrently. See "the logging section of the java library README":https://github.com/ably/ably-java/#logging for more details.
__Type: PrintStream__ + +blang[csharp]. + To set the log level and custom logger sink when using the .Net library, configure the static @IO.Ably.Logger@ class or specify the @ClientOptions@: + + - LogLevel := _@Error@_ This is an enum controlling the verbosity of the output from @Debug@ (maximum) to @Error@ (errors only). A special value of @None@ will silence all logging. Note that the @LogLevel@ is a static variable in the library and will thus not act independently between library instances.
__Type: @Enum@__ + + - LoggerSink := _@IO.Ably.DefaultLoggerSink@_ The default ILoggerSink outputs messages to the debug console. This property allows the user to pipe the log messages to their own logging infrastructure. + +blang[go]. + - LogLevel := _@LogError@_ This is an enum controlling the verbosity of the output from @LogDebug@ (maximum) to @LogError@ (errors only). A special value of @LogNone@ will silence all logging. Note that the @LogLevel@ is a static variable in the library and will thus not act independently between library instances.
__Type: @Enum@__ + +blang[objc,swift]. + - logLevel := _ARTLogLevelWarn_ An enum controlling the verbosity of the output from @ARTLogLevelVerbose@ to @ARTLogLevelNone@. A special value of 99 will silence all logging.
__Type: @ARTLogLevel@__ + + - logHandler := A @ARTLog@ object can be specified to handle each line of log output. If @logHandler@ is not specified, a default @ARTLog@ instance is used.
__Type: @ARTLog *@__ + +blang[ruby]. + - :log_level :=_@:error@_ Log level for the standard Logger that outputs to @STDOUT@. Can be set to @:fatal@, @:error@, @:warn@, @:info@, @:debug@ or @:none@. Alternatively a "@Logger@ severity constant":http://ruby-doc.org/stdlib-2.2.0/libdoc/logger/rdoc/Logger.html#class-Logger-label-Description can be specified.
__Type: @Symbol@, "@Logger::SEVERITY@":http://ruby-doc.org/stdlib-2.2.0/libdoc/logger/rdoc/Logger.html#class-Logger-label-Description__ + + - :logger := _@STDOUT Logger@_ A "Ruby @Logger@":http://ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html compatible object to handle each line of log output. If @logger@ is not specified, @STDOUT@ is used.
__Type: "Ruby @Logger":http://ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html__ + +blang[php]. + - logLevel :=_@Log::WARNING@_ A number controlling the verbosity of the output from 1 (minimum, errors only) to 4 (most verbose);
__Type: @Integer@__ + + - logHandler := _@console.log@_ A function to handle each line of log output. If handler is not specified, @console.log@ is used. Note that the log level and log handler have global scope in the library and will therefore not act independently between library instances when multiple library instances exist concurrently.
__Type: @Function@__ + +- useBinaryProtocolUseBinaryProtocoluse_binary_protocol:use_binary_protocol := _false_ If set to true, will enable the binary protocol (MessagePack) if it is supported. It's disabled by default on browsers for performance considerations (browsers are optimized for decoding JSON)_true_ If set to false, will forcibly disable the binary protocol (MessagePack). The binary protocol is used by default unless it is not supportedNote: The binary protocol is currently not supported in Swiftin Objective-Cin PHP. Find out more about the "benefits of binary encoding":https://support.ably.io/solution/articles/3000047365
__Type: @Boolean@__ + +-
logExceptionReportingUrl
:= Defaults to a string value for an Ably error reporting Data Source Name.
__Type: @String@__ diff --git a/content/partials/versions/v1.1/types/_completion_listener.textile b/content/partials/versions/v1.1/types/_completion_listener.textile new file mode 100644 index 0000000000..5a49a6ef42 --- /dev/null +++ b/content/partials/versions/v1.1/types/_completion_listener.textile @@ -0,0 +1,11 @@ +A @io.ably.lib.realtime.CompletionListener@ is an interface allowing a client to be notified of the outcome of an asynchronous operation. + +```[java] + public interface CompletionListener { + // Called when the associated operation completes successfully, + public void onSuccess(); + + // Called when the associated operation completes with an error. + public void onError(ErrorInfo reason); + } +``` diff --git a/content/partials/versions/v1.1/types/_connection_event.textile b/content/partials/versions/v1.1/types/_connection_event.textile new file mode 100644 index 0000000000..04a0d75de1 --- /dev/null +++ b/content/partials/versions/v1.1/types/_connection_event.textile @@ -0,0 +1,143 @@ +blang[jsall]. + @ConnectionEvent@ is a String that can be emitted as an event on the @Connection@ object; either a "@Realtime Connection@ state":/realtime/connection#connection-states or an @update@ event. + + + ```[javascript] + var ConnectionEvents = [ + 'initialized', + 'connecting', + 'connected', + 'disconnected', + 'suspended', + 'closing', + 'closed', + 'failed', + 'update' + ] + ``` + +blang[java]. + @io.ably.lib.realtime.ConnectionEvent@ is an enum representing all the events that can be emitted be the @Connection@; either a "@Realtime Connection@ state":/realtime/connection#connection-states or an @update@ event. + + ```[java] + public enum ConnectionEvent { + initialized, // 0 + connecting, // 1 + connected, // 2 + disconnected, // 3 + suspended, // 4 + closing, // 5 + closed, // 6 + failed, // 7 + update // 8 + } + ``` + +blang[csharp]. + @IO.Ably.Realtime.ConnectionEvent@ is an enum representing all the events that can be emitted be the @Connection@; either a "@Realtime Connection@ state":/realtime/connection#connection-states or an @Update@ event. + + ```[csharp] + public enum ConnectionState + { + Initialized, //0 + Connecting, //1 + Connected, //2 + Disconnected, //3 + Suspended, //4 + Closing, //5 + Closed, //6 + Failed, //7 + update //8 + }; + ``` + +blang[ruby]. + @Ably::Realtime::Connection::EVENT@ is an enum-like value representing all the events that can be emitted be the @Connection@; either a "@Realtime Connection@ state":/realtime/connection#connection-states or an @:update@ event. @EVENT@ can be represented interchangeably as either symbols or constants. + + h4. Symbol states + + ```[ruby] + :initialized # => 0 + :connecting # => 1 + :connected # => 2 + :disconnected # => 3 + :suspended # => 4 + :closing # => 5 + :closed # => 6 + :failed # => 7 + :update # => 8 + ``` + + h4. Constant states + + ```[ruby] + Connection::EVENT.Initialized # => 0 + Connection::EVENT.Connecting # => 1 + Connection::EVENT.Connected # => 2 + Connection::EVENT.Disconnected # => 3 + Connection::EVENT.Suspended # => 4 + Connection::EVENT.Closing # => 5 + Connection::EVENT.Closed # => 6 + Connection::EVENT.Failed # => 7 + Connection::EVENT.Update # => 8 + ``` + + h4. Example usage + + ```[ruby] + # Example with symbols + client.connection.on(:connected) { ... } + + # Example with constants + client.connection.on(Ably::Realtime::Connection::STATE.Connected) { ... } + + # Interchangeable + Ably::Realtime::Connection::STATE.Connected == :connected # => true + ``` + +blang[objc,swift]. + @ARTRealtimeConnectionEvent@ is an enum representing all the events that can be emitted be the @Connection@; either a "@Realtime Connection@ state":/realtime/connection#connection-states or an @Update@ event. + + ```[objc] + typedef NS_ENUM(NSUInteger, ARTRealtimeConnectionEvent) { + ARTRealtimeConnectionEventInitialized, + ARTRealtimeConnectionEventConnecting, + ARTRealtimeConnectionEventConnected, + ARTRealtimeConnectionEventDisconnected, + ARTRealtimeConnectionEventSuspended, + ARTRealtimeConnectionEventClosing, + ARTRealtimeConnectionEventClosed, + ARTRealtimeConnectionEventFailed, + ARTRealtimeConnectionEventUpdate + }; + ``` + + ```[swift] + public enum ARTRealtimeConnectionEvent : UInt { + case Initialized + case Connecting + case Connected + case Disconnected + case Suspended + case Closing + case Closed + case Failed + case Update + } + ``` + +blang[go]. + @ConnectionEvent@ is a String that can be emitted as an event on the @Connection@ object; either a "@Realtime Connection@ state":/realtime/connection#connection-states or an @update@ event. + + ```[go] + const ( + StateConnInitialized = 1 + StateConnConnecting = 2 + StateConnConnected = 4 + StateConnDisconnected = 8 + StateConnSuspended = 16 + StateConnClosing = 32 + StateConnClosed = 64 + StateConnFailed = 128 + ) + ``` diff --git a/content/partials/versions/v1.1/types/_connection_state.textile b/content/partials/versions/v1.1/types/_connection_state.textile new file mode 100644 index 0000000000..9d5b70f0af --- /dev/null +++ b/content/partials/versions/v1.1/types/_connection_state.textile @@ -0,0 +1,135 @@ +blang[jsall]. + @ConnectionState@ is a String with a value matching any of the "@Realtime Connection@ states":/realtime/connection#connection-states. + + ```[javascript] + var ConnectionStates = [ + 'initialized', + 'connecting', + 'connected', + 'disconnected', + 'suspended', + 'closing', + 'closed', + 'failed' + ] + ``` + +blang[java]. + @io.ably.lib.realtime.ConnectionState@ is an enum representing all the "@Realtime Connection@ states":/realtime/connection#connection-states. + + ```[java] + public enum ConnectionState { + initialized, // 0 + connecting, // 1 + connected, // 2 + disconnected, // 3 + suspended, // 4 + closing, // 5 + closed, // 6 + failed // 7 + } + ``` + +blang[csharp]. + @IO.Ably.Realtime.ConnectionState@ is an enum representing all the "@Realtime Connection@ states":/realtime/connection#connection-states. + + ```[csharp] + public enum ConnectionState + { + Initialized, //0 + Connecting, //1 + Connected, //2 + Disconnected, //3 + Suspended, //4 + Closing, //5 + Closed, //6 + Failed //7 + }; + ``` + +blang[ruby]. + @Ably::Realtime::Connection::STATE@ is an enum-like value representing all the "@Realtime Connection@ states":/realtime/connection#connection-states. @STATE@ can be represented interchangeably as either symbols or constants. + + h4. Symbol states + + ```[ruby] + :initialized # => 0 + :connecting # => 1 + :connected # => 2 + :disconnected # => 3 + :suspended # => 4 + :closing # => 5 + :closed # => 6 + :failed # => 7 + ``` + + h4. Constant states + + ```[ruby] + Connection::STATE.Initialized # => 0 + Connection::STATE.Connecting # => 1 + Connection::STATE.Connected # => 2 + Connection::STATE.Disconnected # => 3 + Connection::STATE.Suspended # => 4 + Connection::STATE.Closing # => 5 + Connection::STATE.Closed # => 6 + Connection::STATE.Failed # => 7 + ``` + + h4. Example usage + + ```[ruby] + # Example with symbols + client.connection.on(:connected) { ... } + + # Example with constants + client.connection.on(Ably::Realtime::Connection::STATE.Connected) { ... } + + # Interchangeable + Ably::Realtime::Connection::STATE.Connected == :connected # => true + ``` + +blang[objc,swift]. + @ARTRealtimeConnectionState@ is an enum representing all the "@Realtime Connection@ states":/realtime/connection#connection-states. + + ```[objc] + typedef NS_ENUM(NSUInteger, ARTRealtimeConnectionState) { + ARTRealtimeInitialized, + ARTRealtimeConnecting, + ARTRealtimeConnected, + ARTRealtimeDisconnected, + ARTRealtimeSuspended, + ARTRealtimeClosing, + ARTRealtimeClosed, + ARTRealtimeFailed + }; + ``` + + ```[swift] + public enum ARTRealtimeConnectionState : UInt { + case Initialized + case Connecting + case Connected + case Disconnected + case Suspended + case Closing + case Closed + case Failed + } + ``` + +blang[go]. + @ConnectionState@ is an enum representing all the "@Realtime Connection@ states":/realtime/connection#connection-states. + + ```[go] + const ( + StateConnInitialized = 1 + StateConnConnecting = 2 + StateConnConnected = 4 + StateConnDisconnected = 8 + StateConnSuspended = 16 + StateConnClosing = 32 + StateConnClosed = 64 + StateConnFailed = 128 + ) + ``` diff --git a/content/partials/versions/v1.1/types/_connection_state_change.textile b/content/partials/versions/v1.1/types/_connection_state_change.textile new file mode 100644 index 0000000000..c2b6245ca7 --- /dev/null +++ b/content/partials/versions/v1.1/types/_connection_state_change.textile @@ -0,0 +1,12 @@ +A @io.ably.lib.realtime.ConnectionStateListener.ConnectionStateChange@@Ably::Models::ConnectionStateChange@@ARTConnectionStateChange@@IO.Ably.Realtime.ConnectionStateChange@@ConnectionStateChange@ is a type encapsulating state change information emitted by the "@Connection@":/realtime/connection object. See "@Connection#on@":/realtime/connection#on to register a listener for one or more events. + +h4. + default: Properties + java: Members + ruby: Attributes + +- currentCurrent := the new state
__Type: "State @String@":/realtime/types#connection-state"@Connection::STATE@":/realtime/types#connection-state"@ConnectionState@":/realtime/types#connection-state__ +- previousPrevious := the previous state. (for the @update@ event, this will be equal to the @current@ state)
__Type: "State @String@":/realtime/types#connection-state"@Connection::STATE@":/realtime/types#connection-state"@ConnectionState@":/realtime/types#connection-state__ +- eventEvent := the event that triggered this state change
__Type: "@ConnectionEvent@Connection::EVENT@":/realtime/types#connection-event__ +- reasonReason := an "@ErrorInfo@":#error-info containing any information relating to the transition
__Type: "@ErrorInfo@":/realtime/types#error-info__ +- retryInretry_inRetryIn := Duration upon which the library will retry a connection where applicable, as millisecondssecondsa @Timespan@
__Type: @Integer@@Timespan@@Long Integer@__ diff --git a/content/partials/versions/v1.1/types/_connection_state_listener.textile b/content/partials/versions/v1.1/types/_connection_state_listener.textile new file mode 100644 index 0000000000..50a626265d --- /dev/null +++ b/content/partials/versions/v1.1/types/_connection_state_listener.textile @@ -0,0 +1,8 @@ +A @io.ably.lib.realtime.ConnectionStateListener@ is an interface allowing a client to be notified of connection state change. See "@Connection#on@":/realtime/connection#on to register a listener for one or more events. + +```[java] + public interface ConnectionStateListener { + // Called when the connection state changes + public void onConnectionStateChanged(ConnectionStateListener.ConnectionStateChange state); + } +``` diff --git a/content/partials/versions/v1.1/types/_deferrable.textile b/content/partials/versions/v1.1/types/_deferrable.textile new file mode 100644 index 0000000000..7645446d3e --- /dev/null +++ b/content/partials/versions/v1.1/types/_deferrable.textile @@ -0,0 +1,30 @@ +The @SafeDeferrable@ class provides an "EventMachine":https://github.com/eventmachine/eventmachine compatible "@Deferrable@":http://www.rubydoc.info/gems/eventmachine/EventMachine/Deferrable. + +A @SafeDeferrable@ ensures that any exceptions in callbacks provided by developers will not break the client library and stop further execution of code. + +h4. Methods + +h6(#callback). callback + +bq(definition). callback(&block) + +Specify a block to be executed if and when the @Deferrable@ object receives a status of @:succeeded@. See the "EventMachine callback documentation":http://www.rubydoc.info/gems/eventmachine/EventMachine/Deferrable#callback-instance_method + +h6(#errback). errback + +bq(definition). errback(&block) + +Specify a block to be executed if and when the @Deferrable@ object receives a status of @:failed@. See the "EventMachine errback documentation":http://www.rubydoc.info/gems/eventmachine/EventMachine/Deferrable#errback-instance_method + +h6(#fail). fail + +bq(definition). fail(*args) + +Mark the @Deferrable@ as failed and trigger all callbacks. See the "EventMachine fail documentation":http://www.rubydoc.info/gems/eventmachine/EventMachine/Deferrable#fail-instance_method + +h6(#succeed). succeed + +bq(definition). succeed(*args) + +Mark the @Deferrable@ as succeeded and trigger all callbacks. See the "EventMachine succeed documentation":http://www.rubydoc.info/gems/eventmachine/EventMachine/Deferrable#succeed-instance_method + diff --git a/content/partials/versions/v1.1/types/_device_details.textile b/content/partials/versions/v1.1/types/_device_details.textile new file mode 100644 index 0000000000..a429afbacd --- /dev/null +++ b/content/partials/versions/v1.1/types/_device_details.textile @@ -0,0 +1,24 @@ +A @DeviceDetails@ is a type encapsulating attributes of a device registered for push notifications. + +h4. + default: Properties + java: Members + ruby: Attributes + +- id := unique identifier for the device generated by the device itself
__Type: @String@__ + +- clientIdclient_id := optional trusted "client identifier":/core-features/authentication#identified-clients for the device
__Type: @String@__ + +- formFactorform_factor := form factor of the push device. Must be one of @phone@, @tablet@, @desktop@, @tv@, @watch@, @car@ or @embedded@
__Type: @String@__ + +- metadata := optional metadata object for this device. The metadata for a device may only be set by clients with @push-admin@ privileges and will be used more extensively in the future with smart notifications
__Type: @Object@@Array@@Hash@__ + +- platform := platform of the push device. Must be one of @ios@ or @android@
__Type: @String@__ + +- deviceSecret := Secret value for the device.
__Type: @String@__ + +- push.recipient := push recipient details for this device. See the "REST API push publish documentation":/rest-api#message-extras-push for more details
__Type: @Object@@Array@@Hash@__ + +- push.state := the current state of the push device being either @Active@, @Failing@ or @Failed@
__Type: @String@__ + +- push.errorReasonpush.error_reason := when the device's state is failing or failed, this attribute contains the reason for the most recent failure
__Type: "@ErrorInfo@":/realtime/types#error-info__ diff --git a/content/partials/versions/v1.1/types/_error_info.textile b/content/partials/versions/v1.1/types/_error_info.textile new file mode 100644 index 0000000000..402580efc0 --- /dev/null +++ b/content/partials/versions/v1.1/types/_error_info.textile @@ -0,0 +1,14 @@ +An @ErrorInfo@ is a type encapsulating error information containing an Ably-specific error code and generic status code. + +h4. + default: Properties + java: Members + ruby: Attributes + +- codeCode := Ably error code (see "ably-common/protocol/errors.json":https://github.com/ably/ably-common/blob/master/protocol/errors.json)
__Type: @Integer@__ + +- statusCodestatus_codeStatusCode := HTTP Status Code corresponding to this error, where applicable
__Type: @Integer@__ + +- messageMessage := Additional message information, where available
__Type: @String@__ + +- causeCause := Information pertaining to what caused the error where available
__Type: @ErrorInfo@__ diff --git a/content/partials/versions/v1.1/types/_history_request_params.textile b/content/partials/versions/v1.1/types/_history_request_params.textile new file mode 100644 index 0000000000..be158f08d5 --- /dev/null +++ b/content/partials/versions/v1.1/types/_history_request_params.textile @@ -0,0 +1,9 @@ +@HistoryRequestParams@ is a type that encapsulates the parameters for a history queries. For example usage see "@Channel#history@@Channel#History@":/realtime/history#channel-history. + +h4. Members + +- Start := _null_ The start of the queried interval
__Type: @DateTimeOffset@__ +- End := _null_ The end of the queried interval
__Type: @DateTimeOffset@__ +- Limit := _null_ By default it is null. Limits the number of items returned by history or stats
__Type: @Integer@__ +- Direction := _Backwards_ Enum which is either @Forwards@ or @Backwards@
__Type: @Direction@ enum__ +- ExtraParameters := Optionally any extra query parameters that may be passed to the query. This is mainly used internally by the library to manage paging.
__Type: @Dictionary@__ diff --git a/content/partials/versions/v1.1/types/_http_paginated_response.textile b/content/partials/versions/v1.1/types/_http_paginated_response.textile new file mode 100644 index 0000000000..15504204f9 --- /dev/null +++ b/content/partials/versions/v1.1/types/_http_paginated_response.textile @@ -0,0 +1,94 @@ +An @HttpPaginatedResponse@ is a superset of "@PaginatedResult@":/rest/types#paginated-result, which is a type that represents a page of results plus metadata indicating the relative queries available to it. @HttpPaginatedResponse@ additionally carries information about the response to an HTTP request. It is used when "making custom HTTP requests":/rest/usage#request. + +h4. + default: Properties + java: Members + ruby: Attributes + python: Attributes + + +- itemsItems := contains a page of results (for example an Array of "@Message@":#message or "@PresenceMessage@":#presence-message objects for a channel history request).
__Type: @Array<>@____Type: @List<>@__ +- statusCodestatus_codeStatusCode := the HTTP status code of the response
__Type: @Number@__ +- successSuccess := whether that HTTP status code indicates success (equivalent to @200 <= statusCode < 300@)
__Type: @Boolean@__ +- headersHeaders := the response's headers
__Type: @Object@__ + +h4. Methods + +
+ +
+
+ +h6. + default: first + csharp: First + +bq(definition). + default: first(callback("ErrorInfo":/realtime/types#error-info err, "HttpPaginatedResponse":/realtime/types#http-paginated-response resultPage)) + ruby: "HttpPaginatedResponse":/realtime/types#http-paginated-response first + php: "HttpPaginatedResponse":/realtime/types#http-paginated-response first() + python: "HttpPaginatedResponse":/realtime/types#http-paginated-response first() + csharp: Task> FirstAsync() + java: "HttpPaginatedResponse":/realtime/types#http-paginated-response first() + swift,objc: first(callback: (ARTHttpPaginatedResponse?, ARTErrorInfo?) -> Void) + go: First() ("HttpPaginatedResponse":/realtime/types#http-paginated-response, error) + +Returns a new @HttpPaginatedResponse@ for the first page of results. When using the Realtime library, the @first@ method returns a "Deferrable":/realtime/types#deferrable and yields an "@HttpPaginatedResponse@":/realtime/types#http-paginated-response.The method is asynchronous and returns a Task which needs to be awaited to get the @HttpPaginatedResponse":/realtime/types#http-paginated-response. + +h6. + default: hasNext + csharp,go: HasNext + ruby: has_next? + python: has_next + +bq(definition). + default: Boolean hasNext() + ruby: Boolean has_next? + php: Boolean hasNext() + python: Boolean has_next() + csharp: Boolean HasNext() + java: Boolean hasNext() + swift,objc: Boolean hasNext() + go: HasNext() (bool) + +Returns @true@ if there are more pages available by calling @next@@Next@ and returns @false@ if this page is the last page available. + +h6. + default: isLast + csharp,go: IsLast + ruby: last? + python: is_last + +bq(definition). + default: Boolean isLast() + ruby: Boolean last? + php: Boolean isLast() + python: Boolean is_last() + csharp: Boolean IsLast() + java: Boolean isLast() + swift,objc: Boolean isLast() + go: IsLast() (bool) + +Returns @true@ if this page is the last page and returns @false@ if there are more pages available by calling @next@@Next@ available. + +
+ +h6. + default: next + csharp,go: Next + +bq(definition). + default: next(callback("ErrorInfo":/realtime/types#error-info err, "HttpPaginatedResponse":/realtime/types#http-paginated-response resultPage)) + ruby: "HttpPaginatedResponse":/realtime/types#http-paginated-response next + php: "HttpPaginatedResponse":/realtime/types#http-paginated-response next() + python: "HttpPaginatedResponse":/realtime/types#http-paginated-response next() + csharp: Task<"HttpPaginatedResponse":/realtime/types#http-paginated-response > NextAsync() + java: "HttpPaginatedResponse":/realtime/types#http-paginated-response next() + swift,objc: next(callback: (ARTHttpPaginatedResponse?, ARTErrorInfo?) -> Void) + go: Next() ("HttpPaginatedResponse":/realtime/types#http-paginated-response, error) + +Returns a new @HttpPaginatedResponse@ loaded with the next page of results. If there are no further pages, then @null@a blank HttpPaginatedResponse will be returned@Null@@None@@nil@ is returned. The method is asynchronous and return a Task which needs to be awaited to get the @HttpPaginatedResponse@When using the Realtime library, the @first@ method returns a "Deferrable":/realtime/types#deferrable and yields an "HttpPaginatedResponse":/realtime/types#http-paginated-response. + +h4. Example + +The @HttpPaginatedResponse@ interface is a superset of @PaginatedResult@, see the "@PaginatedResult@ example":/rest/types/#paginated-result-example diff --git a/content/partials/versions/v1.1/types/_last_connection_details.textile b/content/partials/versions/v1.1/types/_last_connection_details.textile new file mode 100644 index 0000000000..a5bc2c7d57 --- /dev/null +++ b/content/partials/versions/v1.1/types/_last_connection_details.textile @@ -0,0 +1,14 @@ +A @LastConnectionDetails@ object provides details on the last connection in a browser environment persisted when the @window beforeunload@ fired. This object is provided to the callback specified in the @recover@ attribute of "@ClientOptions@":/realtime/types#client-options. The callback in turn instructs the client library whether the connection should be recovered or not. See "connection state recovery":/realtime/connection/#connection-state-recovery for more information. + +Please note that as "@sessionStorage@":https://www.w3.org/TR/webstorage/ is used to persist the @LastConnectionDetails@ between page reloads, it is only available for pages in the same origin and top-level browsing context. + +h4. + default: Properties + +- recoveryKey := An opaque string obtained from the "recoveryKey":/realtime/connection/#recovery-key attribute of the "Connection object":/realtime/connection before the page was unloaded. This property is used by the library to recover the connection
__Type: @String@__ + +- disconnectedAt := the time at which the previous library was abruptly disconnected before the page was unloaded. This is represented as milliseconds since epoch
__Type: @Integer@__ + +- location := a clone of "@location@":https://www.w3.org/TR/html5/browsers.html#the-location-interface object of the previous page's @document@ object before the page was unloaded. A common use case for this attribute is to ensure that the previous page URL is the same as the current URL before allowing the connection to be recovered. For example, you may want the connection to be recovered only for page reloads, but not when a user navigates to a different page
__Type: @String@__ + +- clientId := the "@clientId@":/realtime/authentication/#client-id of the "client's Auth object":/realtime/authentication before the page was unloaded. A common use case for this attribute is to ensure that the current logged in user's @clientId@ matches the previous connection's @clientId@ before allowing the connection to be recovered. Ably prohibits changing a @clientId@ for an existing connection, so any mismatch in @clientId@ during a recover will result in the connection moving to the failed state
__Type: @String@__ diff --git a/content/partials/versions/v1.1/types/_local_device.textile b/content/partials/versions/v1.1/types/_local_device.textile new file mode 100644 index 0000000000..1bed9ec7e1 --- /dev/null +++ b/content/partials/versions/v1.1/types/_local_device.textile @@ -0,0 +1,8 @@ +An extension of "@DeviceDetails@":#device-details. In addition to the propertiesmembersattributes of "@DeviceDetails@":#device-details, it includes the following: + +h4. + default: Properties + java: Members + ruby: Attributes + +- deviceIdentityToken := a unique identity token for the device
__Type: @String@__ diff --git a/content/partials/versions/v1.1/types/_message.textile b/content/partials/versions/v1.1/types/_message.textile new file mode 100644 index 0000000000..7947b7954b --- /dev/null +++ b/content/partials/versions/v1.1/types/_message.textile @@ -0,0 +1,60 @@ +A @Message@ represents an individual message that is sent to or received from Ably. + +h4. + default: Properties + java: Members + ruby: Attributes + python: Attributes + +- nameName := Event name, if provided
__Type: @String@__ + +- dataData := The presence update payload, if provided
__Type: @String@, @ByteArray@, @JSONObject@, @JSONArray@@String@, @byte[]@, plain C# object that can be converted to Json@String@, @[]byte@@String@, @StringBuffer@, @JSON Object@@String@, @Binary@ (ASCII-8BIT String), @Hash@, @Array@@String@, @Bytearray@, @Dict@, @List@@String@, @NSData@, @Dictionary@, @Array@@NSString *@, @NSData *@, @NSDictionary *@, @NSArray *@@String@, @Binary String@, @Associative Array@, @Array@__ + +- extrasExtras := Metadata and/or ancillary payloads, if provided. The only currently valid payload for extras is the "@push@":/general/push/publish#channel-broadcast-example object.
__Type: @JSONObject@, @JSONArray@plain C# object that can be converted to Json@String@, @[]byte@@JSON Object@@Hash@, @Array@@Dict@, @List@@Dictionary@, @Array@@NSDictionary *@, @NSArray *@@Associative Array@, @Array@__ + +- idId := Unique ID assigned by Ably to this message. Can optionally be assigned by the client as part of "idempotent publishing":/rest/messages#idempotent
__Type: @String@__ + +- clientIdclient_idClientId := The client ID of the publisher of this message
__Type: @String@__ + +- connectionIdconnection_idConnectionId := The connection ID of the publisher of this message
__Type: @String@__ + +- timestampTimestamp := Timestamp when the message was received by the Ably, as milliseconds since the epocha @Time@ object
__Type: @Integer@@Long Integer@@DateTimeOffset@@Time@@NSDate@__ + +- encodingEncoding := This will typically be empty as all messages received from Ably are automatically decoded client-side using this value. However, if the message encoding cannot be processed, this attribute will contain the remaining transformations not applied to the @data@ payload
__Type: @String@__ + +h3. + default: Message constructors + +h4(#message-from-encoded). + default: Message.fromEncoded + +bq(definition). + default: Message.fromEncoded(Object encodedMsg, ChannelOptions channelOptions?) -> Message + +A static factory method to create a "@Message@":/realtime/types#message from a deserialized @Message@-like object encoded using Ably's wire protocol. + +h4. Parameters + +- encodedMsg := a @Message@-like deserialized object.
__Type: @Object@__ +- channelOptions := an optional "@ChannelOptions@":/realtime/types#channel-options. If you have an encrypted channel, use this to allow the library can decrypt the data.
__Type: @Object@__ + +h4. Returns + +A "@Message@":/realtime/types#message object + +h4(#message-from-encoded-array). + default: Message.fromEncodedArray + +bq(definition). + default: Message.fromEncodedArray(Object[] encodedMsgs, ChannelOptions channelOptions?) -> Message[] + +A static factory method to create an array of "@Messages@":/realtime/types#message from an array of deserialized @Message@-like object encoded using Ably's wire protocol. + +h4. Parameters + +- encodedMsgs := an array of @Message@-like deserialized objects.
__Type: @Array@__ +- channelOptions := an optional "@ChannelOptions@":/realtime/types#channel-options. If you have an encrypted channel, use this to allow the library can decrypt the data.
__Type: @Object@__ + +h4. Returns + +An @Array@ of "@Message@":/realtime/types#message objects diff --git a/content/partials/versions/v1.1/types/_message_listener.textile b/content/partials/versions/v1.1/types/_message_listener.textile new file mode 100644 index 0000000000..90c269af38 --- /dev/null +++ b/content/partials/versions/v1.1/types/_message_listener.textile @@ -0,0 +1,8 @@ +A @io.ably.lib.realtime.Channel.MessageListener@ is an interface allowing a client to be notified when messages are received on a channel using a "channel subscription":/realtime/messages#message-subscription. + +```[java] + public interface MessageListener { + // Called when one or more messages are received + public void onMessage(Message message); + } +``` diff --git a/content/partials/versions/v1.1/types/_paginated_result.textile b/content/partials/versions/v1.1/types/_paginated_result.textile new file mode 100644 index 0000000000..e2b5da6df1 --- /dev/null +++ b/content/partials/versions/v1.1/types/_paginated_result.textile @@ -0,0 +1,195 @@ +A @PaginatedResult@ is a type that represents a page of results for all message and presence history, stats and REST presence requests. The response from a "Ably REST API paginated query":/rest-api/#pagination is accompanied by metadata that indicates the relative queries available to the @PaginatedResult@ object. + +h4. + default: Properties + java: Members + ruby: Attributes + +- itemsItems := contains the current page of results (for example an Array of "@Message@":#message or "@PresenceMessage@":#presence-message objects for a channel history request)
__Type: @Array @____Type: @List @__ + +h4. Methods + +
+ +
+
+h6. + default: first + csharp: First + +bq(definition). + default: first(callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":/realtime/types#paginated-result resultPage)) + ruby: "PaginatedResult":/realtime/types#paginated-result first + php: "PaginatedResult":/realtime/types#paginated-result first() + python: "PaginatedResult":/realtime/types#paginated-result first() + csharp: Task> FirstAsync() + java: "PaginatedResult":/realtime/types#paginated-result first() + swift,objc: first(callback: (ARTPaginatedResult?, ARTErrorInfo?) -> Void) + go: First() ("PaginatedResult":/realtime/types#paginated-result, error) + +Returns a new @PaginatedResult@ for the first page of results. When using the Realtime library, the @first@ method returns a "Deferrable":/realtime/types#deferrable and yields a "PaginatedResult":/realtime/types#paginated-result.The method is asynchronous and returns a Task which needs to be awaited to get the "PaginatedResult":/realtime/types#paginated-result. + +h6. + default: hasNext + csharp: HasNext + ruby: has_next? + python: has_next + +bq(definition). + default: Boolean hasNext() + ruby: Boolean has_next? + php: Boolean hasNext() + python: Boolean has_next() + csharp: Boolean HasNext() + java: Boolean hasNext() + swift,objc: Boolean hasNext() + go: HasNext() (bool) + +Returns @true@ if there are more pages available by calling @next@@Next@ and returns @false@ if this page is the last page available. + +h6. + default: isLast + csharp: IsLast + ruby: last? + python: is_last + +bq(definition). + default: Boolean isLast() + ruby: Boolean last? + php: Boolean isLast() + python: Boolean is_last() + csharp: Boolean IsLast() + java: Boolean isLast() + swift,objc: Boolean isLast() + go: IsLast() (bool) + +Returns @true@ if this page is the last page and returns @false@ if there are more pages available by calling @next@@Next@ available. +
+ +h6. + default: next + csharp: Next + +bq(definition). + default: next(callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":/realtime/types#paginated-result resultPage)) + ruby: "PaginatedResult":/realtime/types#paginated-result next + php: "PaginatedResult":/realtime/types#paginated-result next() + python: "PaginatedResult":/realtime/types#paginated-result next() + csharp: Task<"PaginatedResult":/realtime/types#paginated-result> NextAsync() + java: "PaginatedResult":/realtime/types#paginated-result next() + swift,objc: next(callback: (ARTPaginatedResult?, ARTErrorInfo?) -> Void) + go: Next() ("PaginatedResult":/realtime/types#paginated-result, error) + +Returns a new @PaginatedResult@ loaded with the next page of results. If there are no further pages, then @null@a blank PaginatedResult will be returned@Null@@None@@nil@ is returned. The method is asynchronous and return a Task which needs to be awaited to get the @PaginatedResult@When using the Realtime library, the @first@ method returns a "Deferrable":/realtime/types#deferrable and yields a "PaginatedResult":/realtime/types#paginated-result. + +
+ +
+
+ +h4(#paginated-result-example). Example + +```[jsall] +channel.history(function(err, paginatedResult) { + console.log('Page 0 item 0:' + paginatedResult.items[0].data); + paginatedResult.next(function(err, nextPage) { + console.log('Page 1 item 1: ' + nextPage.items[1].data); + console.log('Last page?: ' + nextPage.isLast()); + }); +}); +``` + +```[java,android] +PaginatedResult firstPage = channel.history(); +System.out.println("Page 0 item 0:" + firstPage.items[0].data); +if (firstPage.hasNext) { + PaginatedResult nextPage = firstPage.next(); + System.out.println("Page 1 item 1:" + nextPage.items[1].data); + System.out.println("More pages?:" + Strong.valueOf(nextPage.hasNext())); +}; +``` + +```[csharp] +PaginatedResult firstPage = await channel.HistoryAsync(null); +Message firstMessage = firstPage.Items[0]; +Console.WriteLine("Page 0 item 0: " + firstMessage.data); +if (firstPage.HasNext) +{ + var nextPage = await firstPage.NextAsync(); + Console.WriteLine("Page 1 item 1:" + nextPage.Items[1].data); + Console.WriteLine("More pages?: " + nextPage.HasNext()); +} +``` + +```[ruby] +# When using the REST sync library +first_page = channel.history +puts "Page 0 item 0: #{first_page.items[0].data}" +if first_page.has_next? + next_page = first_page.next + puts "Page 1 item 1: #{next_page.items[1].data}" + puts "Last page?: #{next_page.is_last?}" +end + +# When using the Realtime EventMachine library +channel.history do |first_page| + puts "Page 0 item 0: #{first_page.items[0].data}" + if first_page.has_next? + first_page.next do |next_page| + puts "Page 1 item 1: #{next_page.items[1].data}" + puts "Last page?: #{next_page.is_last?}" + end + end +end +``` + +```[python] +result_page = channel.history() +print 'Page 0 item 0: ' + str(result_page.items[0].data) +if result_page.has_next(): + next_page = result_page.next() + print 'Page 1 item 1: ' + str(next_page.items[1].data) + print 'Last page?: ' + str(next_page.is_last()) +``` + +```[php] +$firstPage = $channel.history(); +echo("Page 0 item 0: " . $firstPage->items[0]->data); +if ($firstPage->hasNext()) { + $nextPage = $firstPage->next(); + echo("Page 1 item 1: " . $nextPage->items[1]->data); + echo("Last page?: " . $nextPage->isLast()); +} +``` + +```[objc] +[channel history:^(ARTPaginatedResult *paginatedResult, ARTErrorInfo *error) { + NSLog(@"Page 0 item 0: %@", paginatedResult.items[0].data); + [paginatedResult next:^(ARTPaginatedResult *nextPage, ARTErrorInfo *error) { + NSLog(@"Page 1 item 1: %@", nextPage.items[1].data); + NSLog(@"Last page?: %d", nextPage.isLast()); + }]; +}]; +``` + +```[swift] +channel.history { paginatedResult, error in + let paginatedResult = paginatedResult! + print("Page 0 item 0: \((paginatedResult.items[0] as! ARTMessage).data)") + paginatedResult.next { nextPage, error in + let nextPage = nextPage! + print("Page 1 item 1: \((nextPage.items[1] as! ARTMessage).data)") + print("Last page? \(nextPage.isLast())") + } +} +``` + +```[go] + page0, err := channel.History(nil) + fmt.Println("Page. 0 item 0: %s\n", page0.Messages[0].Data) + page1, err := page0.Next() + fmt.Println("Page. 1 item 1: %s\n", page1.Messages[1].Data) + fmt.Println("Last page? %s\n", page1.IsLast()) +``` + +
diff --git a/content/partials/versions/v1.1/types/_param.textile b/content/partials/versions/v1.1/types/_param.textile new file mode 100644 index 0000000000..1dced69ff8 --- /dev/null +++ b/content/partials/versions/v1.1/types/_param.textile @@ -0,0 +1,10 @@ +@Param@ is a type encapsulating a key/value pair. This type is used frequently in method parameters allowing key/value pairs to be used more flexible, see "@Channel#history@":/realtime/history#channel-history for an example. + +Please note that @key@ and @value@ attributes are always strings. If an @Integer@ or other value type is expected, then you must coerce that type into a @String@. + +h4. + java: Members + +- key := The key value
__Type: @String@__ +- value := The value associated with the @key@
__Type: @String@__ + diff --git a/content/partials/versions/v1.1/types/_presence_action.textile b/content/partials/versions/v1.1/types/_presence_action.textile new file mode 100644 index 0000000000..39cc53e716 --- /dev/null +++ b/content/partials/versions/v1.1/types/_presence_action.textile @@ -0,0 +1,144 @@ +blang[jsall]. + @Presence@ @action@ is a String with a value matching any of the "@Realtime Presence@ states & events":/realtime/presence#presence-states. + + ```[javascript] + var PresenceActions = [ + 'absent', // (reserved for internal use) + 'present', + 'enter', + 'leave', + 'update' + ] + ``` + +blang[java]. + @io.ably.lib.types.PresenceMessage.Action@ is an enum representing all the "@Realtime Presence@ states & events":/realtime/presence#presence-states. + + ```[java] + public enum Action { + ABSENT, // 0 (reserved for internal use) + PRESENT, // 1 + ENTER, // 2 + LEAVE, // 3 + UPDATE // 4 + } + ``` + +blang[csharp]. + @IO.Ably.PresenceAction@ is an enum representing all the "@Realtime Presence@ states & events":/realtime/presence#presence-states. + + ```[csharp] + public enum Action { + Absent, // 0 (reserved for internal use) + Present, // 1 + Enter, // 2 + Leave, // 3 + Update // 4 + } + ``` + +blang[python]. + @PresenceAction@ is an enum-like class representing all the "@Realtime Presence@ states & events":/realtime/presence#presence-states. + + ```[python] + class PresenceAction(object): + ABSENT = 0 # (reserved for internal use) + PRESENT = 1 + ENTER = 2 + LEAVE = 3 + UPDATE = 4 + ``` + +blang[php]. + @PresenceMessage Action@ is one of the class constants representing all the "@Realtime Presence@ states & events":/realtime/presence#presence-states. + + ```[php] + namespace Ably\Models; + class PresenceMessages { + const ABSENT = 0; /* (reserved for internal use) */ + const PRESENT = 1; + const ENTER = 2; + const LEAVE = 3; + const UPDATE = 4; + } + ``` + + h4. Example usage + + ```[php] + if ($presenceMessage->action == Ably\Models\PresenceMesage::ENTER) { + /* do something */ + } + ``` + +blang[ruby]. + @Ably::Models::PresenceMessage::ACTION@ is an enum-like value representing all the "@Realtime Presence@ states & events":/realtime/presence#presence-states. @ACTION@ can be represented interchangeably as either symbols or constants. + + h4. Symbol states + + ```[ruby] + :absent # => 0 (reserved for internal use) + :present # => 1 + :enter # => 2 + :leave # => 3 + :update # => 4 + ``` + + h4. Constant states + + ```[ruby] + PresenceMessage::ACTION.Absent # => 0 (internal use) + PresenceMessage::ACTION.Present # => 1 + PresenceMessage::ACTION.Enter # => 2 + PresenceMessage::ACTION.Leave # => 3 + PresenceMessage::ACTION.Update # => 4 + ``` + + h4. Example usage + + ```[ruby] + # Example with symbols + presence.on(:attached) { ... } + + # Example with constants + presence.on(Ably::Models::PresenceMessage::ACTION.Enter) { ... } + + # Interchangeable + Ably::Models::PresenceMessage::ACTION.Enter == :enter # => true + ``` + +blang[objc,swift]. + @ARTPresenceAction@ is an enum representing all the "@Realtime Presence@ states & events":/realtime/presence#presence-states. + + ```[objc] + typedef NS_ENUM(NSUInteger, ARTPresenceAction) { + ARTPresenceAbsent, + ARTPresencePresent, + ARTPresenceEnter, + ARTPresenceLeave, + ARTPresenceUpdate + }; + ``` + + ```[swift] + enum ARTPresenceAction : UInt { + case Absent + case Present + case Enter + case Leave + case Update + } + ``` + +blang[go]. + @Presence@ @action@ is a String with a value matching any of the "@Realtime Presence@ states & events":/realtime/presence#presence-states. + + ```[go] + const ( + PresenceAbsent = 0 + PresencePresent = 1 + PresenceEnter = 2 + PresenceLeave = 3 + PresenceUpdate = 4 + ) + ``` \ No newline at end of file diff --git a/content/partials/versions/v1.1/types/_presence_listener.textile b/content/partials/versions/v1.1/types/_presence_listener.textile new file mode 100644 index 0000000000..192152f25a --- /dev/null +++ b/content/partials/versions/v1.1/types/_presence_listener.textile @@ -0,0 +1,8 @@ +A @io.ably.lib.realtime.Presence.PresenceListener@ is an interface allowing a client to be notified when presence message events are received on a presence channel using a "presence subscription":/realtime/presence#presence-states. + +```[java] + public interface PresenceListener { + // Called when one or more presence messages are received + public void onPresenceMessage(PresenceMessage message); + } +``` diff --git a/content/partials/versions/v1.1/types/_presence_message.textile b/content/partials/versions/v1.1/types/_presence_message.textile new file mode 100644 index 0000000000..c2ca26b5d9 --- /dev/null +++ b/content/partials/versions/v1.1/types/_presence_message.textile @@ -0,0 +1,66 @@ +A @PresenceMessage@ represents an individual presence update that is sent to or received from Ably. + +h4. + default: Properties + java: Members + ruby: Attributes + +-
action
:= the event signified by a PresenceMessage. See "@PresenceMessage.action@":/realtime/types#presence-action
__Type: @enum { ABSENT, PRESENT, ENTER, LEAVE, UPDATE }@__ +-
Action
:= the event signified by a PresenceMessage. See "@PresenceMessage.action@":/realtime/types#presence-action
__Type: @enum { Absent, Present, Enter, Leave, Update }@__ +-
action
:= the event signified by a PresenceMessage. See "@Presence action@":/realtime/types#presence-action
__Type: @int enum { ABSENT, PRESENT, ENTER, LEAVE, UPDATE }@__ +-
action
:= the event signified by a PresenceMessage. See "@PresenceAction@":/realtime/types#presence-action
__Type: @int enum { ABSENT, PRESENT, ENTER, LEAVE, UPDATE }@__ +-
action
:= the event signified by a PresenceMessage. See "@PresenceMessage::ACTION@":/realtime/types#presence-action
__Type: @enum { :absent, :present, :enter, :leave, :update }@__ +-
action
:= the event signified by a PresenceMessage. See "@PresenceMessage::ACTION@":/realtime/types#presence-action
__Type: @const PresenceMessage::ABSENT,PRESENT,ENTER,LEAVE,UPDATE@__ +-
action
:= the event signified by a PresenceMessage. See "@PresenceMessage.action@":/realtime/types#presence-action
__Type: @ARTPresenceAction@__ +-
Action
:= the event signified by a PresenceMessage. See "@PresenceMessage::action@":/realtime/types#presence-action
__Type: @const PresenceMessage::PresenceAbsent,PresencePresent,PresenceEnter,PresenceLeave,PresenceUpdate@__ + +- dataData := The presence update payload, if provided
__@String@, @ByteArray@, @JSONObject@, @JSONArray@@String@, @byte[]@, plain C# object that can be converted to Json@String@, @StringBuffer@, @JSON Object@@String@, @[]byte@@String@, @Binary@ (ASCII-8BIT String), @Hash@, @Array@@String@, @Bytearray@, @Dict@, @List@@String@, @NSData@, @Dictionary@, @Array@@NSString *@, @NSData *@, @NSDictionary *@, @NSArray *@@String@, @Binary String@, @Associative Array@, @Array@__ + +- extrasExtras := Metadata and/or ancillary payloads, if provided. The only currently valid payload for extras is the "@push@":/general/push/publish#channel-broadcast-example object.
__Type: @JSONObject@, @JSONArray@plain C# object that can be converted to Json@String@, @[]byte@@JSON Object@@Hash@, @Array@@Dict@, @List@@Dictionary@, @Array@@NSDictionary *@, @NSArray *@@Associative Array@, @Array@__ + +- idId := Unique ID assigned by Ably to this presence update
__Type: @String@__ + +- clientIdclient_idClientId := The client ID of the publisher of this presence update
__Type: @String@__ + +- connectionIdconnection_idConnectionId := The connection ID of the publisher of this presence update
__Type: @String@__ + +- timestampTimestamp := Timestamp when the presence update was received by Ably, as milliseconds since the epoch.
__Type: @Integer@@Long Integer@@DateTimeOffset@@Time@@NSDate@__ + +- encodingEncoding := This will typically be empty as all presence updates received from Ably are automatically decoded client-side using this value. However, if the message encoding cannot be processed, this attribute will contain the remaining transformations not applied to the @data@ payload
__Type: @String@__ + +h3. + default: PresenceMessage constructors + +h4(#presence-from-encoded). + default: PresenceMessage.fromEncoded + +bq(definition). + default: PresenceMessage.fromEncoded(Object encodedPresMsg, ChannelOptions channelOptions?) -> PresenceMessage + +A static factory method to create a "@PresenceMessage@":/realtime/types#presence-message from a deserialized @PresenceMessage@-like object encoded using Ably's wire protocol. + +h4. Parameters + +- encodedPresMsg := a @PresenceMessage@-like deserialized object.
__Type: @Object@__ +- channelOptions := an optional "@ChannelOptions@":/realtime/types#channel-options. If you have an encrypted channel, use this to allow the library can decrypt the data.
__Type: @Object@__ + +h4. Returns + +A "@PresenceMessage@":/realtime/types#presence-message object + +h4(#presence-from-encoded-array). + default: PresenceMessage.fromEncodedArray + +bq(definition). + default: PresenceMessage.fromEncodedArray(Object[] encodedPresMsgs, ChannelOptions channelOptions?) -> PresenceMessage[] + +A static factory method to create an array of "@PresenceMessages@":/realtime/types#presence-message from an array of deserialized @PresenceMessage@-like object encoded using Ably's wire protocol. + +h4. Parameters + +- encodedPresMsgs := an array of @PresenceMessage@-like deserialized objects.
__Type: @Array@__ +- channelOptions := an optional "@ChannelOptions@":/realtime/types#channel-options. If you have an encrypted channel, use this to allow the library can decrypt the data.
__Type: @Object@__ + +h4. Returns + +An @Array@ of "@PresenceMessage@":/realtime/types#presence-message objects diff --git a/content/partials/versions/v1.1/types/_push_admin.textile b/content/partials/versions/v1.1/types/_push_admin.textile new file mode 100644 index 0000000000..eca2075bbc --- /dev/null +++ b/content/partials/versions/v1.1/types/_push_admin.textile @@ -0,0 +1,469 @@ +h2(#push-admin). Push Admin object + +This object is accessible through @client.push.admin@ and provides: + +h3. Methods + +h6(#publish). + default: publish + +bq(definition). + default: publish(Object recipient, Object data, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable publish(Hash recipient, Hash data) -> yield + python: publish(recipient=Object, data=Object) + php: publish(Array recipient, Array data) + swift,objc: publish(recipient: ARTPushRecipient, data: AnyObject?, callback: (("ARTErrorInfo":/realtime/types#error-info?) -> Void)?) + java,android: void publish(String recipient, Object data, "CompletionListener":#completion-listener listener) + +Publishes a push notification directly to a device or group of devices sharing a "client identifier":/core-features/authentication#identified-clients. See the "push notification direct publishing documentation":/general/push/publish#direct-publishing for more information. + +h4. Parameters + +- recipient := an objectan arraya Hash containing the push recipient details. See the "push notification publish REST API documentation":/rest-api#push-publish for details on the supported recipient fields +- data := an objectan arraya Hash containing the push notification data. See the "push admin payload structure":/general/push/publish#payload-structure for details on the supported push payload fields + +-
callback
:= a function of the form: @function(err)@ +-
&block
:= yielded upon success +-
listener
:= Listener to be notified on completion
__Type: "@CompletionListener@":#completion-listener__ +-
callback
:= called upon publishing the message, or with an error + +blang[jsall,swift,objc]. + h4. Callback result + + The callback is called upon success or failure to publish the push notification. When this operation fails, @err@ contains an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On success, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method are yielded to. + + Failure to publish the push notification will trigger the @errback@ callback of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +blang[java]. + h4. Listener result + + On successful publish of the message, the @onSuccess@ method of the "CompletionListener":#completion-listener is called. On failure to publish the message, the @onError@ method is called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +h2(#properties). + default: Push Admin Properties + ruby: Push::Admin Properties + +The push admin object exposes the following public propertiesattributesmembers: + +h6(#device-registrations). + default: deviceRegistrations + ruby,python: device_registrations + +The returned "@DeviceRegistrations@":#device-registrations-object object provides functionality for registering, updating, listing and de-registering push devices. + +h6(#channel-subscriptions). + default: channelSubscriptions + ruby,python: channel_subscriptions + +The returned "@PushChannelSubscriptions@":#push-channel-subscriptions object provides functionality for subscribing, listing and unsubscribing individual devices or groups of "identified devices":/core-features/authentication#identified-clients to push notifications published on channels. + +h2(#device-registrations-object). DeviceRegistrations object + +This object is accessible through @client.push.admin.deviceRegistrations@@client.push.admin.device_registrations@ and provides an API to register new push notification devices, update existing devices, deregister old devices, and retrieve or list devices registered to an app. + +h3. Methods + +h6(#device-get). + default: get + +bq(definition#device-get-id). + default: get(String deviceId, callback("ErrorInfo":/realtime/types#error-info err, "DeviceDetails":#device-details device)) + ruby: "Deferrable":/realtime/types#deferrable get(String deviceId) -> yields "DeviceDetails":#device-details + python: "DeviceDetails":#device-details get(device_id=String) + php: "DeviceDetails":#device-details get(String deviceId) + swift,objc: get(deviceId: ArtDeviceId, callback: ((ARTDeviceDetails?, ARTErrorInfo?) -> Void) + java,android: "DeviceDetails":#device-details get(String deviceId) + +
+ +bq(definition#device-get-id-async). + java,android: getAsync(String deviceId, Callback<"DeviceDetails"/rest/push/#device-details> callback) + +
+ +bq(definition#device-get-device). + default: get("DeviceDetails":#device-details device, callback("ErrorInfo":/realtime/types#error-info err, "DeviceDetails":#device-details device)) + ruby: "Deferrable":/realtime/types#deferrable get("DeviceDetails":#device-details device) -> yields "DeviceDetails":#device-details + +Obtain the @DeviceDetails@ for a device registered for receiving push registrations matching the @deviceId@ argument, or the @id@ attribute of the provided @DeviceDetails@ object. Requires @push-admin@ permission or @push-subscribe@ permission together with device authentication matching the requested @deviceId@. + +h4. Parameters + +- deviceId := the unique device ID String for the requested device +- device := a "@DeviceDetails@":#device-details object containing at a minimum the @deviceId@ of the requested device + +-
callback
:= is a function of the form: @function(err, device)@ +-
&block
:= yields a @DeviceDetails@ object upon success + +blang[jsall,swift,objc]. + h4. Callback result + + On success, @device@ contains the device registered for push notifications as a "@DeviceDetails@":#device-details object. + + On failure to retrieve the device, @err@ contains an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On success, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method yield the device registered for push notifications as a "@DeviceDetails@":#device-details object. + + Failure to retrieve the device will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +blang[java]. + h4. Listener result + + On successful publish of the message, the @onSuccess@ method of the "CompletionListener":#completion-listener is called. On failure to get the device, the @onError@ method is called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +h6(#device-list). + default: list + +bq(definition). + default: list(Object params, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":#paginated-result<"DeviceDetails":#device-details device> resultPage)) + ruby: "Deferrable":/realtime/types#deferrable list(Hash params) -> yields "PaginatedResult":#paginated-result<"DeviceDetails":#device-details> + python: "PaginatedResult":#paginated-result list(params=Object) + php: "PaginatedResult":#paginated-result list_(Array params) + swift,objc: list(params: NSDictionary *, callback: (("ARTPaginatedResult":#paginated-result?, ARTErrorInfo?) -> Void) + java,android: "PaginatedResult":#paginated-result list(Param[] params) + +Retrieve all devices matching the params filter as a paginated list of "@DeviceDetails@":#device-details objects. Requires @push-admin@ permission. + +h4. Parameters + +- params"Param":#param[] params := an object containing the query parameters as key value pairs as specified below. + +-
callback
:= is a function of the form: @function(err, resultPage)@ +-
&block
:= yields a @PaginatedResult@ object +error + +h4. @params@ properties + +- clientId:client_id := optional filter to restrict to devices associated with that client identifier
__Type: @String@__ +- deviceId:device_id := optional filter to restrict to devices associated with that device identifier
__Type: @String@__ +- limit:limit := _100_ maximum number of devices per page to retrieve, up to 1,000
__Type: @Integer@__ + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @resultPage@ contains a "@PaginatedResult@":/realtime/types#paginated-result encapsulating an array of "@DeviceDetails@":#device-details objects corresponding to the current page of results. "@PaginatedResult@":/realtime/types#paginated-result supports pagination using "@next()@":/realtime/types#paginated-result and "@first()@":/realtime/types#paginated-result methods. + + On failure to retrieve the devices, @err@ contains an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On success, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method yield a "PaginatedResult":/realtime/types#paginated-result that encapsulates an array of "@DeviceDetails@":#device-details corresponding to the current page of results. "@PaginatedResult@":/realtime/types#paginated-result supports pagination using "@next()@":/realtime/types#paginated-result and "@first()@":/realtime/types#paginated-result methods. + + Failure to retrieve the devices will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +h6(#device-save). + default: save + +bq(definition). + default: save("DeviceDetails":#device-details device, callback("ErrorInfo":/realtime/types#error-info err, "DeviceDetails":#device-details device)) + ruby: "Deferrable":/realtime/types#deferrable save("DeviceDetails":#device-details device) -> yields "DeviceDetails":#device-details + python: "DeviceDetails":#device-details save("DeviceDetails":#device-details device_details) + php: "DeviceDetails":#device-details save("DeviceDetails":#device-details deviceDetails) + swift,objc: save(deviceDetails: "DeviceDetails":#device-details, callback: (("DeviceDetails":#device-details?, ARTErrorInfo?) -> Void) + java,android: "DeviceDetails":#device-details save("DeviceDetails":#device-details deviceDetails) + +Register a new @DeviceDetails@ object, or update an existing @DeviceDetails@ object with the Ably service. Requires @push-admin@ permission or @push-subscribe@ permission together with device authentication matching the requested @deviceId@. + +h4. Parameters + +- device := a "@DeviceDetails@":#device-details object + +-
callback
:= is a function of the form: @function(err, device)@ +-
&block
:= yields the new @DeviceDetails@ object upon success + +blang[jsall,swift,objc]. + h4. Callback result + + On success, @device@ contains the newly registered or updated device as a "@DeviceDetails@":#device-details object. + + On failure to create or update the device, @err@ contains an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On success, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method yield the newly registered or updated device as a "@DeviceDetails@":#device-details object. + + Failure to create or update the device will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +h6(#device-remove). + default: remove + +bq(definition#device-remove-id). + default: remove(String deviceId, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable remove(String deviceId) + python: remove(String device_id) + php: remove(String deviceId) + swift,objc: remove(deviceDetails: "DeviceDetails":#device-details, callback: (("DeviceDetails":#device-details?, ARTErrorInfo?) -> Void) + java,android: "DeviceDetails":#device-details save("DeviceDetails":#device-details deviceDetails) + +bq(definition#device-remove-device). + default: remove("DeviceDetails":#device-details device, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable remove("DeviceDetails":#device-details device) -> yield + python: remove("DeviceDetails":#device-details device_details) + php: remove("DeviceDetails":#device-details deviceDetails) + swift,objc: remove(deviceDetails: "DeviceDetails":#device-details, callback: ((ARTErrorInfo?) -> Void) + java,android: "DeviceDetails":#device-details save("DeviceDetails":#device-details deviceDetails) + +Remove a device registered for receiving push registrations that matches the @deviceId@ argument, or the @id@ attribute of the provided "@DeviceDetails@":#device-details object. Requires @push-admin@ permission or @push-subscribe@ permission together with device authentication matching the requested @deviceId@. + +h4. Parameters + +- deviceId := the unique device ID String for the device +- device := a "@DeviceDetails@":#device-details object containing at a minimum the @deviceId@ of the device + +-
callback
:= is a function of the form: @function(err)@ +-
&block
:= yields upon success + +blang[jsall,swift,objc]. + h4. Callback result + + The callback is called upon success or failure to delete the device. Note that a request to delete a device that does not exist will result in a successful operation. + + When this operation fails, @err@ contains an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On success, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method are yielded to. Note that a request to delete a device that does not exist will result in a successful operation. + + Failure to delete the device will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +h6(#device-remove-where). + default: removeWhere + ruby,python: remove_where + +bq(definition). + default: removeWhere(Object params, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable remove_where(Hash params) -> yield + python: remove_where(params=Object) + php: removeWhere(Array params) + swift,objc: removeWhere(params: NSDictionary *, callback: (ARTErrorInfo?) -> Void) + java,android: removeWhere(Param[] params) + +Delete all devices matching the params filter. Requires @push-admin@ permission. + +h4. Parameters + +- params"Param":#param[] params := an object containing the filter parameters as key value pairs as specified below. + +-
callback
:= is a function of the form: @function(err)@ +-
&block
:= yields upon success +error + +h4. @params@ properties + +- clientId:client_id := optional filter to restrict to devices associated with that client identifier
__Type: @String@__ +- deviceId:device_id := optional filter to restrict to devices associated with that device identifier
__Type: @String@__ + +blang[jsall,swift,objc]. + h4. Callback result + + The callback is called upon success or failure to delete the device. Note that a request that does match any existing devices will result in a successful operation. + + When this operation fails, @err@ contains an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On success, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method are yielded to. Note that a request that does match any existing devices will result in a successful operation. + + Failure to delete the device will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +h2(#push-channel-subscriptions). PushChannelSubscriptions object + +This object is accessible through @client.push.admin.channelSubscriptions@@client.push.admin.channel_subscriptions@ and provides an API to subscribe a push notification device to a channel ensuring it receives any push notifications published in the future on that channel. Additionally, this object allows these subscriptions to be retrieved, listed, updated or removed. + +h3. Methods + +h6(#push-channel-sub-list). + default: list + +bq(definition). + default: list(Object params, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":/realtime/types#paginated-result<"PushChannelSubscription":#push-channel-subscription> resultPage)) + ruby: "Deferrable":/realtime/types#deferrable list(Hash params) -> yields "PaginatedResult":/realtime/types#paginated-result<"PushChannelSubscription":#push-channel-subscription> + python: "PaginatedResult":#paginated-result list(params=Object) + php: "PaginatedResult":#paginated-result list_(Array params) + swift,objc: list(params: NSDictionary *, callback: (("ARTPaginatedResult":#paginated-result?, ARTErrorInfo?) -> Void) + java,android: "PaginatedResult":#paginated-result list(Param[] params) + +Retrieve all push channel subscriptions that match the provided params filter as a paginated list of "@PushChannelSubscription@":#push-channel-subscription objects. Each "@PushChannelSubscription@":#push-channel-subscription represents a device or set of devices sharing the same "client identifier":/core-features/authentication#identified-clients registered to a channel to receive push notifications. + +h4. Parameters + +- params"Param":#param[] params := an object containing the query parameters as key value pairs as specified below. + +-
callback
:= is a function of the form: @function(err, resultPage)@ +-
&block
:= yields a @PaginatedResult@ object +error + +h4. @params@ properties + +- channel:channel := filter to restrict to subscriptions associated with that @channel@ +- clientId:client_id := optional filter to restrict to devices associated with that client identifier. Cannot be used with a @clientId@@:client_id@ param
__Type: @String@__ +- deviceId:device_id := optional filter to restrict to devices associated with that device identifier. Cannot be used with a @deviceId@@:device_id@ param
__Type: @String@__ +- limit:limit := _100_ maximum number of channel subscriptions per page to retrieve, up to 1,000
__Type: @Integer@__ + +blang[jsall,swift,objc]. + h4. Callback result + + On success, @resultPage@ contains a "@PaginatedResult@":/realtime/types#paginated-result encapsulating an array of "@PushChannelSubscription@":#push-channel-subscription objects corresponding to the current page of results. "@PaginatedResult@":/realtime/types#paginated-result supports pagination using "@next()@":/realtime/types#paginated-result and "@first()@":/realtime/types#paginated-result methods. + + On failure to retrieve the channel subscriptions, @err@ contains an "@ErrorInfo@":/realtime/types#error-info object which contains the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On success, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method yield a "PaginatedResult":/realtime/types#paginated-result that encapsulates an array of "@PushChannelSubscription@":#push-channel-subscription corresponding to the current page of results. "@PaginatedResult@":/realtime/types#paginated-result supports pagination using "@next()@":/realtime/types#paginated-result and "@first()@":/realtime/types#paginated-result methods. + + Failure to retrieve the channel subscriptions will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +h6(#push-channel-sub-list-channels). + default: listChannels + ruby,python: list_channels + +bq(definition). + default: listChannels(Object params, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":/realtime/types#paginated-result resultPage)) + ruby: "Deferrable":/realtime/types#deferrable list_channels(Hash params) -> yields "PaginatedResult":/realtime/types#paginated-result + python: "PaginatedResult":#paginated-result list_channels(params=Object) + php: "PaginatedResult":#paginated-result listChannels(Array params) + swift,objc: listChannels(params: NSDictionary *, callback: (("ARTPaginatedResult":#paginated-result?, ARTErrorInfo?) -> Void) + java,android: "PaginatedResult":#paginated-result listChannels(Param[] params) + +Retrieve a list of channels with at least one subscribed device as a paginated list of channel name @String@ objects. Requires @push-admin@ permission. + +h4. Parameters + +- params"Param":#param[] params := an object containing the query parameters as key value pairs as specified below. + +-
callback
:= is a function of the form: @function(err, resultPage)@ +-
&block
:= yields a @PaginatedResult@ object +error + +h4. @params@ properties + +- limit:limit := _100_ maximum number of channels per page to retrieve, up to 1,000
__Type: @Integer@__ + +blang[jsall,swift,objc]. + h4. Callback result + + On success, @resultPage@ contains a "@PaginatedResult@":/realtime/types#paginated-result encapsulating an array of channel name @String@ values corresponding to the current page of results. "@PaginatedResult@":/realtime/types#paginated-result supports pagination using "@next()@":/realtime/types#paginated-result and "@first()@":/realtime/types#paginated-result methods. + + On failure to retrieve the channels, @err@ contains an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On success, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method yield a "PaginatedResult":/realtime/types#paginated-result that encapsulates an array of channel name @String@ values corresponding to the current page of results. "@PaginatedResult@":/realtime/types#paginated-result supports pagination using "@next()@":/realtime/types#paginated-result and "@first()@":/realtime/types#paginated-result methods. + + Failure to retrieve the channels will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +h6(#push-channel-sub-save). + default: save + +bq(definition). + default: save("PushChannelSubscription":#push-channel-subscription channelSubscription, callback("ErrorInfo":/realtime/types#error-info err, "PushChannelSubscription":#push-channel-subscription channelSubscription)) + ruby: save("PushChannelSubscription":#push-channel-subscription channel_subscription) + python: "PushChannelSubscription":#push-channel-subscription save("PushChannelSubscription":#push-channel-subscription channel_subscription) + php: "PushChannelSubscription":#push-channel-subscription save("PushChannelSubscription":#push-channel-subscription channelSubscription) + swift,objc: save(channelSubscription: "PushChannelSubscription":#push-channel-subscription, callback: (("PushChannelSubscription":#push-channel-subscription?, ARTErrorInfo?) -> Void) + java,android: "PushChannelSubscription":#push-channel-subscription save("PushChannelSubscription":#push-channel-subscription channelSubscription) + +Subscribe a device or group of devices sharing a "client identifier":/core-features/authentication#identified-clients for push notifications published on a channel. + +h4. Parameters + +- channelSubscriptionchannel_subscription := a "@PushChannelSubscription@":#push-channel-subscription object + +-
callback
:= is a function of the form: @function(err, channelSubscription)@ + +blang[jsall,swift,objc]. + h4. Callback result + + On success, @channelSubscription@ contains the newly subscribed or updated push channel subscription as a "@PushChannelSubscription@":#push-channel-subscription object. + + On failure to create or update the channel subscription, @err@ contains an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +h6(#push-channel-sub-remove). + default: remove + +bq(definition). + default: remove("PushChannelSubscription":#push-channel-subscription channelSubscription, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: remove("PushChannelSubscription":#push-channel-subscription channel_subscription) + python: remove("PushChannelSubscription":#push-channel-subscription channel_subscription) + php: remove("PushChannelSubscription":#push-channel-subscription subscription) + swift,objc: remove(channelSubscription: "PushChannelSubscription":#push-channel-subscription, callback: ((ARTErrorInfo?) -> Void) + java,android: void save("PushChannelSubscription":#push-channel-subscription channelSubscription) + +Unsubscribe a device or group of devices sharing a "client identifier":/core-features/authentication#identified-clients from push notifications on a channel. Requires @push-admin@ permission or, in the case of a subscription associated with a given @deviceId@, @push-subscribe@ permission together with device authentication matching that @deviceId@. + +h4. Parameters + +- channelSubscriptionchannel_subscription := a "@PushChannelSubscription@":#push-channel-subscription object + +-
callback
:= is a function of the form: @function(err)@ + +blang[jsall,swift,objc]. + h4. Callback result + + The callback is called upon success or failure to unsubscribe. Note that a request to unsubscribe or remove a subscription that does not exist will result in a successful operation. + + When this operation fails, @err@ contains an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. + +h6(#push-channel-sub-remove-where). + default: removeWhere + ruby,python: remove_where + +bq(definition). + default: removeWhere(Object params, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: remove_where(Hash params) + python: remove_where(params=Object) + php: removeWhere(Array params) + swift,objc: removeWhere(params: NSDictionary *, callback: (ARTErrorInfo?) -> Void) + java,android: removeWhere(Param[] params) + +Delete all push channel subscriptions matching the @params@ filter. Requires @push-admin@ permission. + +h4. Parameters + +- params"Param":#param[] params := an object containing the filter parameters as key value pairs as specified below. + +-
callback
:= is a function of the form: @function(err)@ +error + +h4. @params@ properties + +- channel:channel := filter to restrict to subscriptions associated with that @channel@ +- clientId:client_id := optional filter to restrict to devices associated with that client identifier. Cannot be used with @clientId@@:client_id@ param
__Type: @String@__ +- deviceId:device_id := optional filter to restrict to devices associated with that device identifier. Cannot be used with @deviceId@@:device_id@ param
__Type: @String@__ + +blang[jsall,swift,objc]. + h4. Callback result + + The callback is called upon success or failure to unsubscribe. Note that a request to unsubscribe or remove a subscription that does not exist will result in a successful operation. + + When this operation fails, @err@ contains an "@ErrorInfo@":/realtime/types#error-info object with the failure reason. diff --git a/content/partials/versions/v1.1/types/_push_channel.textile b/content/partials/versions/v1.1/types/_push_channel.textile new file mode 100644 index 0000000000..c56e75714f --- /dev/null +++ b/content/partials/versions/v1.1/types/_push_channel.textile @@ -0,0 +1,71 @@ +
+A @PushChannel@ is a property of a "@RealtimeChannel@":/realtime/channels#properties or "@RestChannel@":/rest/channels#properties. It provides "push devices":/general/push#platform-support the ability to subscribe and unsubscribe to push notifications on channels. + +h4. + Methods + +h6(#subscribe-device). + default: subscribeDevice + +bq(definition). + default: subscribeDevice() + +Subscribe your device to the channel's push notifications. + +h6(#subscribe-client). + default: subscribeClient + +bq(definition). + default: subscribeClient() + +"Subscribe all devices associated with your device's clientId":/general/push/activate-subscribe#subscribing-client-id to the channel's push notifications. + +h6(#unsubscribe-device). + default: unsubscribeDevice + +bq(definition). + default: unsubscribeDevice() + +Unsubscribe your device from the channel's push notifications. + +h6(#unsubscribe-client). + default: unsubscribeClient + +bq(definition). + default: unsubscribeClient() + +"Unsubscribe all devices associated with your device's clientId":/general/push/activate-subscribe#subscribing-client-id from the channel's push notifications. + +h6(#list-subscriptions). + default: listSubscriptions + +bq(definition). + java,android: "PaginatedResult":#paginated-result<"PushChannelSubscription":#push-channel-subscription> listSubscriptions(String deviceId, String clientId, String deviceClientId, String channel) + objc,swift: listSubscriptions(deviceId: String?, clientId: String?, deviceClientId: String?, channel: String?, callback: ("ARTPaginatedResult":#paginated-result<"PushChannelSubscription":#push-channel-subscription>?, ARTErrorInfo?) -> Void) + +"Unsubscribe all devices associated with your device's clientId":/general/push/activate-subscribe#subscribing-client-id from the channel's push notifications. + +h4. Parameters + +-
deviceId
:= a deviceId to filter by
__Type: @String@__ + +-
clientId
:= a clientId to filter by
__Type: @String@__ + +-
deviceClientId
:= a client ID associated with a device to filter by
__Type: @String@__ + +-
callback
:= called with a "ARTPaginatedResult":#paginated-result<"PushChannelSubscription":/general/push/admin#push-channel-subscription> object or an error + +blang[objc,swift]. + h4. Callback result + + On success, @resultPage@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "PushChannelSubscription":/general/push/admin#push-channel-subscription objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + On failure to retrieve message history, @err@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +blang[java,android]. + h4. Returns + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "PushChannelSubscription":#push-channel-subscription objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + Failure to retrieve the message history will raise an "@AblyException@":/realtime/types/#ably-exception +
diff --git a/content/partials/versions/v1.1/types/_push_channel_subscription.textile b/content/partials/versions/v1.1/types/_push_channel_subscription.textile new file mode 100644 index 0000000000..c112dbae7a --- /dev/null +++ b/content/partials/versions/v1.1/types/_push_channel_subscription.textile @@ -0,0 +1,53 @@ +An @PushChannelSubscription@ is a type encapsulating the subscription of a device or group of devices sharing a "client identifier":/core-features/authentication#identified-clients to a channel in order to receive push notifications. + +h4. + default: Properties + java: Members + ruby: Attributes + +- channel := the channel that this push notification subscription is associated with
__Type: @String@__ + +- deviceIddevice_id := the device with this identifier is linked to this channel subscription. When present, @clientId@@client_id@ is never present
__Type: @String@__ + +- clientIdclient_id := devices with this "client identifier":/core-features/authentication#identified-clients are included in this channel subscription. When present, @deviceId@@device_id@ is never present
__Type: @String@__ + +h3. + default: PushChannelSubscription constructors + +h4(#push-channel-subscription-for-device). + default: PushChannelSubscription.forDevice + ruby: PushChannelSubscription.for_device + +bq(definition). + default: PushChannelSubscription.forDevice(String channel, String deviceId) -> PushChannelSubscription + ruby: PushChannelSubscription.for_device(String channel, String device_id) -> PushChannelSubscription + +A static factory method to create a @PushChannelSubscription@ object for a channel and single device. + +h4. Parameters + +- channel := channel name linked to this push channel subscription
__Type: @String@__ +- deviceIddevice_id := the device with this identifier will be linked with this push channel subscription
__Type: @String@__ + +h4. Returns + +A "@PushChannelSubscription@":/realtime/types#push-channel-subscription object + +h4(#push-channel-subscription-for-client-id). + default: PushChannelSubscription.forClient + ruby: PushChannelSubscription.for_client + +bq(definition). + default: PushChannelSubscription.forDevice(String channel, String clientId) -> PushChannelSubscription + ruby: PushChannelSubscription.for_device(String channel, String client_id) -> PushChannelSubscription + +A static factory method to create a @PushChannelSubscription@ object for a channel and group of devices sharing a "client identifier":/core-features/authentication#identified-clients. + +h4. Parameters + +- channel := channel name linked to this push channel subscription
__Type: @String@__ +- clientIdclient_id := devices with this "client identifier":/core-features/authentication#identified-clients are included in the new push channel subscription
__Type: @String@__ + +h4. Returns + +A @PushChannelSubscription@ object diff --git a/content/partials/versions/v1.1/types/_push_device.textile b/content/partials/versions/v1.1/types/_push_device.textile new file mode 100644 index 0000000000..35bfe5efaa --- /dev/null +++ b/content/partials/versions/v1.1/types/_push_device.textile @@ -0,0 +1,23 @@ +h2(#push-object). Push Device object + +This object is accessible through @client.push@ and provides to "push-compatible devices":/general/push#platform-support: + +h3. Methods + +h6(#activate). + default: activate + +bq(definition). + android: void activate() + objc,swift: activate(callback: ("ARTErrorInfo":/realtime/types#error-info?, DeviceDetails?) -> Void) + +Register the device for push. When the "activation process":#device-activation is completed, Ably will send a broadcast through the application's "@LocalBroadcastManager@":https://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager. Success or failure will be broadcast through @io.ably.broadcast.PUSH_ACTIVATE@call the @(void)didActivateAblyPush:(nullable ARTErrorInfo *)error@@didActivateAblyPush(error: ARTErrorInfo?)@ method from the @ARTPushRegistererDelegate@. + +h6(#deactivate). + default: deactivate + +bq(definition). + android: void deactivate() + objc,swift: deactivate(deregisterCallback: ("ARTErrorInfo":/realtime/types#error-info?, deviceId: String?) -> Void) + +Deregister the device for push. When the deactivation process is completed, Ably will send a broadcast through the application's "@LocalBroadcastManager@":https://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager. Success or failure will be broadcast through @io.ably.broadcast.PUSH_DEACTIVATE@call the @(void)didDeactivateAblyPush:(nullable ARTErrorInfo *)error@@didDeactivateAblyPush(error: ARTErrorInfo?)@ method from the@ARTPushRegistererDelegate@. diff --git a/content/partials/versions/v1.1/types/_realtime_client_options.textile b/content/partials/versions/v1.1/types/_realtime_client_options.textile new file mode 100644 index 0000000000..52fa13a1d6 --- /dev/null +++ b/content/partials/versions/v1.1/types/_realtime_client_options.textile @@ -0,0 +1,19 @@ +- queueMessagesQueueMessages:queue_messages := _true_ If false, this disables the default behavior whereby the library queues messages on a connection in the disconnected or connecting states. The default behavior allows applications to submit messages immediately upon instancing the library without having to wait for the connection to be established. Applications may use this option to disable queueing if they wish to have application-level control over the queueing under those conditions
__Type: @Boolean@__ + +- echoMessagesEchoMessages:echo_messages := _true_ If false, prevents messages originating from this connection being echoed back on the same connection
__Type: @Boolean@__ + +- autoConnectAutoConnect:auto_connect := _true_ By default as soon as the client library is instanced it will connect to Ably. You can optionally set this to false and explicitly connect to Ably when require using the "@connect@":/realtime/usage/#connect method
__Type: @Boolean@__ + +-
recover
:= This option allows a connection to inherit the state of a previous connection that may have existed under a different instance of the Realtime library. This might typically be used by clients of the browser library to ensure connection state can be preserved when the user refreshes the page. A recovery key string can be explicitly provided, or alternatively if a callback function is provided, the client library will automatically persist the recovery key between page reloads and call the callback when the connection is recoverable. The callback is then responsible for confirming whether the connection should be recovered or not. See "connection state recovery":/realtime/connection/#connection-state-recovery for further information
__Type: @String@, @Callable@__ + +-
closeOnUnload
:= _true_ When true, the client library will automatically send a close request to Ably whenever the @window beforeunload@ event fires. By enabling this option, the close request sent to Ably ensures the connection state will not be retained and all channels associated with the channel will be detached. This is commonly used by developers who want presence leave events to fire immediately i.e. if a user navigates to another page or closes their browser, then enabling this option will result in the presence member leaving immediately. Without this option or an explicit call to the "@close@":/realtime/connection#close method of the "@Connection object@":/realtime/connection, Ably expects that the abruptly disconnected connection could later be recovered and therefore does not immediately remove the user from presence. Instead, to avoid "twitchy" presence behavior an abruptly disconnected client is removed from channels in which they are present after 15 seconds, and the connection state is retained for two minutes
__Type: @Boolean@__ + +-
recoverRecover:recover
:= This option allows a connection to inherit the state of a previous connection that may have existed under a different instance of the library by providing that connection's "@recoveryKey@":/realtime/connection#recovery-key"@recovery_key@":/realtime/connection#recovery-key. This might typically be used by clients of an app to ensure connection state can be preserved following a reload. See "connection state recovery":/realtime/connection/#connection-state-recovery for further information and example code
__Type: @String@__ + +- queryTimeQueryTime:query_time := _false_ If true, the library will query the Ably servers for the current time when "issuing TokenRequests":/realtime/authentication#token-authentication instead of relying on a locally-available time of day. Knowing the time accurately is needed to create valid signed Ably "TokenRequests":/realtime/authentication#token-authentication, so this option is useful for library instances on auth servers where for some reason the server clock cannot be kept synchronized through normal means, such as an "NTP daemon":https://en.wikipedia.org/wiki/Ntpd . The server is queried for the current time once per client library instance (which stores the offset from the local clock), so if using this option you should avoid instancing a new version of the library for each request.
__Type: @Boolean@__ + +- defaultTokenParamsDefaultTokenParams:default_token_params := When a "TokenParams":/realtime/types#token-params object is provided, it will override the client library defaults when issuing new Ably Tokens or Ably TokenRequests
__Type: "@TokenParams@":/realtime/types#token-params__ + +- disconnectedRetryTimeoutDisconnectedRetryTimeout:disconnected_retry_timeout := _15,000ms15s_ When the connection enters the @DISCONNECTED@ state, after this delay in millisecondsin secondsas a @NSTimeInterval@, if the state is still @DISCONNECTED@, the client library will attempt to reconnect automatically
__Type: @Integer@@NSTimeInterval@__ + +- suspendedRetryTimeoutSuspendedRetryTimeout:suspended_retry_timeout := _30,000ms30s_ When the connection enters the @SUSPENDED@ state, after this delay in millisecondsin secondsas a @NSTimeInterval@, if the state is still @SUSPENDED@, the client library will attempt to reconnect automatically
__Type: @Integer@@NSTimeInterval@__ diff --git a/content/partials/versions/v1.1/types/_realtime_client_options_intro.textile b/content/partials/versions/v1.1/types/_realtime_client_options_intro.textile new file mode 100644 index 0000000000..feeb0346d6 --- /dev/null +++ b/content/partials/versions/v1.1/types/_realtime_client_options_intro.textile @@ -0,0 +1,12 @@ +blang[jsall]. + @ClientOptions@ is a plain Javascript object and is used in the @Ably.Realtime@ constructor's @options@ argument. The following attributes can be defined on the object: + +blang[ruby]. + @ClientOptions@ is a Hash object and is used in the @Ably::Realtime@ constructor's @options@ argument. The following key symbol values can be added to the Hash: + +blang[php]. + @ClientOptions@ is a associative array and is used in the @Ably\AblyRealtime@ constructor's @options@ argument. The following named keys and values can be added to the associative array: + +blang[java,objc,swift,csharp,go]. + @ART@@ClientOptions@ is used in the @AblyRealtime@ constructor's @options@ argument. + diff --git a/content/partials/versions/v1.1/types/_rest_client_options.textile b/content/partials/versions/v1.1/types/_rest_client_options.textile new file mode 100644 index 0000000000..40e6fc2f5f --- /dev/null +++ b/content/partials/versions/v1.1/types/_rest_client_options.textile @@ -0,0 +1,3 @@ +- queryTimeQueryTime:query_timequery_time := _false_ If true, the library will query the Ably servers for the current time when "issuing TokenRequests":/rest/authentication#token-authentication instead of relying on a locally-available time of day. Knowing the time accurately is needed to create valid signed Ably "TokenRequests":/rest/authentication#token-authentication, so this option is useful for library instances on auth servers where for some reason the server clock cannot be kept synchronized through normal means, such as an "NTP daemon":https://en.wikipedia.org/wiki/Ntpd . The server is queried for the current time once per client library instance (which stores the offset from the local clock), so if using this option you should avoid instancing a new version of the library for each request.
__Type: @Boolean@__ + +- defaultTokenParamsdefault_token_params:default_token_paramsDefaultTokenParams := When a "TokenParams":/rest/types#token-params object is provided, it will override the client library defaults when issuing new "Ably Tokens":/core-features/authentication#ably-tokens or "Ably @TokenRequests@":/rest/authentication#token-request
__Type: "@TokenParams@":/rest/types#token-params__ diff --git a/content/partials/versions/v1.1/types/_rest_client_options_intro.textile b/content/partials/versions/v1.1/types/_rest_client_options_intro.textile new file mode 100644 index 0000000000..98ff3b925c --- /dev/null +++ b/content/partials/versions/v1.1/types/_rest_client_options_intro.textile @@ -0,0 +1,11 @@ +blang[jsall]. + @ClientOptions@ is a plain Javascript object and is used in the @Ably.Rest@ constructor's @options@ argument. The following attributes can be defined on the object: + +blang[ruby]. + @ClientOptions@ is a Hash object and is used in the @Ably::Rest@ constructor's @options@ argument. The following key symbol values can be added to the Hash: + +blang[php]. + @ClientOptions@ is a associative array and is used in the @Ably\AblyRest@ constructor's @options@ argument. The following named keys and values can be added to the associative array: + +blang[java,objc,swift,csharp,go]. + @ART@@ClientOptions@ is used in the @AblyRest@ constructor's @options@ argument. \ No newline at end of file diff --git a/content/partials/versions/v1.1/types/_stats.textile b/content/partials/versions/v1.1/types/_stats.textile new file mode 100644 index 0000000000..54915e4aed --- /dev/null +++ b/content/partials/versions/v1.1/types/_stats.textile @@ -0,0 +1,26 @@ +A @Stats@ object represents an application's statistics for the specified interval and time period. Ably aggregates statistics globally for all accounts and applications, and makes these available both through our "statistics API":/realtime/statistics as well as your "application dashboard":https://support.ably.io/solution/articles/3000030053-how-do-i-access-my-app-dashboard. + +Please note that most attributes of the @Stats@ type below contain references to further stats types. This documentation is not exhaustive for all stats types, and as such, links to the stats types below will take you to the "Ruby library stats documentation":http://www.rubydoc.info/gems/ably/Ably/Models/Stats which contains exhaustive stats documentation. Ruby and Python however uses @under_score@ case instead of the default @camelCase@ in most languages, so please bear that in mind. + +h4. + default: Properties + java: Members + ruby: Attributes + python: Keyword arguments + +- unit := the length of the interval that this statistic covers, such as @:minute@, @:hour@, @:day@, @:month@@Minute@, @Hour@, @Day@, @Month@@StatGranularityDay@, @StatGranularityMonth@@'minute'@, @'hour'@, @'day'@, @'month'@.
__Type: "@Stats::GRANULARITY@":http://www.rubydoc.info/gems/ably/Ably/Models/Stats#GRANULARITY-constant@StatsIntervalGranularity enum@@ARTStatsGranularity@@String@__ +-
interval_granularityintervalGranularity
:= Deprecated alias for @unit@; scheduled to be removed in version 2.x client library versions.
__Type: "@Stats::GRANULARITY@":http://www.rubydoc.info/gems/ably/Ably/Models/Stats#GRANULARITY-constant@StatsIntervalGranularity enum@@ARTStatsGranularity@@String@__ +- intervalIdinterval_idIntervalId := the UTC time at which the time period covered by this @Stats@ object starts. For example, an interval ID value of "2018-03-01:10" in a @Stats@ object whose @unit@ is @day@ would indicate that the period covered is "2018-03-01:10 .. 2018-03-01:11". All @Stats@ objects, except those whose @unit@ is @minute@, have an interval ID with resolution of one hour and the time period covered will always begin and end at a UTC hour boundary. For this reason it is not possible to infer the @unit@ by looking at the resolution of the @intervalId@. @Stats@ objects covering an individual minute will have an interval ID indicating that time; for example "2018-03-01:10:02".
__Type: @String@__ +-
interval_timeIntervalTime
:= A @Time@@DateTime@@DateTimeOffset@ object representing the parsed @intervalId@@interval_id@@IntervalId@ (the UTC time at which the time period covered by this @Stats@ object starts)
__Type: @Time@@DateTime@@DateTimeOffset@__ +- allAll := aggregate count of both @inbound@ and @outbound@ message stats
__Type: "@MessageTypes@":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/MessageTypes__ +- apiRequestsapi_requestsApiRequests := breakdown of API requests received via the Ably REST API
__Type: "@RequestCount@":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/RequestCount__ +- channelsChannels := breakdown of channel related stats such as min, mean and peak channels
__Type: "@ResourceCount@":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/ResourceCount__ +- connectionsConnections := breakdown of connection related stats such as min, mean and peak connections for TLS and non-TLS connections
__Type: "@ConnectionTypes@":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/ConnectionTypes__ +- inboundInbound := statistics such as count and data for all inbound messages received over REST and Realtime connections, broken down by normal channel messages or presence messages
__Type: "@MessageTraffic@":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/MessageTraffic__ +- outboundOutbound := statistics such as count and data for all outbound messages retrieved via REST history requests, received over Realtime connections, or pushed with Webhooks, broken down by normal channel messages or presence messages
__Type: "@MessageTraffic@":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/MessageTraffic__ +- persistedPersisted := messages persisted and later retrieved via the "history API":/realtime/history
__Type: "@MessageTypes@":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/MessageTypes__ +- tokenRequeststoken_requestsTokenRequests := breakdown of Ably Token requests received via the Ably REST API.
__Type: "@RequestCount@":http://www.rubydoc.info/gems/ably/Ably/Models/Stats/RequestCount__ +- pushPush := Detailed stats on push notifications, see "our Push documentation":/general/push for more details
__Type: @PushStats@__ + + + diff --git a/content/partials/versions/v1.1/types/_stats_granularity.textile b/content/partials/versions/v1.1/types/_stats_granularity.textile new file mode 100644 index 0000000000..d3044ebec5 --- /dev/null +++ b/content/partials/versions/v1.1/types/_stats_granularity.textile @@ -0,0 +1,33 @@ +blang[swift,objc]. + @ARTStatsGranularity@ is an enum specifying the granularity of a "@ARTStats interval@":/rest/statistics#stats-type. + + ```[objc] + typedef NS_ENUM(NSUInteger, ARTStatsGranularity) { + ARTStatsGranularityMinute, + ARTStatsGranularityHour, + ARTStatsGranularityDay, + ARTStatsGranularityMonth + }; + ``` + + ```[swift] + enum ARTStatsGranularity : UInt { + case Minute + case Hour + case Day + case Month + } + ``` + +blang[csharp]. + @StatsIntervalGranularity@ is an enum specifying the granularity of a "@Stats interval@":/rest/statistics#stats-type. + + ```[csharp] + public enum StatsGranularity + { + Minute, + Hour, + Day, + Month + } + ``` diff --git a/content/partials/versions/v1.1/types/_stats_request_params.textile b/content/partials/versions/v1.1/types/_stats_request_params.textile new file mode 100644 index 0000000000..ca91c1e781 --- /dev/null +++ b/content/partials/versions/v1.1/types/_stats_request_params.textile @@ -0,0 +1,10 @@ +@StatsRequestParams@ is a type that encapsulates the parameters for a stats query. For example usage see "@Realtime#stats@@Realtime#Stats@":/realtime/statistics. + +h4. Members + +- Start := _null_ The start of the queried interval
__Type: @DateTimeOffset@__ +- End := _null_ The end of the queried interval
__Type: @DateTimeOffset@__ +- Limit := _null_ By default it is null. Limits the number of items returned by history or stats
__Type: @Integer@__ +- Direction := _Backwards_ Enum which is either @Forwards@ or @Backwards@
__Type: @Direction@ enum__ +- Unit := _Minute_ @Minute@, @Hour@, @Day@ @Month@. Based on the unit selected, the given start or end times are rounded down to the start of the relevant interval depending on the unit granularity of the query
__Type: @StatsIntervalGranularity@ enum__ +- ExtraParameters := Optionally any extra query parameters that may be passed to the query. This is mainly used internally by the library to manage paging.
__Type: @Dictionary@__ diff --git a/content/partials/versions/v1.1/types/_token_details.textile b/content/partials/versions/v1.1/types/_token_details.textile new file mode 100644 index 0000000000..3d3c2eb062 --- /dev/null +++ b/content/partials/versions/v1.1/types/_token_details.textile @@ -0,0 +1,48 @@ +@TokenDetails@ is a type providing details of Ably Token string and its associated metadata. + +h4. + default: Properties + java: Members + ruby: Attributes + +- tokenToken := The "Ably Token":/core-features/authentication#ably-tokens itself. A typical "Ably Token":/core-features/authentication#ably-tokens string may appear like @{{TOKEN}}@
__Type: @String@__ +- expiresExpires := The time (in milliseconds since the epoch)The time at which this token expires
__Type: @Integer@@Long Integer@@DateTimeOffset@@Time@@NSDate@__ +- issuedIssued := The time (in milliseconds since the epoch)The time at which this token was issued
__Type: @Integer@@Long Integer@@DateTimeOffset@@Time@@NSDate@__ +- capabilityCapability := The capability associated with this "Ably Token":/core-features/authentication#ably-tokens. The capability is a a JSON stringified canonicalized representation of the resource paths and associated operations. "Read more about authentication and capabilities":/core-features/authentication/#capabilities-explained
__Type: @String@@Capability@__ +- clientIdclient_idClientId := The client ID, if any, bound to this "Ably Token":/core-features/authentication#ably-tokens. If a client ID is included, then the "Ably Token":/core-features/authentication#ably-tokens authenticates its bearer as that client ID, and the "Ably Token":/core-features/authentication#ably-tokens may only be used to perform operations on behalf of that client ID. The client is then considered to be an "identified client":/core-features/authentication#identified-clients
__Type: @String@__ + +blang[ruby]. + h3. Methods + + - expired? := True when the token has expired
__Type: @Boolean@__ + +blang[python]. + h3. Methods + + - is_expired() := True when the token has expired
__Type: @Boolean@__ + +blang[csharp]. + h3. Methods + + - IsValidToken() := True if the token has not expired
__Type: @Boolean@__ + +h3. + default: TokenDetails constructors + +h4(#token-details-from-json). + default: TokenDetails.fromJson + ruby: TokenDetails.from_json + +bq(definition). + default: TokenDetails.fromJson(String json) -> TokenDetails + ruby: TokenDetails.from_json(String json) -> TokenDetails + +A static factory method to create a "@TokenDetails@":/realtime/types#token-details from a deserialized @TokenDetails@-like object or a JSON stringified @TokenDetails@. This method is provided to minimize bugs as a result of differing types by platform for fields such as @timestamp@ or @ttl@. For example, in Ruby @ttl@ in the @TokenDetails@ object is exposed in seconds as that is idiomatic for the language, yet when serialized to JSON using @to_json@ it is automatically converted to the Ably standard which is milliseconds. By using the @fromJson@ method when constructing a @TokenDetails@, Ably ensures that all fields are consistently serialized and deserialized across platforms. + +h4. Parameters + +- json := a @TokenDetails@-like deserialized object or JSON stringified @TokenDetails@.
__Type: @Object, String@__ + +h4. Returns + +A "@TokenDetails@":/realtime/types#token-details object diff --git a/content/partials/versions/v1.1/types/_token_params.textile b/content/partials/versions/v1.1/types/_token_params.textile new file mode 100644 index 0000000000..43451dcdc3 --- /dev/null +++ b/content/partials/versions/v1.1/types/_token_params.textile @@ -0,0 +1,33 @@ +blang[jsall]. + @TokenParams@ is a plain Javascript object and is used in the parameters of "token authentication":/realtime/authentication requests, corresponding to the desired attributes of the "Ably Token":/core-features/authentication#ably-tokens. The following attributes can be defined on the object: + +blang[ruby]. + @TokenParams@ is a Hash object and is used in the parameters of "token authentication":/realtime/authentication requests, corresponding to the desired attributes of the "Ably Token":/core-features/authentication#ably-tokens. The following key symbol values can be added to the Hash: + +blang[python]. + @TokenParams@ is a Dict and is used in the parameters of "token authentication":/realtime/authentication requests, corresponding to the desired attributes of the "Ably Token":/core-features/authentication#ably-tokens. The following keys-value pairs can be added to the Dict: + +blang[php]. + @TokenParams@ is an Associative Array and is used in the parameters of "token authentication":/realtime/authentication requests, corresponding to the desired attributes of the "Ably Token":/core-features/authentication#ably-tokens. The following named keys and values can be added to the Associative Array: + +blang[java,csharp]. + @TokenParams@ is used in the parameters of "token authentication":/realtime/authentication requests, corresponding to the desired attributes of the "Ably Token":/core-features/authentication#ably-tokens. + +blang[objc,swift]. + @ARTTokenParams@ is used in the parameters of "token authentication":/realtime/authentication requests, corresponding to the desired attributes of the "Ably Token":/core-features/authentication#ably-tokens. + +h4. + default: Properties + java: Members + ruby: Attributes + python: Attributes + +- capabilityCapability:capability := JSON stringified capability of the "Ably Token":/core-features/authentication#ably-tokens. If the "Ably Token":/core-features/authentication#ably-tokens request is successful, the capability of the returned "Ably Token":/core-features/authentication#ably-tokens will be the intersection of this capability with the capability of the issuing key. "Find our more about how to use capabilities to manage access privileges for clients":/core-features/authentication#capabilities-explained. __Type: @String@@Capability@__ + +- clientIdClientIdclient_id:client_id := A client ID, used for identifying this client when publishing messages or for presence purposes. The @clientId@@client_id@@ClientId@ can be any non-empty string. This option is primarily intended to be used in situations where the library is instanced with a key; note that a @clientId@@client_id@@ClientId@ may also be implicit in a token used to instance the library; an error will be raised if a @clientId@@client_id@@ClientId@ specified here conflicts with the @clientId@@client_id@@ClientId@ implicit in the token. "Find out more about client identities":/how-ably-works#client-identity
__Type: @String@__ + +- nonceNonce:nonce := An optional opaque nonce string of at least 16 characters to ensure uniqueness of this request. Any subsequent request using the same nonce will be rejected.
__Type: @String@__ + +- timestampTimestamp:timestamp := The timestamp (in milliseconds since the epoch)The timestamp of this request. @timestamp@, in conjunction with the @nonce@, is used to prevent requests for "Ably Token":/core-features/authentication#ably-tokens from being replayed.
__Type: @Integer@@Long Integer@@Time@@NSDate@@DateTimeOffset@__ + +- ttlTtl:ttl := _1 hour_ Requested time to live for the "Ably Token":/core-features/authentication#ably-tokens being created in millisecondsin secondsas a @NSTimeInterval@as a @TimeSpan@. When omitted, the Ably REST API default of 60 minutes is applied by Ably
__Type: @Integer@ (milliseconds)@Integer@ (seconds)@NSTimeInterval@@Long Integer@@TimeSpan@__ diff --git a/content/partials/versions/v1.1/types/_token_request.textile b/content/partials/versions/v1.1/types/_token_request.textile new file mode 100644 index 0000000000..0c2fd2cc13 --- /dev/null +++ b/content/partials/versions/v1.1/types/_token_request.textile @@ -0,0 +1,35 @@ +@TokenRequest@ is a type containing parameters for an Ably @TokenRequest@. "Ably Tokens":/core-features/authentication#ably-tokens are requested using "Auth#requestToken":/rest/authentication#request-token"Auth#request_token":/rest/authentication#request-token + +h4. + default: Properties + java: Members + ruby: Attributes + +- keyNamekey_nameKeyName := The key name of the key against which this request is made. The key name is public, whereas the key secret is private
__Type: @String@__ +- ttlTtl := Requested time to live for the "Ably Token":/core-features/authentication#ably-tokens in millisecondsin secondsas a @TimeSpan@. If the Ably @TokenRequest@ is successful, the TTL of the returned "Ably Token":/core-features/authentication#ably-tokens will be less than or equal to this value depending on application settings and the attributes of the issuing key.
__Type: @Integer@@TimeSpan@@NSTimeInterval@__ +- timestampTimestamp := The timestamp of this request in milliseconds
__Type: @Integer@@Long Integer@@Time@@DateTimeOffset@@NSDate@__ +- capabilityCapability := Capability of the requested "Ably Token":/core-features/authentication#ably-tokens. If the Ably @TokenRequest@ is successful, the capability of the returned "Ably Token":/core-features/authentication#ably-tokens will be the intersection of this capability with the capability of the issuing key. The capability is a JSON stringified canonicalized representation of the resource paths and associated operations. "Read more about authentication and capabilities":/realtime/authentication
__Type: @String@__ +- clientIdclient_idClientId := The client ID to associate with the requested "Ably Token":/core-features/authentication#ably-tokens. When provided, the "Ably Token":/core-features/authentication#ably-tokens may only be used to perform operations on behalf of that client ID
__Type: @String@__ +- nonceNonce := An opaque nonce string of at least 16 characters
__Type: @String@__ +- macMac := The Message Authentication Code for this request
__Type: @String@__ + +h3. + default: TokenRequest constructors + +h4(#token-request-from-json). + default: TokenRequest.fromJson + ruby: TokenRequest.from_json + +bq(definition). + default: TokenRequest.fromJson(String json) -> TokenRequest + ruby: TokenRequest.from_json(String json) -> TokenRequest + +A static factory method to create a "@TokenRequest@":/realtime/types#token-request from a deserialized @TokenRequest@-like object or a JSON stringified @TokenRequest@. This method is provided to minimize bugs as a result of differing types by platform for fields such as @timestamp@ or @ttl@. For example, in Ruby @ttl@ in the @TokenRequest@ object is exposed in seconds as that is idiomatic for the language, yet when serialized to JSON using @to_json@ it is automatically converted to the Ably standard which is milliseconds. By using the @fromJson@ method when constructing a @TokenRequest@, Ably ensures that all fields are consistently serialized and deserialized across platforms. + +h4. Parameters + +- json := a @TokenRequest@-like deserialized object or JSON stringified @TokenRequest@.
__Type: @Object, String@__ + +h4. Returns + +A "@TokenRequest@":/realtime/types#token-request object diff --git a/content/realtime/channel-params.textile b/content/realtime/channel-params.textile index 75cf596cc1..d2e4d76e7f 100644 --- a/content/realtime/channel-params.textile +++ b/content/realtime/channel-params.textile @@ -57,7 +57,7 @@ h3. SSE example For example, to specify the @rewind@ channel param with the value @"1"@ using a querystring parameter, where it will apply to all channels: ```[javascript] -var querystring = 'v=1.1&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; +var querystring = 'v=1.2&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); ``` @@ -67,7 +67,7 @@ Or to specify the same parameter but only applying to one channel of two, using var channelOne = encodeURIComponent('[?rewind=1]channel1'); var channelTwo = 'channel2'; var channels = channelOne + ',' + channelTwo; - var querystring = 'v=1.1&key={{API_KEY}}&channels=' + channels'; + var querystring = 'v=1.2&key={{API_KEY}}&channels=' + channels'; var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); ``` @@ -98,7 +98,7 @@ h3(#rewind-example-ably). Rewind example with an Ably client library To subscribe to a channel, getting the most recent message if available: ```[jsall] - // only with ably-js v1.2 or later; currently in beta + // only with ably-js v1.2 or later const realtime = new Ably.Realtime('{{API_KEY}}'); realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', { rewind: '1' @@ -117,7 +117,7 @@ h3(#rewind-example-sse). Rewind example with SSE To subscribe to a channel, getting the most recent message if available: ```[javascript] - var querystring = 'v=1.1&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; + var querystring = 'v=1.2&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); ``` @@ -139,3 +139,109 @@ h3(#rewind-examples-mqtt). Rewind example with MQTT ... }); ``` + +h2(#delta). Delta parameter + +The @delta@ channel parameter allows subscribers to expresse their desire to receive deltas (diffs) between the previous and current message instead of the current message in full on a given channel. The effects of this parameter are invisible to message senders and only affect subscribers that specify it - i.e. the sender sends messages in full and Ably computes and sends the message deltas to any subscriber which subscribed using this parameter. + +Note that requesting deltas does not guarantee that every message received will be a delta, as the server may chose to send some messages in full depending on system load and other factors. Therefore a subscriber using the @delta@ parameter should be prepared to handle a random mix of delta and full messages. + +The @delta@ parameter can take only the value @vcdiff@. VCDIFF is the format of the deltas. It is an open format specified in "RFC 3284":https://tools.ietf.org/html/rfc3284. + +Ably provides a JavaScript "delta codec library":https://github.com/ably/delta-codec-js to help you avoid writing boilerplate VCDIFF handling code. Some examples follow. + +h3(#delta-example-sse). Delta example with enveloped SSE + +```[jsall] +const key = '{{API_KEY}}'; +const channel = 'sample-app-sse'; +const url = `https://realtime.ably.io/event-stream?channels=${channel}&v=1.2&key=${key}&delta=vcdiff`; +const eventSource = new EventSource(url); +const channelDecoder = new DeltaCodec.CheckedVcdiffDecoder(); + +eventSource.onmessage = (event) => { + /* event.data is JSON-encoded Ably Message (see https://www.ably.io/documentation/realtime/types#message) */ + const message = JSON.parse(event.data); + const { id, extras } = message; + let { data } = message; + + try { + if (extras && extras.delta) { + data = channelDecoder.applyBase64Delta(data, id, extras.delta.from).asUtf8String(); + } else { + channelDecoder.setBase(data, id); + } + } catch(e) { + /* Delta decoder error */ + console.log(e); + } + + /* Process decoded data */ + console.log(data); +}; +``` + +h3(#delta-example-unenv-sse). Delta example with unenveloped SSE + +```[jsall] +const key = '{{API_KEY}}'; +const channel = 'sample-app-sse'; +const url = `https://realtime.ably.io/event-stream?channels=${channel}&v=1.2&key=${key}&delta=vcdiff&enveloped=false`; +const eventSource = new EventSource(url); +const channelDecoder = new DeltaCodec.VcdiffDecoder(); + +eventSource.onmessage = (event) => { + let data = event.data; + + try { + if (DeltaCodec.VcdiffDecoder.isBase64Delta(data)) { + data = channelDecoder.applyBase64Delta(data).asUtf8String(); + } else { + channelDecoder.setBase(data); + } + } catch(e) { + /* Delta decoder error */ + console.log(e); + } + + /* Process decoded data */ + console.log(data); +}; +``` + +h3(#delta-example-mqtt). Delta example with MQTT + +```[jsall] +const mqtt = require('mqtt'); +const { VcdiffDecoder } = require('./lib'); + +const options = { + keepalive: 30, + username: 'FIRST_HALF_OF_API_KEY', + password: 'SECOND_HALF_OF_API_KEY', + port: 8883 +}; +const client = mqtt.connect('mqtts:mqtt.ably.io', options); +const channelName = 'sample-app-mqtt'; +const channelDecoder = new VcdiffDecoder(); + +client.on('message', (_, payload) => { + let data = payload; + + try { + if (VcdiffDecoder.isDelta(data)) { + data = channelDecoder.applyDelta(data).asUint8Array(); + } else { + channelDecoder.setBase(data); + } + } catch(e) { + /* Delta decoder error */ + console.log(e); + } + + /* Process decoded data */ + console.log(data); +}); + +client.subscribe(`[?delta=vcdiff]${channelName}`); +``` diff --git a/content/realtime/usage.textile b/content/realtime/usage.textile index fe1ca491c3..7043e01c58 100644 --- a/content/realtime/usage.textile +++ b/content/realtime/usage.textile @@ -100,13 +100,13 @@ blang[java]. For Java applications: ```[java] - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' ``` For Android apps: ```[java] - compile 'io.ably:ably-android:1.0.0' + compile 'io.ably:ably-android:1.2.0' ``` In the above example a specific version of the library is referenced, however we recommend you check which is the latest stable version and always use that. Follow links to get the latest stable release for "Java":https://bintray.com/ably-io/ably/ably-java and "Android":https://bintray.com/ably-io/ably/ably-android. diff --git a/content/realtime/versions/v1.1/authentication.textile b/content/realtime/versions/v1.1/authentication.textile new file mode 100644 index 0000000000..8fcfdbf25b --- /dev/null +++ b/content/realtime/versions/v1.1/authentication.textile @@ -0,0 +1,600 @@ +--- +title: Authentication +section: realtime +index: 40 +languages: + - javascript + - nodejs + - ruby + - java + - swift + - objc + - csharp +api_separator: +jump_to: + Help with: + - Understanding Auth + - Basic Authentication + - Token Authentication + - Token Types + - Identified clients + Channel API properties: + - clientId#client-id + Channel API methods: + - authorize + - createTokenRequest#create-token-request + - requestToken#request-token + Types: + - Related Types#related-types +--- + +Ably clients can authenticate with Ably in one of two ways. They can use "Basic Authentication":/core-features/authentication#basic-authentication or "Token Authentication":/core-features/authentication#token-authentication. Basic Authentication makes use of the customer's "API key":https://support.ably.io/solution/articles/3000030054-what-is-an-app-api-key) to connect with Ably. Token Authentication requires a server to provide an "Ably Token":/core-features/authentication#tokens, an "Ably @TokenRequest@":#request-token, an "Ably JWT (JSON Web Token)":/core-features/authentication#ably-jwt, or an "External JWT":https://jwt.io with an embedded Ably-compatible token to the client. "Token Authentication":/core-features/authentication#token-authentication, in most cases, is the recommended strategy due to it providing robust access control and stringent security measures. + +h2(#understanding-auth). Understanding the Ably authentication schemes + +This page of documentation is intended to describe the Realtime Authentication API and is not intended to explain in depth how Ably's authentication works. If you are new to Ably and/or the authentication schemes, we strongly recommend that you get acquainted with the following first: + +* "Getting started with Ably's authentication":/core-features/authentication/#getting-started +* "Selecting the right authentication scheme":/core-features/authentication/#selecting-auth +* "Basic Authentication explained":/core-features/authentication/#basic-authentication +* "Token Authentication explained":/core-features/authentication/#token-authentication + +h3. Tutorials + +If you're the kind of person who prefers to dive into code, we have "client-server authentication tutorials":/tutorials. + +h2(#basic-authentication). Basic Authentication + +*Basic Authentication* uses one of the "api keys":https://support.ably.io/solution/articles/3000030054-what-is-an-app-api-key "configured via the application dashboard":https://support.ably.io/support/solutions/articles/3000030502-setting-up-and-managing-api-keys as the authentication token. Basic Authentication is the simplest method to use but has some important limitations described in detail in the "Basic Authentication documentation":/core-features/authentication/#basic-authentication. + +Here is a straightforward example of using Basic Authentication to connect to Ably: + +```[jsall](code-editor:realtime/connection) + var realtime = new Ably.Realtime({ key: '{{API_KEY}}' }); +``` + +```[ruby] + realtime = Ably::Realtime.new(key: '{{API_KEY}}') +``` + +```[java] + ClientOptions options = new ClientOptions(); + options.key = "{{API_KEY}}"; + AblyRealtime realtime = new AblyRealtime(options); +``` + +```[swift] + let realtime = ARTRealtime(key: "{{API_KEY}}") +``` + +```[objc] + ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"]; +``` + +```[csharp] + AblyRealtime realtime = AblyRealtime("{{API_KEY}}"); +``` + +h2(#token-authentication). Token Authentication + +p(tip). Look at the "general authentication documentation":/core-features/authentication#token-authentication for more in-depth information on Token Authentication. + +*Token Authentication* uses an Ably-compatible token to authenticate with Ably. This can be an "Ably Token":/core-features/authentication#tokens obtained via the REST API "@requestToken@":#request-token"@RequestToken@":#request-token"@request_token@":#request-token endpoint, an "Ably JWT":/core-features/authentication#ably-jwt signed by your API key, or an "External JWT":https://jwt.io object "with an embedded Ably-compatible token":/core-features/authentication#jwt-embed-process. Tokens are authentication credentials that are short-lived, and therefore they may more readily be distributed to clients where there is a risk of compromise. Tokens may also be issued with a particular scope - such as a limited set of "access rights or capabilities":/core-features/authentication#capabilities-explained or being limited to use by a specific "@clientId@@ClientId@ identity":#identified-clients - and therefore token-based authentication provides the flexibility to implement access and identity control policies in the application. See the "Token Authentication documentation":/core-features/authentication#token-authentication for more details. To initialize the "Realtime library":/rest/usage#constructor to a previously obtained "@Ably Token@":#token-details or "@Ably JWT@":/core-features/authentication#ably-jwt, set the @:token@ or @:token_details@@token@ or @token_details@@Token@ or @TokenDetails@@token@ or @tokenDetails@ attribute of "@ClientOptions@":/rest/usage#client-options to said token. + +Below is a rather contrived yet straightforward example that instances a Realtime library using "Token Authentication":/core-features/authentication#token-authentication with a means to reissue tokens as required. Typically, in a browser environment, the "@authUrl@":#auth-options"@AuthUrl@":#auth-options provided would be a relative URL to a local endpoint that is used to issue tokens to trusted clients. Client requests can, for example, be trusted based on session cookies. For non-browser clients, an authentication callback is preferred thus relying on your application to communicate securely with your own servers to obtain a token. + +```[jsall](code-editor:realtime/auth-token-callback) + var realtime = new Ably.Realtime({ authUrl: 'http://my.website/auth' }); +``` + +```[ruby] + realtime = Ably::Realtime.new(auth_url: 'http://my.website/auth') +``` + +```[java] + ClientOptions options = new ClientOptions(); + options.authUrl = "http://my.website/auth"; + AblyRealtime realtime = new AblyRealtime(options); +``` + +```[objc] + ARTClientOptions *options = [[ARTClientOptions alloc] init]; + options.authUrl = [NSURL URLWithString:@"http://my.website/auth"]; + ARTRealtime *realtime = [[ARTRealtime alloc] initWithOptions:options]; +``` + +```[swift] + let options = ARTClientOptions() + options.authUrl = NSURL(string: "http://my.website/auth") + let realtime = ARTRealtime(options: options) +``` + +```[csharp] + ClientOptions options = new ClientOptions(); + options.AuthUrl = new Uri("http://my.website/auth"); + AblyRealtime realtime = new AblyRealtime(options); +``` + +h2(#selecting-auth). Selecting an authentication mechanism + +<%= partial partial_version('core-features/_authentication_comparison') %> + +h2(#identified-clients). 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 (i.e. a "@client_id@":#token-details"@ClientId@":#token-details"@clientId@":#token-details), then they are considered to be an *identified client* and for all operations they perform with the Ably service, their "@client_id@":#token-details"@ClientId@":#token-details"@clientId@":#token-details field will be automatically populated and can be trusted by other clients. + +We encourage customers to always issue 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@":#token-details"@ClientId@":#token-details"@clientId@":#token-details that may be used by any of its clients, all other clients can rely on the validity of the "@client_id@":#token-details"@ClientId@":#token-details"@clientId@":#token-details in published messages and of members present in presence channels. + +The following example demonstrates how to issue an Ably "@TokenRequest@":#request-token with an explicit "@client_id@":#token-details"@ClientId@":#token-details"@clientId@":#token-details that, when used by a client, will then be considered an *identified client*. + +minimize. See example + ```[jsall](code-editor:realtime/auth-client-id) + var realtime = new Ably.Realtime({ key: '{{API_KEY}}' }); + realtime.auth.createTokenRequest({ clientId: 'Bob' }, function(err, tokenRequest) { + /* ... issue the TokenRequest to a client ... */ + }) + ``` + + ```[ruby] + realtime = Ably::Realtime.new(key: '{{API_KEY}}') + realtime.auth.createTokenRequest(client_id: 'Bob') do |token_request| + # ... issue the TokenRequest to a client ... + end + ``` + + ```[java] + ClientOptions options = new ClientOptions(); + options.key = "{{API_KEY}}"; + AblyRealtime realtime = new AblyRealtime(options); + TokenParams tokenParams = new TokenParams(); + tokenParams.clientId = "Bob"; + TokenRequest tokenRequest; + tokenRequest = realtime.auth.createTokenRequest(tokenParams, null); + /* ... issue the TokenRequest to a client ... */ + ``` + + ```[csharp] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + TokenParams tokenParams = new TokenParams {ClientId = "Bob"}; + string tokenRequest = await realtime.Auth.CreateTokenRequestAsync(tokenParams); + /* ... issue the TokenRequest to a client ... */ + ``` + + ```[objc] + ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"]; + ARTTokenParams *tokenParams = [[ARTTokenParams alloc] initWithClientId:@"Bob"]; + [realtime.auth createTokenRequest:tokenParams options:nil + callback:^(ARTTokenRequest *tokenRequest NSError *error) { + // ... issue the TokenRequest to a client ... + }]; + ``` + + ```[swift] + let realtime = ARTRealtime(key: "{{API_KEY}}") + let tokenParams = ARTTokenParams(clientId: "Bob") + realtime.auth.createTokenRequest(tokenParams, options: nil) { tokenRequest, error in + // ... issue the TokenRequest to a client ... + } + ``` + +h1. Auth API Reference + +inline-toc. + Properties: + - clientId#client-id + Methods: + - authorize + - createTokenRequest#create-token-request + - requestToken#request-token + Related types: + - AuthOptions#auth-options + - TokenDetails#token-details + - TokenParams#token-params + - TokenRequest#token-request + +The Auth object is available as the "@auth@ field":/realtime/usage#auth"@Auth@ property":/realtime/usage#auth"@auth@ property":/realtime/usage#auth"@auth@ attribute":/realtime/usage#auth of an "Ably Realtime client instance":/realtime/usage#constructor. + +h2(#properties). + default: Auth Properties + java: io.ably.lib.rest.Auth Members + csharp: IO.Ably.AblyAuth Properties + ruby: Ably::Auth Attributes + objc,swift: ARTAuth Properties + +The @ART@@Auth@ object exposes the following public propertiesattributesmembers: + +h6(#client-id). + default: clientId + ruby: client_id + csharp: ClientId + +The client ID string, if any, configured for this client connection. See "identified clients":#identified-clients for more information on trusted client identifiers. + +h2(#methods). + default: Auth Methods + java: io.ably.lib.rest.Auth Methods + csharp: IO.Ably.AblyAuth Methods + ruby: Ably::Auth Methods + objc,swift: ARTAuth Methods + +h6(#authorize). + default: authorize + csharp: Authorize +authorize + +bq(definition). + default: authorize("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions, callback("ErrorInfo":/realtime/types#error-info err, "TokenDetails":#token-details tokenDetails)) + ruby: "Deferrable":/realtime/types#deferrable authorize("TokenParams":#token-params token_params, "AuthOptions":#auth-options auth_options) -> yields "TokenDetails":#token-details + java: "TokenDetails":#token-details authorize("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions) + csharp: "Task:#token-details AuthorizeAsync("TokenParams:#token-params?, "AuthOptions":#auth-options?) + swift,objc: authorize(tokenParams: "ARTTokenParams":#token-params?, authOptions: "ARTAuthOptions":#auth-options?, callback: ("ARTTokenDetails":#token-details?, NSError?) -> Void) + +Instructs the library to get a new token immediately. Once fetched, it will upgrade the current realtime connection to use the new token, or if not connected, will initiate a connection to Ably once the new token has been obtained. Also stores any @token_params@ and @auth_options@@tokenParams@ and @authOptions@ passed in as the new defaults, to be used for all subsequent implicit or explicit token requests. + +Any @token_params@ and @auth_options@@tokenParams@ and @authOptions@ objects passed in will entirely replace (as opposed to being merged with) the currently client library saved @token_params@ and @auth_options@@tokenParams@ and @authOptions@. + +h4. Parameters + +- token_paramstokenParams := an optional object containing the "token parameters":#token-paramsan optional "@TokenParams@":#token-params object containing the "Ably Token":/core-features/authentication#ably-tokens parametersan optional set of key value pairs containing the "token parameters":#token-params for the authorization request
__Type: "@TokenParams@":#token-params__ + +- auth_optionsauthOptions := an optional object containing the "authentication options":#auth-optionsan optional "@TokenParams@":#auth-options object containing the authentication optionsan optional set of key value pairs containing the "authentication options":#auth-options for the authorization request
__Type: "@AuthOptions@":#auth-options__ + +-
callback
:= is a function of the form: @function(err, tokenDetails)@ +-
&block
:= yields a "@TokenDetails@":#token-details object +-
callback
:= called with a "@ARTTokenDetails@":#token-details object or an error + +blang[objc,swift]. + h4. Callback result + + On success, the callback will be called with the new "@TokenDetails@":#token-details object only once the realtime connection has been successfully upgraded to use the new token. + + On failure to obtain an token or upgrade the token, the connection will move to the @SUSPENDED@ or @FAILED@ state, and the callback will be called with @err@ containing an @NSError@ object with the error response as defined in the "Ably REST API":/rest-api#common documentation. + + The @authorize@ callback can therefore be used to only trigger an event once the new token has taken effect. This can be useful if, for example, you want to do attach to a new channel following a new channel capability being applied to the connection. + +blang[jsall]. + h4. Callback result + + On success, the callback will be called with the new "@TokenDetails@":#token-details only once the realtime connection has been successfully upgraded to use the new token. + + On failure to obtain a token or upgrade the token, the connection will move to the @SUSPENDED@ or @FAILED@ state, and the callback will be called with @err@ containing an "@ErrorInfo@":/realtime/types#error-info object with the error response as defined in the "Ably REST API":/rest-api#common documentation. + + The @authorize@ callback can be used to trigger an event once the new token has taken effect. This can be useful if, for example, you want to do attach to a new channel following a new channel capability being applied to the connection. + +blang[java]. + h4. Returns + + On success, a new "@TokenDetails@":#token-details is returned only once the realtime connection has been successfully upgraded to use the new "Ably Token":/core-features/authentication#ably-tokens. + + On failure to obtain a token or upgrade the token, the connection will move to the @SUSPENDED@ or @FAILED@ state and an "@AblyException@":#ably-exception will be raised with the error response as defined in the "Ably REST API":/rest-api#common documentation. + + The synchronous @authorize@ method can therefore be used to run subsequent code only once the new token has taken effect. This can be useful if, for example, you want to do attach to a new channel following a new channel capability being applied to the connection. + +blang[csharp]. + h4. Returns + + Returns a @Task@ which needs to be awaited. + + On success, a new "@TokenDetails@":#token-details is returned only once the realtime connection has been successfully upgraded to use the new token. + + On failure to obtain a token or upgrade the token, the connection will move to the @SUSPENDED@ or @FAILED@ state and an "@AblyException@":#ably-exception will be raised with the error response as defined in the "Ably REST API":/rest-api#common documentation. + + By waiting for the @authorize@ method return value, it can be used to run subsequent code only once the new token has taken effect. This can be useful if, for example, you want to do attach to a new channel following a new channel capability being applied to the connection. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from this method. + + On success, the registered success callbacks for the "@Deferrable@":/realtime/types#deferrable and any block provided to this method yields a "@TokenDetails@":#token-details only once the realtime connection has been successfully upgraded to use the new token. + + On failure to obtain a token or upgrade the token, the connection will move to the @SUSPENDED@ or @FAILED@ state, triggering the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object with the error response as defined in the "Ably REST API":/rest-api#common documentation. + + The @authorize@ callbacks can therefore be used to trigger an event once the new "Ably Token":/core-features/authentication#ably-tokens has taken effect. This can be useful if, for example, you want to do attach to a new channel following a new channel capability being applied to the connection. + +h4. Example + +bc[jsall]. client.auth.authorize({ clientId: 'bob' }, function(err, tokenDetails) { + if(err) { + console.log('An error occurred; err = ' + err.toString()); + } else { + console.log('Success! Realtime connection upgraded with new token: ' + + tokenDetails.token); + } +}); + +bc[java]. try { + TokenParams tokenParams = new TokenParams(); + tokenParams.clientId = "bob"; + TokenDetails tokenDetails = client.auth.authorize(tokenParams, null); + System.out.println("Success; token = " + tokenDetails.token); +} catch(AblyException e) { + System.out.println("An error occurred; err = " + e.getMessage()); +} + +bc[csharp]. try { + TokenParams tokenParams = new TokenParams {ClientId = "bob"}; + TokenDetails tokenDetails = await client.Auth.AuthorizeAsync(tokenParams); + Console.WriteLine("Success; Token = " + tokenDetails.Token); +} catch (AblyException e) { + Console.WriteLine("An error occurred; Error = " + e.Message); +} + +bc[ruby]. client.auth.authorize(client_id: 'bob') do |token_details| + puts "Success; token = #{token_details.token}" +end + +bc[objc]. ARTTokenParams *tokenParams = [[ARTTokenParams alloc] initWithClientId:@"Bob"]; +[client.auth authorize:tokenParams options:nil callback:^(ARTTokenDetails *tokenDetails, NSError *error) { + if (error) { + NSLog(@"An error occurred; err = %@", error); + } else { + NSLog(@"Success; token = %@", tokenDetails.token); + } +}]; + +bc[swift]. let tokenParams = ARTTokenParams(clientId: "Bob") +client.auth.authorize(tokenParams, options: nil) { tokenDetails, error in + guard let tokenDetails = tokenDetails else { + print("An error occurred; err = \(error!)") + return + } + print("Success; token = \(tokenDetails.token)") +} + +h6(#create-token-request). + default: createTokenRequest + ruby: create_token_request + csharp: CreateTokenRequestAsync + +bq(definition). + default: createTokenRequest("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions, callback("ErrorInfo":/realtime/types#error-info err, "TokenRequest":#token-request tokenRequest)) + ruby: "Deferrable":/realtime/types#deferrable create_token_request("TokenParams":#token-params token_params, "AuthOptions":#auth-options auth_options) -> yields "TokenRequest":#token-request + java: "TokenRequest":#token-request createTokenRequest("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions) + csharp: Task<"TokenRequest":#token-request> CreateTokenRequestAsync("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions) + objc,swift: createTokenRequest(tokenParams: "ARTTokenParams":#token-params?, options: "ARTAuthOptions":#auth-options?, callback: ("ARTTokenRequest":#token-request?, NSError?) -> Void) + +Creates and signs an Ably "@TokenRequest@":#request-token based on the specified (or if none specified, the client library stored) @token_params@ and @auth_options@@tokenParams@ and @authOptions@. Note this can only be used when the "API @key@":https://support.ably.io/solution/articles/3000030054-what-is-an-app-api-key value is available locally. Otherwise, the Ably "@TokenRequest@":#request-token must be obtained from the key owner. Use this to generate Ably "@TokenRequests@":#request-token in order to implement an "Ably Token":/core-features/authentication#ably-tokens request callback for use by other clients. + +Both @auth_options@ and @token_params@@authOptions@ and @tokenParams@ are optional. When omitted or @null@, the default token parameters and authentication options for the client library are used, as specified in the @ClientOptions@ when the client library was instanced, or later updated with an explicit "@authorize@@Authorize@":#authorize request. Values passed in will be used instead of (rather than being merged with) the default values. + +To understand why an Ably "@TokenRequest@":#request-token may be issued to clients in favor of a token, see "Token Authentication explained":/core-features/authentication/#token-authentication. + +h4. Parameters + +- token_paramstokenParams := an optional object containing the "token parameters":#token-paramsan optional "@TokenParams@":#token-params object containing the token parametersan optional set of key value pairs containing the "token parameters":#token-params for the "Ably Token":/core-features/authentication#ably-tokens request
__Type: "@TokenParams@":#token-params__ + +- auth_optionsauthOptions := an optional object containing the "authentication options":#auth-optionsan optional "@TokenParams@":#token-params object containing the authentication optionsan optional set of key value pairs containing the "authentication options":#auth-optionsan optional "@ARTTokenParams@":#token-params containing the "authentication options":#auth-options
__Type: "@AuthOptions@":#auth-options__ + +-
callback
:= is a function of the form: @function(err, tokenRequest)@ +-
&block
:= yields a "@TokenRequest@":#token-request object +-
callback
:= called with a "@ARTTokenRequest@":#token-request object or an error + +blang[jsall]. + h4. Callback result + + On success, @tokenRequest@ contains a "@TokenRequest@":#token-request JSON object. + + On failure to issue a "@TokenRequest@":#token-request, @err@ contains an "@ErrorInfo@":#error-info object with an error response as defined in the "Ably REST API":/rest-api#common documentation. + +blang[java]. + h4. Returns + + On success, a "@TokenRequest@":#token-request object is returned. + + Failure to issue a "@TokenRequest@":#token-request will raise an "@AblyException@":#ably-exception. + +blang[csharp]. + h4. Returns + + Returns a @Task@ which needs to be awaited. + + On success, a "@TokenRequest@":#token-request object is returned. + + Failure to issue a "@TokenRequest@":#token-request will raise an "@AblyException@":#ably-exception. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from this method. + + On success, the registered success callbacks for the "@Deferrable@":/realtime/types#deferrable and any block provided to this method yields a "@TokenRequest@":#token-request object. + + Failure to issue a "@TokenRequest@":#token-request will trigger the errback callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object containing an error response as defined in the "Ably REST API":/rest-api#common documentation. + +h4. Example + +bc[jsall]. client.auth.createTokenRequest({ clientId: 'bob' }, function(err, tokenRequest) { + if(err) { + console.log('An error occurred; err = ' + err.message); + } else { + console.log('Success; token request = ' + tokenRequest); + } +}); + +bc[java]. try { + TokenParams tokenParams = new TokenParams(); + tokenParams.clientId = "bob"; + TokenRequest tokenRequest = client.auth.createTokenRequest(tokenParams, null); + System.out.println("Success; token request issued"); +} catch(AblyException e) { + System.out.println("An error occurred; err = " + e.getMessage()); +} + +bc[csharp]. try { + TokenParams tokenParams = new TokenParams {ClientId = "bob"}; + TokenRequest tokenRequest = await client.Auth.CreateTokenRequestAsync(tokenParams); + Console.WriteLine("Success; token request issued"); +} catch (AblyException e) { + Console.WriteLine("An error occurred; err = " + e.Message); +} + +bc[ruby]. client.auth.create_token_request(client_id: 'bob') do |token_request| + puts "Success; token request = #{token_request}" +end + +bc[objc]. ARTTokenParams *tokenParams = [[ARTTokenParams alloc] initWithClientId:@"Bob"]; +[client.auth createTokenRequest:tokenParams options:nil callback:^(ARTTokenRequest *tokenRequest, NSError *error) { + if (error) { + NSLog(@"An error occurred; err = %@", error); + } else { + NSLog(@"Success; token request = %@", tokenRequest); + } +}]; + +bc[swift]. let tokenParams = ARTTokenParams(clientId: "Bob") +client.auth.createTokenRequest(tokenParams, options: nil) { tokenRequest, error in + guard let tokenRequest = tokenRequest else { + print("An error occurred; err = \(error!)") + return + } + print("Success; token request = \(tokenRequest)") +} + +h6(#request-token). + default: requestToken + ruby: request_token + csharp: RequestTokenAsync + +bq(definition). + default: requestToken("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions, callback("ErrorInfo":/realtime/types#error-info err, "TokenDetails":#token-details tokenDetails)) + ruby: "Deferrable":/realtime/types#deferrable request_token("TokenParams":#token-params token_params, "AuthOptions":#auth-options auth_options) -> yields "TokenDetails":#token-details + java: "TokenDetails":#token-details requestToken("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions) + csharp: async Task<"TokenDetails":#token-details> RequestTokenAsync("TokenParams":#token-params? tokenParams, "AuthOptions":#auth-options? options) + objc,swift: requestToken(tokenParams: "ARTTokenParams":#token-params?, withOptions: "ARTAuthOptions":#auth-options?, callback: ("ARTTokenDetails":#token-details?, NSError?) -> Void) + +Calls the "@requestToken@ REST API endpoint":/rest-api#request-token to obtain an "Ably Token":/core-features/authentication#ably-tokens according to the specified @token_params@ and @auth_options@@tokenParams@ and @authOptions@. + +Both @auth_options@ and @token_params@@authOptions@ and @tokenParams@ are optional. When omitted or @null@, the default token parameters and authentication options for the client library are used, as specified in the @ClientOptions@ when the client library was instanced, or later updated with an explicit "@authorize@@Authorize@":#authorize request. Values passed in will be used instead of (rather than being merged with) the default values. + +To understand why an Ably "@TokenRequest@":#request-token may be issued to clients in favor of a token, see "Token Authentication explained":/core-features/authentication/#token-authentication. + +h4. Parameters + +- token_paramstokenParams := an optional object containing the "token parameters":#token-paramsan optional "@TokenParams@":#token-params object containing the token parametersan optional set of key value pairs containing the "token parameters":#token-params for the requested token
__Type: "@TokenParams@":#token-params__ + +- auth_optionsauthOptions := an optional object containing the "authentication options":#auth-optionsan optional "@TokenParams@":#auth-options object containing the authentication optionsan optional set of key value pairs containing the "authentication options":#auth-options for the requested "Ably Token":/core-features/authentication#ably-tokens
__Type: "@AuthOptions@":#auth-options__ + +-
callback
:= is a function of the form: @function(err, tokenDetails)@ +-
&block
:= yields a "@TokenDetails@":#token-details object +-
callback
:= called with a "@ARTTokenDetails@":#token-details object or an error + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @tokenDetails@ contains a "@TokenDetails@":#token-details object containing the details of the new "Ably Token":/core-features/authentication#ably-tokens along with the @token@ string. + + On failure to obtain an "Ably Token":/core-features/authentication#ably-tokens, @err@ contains an "@ErrorInfo@":#error-info@NSError@ object with an error response as defined in the "Ably REST API":/rest-api#common documentation. + +blang[java]. + h4. Returns + + On success, a "@TokenDetails@":#token-details object containing the details of the new "Ably Token":/core-features/authentication#ably-tokens along with the @token@ string is returned. + + Failure to obtain an "Ably Token":/core-features/authentication#ably-tokens will raise an "@AblyException@":#ably-exception. + +blang[csharp]. + h4. Returns + + Returns a @Task@ which needs to be awaited. + + On success, a "@TokenDetails@":#token-details object containing the details of the new "Ably Token":/core-features/authentication#ably-tokens along with the @token@ string is returned. + + Failure to obtain an "Ably Token":/core-features/authentication#ably-tokens will raise an "@AblyException@":#ably-exception. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from this method. + + On success, the registered success callbacks for the "@Deferrable@":/realtime/types#deferrable and any block provided to this method yields a "@TokenDetails@":#token-details object containing the details of the new "Ably Token":/core-features/authentication#ably-tokens along with the @token@ string. + + Failure to obtain an "Ably Token":/core-features/authentication#ably-tokens will trigger the errback callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object containing an error response as defined in the "Ably REST API":/rest-api#common documentation. + +h4. Example + +bc[jsall]. client.auth.requestToken({ clientId: 'bob'}, function(err, tokenDetails){ + if(err) { + console.log('An error occurred; err = ' + err.message); + } else { + console.log('Success; token = ' + tokenDetails.token); + } +}); + +bc[ruby]. client.auth.request_token(client_id: 'bob') do |token_details| + puts "Success; token = #{token_details.token}" +end + +bc[java]. try { + TokenParams tokenParams = new TokenParams(); + tokenParams.clientId = "bob"; + TokenDetails tokenDetails = client.auth.requestToken(tokenParams, null); + System.out.println("Success; token = " + tokenDetails.token); +} catch(AblyException e) { + System.out.println("An error occurred; err = " + e.getMessage()); +} + +bc[csharp]. try { + TokenParams tokenParams = new TokenParams {ClientId = "bob"}; + TokenDetails tokenDetails = await client.Auth.RequestTokenAsync(tokenParams); + Console.WriteLine("Success; token = " + tokenDetails.Token); +} catch (AblyException e) { + Console.WriteLine("An error occurred; err = " + e.Message); +} + +bc[objc]. ARTTokenParams *tokenParams = [[ARTTokenParams alloc] initWithClientId:@"Bob"]; +[client.auth requestToken:tokenParams withOptions:nil callback:^(ARTTokenDetails *tokenDetails, NSError *error) { + if (error) { + NSLog(@"An error occurred; err = %@", error); + } else { + NSLog(@"Success; token = %@", tokenDetails.token); + } +}]; + +bc[swift]. let tokenParams = ARTTokenParams(clientId: "Bob") +client.auth.requestToken(tokenParams, withOptions: : nil) { tokenDetails, error in + guard let tokenDetails = tokenDetails else { + print("An error occurred; err = \(error!)") + return +} + print("Success; token = \(tokenDetails.token)") +} + +h2(#related-types). Related types + +h3(#auth-options). + default: AuthOptions Object + objc,swift: ARTAuthOptions + ruby: AuthOptions Hash + java: io.ably.lib.rest.Auth.AuthOptions + dotent: IO.Ably.AuthOptions + +<%= partial partial_version('types/_auth_options') %> + +h3(#token-details). + default: TokenDetails Object + objc,swift: ARTTokenDetails + java: io.ably.lib.types.TokenDetails + ruby: Ably::Models::TokenDetails + csharp: IO.Ably.TokenDetails + +<%= partial partial_version('types/_token_details') %> + +h3(#token-params). + default: TokenParams Object + objc,swift: ARTTokenParams + ruby: TokenParams Hash + java: io.ably.lib.rest.Auth.TokenParams + csharp: IO.Ably.TokenParams + +<%= partial partial_version('types/_token_params') %> + +h3(#token-request). + default: TokenRequest Object + objc,swift: ARTTokenRequest + ruby: Ably::Models::TokenRequest + java: io.ably.lib.rest.Auth.TokenRequest + csharp: IO.Ably.TokenRequest + +<%= partial partial_version('types/_token_request') %> + diff --git a/content/realtime/versions/v1.1/channel-metadata.textile b/content/realtime/versions/v1.1/channel-metadata.textile new file mode 100644 index 0000000000..8af2ac9b14 --- /dev/null +++ b/content/realtime/versions/v1.1/channel-metadata.textile @@ -0,0 +1,127 @@ +--- +title: Channel Metadata API +section: realtime +index: 31 +jump_to: + Help with: + - Overview#overview + - Metachannels#metachannels + - Permissions#permissions + - Subscribing to metadata events#subscribing-to-metadata-events + - Use cases#use-cases + - Tutorials#tutorials + Channel API Types: + - ChannelDetails#channel-details + - ChannelStatus#channel-status + - Occupancy#occupancy +--- + +h2(#overview). Overview + +The @Channel Metadata API@ allows for information about channels, such as the current state changes of a channel, or the changes in its occupancy, to be accessed. This is enabled through three primary mechanisms: + +* "Channel lifecycle events":#lifecycle-events are triggered when a channel changes its "lifecycle state":#lifecycle-events. +* "Channel Occupancy events":#occupancy are sent periodically whenever there is a change in the occupancy of a channel, indicating how many of each type of occupant i.e. publishers, subscribers, presence members or connections there are. +* The "Channel Status API":/rest/channel-status allows for users to query a one-off request with regards to status and occupancy data of channels using a REST query. In addition to that, it also allows you "enumerate":/rest/channel-status#enumeration-rest all the active channels in a particular app. The details of the Channel Status API are covered in out "REST library documentation":/rest/channel-status. + +In addition, note that since the metadata of various channels is prone to change very frequently, unless you have a special use case within your app, we recommend you to subscribe to the realtime events via the "Channel Metadata API":/realtime/channel-metadata rather than poll for updates via REST, as this is inefficient and data is still likely to become stale as soon as you have received it. + +h2(#metachannels). Metachannels + +Metachannels are a namespace of channels which all start with the @[meta]@ qualifier, uniquely identifying them from regular channels. An example of a metachannel would be @[meta]channel.lifecycle@. + +There are a number of metachannels available, which are: + +- [meta]log := This metachannel is used to broadcast log messages (usually error messages) for events that occur within the application's context +- [meta]channel.lifecycle := This metachannel carries messages about channel lifecycle and metadata +- [meta]connection.lifecycle := This metachannel carries messages about the lifecycle of realtime connections + +All of the metadata associated with an app or a channel is available on one of these metachannels only. + +h2(#permissions). Permissions + +A regular Ably key has a "capability":/core-features/authentication#capabilities-explained which lists available resources and, for any given resource, a set of permitted operations. The wildcard resource '@*@' will match any regular channel name. + +In order to grant permission to a user to access a "metachannel":#metachannels, however, the resource name(s) in the @capability@ must include the @[meta]@ qualifier explicitly. If you are using an API Key, you can "set up capabilities in your dashboard":https://support.ably.io/solution/articles/3000073773. + +If you are making use of "tokens":/core-features/authentication#token-authentication, you specify it within the token. The following are examples of capabilities that will validly permit access to a metachannel with tokens: + +minimize. See an example of a capability allowing subscription to all metachannels + ```[json] + {"[meta]*":["subscribe"]} + ``` + + The above will allow for the key to @subscribe@ to any @meta@ channel. The wildcard @*@ indicates anything can follow the @[meta]@ claim, so an example of a valid channel would be @[meta]log@. However, this capability will not allow for any other actions to be performed on the metachannels, nor will it allow the key to do anything with any non-metachannels. + +minimize. See an example of a capability allowing all permissible actions on all metachannels and all regular channels + ```[json] + { + "[meta]*":["*"], + "*":["*"] + } + ``` + + The above permission provides two capabilities; the ability to perform any action on any metachannel (such as @[meta]log@) with @"[meta]*":["*"]@, and the ability to perform any action on any channel (such as @another:channel@) with @"*":["*"]@. However, you are *never* able to publish or be present in a metachannel, thus this permission in effect would result in an actual permission excluding publish and presence capabilities in @[meta]@ channels due to the "intersecting capabilities":/core-features/authentication#capabilities-explained-intersection. + +If @[meta]@ is not specified in the permissions, you will be unable to access the metachannels however. An example of this would be the following: + +minimize. See an example of a capability which is unable to use metachannels + ```[json] + { + "*":["*"] + } + ``` + + Although the above provide all capabilities in all regular channel, without a @[meta]@ permission being explicitly specified, you will be unable to perform any actions on a @[meta]@ channel. + +h2(#subscribing-to-metadata-events). Subscribing to realtime events via the Channel Metadata API + +Both the "channel lifecycle":#lifecycle-events and the "occupancy":#occupancy events are published onto a special channel referred to as a "metachannel":#metachannels bearing the name @[meta]channel.lifecycle@, that can be subscribed to with our "realtime libraries":/realtime. This is often used in conjunction with the "Ably Reactor":/reactor, which allows you to send this data to your "serverless endpoints":/general/events, "queues":/general/queues, and "streams":/general/firehose. + +h3(#lifecycle-events). Lifecycle Events + +The following events arise as messages on the @[meta]channel.lifecycle@ channel. In all cases, the @data@ member of the message is a "ChannelDetails":#channel-details instance. + +- channel.opened := indicates that the channel has been activated globally; that is, it has become active in at least one region, having previously been inactive. The included ChannelDetails.status includes "occupancy":#occupancy globally +- channel.closed := indicates that the channel has been deactivated globally +- channel.region.active := indicates that the channel has been activated in a specific region. The included @ChannelDetails.status@ includes "occupancy":#occupancy in that region +- channel.region.inactive := indicates that the channel has been deactivated in a specific region. The included @ChannelDetails.status@ includes "occupancy":#occupancy in that region +- channel.occupancy := indicates a change in global "occupancy":#occupancy on the channel. Not all "occupancy":#occupancy changes are sent; there is a minimum interval on a per-channel basis, so if the "occupancy":#occupancy is changing continuously, then only periodic snapshots of occupancy are sent. Further roll-up of events may occur depending on the capacity of the lifecycle channel. Any "occupancy":#occupancy changes in which any of the "occupancy":#occupancy categories changes from 0 to a non-zero count, or vice versa, are always included. This feature is only available to our "enterprise customers":https://www.ably.io/pricing/enterprise. + +h3(#occupancy-events). channel.occupancy lifecycle events + +When "channel.occupancy events are enabled":https://support.ably.io/support/solutions/articles/3000088326 for your app, you can subscribe to channel "occupancy":#occupancy events by attaching to the channel @[meta]channel.lifecycle@ using a realtime connection. However, please bear in mind that "realtime connections and channels have rate limits":https://support.ably.io/solution/articles/3000053845 based on your package size. If the rate of channel lifecycle events exceeds this, a realtime connection is not going to be a reliable way to consume these events as you may get disconnected or lifecycle events may be discarded. + +If you wish to stream this data beyond the limit of a single subscriber connection, you can make use of "Ably Reactor":/reactor with a "Reactor Rule":https://support.ably.io/solution/articles/3000074406. This allows you to react to lifecycle events and trigger "serverless functions":/general/events, "deliver messages to queues":/general/queues, or "stream data to third party services":/general/firehose for realtime processing. + +h3(#reactor). Reactor rules + +You can associate "Reactor rules":https://www.ably.io/reactor with metachannels in the same way as for regular channels; configure these using the "Message":/realtime/messages source and use a @channelFilter@ of, for example, @^\[meta\]log@. Normally the @channelFilter@ regex will only match normal, non-meta channels; however, a regex of the form @^\[qualifier\]pattern@ is special-cased to match channels with that qualifier. The @pattern@ is a regex as normal. + +If you want webhooks for channel lifecycle events, creating a "channel lifecycle webhook":/general/events#sources is an easy alternative to creating a message rule for the @[meta]channel.lifecycle@ metachannel; such webhooks will have simpler POST bodies as the lifecycle events won't be wrapped in "@Message@":/realtime/types#message objects. + +h2(#use-cases). Use cases + +Having access to channel metadata can provide numerous benefits. In a scenario where the number of subscribers of a channel goes well beyond a hundred, usage of other options such as "presence":/core-features/presence becomes less effective leading to an unexpected n-squared problem if all of the clients are subscribed to presence. You could instead make use of our "channel metadata":#metachannels to check the number of active subscribers. + +Equally, you may want to publish your data only if there is a subscriber for that data. The "channel lifecycle events":#lifecycle-events can notify you when a channel is opened, becomes active, or is no longer active thus giving your publisher clients an opportunity to know when the last subscriber leaves the channel. + +If you need to be able to query channel metadata at any point, you can make use of the "Channel Status API":/rest/channel-status#metadata-rest to inspect the state of individual channels, or "enumerate":/rest/channel-status#enumeration-rest all active channels in an app. + +h2(#tutorials). Tutorials + +If you wish to see an example of how to use channel metadata API, you can check out our "Channel Lifecycle Events":/tutorials/channel-lifecycle-events tutorial, "Channel Occupancy Events":/tutorials/channel-occupancy-events tutorial, or the "Channel Enumeration":/tutorials/channel-enumeration-rest tutorial. + +h1. Channel Metadata API Reference + +inline-toc. + Types: + - ChannelDetails#channel-details + - ChannelStatus#channel-status + - Occupancy#occupancy + +h2(#types). Types + +The payload of metadata events for channels is the "@ChannelDetails@":#channel-details type which contains the @channelId@ and other static information about the channel, plus a @status@ containing a "@ChannelStatus@":#channel-status instance which contains information about the current state of the channel. + +<%= partial partial_version('types/_channel_details') %> diff --git a/content/realtime/versions/v1.1/channel-params.textile b/content/realtime/versions/v1.1/channel-params.textile new file mode 100644 index 0000000000..75cf596cc1 --- /dev/null +++ b/content/realtime/versions/v1.1/channel-params.textile @@ -0,0 +1,141 @@ +--- +title: Channel Parameters +section: realtime +index: 32 +jump_to: + Help with: + - Overview#overview + - Using params with v1.1 or earlier Ably libraries#using-params-with-lib-v11 + - Using params with non-Ably transports#using-params-with-other-transports + - Examples#examples + Parameters: + - rewind#rewind +--- + +h2(#overview). Overview + +Channel parameters are a general mechanism by which a client can express properties of a channel, or of its attachment to a channel. A set of channel parameters is simply a set of key/value pairs, where both keys and values are strings; the keys correspond to specific features that Ably defines. + +The methods provided for specifying channel parameters, and the currently available features, are outlined below. + +h2(#supported-params). Currently supported channel params + +- rewind := Used to request that an attachment start from some number of messages or point in time in the past. See "rewind":#rewind for more information +- delta := **In an experimental state**. Used to request that data payloads should be sent as deltas to the previous payload. "Contact us":https://www.ably.io/contact for more information and supported values + +h2(#using-params-with-lib-v11). Using channel params with v1.1 or earlier Ably client libraries + +The current Ably libraries, at version 1.1, do not expose an API for expressing channel parameters. This means that it is necessary to specify parameters in a way that is opaque to the library. + +A set of params is expressed by including a query string, using standard URL query syntax and encoding, within the qualifier part of a channel name. The qualifier part is in square brackets at the start of the channel name. + +For example, to specify the parameter @foo@ with value @bar@ on channel @baz@, the qualified channel name would be @[?foo=bar]baz@. If the channel name already has a qualifier, such as @[meta]log@, then the query string follows the existing qualifier, as in @[meta?foo=bar]log@. + +Using this syntax with the Ably library means that channel parameters are specified for the lifetime of the @Channel@ instance; in order to reference the same channel, but with different channel parameters, it is necessary to get a new @Channel@ instance, using a qualified name that includes the new channel parameters. + +h3. Example + +For example, to specify the @rewind@ channel param with the value @"1"@: + +```[javascript] + const realtime = new Ably.Realtime('{{API_KEY}}'); + const channel = realtime.channels.get('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); +``` + +h2(#using-params-with-other-transports). Using params with non-Ably transports + +It is possible to interact with Ably channels using transports that do not involve using an Ably library; for example using SSE without any library, or using a supported non-Ably protocol such as MQTT. In these cases, it is also necessary to use a qualified channel name. + +A set of params is expressed by including a query string, using standard URL query syntax and encoding, within the qualifier part of a channel name. The qualifier part is in square brackets at the start of the channel name. + +For example, to specify the parameter @foo@ with value @bar@ on channel @baz@, the qualified channel name would be @[?foo=bar]baz@. If the channel name already has a qualifier, such as @[meta]log@, then the query string follows the existing qualifier, as in @[meta?foo=bar]log@. + +In an SSE connection, it is also possible to specify channel parameters as a query string in the connection URL, instead of as a qualifier on an individual channel name. In this case, the given channel parameters apply to all channel attachments associated with that connection. + +h3. SSE example + +For example, to specify the @rewind@ channel param with the value @"1"@ using a querystring parameter, where it will apply to all channels: + +```[javascript] +var querystring = 'v=1.1&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; +var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); +``` + +Or to specify the same parameter but only applying to one channel of two, using a qualified channel name: + +```[javascript] + var channelOne = encodeURIComponent('[?rewind=1]channel1'); + var channelTwo = 'channel2'; + var channels = channelOne + ',' + channelTwo; + var querystring = 'v=1.1&key={{API_KEY}}&channels=' + channels'; + var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); +``` + +h2(#rewind). Rewind parameter + +The @rewind@ channel parameter relates to the initial attachment of a connection to a channel, and expresses the intent to attach to the channel at a position, or a point in time, in the past (that is, effectively "rewinding" the channel for the purposes of the present attachment). + +A @rewind@ parameter can express a channel position in terms of a number of messages, or a time interval. + +A @rewind@ value that is simply a number @n@ (eg @rewind=1@) is a request to attach to the channel at a position @n@ messages before the present position. If that attachment is successful, and one or more messages exist on the channel prior to the present position, then those messages will be delivered to the subscriber immediately after the attachment has completed, and before any subsequent messages that arise in real time. + +If fewer than the requested number of messages exists on the channel (including the case that there are no prior messages), then the available messages are sent; this does not constitute an error. + +A @rewind@ value can also be a string that is a time interval specifier. Supported specifier values express an integral number of seconds (eg @15s@) or minutes (eg @2m@). If that attachment is successful, and one or more messages exist on the channel in the given time interval prior to the present time, then those messages will be delivered to the subscriber immediately after the attachment has completed, and before any subsequent messages that arise in real time. + +If you wish to use a time interval rewind but additionally specify a limit on the number of messages to be returned, you can use the @rewindLimit@ channel param. For example, to request up to 10 messages in a window 5m before the present time, specify a channel parameter string of @rewind=5m&rewindLimit=10@. If fewer than the requested number of messages exists on the channel in that interval (including the case that there are no messages), then the available messages are sent; this does not constitute an error. + +At most 100 messages will be sent in a rewind request. If the number of messages within the specified interval is greater than that limit, then only the most recent messages up to that limit are sent. The attachment succeeds, but truncation of the message backlog is indicated as a non-fatal error in the attachment response. + +By default, a maximum of two minutes of channel history is available when attaching. This means that a rewind time specifier of greater than two minutes will only be able to rewind by two minutes. If a channel has persistence enabled, then it is possible to rewind back in time by up to the persistence TTL on the channel. + +The channel position expressed by a @rewind@ parameter has an effect only on an initial channel attachment. Any subsequent reattachment of the same channel on the same connection, in order to resume the connection, will attempt to resume with continuity from the point at which the connection dropped. (There are a few exceptions to this: in particular, client libraries earlier than v1.2 that have been disconnected for over two minutes, and all clients when using "@recover@ mode":/realtime/connection#connection-state-recovery ; in both cases the previous attachment state is not preserved). + +Any @rewind@ parameter value that cannot be parsed either as a number or a time specifier represents an error, and any attachment request will fail with an error. + +h3(#rewind-example-ably). Rewind example with an Ably client library + +To subscribe to a channel, getting the most recent message if available: + +```[jsall] + // only with ably-js v1.2 or later; currently in beta + const realtime = new Ably.Realtime('{{API_KEY}}'); + realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', { + rewind: '1' + }).subscribe(msg => console.log("Received message: ", msg)); +``` + +```[jsall] + // with ably-js v1.1 or below + const realtime = new Ably.Realtime('{{API_KEY}}'); + const channel = realtime.channels.get('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); + channel.subscribe(msg => console.log("Received message: ", msg)); +``` + +h3(#rewind-example-sse). Rewind example with SSE + +To subscribe to a channel, getting the most recent message if available: + +```[javascript] + var querystring = 'v=1.1&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; + var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); +``` + +h3(#rewind-examples-mqtt). Rewind example with MQTT + +```[nodejs] + var mqtt = require('mqtt'); + var options = { + keepalive: 30, + username: 'FIRST_HALF_OF_API_KEY', + password: 'SECOND_HALF_OF_API_KEY', + port: 8883 + }; + var client = mqtt.connect('mqtts:mqtt.ably.io', options); + client.on('connect', () => { + client.subscribe('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); + }); + client.on('message', (topic, message) => { + ... + }); +``` diff --git a/content/realtime/versions/v1.1/channels.textile b/content/realtime/versions/v1.1/channels.textile new file mode 100644 index 0000000000..0332e35ed4 --- /dev/null +++ b/content/realtime/versions/v1.1/channels.textile @@ -0,0 +1,1403 @@ +--- +title: Channels +section: realtime +index: 30 +languages: + - javascript + - nodejs + - ruby + - java + - swift + - objc + - csharp +api_separator: +jump_to: + Help with: + - Getting started#getting-started + - Channels#channels + - Subscribing to a channel#subscribing + - Publishing to a channel#publishing + - Channel lifecycle#channel-lifecycle + - Channel metadata#channel-metadata + - Implicit attach#implicit-attach + - Publishing to multiple channels#multi-publish + - Transient Publishing#transient-publish + - Channel states#channel-states + - Handling failures#handling-failures + - Channel namespaces#channel-namespaces + - Presence#presence-api + Channel API properties: + - state + - errorReason#error-reason + - name + - presence + Channel API methods: + - publish + - subscribe + - unsubscribe + - history + - attach + - detach + - on#on + - once + - off#off + Types: + - Related Types#related-types +--- + +Ably DSN service organizes the message traffic within applications into named channels. Channels are the medium through which "messages":/realtime/messages are distributed; clients attach to channels to subscribe to "messages":/realtime/messages, and every "message":/realtime/messages published to a unique channel is broadcast by Ably to all subscribers. 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":/realtime/messages#message-publish and "subscribing":/realtime/messages#message-subscription to messages on a channel. If the channel does not exist at the time the client is attached, a channel 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}}"); + IRealtimeChannel 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 channels, then we recommend you take a look at our "Realtime tutorials":/tutorials. + +h2(#channels). Channels + +In order to "publish":/realtime/messages#message-publish, "subscribe":/realtime/messages#message-subscription to, or be "present":/realtime/presence on a channel, you must first obtain a channel instance and then attach to that channel. In most instances, as a convenience, it is unnecessary to explicitly attach a channel as it will implicitly attached when performing any operation on the channel such as publishing or subscribing. + +h3(#obtaining-channel). Obtaining a channel instance + +A @Channel@ object is a reference to a single channel. A channel instance is obtained from the "@channels@ collection":/realtime/usage/#channels of the @Realtime::Client@@Realtime@@ARTRealtime@@AblyRealtime@ instance, and is uniquely identified by its unicode string name. Find out more about "channel naming":https://support.ably.io/solution/articles/3000046824-what-restrictions-exist-for-the-name-field-of-a-channel + +bc[jsall](code-editor:realtime/channel). var channel = realtime.channels.get('channelName'); + +bc[java]. Channel channel = realtime.channels.get("channelName"); + +bc[csharp]. IRealtimeChannel channel = realtime.Channels.Get("channelName"); + +bc[ruby]. channel = realtime.channels.get('channelName') + +bc[objc]. ARTRealtimeChannel *channel = [realtime.channels get:@"channelName"]; + +bc[swift]. let channel = realtime.channels.get("channelName") + +h4(#setting-channel). Setting channel options and encryption + +A set of "channel options":#channel-options may also be passed to configure a channel for encryption. Find out more about "symmetric message encryption":/realtime/encryption. + +bc[jsall]. Ably.Realtime.Crypto.generateRandomKey(function(err, key) { + var options = { cipher: { key: key } }; + var channel = realtime.channels.get('channelName', options); +}); + +bc[java]. CipherParams params = Crypto.getDefaultParams(key); +ChannelOptions options = new ChannelOptions(); +options.encrypted = true; +options.cipherParams = params; +Channel channel = realtime.channels.get("channelName", options); + +bc[csharp]. byte[] key = Crypto.GenerateRandomKey(); +CipherParams cipherParams = Crypto.GetDefaultParams(key); +ChannelOptions channelOpts = new ChannelOptions(cipherParams); +IRealtimeChannel encryptedChannel = realtime.Channels.Get("channelName", channelOpts); + +bc[ruby]. key = Ably::Util::Crypto.generate_random_key +options = { cipher: { key: key } } +channel = realtime.channels.get('channelName', options) + +bc[objc]. NSData *key = [ARTCrypto generateRandomKey]; +ARTChannelOptions *options = [[ARTChannelOptions alloc] initWithCipherKey:key]; +ARTRealtimeChannel *channel = [realtime.channels get:@"channelName" options:options]; + +bc[swift]. let key = ARTCrypto.generateRandomKey() +let options = ARTChannelOptions(cipherKey: key) +let channel = realtime.channels.get("channelName", options: options) + +h3(#subscribing). Subscribing to a channel + +To subscribe to a channel, use the "subscribe":#subscribe method of a channel: + +```[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); + }); +``` + +```[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); + }); +``` + +```[ruby] + realtime = Ably::Realtime.new('{{API_KEY}}') + channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}') + channel.subscribe do |message| + puts "Received: #{message.data}" + end +``` + +```[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); + } + }); +``` + +```[csharp] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + IRealtimeChannel channel = realtime.Channels.Get("{{RANDOM_CHANNEL_NAME}}"); + channel.Subscribe(message => { + Console.WriteLine($"Message: {message.Name}:{message.Data} received"); + }); +``` + +```[objc] +ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"]; +ARTRealtimeChannel *channel = [realtime.channels get:@"{{RANDOM_CHANNEL_NAME}}"]; +[channel subscribe:^(ARTMessage *message) { + NSLog(@"Received: %@", 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)") +} +``` + +h4(#rewind). Subscribing to a channel with the rewind option enabled + +It is possible to subscribe to a channel and obtain messages from that channel's "history":/realtime/history with a single API request. To do so you would use the "rewind":/realtime/channel-params#rewind feature. This will result in the specified number of messages from "history":/realtime/history being returned as part of the subscription process. + +minimize. View details + "Rewind":/realtime/channel-params has two methods of obtaining messages from history. You can retrieve a specified number of messages from history or messages from a set period of time into the past. + + In current Ably libraries, or when using the "service without a library":https://www.ably.io/adapters, this is done by qualifying the channel name. There is a future library release planned in which it will be possible to specify channel params directly via the API. As an example, if you have no metadata and wish to subscribe to channel @my_channel@ and fetch the most recent message from "history":/realtime/history, you would specify the channel as @[?rewind=1]my_channel@. If the channel had some metadata, @[some_metadata]my_channel@, you would apply "rewind":/realtime/channel-params with @[some_metadata?rewind=1]my_channel@. For more details, look at our "channel parameter":/realtime/channel-params documentation. + + *Note* that "rewind":/realtime/channel-params is limited to a maximum of @100 messages@, and can only get messages no older than 2 minutes by default. If a channel has persistence enabled, then it is possible to rewind back in time by up to the persistence TTL on the channel. + + In addition, rewind will only apply upon *attaching* to a channel, so any subsequent subscriptions post-attach will not fetch old messages. + + *Rewind by number of messages* + + To rewind by a set number of messages, add @?rewind=NUM_MSG@ to your metadata, where @NUM_MSG@ is the number of messages you wish to get at most. For example, to obtain 1 message from history as you subscribe, you would do the following: + + ```[javascript](code-editor:realtime/rewind) + var realtime = new Ably.Realtime('{{API_KEY}}'); + var channel = realtime.channels.get('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); + channel.subscribe(function(message) { + alert('Received: ' + message.data); + }); + ``` + + ```[nodejs](code-editor:realtime/rewind) + var Ably = require('ably'); + var realtime = new Ably.Realtime('{{API_KEY}}'); + var channel = realtime.channels.get('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); + channel.subscribe(function(message) { + console.log("Received: " + message.data); + }); + ``` + + ```[ruby] + realtime = Ably::Realtime.new('{{API_KEY}}') + channel = realtime.channels.get('[?rewind=1]{{RANDOM_CHANNEL_NAME}}') + channel.subscribe do |message| + puts "Received: #{message.data}" + end + ``` + + ```[java] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + Channel channel = realtime.channels.get("[?rewind=1]{{RANDOM_CHANNEL_NAME}}"); + channel.subscribe(new MessageListener() { + @Override + public void onMessage(Message message) { + System.out.println("New messages arrived. " + message.name); + } + }); + ``` + + ```[csharp] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + IRealtimeChannel channel = realtime.Channels.Get("[?rewind=1]{{RANDOM_CHANNEL_NAME}}"); + channel.Subscribe(message => { + Console.WriteLine($"Message: {message.Name}:{message.Data} received"); + }); + ``` + + ```[objc] + ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"]; + ARTRealtimeChannel *channel = [realtime.channels get:@"[?rewind=1]{{RANDOM_CHANNEL_NAME}}"]; + [channel subscribe:^(ARTMessage *message) { + NSLog(@"Received: %@", message.data); + }]; + ``` + + ```[swift] + let realtime = ARTRealtime(key: "{{API_KEY}}") + let channel = realtime.channels.get("[?rewind=1]{{RANDOM_CHANNEL_NAME}}") + channel.subscribe { message in + print("Received: \(message.data)") + } + ``` + + *Rewind by period of time* + + To rewind by a time interval, add @?rewind=TIME@ to your metadata, where @TIME@ is the time specifier. For example, to obtain 10 seconds of messages from history as you subscribe, you would do the following: + + ```[javascript](code-editor:realtime/rewind) + var realtime = new Ably.Realtime('{{API_KEY}}'); + var channel = realtime.channels.get('[?rewind=10s]{{RANDOM_CHANNEL_NAME}}'); + channel.subscribe(function(message) { + alert('Received: ' + message.data); + }); + ``` + + ```[nodejs](code-editor:realtime/rewind) + var Ably = require('ably'); + var realtime = new Ably.Realtime('{{API_KEY}}'); + var channel = realtime.channels.get('[?rewind=10s]{{RANDOM_CHANNEL_NAME}}'); + channel.subscribe(function(message) { + console.log("Received: " + message.data); + }); + ``` + + ```[ruby] + realtime = Ably::Realtime.new('{{API_KEY}}') + channel = realtime.channels.get('[?rewind=10s]{{RANDOM_CHANNEL_NAME}}') + channel.subscribe do |message| + puts "Received: #{message.data}" + end + ``` + + ```[java] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + Channel channel = realtime.channels.get("[?rewind=10s]{{RANDOM_CHANNEL_NAME}}"); + channel.subscribe(new MessageListener() { + @Override + public void onMessage(Message message) { + System.out.println("New messages arrived. " + message.name); + } + }); + ``` + + ```[csharp] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + IRealtimeChannel channel = realtime.Channels.Get("[?rewind=10s]{{RANDOM_CHANNEL_NAME}}"); + channel.Subscribe(message => { + Console.WriteLine($"Message: {message.Name}:{message.Data} received"); + }); + ``` + + ```[objc] + ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"]; + ARTRealtimeChannel *channel = [realtime.channels get:@"[?rewind=1]{{RANDOM_CHANNEL_NAME}}"]; + [channel subscribe:^(ARTMessage *message) { + NSLog(@"Received: %@", message.data); + }]; + ``` + + ```[swift] + let realtime = ARTRealtime(key: "{{API_KEY}}") + let channel = realtime.channels.get("[?rewind=10s]{{RANDOM_CHANNEL_NAME}}") + channel.subscribe { message in + print("Received: \(message.data)") + } + ``` + + *Note* that currently only seconds (@10s@) and minutes (@2m@) are supported as time periods. + +h3(#publishing). Publishing on a channel + +In order to publish on a channel, you simply need to make use of the "publish":#publish method: + +```[javascript](code-editor:realtime/channel-publish) + var realtime = new Ably.Realtime('{{API_KEY}}'); + var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}'); + 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.publish("example", "message data"); +``` + +```[ruby] + realtime = Ably::Realtime.new('{{API_KEY}}') + channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}') + channel.publish 'example', 'message data' +``` + +```[java] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + Channel channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}"); + channel.publish("example", "message data"); +``` + +```[csharp] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + IRealtimeChannel channel = realtime.Channels.Get("{{RANDOM_CHANNEL_NAME}}"); + channel.Publish("example", "message data"); +``` + +```[objc] +ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"]; +ARTRealtimeChannel *channel = [realtime.channels get:@"{{RANDOM_CHANNEL_NAME}}"]; +[channel publish:@"example" data:@"message data"]; +``` + +```[swift] +let realtime = ARTRealtime(key: "{{API_KEY}}") +let channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}") +channel.publish("example", data: "message data") +``` + +h3(#channel-lifecycle). Channel lifecycle + +Channels are not pre-configured or provisioned by Ably in advance; they are created on demand when clients attach, and remain active until such time that there are no remaining attached clients. Within the "dashboard for your app":https://support.ably.io/solution/articles/3000030053 however, you can pre-configure one or more "channel namespaces":#channel-namespaces (i.e. name prefixes), and associate different attributes and access rights with those namespaces. Find out more about "channel namespaces":#channel-namespaces. + +The following example explicitly attaches to a channel, which results in the channel being provisioned in Ably's global realtime cluster. This channel will remain available globally until there are no more clients attached to the channel: + +bc[jsall]. realtime.channels.get('chatroom').attach(function(err) { + console.log('"chatroom" exists and is now available globally in every datacenter'); +}); + +bc[ruby]. realtime.channels.get('chatroom').attach do |channel| + puts "'chatroom' exists and is now available globally in every datacenter" +end + +bc[java]. Channel channel = realtime.channels.get("chatroom"); +channel.on(new ChannelStateListener() { + @Override + public void onChannelStateChanged(ChannelStateChange state) { + switch (state.current) { + case attached: { + System.out.println("'chatroom' exists and is now available globally"); + } + } + } +}); + +bc[csharp]. IRealtimeChannel channel = realtime.Channels.Get("chatroom"); +channel.Attach((success, error) => { + Console.WriteLine("'chatroom' exists and is now available globally"); +}); + +bc[objc]. [[realtime.channels get:@"chatroom" options:options] attach:^(ARTErrorInfo *error) { + NSLog(@"'chatroom' exists and is now available globally in every datacenter"); +}]; + +bc[swift]. realtime.channels.get("chatroom").attach { error in + print("'chatroom' exists and is now available globally in every datacenter") +} + +Clients attach to a channel in order to participate on that channel in any way (either to publish, subscribe, or be present on the channel). It is worth noting that some libraries allow for "publishing to a channel without attaching to it":#transient-publish. "See if your chosen SDK supports this":https://www.ably.io/download. + +h3(#channel-metadata). Channel metadata + +Ably provides a "REST API":/realtime/channel-metadata to query your app for metadata about channels, as well as a "realtime API":/realtime/channel-metadata to subscribe to channel lifecycle events. Using the "REST API":/rest-api, you can enumerate all active channels, or obtain the status of an individual channel. Using our Realtime API, you can subscribe to "channel lifecycle events":/realtime/channel-metadata#lifecycle-events (such as being created or closed etc), or subscribe to periodic "occupancy":/realtime/channel-metadata#occupancy-rest updates for all active channels (such as how many people are subscribed to a channel). + +h3(#implicit-attach). Implicit attach + +Although the attach operation can be initiated explicitly by a client, it is more common for the client to subscribe, which will initiate the attach if the channel is not already attached. This client library allows clients to begin publishing "messages":/realtime/messages without attaching to the channel with "transient publishing":#transient-publish. + +bc[jsall](code-editor:realtime/channel-implicit). var channel = realtime.channels.get('chatroom'); +channel.subscribe('action', function(message) { // implicit attach + console.log('Message received '' + message.data); +}); +channel.publish('action', 'boom!'); + +bc[ruby]. channel = realtime.channels.get('chatroom') +channel.subscribe('action') do |message| # implicit attach + puts "Message received: #{message}"; +end +channel.publish 'action', 'boom!' + +bc[java]. Channel channel = realtime.channels.get("chatroom"); +/* Implicit attach when subscribing */ +channel.subscribe(new MessageListener() { + @Override + public void onMessage(Message message) { + System.out.println("Message received: " + message.data); + } +}); +channel.publish("action", "boom!"); + +bc[csharp]. IRealtimeChannel channel = realtime.Channels.Get("chatroom"); +channel.Subscribe(message => Console.WriteLine("Message received: " + message.Data)); +channel.Publish("action", "boom"); + +bc[objc]. ARTRealtimeChannel *channel = [realtime.channels get:@"chatroom" options:options]; +[channel subscribe:@"action" callback:^(ARTMessage *message) { + NSLog(@"Message received: %@", message.data); +}] +[channel publish:@"action" data:@"boom!"]; + +bc[swift]. let channel = realtime.channels.get("chatroom") +channel.subscribe("action") { message in + print("Message received: \(message.data)") +} +channel.publish("action", data: "boom!") + +Normally, errors in attaching to a channel are communicated through the attach callback. For implicit attaches (and other cases where a channel is attached or reattached automatically, e.g. following the library reconnecting after a period in the @suspended@ state), there is no callback, so if you want to know what happens, you'll need to listen for channel state changes. + +h3(#multi-publish). Publishing to multiple channels + +Often it is necessary to publish a single message in multiple channels at the same time. In the realtime API, this is achieved simply by making multiple separate "publish":#publish requests. If a separate publish is made in each of the channels in question, the realtime protocol will allow for those concurrent requests to be in-flight simultaneously. This ensures that a publish on a channel is not delayed waiting for completion of operations in other channels. + +It is also possible to publish one or more messages into multiple channels in a single operation using the "REST batch API":/rest-api/beta#batch. + +h3(#transient-publish). Transient publishing + +When attempting to only publish to a client, it is possible to publish without attaching to the channel. This can be beneficial if you intend to publish into many channels, removing the need to attach to a channel each time you wish to publish. Additionally, it avoids the client subscribing to messages, avoiding messages being sent to it redundantly. + +*Note* that transient publishing is only available for "certain libraries":https://www.ably.io/download/sdk-feature-support-matrix, otherwise publishing will also attach you to the channel. + +bc[jsall]. var channel = realtime.channels.get('chatroom'); +// The publish below will not attach you to the channel +channel.publish('action', 'boom!'); + +bc[ruby]. channel = realtime.channels.get('chatroom') +# The publish below will not attach you to the channel +channel.publish 'action', 'boom!' + +bc[swift]. let channel = realtime.channels.get("chatroom") +// The publish below will not attach you to the channel +channel.publish("action", data: "boom!") + +h3(#channel-states). Channel states + +A channel can exist in any of the following states: + +- initializedInitialized := A @Channel@ object having this state has been initialized but no attach has yet been attempted + +- attachingAttaching := An attach has been initiated by sending a request to Ably. This is a transient state; it will be followed either by a transition to attached, suspended, or failed + +- attachedAttached := Attach has succeeded. In the attached state a client may publish and subscribe to messages, or be present + +- detachingDetaching := A detach has been initiated on the attached @Channel@ by sending a request to Ably. This is a transient state; it will be followed either by a transition to detached or failed + +- detachedDetached := The @Channel@, having previously been attached, has been detached by the user + +- suspendedSuspended := The @Channel@, having previously been attached, has lost continuity, usually due to the client being disconnected from Ably for more than two minutes. It will automatically attempt to reattach as soon as connectivity is restored + +- failedFailed := An indefinite failure condition. This state is entered if a @Channel@ error has been received from the Ably service (such as an attempt to attach without the necessary access rights) + +h3(#listening-state). Listening for state changes + +The @Channel@ object is an @EventEmitter@ and emits an event whose name is the new state whenever there is a channel state change. Listeners are passed a "ChannelStateChange":#channel-state-change object in the first argument. This contains the @current@ and @previous@ states, a @resumed@ flag which indicated whether message continuity on this channel is preserved (from the last time that the channel was attached), and (sometimes) a @reason@ for the state change. As with all events from an @EventEmitter@ in the Ably library, @this@ within the listener function is a reference to an event object whose @event@ property is the name of the event that fired. This allows a listener to listen for all events with a single registration and know, which event fired when it is entered. + +The @Channel@ object can also emit one event that is not a state change: an @update@ event. This happens when there's a change to channel conditions for which the channel state doesn't change. For example, a partial loss of message continuity on a channel (typically after a resume) for which the channel state remains @attached@ would lead to an @update@ event being emitted, with both @current@ and @previous@ set to "@attached@", and the @resumed@ flag set to @false@. So if you get such an event, you'll know there may be messages you've missed on the channel, and if necessary you can use the "History":#history api to retrieve them. + +bc[jsall]. channel.on('attached', function(stateChange) { + console.log('channel ' + channel.name + ' is now attached'); + console.log('Message continuity on this channel ' + \ + (stateChange.resumed ? 'was' : 'was not') + ' preserved'); +}); + +bc[ruby]. channel.on(:attached) do |channel_state_change| + puts "channel #{channel.name} is now attached" + puts "Message continuity #{channel_state_change.resumed ? 'was' : 'was not'} preserved" +end + +bc[java]. channel.on(ChannelEvent.attached, new ChannelStateListener() { + @Override + public void onChannelStateChanged(ChannelStateChange stateChange) { + System.out.println("channel " + channel.name + " is now attached"); + if (stateChange.resumed) { + System.out.println("Message continuity was preserved"); + } else { + System.out.println("Message continuity was not preserved"); + } + } +}); + +bc[csharp]. IRealtimeChannel channel = realtime.Channels.Get("chatroom"); +channel.On(ChannelEvent.Attached, stateChange => { + Console.WriteLine("channel " + channel.Name + " is now attached"); + if (stateChange.resumed) { + Console.WriteLine("Message continuity was preserved"); + } else { + Console.WriteLine("Message continuity was not preserved"); + } +}); + +bc[objc]. [channel on:ARTChannelEventAttached call:^(ARTChannelStateChange *stateChange) { + NSLog(@"channel %@ is now attached", channel.name); + if (stateChange.resumed) { + NSLog(@"Message continuity was preserved"); + } else { + NSLog(@"Message continuity was not preserved"); + } +}]; + +bc[swift]. channel.on(.attached) { stateChange in + print("channel \(channel.name) is now attached") + if (stateChange.resumed) { + print("Message continuity was preserved") + } else { + print("Message continuity was not preserved") + } +} + +Alternatively a listener may be registered so that it receives all state change events. + +bc[jsall](code-editor:realtime/channel-states). var myListener = function(stateChange) { + console.log('channel state is ' + stateChange.current); + console.log('previous state was ' + stateChange.previous); + if(stateChange.reason) { + console.log('the reason for the state change was: ' + stateChange.reason.toString()); + } +}); +channel.on(myListener); + +bc[ruby]. channel.on do |channel_state_change| + puts "channel state is #{channel_state_change.current}" +end + +bc[java]. channel.on(new ChannelStateListener() { + @Override + public void onChannelStateChanged(ChannelStateChange stateChange, ErrorInfo reason) { + System.out.println("channel state is " + ChannelState.values()[stateChange.current]); + } +}); + +bc[csharp]. channel.On(stateChange => Console.WriteLine("channel state is " + stateChange.Current)); + +bc[objc]. ARTEventListener *listener = [channel on:^(ARTChannelStateChange *stateChange) { + NSLog(@"channel state is %@", stateChange.current); +}]; + +bc[swift]. let listener = channel.on { stateChange in + print("channel state is \(stateChange.current)") +} + +Previously registered listeners can be removed individually or all together. + +```[jsall] + /* remove the listener registered for a single event */ + channel.off('attached', myListener); + + /* remove the listener registered for all events */ + channel.off(myListener); +``` + +```[ruby] + # remove the listener proc registered for a single event + channel.off(:attached, &my_proc) + + # remove the listener proc registered for all events + channel.off(&my_proc) +``` + +```[java] + /* remove the listener registered for a single event */ + channel.off(ChannelEvent.attached, channelStateListener); + + /* remove the listener registered for all events */ + channel.off(channelStateListener); +``` + +```[csharp] + // remove the listener registered for a single event + channel.Off(ChannelEvent.Attached, channelStateListener); + + // remove the listener registered for all events + channel.Off(channelStateListener); +``` + +```[objc] + // remove the listener registered for a single event + [channel off:ARTChannelEventAttached listener:listener]; + + // remove the listener registered for all events + [channel off:listener]; +``` + +```[swift] + // remove the listener registered for a single event + channel.off(.attached, listener: listener) + + // remove the listener registered for all events + channel.off(listener) +``` + +h3(#handling-failures). Handling channel failures + +Channel attach and detach operations are asynchronous - after initiating an attach request, the client will wait for a response from Ably that confirms that the channel is established on the service and then trigger a state change event. + +The client libraries will attempt to automatically recover from non-fatal error conditions. However, you can handle them yourself if you prefer by subscribing to channel state changes, or using the callbacks availablewaiting for a result when explicitly calling @attach@. + +bc[jsall]. realtime.channels.get('private:chatroom').attach(function(err) { + if (err) { + console.error('Attach failed: ' + err); + } +}); + +bc[ruby]. deferrable = realtime.channels.get('private:chatroom').attach +deferrable.errback do |error| + puts "Attach failed: #{error}" +end + +bc[java]. Channel channel = realtime.channels.get("private:chatroom"); +channel.on(new ChannelStateListener() { + @Override + public void onChannelStateChanged(ChannelStateChange stateChange, ErrorInfo reason) { + switch (stateChange.current) { + case failed: { + System.out.println("Attach failed: " + reason.message); + } + } + } +}); +channel.attach(); + +bc[csharp]. IRealtimeChannel privateChannel = realtime.Channels.Get("private:chatroom"); +privateChannel.Attach((_, error) => { + if (error != null) + { + Console.WriteLine("Attach failed: " + error.Message); + } +}); + +bc[objc]. [[realtime.channels get:@"private:chatroom"] attach:^(ARTErrorInfo *error) { + if (error) { + NSLog(@"Attach failed: %@", error); + } +}]; + +bc[swift]. realtime.channels.get("private:chatroom").attach { error in + if let error = error { + print("Attach failed: \(error)") + } +} + +h4(#fatal-errors). Fatal channel errors + +Some classes of errors are fatal. These cause the channel to move to the @FAILED@ state. The client library will not attempt any automatic recovery actions. For example: attempting to attach to a channel that the token you're using does not have the @subscribe@ capability for will cause that channel to enter the @FAILED@ state. + +Note that while fatal errors won't get better on their own, they can be fixable. For example, if a channel goes into the @FAILED@ state due to the client not having the right capabilities to attach to it, that client could use "@Auth#authorize()@":/realtime/authentication#authorize to obtain a new token which does have the right capabilities, then call "@attach()@":#attach on the channel. While the library will not automatically reattach in the @FAILED@ state, explicit calls to "@attach()@":#attach will make the client try again. + +h4(#nonfatal-errors). Nonfatal errors + +Other types of errors are nonfatal. For example, a client may have network connectivity issues, or a channel may experience a loss of strict message continuity. The library will automatically attempt to recover from these events. If channel continuity is lost in the process, the library will notify you though a @resumed@ flag in the @ATTACHED@ or @UPDATE@ event, so you can decide how to handle that failure. + +For every channel @ATTACHED@ and @UPDATE@ event, the "@ChannelStateChange@ object":/realtime/types/#channel-state-change contains a @resumed@ attribute. When true, there has been no loss of continuity from the last time the channel was attached. When false, there has been a loss of continuity. So for example: + +* The first time you attach to a channel on a fresh connection, @resumed@ will be false, as there was nothing to continue from. +* If you successfully "recover":/realtime/connection#connection-state-recovery a connection and reattach to your channels, the @resumed@ flag on the @ATTACHED@ events will tell you whether messages continuity was preserved. Any channel for which it's @true@ is guaranteed to receive every message it missed while the client was disconnected. +* If you "resume or recover":/realtime/connection#connection-state-recovery a connection unsuccessfully (so you get a fresh connection) — generally because you were disconnected for more than two minutes, which is how long Ably holds connection state for — continuity is lost. If you were resuming, all your channels (which will have gone into the @SUSPENDED@ state after two minutes) will still reattach automatically, and you will get @ATTACHED@ events with @resumed@ false. +* If Ably needs to signal a loss of message continuity on an attached channel (for example, during a partially successful resume where the client was disconnected for less than two minutes), you will get an @UPDATE@ event with @resumed@ false. + +h4(#connection-state-change). Connection state change side effects on channels + +* If the connection state becomes @CLOSED@, all channels will become @DETACHED@ +* If the connection state becomes @FAILED@, all channels will become @FAILED@ +* If the connection state becomes @SUSPENDED@, all previously-@ATTACHED@ or @ATTACHING@ channels will become @SUSPENDED@ +* If the connection state becomes @CONNECTED@, any channels that were @SUSPENDED@ will be automatically reattached + +h3(#channel-namespaces). Channel namespaces + +<%= partial partial_version('shared/_channel_namespaces') %> + +h2(#presence-api). Presence + +Channels expose a @presence@@Presence@ member which a client can use to obtain channel presence information and to enter and leave the presence channel itself. See the "presence documentation":/realtime/presence for details. + +h1. API Reference + +inline-toc. + Channels Methods: + - get#get + Channel Properties: + - state#state + - errorReason#error-reason + - name#name + - presence#presence + - push#push + Channel Methods: + - publish#publish + - subscribe#subscribe + - unsubscribe#unsubscribe + - history(options)#history + - attach()#attach + - detach()#detach + - on#on-state-listener + - once#once-state-listener + - off#off-state-listener + Related types: + - ChannelState#channel-state + - ChannelEvent#channel-event + - ChannelStateChange#channel-state-change + - Message#message + - ChannelOptions#channel-options + - ChannelStateListener#channel-state-listener + - CompletionListener#completion-listener + - MessageListener#message-listener + - PaginatedResult#paginated-result + - Param + +h2(#channels-object). + default: Channels + +The @Channels@ object, accessed from the "realtime library client constructor":/realtime/usage#channels, is used to create and destroy @Channel@ objects. It exposes the following public methods: + +h3(#channels-methods). Channels Methods + +h6(#get). + default: get + csharp: Get + +bq(definition). + default: "Channel":#properties get(String channelName, "ChannelOptions":#channel-options channelOptions) + csharp: "Channel":#properties Get(String channelName, "ChannelOptions":#channel-options channelOptions) + objc,swift: "ARTRealtimeChannel":#properties get(String channelName, "ARTChannelOptions":#channel-options channelOptions); + +Creates a new "Channel":#properties object if none for the channel exists, or returns the existing channel object. + +h2(#channel-object). Channel + +The Channel object, created via the "Channels":#channels-object object, is used to interact with a specific channel. + +h3(#properties). + default: Channel Properties + objc,swift: ARTRealtimeChannel Properties + ruby: Ably::Realtime::Channel Attributes + java: io.ably.lib.realtime.Channel Members + csharp: IO.Ably.Realtime.RealtimeChannel Properties + +The @Channel@ object exposes the following public propertiesattributesmembers: + +h6(#state). + default: state + csharp: State + +The current "@io.ably.lib.realtime.ChannelState@":#channel-state @state@"@IO.Ably.Realtime.ChannelState@":#channel-state @state@"@Ably::Realtime::Channel::STATE@":#channel-state @state@"@ARTRealtimeChannelState@":#channel-state"@ChannelState@":#channel-state of this @Channel@. See the supported "channel states":#channel-states for more information. + +h6(#error-reason). + default: errorReason + java: reason + ruby: error_reason + csharp: ErrorReason + +When a channel failure occurs this attributememberpropertyproperty is populated. +The type is "@ErrorInfo@":/realtime/types#error-info"@AblyException@":/realtime/types#ably-exception. + +h6(#name). + default: name + csharp: Name + +The name @String@ unique to this channel. + +h6(#presence). + default: presence + csharp: Presence + +Provides access to the "Presence":/realtime/presence object for this channel which can be used to access members present on the channel, or participate in presence. + +h6(#push). + default: push + +Provides access to the "PushChannel":/general/push/activate-subscribe#push-channel object for this channel which can be used to access members present on the channel, or participate in presence. + +h3. Methods + +h6(#publish). + default: publish + csharp: Publish + +There are two overloaded versions of this method: + +bq(definition#publish-data). + default: publish(String name, Object data, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable publish(String name, Object data) -> yields + java: void publish(String name, Object data, "CompletionListener":#completion-listener listener) + csharp: void Publish(string name, object data, Action callback = null, string clientId = null) + objc,swift: publish(name: String?, data: AnyObject?, callback: (("ARTErrorInfo":/realtime/types#error-info?) -> Void)?) + +Publish a single message on this channel based on a given event name and payload. A callbacklistener may optionally be passed in to this call to be notified of success or failure of the operation. When publish is called with this client library, it "won't attempt to implicitly attach to the channel":#transient-publish, so long as "transient publishing":#transient-publish is available in the library. Otherwise, the client will implicitly attach. + +bq(definition#publish-msg-array). + default: publish(Object[] messages, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable publish(Message[] messages) -> yields + java: void publish(Message[] messages, "CompletionListener":#completion-listener listener) + csharp: void Publish(IEnumerable<"Message":#message> messages, Action callback = null) + objc,swift: publish(messages: [ "ARTMessage":#message ], callback: (("ARTErrorInfo":/realtime/types#error-info?) -> Void)?) + +Publish several messages on this channel. A callbacklistenerblock may optionally be passed in to this call to be notified of success or failure of the operation. If the channel is @initialized@ (i.e. no attempt to attach has yet been made for this channel), then calling @publish@ will implicitly attach the channel.When publish is called with this client library, it "won't attempt to implicitly attach to the channel":#transient-publish. + +h4. Parameters + +-
name
:= event name for the published message
__Type: @String@__ + +-
data
:= data payload for the message. The supported payload types are Strings, JSON objects and arrays, buffers containing arbitrary binary data, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are Strings, JsonObject, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are Strings, binary data as byte arrays, any plain c# class that can be serialized to Json, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are Strings, Hash or Array objects that can be serialized to JSON using @to_json@, binary data as @ASCII-8BIT@ byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are @NS@@String@, @NS@@Dictionary@ or @NS@@Array@ objects that can be serialized to JSON, binary data as @NSData@, and @nil@.
__Type: @Object@__ + +- messages := An array of message objects to publish
__Type: "@Message []@":#message__ + +-
callback
:= is a function of the form @function(err)@ which is called upon completion +-
listener
:= Listener to be notified on completion
__Type: "@CompletionListener@":#completion-listener__ +-
callback
:= is an action of the form @Action@ which is called upon completion +-
&block
:= yields upon successfully publishing the message +-
callback
:= called upon publishing the message, or with an error + +blang[jsall,swift,objc]. + h4. Callback result + + On successful publish of the message, @err@ is null. On failure to publish the message, @err@ contains an "@ErrorInfo@":#error-info object describing the failure reason. + +blang[csharp]. + h4. Callback result + + On successful publish of the message, @error@ is null. On failure to publish the message, @error@ contains an "@ErrorInfo@":#error-info object describing the failure reason. + +blang[java]. + h4. Listener result + + On successful publish of the message, the @onSuccess@ method of the "CompletionListener":#completion-listener is called. On failure to publish the message, the @onError@ method is called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On successful publish of the message, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method are executed. On failure to publish the message, the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable are called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +h6(#subscribe). + default: subscribe + csharp: Subscribe + +There are overloaded versions of this method: + +bq(definition#subscribe-listener). + default: subscribe(listener("Message":#message)) + java: void subscribe("MessageListener":#message-listener listener) + csharp: void Subscribe(Action<"Message":#message> handler) + ruby: subscribe → yields "Message":#message + objc,swift: subscribe(callback: ("ARTMessage":#message) -> Void) -> ARTEventListener? + +Subscribe to messages on this channel. The caller supplies a listener functiona lambda expressionan implementation of the "MessageListener":#message-listener interfacea block, which is called each time one or more messages arrives on the channel. + +bq(definition#subscribe-event). + default: subscribe(String name, listener("Message":#message)) + java: void subscribe(String name, "MessageListener":#message-listener listener) + csharp: void Subscribe(string name, Action<"Message":#message> handler) + ruby: subscribe(String name) → yields "Message":#message + objc,swift: subscribe(name: String, callback: ("ARTMessage":#message) -> Void) -> ARTEventListener? + +Subscribe to messages with a given event @name@ on this channel. The caller supplies a listener functiona lambda expressionan implementation of the "MessageListener":#message-listener interfacea block, which is called each time one or more matching messages arrives on the channel. + +
+
+bq(definition#subscribe-event-array). + default: subscribe(String[] names, listener("Message":#message)) + java: void subscribe(String[] names, "MessageListener":#message-listener listener) + ruby: subscribe(String *names) → yields "Message":#message + +Subscribe a single listener to messages on this channel for multiple event @name@ values. +
+ +h4. Parameters + +- name := The event name to subscribe to
__Type: @String@__ + +
+
+- names := An argument array of event names to subscribe to
__Type: @String[]@ @*argument@__ +
+ +-
listener
:= is a function of the form @function(message)@ to be called for each message +-
"MessageListener":#message-listener listener
:= Message listener to be notified for matching messages +-
&block
:= yields each matching message when received on the channel +-
callback
:= called with each matching "@message@":#message when received on the channel +-
handler
:= called with each matching "@message@":#message when received on the channel + +h4. Considerations + +* If the channel is @initialized@ (i.e. no attempt to attach has yet been made for this channel), then calling @subscribe@ will implicitly attach the channel. However, regardless of the implicit attach outcome, the listenerblockhandler will still be registered +* If @subscribe@ is called more than once with the same listenerblockhandler, then duplicates will be registered. For example, if you @subscribe@ twice with the same listenerblockhandler and a message is later received, the same listenerblockhandler will be invoked twice +* The registered listenerblockhandler remains active on the channel regardless of the current channel state. For example, if you call @subscribe@ when a channel is @attached@ and it later becomes @detached@ or even @failed@, when the channel is reattached and a message is received, the listenersblockshandlers originally registered will still be invoked. Listeners are only removed when calling "@unsubscribe@":#unsubscribe or when a channel is @released@ using the @Realtime.channels.release(name)@@Realtime.Channels.Release(name)@ method +* If an exception is thrown in the subscribe listener and bubbles up to the event emitter, it will be caught and logged at @error@ level, so as not to affect other listeners for the same event + +h6(#unsubscribe). + default: unsubscribe + csharp: Unsubscribe + +There are sixthree overloaded versions of this method: + +bq(definition#unsubscribe-event). + default: unsubscribe(String name, listener) + java: void unsubscribe(String name, "MessageListener":#message-listener listener) + csharp: bool Unsubscribe(string eventName, Action<"Message":#message> handler) + ruby: unsubscribe(String name, &listener_proc) + objc,swift: unsubscribe(name: String, listener: ARTEventListener) + +Unsubscribe the given listener for the specified event name. This removes an earlier event-specific subscription. + +bq(definition#unsubscribe-listener). + default: unsubscribe(listener) + java: void unsubscribe("MessageListener":#message-listener listener) + csharp: bool Unsubscribe(Action<"Message":#message> handler) + ruby: unsubscribe(&listener_proc) + objc,swift: unsubscribe(listener: ARTEventListener) + +Unsubscribe the given listener (for any/all event names). This removes an earlier subscription. + +
+bq(definition). unsubscribe(String[] names, listener) + +Unsubscribe the given listener from all event names in the array. + +bq(definition). unsubscribe(String name) + +Unsubscribe all listeners for a given event name. + +bq(definition). unsubscribe(String[] names) + +Unsubscribe all listeners for all event names in the array. +
+ +bq(definition#unsubscribe-all). + default: unsubscribe() + java: void unsubscribe() + csharp: void Unsubscribe() + objc,swift: unsubscribe() + +Unsubscribes all listeners to messages on this channel. This removes all earlier subscriptions. + +h4. Parameters + +- name := The event name to unsubscribe from
__Type: @String@__ +-
names
:= An array of event names to unsubscribe from
__Type: @String[]@__ +-
listener
:= is the callback listener function that was previously subscribed +-
listener
:= previously registered listener
__Type: "@MessageListener@":#message-listener__ +-
&listener_block
:= previously registered listener block +-
listener
:= previous return value from a @subscribe@ call +-
handler
:= is the lambda expression that was previously subscribed + +h6(#history). + default: history + csharp: History + +bq(definition). + default: history(Object options, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":#paginated-result<"Message":#message> resultPage)) + ruby: "Deferrable":/realtime/types#deferrable history(Hash options) -> yields "PaginatedResult":#paginated-result<"Message":#message> + java: "PaginatedResult":#paginated-result<"Message":#message> history("Param []":#param options) + csharp: Task<"PaginatedResult":#paginated-result> HistoryAsync("PaginatedRequestParams":#paginated-request-params dataQuery, bool untilAttach = false) + objc,swift: history(query: ARTRealtimeHistoryQuery?, callback: ("ARTPaginatedResult":#paginated-result<"ARTMessage":#message>?, ARTErrorInfo?) -> Void) throws + +Gets a "paginated":#paginated-result set of historical messages for this channel. If the "channel is configured to persist messages to disk":https://support.ably.io/support/solutions/articles/3000030059-how-long-are-messages-stored-for, then message history will "typically be available for 24 - 72 hours":https://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for. If not, messages are only retained in memory by the Ably service for two minutes. + +h4. Parameters + +- optionsquerydataQuery := an optional object containing the query parametersan optional set of key value pairs containing the query parameters, as specified in the "message history API documentation":/realtime/history#channel-history.
__Type: @Object@@ARTRealtimeHistoryQuery@@PaginatedRequestParams@@Hash@"@Param []@":#param__ + +-
callback
:= is a function of the form: @function(err, resultPage)@ +-
&block
:= yields a @PaginatedResult@ object +-
callback
:= called with a "ARTPaginatedResult":#paginated-result<"ARTMessage":#message> object or an error + +Further details of the supported @options@ params, see "message history API documentation":/realtime/history#channel-history. + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @resultPage@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + On failure to retrieve message history, @err@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +blang[java]. + h4. Returns + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + Failure to retrieve the message history will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[csharp]. + h4. Returns + + The return value is @Task>@ which needs to be awaited. + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@NextAsync@":#paginated-result and "@FirstAsync@":#paginated-result methods. + + Failure to retrieve the message history will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On success, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method yield a "PaginatedResult":#paginated-result that encapsulates an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + Failure to retrieve the message history will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object with the failure reason. + +h6(#attach). + default: attach + csharp: Attach + +bq(definition). + default: attach(callback("ErrorInfo":/realtime/types#error-info err)) + java: void attach() + csharp: void Attach(Action callback = null) + ruby: "Deferrable":/realtime/types#deferrable attach -> yields + objc,swift: attach(callback: (("ARTErrorInfo":/realtime/types#error-info?) -> Void)?) + +Attach to this channel ensuring the channel is created in the Ably system and all messages published on the channel will be received by any channel listeners registered using @subscribe()@@Subscribe()@@subscribe@. Any resulting channel state change will be emitted to any listeners registered using the "@on@@On@":#on or "@once@@Once@":#once methods. + +As a convenience, @attach()@@Attach()@@attach@ will be called implicitly if "@subscribe()@@Subscribe()@@subscribe@":#subscribe for the @Channel@ is called, or "@enter()@@Enter()@@enter@":/realtime/presence#enter or "@subscribe()@@Subscribe()@@subscribe@":/realtime/presence#subscribe is called on the "@Presence@":/realtime/presence for this @Channel@. + +h4. Parameters + +-
callback
:= is a function of the form @function(err)@ and is called once the channel attach succeeds or fails +-
&block
:= yields once the channel becomes attached +-
callback
:= is a lambda expression of the form @Action@ and is called once the channel attach succeeds or fails +-
callback
:= called once the channel becomes attached or if an error occurs + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the attach method. + + On successful attachment, the success callbacks for the "@Deferrable@":/realtime/types#deferrable are called and any block provided to the method is yielded. Failure to attach will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object with the failure reason. + +h6(#detach). + default: detach + csharp: Detach + +bq(definition). + default: detach(callback("ErrorInfo":/realtime/types#error-info err)) + java: void detach() + csharp: void Detach(Action callback = null) + ruby: "Deferrable":/realtime/types#deferrable detach -> yields + objc,swift: detach(callback: (("ARTErrorInfo":/realtime/types#error-info?) -> Void)?) + +Detach from this channel. Any resulting channel state change will be emitted to any listeners registered using the "@on@@On@":#on or "@once@@Once@":#once methods. + +Please note: Once all clients globally have detached from the channel, the channel will be released in the Ably service within two minutes. + +h4. Parameters + +-
callback
:= is a function of the form @function(err)@ and is called once the channel detach succeeds or fails +-
callback
:= is a lambda expression in the form @Action@ and is called once the channel detach succeeds or fails +-
&block
:= yields once the channel becomes detached +-
callback
:= called once the channel becomes detached or if an error occurs + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the detach method. + + On successful detachment, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method are executed. Failure to detach will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object with the failure reason. + +h6(#on). + default: on + csharp: On + +There are threetwo overloaded versions of this method: + +bq(definition#on-state-listener). + default: on(String event, listener("ChannelStateChange":#channel-state-change stateChange)) + ruby: on("ChannelEvent":#channel-event *event) -> yields "ChannelStateChange":#channel-state-change + java: void on("ChannelEvent":#channel-event event, "ChannelStateListener":#channel-state-listener listener) + csharp: void On("ChannelEvent":#channel-event event, Action<"ChannelStateChange":#channel-state-change> action) + objc,swift: on(event: "ARTChannelEvent":#channel-event, call: ("ARTChannelStateChange?":#channel-state-change) -> Void) -> ARTEventListener + +Register the given listener blockfunction for the specified "ChannelEvent":#channel-event on the @Channel@. +The listener is passed a "ChannelStateChangeARTChannelStateChange":#channel-state-change object that contains the current state, previous state, a boolean indicating whether the channel was resumed, and an optional reason for the state change. + +
+bq(definition#on-state-array-listener). + jsall: on(String[] events, listener("ChannelStateChange":#channel-state-change stateChange)) + +Same as above, but registers multiple listeners, one for each event in the array. +
+ +bq(definition#on-listener). + default: on(listener("ChannelStateChange":#channel-state-change stateChange)) + ruby: on -> yields "ChannelStateChange":#channel-state-change + csharp: void On(Action<"ChannelStateChange":#channel-state-change> listener) + java: void on("ChannelStateListener":#channel-state-listener listener) + objc,swift: on(call: ("ARTChannelStateChange?":#channel-state-change) -> Void) -> ARTEventListener + +Register the given listener blockfunctionlambda expression for all "ChannelEvents":#channel-event on the @Channel@. +The listener is passed a "ChannelStateChangeARTChannelStateChange":#channel-state-change object that contains the current state, previous state, the event, a boolean indicating whether the channel was resumed, and an optional reason for the state change. + +If an exception is thrown in the listener and bubbles up to the event emitter, it will be caught and logged at @error@ level, so as not to affect other listeners for the same event + +h4. Parameters + +-
event(s)
:= the channel event(s) to subscribe to, see "ChannelEvent":#channel-event for supported channel events
__Type: @String@ or @String[]@__ +-
event
:= the channel event to subscribe to
__Type: "@ChannelEvent@":#channel-event__ +-
event
:= the channel event to subscribe to
__Type: "@ChannelEvent@":#channel-event__ +-
event
:= the channel event as a Symbol such as @:attached@ or a @ChannelEvent@ object to subscribe to
__Type: "@ChannelEvent@":#channel-event__ +-
event
:= the @ARTChannelEvent@ to subscribe to
__Type: "@ARTChannelEvent@":#channel-event__ + +-
listener
:= is a function of the form @function(stateChange)@ to be notified for matching event changes. +-
listener
:= listener to be notified for matching event changes
__Type: "@ChannelStateListener@":#channel-state-listener__ +-
action
:= lambda expression to be notified for matching event changes
__Type: "@ChannelStateChange@":#channel-state-listener__ +-
&block
:= listener block that is yielded to for matching event changes +-
call
:= called possibly with an "@ErrorInfo@":#error-info + +h4. Example + +"(external) Browser @ChannelState@ change example":<%= JsBins.url_for('realtime/channel-states') %> + +h6(#once). + default: once + csharp: Once + +There are two overloaded versions of this method: + +bq(definition#once-state-listener). + default: once(String event, listener("ChannelStateChange":#channel-state-change stateChange)) + ruby: once("ChannelEvent":#channel-event *event) -> yields "ChannelStateChange":#channel-state-change + java: void once("ChannelEvent":#channel-event event, "ChannelStateListener":#channel-state-listener listener) + csharp: void Once("ChannelEvent":#channel-event event, Action<"ChannelStateChange":#channel-state-change> action) + objc,swift: once(event: "ARTChannelEvent":#channel-event, call: ("ARTChannelStateChange?":#channel-state-change) -> Void) -> ARTEventListener + +Register the given listener blockfunctionlambda expression for a single occurrence of the specified "ChannelEvent":#channel-event on the @Channel@. Once the listener has been called, it is removed as a registered listener and will not be called again. +The listener is passed a "ChannelStateChangeARTChannelStateChange":#channel-state-change object that contains the current state, previous state, the event, a boolean indicating whether the channel was resumed, and an optional reason for the event change. + +bq(definition#once-listener). + default: once(listener("ChannelStateChange":#channel-state-change stateChange)) + ruby: once -> yields "ChannelStateChange":#channel-state-change + java: void once("ChannelStateListener":#channel-state-listener listener) + csharp: void Once(Action<"ChannelStateChange":#channel-state-change> listener) + objc,swift: once(call: ("ARTChannelStateChange?":#channel-state-change) -> Void) -> ARTEventListener + +Register the given listener blockfunctionlambda expression for a single occurrence of any "ChannelEvent":#channel-event on the @Channel@. Once the listener has been called, it is removed as a registered listener and will not be called again. +The listener is passed a "ChannelStateChangeARTChannelStateChange":#channel-state-change object that contains the current state, previous state, the event, a boolean indicating whether the channel was resumed, and an optional reason for the event change. + +If an exception is thrown in the listener and bubbles up to the event emitter, it will be caught and logged at @error@ level, so as not to affect other listeners for the same event + +h4. Parameters + +-
event
:= the channel event to subscribe to, see "ChannelEvent":#channel-event for supported channel events
__Type: @String@__ +-
event
:= the channel event to subscribe to
__Type: "@ChannelEvent@":#channel-event__ +-
event
:= the channel event to subscribe to
__Type: "@ChannelEvent@":#channel-event__ +-
event
:= the channel event as a Symbol such as @:attached@ or a @ChannelEvent@ object to subscribe to
__Type: "@ChannelEvent@":#channel-event__ +-
event
:= the @ARTChannelEvent@ to subscribe to
__Type: "@ARTChannelEvent@":#channel-event__ + +-
listener
:= is a function of the form @function()@ to be notified for a single occurrence of a matching event change. The current event is available as @this.event@ +-
listener
:= listener to be notified for a single occurrence of a matching event change
__Type: "@ChannelStateListener@":#channel-state-listener__ +-
&block
:= listener block that is yielded to for a single occurrence of a matching event change +-
listener
:= listener lambda expression that is notified only once for a matching event change +-
call
:= called possibly with an "@ErrorInfo@":#error-info + +h6(#off). + default: off + csharp: Off + +There are sixtwo overloaded versions of this method: + +bq(definition#off-state-listener). + default: off(String event, listener) + ruby: off("ChannelEvent":#channel-event *event, &block) + java: void off("ChannelEvent":#channel-event event, "ChannelStateListener":#channel-state-listener listener) + csharp: void Off("ChannelEvent":#channel-event event, Action<"ChannelStateChange":#channel-state-change> listener) + objc,swift: off(event: "ARTChannelEvent":#channel-event, listener: ARTEventListener) + +Remove the given listener block for the "ChannelEvent":#channel-event . + +bq(definition#off-listener). + default: off(listener) + ruby: off(&block) + java: void off("ChannelStateListener":#channel-state-listener listener) + csharp: void Off(Action<"ChannelStateChange":#channel-state-change> listener) + objc,swift: off(listener: ARTEventListener) + +Remove the given listener block for all "ChannelEvents":#channel-event. + +
+bq(definition). off(String[] events, listener) + +Removes the given listener from all "ChannelEvents":#channel-event in the array. + +bq(definition). off(String event) + +Removes all listeners for a given "@ChannelEvent@":#channel-event . + +bq(definition). off(String[] events) + +Removes all listeners for all "ChannelEvents":#channel-event in the array. + +bq(definition). off() + +Removes all listeners (including both those registered against specific events and those registered without an event). +
+ +h4. Parameters + +-
event(s)
:= the channel event(s) to unsubscribe from
__Type: @String@ or @String[]@__ +-
event
:= the channel event to unsubscribe from
__Type: "@ChannelEvent@":#channelevent- +-
event
:= the channel event to unsubscribe from
__Type: "@ChannelEvent@":#channel-event__ +-
event
:= the channel event as a Symbol such as @:detached@ or a @ChannelEvent@ object to unsubscribe from
__Type: "@ChannelEvent@":#channel-event__ +-
event
:= the @ARTChannelEvent@ to unsubscribe from
__Type: "@ARTChannelEvent@":#channel-event__ + +-
listener
:= is the listener function to be removed +-
listener
:= is the listener to be removed
__Type: "@ChannelStateListener":#channel-state-listener__ +-
listener
:= is the listener function to be removed +-
&block
:= is the listener block to be removed +-
listener
:= previous return value from a @on@ or @once@ call + +h2(#related-types). Related types + +h3(#channel-state). + default: ChannelState + objc,swift: ARTRealtimeChannelState + ruby: Channel::STATE Enum + java: io.ably.lib.realtime.ChannelState Enum + csharp: IO.Ably.Realtime.ChannelState Enum + +<%= partial partial_version('types/_channel_state') %> + +h3(#channel-event). + default: ChannelEvent + swift,objc: ARTChannelEvent + ruby: Channel::EVENT Enum + java: io.ably.lib.realtime.ChannelEvent Enum + csharp: IO.Ably.Realtime.ChannelEvent Enum + +<%= partial partial_version('types/_channel_event') %> + +h3(#channel-state-change). + default: ChannelStateChange + objc,swift: ARTRealtimeChannelStateChange + java: io.ably.lib.realtime.ChannelStateListener.ChannelStateChange + ruby: ChannelStateChange + csharp: IO.Ably.Realtime.ChannelStateChanged + +<%= partial partial_version('types/_channel_state_change') %> + +h3(#message). + default: Message + objc,swift: ARTMessage + ruby: Ably::Models::Message + java: io.ably.lib.types.Message + csharp: IO.Ably.Message + +<%= partial partial_version('types/_message') %> + +h3(#channel-options). + default: ChannelOptions Object + objc,swift: ARTChannelOptions + ruby: ChannelOptions Hash + java: io.ably.lib.types.ChannelOptions + csharp: IO.Ably.Rest.ChannelOptions + +<%= partial partial_version('types/_channel_options') %> + +h3(#channel-state-listener). + java: io.ably.lib.realtime.ChannelStateListener + +blang[java]. + <%= partial partial_version('types/_channel_state_listener'), indent: 2, skip_first_indent: true %> + +h3(#completion-listener). + java: io.ably.lib.realtime.CompletionListener + +blang[java]. + <%= partial partial_version('types/_completion_listener'), indent: 2, skip_first_indent: true %> + +h3(#paginated-request-params). + csharp: PaginatedRequestParams + +blang[csharp]. + <%= partial partial_version('types/_history_request_params'), indent: 2, skip_first_indent: true %> + +h3(#message-listener). + java: io.ably.lib.realtime.Channel.MessageListener + +blang[java]. + <%= partial partial_version('types/_message_listener'), indent: 2, skip_first_indent: true %> + +h3(#paginated-result). + default: PaginatedResult + objc,swift: ARTPaginatedResult + ruby: Ably::Models::PaginatedResult + java: io.ably.lib.types.PaginatedResult + csharp: IO.Ably.PaginatedResult + +<%= partial partial_version('types/_paginated_result') %> + +h3(#param). + java: io.ably.lib.types.Param + +blang[java]. + <%= partial partial_version('types/_param'), indent: 2, skip_first_indent: true %> diff --git a/content/realtime/versions/v1.1/connection.textile b/content/realtime/versions/v1.1/connection.textile new file mode 100644 index 0000000000..ef4d9cd03f --- /dev/null +++ b/content/realtime/versions/v1.1/connection.textile @@ -0,0 +1,726 @@ +--- +title: Connection +section: realtime +index: 20 +languages: + - javascript + - nodejs + - ruby + - java + - swift + - objc + - csharp +api_separator: +jump_to: + Help with: + - Getting started#title + - Connection state explained + - Handling failures + - Connection state recovery + Connection API properties: + - id + - state + - error#error-reason + - key + - recovery key + - serial + Connection API methods: + - connect + - close + - on#on + - once + - off#off + - ping + Types: + - Related Types#related-types +--- + +The Ably Realtime library establishes and maintains a connection to the Ably service, using the most efficient transport available, typically "WebSockets":/concepts/websockets. The Ably realtime protocol operates and multiplexes all channel traffic over that connection. + +h3(#getting-started). Getting started + +The Ably Realtime library will open and maintain a connection to the Ably realtime servers as soon as it is instanced. The @Connection@ object provides a straightforward API to monitor and manage connection state. + +The following example relies on the default auto-connect behavior of the library, and then subscribes to the connection's @connected@ event. + +bc[javascript](code-editor:realtime/connection). var ably = new Ably.Realtime('{{API_KEY}}'); +ably.connection.on('connected', function() { + alert('Connected, that was easy'); +}) + +bc[nodejs](code-editor:realtime/connection). var Ably = require('ably'); +var ably = new Ably.Realtime('{{API_KEY}}'); +ably.connection.on('connected', function() { + console.log('Connected, that was easy'); +}) + +bc[ruby]. ably = Ably::Realtime.new('{{API_KEY}}') +ably.connection.on(:connected) do + puts "Connected, that was easy" +end + +bc[java]. AblyRealtime ably = new AblyRealtime("{{API_KEY}}"); +ably.connection.on('connected', new ConnectionStateListener() { + @Override + public void onConnectionStateChanged(ConnectionStateChange change) { + System.out.println("Connected, that was easy"); + } +}); + +bc[csharp]. AblyRealtime ably = new AblyRealtime("{{API_KEY}}"); +ably.Connection.On(ConnectionState.Connected, args => { + Console.WriteLine("Connected, that was easy"); +}); + +bc[objc]. ARTRealtime *ably = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"]; +[ably.connection on:ARTRealtimeConnectionEventConnected call:^(ARTConnectionStateChange *change) { + NSLog(@"Connected, that was easy"); +}]; + +bc[swift]. let realtime = ARTRealtime(key: "{{API_KEY}}") +realtime.connection.on(.connected) { change in + print("Connected, that was easy") +} + +Note that all examples on this page assume you are running them within an EventMachine reactor. Find out more in our "Realtime usage documentation":/realtime/usage. + +h2(#connection-state-explained). Connection state explained + +Although connection state is temporary, the Ably protocol provides continuity of message delivery between the client and the service, provided that a dropped connection is reestablished by the client within a limited interval (typically around 2 minutes). Beyond that, the connection becomes stale and the system will not attempt to recover the connection state. The lifecycle of a connection, and the strategy for reconnecting on failure, reflect the transient nature of the connection state. + +The client library is responsible for managing the connection; this includes selecting a transport (in those environments supporting multiple transports), selecting a host to connect to (automatically falling back to an alternate datacenter host if the closest datacenter is unreachable), and managing continuity of operation when the connection drops. + +When the library is instanced, if connectivity to the service is available, the library will establish a connection immediately, and if the connection drops at any time it will attempt to re-establish it by making repeated connection attempts every 15 seconds for up to two minutes. + +If, after that time, there has been no connection, the library falls back to a lower level of activity, still periodically attempting reconnection at 30 second intervals. This reflects the assumption that there will no longer be recoverable connection state and the client may be offline for a period of time. As soon as a reconnection attempt has been successful, the system reverts to the more active connection behavior. Further, you can explicitly trigger a reconnection attempt at any time if you wish to implement a different reconnection strategy. + +The connection object provides methods to observe the lifecycle of the connection and to trigger state transitions. + +h3(#connection-states). Available connection states + +A series of connection states is defined as follows: + +- initialized := A @Connection@ object having this state has been initialized but no connection has yet been attempted. + +- connecting := A connection attempt has been initiated. The connecting state is entered as soon as the library has completed initialization, and is reentered each time connection is re-attempted following disconnection. + +- connected := A connection exists and is active. + +- disconnected := A temporary failure condition. No current connection exists because there is no network connectivity or no host is available.

The disconnected state is entered if an established connection is dropped, or if a connection attempt was unsuccessful. In the disconnected state the library will periodically attempt to open a new connection (approximately every 15 seconds), anticipating that the connection will be re-established soon and thus connection and channel continuity will be possible.

In this state, developers can continue to publish messages as they are automatically placed in a local queue, to be sent as soon as a connection is reestablished. Messages published by other clients whilst this client is disconnected will be delivered to it upon reconnection, so long as the connection was resumed within 2 minutes.

After 2 minutes have elapsed, recovery is no longer possible and the connection will move to the @suspended@ state. + +- suspended := A long term failure condition. No current connection exists because there is no network connectivity or no host is available.

The suspended state is entered after a failed connection attempt if there has then been no connection for a period of two minutes. In the suspended state, the library will periodically attempt to open a new connection every 30 seconds. Developers are unable to publish messages in this state. A new connection attempt can also be triggered by an explicit call to "@connect()@@connect@@Connect()@:#connect":#connect on the @Connection@ object.

Once the connection has been re-established, channels will be automatically re-attached. The client has been disconnected for too long for them to resume from where they left off, so if it wants to catch up on messages published by other clients while it was disconnected, it needs to use the "history API":/realtime/history. + +- closing := An explicit request by the developer to close the connection has been sent to the Ably service. If a reply is not received from Ably within a short period of time, the connection will be forcibly terminated and the connection state will become @closed@. + +- closed := The connection has been explicitly closed by the client.

In the closed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. No connection state is preserved by the service or by the library. A new connection attempt can be triggered by an explicit call to "@connect()@@connect@@Connect()@:#connect":#connect on the @Connection@ object, which will result in a new connection. + +- failed := This state is entered if the client library encounters a failure condition that it cannot recover from. This may be a fatal connection error received from the Ably service (e.g. an attempt to connect with an incorrect API key), or some local terminal error (e.g. the token in use has expired and the library does not have any way to renew it).

In the failed state, no reconnection attempts are made automatically by the library, and clients may not publish messages. A new connection attempt can be triggered by an explicit call to "@connect()@@connect@@Connect()@:#connect":#connect on the @Connection@ object. + +h4. Typical connection state sequences + +The library is initialized and initiates a successful connection. + +bq. initialized → connecting → connected + +An existing connection is dropped and reestablished on the first attempt. + +bq. connected → disconnected → connecting → connected + +An existing connection is dropped, and reestablished after several attempts but within a two minute interval. + +bq. connected → disconnected → connecting → disconnected → … → connecting → connected + +There is no connection established after initializing the library. + +bq. initialized → connecting → disconnected → connecting → … → suspended + +After a period of being offline a connection is reestablished. + +bq. suspended → connecting → suspended → … → connecting → connected + +h4. Listening for state changes + +The @Connection@ object is an @EventEmitter@ and emits an event whose name is the new state whenever there is a connection state change. An event listener function is passed a "ConnectionStateChange":#connection-state-change object as the first argument for state change events.An event listener function is passed a "ConnectionStateChange":#connection-state-change object as the first argument for state change events.The event block is passed the new state and an optional "@ErrorInfo@":/realtime/types#error-info object + +The @Connection@ object can also emit an event that is not a state change: an @update@ event. This happens when there's a change to connection conditions for which the connection state doesn't change - that is, when the library remains connected, e.g. after a "reauth":/realtime/authentication#token-upgrading. + +blang[jsall]. + ```[jsall] + realtime.connection.on('connected', function(stateChange) { + console.log('Ably is connected'); + }); + ``` + + Alternatively a listener may be registered so that it receives all state change events. + + ```[jsall] + realtime.connection.on(function(stateChange) { + console.log('New connection state is ' + stateChange.current); + }); + ``` + + Previously registered listeners can be removed individually or all together. + + ```[jsall] + /* remove a listener registered for a single event */ + realtime.connection.off('connected', myListener); + + /* remove a listener registered for all events */ + realtime.connection.off(myListener); + + /* remove all event listeners */ + realtime.connection.off(); + ``` + +blang[java]. + ```[java] + realtime.connection.on(ConnectionEvent.connected, new ConnectionStateListener() { + @Override + public void onConnectionStateChanged(ConnectionStateChange change) { + System.out.println("New state is connected"); + } + }); + ``` + + Alternatively a listener may be registered so that it receives all state change events. + + ```[java] + realtime.connection.on(new ConnectionStateListener() { + @Override + public void onConnectionStateChanged(ConnectionStateChange change) { + System.out.println("New state is " + change.current.name()); + } + }); + ``` + + Previously registered listeners can be removed individually or all together. + + ```[java] + /* remove a single listener */ + realtime.connection.off(myListener); + + /* remove all event listeners */ + realtime.connection.off(); + ``` + +blang[csharp]. + ```[csharp] + realtime.Connection.On(ConnectionState.Connected, args => { + Console.WriteLine("Connected, that was easy") + }); + ``` + + Alternatively a handler may be registered so that it receives all state change events. + + ```[csharp] + realtime.Connection.On(args => { + Console.WriteLine("New state is " + args.Current) + }); + ``` + + Previously registered handlers can be removed individually or all together. + + ```[csharp] + /* remove a single handler */ + realtime.Connection.Off(action); + + /* remove all event handlers */ + realtime.Connection.Off(); + ``` + +blang[ruby]. + ```[ruby] + realtime.connection.on(:connected) do + puts 'Ably is connected' + end + ``` + + Alternatively a listener may be registered so that it receives all state change events. + + ```[ruby] + realtime.connection.on do |state_change| + puts "New connection state is #{state_change.current}" + end + ``` + + Previously registered listeners can be removed individually or all together. + + ```[ruby] + # remove a listener registered for a single even + realtime.connection.off :connected, &block + + # remove a listener registered for all events + realtime.connection.off &block + + # remove all event listeners + realtime.connection.off + ``` + +blang[objc]. + ```[objc] + ARTEventListener *listener = [realtime.connection on:ARTRealtimeConnectionEventConnected call:^(ARTConnectionStateChange *change) { + NSLog(@"Ably is connected"); + }]; + ``` + + Alternatively a listener may be registered so that it receives all state change events. + + ```[objc] + ARTEventListener *listener = [realtime.connection on:^(ARTConnectionStateChange *change) { + NSLog(@"New connection state is %lu", (unsigned long)change.current); + }]; + ``` + + Previously registered listeners can be removed individually or all together. + + ```[objc] + // remove a listener registered for a single event + [realtime.connection off:ARTRealtimeConnectionEventConnected listener:listener]; + + // remove a listener registered for all events + [realtime.connection off:listener]; + + // remove all event listeners + [realtime.connection off]; + ``` + +blang[swift]. + ```[swift] + let listener = realtime.connection.on(.connected) { change in + print("Ably is connected") + } + ``` + + Alternatively a listener may be registered so that it receives all state change events. + + ```[swift] + let listener = realtime.connection.on { change in + print("New connection state is \(change!.current)") + } + ``` + + Previously registered listeners can be removed individually or all together. + + ```[swift] + // remove a listener registered for a single event + realtime.connection.off(.connected, listener: listener) + + // remove a listener registered for all events + realtime.connection.off(listener) + + // remove all event listeners + realtime.connection.off() + ``` + +h3(#handling-failures). Handling failures + +The client libraries will attempt to automatically recover from non-fatal error conditions. However, it will emit events to say what it's doing, so you can handle them yourself if you prefer. + +h4(#fatal-errors). Fatal errors + +Some classes of errors are fatal. These cause the connection to move to the @FAILED@ state. The client library will not attempt any automatic recovery actions. For example, if your token expires and the client library has no way to get a new token (so no authUrl and authCallback), the connection will enter the @FAILED@ state + +While the library will not automatically attempt to reconnect in the @FAILED@ state, explicit calls to "@connect()@":#connect will make the client try again. + +h4(#nonfatal-errors). Nonfatal errors + +Other classes of error are nonfatal. For example, a client may have network connectivity issues. The library will attempt to automatically reconnect and recover from these sort of issues, as detailed in the @DISCONNECTED@ and @SUSPENDED@ explanations in the "Available connection states":#connection-states section. + +If message continuity is lost in the process, e.g. because you have been disconnected from Ably for more than two minutes, the library will notify you though the @resumed@-flag mechanism, detailed in the "Channels and Messages":/realtime/channels#nonfatal-errors page. + +h3(#connection-state-recovery). Connection state recovery + +The Ably system preserves connection state to allow connections to continue transparently across brief disconnections. The connection state that is tracked includes the messages sent to the client on the connection, members present on a channel and the set of channels that the client is attached to. + +There are two modes of connection state recovery: + +* @resume@: this is transparent recovery of a live client instance across disconnections. Upon disconnection, the library will automatically re-attempt connection and, once the connection is re-established, any missed messages will be sent to the client. The developer does not need to do anything to trigger this behavior; all client channel event listeners remain attached and are called when the backlog of messages is received. + +* @recover@: this addresses the case in which a new client library instance wishes to connect and recover the state of an earlier connection. This occurs typically in a browser environment when the page has been refreshed and therefore the client instance is disposed and no client state is retained. In this case any message listeners associated with channels will no longer exist so it is not possible for the library simply to send the message backlog on reconnection; instead the client must re-subscribe to each channel it is interested in within 15 seconds, and its message listener(s) will be called with any message backlog for that channel. If it had any members in the presence set, they will need to explicitly re-enter. If the previously attached channels are not re-attached within 15 seconds of a connection being recovered, the client will lose the ability to continue the message stream from before; any subsequent attach() will result in a fresh attachment, with no backlog sent. A client requests recovery of connection state by including a recovery string in the "client options":/realtime/usage#client-options when instancing the Realtime library. See "connection state recover options":#connection-state-recover-options for more info. + +In either case, when a connection is resumed or recovered, the message backlog held on the server will be pushed to the client. However, any new messages published will be sent as they become available or messages could be indefinitely deferred on very heavily loaded connections. Therefore the system does not guarantee that messages received after reconnection are delivered in the same order that would have occurred if the connection had not been dropped. In the @recover@ case, in particular, the order of the message delivery depends on the timing of the re-attachment of each channel. + +h4(#connection-state-recover-options). Connection state recover options + +In @recover@ mode it is necessary to request recovery mode in the "client options":/realtime/usage#client-options when instancing the library. Recovery requires that the library knows the previous connection's "@recoveryKey@@recovery_key@@RecoveryKey@":#recovery-key value (which includes both the private unique "@Connection#key@@Connection#Key@":#key and the last message serial received on that connection). As the recovery key is never shared with any other clients, it allows Ably to safely resend message backlogs to the original client. + +blang[javascript]. + In the browser environment, if a callback is provided in the @recover@ option, when the @window.beforeunload@ event fires, the connection details, including the "@recoveryKey@":#recovery-key, are stored in the "browser's sessionStorage":https://www.w3.org/TR/webstorage/. The provided @recover@ callback is then invoked whenever the connection state can be recovered and just before a connection is established, passing in the "@LastConnectionDetails@":#last-connection-details. The callback is then responsible for confirming whether the connection state should be recovered or not. For example, it is common to recover connection state when the page is reloaded but not for different pages the user has navigated to. The callback allows the developer to decide if the connection should be recovered or not at the time the new connection is established by inspecting the "@LastConnectionDetails@":#last-connection-details and evaluating that against any other application state. Below are two examples: + + * **Always recover** - always recover the previous connection state if possible + + ```[jsall](code-editor:realtime/connection-recover) + var ably = new Ably.Realtime({ + authUrl: '/obtainToken', + recover: function(_, cb) { cb(true); } + }); + ``` + + * **Sometimes recover** - recover the previous connection state conditionally based on some logic + + ```[jsall] + var ably = new Ably.Realtime({ + authUrl: '/obtainToken', + recover: function(lastConnectionDetails, cb) { + /* Only recover if the current path hasn't changed, start a + * fresh connection if it has. This is just an example, you + * can use whatever logic your app requires */ + if (lastConnectionDetails.location.href === document.location.href) { + cb(true); /* recover connection */ + } else { + cb(false); /* do not recover connection */ + } + } + }); + ``` + + Please note that as "@sessionStorage@":https://www.w3.org/TR/webstorage/ is used to persist the @LastConnectionDetails@ between page reloads, it is only available for pages in the same origin and top-level browsing context. + + Alternatively, if it is necessary to be explicit about the connection @recoveryKey@ , the connection can be recovered by providing the last value of the connection's @recoveryKey@ value in the "client options":/realtime/usage#client-options @recover@ attribute when instancing the library. + +h4. Connection recovery constraints + +Connection recovery requires that the new client library instance uses credentials that are compatible with those used for the inherited connection; this requires that the same authentication mode is used, with the same key. If token auth was used, the same token is not required, but the token used must have the same @capability@ and @clientId@@client_id@@ClientId@. This ensures that the client recovering the connection cannot receive a backlog of messages that its new credentials are not entitled to access. Incompatible credentials will result in an unrecoverable connection error. + +h1. Connection API reference + +inline-toc. + Properties: + - id#id + - state#state + - errorReason#error-reason + - key#key + - recoveryKey#recovery-key + - serial#serial + Methods: + - connect()#connect + - close()#close + - on()#on + - once()#once + - off()#off + - ping()#ping + Related types: + - CompletionListener#completion-listener + - ConnectionState#connection-state + - ConnectionEvent#connection-event + - ConnectionStateListener#connection-state-listener + - ConnectionStateChange#connection-state-change + - LastConnectionDetails#last-connection-details + +h2(#properties). + default: Connection Properties + swift,objct: ARTConnection Properties + ruby: Ably::Realtime::Connection Attributes + java: io.ably.lib.realtime.Connection Members + csharp: IO.Ably.Realtime.Connection Properties + +The @Connection@ object exposes the following public propertiesattributesmembers: + +h6(#id). + default: id + csharp: Id + +A unique public identifier @String@ for this connection, used to identify this member in presence events and messages. + +h6(#state). + default: state + csharp: State + +The current "@io.ably.lib.realtime.ConnectionState@":#connection-state @state@"@IO.Ably.Realtime.ConnectionState@":#connection-state @state@"@Ably::Realtime::Connection::STATE@":#connection-state @state@"@ARTRealtimeConnectionState@":#connection-state @state@@state String@ of this Connection. See the "@Connection@ states":#connection-states for more information. + +h6(#error-reason). + default: errorReason + java: reason + csharp: ErrorReason + ruby: error_reason + +When a connection failure occurs this attributememberproperty contains the "@ErrorInfo@":/realtime/types#error-info"@AblyException@":/realtime/types#ably-exception. + +h6(#key). + default: key + csharp: Key + +A unique private connection key @String@ used to recover or resume a connection, assigned by Ably. When recovering a connection explicitly, the @recoveryKey@@recovery_key@ is used in the @recover@ "client options":/realtime/usage#client-options as it contains both the @key@ and the last message @serial@. + +This private connection key can also be used by other REST clients to publish on behalf of this client. See the "publishing over REST on behalf of a realtime client documentation":/rest/channels#publish-on-behalf for more info. + +h6(#recovery-key). + default: recoveryKey + ruby: recovery_key + csharp: RecoveryKey + +The recovery key @String@ can be used by another client to recover this connection's state in the @recover@@Recover@ "client options":/realtime/usage#client-options propertymemberattribute. See "connection state recover options":#connection-state-recover-options for more information. + +h6(#serial). + default: serial + csharp: Serial + +The serial number @Integer@ of the last message to be received on this connection, used automatically by the library when recovering or resuming a connection. When recovering a connection explicitly, the @recoveryKey@@recovery_key@@RecoveryKey@ is used in the @recover@@Recover@ "client options":/realtime/usage#client-options as it contains both the @key@ and the last message @serial@. + +h2(#methods). + default: Connection Methods + swift,objc: ARTConnection Methods + ruby: Ably::Realtime::Connection Methods + java: io.ably.lib.realtime.Connection Methods + csharp: IO.Ably.Realtime.Connection Methods + +h6(#connect). + default: connect + csharp: Connect + +bq(definition). + default: connect() + ruby: "Deferrable":/realtime/types#deferrable connect -> yields "@Connection@":/realtime/connection + java: void connect() + csharp: void Connect() + +Explicitly calling @connect@@Connect@ is unnecessary unless the "@ClientOptions@":#client-options attribute @autoConnect@@auto_connect@@AutoConnect@ is @false@. Unless already @connected@ or @connecting@, this method causes the connection to open, entering the @connecting@ state. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from this method. + + On successfully connecting to Ably, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to this method yields a "@Connection@":/realtime/connection object. + + Failure to connect will trigger the errback callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object containing an error response as defined in the "Ably REST API":/rest-api#common documentation. + +h6(#close). + default: close + csharp: Close + +bq(definition). + default: close() + ruby: "Deferrable":/realtime/types#deferrable close -> yields "@Connection@":/realtime/connection + java: void close() + csharp: void Close() + +Causes the connection to close, entering the @closing@ state. Once @closed@, the library will not attempt to re-establish the connection without an explicit call to @connect()@@Connect()@@connect@":#connect. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from this method. + + On successfully closing the connection, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to this method yields a "@Connection@":/realtime/connection object. + + Failure to close the connection will trigger the errback callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object containing an error response as defined in the "Ably REST API":/rest-api#common documentation. + +h6(#on). + default: on + csharp: On + +There are threetwo overloaded versions of this method: + +bq(definition#on-state-listener). + default: on(String event, listener("ConnectionStateChange":#connection-state-change stateChange)) + ruby: on("ConnectionEvent":#connection-event *event) -> yields "ConnectionStateChange":#connection-state-change + java: void on("ConnectionEvent":#connection-event event, "ConnectionStateListener":#connection-state-listener listener) + objc,swift: on(event: "ARTRealtimeConnectionEvent":#connection-event, call: ("ARTConnectionStateChange":#connection-state-change?) -> Void) -> ARTEventListener + csharp: void On("ConnectionEvent":#connection-event event, Action<"ConnectionStateChange":#connection-state-change> action) + +Register the given listener blockfunctionaction for the specified "@ConnectionEvent@":#connection-event on the @Connection@. The listener is passed a "ConnectionStateChange":#connection-state-change object that contains the current state, previous state, and an optional reason for the event or state change.
+ +
+bq(definition). on(String[] events, listener("ConnectionStateChange":#connection-state-change stateChange)) + +Same as above, but registers multiple listeners, one for each event in the array. +
+ +bq(definition#on-listener). + default: on(listener("ConnectionStateChange":#connection-state-change stateChange)) + ruby: on -> yields "ConnectionStateChange":#connection-state-change + java: void on("ConnectionStateListener":#connection-state-listener listener) + objc,swift: on(call: ("ARTConnectionStateChange":#connection-state-change?) -> Void) -> ARTEventListener + csharp: void On(Action<"ConnectionStateChange":#connection-state-change> action) + +Register the given listener blockfunctionaction for all "ConnectionEvents":#connection-event on the @Connection@. The listener is passed a "ConnectionStateChange":#connection-state-change object that contains the current state, previous state, the event, and an optional reason for the event or state change. (For the @update@ event, the current and previous states will be the same).
+ +
+If an exception is thrown in the listener and bubbles up to the event emitter, it will be caught and logged at @error@ level, so as not to affect other listeners for the same event +
+ +h4. Parameters + +-
event(s)
:= the connection event(s) to subscribe to
__Type: @String@ or @String[]@__ +-
event
:= the connection event to subscribe to
__Type: "@ConnectionEvent@":#connection-event__ +-
event
:= the connection event to subscribe to
__Type: "@ConnectionEvent@":#connection-event__ +-
event
:= the connection event as a Symbol such as @:connected@ or @ConnectionEvent@ object to subscribe to
__Type: "@ConnectionEvent@":#connection-event__ +-
event
:= the connection event to subscribe to
__Type: "@ARTRealtimeConnectionEvent@":#connection-event__ + +-
listener
:= is a function of the form @function(stateChange)@ to be notified for matching events +-
listener
:= listener to be notified for matching events
__Type: "@ConnectionStateListener@":#connection-state-listener__ +-
action
:= action to be executed for matching events
__Type: "@ConnectionStateChange@":#connection-state-listener__ +-
&block
:= listener block that is yielded to for matching events +-
call
:= called with matching events + +h4. Example + +"(external) Browser @ConnectionState@ change example":<%= JsBins.url_for('realtime/connection-states') %> + +h6(#once). + default: once + csharp: Once + +There are two overloaded versions of this method: + +bq(definition#once-state-listener). + default: once(String event, listener("ConnectionStateChange":#connection-state-change stateChange)) + ruby: once("ConnectionEvent":#connection-event *event) -> yields "ConnectionStateChange":#connection-state-change + java: void once("ConnectionEvent":#connection-event event, "ConnectionStateListener":#connection-state-listener listener) + objc,swift: once(event: "ARTRealtimeConnectionEvent":#connection-event, call: ("ARTConnectionStateChange":#connection-state-change?) -> Void) -> ARTEventListener + csharp: void Once("ConnectionEvent":#connection-event event, Action<"ConnectionStateChange":#connection-state-change> action) + +Register the given listener blockfunctionaction for a single occurrence of the specified "ConnectionEvent":#connection-event on the @Connection@. Once the listener has been called, it is removed as a registered listener and will not be called again. The listener is passed a "ConnectionStateChange":#connection-state-change object that contains the current state, previous state, the event, and an optional reason for the state change. (For the @update@ event, the current and previous states will be the same). + +bq(definition#once-listener). + default: once(listener("ConnectionStateChange":#connection-state-change stateChange)) + ruby: once -> yields "ConnectionStateChange":#connection-state-change + java: void once("ConnectionStateListener":#connection-state-listener listener) + objc,swift: once(call: ("ARTConnectionStateChange":#connection-state-change?) -> Void) -> ARTEventListener + csharp: void Once(Action<"ConnectionStateChange":#connection-state-change> action) + +Register the given listener blockaction for a single occurrence of any "ConnectionEvent":#connection-event on the @Connection@. Once the listener has been called, it is removed as a registered listener and will not be called again. The listener is passed a "ConnectionStateChange":#connection-state-change object that contains the current state, previous state, and an optional reason for the state change. (For the @update@ event, the current and previous states will be the same). + +
+If an exception is thrown in the listener and bubbles up to the event emitter, it will be caught and logged at @error@ level, so as not to affect other listeners for the same event +
+ +h4. Parameters + +-
event(s)
:= the connection event(s) to subscribe to
__Type: @String@ or @String[]@__ +-
event
:= the connection event to subscribe to
__Type: "@ConnectionEvent@":#connection-event__ +-
event
:= the connection event to subscribe to
__Type: "@ConnectionEvent@":#connection-event__ +-
event
:= the connection event as a Symbol such as @:connected@ or @ConnectionEvent@ object to subscribe to
__Type: "@ConnectionEvent@":#connection-event__ +-
event
:= the connection event to subscribe to
__Type: "@ARTRealtimeConnectionEvent@":#connection-event__ + +-
listener
:= is a function of the form @function(stateChange)@ to be notified for a single occurrence of a matching event +-
listener
:= listener to be notified for a single occurrence of a matching state change
__Type: "@ConnectionStateListener@":#connection-state-listener__ +-
action
:= action to be executed for matching state changes
__Type: "@ConnectionStateChange@":#connection-state-listener__ +-
&block
:= listener block that is yielded to for a single occurrence of a matching event +-
call
:= called with matching events + +h6(#off). + default: off + csharp: Off + +There are sixtwo overloaded versions of this method: + +bq(definition#off-state-listener). + default: off(String event, listener) + ruby: off("ConnectionEvent":#connection-event *event, &block) + java: void off("ConnectionEvent":#connection-event event, "ConnectionStateListener":#connection-state-listener listener) + objc,swift: off(event: "ARTRealtimeConnectionEvent":#connection-event, listener: ARTEventListener) + csharp: void Off("ConnectionEvent":#connection-event event, Action<"ConnectionStateChange":#connection-state-change> action) + +Remove the given listener blockaction for the "ConnectionEvent":#connection-event. + +bq(definition#off-listener). + default: off(listener) + ruby: off(&block) + java: void off("ConnectionStateListener":#connection-state-listener listener) + objc,swift: off(listener: ARTEventListener) + csharp: void Off(Action<"ConnectionStateChange":#connection-state-change> action) + +Remove the given listener blockaction for all "ConnectionEvents":#connection-event + +
+bq(definition). off(String[] states, listener) + +Removes the given listener from all "ConnectionEvents":#connection-event in the array. + +bq(definition). off(String state) + +Removes all listeners for a given "@ConnectionEvent@s.":#connection-event + +bq(definition). off(String[] states) + +Removes all listeners for all "ConnectionEvents":#connection-event in the array. + +bq(definition). off() + +Removes all listeners (including both those registered against specific events and those registered without an event). +
+ +h4. Parameters + +-
event(s)
:= the connection event(s) to unsubscribe from
__Type: @String@ or @String[]@__ +-
event
:= the connection event to unsubscribe from
__Type: "@ConnectionEvent@":#connection-event__ +-
event
:= the connection event to subscribe to
__Type: "@ConnectionEvent@":#connection-event__ +-
event
:= the connection event as a Symbol such as @:connected@ or @ConnectionEvent@ object to unsubscribe from
__Type: "@ConnectionEvent@":#connection-event__ +-
event
:= the connection event to unsubscribe from
__Type: "@ARTRealtimeConnectionEvent@":#connection-event__ + +-
listener
:= is the listener function to be removed +-
listener
:= is the listener to be removed
__Type: "@ConnectionStateListener@":#connection-state-listener__ +-
action
:= action to be executed for matching event changes
__Type: "@ConnectioneventChangeEventArgs@":#connection-state-listener__ +-
&block
:= is the listener block to be removed +-
listener
:= previous return value from a @on@ or @once@ call + +h6(#ping). + default: ping + csharp: Ping + +bq(definition). + default: ping(callback("ErrorInfo":/realtime/types#error-info err, Number responseInMilliseconds)) + ruby: "Deferrable":/realtime/types#deferrable ping -> yields @Float seconds@ + java: void ping("CompletionListener":#completion-listener listener) + objc,swift: ping(callback: (ARTErrorInfo?) -> Void) + csharp: void Ping(Action callback) + +When connected, sends a heartbeat ping to the Ably server and executes the callback with any error and the response time in millisecondsexecutes the callbackcalls the listeneryields the elapsed time in seconds when a heartbeat ping request is echoed from the server. This can be useful for measuring true round-trip latency to the connected Ably server. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from this method. + + On successfully echoing a heartbeat from Ably, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to this method yields a @Float@ representing the time in seconds for the heartbeat ping request to be echoed. + + Failure to receive a heartbeat ping will trigger the errback callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object containing an error response as defined in the "Ably REST API":/rest-api#common documentation. + +h2(#related-types). Related types + +h3(#completion-listener). + java: io.ably.lib.realtime.CompletionListener + +blang[java]. + <%= partial partial_version('types/_completion_listener'), indent: 2, skip_first_indent: true %> + +h3(#connection-state). + default: ConnectionState + swift,objc: ARTRealtimeConnectionState + ruby: Connection::STATE Enum + java: io.ably.lib.realtime.ConnectionState Enum + csharp: IO.Ably.Realtime.ConnectionState Enum + +<%= partial partial_version('types/_connection_state') %> + +h3(#connection-event). + default: ConnectionEvent + swift,objc: ARTRealtimeConnectionEvent + ruby: Connection::EVENT Enum + java: io.ably.lib.realtime.ConnectionEvent Enum + csharp: IO.Ably.Realtime.ConnectionEvent Enum + +<%= partial partial_version('types/_connection_event') %> + +h3(#connection-state-listener). + java: io.ably.lib.realtime.ConnectionStateListener + +blang[java]. + <%= partial partial_version('types/_connection_state_listener'), indent: 2, skip_first_indent: true %> + +h3(#connection-state-change). + default: ConnectionStateChange Object + objc,swift: ARTConnectionStateChange + java: io.ably.lib.realtime.ConnectionStateListener.ConnectionStateChange + ruby: ConnectionStateChange + csharp: IO.Ably.Realtime.ConnectionStateChange + +<%= partial partial_version('types/_connection_state_change') %> + +h3(#last-connection-details). + javascript: LastConnectionDetails + +blang[javascript]. + <%= partial partial_version('types/_last_connection_details'), indent: 2, skip_first_indent: true %> diff --git a/content/realtime/versions/v1.1/encryption.textile b/content/realtime/versions/v1.1/encryption.textile new file mode 100644 index 0000000000..3bb493d4d6 --- /dev/null +++ b/content/realtime/versions/v1.1/encryption.textile @@ -0,0 +1,294 @@ +--- +title: Encryption +section: realtime +index: 70 +languages: + - javascript + - nodejs + - ruby + - java + - swift + - objc + - csharp +api_separator: +jump_to: + Help with: + - Getting started#getting-started + - Understanding encryption#encryption + API Reference: + - getDefaultParams#get-default-params + - generateRandomKey#generate-random-key + Related types: + - Channel Options + - CipherParams#cipher-params +--- + +Ably client libraries support built-in symmetric encryption of message content, making it easier to build apps that encrypt content fully end-to-end. Whilst "TLS is enabled by default":https://support.ably.io/solution/articles/3000045208-are-messages-sent-to-and-received-from-ably-sent-securely-using-tls and ensures that data is securely sent to and received from Ably, messages are not encrypted within the Ably system. Using the encryption feature of our client libraries ensures that message payloads are opaque, can never be decrypted by Ably, and can only be decrypted by other clients that share your secret key. + +h2(#getting-started). Getting started + +"Channels":/realtime/channels can be easily configured to automatically encrypt and decrypt all message payloads using the secret @key@ provided in the "channel options":/realtime/types#channel-options. Below is a simple example: + +```[javascript](code-editor:realtime/channel-encrypted) + Ably.Realtime.Crypto.generateRandomKey(function(err, key) { + var channelOpts = { cipher: { key: key } }; + var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', channelOpts); + channel.subscribe(function(message) { + alert('Decrypted data: ' + message.data); + }); + channel.publish('unencrypted', 'encrypted secret payload'); + }); +``` + +```[nodejs](code-editor:realtime/channel-encrypted) + Ably.Realtime.Crypto.generateRandomKey(function(err, key) { + var channelOpts = { cipher: { key: key } }; + var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', channelOpts); + channel.subscribe(function(message) { + console.log('Decrypted data: ' + message.data); + }); + channel.publish('unencrypted', 'encrypted secret payload'); + }); +``` + +```[ruby] + key = Ably::Util::Crypto.generateRandomKey() + channel_opts = { cipher: { key: key } } + channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', channel_opts) + channel.subscribe do |message| + puts "Decrypted data: #{message.data}" + end + channel.publish 'unencrypted', 'encrypted secret payload' +``` + +```[java] + ChannelOptions options = ChannelOptions.withCipherKey(); + Channel channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}", options); + channel.subscribe(new MessageListener() { + @Override + public void onMessage(Message message) { + System.out.println("Decrypted data: " + message.data); + } + }); + channel.publish('unencrypted', 'encrypted secret payload'); +``` + +```[csharp] + byte[] key = Crypto.GenerateRandomKey(); + ChannelOptions options = new ChannelOptions(key); + IRealtimeChannel channel = realtime.Channels.Get("{{RANDOM_CHANNEL_NAME}}", options); + channel.Subscribe(message => { + Console.WriteLine("Decrypted data: " + message.Data); + }); + channel.Publish("unencrypted", "encrypted secret payload"); +``` + +```[objc] + ARTChannelOptions *options = [[ARTChannelOptions alloc] initWithCipherKey:]; + ARTRealtimeChannel *channel = [realtime.channels get:@"{{RANDOM_CHANNEL_NAME}}" options:options]; + [channel subscribe:^(ARTMessage *message) { + NSLog(@"Decrypted data: %@", message.data); + }]; + [channel publish:@"unencrypted" data:@"encrypted secret payload"]; +``` + +```[swift] + let options = ARTChannelOptions(cipherKey: ) + let channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}", options: options) + channel.subscribe { message in + print("Decrypted data: \(message.data)") + } + channel.publish("unencrypted", data: "encrypted secret payload") +``` + +Note that all examples on this page assume you are running them within an EventMachine reactor. Find out more in our "Realtime usage documentation":/realtime/usage. + +Note that the @key@ should not be a pass-phrase, but a cryptographic key - generated from a secure random source, 128 or 256 bits long, binary or base64-encoded. If you wish to encrypt messages with a pass-phrase (for example, one entered by a user), you should use a "key derivation function":https://en.wikipedia.org/wiki/Key_derivation_function to transform that into a key. The client libraries are also capable of "generating a random key":#generate-random-key for you. + +h2(#understanding-encyrption). Understanding encryption + +The libraries support encryption purely as a convenience; the libraries ensure interoperability between environments by having compatible implementations of encryption algorithms and by making common choices on format, mode, padding etc. However, Ably intentionally does not manage the distribution of keys between clients, and end-to-end encryption is enabled without exposing keys to the Ably service at all. This has the advantage that Ably demonstrably has no access to the un-encrypted contents of your messages, but also means that each app is responsible for enabling the distribution of keys to clients independently of Ably. + +The client library support for encryption supports symmetric encryption only, and requires each participating client to each specify the correct "@CipherParams@":#cipher-params secret @key@ when creating a @Channel@ instance. Clients that do not specify a key will be delivered the still-encrypted message payloads that they may then still wish to decrypt offline. + +The client libraries are designed to be extensible, but initially only support the AES algorithm (with a default key length of 256 bits) and CBC mode. These defaults are intended to ensure that encryption support can be provided in all target environments and platforms. + +Encryption is supported for the @data@ attribute (payload) of "published messages":/realtime/types#messages and "presence member messages":/realtime/types#presence-message on a channel, over both REST and Realtime publish operations. Decryption is supported in Realtime "message":/realtime/messages and "presence message":/realtime/presence subscriptions and in REST and "Realtime history":/realtime/history operations. + +All other attributes of "messages":/realtime/types#messages and "presence messages":/realtime/types#presence-message, such as event @name@ or @clientId@@client_id@@ClientId@ remain un-encrypted. All sensitive data, when using the library's symmetric encryption, must be placed in the @data@ attribute to ensure it is encrypted before it is transmitted to Ably. + +The key in use at any given time is known by the client library, but the Ably service has no visibility of the key; it knows only that a given message payload was encrypted. When accessing messages via the "history API":/realtime/history, it is the caller's responsibility to ensure that the correct key is configured for the channel before the history request is made. + +Encryption options (algorithm, key, etc) are specified on a per-channel basis; it is expected that apps may wish to have both un-encrypted and encrypted channels on a single connection. + + +h1. Crypto API Reference + +inline-toc. + Methods: + - getDefaultParams#get-default-params + - generateRandomKey#generate-random-key + Related types: + - Channel Options + - CipherParams#cipher-params + +The @Ably.Realtime.@@Ably::Util::@@io.ably.lib.util.@@ART@@Crypto@ object exposes the following public methods: + +h2(#methods). Methods + +h6(#get-default-params). + default: getDefaultParams + ruby: get_default_params + csharp: GetDefaultParams + +bq(definition). + jsall: "CipherParams":#cipher-params Crypto.getDefaultParams(Object params) + ruby: "CipherParams":#cipher-params Crypto.get_default_params(Hash params) + java: "CipherParams":#cipher-params Crypto.getDefaultParams(Param[] params) + swift,objc: getDefaultParams(values: [NSObject : AnyObject]) -> "ARTCipherParams":#cipher-params + csharp: static CipherParams GetDefaultParams(byte[] key = null, byte[] iv = null, CipherMode? mode = null) + +This call obtains a "@CipherParams@":#cipher-params object using the values passed in (which must be a subset of @CipherParams@ fields that at a minimum includes a @key@), filling in any unspecified fields with default values, and checks that the result is a valid and self-consistent. + +You will rarely need to call this yourself, since the client library will handle it for you if you specify @cipher@ params when initializing a channel (as in the example "at the top":#getting-started) or when setting channel options with @channel.setOptions()@. + +h4. Parameters + +- paramsarguments := The cipher paramsarguments that you want to specify. It must at a minimum include a @key@, which should be either a binary (@byte[]@@ArrayBuffer@ or @WordArray@@Buffer@byte array@NSData@) or a base64-encoded @NS@@String@. + +h4. Returns + +On success, the method returns a complete "@CipherParams@":#cipher-params object. Failure will raise an "@AblyException@":/realtime/types/#ably-exception exception. + +h4. Example + +```[jsall](code-editor:realtime/channel-encrypted) + var cipherParams = Ably.Realtime.Crypto.getDefaultParams({key: }); + var channelOpts = { cipher: cipherParams }; + var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', channelOpts); +``` + +```[ruby] + cipher_params = Ably::Util::Crypto.get_default_params({key: }) + channel_opts = { cipher: cipher_params } + channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', channel_opts) +``` + +```[java] + CipherParams params = Crypto.getDefaultParams(new Param[]{ new Param("key", ) }); + ChannelOptions options = new ChannelOptions(); + options.encrypted = true; + options.cipherParams = params; + Channel channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}", options); +``` + +```[csharp] + var @params = Crypto.GetDefaultParams(); + ChannelOptions options = new ChannelOptions(@params); + var realtime = new AblyRealtime("{{API_KEY}}"); + var channel = realtime.Channels.Get("{{RANDOM_CHANNEL_NAME}}", options); +``` + +```[objc] +ARTCipherParams *params = [ARTCrypto getDefaultParams:@{@"key": }]; +ARTChannelOptions *options = [[ARTChannelOptions alloc] initWithCipher:params]; +ARTRealtimeChannel *channel = [realtime.channels get:@"{{RANDOM_CHANNEL_NAME}}" options:options]; +``` + +```[swift] +let params = ARTCrypto.getDefaultParams(["key": ]) +let options = ARTChannelOptions(cipher: params) +let channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}", options: options) +``` + +h6(#generate-random-key). + default: generateRandomKey + ruby: generate_random_key + csharp: GenerateRandomKey + +bq(definition). + default: Crypto.generateRandomKey(Int keyLength?, callback("ErrorInfo":/realtime/types#error-info err, @Buffer@ key)) + ruby: byte array Crypto.generate_random_key(Int key_length?) + java: @byte[]@ Crypto.generateRandomKey(Int keyLength?) + objc,swift: generateRandomKey(length?: UInt) -> NSData + csharp: static byte[] GenerateRandomKey(CipherMode? mode = null, int? keyLength = null) + +This call obtains a randomly-generated binary key of the specified key length. + +h4. Parameters + +- keyLengthkey_length := Optional @Int@ with the length of key to generate. For AES, this should be either 128 or 256. If unspecified, defaults to 256. + +-
mode
:= Optional AES @CipherMode@ which is used when the key is generated +-
callback
:= is a function of the form @function(err, key)@ which is called upon completion + +blang[jsall]. + h4. Callback result + + On successfully generating a key, the callback is called with that key as a @WordArray@@Buffer@, and @err@ is @null@. On failure to create a key, @err@ contains an "@ErrorInfo@":#error-info object describing the failure reason. + +blang[java,ruby,objc,swift,csharp]. + h4. Returns + + On success, the method returns the generated key as a @byte[]@ arraybyte array@NSData@. Failure will raise an "@AblyException@":/realtime/types/#ably-exception . + +h4. Example + +```[jsall] + Ably.Realtime.Crypto.generateRandomKey(256, function(err, key) { + if(err) { + console.log("Key generation failed: " + err.toString()); + } else { + var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', {cipher: {key: key}}); + } + }); +``` + +```[ruby] + key = Ably::Util::Crypto.generate_random_key(256) + channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', {cipher: {key: key}}) +``` + +```[java] + byte[] key = Crypto.generateRandomKey(256); + ChannelOptions options = ChannelOptions.withCipher(key); + Channel channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}", options); +``` + +```[csharp] + byte[] key = Crypto.GenerateRandomKey(keyLength: 256); + var options = new ChannelOptions(key); + var channel = realtime.Channels.Get("{{RANDOM_CHANNEL_NAME}}", options); +``` + +```[objc] + NSData *key = [ARTCrypto generateRandomKey:256]; + ARTChannelOptions *options = [[ARTChannelOptions alloc] initWithCipherKey:key]; + ARTRealtimeChannel *channel = [realtime.channels get:@"{{RANDOM_CHANNEL_NAME}}" options:options]; +``` + +```[swift] +let key = ARTCrypto.generateRandomKey(256) +let options = ARTChannelOptions(cipherWithKey: key) +let channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}", options: options) +``` + +h2(#related-types). Related types + +h3(#channel-options). + default: ChannelOptions Object + objc,swift: ARTChannelOptions + ruby: ChannelOptions Hash + java: io.ably.types.ChannelOptions + csharp: IO.Ably.ChannelOptions + +<%= partial partial_version('types/_channel_options') %> + +h3(#cipher-params). + default: CipherParams + objc,swift: ARTCipherParams + ruby: CipherParams Hash + java: io.ably.lib.util.Crypto.CipherParams + csharp: IO.Ably.CipherParams + +<%= partial partial_version('types/_cipher_params') %> diff --git a/content/realtime/versions/v1.1/history.textile b/content/realtime/versions/v1.1/history.textile new file mode 100644 index 0000000000..64bea8d240 --- /dev/null +++ b/content/realtime/versions/v1.1/history.textile @@ -0,0 +1,502 @@ +--- +title: History +section: realtime +index: 50 +languages: + - javascript + - nodejs + - ruby + - java + - swift + - objc + - csharp +api_separator: + Tutorials: + - History:/tutorials/history + - Vue.js with History:/tutorials/vue-tictactoe +jump_to: + Help with: + - Getting started + - Channel and Presence history#channel-and-presence + - Persisted history + - Continuous history + API reference: + - Message history#channel-history + - Presence history#presence-history + Types: + - Related Types#related-types +--- + +The Realtime client library provides message and presence event history for channels. Channel history can be used to return continuous message history up to the exact point a realtime channel was attached, and combines both instantaneous "live" history as well as the longer term "persisted history":#persisted-history. If "persisted history":#persisted-history is enabled for the channel, then messages will "typically be stored for 24 - 72 hours on disk":https://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for. If persisted history is not enabled, Ably retains the last two minutes of instantaneous "live" message history in memory. + +h2(#getting-started). Getting started + +The Ably Realtime client library provides a straightforward API to retrieve "paginated":#paginated-result message or presence event history. Each page of history, by default, contains up to 100 messages. Message ordering, by default, is from most recent to oldest. + +```[javascript](code-editor:realtime/channel-history) + var realtime = new Ably.Realtime('{{API_KEY}}'); + var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.publish('example', 'message data', function(err) { + channel.history(function(err, resultPage) { + var lastMessage = resultPage.items[0]; + alert('Last message: ' + lastMessage.id + ' - ' + lastMessage.data); + }); + }); +``` + +```[nodejs](code-editor:realtime/channel-history) + var realtime = new Ably.Realtime('{{API_KEY}}'); + var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.publish('example', 'message data', function(err) { + channel.history(function(err, resultPage) { + var lastMessage = resultPage.items[0]; + console.log('Last message: ' + lastMessage.id + ' - ' + lastMessage.data); + }); + }); +``` + +```[ruby] + realtime = Ably::Realtime.new('{{API_KEY}}') + channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}') + channel.publish 'example', 'message data' do + channel.history do |result_page| + last_message = result_page.items.last + puts "Last message: #{last_message.message.id} - #{last_message.data}") + end + end +``` + +```[java] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + Channel channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}"); + channel.publish("example", "message data", new CompletionListener() { + @Override + public void onError(ErrorInfo reason) { + System.out.println("Unable to publish message; err = " + reason.message); + } + @Override + public void onSuccess() { + PaginatedResult resultPage = channel.history(null); + Message lastMessage = resultPage.items[0]; + System.out.println("Last message: " + lastMessage.id + " - " + lastMessage.data); + } + }); +``` + +```[csharp] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + IRealtimeChannel channel = realtime.Channels.Get("{{RANDOM_CHANNEL_NAME}}"); + channel.Publish("example", "message data", async (success, error) => + { + PaginatedResult resultPage = await channel.HistoryAsync(null); + Message lastMessage = resultPage.Items[0]; + Console.WriteLine("Last message: " + lastMessage.Id + " - " + lastMessage.Data); + }); +``` + +```[objc] +ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"]; +ARTRealtimeChannel *channel = [realtime.channels get:@"RANDOM_CHANNEL_NAME"]; +[channel publish:@"example" data:@"message data" callback:^(ARTErrorInfo *error) { + if (error) { + NSLog(@"Unable to publish message; err = %@", error.message); + return; + } + [channel history:^(ARTPaginatedResult *resultPage, ARTErrorInfo *error) { + ARTMessage *lastMessage = resultPage.items[0]; + NSLog(@"Last message: %@ - %@", lastMessage.id,lastMessage.data); + }]; +}]; +``` + +```[swift] +let realtime = ARTRealtime(key: "{{API_KEY}}") +let channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}") +channel.publish("example", data: "message data") { error in + if let error = error { + print("Unable to publish message; err = \(error.message)") + return + } + channel.history { resultPage, error in + let lastMessage = resultPage!.items[0] as! ARTMessage + print("Last message: \(lastMessage.id) - \(lastMessage.data)") + } +} +``` + +Note that all examples on this page assume you are running them within an EventMachine reactor. Find out more in our "Realtime usage documentation":/realtime/usage. + +If you would prefer to just dive into code and see some examples of how to use history via the Realtime API, then we recommend you take a look at our "Realtime tutorials":/tutorials. + +h2(#channel-and-presence). Channel & Presence history + +Both the "@Channel@":/realtime/channels and "@Presence@":/realtime/presence objects provide history. The "@Channel@":/realtime/channels object provides the history of "@Message@":#message objects published on the channel, whereas the "@Presence@":/realtime/presence object provides presence event history of that channel i.e. members entering, updating or leaving the channel as "@PresenceMessage@":#presence-message objects. + +h3(#persisted-history). Enabling persistent history + +By default, persisted history on channels is disabled and messages are only stored by the Ably service for two minutes in memory. If persisted history is enabled for the channel, then messages will "typically be stored for 24 - 72 hours on disk":https://support.ably.io/solution/articles/3000030059. + +Every message that is persisted to or retrieved from disk counts as an extra message towards your monthly quote. 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. See the "documentation on channel rules":https://support.ably.io/solution/articles/3000030057 for further information on what they are and how to configure them. + +h3(#continuous-history). Continuous history + +By using "rewind":/realtime/channel-params#rewind or History's @untilAttach@, it is possible to obtain message history that is continuous with the realtime messages received on an attached channel. + +h4(#rewind). Rewind + +If you wish to obtain history as part of attaching to a channel, you can use the "rewind channel parameter":/realtime/channel-params#rewind. This will act as though you had attached to a channel from a certain message or time in the past, and play through all messages since that point. Check out the main "rewind documentation":/realtime/channel-params#rewind for further details. + +In current Ably libraries, or when using the "service without a library":https://www.ably.io/adapters, this is done by qualifying the channel name. There is a future library release planned in which it will be possible to specify channel params directly via the API. As an example, if the channel were @[some_metadata]my_channel@, you would specify the use of rewind via @[some_metadata?rewind=1]my_channel@. You can specify either a number of messages up to 100 to rewind to (for example @10@), or a time specifier of either seconds (@10s@), or minutes (@2m@) up to 2 minutes. + +*Note* that this will only work whilst attaching to a channel. + +```[javascript](code-editor:realtime/rewind) + var realtime = new Ably.Realtime('{{API_KEY}}'); + var channel = realtime.channels.get('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); + channel.subscribe(function(message) { + alert('Received: ' + message.data); + }); +``` + +```[nodejs](code-editor:realtime/rewind) + var Ably = require('ably'); + var realtime = new Ably.Realtime('{{API_KEY}}'); + var channel = realtime.channels.get('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); + channel.subscribe(function(message) { + console.log("Received: " message.data); + }); +``` + +```[ruby] + realtime = Ably::Realtime.new('{{API_KEY}}') + channel = realtime.channels.get('[?rewind=1]{{RANDOM_CHANNEL_NAME}}') + channel.subscribe do |message| + puts "Received: #{message.data}" + end +``` + +```[java] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + Channel channel = realtime.channels.get("[?rewind=1]{{RANDOM_CHANNEL_NAME}}"); + channel.subscribe(new MessageListener() { + @Override + public void onMessage(Message message) { + System.out.println("New messages arrived. " + message.name); + } + }); +``` + +```[csharp] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + IRealtimeChannel channel = realtime.Channels.Get("[?rewind=1]{{RANDOM_CHANNEL_NAME}}"); + channel.Subscribe(message => { + Console.WriteLine($"Message: {message.Name}:{message.Data} received"); + }); +``` + +```[objc] +ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"]; +ARTRealtimeChannel *channel = [realtime.channels get:@"[?rewind=1]{{RANDOM_CHANNEL_NAME}}"]; +[channel subscribe:^(ARTMessage *message) { + NSLog(@"Received: %@", message.data); +}]; +``` + +```[swift] +let realtime = ARTRealtime(key: "{{API_KEY}}") +let channel = realtime.channels.get("[?rewind=1]{{RANDOM_CHANNEL_NAME}}") +channel.subscribe { message in + print("Received: \(message.data)") +} +``` + +h4(#until-attach). History with untilAttach + +It is possible to obtain message history that is continuous with the realtime messages received on an attached channel, in the backwards direction from the point of attachment. When a @Channel@ instance is attached, it's automatically populated by the Ably service with the serial number of the last published message on the channel. As such, using this serial number, the client library is able to make a history request to the Ably service for all messages received since the channel was attached. Any new messages therefore are received in realtime via the attached channel, and any historical messages are accessible via the history method. + +In order to benefit from this functionality, the @untilAttach@ option can be used when making history requests on attached channels. If the channel is not yet attached, this will result in an error. + +```[javascript](code-editor:realtime/channel-history-until-attach) + var realtime = new Ably.Realtime('{{API_KEY}}'); + var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.attach(function(err) { + channel.history({ untilAttach: true}, function(err, resultPage) { + var lastMessage = resultPage.items[0]; + alert('Last message before attach: ' + lastMessage.data); + }); + }); +``` + +```[nodejs](code-editor:realtime/channel-history-until-attach) + var realtime = new Ably.Realtime('{{API_KEY}}'); + var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.attach(function(err) { + channel.history({ untilAttach: true}, function(err, resultPage) { + var lastMessage = resultPage.items[0]; + console.log('Last message before attach: ' + lastMessage.data); + }); + }); +``` + +```[ruby] + realtime = Ably::Realtime.new('{{API_KEY}}') + channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}') + channel.attach do + channel.history(until_attach: true) do |result_page| + last_message = result_page.items.last + puts "Last message before attach: #{last_message.data}") + end + end +``` + +```[java] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + Channel channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}"); + channel.on(ChannelState.attached, new ChannelStateListener() { + @Override + public void onChannelStateChanged(ChannelStateChange stateChange, ErrorInfo reason) { + Param[] options = new Param[]{ new Param("untilAttach", "true") }; + PaginatedResult resultPage = channel.history(options); + Message lastMessage = resultPage.items[0]; + System.out.println("Last message before attach: " + lastMessage.data); + } + }); + channel.attach(); +``` + +```[csharp] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + IRealtimeChannel channel = realtime.Channels.Get("{{RANDOM_CHANNEL_NAME}}"); + await channel.AttachAsync(); + PaginatedResult resultPage = await channel.HistoryAsync(untilAttach: true); + Message lastMessage = resultPage.Items[0]; + Console.WriteLine("Last message before attach: " + lastMessage.data); +``` + +```[objc] +ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"]; +ARTRealtimeChannel *channel = [realtime.channels get:@"RANDOM_CHANNEL_NAME"]; +[channel attach]; +[channel on:ARTChannelEventAttached call:^(ARTErrorInfo *error) { + ARTRealtimeHistoryQuery *query = [[ARTRealtimeHistoryQuery alloc] init]; + query.untilAttach = YES; + [channel history:query callback:^(ARTPaginatedResult *resultPage, ARTErrorInfo *error) { + ARTMessage *lastMessage = resultPage.items[0]; + NSLog(@"Last message: %@ - %@", lastMessage.id,lastMessage.data); + } error:nil]; +}]; +``` + +```[swift] +let realtime = ARTRealtime(key: "{{API_KEY}}") +let channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}") +channel.attach() +channel.on(.attached) { error in + let query = ARTRealtimeHistoryQuery() + query.untilAttach = true + try! channel.history(query) { resultPage, error in + let lastMessage = resultPage!.items[0] as! ARTMessage + print("Last message before attach: \(lastMessage.id) - \(lastMessage.data)") + } +} +``` + +h1. API reference + +inline-toc. + Channel: + - history(options)#channel-history + Presence: + - history(options)#presence-history + Related types: + - Message + - PresenceMessage#presence-message + - Presence action + - PaginatedResult#paginated-result + - Param + +h2. Channel object + +The "Realtime @Channel@ object":/realtime/channelsexposes the following public method to obtain "@Message@":#message history. + +h3. Methods + +h6(#channel-history). + default: history + csharp: History + +bq(definition). + default: history(Object option, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":#paginated-result<"Message":#message> resultPage)) + ruby: "Deferrable":/realtime/types#deferrable history(Hash option) -> yields "PaginatedResult":#paginated-result<"Message":#message> + java: "PaginatedResult":#paginated-result<"Message":#message> history("Param":#param[] option) + objc,swift: history(query: ARTRealtimeHistoryQuery?, callback: ("ARTPaginatedResult":#paginated-result<"ARTMessage":#message>?, ARTErrorInfo?) -> Void) throws + csharp: Task> HistoryAsync("PaginatedRequestParams":#paginated-request-params dataQuery, bool untilAttach = false); + +Gets a "paginated":#paginated-result set of historical messages for this channel. + +h4. Parameters + +- optionquery"Param":#param[] option"PaginatedRequestParams":#paginated-request-params query := an optional object containing the query parametersan optional set of key value pairs containing the query parameters, as specified below. + +-
callback
:= is a function of the form: @function(err, resultPage)@ +-
&block
:= yields a @PaginatedResult@ object +-
callback
:= called with a "ARTPaginatedResult":#paginated-result<"ARTMessage":#message> object or an error + +h4. @options@ parameters@ARTRealtimeHistoryQuery@ properties"@PaginatedRequestParams@":#paginated-request-params properties + +- start:startStart := _beginning of time_ earliest @DateTimeOffset@ or @Time@ or time in milliseconds since the epoch for any messages retrieved
__Type: @Long@@Int or @Time@@DateTimeOffset@__ +- end:endEnd := _current time_ latest @DateTimeOffset@ or @Time@ or time in milliseconds since the epoch for any messages retrieved
__Type: @Long@@Int or @Time@@DateTimeOffset@__ +- direction:directionDirection := _backwards_ @:@@forwards@ or @:@@backwards@
__Type: @String@@Symbol@@Direction@ enum__ +- limit:limitLimit := _100_ maximum number of messages to retrieve up to 1,000
__Type: @Integer@__ +- untilAttach:until_attach := _false_ when true, ensures message history is up until the point of the channel being attached. See "continuous history":#continuous-history for more info. Requires the @direction@ to be @backwards@ (the default). If the @Channel@ is not attached, or if @direction@ is set to @forwards@, this option will result in an error
__Type: @Boolean@__ + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @resultPage@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + On failure to retrieve message history, @err@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +blang[java]. + h4. Returns + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + Failure to retrieve the message history will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[csharp]. + h4. Returns + + Returns a @Task@ that needs to be awaited. + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@NextAsync@":#paginated-result and "@FirstAsync@":#paginated-result methods. + + Failure to retrieve the message history will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On success, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method yield a "PaginatedResult":#paginated-result that encapsulates an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + Failure to retrieve the message history will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object with the failure reason. + +h2. Presence object + +"Realtime @Presence@ object":/realtime/presence exposes the following public method to obtain presence event history such as enter, update and leave events. These events are represented as "@PresenceMessage@":#presence-message objects. + +h3. Methods + +h6(#presence-history). + default: history + csharp: History + +bq(definition). + default: history(Object option, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> resultPage)) + ruby: "Deferrable":/realtime/types#deferrable history(Hash option) -> yields "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> + java: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> history("Param":#param[] option) + objc,swift: history(query: ARTRealtimeHistoryQuery?, callback: ("ARTPaginatedResult":#paginated-result<"ARTPresenceMessage":#presence-message>?, ARTErrorInfo?) -> Void) throws + csharp: Task<"PaginatedResult":#paginated-result<"PresenceMessage":#presence-message>> HistoryAsync("PaginatedRequestParams":#paginated-request-params query, bool untilAttach = false) + +Gets a "paginated":#paginated-result set of historical presence events for this channel. + +h4. Parameters + +- optionquery"Param":#param[] option"PaginatedRequestParams":#paginated-request-params query := an optional object containing the query parametersan optional set of key value pairs containing the query parameters, as specified below. + +-
callback
:= is a function of the form: @function(err, resultPage)@ +-
&block
:= yields a @PaginatedResult@ object +-
callback
:= called with a "ARTPaginatedResult":#paginated-result<"ARTPresenceMessage":#presence-message> object or an error + +h4. @options@ parameters@ARTRealtimeHistoryQuery@ properties"@PaginatedRequestParams@":#paginated-request-params properties + +- start:startStart := _beginning of time_ earliest @DateTimeOffset@ or @Time@ or time in milliseconds since the epoch for any presence events retrieved
__Type: @Long@@Int or @Time@@DateTimeOffset@__ +- end:endEnd := _current time_ latest @DateTimeOffset@ or @Time@ or time in milliseconds since the epoch for any presence events retrieved
__Type: @Long@@Int or @Time@@DateTimeOffset@__ +- direction:directionDirection := _backwards_ @:@@forwards@ or @:@@backwards@
__Type: @String@@Symbol@@Direction@ enum__ +- limit:limitLimit := _100_ maximum number of presence events to retrieve up to 1,000
__Type: @Integer@__ +- untilAttach:until_attach := _false_ when true, ensures presence event history is up until the point of the channel being attached. See "continuous history":#continuous-history for more info. Requires the @direction@ to be @backwards@ (the default). If the @Channel@ is not attached, or if @direction@ is set to @forwards@, this option will result in an error
__Type: @Boolean@__ + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @resultPage@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + On failure to retrieve presence event history, @err@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +blang[java]. + h4. Returns + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + Failure to retrieve the presence event history will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[csharp]. + h4. Returns + + Returns a @Task@ that needs to be awaited. + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@NextAsync@":#paginated-result and "@FirstAsync@":#paginated-result methods. + + Failure to retrieve the presence event history will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On success, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method yield a "PaginatedResult":#paginated-result that encapsulates an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + Failure to retrieve the presence event history will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object with the failure reason. + +h2(#related-types). Related types + +h3(#message). + default: Message + swift,objc: ARTMessage + ruby: Ably::Models::Message Enum + java: io.ably.lib.types.Message + csharp: IO.Ably.Message + +<%= partial partial_version('types/_message') %> + +h3(#presence-message). + default: PresenceMessage + swift,objc: ARTPresenceMessage + ruby: Ably::Models::PresenceMessage Enum + java: io.ably.lib.types.PresenceMessage + csharp: IO.Ably.PresenceMessage + +<%= partial partial_version('types/_presence_message') %> + +h3(#presence-action). + default: Presence action + swift,objc: ARTPresenceAction + java: io.ably.lib.types.PresenceMessage.Action + ruby: Ably::Models::PresenceMessage::ACTION + csharp: IO.Ably.PresenceAction + +<%= partial partial_version('types/_presence_action') %> + +h3(#paginated-request-params). + csharp: IO.Ably.PaginatedRequestParams + +blang[csharp]. + <%= partial partial_version('types/_history_request_params'), indent: 2, skip_first_indent: true %> + +h3(#paginated-result). + default: PaginatedResult + swift,objc: ARTPaginatedResult + ruby: Ably::Models::PaginatedResult + java: io.ably.lib.types.PaginatedResult + csharp: IO.Ably.PaginatedResult + +<%= partial partial_version('types/_paginated_result') %> + +h3(#param). + java: io.ably.lib.types.Param + +blang[java]. + <%= partial partial_version('types/_param'), indent: 2, skip_first_indent: true %> diff --git a/content/realtime/versions/v1.1/index.textile b/content/realtime/versions/v1.1/index.textile new file mode 100644 index 0000000000..c88fb8da68 --- /dev/null +++ b/content/realtime/versions/v1.1/index.textile @@ -0,0 +1,74 @@ +--- +title: Realtime Client Library API +section: realtime +index: 0 +--- + +The Ably Realtime client library establishes and maintains a persistent connection to Ably enabling low latency broadcasting and receiving of messages and presence state. All official client library APIs are consistent across every language offering "pub/sub functionality":/realtime/channels, "presence":/realtime/presence, "token and basic authentication":/realtime/authentication, "active connection management":https://support.ably.io/solution/articles/3000044636-routing-around-network-and-dns-issues, "symmetric encryption":/realtime/encryption and access to "channel, presence and metrics history":/realtime/history. + +The Ably Realtime client library is available in "most popular languages and platforms":https://www.ably.io/download including "Javascript":https://github.com/ably/ably-js, "iOS":https://github.com/ably/ably-cocoa, "Android":https://github.com/ably/ably-java, "Java":https://github.com/ably/ably-java, ".NET":https://github.com/ably/ably-dotnet, "Node.js":https://github.com/ably/ably-js, "Ruby":https://github.com/ably/ably-ruby, "Go":https://github.com/ably/ably-go and "more...":https://www.ably.io/download + +"Download one of our Realtime client libraries now »":https://www.ably.io/download + +h2(#quick-intro). Quick intro to the Realtime library + +The Realtime library, when instanced, establishes a "WebSocket":/concepts/websockets connection to Ably. This "WebSocket":/concepts/websockets connection is both multiplexed, allowing multiple channels to share a single connection, and bi-directional, allowing realtime messages to be sent both to and from the Ably service without having to fall back to inefficient techniques like "HTTP polling":/concepts/long-polling. + +Ably organizes realtime data (messages) within applications into named channels that are the "unit" of distribution. A client attaches to a channel to subscribe to messages, and every message published on a channel is broadcast by Ably to all subscribers. This scalable and resilient messaging pattern is commonly called "pub/sub":https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern. + +Data published on a channel is packaged as a message by the library before being sent to the Ably service. The message can contain, in addition to a binary, string or JSON payload, an event @name@ and additional metadata in the @extras@ field. + +These concepts are illustrated in the diagram below: + + + Realtime client diagram + + +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. + +
+A client can subscribe to all messages on a channel by passing a listener function to the @subscribe@ method. The listener is passed a "@Message@":#message object for each message received. +
+A client can register for messages on a channel by implementing "MessageListener":#message-listener and calling the @subscribe(MessageListener listener)@ or @subscribe(String name, MessageListener listener)@ method. The listener is passed an array of one or more "@Message@":#message objects when messages are received. +
+A client can subscribe to all messages on a channel by passing a block to the @subscribe@ method. The block is passed a "@Message@":#message object for each message are received. +
+A client can subscribe to all messages on a channel by passing a lambda expression to the @Subscribe@ method. The lambda is passed a "@Message@":#message object for each message are received. +
+ +bc[jsall](code-editor:realtime/channel-publish). channel.subscribe(function(message) { + console.log('message received for event ' + message.name); + console.log('message data:' + message.data); +}); + +bc[java]. channel.subscribe(new MessageListener() { + @Override + public void onMessage(Message message) { + System.out.println("Message received: " + message.data); + } +}); + +bc[csharp]. channel.Subscribe(message => +{ + Console.WriteLine($"message received for event {message.Name}"); + Console.WriteLine($"message data: {message.Data}"); +}); + +bc[ruby]. channel.subscribe do |message| + puts "message received for event #{message.name}" + puts "message data: #{message.data}" +end + +bc[objc]. [channel subscribe:^(ARTMessage *message) { + NSLog(@"message received for event %@", message.name); + NSLog(@"message data: %@", message.data); +}]; + +bc[swift]. channel.subscribe { message in + print("message received for event \(message.name)") + print("message data: \(message.data)") +} + +Alternatively a listener may be registered so that it is called only for messages having a specific event name. + +bc[jsall]. channel.subscribe('myEvent', function(message) { + console.log('message received for event ' + message.name); + console.log('message data:' + message.data); +}); + +bc[java]. channel.subscribe("myEvent", new MessageListener() { + @Override + public void onMessage(Message message) { + System.out.println("Message received: " + message.data); + } +}); + +bc[csharp]. channel.Subscribe("myEvent", message => +{ + Console.WriteLine($"message received for event {message.Name}"); + Console.WriteLine($"message data: {message.Data}"); +}); + +bc[ruby]. channel.subscribe('myEvent') do |message| + puts "message received for event #{message.name}" + puts "message data: #{message.data}" +end + +bc[swift]. channel.subscribe("myEvent") { message in + print("message received for event \(message.name)") + print("message data: \(message.data)") +} + +bc[objc]. [channel subscribe:@"myEvent" callback:^(ARTMessage *message) { + NSLog(@"message received for event %@", message.name); + NSLog(@"message data: %@", message.data); +}]; + +Previously registered listeners can be removed individually or all together. + +```[jsall] +/* remove the listener registered for a single event */ +channel.unsubscribe('myEvent', myListener); + +/* remove the listener registered for all events */ +channel.unsubscribe(myListener); +``` + +```[java] +/* remove a single listener */ +channel.unsubscribe(myListener); + +/* remove the listener registered for all events */ +channel.unsubscribe("myEvent", myListener); +``` + +```[csharp] +/* remove a single listener */ +channel.Unsubscribe(myHandler); + +/* remove the listener registered for all events */ +channel.Unsubscribe("myEvent", myHandler); +``` + +```[ruby] +# remove the listener proc registered for a single event +channel.unsubscribe("myEvent", &my_proc) + +# remove the listener proc registered for all events +channel.unsubscribe(&my_proc) +``` + +```[objc] +// remove the listener registered for a single event +[channel unsubscribe:@"myEvent" listener:listener]; + +// remove the listener registered for all events +[channel unsubscribe:listener]; +``` + +```[swift] +// remove the listener registered for a single event +channel.unsubscribe("myEvent", listener: listener) + +// remove the listener registered for all events +channel.unsubscribe(listener) +``` + +h3(#message-publish). Publishing messages + +Channels expose a "@publish@@Publish@":/realtime/channels#publish method whereby a client can "publish":/realtime/channels#publish either a single message or an array of messages to a channel. A listener optionally passed in to the @publish@@Publish@ method enables the client to know whether or not the operation succeeded. + +bc[jsall](code-editor:realtime/channel-publish). channel.publish('event', 'This is my payload', function(err) { + if(err) { + console.log('Unable to publish message; err = ' + err.message); + } else { + console.log('Message successfully sent'); + } +}); + +bc[ruby]. deferrable = channel.publish('event', 'This is my payload') do + puts 'Messages successfully sent' +end +deferrable.errback do |err| + puts "Unable to publish messages; err = #{err}" +end + +bc[java]. channel.publish("event", "This is my payload", new CompletionListener() { + @Override + public void onError(ErrorInfo reason) { + System.out.println("Unable to publish message; err = " + reason.message); + } + @Override + public void onSuccess() { + System.out.println("Message successfully sent"); + } +}); + +bc[csharp]. channel.Publish("event", "payload", (success, error) => +{ + if (error != null) { + Console.WriteLine("Unable to publish message. Reason: " + error.Message); + } else { + Console.WriteLine("Message published successfully"); + } +}); + +bc[objc]. [channel publish:@"event" data:@"This is my payload" callback:^(ARTErrorInfo *error) { + if (error) { + NSLog(@"Unable to publish message; err = %@", error.message); + } else { + NSLog(@"Message successfully sent"); + } +}]; + +bc[swift]. channel.publish("event", data: "This is my payload") { error in + if let error = error { + print("Unable to publish message; err = \(error.message)") + } else { + print("Message successfully sent") + } +} + +blang[csharp]. + Channels also expose an async version @PublishAsync@ of the @Publish@ call which resumes execution once the message is confirmed received. It is purely for convenience. + + ```[csharp] + Result result = await channel.PublishAsync("event", "payload"); + if(result.IsFailure) { + Console.WriteLine("Unable to publish message. Reason: " + result.Error.Message); + } else { + Console.WriteLine("Message published successfully"); + } + ``` + +h4(#batch-publish). Batch publishing + +It is common for a single message to be intended for multiple channels. With a realtime connection, you can effectively send a message to multiple channels at once by allowing multiple concurrent publish operations. If you wish to send a message to multiple channels within a single operation, you can make use of the "REST batch API":/rest-api/beta#batch. + +h3(#message-history). Retrieving message history + +Channels expose a "@history@@History@":#history method providing a means for clients to obtain messages previously sent on the channel. Channel history can be used to return continuous message history up to the exact point a realtime channel was attached. + +History provides access to instantaneous "live" history as well as the longer term persisted history for attached channels. If "persisted history":/realtime/history#persisted-history is enabled for the channel, then messages will "typically be stored for 24 - 72 hours":https://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for. If persisted history is not enabled, Ably retains the last two minutes of message history in memory. + +The following example retrieves the first two pages of historical messages published up until the point the channel was attached. + +bc[jsall]. channel.attach(function() { + channel.history({ untilAttach: true }, function(err, resultPage) { + if(err) { + console.log('Unable to get channel history; err = ' + err.message); + } else { + console.log(resultPage.items.length + ' messages received in first page'); + if(resultPage.hasNext()) { + resultPage.next(function(err, nextPage) { ... }); + } + } + }); +}); + +bc[java]. Param[] options = new Param[]{ new Param("untilAttach", "true") }; +PaginatedResult resultPage = channel.history(options); +System.out.println(resultPage.items().length + " messages received in first page"); +if(resultPage.hasNext()) { + PaginatedResult nextPage = resultPage.next(); + System.out.println(nextPage.items().length + " messages received in second page"); +} + +bc[csharp]. PaginatedResult history = await channel.HistoryAsync(untilAttach: true); +Console.WriteLine($"{history.Items.Count} messages received in the first page"); +if (history.HasNext) +{ + PaginatedResult nextPage = await history.NextAsync(); + Console.WriteLine($"{nextPage.Items.Count} messages received in the second page"); +} + +bc[ruby]. channel.attach do + channel.history(until_attach: true) do |result_page| + puts "#{result_page.items.length} messages received in first page" + if result_page.has_next? + result_page.next { |next_page| ... } + end + end +end + +bc[objc]. [channel attach:^(ARTErrorInfo *error) { + ARTRealtimeHistoryQuery *query = [[ARTRealtimeHistoryQuery alloc] init]; + query.untilAttach = true; + [channel history:query callback:^(ARTPaginatedResult *resultPage, ARTErrorInfo *error) { + NSLog(@"%lu messages received in first page", (unsigned long)[resultPage.items count]); + if (resultPage.hasNext) { + [resultPage next:^(ARTPaginatedResult *nextPage, ARTErrorInfo *error) { + // ... + }]; + } + } error:nil]; +}]; + +bc[swift]. channel.attach { error in + let query = ARTRealtimeHistoryQuery() + query.untilAttach = true + try! channel.history(query) { resultPage, error in + let resultPage = resultPage! + print("\(resultPage.items.count) messages received in first page") + if resultPage.hasNext { + resultPage.next { nextPage, error in + // ... + } + } + } +} + +See the "history documentation":/realtime/history for further details of the supported query parameters. + +h1. Message API Reference + +inline-toc. + Properties: + - name#name + - data#data + - extras#extras + - id#id + - clientId#client-id + - connectionId#connection-id + - timestamp#timestamp + - encoding#encoding + Methods: + - fromEncoded#message-from-encoded + - fromEncodedArray#message-from-encoded-array + +h2(#properties). + default: Properties + java: Members + ruby: Attributes + python: Attributes + +A @Message@ represents an individual message that is sent to or received from Ably. + +h6(#name). + default: name + csharp: Name + +The event name, if provided.
__Type: @String@__ + +h6(#data). + default: data + csharp: Data + +The message payload, if provided.
__Type: @String@, @StringBuffer@, @JSON Object@@String@, @ByteArray@, @JSONObject@, @JSONArray@@String@, @byte[]@, @plain C# object that can be serialized to JSON@@String@, @Binary@ (ASCII-8BIT String), @Hash@, @Array@@String@, @Bytearray@, @Dict@, @List@@String@, @Binary String@, @Associative Array@, @Array@@NSString *@, @NSData *@, @NSDictionary *@, @NSArray *@@String@, @NSData@, @Dictionary@, @Array@__ + +h6(#extras). + default: extras + csharp: Extras + +Metadata and/or ancillary payloads, if provided. The only currently valid payload for extras is the "@push@":/general/push/publish#channel-broadcast-example object.
__Type: @JSONObject@, @JSONArray@plain C# object that can be converted to JSON@JSON Object@@Hash@, @Array@@Dict@, @List@@Dictionary@, @Array@@NSDictionary *@, @NSArray *@@Associative Array@, @Array@__ + +h6(#id). + default: id + csharp: Id + +A Unique ID assigned by Ably to this message.
__Type: @String@__ + +h6(#client-id). + default: clientId + csharp: ClientId + ruby: client_id + python: client_id + +The client ID of the publisher of this message.
__Type: @String@__ + +h6(#connection-id). + default: connectionId + csharp: ConnectionId + ruby: connection_id + python: connection_id + +The connection ID of the publisher of this message.
__Type: @String@__ + +h6(#timestamp). + default: timestamp + csharp: Timestamp + +Timestamp when the message was received by the Ably, as milliseconds since the epocha @Time@ object
.__Type: @Integer@@Long Integer@@DateTimeOffset@@Time@@NSDate@__ + +h6(#encoding). + default: encoding + csharp: Encoding + +This will typically be empty as all messages received from Ably are automatically decoded client-side using this value. However, if the message encoding cannot be processed, this attribute will contain the remaining transformations not applied to the @data@ payload.
__Type: @String@__ + +h2(methods). + default: Message methods + +h6(#message-from-encoded). + default: Message.fromEncoded + +bq(definition). + default: Message.fromEncoded(Object encodedMsg, ChannelOptions channelOptions?) -> Message + +A static factory method to create a "@Message@":/realtime/types#message from a deserialized @Message@-like object encoded using Ably's wire protocol. + +h4. Parameters + +- encodedMsg := a @Message@-like deserialized object.
__Type: @Object@__ +- channelOptions := an optional "@ChannelOptions@":/realtime/types#channel-options. If you have an encrypted channel, use this to allow the library can decrypt the data.
__Type: @Object@__ + +h4. Returns + +A "@Message@":/realtime/types#message object + +h6(#message-from-encoded-array). + default: Message.fromEncodedArray + +bq(definition). + default: Message.fromEncodedArray(Object[] encodedMsgs, ChannelOptions channelOptions?) -> Message[] + +A static factory method to create an array of "@Messages@":/realtime/types#message from an array of deserialized @Message@-like object encoded using Ably's wire protocol. + +h4. Parameters + +- encodedMsgs := an array of @Message@-like deserialized objects.
__Type: @Array@__ +- channelOptions := an optional "@ChannelOptions@":/realtime/types#channel-options. If you have an encrypted channel, use this to allow the library can decrypt the data.
__Type: @Object@__ + +h4. Returns + +An @Array@ of "@Message@":/realtime/types#message objects diff --git a/content/realtime/versions/v1.1/presence.textile b/content/realtime/versions/v1.1/presence.textile new file mode 100644 index 0000000000..f44a674c29 --- /dev/null +++ b/content/realtime/versions/v1.1/presence.textile @@ -0,0 +1,1199 @@ +--- +title: Presence +section: realtime +index: 40 +languages: + - javascript + - nodejs + - ruby + - java + - swift + - objc + - csharp +api_separator: +jump_to: + Help with: + - Getting started#getting-started + - Presence & events#presence + - Member data + - Presence member list#member-list + - Presence history + - Multiple client IDs#presence-multiple-client-id + - Handling failures#handling-failures + Presence API properties: + - syncComplete#sync-complete + Presence API methods: + - enter + - leave + - update + - get + - history + - subscribe + - unsubscribe + - enterClient#enter-client + - leaveClient#leave-client + - updateClient#update-client + Types: + - Related Types#related-types +--- + +Presence enables clients to be aware of other clients that are currently "present" on a channel. Each member present on a channel has a unique self-assigned client identifier and system-assigned connection identifier, along with an optional payload that can be used to describe the member's status or attributes. Presence allows you to quickly build apps such as chat rooms and multiplayer games by automatically keeping track of who is present in real time across any device. + +h2(#getting-started). Getting started + +The @Presence@ object provides a straightforward API to subscribe to presence events such as members entering or leaving, retrieve a list of members present, or register the connected client as "present" on a channel. Here is a simple presence example using the "@presence@@Presence@":/realtime/channels#presence propertyfieldattribute of the "@Channel@":/realtime/channels object to enter a channel and subscribe to presence events. + +```[javascript](code-editor:realtime/presence-simple) + var realtime = new Ably.Realtime({ + key: '{{API_KEY}}', + clientId: 'bob' } + ); + var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.presence.subscribe('enter', function(member) { + alert('Member ' + member.clientId + ' entered'); + }); + channel.presence.enter(); +``` + +```[nodejs](code-editor:realtime/presence-simple) + var Ably = require('ably'); + var realtime = new Ably.Realtime({ + key: '{{API_KEY}}', + clientId: 'bob' } + ); + var channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.presence.subscribe('enter', function(member) { + console.log('Member ' + member.clientId + ' entered'); + }); + channel.presence.enter(); +``` + +```[ruby] + realtime = Ably::Realtime.new(key: '{{API_KEY}}', client_id: 'bob') + channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}') + channel.presence.subscribe(:enter) do |member| + puts "Member #{member.client_id} entered" + end + channel.presence.enter +``` + +```[java] + ClientOptions options = new ClientOptions("{{API_KEY}}"); + options.clientId = "bob"; + AblyRealtime realtime = new AblyRealtime(options); + Channel channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}"); + channel.presence.subscribe(new PresenceListener() { + @Override + public void onPresenceMessage(PresenceMessage member) { + System.out.println("Member " + member.clientId + " : " + member.action.toString()); + } + }); + channel.presence.enter(null, new CompletionListener()); +``` + +```[csharp] + ClientOptions options = new ClientOptions("{{API_KEY}}") { ClientId = "bob"}; + AblyRealtime realtime = new AblyRealtime(options); + IRealtimeChannel channel = realtime.Channels.Get("{{RANDOM_CHANNEL_NAME}}"); + channel.Presence.Subscribe(member => { + Console.WriteLine("Member " + member.ClientId + " : " + member.Action); + }); + await channel.Presence.EnterAsync(null); +``` + +```[objc] + ARTClientOptions *options = [[ARTClientOptions alloc] initWithKey:@"{{API_KEY}}"]; + options.clientId = @"bob"; + ARTRealtime *realtime = [[ARTRealtime alloc] initWithOptions:options]; + ARTRealtimeChannel *channel = [realtime.channels get:@"{{RANDOM_CHANNEL_NAME}}"]; + [channel.presence subscribe:ARTPresenceEnter callback:^(ARTPresenceMessage *member) { + NSLog(@"Member %@ entered", member.clientId); + }]; + [channel.presence enter:nil]; +``` + +```[swift] + let options = ARTClientOptions(key: "{{API_KEY}}") + options.clientId = "bob" + let realtime = ARTRealtime(options: options) + let channel = realtime.channels.get("{{RANDOM_CHANNEL_NAME}}") + channel.presence.subscribe(.enter) { member in + print("Member \(member.clientId) entered") + } + channel.presence.enter(nil) +``` + +Note that all examples on this page assume you are running them within an EventMachine reactor. Find out more in our "Realtime usage documentation":/realtime/usage. + +If you would prefer to just dive into code and see some examples of how to use presence via the Realtime API, then we recommend you take a look at our "Realtime tutorials":/tutorials. + +h2(#presence). Presence + +In order to be present on a channel, a client must "be identified by having a client ID":https://support.ably.io/solution/articles/3000038608-authenticated-and-identified-clients, "have permission to be present":https://support.ably.io/solution/articles/3000048535-using-capabilities-to-manage-client-access-privileges-on-channels, and be attached to the channel. For simplicity, the library will implicitly attach to a channel when entering or subscribing to presence events. Clients are assigned a "@clientId@":/realtime/usage#client-id"@client_id@":/realtime/usage#client-id"@ClientId@":/realtime/usage#client-id when using token authentication, "find out more about token authentication":/core-features/authentication#token-authentication. + +A single "@clientId@":/realtime/usage#client-id"@client_id@":/realtime/usage#client-id"@ClientId@":/realtime/usage#client-id may be present multiple times on the same channel via different client connections. As far as Ably is concerned, these are different members of the presence set for the channel, however they will be differentiated by their unique "@connectionId@@ConnectionId@@connection_id@":/realtime/connection/#id. For example, if a client with ID "Sarah" is connected to a chat channel on both a desktop and a mobile device simultaneously, "Sarah" will be present twice in the presence member set with the same client ID, yet will have two unique connection IDs. A member of the presence set is therefore unique by the combination of the "@clientId@":/realtime/usage#client-id"@client_id@":/realtime/usage#client-id"@ClientId@":/realtime/usage#client-id and "@connectionId@@ConnectionId@@connection_id@":/realtime/connection/#id strings. + +h3(#presence-states). Presence states and events + +<%= partial partial_version('shared/_presence_states') %> + +"View a presence states and events example":<%= JsBins.url_for('realtime/presence-events') %> + +h3(#member-data). Member data + +In addition to the "@clientId@":/realtime/usage#client-id"@client_id@":/realtime/usage#client-id"@ClientId@":/realtime/usage#client-id for members on a channel, it is also possible to include data when entering a channel. Clients can "update":#update their data at any point which will be broadcasted to all presence subscribers as an @:update@@Action.UPDATE@@update@ event. + +```[jsall] +/* Subscribe to presence enter events */ +channel.presence.subscribe('enter', function(member) { + console.log(member.data); // => not moving +}); + +/* Subscribe to presence update events */ +channel.presence.subscribe('update', function(member) { + console.log(member.data); // => travelling North +}); + +/* Enter this client with data and update once entered */ +channel.presence.enter('not moving', function(err) { + channel.presence.update('travelling North'); +}); +``` + +```[java] +/* Subscribe to presence enter and update events */ +channel.presence.subscribe(new PresenceListener() { + @Override + public void onPresenceMessage(PresenceMessage member) { + switch (member.action) { + case ENTER: { + System.out.println(member.data); // => not moving + break; + } + case UPDATE: { + System.out.println(member.data); // => travelling North + break; + } + } + } +}); + +/* Enter this client with data and update once entered */ +channel.presence.enter("not moving", new CompletionListener() { + @Override + public void onSuccess() { + channel.presence.update("travelling North", new CompletionListener()); + } +}); +``` + +```[csharp] +/* Subscribe to presence enter and update events */ +channel.Presence.Subscribe(member => +{ + switch (member.Action) + { + case PresenceAction.Enter: + case PresenceAction.Update: + { + Console.WriteLine(member.Data); // => travelling North + break; + } + } +}); + +/* Enter this client with data and update once entered */ +await channel.Presence.EnterAsync("not moving"); +await channel.Presence.UpdateAsync("travelling North"); +``` + +```[ruby] +# Subscribe to presence enter events +channel.presence.subscribe(:enter) do |member| + puts member.data # => not moving +end + +# Subscribe to presence update events +channel.presence.subscribe(:update) do |member| + puts member.data # => travelling North +end + +# Enter this client with data and update once entered +channel.presence.enter(data: 'not moving') do + channel.presence.update(data: 'travelling North') +end +``` + +```[objc] +// Subscribe to presence enter events +[channel.presence subscribe:ARTPresenceEnter callback:^(ARTPresenceMessage *member) { + NSLog(@"%@", member.data); // prints "not moving" +}]; + +// Subscribe to presence update events +[channel.presence subscribe:ARTPresenceUpdate callback:^(ARTPresenceMessage *member) { + NSLog(@"%@", member.data); // prints "travelling North" +}]; + +// Enter this client with data and update once entered +[channel.presence enter:@"not moving" callback:^(ARTErrorInfo *error) { + [channel.presence update:@"travelling North"]; +}]; +``` + +```[swift] +// Subscribe to presence enter events +channel.presence.subscribe(.enter) { member in + print(member.data) // prints "not moving" +} + +// Subscribe to presence update events +channel.presence.subscribe(.update) { member in + print(member.data) // prints "travelling North" +} + +// Enter this client with data and update once entered +channel.presence.enter("not moving") { error in + channel.presence.update("travelling North") +} +``` + +h3(#member-list). Presence member list + +The @Presence@ object exposes a "@get@@Get@":#get method allowing a client to retrieve an array of all members present on the channel. The Ably client is responsible for keeping track of the presence set from the time that the channel is attached; an up to date presence set is pushed to the client following attach and the presence set is updated on each subsequent presence event. Thus "@get@@Get@":#get returns the already-known presence set retained in memory and does not trigger a new request to the Ably service. + +```[jsall] +channel.presence.get(function(err, members) { + console.log('There are ' + members.length + ' members on this channel'); + console.log('The first member has client ID: ' + members[0].clientId); +}); +``` + +```[ruby] +channel.presence.get do |members| + puts "There are #{members.size} members on this channel" + puts "The first member has client ID: #{members.first.client_id}" +end +``` + +```[java] +PresenceMessage[] members = channel.presence.get(); +System.out.println("There are " + members.length + " members on this channel"); +System.out.println("The first member has client ID: " + members[0].clientId); +``` + +```[csharp] +IEnumerable presence = await channel.Presence.GetAsync(); +Console.WriteLine($"There are {presence.Count()} members on this channel"); +Console.WriteLine($"The first member has client ID: {presence.First().ClientId}"); +``` + +```[objc] +[channel.presence get:^(NSArray *members, ARTErrorInfo *error) { + NSLog(@"There are %lu members on this channel", [members count]); + NSLog(@"The first member has client ID: %@", members[0].clientId); +}]; +``` + +```[swift] +channel.presence.get { members, error in + print("There are \(members.count) members on this channel") + print("The first member has client ID: \(members[0].clientId)") +} +``` + +h3(#presence-history). Presence History + +The @Presence@ object exposes a "@history@@History@":#history method allowing a client to retrieve historical presence events on the channel. Presence history can be used to return continuous presence event history up to the exact point a realtime channel was attached. + +History provides access to instantaneous "live" history as well as the longer term persisted history for presence channels. If "persisted history":/realtime/history#persisted-history is enabled for the channel, then presence events will "typically be stored for 24 - 72 hours":https://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for. If persisted history is not enabled, Ably retains the last two minutes of presence event history in memory. + +The following example retrieves the first two pages of historical presence events published up until the point the channel was attached. + +bc[jsall]. channel.attach(function() { + var presence = channel.presence; + presence.history({ untilAttach: true }, function(err, resultPage) { + if(err) { + console.log('Unable to get presence history; err = ' + err.message); + } else { + console.log(resultPage.items.length + ' presence events received in first page'); + if(resultPage.hasNext()) { + resultPage.next(function(err, nextPage) { ... }); + } + }); +}); + +bc[ruby]. channel.attach do + presence = channel.presence + presence.history(until_attach: true) do |result_page| + puts "#{result_page.items.length} presence events received in first page" + if result_page.has_next? + result_page.next { |next_page| ... } + end + end +end + +```[java] +Param[] options = new Param[]{ new Param("untilAttach", "true") } +PaginatedResult resultPage = channel.presence.history(options); +System.out.println(resultPage.items().length + " presence events received in first page"); +if(resultPage.hasNext()) { + PaginatedResult nextPage = resultPage.next(); + System.out.println(nextPage.items().length + " presence events received in 2nd page"); +} +``` + +```[csharp] +PaginatedResult resultPage; +resultPage = await channel.Presence.HistoryAsync(untilAttach: true); +Console.WriteLine(resultPage.Items.Count + " presence events received in first page"); +if (resultPage.HasNext) +{ + PaginatedResult nextPage = await resultPage.NextAsync(); + Console.WriteLine(nextPage.Items.Count + " presence events received in 2nd page"); +} +``` + +```[objc] +ARTRealtimeHistoryQuery *query = [[ARTRealtimeHistoryQuery alloc] init]; +query.untilAttach = YES; +[channel.presence history:query callback:^(ARTPaginatedResult *resultPage, + ARTErrorInfo *error) { + NSLog(@"%lu presence events received in first page", [resultPage.items count]); + if (resultPage.hasNext) { + [resultPage next:^(ARTPaginatedResult *nextPage, ARTErrorInfo *error) { + NSLog(@"%lu presence events received in 2nd page", [nextPage.items count]); + }]; + } +}]; +``` + +```[swift] +let query = ARTRealtimeHistoryQuery() +query.untilAttach = true +channel.presence.history(query) { resultPage, error in + let resultPage = resultPage! + print("\(resultPage.items.count) presence events received in first page") + if resultPage.hasNext { + resultPage.next { nextPage, error in + print("\(nextPage!.items.count) presence events received in 2nd page") + } + } +} +``` + +See the "presence history documentation":/realtime/history#presence-history for further details of the supported query parameters. + +h3(#presence-multiple-client-id). Managing multiple client IDs + +p(tip). Each unique "@clientId@":/realtime/usage#client-id"@client_id@":/realtime/usage#client-id"@ClientId@":/realtime/usage#client-id may only be present once when entering on behalf of another client as the unique identifier for each member in a presence set is the combined "@clientId@":/realtime/usage#client-id"@client_id@":/realtime/usage#client-id"@ClientId@":/realtime/usage#client-id and shared "@connectionId@@ConnectionId@@connection_id@":/realtime/connection/#id
+ +An Ably client instance might, if on an application server for example, publish messages and be present on channels on behalf of multiple distinct "client IDs":/realtime/usage#client-id. The channel's @Presence@ object therefore also supports methods that enable presence messages to be emitted for a "@clientId@":/realtime/usage#client-id"@client_id@":/realtime/usage#client-id"@ClientId@":/realtime/usage#client-id specified at the time of the call, rather than implicitly based on the "@clientId@":/realtime/usage#client-id"@client_id@":/realtime/usage#client-id"@ClientId@":/realtime/usage#client-id "specified when the library is instanced or authenticated":/realtime/usage#client-id. + +In order to be able to publish presence changes for arbitrary client IDs, the client library must have been instanced either with an "API key":https://support.ably.io/solution/articles/3000030054-what-is-an-app-api-key, or with a "token bound to a wildcard client ID":https://support.ably.io/solution/articles/3000048586-can-a-client-emulate-any-client-id-i-e-use-a-wildcard-client-id. + +```[jsall](code-editor:realtime/presence-multiple-client-id) +var rest = new Ably.Rest({ key: '{{API_KEY}}' }); +/* request a wildcard token */ +rest.auth.requestToken({ clientId: '*' }, function(err, token) { + var realtime = new Ably.Realtime({ token: token }); + var channel = realtime.channels.get('realtime-chat'); + + channel.presence.subscribe('enter', function(member) { + console.log(member.client_id + 'entered realtime-chat'); + }); + + channel.presence.enterClient('Bob'); // => Bob entered realtime-chat + channel.presence.enterClient('Mary'); // => Mary entered realtime-chat +}); +``` + +```[ruby] +rest = Ably::Rest.new(key: '{{API_KEY}}') +# request a wildcard token +rest.auth.requestToken(clientId: '*') do |token| + realtime = Ably::Realtime.new(token: token) + channel = realtime.channels.get('realtime-chat') + + channel.presence.subscribe(:enter) do |member| + puts "#{member.client_id} entered realtime-chat" + end + + channel.presence.enter_client 'Bob' # => Bob entered realtime-chat + channel.presence.enter_client 'Mary' # => Mary entered realtime-chat +end +``` + +```[java] +/* request a wildcard token */ +AblyRest rest = new AblyRest('{{API_KEY}}'); +TokenParams params = new TokenParams(); +params.clientId = "*"; +ClientOptions options = new ClientOptions(); +options.tokenDetails = rest.auth.requestToken(params, null); + +AblyRealtime realtime = new AblyRealtime(options); +Channel channel = realtime.channels.get("realtime-chat"); + +channel.presence.subscribe(new PresenceListener() { + @Override + public void onPresenceMessage(PresenceMessage member) { + System.out.println(member.clientId + " entered realtime-chat"); + } +}); + +CompletionListener noop = new CompletionListener(); +channel.presence.enterClient("Bob", noop); /* => Bob entered realtime-chat */ +channel.presence.enterClient('Mary', noop); /* => Mary entered realtime-chat */ +``` + +```[csharp] +/* request a wildcard token */ +AblyRest rest = new AblyRest("{{API_KEY}}"); +TokenParams tokenParams = new TokenParams() { ClientId = "*"}; +ClientOptions options = new ClientOptions(); +options.TokenDetails = await rest.Auth.RequestTokenAsync(tokenParams, null); + +AblyRealtime realtime = new AblyRealtime(options); +IRealtimeChannel channel = realtime.Channels.Get("realtime-chat"); + +channel.Presence.Subscribe(member => { + Console.WriteLine(member.ClientId + " entered realtime-chat"); +}); + +await channel.Presence.EnterClientAsync("Bob", null); /* => Bob entered realtime-chat */ +await channel.Presence.EnterClientAsync("Mary", null); /* => Mary entered realtime-chat */ +``` + +```[objc] +ARTRest* rest = [[ARTRest alloc] initWithKey:@"{{API_KEY}}"]; +// request a wildcard token +ARTTokenParams *tokenParams = [[ARTTokenParams alloc] initWithClientId:@"*"]; +[rest.auth requestToken:tokenParams withOptions:nil callback:^(ARTTokenDetails *tokenDetails, + NSError *error) { + ARTRealtime *realtime = [[ARTRealtime alloc] initWithToken:tokenDetails.token]; + ARTRealtimeChannel *channel = [realtime.channels get:@"realtime-chat"]; + + [channel.presence subscribe:ARTPresenceEnter callback:^(ARTPresenceMessage *member) { + NSLog(@"%@ entered realtime-chat", member.clientId); + }]; + + [channel.presence enterClient:@"Bob" data:nil]; // prints 'Bob entered realtime-chat' + [channel.presence enterClient:@"Mary" data:nil]; // prints 'Mary entered realtime-chat' +}]; +``` + +```[swift] +let rest = ARTRest(key: "{{API_KEY}}") +// request a wildcard token +rest.auth.requestToken(ARTTokenParams(clientId: "*"), withOptions: nil) { tokenDetails, error in + let realtime = ARTRealtime(token: tokenDetails!.token) + let channel = realtime.channels.get("realtime-chat") + + channel.presence.subscribe(.enter) { member in + print("\(member.clientId) entered realtime-chat") + } + + channel.presence.enterClient("Bob", data: nil) // prints 'Bob entered realtime-chat' + channel.presence.enterClient("Mary", data: nil) // prints 'Mary entered realtime-chat' +} +``` + +h3(#handling-failures). Handling transient channel/connection failures + +Any time a channel is re-attached and the presence set is re-synced, e.g. after a short disconnection, the client will check whether any members it has entered into the presence set are there. If not, it will automatically re-enter them. This means that if a channel loses continuity (for example, because a client was disconnected from Ably for more than two minutes before reconnecting), then after the channel automatically re-attaches, any presence members it had previously entered will be restored. + +The exception is if you use the "recover":/realtime/connection#connection-state-recovery feature to resume a previous connection with a fresh client library instance (for example, to have continuity over a page refresh). In that case you will need to explicitly re-enter presence after you re-attach to the channel, due to the loss of client library internal state. + +h1. API Reference + +inline-toc. + Properties: + - syncComplete#sync-complete + Methods: + - enter()#enter + - leave()#leave + - update()#update + - get()#get + - history()#history + - subscribe()#subscribe + - unsubscribe()#unsubscribe + - enterClient()#enter-client + - leaveClient()#leave-client + - updateClient()#update-client + Related types: + - PresenceMessage#presence-message + - Presence action + - CompletionListener#completion-listener + - PaginatedResult#paginated-result + - Param + - PresenceListener#presence-listener + +h2(#properties). + default: Presence Properties + objc,swift: ARTPresence Properties + ruby: Ably::Realtime::Presence Attributes + java: io.ably.lib.realtime.Presence Members + csharp: IO.Ably.Realtime.Presence Properties + +The @Presence@ object exposes the following public propertiesattributesmembers: + +h6(#sync-complete). + default: syncComplete + ruby: sync_complete? + csharp: SyncComplete + +A @boolean@ field indicating whether the presence member set is synchronized with server after a channel attach. +When a channel is attached, the Ably service immediately synchronizes the presence member set with the client. Typically this process completes in milliseconds, however when the presence member set is very large, bandwidth constraints may slow this synchronization process down. + +h2. Methods + +h6(#enter). + default: enter + csharp: EnterAsync + +In order to enter and be present on a channel, the client must "be identified by having a client ID":https://support.ably.io/solution/articles/3000038608-authenticated-and-identified-clients, "have permission to be present":https://support.ably.io/solution/articles/3000048535-using-capabilities-to-manage-client-access-privileges-on-channels, and be attached to the channel. For simplicity, the library will implicitly attach to a channel when entering. Entering when already entered is treated as an "update":#update. + +There are two overloaded versions of this method. + With both versions, a callbackblockcompletion listener can optionally be passed in to be notified of success or failure to enter. + + +bq(definition#enter-none). + default: enter(callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable enter() -> yields + java: void enter("CompletionListener":#completion-listener listener) + objc,swift: enter(data: nil, callback: ((ARTErrorInfo?) -> Void)?) + csharp: Task EnterAsync() + +Enter a presence channel without any data. If the channel is @initialized@ (i.e. no attempt to attach has yet been made for this channel), then calling @enter@ will implicitly attach the channel. + +bq(definition#enter-data). + default: enter(Object data, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable enter(Object data) -> yields + java: void enter(Object data, "CompletionListener":#completion-listener listener) + objc,swift: enter(data: AnyObject?, callback: ((ARTErrorInfo?) -> Void)?) + csharp: Task EnterAsync(object clientData) + +Enter a presence channel and provide data that is associated with the current present member. If the channel is @initialized@ (i.e. no attempt to attach has yet been made for this channel), then calling @enter@ will implicitly attach the channel. + +h4. Parameters + +-
data
:= data payload for the current present member. The supported payload types are Strings, JSON objects and arrays, buffers containing arbitrary binary data, and null.
__Type: @Object@__ +-
data
:= data payload for the current present member. The supported payload types are Strings, JsonObject, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the current present member. The supported payload types are strings, plain object convertible to json, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the current present member. The supported payload types are Strings, Hash or Array objects that can be serialized to JSON using @to_json@, binary data as @ASCII-8BIT@ byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are @NS@@String@, @NS@@Dictionary@ or @NS@@Array@ objects that can be serialized to JSON, binary data as @NSData@, and @nil@.
__Type: @Object@__ + +-
callback
:= is a function of the form @function(err)@ which is called upon completion +-
listener
:= Listener to be notified on completion
__Type: "@CompletionListener@":#completion-listener__ +-
&block
:= yields upon successfully entering the channel +-
callback
:= called upon successfully entering the channel, or with an error + +blang[jsall,swift,objc]. + h4. Callback result + + On successfully entering the channel, @err@ is null. On failure to enter, @err@ contains an "@ErrorInfo@":#error-info object describing the failure reason. + +blang[java]. + h4. Listener result + + On successfully entering the channel, the @onSuccess@ method of the "CompletionListener":#completion-listener is called. On failure to enter, the @onError@ method is called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +blang[csharp]. + h4. Task result + + On successfully entering the channel, the method will complete. On failure to enter, an @AblyException@ will be thrown with an "@ErrorInfo@":#error-info object describing the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On successfully entering the channel, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method are executed. On failure to enter, the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable are called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +h6(#leave). + default: leave + csharp: Leave + +In order to leave the presence set of a channel, the client must have already "entered and been present":#enter. + +There are two overloaded versions of this method. + With both versions, a callbackblockcompletion listener can optionally be passed in to be notified of success or failure to leave. + + +bq(definition#leave-none). + default: leave(callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable leave() -> yields + java: void leave("CompletionListener":#completion-listener listener) + swift,objc: leave(data: nil, callback: ((ARTErrorInfo?) -> Void)?) + csharp: Task LeaveAsync() + +Leave a presence channel without emitting any data. + +bq(definition#leave-data). + default: leave(Object data, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable leave(Object data) -> yields + java: void leave(Object data, "CompletionListener":#completion-listener listener) + swift,objc: leave(data: AnyObject?, callback: ((ARTErrorInfo?) -> Void)?) + csharp: Task LeaveAsync(object clientData) + +Leave a presence channel and emit data that is associated with the current leaving member. + +h4. Parameters + +-
data
:= data payload for the current present member. The supported payload types are Strings, JSON objects and arrays, buffers containing arbitrary binary data, and null.
__Type: @Object@__ +-
data
:= data payload for the current present member. The supported payload types are Strings, JsonObject, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the current present member. The supported payload types are strings, plain objects convertible to json, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the current present member. The supported payload types are Strings, Hash or Array objects that can be serialized to JSON using @to_json@, binary data as @ASCII-8BIT@ byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are @NS@@String@, @NS@@Dictionary@ or @NS@@Array@ objects that can be serialized to JSON, binary data as @NSData@, and @nil@.
__Type: @Object@__ + +-
callback
:= is a function of the form @function(err)@ which is called upon completion +-
listener
:= Listener to be notified on completion
__Type: "@CompletionListener@":#completion-listener__ +-
&block
:= yields upon successfully leaving the channel +-
callback
:= called upon leaving the channel, or with an error + +blang[jsall,swift,objc]. + h4. Callback result + + On successfully leaving the channel, @err@ is null. On failure to leave, @err@ contains an "@ErrorInfo@":#error-info object describing the failure reason. + +blang[java]. + h4. Listener result + + On successfully leaving the channel, the @onSuccess@ method of the "CompletionListener":#completion-listener is called. On failure to leave, the @onError@ method is called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +blang[csharp]. + h4. Task result + + On successfully leaving the channel, the method will complete. On failure to leave, an @AblyException@ will be thrown with an "@ErrorInfo@":#error-info object describing the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On successfully leaving the channel, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method are executed. On failure to leave, the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable are called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +h6(#update). + default: update + csharp: Update + +Clients can update their member data on the channel which will trigger a broadcast of this update to all presence subscribers. The "pre-requisites for @update@@Update@":#update are the same as for "@enter@@Enter@":#enter. If an attempt to @update@@Update@ is made before the client has entered the channel, the update is treated as an @enter@@Enter@. + + + A callbackblockcompletion listener can optionally be passed in to be notified of success or failure to update the member data. + + +bq(definition#update-data). + default: update(Object data, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable update(Object data) -> yields + java: void update(Object data, "CompletionListener":#completion-listener listener) + swift,objc: update(data: AnyObject?, callback: ((ARTErrorInfo?) -> Void)?) + csharp: Task UpdateAsync(object clientData) + +Update the current member's data and broadcast an update event to all subscribers. @data@ may be @null@. If the channel is @initialized@ (i.e. no attempt to attach has yet been made for this channel), then calling @update@ will implicitly attach the channel. + +h4. Parameters + +-
data
:= data payload for the current present member. The supported payload types are Strings, JSON objects and arrays, buffers containing arbitrary binary data, and null.
__Type: @Object@__ +-
data
:= data payload for the current present member. The supported payload types are Strings, JsonObject, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the current present member. The supported payload types are strings, plain objects convertible to json, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the current present member. The supported payload types are Strings, Hash or Array objects that can be serialized to JSON using @to_json@, binary data as @ASCII-8BIT@ byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are @NS@@String@, @NS@@Dictionary@ or @NS@@Array@ objects that can be serialized to JSON, binary data as @NSData@, and @nil@.
__Type: @Object@__ + +-
callback
:= is a function of the form @function(err)@ which is called upon completion +-
listener
:= Listener to be notified on completion
__Type: "@CompletionListener@":#completion-listener__ +-
&block
:= yields upon successfully updating the member data +-
callback
:= called upon updating the channel, or with an error + +blang[jsall,objc,swift]. + h4. Callback result + + On successfully updating the data, @err@ is null. On failure to update, @err@ contains an "@ErrorInfo@":#error-info object describing the failure reason. + +blang[java]. + h4. Listener result + + On successfully updating the data, the @onSuccess@ method of the "CompletionListener":#completion-listener is called. On failure to update, the @onError@ method is called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +blang[csharp]. + h4. Task result + + On successfully entering the channel, the method will complete. On failure to enter, an @AblyException@ will be thrown with an "@ErrorInfo@":#error-info object describing the failure reason. The methods needs to be awaited otherwise the exception won't be thrown. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On successfully updating the data, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method are executed. On failure to update, the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable are called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +h6(#get). + default: get + csharp: Get + +Get the current presence member set for this channel. Typically, this method returns the member set immediately as the member set is retained in memory by the client. However, by default this method will wait until the presence member set is synchronized, so if the synchronization is not yet complete following a channel being attached, this method will wait until the presence member set is synchronized. + +When a channel is attached, the Ably service immediately synchronizes the presence member set with the client. Typically this process completes in milliseconds, however when the presence member set is very large, bandwidth constraints may slow this synchronization process down. + +When a channel is @initialized@ (i.e. no attempt to attach has yet been made for this channel), then calling @get@ will implicitly attach the channel. + +bq(definition). + default: get(Object options, callback("ErrorInfo":/realtime/types#error-info err, "PresenceMessage[]":#presence-message members)) + ruby: "Deferrable":/realtime/types#deferrable get(Hash options) -> yields "PresenceMessage[]":#presence-message + java: "PresenceMessage[]":#presence-message get("Param":#param[] options) + swift,objc: get(query: ARTRealtimePresenceQuery, callback: (["ARTPresenceMessage":#presence-message], ARTErrorInfo?) -> Void) + csharp: Task> GetAsync(GetOptions { bool waitForSync = true, string clientId = "", string connectionId = "" }) + +Gets an array of members present on the channel as "PresenceMessage":#presence-message objects. + +blang[jsall,ruby,java,swift,objc]. + h4. Parameters + + - optionsquery"Param":#param[] optionsoptions := an optional object containing query parametersan optional set of symbol key and value pairs containing the query parametersan optional set of parameters as specified below. + + -
callback
:= is a function of the form: @function(err, "PresenceMessage[]":#presence-message members)@ + -
&block
:= yields a "@PresenceMessage[]@":#presence-message array + -
callback
:= called with a "@[ARTPresenceMessage]@":#presence-message, or with an error + +h4. @options@ parameters@ARTRealtimePresenceQuery@ properties@GetOptions@ properties + +- clientId:client_id := when provided, will filter array of members returned that match the provided "@clientId@":/realtime/usage#client-id"@client_id@":/realtime/usage#client-id"@ClientId@":/realtime/usage#client-id string +- connectionId:connection_id := when provided, will filter array of members returned that match the provided @connectionId@@ConnectionId@@connection_id@":/realtime/connection/#id string +- waitForSync:wait_for_sync := _true_ A boolean value that by default waits for the initial presence synchronization following channel attachment to complete before returning the members present. When false, the current list of members is returned without waiting for a complete synchronization + +blang[jsall,swift,objc]. + h4. Callback result + + On success, @members@ contains an array of "@PresenceMessage@":#presence-message objects corresponding to the current set of present members on the channel. + + On failure to retrieve the current presence member set, @err@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +blang[java]. + h4. Returns + + On success, the returned array of "@PresenceMessage@":#presence-message objects corresponds to the current set of present members on the channel. + + Failure to retrieve the current presence member set will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[csharp]. + h4. Returns + + On success, the returned enumerable of "#PresenceMessage@":#presence-message objects correspond to the current set of present members on the channel. + + Failure to retrieve the current presence member set will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On success, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method yield an array of "@PresenceMessage@":#presence-message objects to the current set of present members on the channel. + + Failure to retrieve the current presence member set will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object with the failure reason. + +h6(#history). + default: history + csharp: History + +bq(definition). + default: history(Object options, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> resultPage)) + ruby: "Deferrable":/realtime/types#deferrable history(Hash options) -> yields "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> + java: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> history("Param":#param[] options) + objc,swift: history(query: ARTRealtimeHistoryQuery?, callback: ("ARTPaginatedResult":#paginated-result<"ARTPresenceMessage":#presence-message>?, ARTErrorInfo?) -> Void) throws + csharp: Task> HistoryAsync("PaginatedRequestParams":#paginated-request-params query, bool untilAttach = false) + + +Gets a "paginated":#paginated-result set of historical presence message events for this channel. If the "channel is configured to persist messages to disk":https://support.ably.io/support/solutions/articles/3000030059-how-long-are-messages-stored-for, then the presence message event history will "typically be available for 24 - 72 hours":https://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for. If not, presence message events are only retained in memory by the Ably service for two minutes. + +h4. Parameters + +- optionsquery"Param":#param[] options"PaginatedRequestParams":#paginated-request-params query := an optional object containing query parametersan optional set of key value pairs containing query parameters, as specified in the "presence history API documentation":/realtime/history#presence-history. + +-
callback
:= is a function of the form: @function(err, resultPage)@ +-
&block
:= yields a @PaginatedResult@ object +-
callback
:= called with a "ARTPaginatedResult":#paginated-result<"ARTPresenceMessage":#presence-message> object or an error + +Further details of the supported @options@ params, see "presence history API documentation":/realtime/history#presence-history. + +blang[jsall,swift,objc]. + h4. Callback result + + On success, @resultPage@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + On failure to retrieve message history, @err@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +blang[java]. + h4. Returns + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + Failure to retrieve the message history will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[csharp]. + h4. Returns + + Returns a task that needs to be awaited. + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@NextAsync@":#paginated-result and "@FirstAsync@":#paginated-result methods. + + Failure to retrieve the message history will raise an "@AblyException@":/realtime/types/#ably-exception. Only if the task is awaited. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On success, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method yield a "PaginatedResult":#paginated-result that encapsulates an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + Failure to retrieve the message history will trigger the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object with the failure reason. + +h6(#subscribe). + default: subscribe + csharp: Subscribe + +There are overloaded versions of this method: + +bq(definition#subscribe-listener). + default: subscribe(listener("PresenceMessage":#presence-message)) + java: void subscribe("PresenceListener":#presence-listener listener) + ruby: subscribe → yields "PresenceMessage":#presence-message + objc,swift: subscribe(callback: ("ARTPresenceMessage":#presence-message) -> Void) -> ARTEventListener + csharp: void Subscribe(Action handler) + +Subscribe to presence message events on this channel. The caller supplies a listener functionan implementation of the "PresenceListener":#presence-listener interfacea blocka handler, which is called each time one or more presence events occurs such as a member entering or leaving a channel. + +bq(definition#subscribe-event). + default: subscribe(String "action":#presence-action, listener("PresenceMessage":#presence-message)) + java: void subscribe("PresenceMessage.Action":#presence-action action, "PresenceListener":#presence-listener listener) + ruby: subscribe("PresenceMessage::ACTION":#presence-action action) → yields "PresenceMessage":#presence-message + objc,swift: subscribe(action: ARTPresenceAction, callback: ("ARTPresenceMessage":#presence-message) -> Void) -> ARTEventListener + csharp: void Subscribe("PresenceAction":#presence-action action, Action handler) + +Subscribe to presence message events with a given "@action@":#presence-action on this channel. The caller supplies a listener functionan implementation of the "PresenceListener":#presence-listener interfacea blocka handler, which is called each time one or more presence events occurs such as a member entering or leaving a channel. + +
+
+bq(definition#subscribe-event-array). + default: subscribe(String[] "actions":#presence-action, listener("PresenceMessage":#presence-message)) + java: void subscribe("PresenceMessage.Action[]":#presence-action actions, "PresenceListener":#presence-listener listener) + ruby: subscribe("PresenceMessage::ACTION":#presence-action *actions) → yields "PresenceMessage":#presence-message + +Subscribe a single listener to messages on this channel for multiple @name@ values. +
+ +h4. Parameters + +- action := The presence "@action@ event":#presence-action to subscribe to
__Type: @String@"@PresenceMessage.Action@":#presence-action"@PresenceAction@":#presence-action"@PresenceMessage::ACTION@":#presence-action action@ARTPresenceAction@__ + +
+
+- actions := An argument array of "@action@ events":#presence-action to subscribe to
__Type: @String[]@@*argument__ +
+ +-
listener
:= is a function of the form @function(message)@ to be called for each matching presence message event +-
listener
:= Message listener to be notified for matching presence message event
__Type: "@PresenceListener":#presence-listener__ +-
&block
:= yields matching presence message event when received on the channel +-
callback
:= called with each matching "@presence message@":#presence-message when received on the channel +-
handler
:= called with each matching "@presence message@":#presence-message when received on the channel + +h4. Considerations + +* If the channel is @initialized@ (i.e. no attempt to attach has yet been made for this channel), then calling @subscribe@ will implicitly attach the channel. However, regardless of the implicit attach outcome, the listenerblockhandler will still be registered +* If @subscribe@ is called more than once with the same listenerblockhandler, then duplicates will be registered. For example, if you @subscribe@ twice with the same listenerblockhandler and a presence message is later received, the same listenerblockhandler will be invoked twice +* The registered listenerblockhandler remains active on the presence channel regardless of the underlying channel state. For example, if you call @subscribe@ when the underlying channel is @attached@ and it later becomes @detached@ or even @failed@, when the channel is reattached and a presence message is received, the listenersblockshandlers originally registered will still be invoked. Listeners are only removed when calling "@unsubscribe@":#unsubscribe or when the underlying channel is @released@ using the @Realtime.channels.release(name)@@Realtime.Channels.Release(name)@ method +* If an exception is thrown in the subscribe listener and bubbles up to the event emitter, it will be caught and logged at @error@ level, so as not to affect other listeners for the same event + +h6(#unsubscribe). + default: unsubscribe + csharp: Unsubscribe + +There are sixthree overloaded versions of this method: + +bq(definition#unsubscribe-event). + default: unsubscribe(String action, listener) + java: void unsubscribe("PresenceMessage.Action":#presence-action action, "PresenceListener":#presence-listener listener) + ruby: unsubscribe("PresenceMessage::ACTION":#presence-action action, &listener_proc) + objc,swift: unsubscribe(action: ARTPresenceAction, listener: ARTEventListener) + csharp: bool Unsubscribe("PresenceAction":#presence-action presenceAction, Action handler) + +Unsubscribe the given listener from presence message events on this channel for the given "@action@":#presence-action. This removes an earlier event-specific subscription. + +bq(definition#unsubscribe-listener). + default: unsubscribe(listener) + java: void unsubscribe("PresenceListener":#presence-listener listener) + ruby: unsubscribe(&listener_proc) + objc,swift: unsubscribe(listener: ARTEventListener) + csharp: bool Unsubscribe(Action handler) + +Unsubscribe the given listener from presence message events on this channel. This removes an earlier subscription. + +
+bq(definition). unsubscribe(String[] actions, listener) + +Unsubscribe the given listener from all presence actions in the array. + +bq(definition). unsubscribe(String action) + +Unsubscribe all listeners for a given "@action@":#presence-action. + +bq(definition). unsubscribe(String[] actions) + +Unsubscribe all listeners for all presence actions in the array. +
+ +bq(definition#unsubscribe-all). + default: unsubscribe() + java: void unsubscribe() + objc,swift: unsubscribe() + csharp: bool Unsubscribe() + +Unsubscribes all listeners to presence message events on this channel. This removes all earlier subscriptions. + +h4. Parameters + +- action := The presence "@action@ event":#presence-action to unsubscribe from
__Type: @String@"@PresenceMessage.Action@":#presence-action"@PresenceMessage::ACTION@":#presence-action action@ARTPresenceAction@__ +-
actions
:= An array of actions to unsubscribe from
__Type: @String[]@__ +-
listener
:= is the callback listener function that was previously subscribed +-
listener
:= previously registered listener
__Type: "@PresenceListener":#presence-listener__ +-
handler
:= previously registered handler +-
&listener_block
:= previously registered listener block +-
listener
:= previous return value from a @subscribe@ call + +h6(#enter-client). + default: enterClient + ruby: enter_client + csharp: EnterClientAsync + +Enter this presence channel for the given @clientId@@client_id@@ClientId@. This method is provided to support typically server instances that act on behalf of multiple client IDs. See "Managing multiple client IDs":#presence-multiple-client-id for more info. In order to be able to publish presence changes for arbitrary client IDs, the client library must have been instanced either with an "API key":https://support.ably.io/solution/articles/3000030054-what-is-an-app-api-key, or with a "token bound to a wildcard client ID":https://support.ably.io/solution/articles/3000048586-can-a-client-emulate-any-client-id-i-e-use-a-wildcard-client-id. + +There are two overloaded versions of this method. + + With both versions, a callbackblockcompletion listener can optionally be passed in to be notified of success or failure to enter. + + +bq(definition#enter-client-none). + default: enterClient(String clientId, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable enter_client(String client_id) -> yields + java: void enterClient(String clientId, "CompletionListener":#completion-listener listener) + objc,swift: enterClient(clientId: String, data: nil, callback: ((ARTErrorInfo?) -> Void)?) + csharp: Task EnterClientAsync(string clientId, object clientData) + +Enter a presence channel on behalf of the provided @clientId@@client_id@@ClientId@ without any data. If the channel is @initialized@ (i.e. no attempt to attach has yet been made for this channel), then calling @enter@ will implicitly attach the channel. + +bq(definition#enter-client-data). + default: enterClient(String clientId, Object data, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable enter_client(String client_id, Object data) -> yields + java: void enterClient(String clientId, Object data, "CompletionListener":#completion-listener listener) + objc,swift: enterClient(clientId: String, data: AnyObject?, callback: ((ARTErrorInfo?) -> Void)?) + csharp: Task EnterClientAsync(string clientId, object clientData) + +Enter a presence channel and provide data that is associated with the current present member. If the channel is @initialized@ (i.e. no attempt to attach has yet been made for this channel), then calling @enter@ will implicitly attach the channel. + +h4. Parameters + +-
data
:= data payload for the member. The supported payload types are Strings, JSON objects and arrays, buffers containing arbitrary binary data, and null.
__Type: @Object@__ +-
data
:= data payload for the member. The supported payload types are Strings, JsonObject, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the member. The supported payload types are strings, plain object convertible to json, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the member. The supported payload types are Strings, Hash or Array objects that can be serialized to JSON using @to_json@, binary data as @ASCII-8BIT@ byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are @NS@@String@, @NS@@Dictionary@ or @NS@@Array@ objects that can be serialized to JSON, binary data as @NSData@, and @nil@.
__Type: @Object@__ + +-
callback
:= is a function of the form @function(err)@ which is called upon completion +-
listener
:= Listener to be notified on completion
__Type: "@CompletionListener@":#completion-listener__ +-
&block
:= yields upon successfully entering the channel +-
callback
:= called upon successfully entering the channel, or with an error + +blang[jsall,swift,objc]. + h4. Callback result + + On successfully entering the channel, @err@ is null. On failure to enter, @err@ contains an "@ErrorInfo@":#error-info object describing the failure reason. + +blang[java]. + h4. Listener result + + On successfully entering the channel, the @onSuccess@ method of the "CompletionListener":#completion-listener is called. On failure to enter, the @onError@ method is called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +blang[csharp]. + h4. Returns + + The method returns a @Task@ that will need to be awaited. On success the method will complete and will not throw an exception. On failure to enter the method will throw an @AblyException@ with an "@ErrorInfo@":#error-info argument describing the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On successfully entering the channel, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method are executed. On failure to enter, the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable are called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +h6(#leave-client). + default: leaveClient + ruby: leave_client + csharp: LeaveClientAsync + +Leave this presence channel for the given @clientId@@client_id@@ClientId@. This method is provided to support typically server instances that act on behalf of multiple client IDs. See "Managing multiple client IDs":#presence-multiple-client-id for more info. In order to leave the presence set of a channel, the client must have already "entered and been present":#enter-client. + +There are two overloaded versions of this method. + + With both versions, a callbackblockcompletion listenerhandler can optionally be passed in to be notified of success or failure to leave. + + +bq(definition#leave-client-none). + default: leaveClient(String clientId, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable leave_client(String client_id) -> yields + java: void leaveClient(String clientId, "CompletionListener":#completion-listener listener) + swift,objc: leaveClient(clientId: String, data: nil, callback: ((ARTErrorInfo?) -> Void)?) + csharp: Task LeaveClientAsync(string clientId, object data) + +Leave a presence channel on behalf of the provided @clientId@@client_id@@ClientId@ without emitting any data. + +bq(definition#leave-client-data). + default: leaveClient(String clientId, Object data, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable leave_client(String client_id, Object data) -> yields + java: void leaveClient(String clientId, Object data, "CompletionListener":#completion-listener listener) + swift,objc: leave(clientId: String, data: AnyObject?, callback: ((ARTErrorInfo?) -> Void)?) + csharp: Task LeaveClientAsync(string clientId) + +Leave a presence channel on behalf of the provided @clientId@@client_id@@ClientId@ and emit data that is associated with the current leaving member. + +h4. Parameters + +-
data
:= data payload for the member. The supported payload types are Strings, JSON objects and arrays, buffers containing arbitrary binary data, and null.
__Type: @Object@__ +-
data
:= data payload for the member. The supported payload types are Strings, JsonObject, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the member. The supported payload types are strings, plain object convertible to json, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the member. The supported payload types are Strings, Hash or Array objects that can be serialized to JSON using @to_json@, binary data as @ASCII-8BIT@ byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are @NS@@String@, @NS@@Dictionary@ or @NS@@Array@ objects that can be serialized to JSON, binary data as @NSData@, and @nil@.
__Type: @Object@__ + +-
callback
:= is a function of the form @function(err)@ which is called upon completion +-
listener
:= Listener to be notified on completion
__Type: "@CompletionListener@":#completion-listener__ +-
&block
:= yields upon successfully entering the channel +-
callback
:= called upon successfully entering the channel, or with an error + +blang[jsall,swift,objc]. + h4. Callback result + + On successfully leaving the channel, @err@ is null. On failure to leave, @err@ contains an "@ErrorInfo@":#error-info object describing the failure reason. + +blang[java]. + h4. Listener result + + On successfully leaving the channel, the @onSuccess@ method of the "CompletionListener":#completion-listener is called. On failure to leave, the @onError@ method is called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +blang[csharp]. + h4. Returns + + The method returns a @Task@ that will need to be awaited. On success the method will complete and will not throw an exception. On failure to leave the method will throw an @AblyException@ with an "@ErrorInfo@":#error-info argument describing the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On successfully leaving the channel, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method are executed. On failure to leave, the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable are called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +h6(#update-client). + default: updateClient + ruby: update_client + csharp: UpdateClientAsync + +Clients can update the member data on behalf of the given @clientId@@client_id@@ClientId@ which will trigger a broadcast of this update to all presence subscribers. This method is provided to support typically server instances that act on behalf of multiple client IDs. See "Managing multiple client IDs":#presence-multiple-client-id for more info. If an attempt to update is made before the member has entered the channel, the update is treated as an enter. + + + A callbackblockcompletion listener can optionally be passed in to be notified of success or failure to update the member data. + + +bq(definition#update-client-data). + default: updateClient(String clientId, Object data, callback("ErrorInfo":/realtime/types#error-info err)) + ruby: "Deferrable":/realtime/types#deferrable update_client(String client_id, Object data) -> yields + java: void updateClient(String clientId, Object data, "CompletionListener":#completion-listener listener) + swift,objc: update(clientId: String, data: AnyObject?, callback: ((ARTErrorInfo?) -> Void)?) + csharp: Task UpdateClientAsync(string clientId, object data) + +Update the member data on behalf of the provided @clientId@@client_id@@ClientId@ and broadcast an update event to all subscribers. @data@ may be @null@. If the channel is @initialized@ (i.e. no attempt to attach has yet been made for this channel), then calling @enter@ will implicitly attach the channel. + +h4. Parameters + +-
data
:= data payload for the member. The supported payload types are Strings, JSON objects and arrays, buffers containing arbitrary binary data, and null.
__Type: @Object@__ +-
data
:= data payload for the member. The supported payload types are Strings, JsonObject, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the member. The supported payload types are strings, plain object convertible to json, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the member. The supported payload types are Strings, Hash or Array objects that can be serialized to JSON using @to_json@, binary data as @ASCII-8BIT@ byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are @NS@@String@, @NS@@Dictionary@ or @NS@@Array@ objects that can be serialized to JSON, binary data as @NSData@, and @nil@.
__Type: @Object@__ + +-
callback
:= is a function of the form @function(err)@ which is called upon completion +-
listener
:= Listener to be notified on completion
__Type: "@CompletionListener@":#completion-listener__ +-
&block
:= yields upon successfully entering the channel +-
callback
:= called upon successfully entering the channel, or with an error + +blang[jsall,objc,swift]. + h4. Callback result + + On successfully updating the data, @err@ is null. On failure to update, @err@ contains an "@ErrorInfo@":#error-info object describing the failure reason. + +blang[java]. + h4. Listener result + + On successfully updating the data, the @onSuccess@ method of the "CompletionListener":#completion-listener is called. On failure to update, the @onError@ method is called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +blang[csharp]. + h4. Returns + + The method returns a Task that will need to be awaited. On success the method will complete and will not throw an exception. On failure to update the method will throw an @AblyException@ with an "@ErrorInfo@":#error-info argument describing the failure reason. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from the method. + + On successfully updating the data, the registered success blocks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method are executed. On failure to update, the @errback@ callbacks of the "@Deferrable@":/realtime/types#deferrable are called with an "@ErrorInfo@":#error-info argument describing the failure reason. + +h2(#related-types). Related types + +h3(#presence-message). + default: PresenceMessage + swift,objc: ARTPresenceMessage + ruby: Ably::Models::PresenceMessage + java: io.ably.lib.types.PresenceMessage + csharp: IO.Ably.PresenceMessage + +<%= partial partial_version('types/_presence_message') %> + +h3(#presence-action). + default: Presence action + swift,objc: ARTPresenceAction + java: io.ably.lib.types.PresenceMessage.Action + ruby: Ably::Models::PresenceMessage::ACTION + csharp: IO.Ably.PresenceAction + +<%= partial partial_version('types/_presence_action') %> + +h3(#paginated-request-params). + csharp: PaginatedRequestParams + +blang[csharp]. + <%= partial partial_version('types/_history_request_params'), indent: 2, skip_first_indent: true %> + +h3(#completion-listener). + java: io.ably.lib.realtime.CompletionListener + +blang[java]. + <%= partial partial_version('types/_completion_listener'), indent: 2, skip_first_indent: true %> + +h3(#paginated-result). + default: PaginatedResult + swift,objc: ARTPaginatedResult + ruby: Ably::Models::PaginatedResult + java: io.ably.lib.types.PaginatedResult + csharp: IO.Ably.PaginatedResult + +<%= partial partial_version('types/_paginated_result') %> + +h3(#param). + java: io.ably.lib.types.Param + +blang[java]. + <%= partial partial_version('types/_param'), indent: 2, skip_first_indent: true %> + +h3(#presence-listener). + java: io.ably.lib.realtime.PresenceListener + +blang[java]. + <%= partial partial_version('types/_presence_listener'), indent: 2, skip_first_indent: true %> diff --git a/content/realtime/versions/v1.1/push.textile b/content/realtime/versions/v1.1/push.textile new file mode 100644 index 0000000000..4d2bfa66a9 --- /dev/null +++ b/content/realtime/versions/v1.1/push.textile @@ -0,0 +1,100 @@ +--- +title: Push +section: realtime +index: 55 +languages: + - javascript + - java + - ruby + - android + - nodejs + - swift + - objc +api_separator: +jump_to: + Help with: + - Delivery push notifications#deliver + - Activating and subscribing a device#activate-device + - Managing devices and subscriptions#admin + - Platform support#platform-support +--- + +<%= partial partial_version('general/push/_push_intro') %> + +h1. Push API reference + +inline-toc. + Push Admin: + - Methods: + - publish#publish + - Properties: + - deviceRegistrations#device-registrations + - channelSubscriptions#channel-subscriptions + - DeviceRegistrations: + - get#device-get-id + - list(params)#device-list + - save(DeviceDetails)#device-save + - remove#device-remove-id + - removeWhere(params)#device-remove-where + - PushChannelSubscriptions: + - list(params)#push-channel-sub-list + - listChannels(params)#push-channel-sub-list-channels + - save(PushChannelSubscription)#push-channel-sub-save + - remove(PushChannelSubscription)#push-channel-sub-remove + - removeWhere(params)#push-channel-sub-remove-where + Push Device: + - Methods: + - activate()#activate + - deactivate()#deactivate + Related types: + - DeviceDetails#device-details + - PushChannelSubscription#push-channel-subscription + - LocalDevice#local-device + - PushChannel#push-channel + - PaginatedResult#paginated-result + +<%= partial partial_version('types/_push_admin') %> + +
+ +<%= partial partial_version('types/_push_device') %> + +
+ +h2(#related-types). Related types + +h3(#device-details). + default: DeviceDetails + ruby: Ably::Models::DeviceDetails + swift,objc: ARTDeviceDetails + +<%= partial partial_version('types/_device_details') %> + +h3(#push-channel-subscription). + default: PushChannelSubscription + ruby: Ably::Models::PushChannelSubscription + java,android: ChannelSubscription + swift,objc: ArtPushChannelSubscription + +<%= partial partial_version('types/_push_channel_subscription') %> + +h3(#local-device). + default: LocalDevice + ruby: Ably::Models::LocalDevice + +<%= partial partial_version('types/_local_device') %> + +h3(#push-channel). + default: PushChannel + ruby: Ably::Models::PushChannel + +<%= partial partial_version('types/_push_channel') %> + +h3(#paginated-result). + default: PaginatedResult + swift,objc: ARTPaginatedResult + ruby: Ably::Models::PaginatedResult + java,android: io.ably.lib.types.PaginatedResult + csharp: IO.Ably.PaginatedResult + +<%= partial partial_version('types/_paginated_result') %> diff --git a/content/realtime/versions/v1.1/statistics.textile b/content/realtime/versions/v1.1/statistics.textile new file mode 100644 index 0000000000..97fa96bb06 --- /dev/null +++ b/content/realtime/versions/v1.1/statistics.textile @@ -0,0 +1,132 @@ +--- +title: Statistics +section: realtime +index: 100 +languages: + - javascript + - nodejs + - ruby + - java + - swift + - objc + - csharp +api_separator: +jump_to: + Help with: + - Statistics#title + - Getting started + API reference: + - stats#stats + Types: + - Related Types#related-types +--- + +The Ably service retains usage statistics per application and per account at 1 minute intervals. Your application statistics are available programmatically through our client libraries at 1 minute intervals, or aggregated up to the hour, day, or month. + +p(tip). Your most recent statistics are delayed by up to 6 seconds. + +Your application and account statistics are also available as graphs, tabular data or downloads in your "application dashboard":https://support.ably.io/solution/articles/3000030053-how-do-i-access-my-app-dashboard. + +The Ably service aggregates your application statistics by minute, hour, day and month, both at an application level and at an account level. Account level statistics are not available via the client library APIs and must be viewed using your "account dashboard":https://support.ably.io/solution/articles/3000048664-how-do-i-access-my-account-dashboard. Your monthly quota and any potential overages are calculated using the account level statistics. "Find out what happens if you exceed your package limits":https://support.ably.io/solution/articles/3000044717-what-happens-if-i-exceed-my-package-limits. + +h2(#getting-started). Getting started + +The Ably Realtime client library provides a straightforward API for retrieving application statistics: + +```[jsall](code-editor:realtime/stats) + var realtime = new Ably.Realtime('{{API_KEY}}'); + realtime.stats({ unit: 'hour' }, function(err, resultPage) { + var thisHour = resultPage.items[0]; + console.log(thisHour); // => {all: a, inbound: f, outbound: f, …} + }); +``` + +```[ruby] + realtime = Ably::Realtime.new('{{API_KEY}}') + realtime.stats(unit: 'hour') do |result_page| + this_hour = result_page.items.first + puts this_hour # => # results = realtime.stats(options); + Stats thisHour = results.items[0]; + System.out.println("Published this hour " + thisHour.inbound.all.all.count); +``` + +```[csharp] + AblyRealtime realtime = new AblyRealtime("{{API_KEY}}"); + StatsRequestParams query = new StatsRequestParams() { Unit = StatsIntervalGranularity.Hour }; + PaginatedResult results = await realtime.StatsAsync(query); + Stats thisHour = results.Items[0]; + Console.WriteLine("Published this hour " + thisHour.Inbound.All.All); +``` + +```[objc] +ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"]; +ARTStatsQuery *query = [[ARTStatsQuery alloc] init]; +query.unit = ARTStatsGranularityHour; +[realtime stats:query callback:^(ARTPaginatedResult *result, ARTErrorInfo *error) { + ARTStats *thisHour = result.items[0]; + NSLog(@"Published this hour %lu", thisHour.inbound.all.all.count); +} error:nil]; +``` + +```[swift] +let realtime = ARTRealtime(key: "{{API_KEY}}") +let query = ARTStatsQuery() +query.unit = .Hour +try! realtime.stats(query) { results, error in + let thisHour = results!.items[0] + print("Published this hour \(thisHour.inbound.all.all.count)") +} +``` + +Note that all examples on this page assume you are running them within an EventMachine reactor. Find out more in our "Realtime usage documentation":/realtime/usage. + +h1. Realtime API Reference + +h6(#stats). stats + +bq(definition). + jsall: stats(Object options, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":/realtime/types#paginated-result<"Stats":/realtime/types#stats> results)) + ruby: "Deferrable":/realtime/types#deferrable stats(Hash options) -> yields "PaginatedResult":/realtime/types#paginated-result<"Stats":/realtime/types#stats> + java: "PaginatedResult":/realtime/types#paginated-result<"Stats":/realtime/types#stats-type> stats("Param":#param[] options) + swift,objc: stats(query: ARTStatsQuery?, callback: ("ARTPaginatedResult":/realtime/types#paginated-result<"ARTStats":/realtime/types#stats>?, ARTErrorInfo?) -> Void) throws + csharp: Task<"PaginatedResult":/realtime/types#paginated-result<"Stats":/realtime/types#status>> StatsAsync("StatsRequestParams":#statsdatarequest query) + +This call queries the "REST @/stats@ API":/rest-api#stats-type and retrieves your application's usage statistics. A "PaginatedResult":/realtime/types#paginated-result is returned, containing an array of "Stats":/realtime/types#stats-type for the first page of results. "PaginatedResult":/realtime/types#paginated-result objects are iterable providing a means to page through historical statistics. "See an example set of raw stats returned via the REST API":/general/statistics. + +<%= partial partial_version('realtime/_stats') %> + +h2(#related-types). Related types + +h3(#stats-type). + default: Stats object + objc,swift: ARTStats + java: io.ably.lib.types.Stats + ruby: Ably::Models::Stats + csharp: IO.Ably.Stats + +<%= partial partial_version('types/_stats') %> + +h3(#stats-request-params). + csharp: IO.Ably.StatsRequestParams + +blang[csharp]. + <%= partial partial_version('types/_history_request_params'), indent: 2, skip_first_indent: true %> + +h3(#stats-granularity). + objc,swift: ARTStatsGranularity + csharp: StatsIntervalGranularity + +<%= partial partial_version('types/_stats_granularity') %> + +h3(#param). + java: io.ably.lib.types.Param + +blang[java]. + <%= partial partial_version('types/_param'), indent: 2, skip_first_indent: true %> diff --git a/content/realtime/versions/v1.1/types.textile b/content/realtime/versions/v1.1/types.textile new file mode 100644 index 0000000000..61033b6f04 --- /dev/null +++ b/content/realtime/versions/v1.1/types.textile @@ -0,0 +1,335 @@ +--- +title: Types +section: realtime +index: 500 +languages: + - javascript + - nodejs + - ruby + - java + - swift + - objc + - csharp +--- + +The Ably client library defines both data types and option types. Data types are used to represent object such as messages; Option types are used in method arguments. + +Where client libraries support both Realtime and REST APIs, the types are shared between both clients. + +All types are always classes or their respective equivalent for each language implementation. Options on the other hand, may often support both typed option classes or more flexible key value objects such as a Hash or plain Javascript object. + +If you are interested in finding out more about the exact types and options definitions in each language, we recommend you download "our open source libraries":https://www.ably.io/download and review the code. + +h1. API Reference + +inline-toc. + Data types: + - AblyException#ably-exception + - Channel Details + - DeviceDetails#device-details + - Error Info + - Message + - Presence Message + - Presence action + - Paginated Result + - HTTP Paginated Response + - Param + - PushChannelSubscription#push-channel-subscription + - Token Details + - Token Request + - Stats + - Stats Granularity + Other Types: + - Auth Options + - Client Options + - Channel Options + - Channel State + - Channel Event + - Channel State Change + - Channel State Listener + - Cipher Params + - Completion Listener + - Connection State#connection-state + - Connection Event#connection-event + - Connection State Listener + - Connection State Change + - History Request Params + - LastConnnectionDetails#last-connection-details + - Message Listener + - Presence Listener + - Stats Request Params + - Token Params + +h2. Data types + +h3(#ably-exception). + java: io.ably.lib.types.AblyException + ruby: Ably::Exceptions::BaseAblyException + csharp: IO.Ably.AblyException + +blang[java,csharp]. + <%= partial partial_version('types/_ably_exception'), indent: 2, skip_first_indent: true %> + +blang[ruby]. + <%= partial partial_version('types/_base_ably_exception'), indent: 2, skip_first_indent: true %> + +<%= partial partial_version('types/_channel_details') %> + +h3(#device-details). + jsall: DeviceDetails + ruby: Ably::Models::DeviceDetails + +blang[jsall,ruby]. + <%= partial partial_version('types/_device_details'), indent: 2, skip_first_indent: true %> + +h3(#error-info). + default: ErrorInfo + objc,swift: ARTErrorInfo + java: io.ably.lib.types.ErrorInfo + ruby: Ably::Models::ErrorInfo + csharp: IO.Ably.ErrorInfo + +<%= partial partial_version('types/_error_info') %> + +h3(#message). + default: Message + objc,swift: ARTMessage + java: io.ably.lib.types.Message + ruby: Ably::Models::Message + csharp: IO.Ably.Message + +<%= partial partial_version('types/_message') %> + +h3(#presence-message). + default: PresenceMessage + objc,swift: ARTPresenceMessage + java: io.ably.lib.types.PresenceMessage + ruby: Ably::Models::PresenceMessage + csharp: IO.Ably.PresenceMessage + +<%= partial partial_version('types/_presence_message') %> + +h3(#presence-action). + default: Presence action + objc,swift: ARTPresenceAction + java: io.ably.lib.types.PresenceMessage.Action + ruby: Ably::Models::PresenceMessage::ACTION + csharp: IO.Ably.PresenceAction + +<%= partial partial_version('types/_presence_action') %> + +h3(#paginated-result). + default: PaginatedResult + objc,swift: ARTPaginatedResult + java: io.ably.lib.types.PaginatedResult + ruby: Ably::Models::PaginatedResult + csharp: IO.Ably.PaginatedResult + +<%= partial partial_version('types/_paginated_result') %> + +h3(#http-paginated-response). + default: HttpPaginatedResponse + +<%= partial partial_version('types/_http_paginated_response') %> + +h3(#param). + java: io.ably.lib.types.Param + +blang[java]. + <%= partial partial_version('types/_param'), indent: 2, skip_first_indent: true %> + +h3(#push-channel-subscription). + jsall: PushChannelSubscription + ruby: Ably::Models::PushChannelSubscription + +blang[jsall,ruby]. + <%= partial partial_version('types/_push_channel_subscription'), indent: 2, skip_first_indent: true %> + +h3(#token-details). + default: TokenDetails + objc,swift: ARTTokenDetails + java: io.ably.lib.types.TokenDetails + ruby: Ably::Models::TokenDetails + csharp: IO.Ably.TokenDetails + +<%= partial partial_version('types/_token_details') %> + +h3(#token-request). + default: TokenRequest + objc,swift: ARTTokenRequest + java: io.ably.lib.types.TokenRequest + ruby: Ably::Models::TokenRequest + csharp: IO.Ably.TokenRequest + +<%= partial partial_version('types/_token_request') %> + +h3(#stats). + default: Stats object + objc,swift: ARTStats + java: io.ably.lib.types.Stats + ruby: Ably::Models::Stats + csharp: IO.Ably.Stats + +<%= partial partial_version('types/_stats') %> + +h3(#stats-granularity). + objc,swift: ARTStatsGranularity + +<%= partial partial_version('types/_stats_granularity') %> + +h2. Other types + +h3(#auth-options). + default: AuthOptions Object + objc,swift: ARTAuthOptions + ruby: AuthOptions Hash + java: io.ably.lib.rest.Auth.AuthOptions + csharp: IO.Ably.AuthOptions + +<%= partial partial_version('types/_auth_options') %> + +h3(#client-options). + default: ClientOptions Object + objc,swift: ARTClientOptions + ruby: ClientOptions Hash + java: io.ably.types.ClientOptions + csharp: IO.Ably.ClientOptions + +<%= partial partial_version('types/_realtime_client_options_intro') %> +<%= partial partial_version('types/_client_options') %> +<%= partial partial_version('types/_realtime_client_options') %> + +h3(#channel-options). + default: ChannelOptions Object + objc,swift: ARTChannelOptions + ruby: ChannelOptions Hash + java: io.ably.lib.types.ChannelOptions + csharp: IO.Ably.Rest.ChannelOptions + +<%= partial partial_version('types/_channel_options') %> + +h3(#channel-state). + default: ChannelState + objc,swift: ARTRealtimeChannelState + ruby: Channel::STATE Enum + java: io.ably.lib.realtime.ChannelState Enum + csharp: IO.Ably.Realtime.ChannelState Enum + +<%= partial partial_version('types/_channel_state') %> + +h3(#channel-event). + default: ChannelEvent + swift,objc: ARTChannelEvent + ruby: Channel::EVENT Enum + java: io.ably.lib.realtime.ChannelEvent Enum + csharp: IO.Ably.Realtime.ChannelEvent Enum + +<%= partial partial_version('types/_channel_event') %> + +h3(#channel-state-change). + default: ChannelStateChange Object + objc,swift: ARTRealtimeChannelStateChange + ruby: ChannelStateChange + java: io.ably.lib.realtime.ConnectionStateListener.ChannelStateChange + csharp: IO.Ably.Realtime.ChannelStateChange + +<%= partial partial_version('types/_channel_state_change') %> + +h3(#channel-state-listener). + java: io.ably.lib.realtime.ChannelStateListener + +blang[java]. + <%= partial partial_version('types/_channel_state_listener'), indent: 2, skip_first_indent: true %> + +h3(#cipher-params). + default: CipherParams + objc,swift: ARTCipherParams + ruby: CipherParams Hash + java: io.ably.lib.util.Crypto.CipherParams + csharp: IO.Ably.CipherParams + +<%= partial partial_version('types/_cipher_params') %> + +h3(#completion-listener). + java: io.ably.lib.realtime.CompletionListener + +blang[java]. + <%= partial partial_version('types/_completion_listener'), indent: 2, skip_first_indent: true %> + +h3(#connection-state). + default: ConnectionState + objc,swift: ARTRealtimeConnectionState + ruby: Connection::STATE + java: io.ably.lib.realtime.ConnectionState + csharp: IO.Ably.Realtime.ConnectionState + +<%= partial partial_version('types/_connection_state') %> + +h3(#connection-event). + default: ConnectionEvent + swift,objc: ARTRealtimeConnectionEvent + ruby: Connection::EVENT Enum + java: io.ably.lib.realtime.ConnectionEvent Enum + csharp: IO.Ably.Realtime.ConnectionEvent Enum + +<%= partial partial_version('types/_connection_event') %> + +h3(#connection-state-listener). + java: io.ably.lib.realtime.ConnectionStateListener + +blang[java]. + <%= partial partial_version('types/_connection_state_listener'), indent: 2, skip_first_indent: true %> + +h3(#connection-state-change). + default: ConnectionStateChange Object + objc,swift: ARTConnectionStateChange + ruby: ConnectionStateChange + java: io.ably.lib.realtime.ConnectionStateListener.ConnectionStateChange + csharp: IO.Ably.Realtime.ConnectionStateChange + +<%= partial partial_version('types/_connection_state_change') %> + +h3(#paginated-request-params). + csharp: IO.Ably.PaginatedRequestParams + +blang[csharp]. + <%= partial partial_version('types/_history_request_params'), indent: 2, skip_first_indent: true %> + +h3(#message-listener). + java: io.ably.lib.realtime.Channel.MessageListener + +blang[java]. + <%= partial partial_version('types/_message_listener'), indent: 2, skip_first_indent: true %> + +h3(#last-connection-details). + javascript: LastConnectionDetails + +blang[javascript]. + <%= partial partial_version('types/_last_connection_details'), indent: 2, skip_first_indent: true %> + +h3(#presence-listener). + java: io.ably.lib.realtime.Presence.PresenceListener + +blang[java]. + <%= partial partial_version('types/_message_listener'), indent: 2, skip_first_indent: true %> + +blang[ruby]. + h3(#deferrable). + ruby: Ably::Util::SafeDeferrable + + <%= partial partial_version('types/_deferrable'), indent: 2, skip_first_indent: true %> + +h3(#stats-request-params). + csharp: IO.Ably.StatsRequestParams + +blang[csharp]. + <%= partial partial_version('types/_history_request_params'), indent: 2, skip_first_indent: true %> + +h3(#token-params). + default: TokenParams Object + objc,swift: ARTTokenParams + ruby: TokenParams Hash + java: io.ably.lib.rest.Auth.TokenParams + csharp: IO.Ably.TokenParams + +<%= partial partial_version('types/_token_params') %> diff --git a/content/realtime/versions/v1.1/usage.textile b/content/realtime/versions/v1.1/usage.textile new file mode 100644 index 0000000000..c640dd5091 --- /dev/null +++ b/content/realtime/versions/v1.1/usage.textile @@ -0,0 +1,428 @@ +--- +title: Using the Realtime library +section: realtime +index: 10 +languages: + - javascript + - nodejs + - ruby + - java + - swift + - objc + - csharp +api_separator: +jump_to: + Help with: + - Usage#title + Realtime API reference: + - constructor + Attributes: + - auth + - channels + - client_id#client-id + - connection + - rest_client#rest-client + Methods: + - connect + - close + - stats + - time + - request + Related Types: + - ClientOptions#client-options + - stats#stats-type + - HttpPaginatedResponse#http-paginated-response +--- + +The Realtime library when initialized attempts to establish and maintain a single connection to the Ably realtime service. This library extends the "REST library":/rest/usage to provide all REST API functionality including the "authentication API":/rest/authentication. Whereas the "Ably REST API":/rest-api is stateless, the Realtime API maintains connection and channel state. + +The Ably Realtime client library is available for "most popular languages and platforms":https://www.ably.io/download with a consistent API across all platforms. You can view this documentation with your preferred language API and code examples using the language selector navigation above. If your preferred language is not listed in the language selector, we recommend you "download a library in your preferred language":https://www.ably.io/download and use this documentation as an API reference. + +blang[javascript]. + The Realtime library for browser Javascript environments should be loaded from the Ably CDN as follows: + + ```[javascript] + + ``` + + You can also obtain the library as an "NPM module":https://www.npmjs.com/package/ably, or download the source directly from "Github":https://github.com/ably/ably-js + + The script and module both contain the Realtime and REST libraries as well as all their dependencies. To instance the Realtime library: + + ```[javascript] + var realtime = new Ably.Realtime({ key: apiKey }); + ``` + + When including the client library from our CDN, we recommend you lock into major version @1@ of the library. According to the "semantic versioning scheme":http://semver.org/ we adopt, you will then automatically receive minor and patch updates but you will never receive breaking changes. For example, if you lock into major version @1@ of the library by including "@https://cdn.ably.io/lib/ably.min-1.js@":https://cdn.ably.io/lib/ably.min-1.js, you will receive all minor updates and patch fixes automatically (i.e @1.*.*@). If you want to lock into minor version @1.0@ and receive all patch fixes automatically (i.e. @1.0.*@), then use "@https://cdn.ably.io/lib/ably.min-1.0.js@":https://cdn.ably.io/lib/ably.min-1.0.js. Additionally, the @.min@ suffix can be dropped if you want the non-minified version for debugging. + + "View a complete list of the Javascript library releases":https://github.com/ably/ably-js/releases + +blang[nodejs]. + The Realtime library for Node.js is suitable for clients and servers and is "hosted on Github":https://github.com/ably/ably-js and is obtainable as an NPM module "directly from npm":https://npmjs.org/package/ably. Install with: + + ```[sh] + npm install ably + ``` + + The Ably Node.js module contains both the REST and Realtime libraries; each is exported separately by the module. To instance the Realtime library: + + ```[nodejs] + var Ably = require('ably'); + var realtime = new Ably.Realtime({ key: apiKey }); + ``` + +blang[ruby]. + The Realtime library for Ruby "hosted on Github":https://github.com/ably/ably-ruby and is "published as a RubyGem":https://rubygems.org/gems/ably and can be installed as follows: + + ```[sh] + gem install ably + ``` + + If using bundler, simply add the following to your Gemfile and run @bundle install@: + + ```[ruby] + gem 'ably' + ``` + + The Ably Ruby gem contains both the REST and Realtime libraries; each is namespaced separately by the gem. The Realtime library must be run within an "EventMachine reactor":https://github.com/eventmachine/eventmachine which provides an asynchronous evented framework for the library to run within. To instance the Realtime library: + + ```[ruby] + EventMachine.run do + ably = Ably::Realtime.new(key: api_key) + end + ``` + + If you are using Ably in your Rails or Sinatra apps, you probably want to be using "the Ably REST library":/rest.usage that offers a synchronous API and has no dependency on EventMachine. + +blang[java]. + The Realtime library for Java and Android is "hosted on Github":https://github.com/ably/ably-java and can be used by adding one line to @build.gradle@ dependencies section. + + For Java applications: + + ```[java] + compile 'io.ably:ably-java:1.1.0' + ``` + + For Android apps: + + ```[java] + compile 'io.ably:ably-android:1.1.0' + ``` + + In the above example a specific version of the library is referenced, however we recommend you check which is the latest stable version and always use that. Follow links to get the latest stable release for "Java":https://bintray.com/ably-io/ably/ably-java and "Android":https://bintray.com/ably-io/ably/ably-android. + + Ensure the library is included in your classpath as follows: + + ```[java] + import io.ably.lib.types.*; + import io.ably.lib.realtime.*; + ClientOptions options = new ClientOptions(apiKey); + AblyRealtime realtime = new AblyRealtime(options); + ``` + +blang[csharp]. + The Realtime library for .Net is available as a Nuget Package. Open the nuget console in visual studio and type. + + PM> Install-Package ably.io + + Alternatively, search for the 'ably.io' package through the Nuget UI + + ```[csharp] + using IO.Ably; + + ClientOptions clientOptions = new ClientOptions(""); + AblyRealtime realtime = new AblyRealtime(clientOptions); + ``` + +blang[objc,swift]. + The Ably library is "hosted on Github":https://github.com/ably/ably-cocoa and is "available as a CocoaPod":https://cocoapods.org/pods/Ably. Add this line to your application's Podfile: + + bc[text]. pod 'Ably' + + And install with @pod install@. Then in your files: + + ```[objc] + #import "Ably/Ably.h" + + ARTRealtime realtime = [[ARTRealtime alloc] initWithKey:apiKey]; + ``` + + ```[swift] + import Ably + + let realtime = ARTRealtime(key: apiKey) + ``` + + **Note: Since version 1.0.8, the iOS client libraries are thread-safe.** Check "the README":https://github.com/ably/ably-cocoa#thread-safety for details. + +"View our client library SDKs feature support matrix":https://www.ably.io/download/sdk-feature-support-matrix to see the list of all the available features. + +The supported "client options are described below":#client-options. + +h2. Tutorials + +If you would like to see examples of using the Ably Realtime client library, then we recommend you take a look at our "Realtime tutorials":/tutorials. + +h1. + default: Realtime API Reference + ruby: Realtime::Client API reference + +inline-toc. + Properties: + - auth#auth + - push#push + - device#device + - channels#channels + - connection#connection + Methods: + - connect()#connect + - close()#close + - stats(options)#stats + - time()#time + - request()#request + Related types: + - ClientOptions#client-options + - Stats + - Params + - HttpPaginatedResponse#http-paginated-response + +h2(#constructor). Constructor + +The Ably Realtime library constructor is overloaded allowing it to be instanced using a "@ClientOptions@":#client-options object, or more simply using a string containing an "API key":/core-features/authentication/#basic-authentication or "Token":/core-features/authentication/#token-authentication, as shown below: + +bq(definition). + default: new Ably.Realtime("ClientOptions":#client-options clientOptions) + ruby: Ably::Realtime.new("ClientOptions":#client-options client_options) + java: new io.ably.lib.AblyRealtime("ClientOptions":#client-options clientOptions) + csharp: new AblyRealtime("ClientOptions":#client-options clientOptions) + objc: (instancetype)initWithOptions:("ARTClientOptions":#client-options *)options; + swift: init(options: "ARTClientOptions":#client-options) + +blang[default]. + This will instance the library using the specified "ClientOptions":#client-options. + +blang[ruby]. + This will instance the library and create a new @Ably::Realtime::Client@ using the specified "@ClientOptions@":#client-options. + +bq(definition). + default: new Ably.Realtime(String keyOrTokenId) + ruby: Ably::Realtime.new(String key_or_token_id) + java: new io.ably.lib.AblyRealtime(String keyOrTokenIdString) + csharp: new AblyRealtime(string key) + objc: (instancetype)initWithKey:(NSString *)key
(instancetype)initWithToken:(NSString *)token + swift: init(key: String)
init(token: String) + +This will instance the Realtime library with the provided API key or Token ID string. + +The Realtime constructor is used to instance the library. The Realtime library may be instanced multiple times with the same or different "@ClientOptions@":#client-options in any given context. Except where specified otherwise, instances operate independently of one another. + + +h3(#authentication). Authentication + +The Realtime library needs to have credentials to be able to authenticate with the Ably service. Ably supports both Basic and Token based authentication schemes. Read more on "authentication":/realtime/authentication. + +h4. Basic Authentication + +You can pass a full-length API key in as "@ClientOptions#key@@ClientOptions#Key@":#client-options (or just straight into the constructor instead of a @ClientOptions@ instance), as obtained from the "application dashboard":http://support.ably.io/solution/articles/3000030053-how-do-i-access-my-app-dashboard. Use this option if you wish to use "Basic authentication":/core-features/authentication/#basic-authentication, or if you want to be able to "request Ably Tokens":/realtime/authentication/#request-token without needing to defer to a separate entity to sign "Ably TokenRequests":/realtime/authentication/#token-request. Note that initializing the library with a @key@@Key@ does not necessarily mean that the library will use Basic auth; it is also able to create and sign Ably "TokenRequests":/realtime/authentication/#token-request, and can use token authentication for itself if it needs to or if "@ClientOptions#useTokenAuth@":#client-options is enabled. + +h4. Token Authentication + +The "@ClientOptions#token@":#client-options option takes a @token@ string or "@tokenDetails@":/realtime/types#token-details object, which may have been obtained from some other instance that requested the Ably Token. This option is rarely used in production since tokens are short-lived, so generally you would not start with a token without the means to refresh it. The "@authUrl@ and @authCallback@@AuthUrl@ and @AuthCallback@@:auth_url@ and @:auth_callback@ options":#client-options allow the library to request new Ably-compatible tokens or Ably TokenRequests as it needs to; using these options allows the library to be instanced without a @key@ or @token@@Key@ or @Token@, and an initial token will be obtained automatically when required. + +Read more on "authentication":/realtime/authentication. + +h2(#properties). + default: AblyRealtime Properties + jsall: Ably.Realtime Properties + java: io.ably.lib.AblyRealtime Members + ruby: Ably::Realtime::Client Attributes + objc,swift: ARTRealtime Properties + +The Realtime client exposes the following public attributesmembersproperties: + +h6(#auth). + default: auth + csharp: Auth + +A reference to the "@Auth@":/realtime/authentication authentication object configured for this client library. + +h6(#push). + default: push + +A reference to the "@Push@":/realtime/push object in this client library. + +
+h6(#device). + default: device + +A reference to the "@LocalDevice@@ARTLocalDevice@":/general/push/activate-subscribe#local-device object. +
+ +h6(#channels). + default: channels + csharp: Channels + +"@Channels@":/realtime/channels#channels-object is a reference to the "@Channel@":/realtime/channels collection instance for this library indexed by the channel name. You can use the "@Get@":/realtime/channels#get method of this to get a @Channel@ instance. See "channels":/realtime/channels and "messages":/realtime/messages/ for more information. + +h6(#connection). + default: connection + csharp: Connection + +A reference to the "@Connection@":/realtime/connection object for this library instance. + +blang[ruby]. + h6(#rest-client). rest_client + + A reference to the "REST Client":/rest/usage configured with the same "@ClientOptions@":#client-options. The Realtime library is a super-set of the REST library, however accessing methods in the REST library, unlike the Realtime library, are blocking operations. + +h2(#methods). + default: AblyRealtime Methods + jsall: Ably.Realtime Methods + java: io.ably.lib.AblyRealtime Methods + ruby: Ably::Realtime::Client Methods + objc,swift: ARTRealtime Methods + +h6(#connect). + default: connect + csharp: Connect + +bq(definition). + default: connect() + ruby: "Deferrable":/realtime/types#deferrable connect -> yields "@Connection@":/realtime/connection + java: void connect() + csharp: void Connect() + +Explicitly calling @connect@ is unnecessary unless the "@ClientOptions@":#client-options @autoConnect@@auto_connect@@AuthConnect@ is disabled. This method calls "@connection.connect()@@connection.connect@@connection.Connect()@":/realtime/connection#connect and causes the connection to open, entering the @connecting@ state. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from this method. + + On successfully connecting to Ably, the registered success callbacks for the "@Deferrable@":/realtime/types#deferrable and any block provided to this method yields a "@Connection@":/realtime/connection object. + + Failure to connect will trigger the errback callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object containing an error response as defined in the "Ably REST API":/rest-api#common documentation. + +h6(#close). + default: close + csharp: Close + +bq(definition). + default: close() + ruby: "Deferrable":/realtime/types#deferrable close -> yields "@Connection@":/realtime/connection + java: void close() + csharp: void Close() + +This calls "@connection.close()@@connection.close@@connection.Close()@":/realtime/connection#close and causes the connection to close, entering the @closing@ state. Once @closed@, the library will not attempt to re-establish the connection without an explicit call to "@connect()@@connect@@Connect()@":/realtime/connection#connect. + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from this method. + + On successfully closing the connection, the registered success callbacks for the "@Deferrable@":/realtime/types#deferrable and any block provided to this method yields a "@Connection@":/realtime/connection object. + + Failure to close the connection will trigger the errback callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object containing an error response as defined in the "Ably REST API":/rest-api#common documentation. + +h6(#stats). + default: stats + csharp: Stats + +bq(definition). + default: stats(Object options, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":/realtime/types#paginated-result<"Stats":#stats> results)) + ruby: "Deferrable":/realtime/types#deferrable stats(Hash options) -> yields "PaginatedResult":/realtime/types#paginated-result<"Stats":#stats> + java: "PaginatedResult":/realtime/types#paginated-result<"Stats":#stats> stats("Param":#param[] options) + swift,objc: stats(query: ARTStatsQuery?, callback: ("ARTPaginatedResult":/realtime/types#paginated-result<"ARTStats":/realtime/types#stats>?, ARTErrorInfo?) -> Void) throws + csharp: Task<"PaginatedResult":/realtime/types#paginated-result<"Stats":/realtime/types#stats>> StatsAsync(StatsRequestParams query) + +This call queries the "REST @/stats@ API":/rest-api#stats and retrieves your application's usage statistics. A "PaginatedResult":/realtime/types#paginated-result is returned, containing an array of "Stats":#stats for the first page of results. "PaginatedResult":/realtime/types#paginated-result objects are iterable providing a means to page through historical statistics. "See an example set of raw stats returned via the REST API":/general/statistics. + +See "statistics":/realtime/statistics for more information. + +<%= partial partial_version('realtime/_stats') %> + +h6(#time). + default: time + csharp: Time + +bq(definition). + default: time(callback("ErrorInfo":/realtime/types#error-info err, Number time)) + ruby: "Deferrable":/realtime/types#deferrable time -> yields Time + java: long time() + csharp: Task TimeAsync() + objc,swift: time(callback: (NSDate?, NSError?) -> Void) + +Obtains the time from the Ably service as a @Time@ objecta @DateTimeOffset@ objectmilliseconds since epoch. (Clients that do not have access to a sufficiently well maintained time source and wish to issue Ably "TokenRequests":/realtime/authentication#token-authentication with a more accurate timestamp should use the @queryTime@ "clientOptions":#client-options instead of this method). + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @time@ is a number containing the number of milliseconds since the epoch. + + On failure to retrieve the Ably server time, @err@ contains an "@ErrorInfo@":#error-info object with an error response as defined in the "Ably REST API":/rest-api#common documentation. + +blang[java]. + h4. Returns + + On success, milliseconds since the epoch is returned. + + Failure to retrieve the Ably server time will raise an "@AblyException@":#ably-exception. + +blang[csharp]. + h4. Returns + + A @Task@ is returned from this method. + + When awaited on success it will return the server time converted to a @DateTimeOffset@. + + Failure to retrieve the Ably server time will raise an "@AblyException@":#ably-exception. + + +blang[ruby]. + h4. Returns + + A "@Deferrable@":/realtime/types#deferrable object is returned from this method. + + On success, the registered success callbacks for the "@Deferrable@":/realtime/types#deferrable and any block provided to the method yields a @Time@ object. + + Failure to retrieve the Ably server time will trigger the errback callbacks of the "@Deferrable@":/realtime/types#deferrable with an "@ErrorInfo@":#error-info object containing an error response as defined in the "Ably REST API":/rest-api#common documentation. + +<%= partial partial_version('rest/_request') %> + +h2(#related-types). Related types + +h3(#client-options). + default: ClientOptions + swift,objc: ARTClientOptions + java: io.ably.types.ClientOptions + csharp: IO.Ably.ClientOptions + +<%= partial partial_version('types/_realtime_client_options_intro') %> +<%= partial partial_version('types/_client_options') %> +<%= partial partial_version('types/_realtime_client_options') %> + +h3(#stats-type). + default: Stats object + swift,objc: ARTStats + java: io.ably.lib.types.Stats + ruby: Ably::Models::Stats + csharp: IO.Ably.Stats + +<%= partial partial_version('types/_stats') %> + +h3(#stats-granularity). + objc,swift: ARTStatsGranularity + +<%= partial partial_version('types/_stats_granularity') %> + +h3(#http-paginated-response). + default: HttpPaginatedResponse + +<%= partial partial_version('types/_http_paginated_response') %> + +h3(#param). + java: io.ably.lib.types.Param + +blang[java]. + <%= partial partial_version('types/_param'), indent: 2, skip_first_indent: true %> + diff --git a/content/rest-api/index.textile b/content/rest-api/index.textile index cedf73e65c..fbb5a7c8c8 100644 --- a/content/rest-api/index.textile +++ b/content/rest-api/index.textile @@ -147,14 +147,14 @@ By default, all requests receive the latest version of the API, which is current When we make backwards-incompatible API changes to the API, we release new versions. Therefore, we encourage you to explicitly request the version you are interfacing with in all requests using one of the following mechanisms: -1. Include a @X-Ably-Version@ header. Example: +1. Include the @X-Ably-Version@ header. Example: bc[sh]. curl https://rest.ably.io/time \ - -H "X-Ably-Version: 1.0" + -H "X-Ably-Version: 1.2" 2. Include the version query string param @v@. Example: -bc[sh]. curl https://rest.ably.io/time?v=1.0 +bc[sh]. curl https://rest.ably.io/time?v=1.2 h3(#pagination). Pagination @@ -302,7 +302,7 @@ bc[sh]. curl https://rest.ably.io/channels/rest-example/messages \ h3(#token-authentication). Token Authentication -*Token Authentication* uses an Ably-compatible token to authenticate with Ably without sharing a private API key. This can be an "Ably Token":/core-features/authentication#tokens obtained via the REST API "@requestToken@":#request-token"@RequestToken@":#request-token"@request_token@":/#request-token endpoint, an "Ably JWT":/core-features/authentication#ably-jwt signed by your API key, or an "External JWT":https://jwt.io object "with an embedded Ably-compatible token":/core-features/authentication#jwt-embed-process. Tokens are authentication credentials that are short-lived, and therefore they may more readily be distributed to clients where there is a risk of compromise. Tokens may also be issued with a particular scope - such as a limited set of "access rights or capabilities":/core-features/authentication#capabilities-explained or being limited to use by a specific "@clientId@@ClientId@ identity":#identified-clients - and therefore token-based authentication provides the flexibility to implement access and identity control policies in the application. See the "Token Authentication documentation":/core-features/authentication#token-authentication for more details. +*Token Authentication* uses an Ably-compatible token to authenticate with Ably without sharing a private API key. This can be an "Ably Token":/core-features/authentication#tokens obtained via the REST API "@requestToken@":#request-token"@RequestToken@":#request-token"@request_token@":#request-token endpoint, an "Ably JWT":/core-features/authentication#ably-jwt signed by your API key, or an "External JWT":https://jwt.io object "with an embedded Ably-compatible token":/core-features/authentication#jwt-embed-process. Tokens are authentication credentials that are short-lived, and therefore they may more readily be distributed to clients where there is a risk of compromise. Tokens may also be issued with a particular scope - such as a limited set of "access rights or capabilities":/core-features/authentication#capabilities-explained or being limited to use by a specific "@clientId@@ClientId@ identity":#identified-clients - and therefore token-based authentication provides the flexibility to implement access and identity control policies in the application. See the "Token Authentication documentation":/core-features/authentication#token-authentication for more details. h4(#using-token-auth). Usage in HTTP request header diff --git a/content/rest-api/versions/v1.1/beta.textile b/content/rest-api/versions/v1.1/beta.textile new file mode 100644 index 0000000000..287138a5f7 --- /dev/null +++ b/content/rest-api/versions/v1.1/beta.textile @@ -0,0 +1,379 @@ +--- +title: REST API Beta Features +index: 0 +api_separator: +jump_to: + Help with: + - Overview#overview + - Batch Publish#batch + - Batch Presence#batch-presence +--- + +h2(#overview). REST API Beta Features + +The contents of this section are beta features of our REST API. Although many of these features are fully functional, until the feature is released as generally available, the APIs may change. We recommend customers "get in touch":https://www.ably.io/contact if they want to use these features in production. + +h2(#batch). Batch requests + +Batch mode is a way of querying an API multiple times with a single HTTP request. The simplest example to consider is publishing a single message to multiple channels; a single request is made, indicating the channels and the contents of the message, and the system attempts to publish on all channels in parallel. Each of those attempts may succeed or fail independently. The batch mode API provides a way for the results of that request to be indicated back to the caller. + +h3(#batch-operation). Generic operation + +A batch mode request translates a single set of request details (ie the request body, params and headers) in to an array of requests for the underlying API. Each of those requests is then performed in parallel. + +Once each request has completed, the batch-mode response is formulated to be returned to the caller. There are three possible outcomes: + +* all requests succeeded. This is of course the expected case. The response is then an array containing the responses to the constituent queries in request order. +* the batch request failed prior to attempting the constituent API calls. This could typically be an authorisation failure, an invalid request, or some required element of the service being unavailable. The response is then an error response, with the applicable status code and error response body (with @message@, @code@ and any other relevant error information). +* one or more constituent API calls failed. In this case the response contains a generic @400@ status code and the response body contains an error object with a specific error code @40020@ signifying an error in a batch operation. The error body also then contains a @batchResponse@ property which in an array, in request order, containing the outcome of each of the constituent API calls. +This means that caller can usually make the call and have access to the success result or an error in the case of failure. In the case of a partial failure, many callers may just wish to know whether or not the call succeeded fully, in which case they do not need to look at the @batchResponse@ detail. However, callers that wish to know the detail of the outcome of each call can discover this from the @batchResponse@. + +All batch-mode api calls are limited to addressing at most 100 different channels per request. + +All @POST@/@PUT@ requests (such as bulk publish) are subject to a maximum request body size of 2MiB. + +h3(#batch-publish). Bulk publish + +The REST API has been extended to include batch-mode publish operations. The API concerned is: + +h6. POST /messages + +This is the new bulk publish API which is implicitly a batch-mode API. This performs a publish in parallel of messages to channels specified in the @request@ body. Check out the code example below on how to do this with the "@request@":/rest/usage#request method in the REST client library. + +```[javascript](code-editor:rest/batch-publish) +var ablyRest = new Ably.Rest({ key: '{{API_KEY}}' }) +var content = { "channels": [ "test1", "test2" ], "messages": { "data": 'myData' } } +ablyRest.request('post', '/messages', null, content, null, +function(err, response) { + if(err) { + alert('An error occurred; err = ' + err.toString()); + } else { + alert('Success! status code was ' + response.statusCode); + } +}); +``` + +You can also use the REST API directly with the following: + +```[json](code-editor:rest/batch-publish) +curl -X POST https://rest.ably.io/messages \ + -u "{{API_KEY}}" \ + -H "Content-Type: application/json" \ + --data '{ "channels": [ "test1", "test2"], +"messages": {"data": "My test message text" } }' +``` + +A single @BatchSpec@ is an object of the form: + +```[json] +{ + channels: , + messages: +} +``` + +where: +* @@ is a single channel name @String@, or an @Array@ of channel name @Strings@; and +* @@ is a single @Message@, or an @Array@ of @Messages@. + +The bulk publish endpoint accepts a request body containing either a single @BatchSpec@ object, or an array of @BatchSpec@ objects. + +Therefore the following are all valid request bodies for a bulk publish request: + +h5. Using a single BatchSpec object + +```[json] +{ + channels: ['a channel name, containing a comma', 'another channel name'], + messages: {data: 'My message contents'} +} +``` + +h5. Using multiple BatchSpec objects in an array + +```[json] +[ + { + channels: ['a channel name, containing a comma', 'another channel name'], + messages: {data: 'My message contents'} + }, + { + channels: 'single channel', + messages: [ + {data: 'My message contents'}, + {name: 'an event', data: 'My event message contents'}, + ] + } +] +``` + +There is an obvious mapping from the request body to the array of individual publish requests. + +The 100 channel limit caps the total number of _different_ channels in the request, irrespective of how they are arranged. So if the same channel name occurs in multiple @BatchSpec@ objects in a single requests, it will contribute only one channel towards the limit. + +The @messages@ array of each @BatchSpec@ is treated atomically (for each channel), and so subject to the usual "max message size limits":https://support.ably.io/support/solutions/articles/3000035792 — the total size of all @Messages@ in the array must be less than the limit (64kiB for paid accounts, 16kiB for free accounts). If the total number of messages you want to publish to one channel adds up to more than that, you can just split the messages for that channel up into multiple batchspecs. All you lose is atomicity (that is, it is then possible that the @Messages@ from one batchspec will be accepted, and the other rejected). + +Remember, the total size of each request body must be less than 2MiB. + +h4(#batch-publish-response). Batch reponses + +The request + +```[sh] +POST /messages +{ + channels: ['channel0', 'channel1', 'channel2'], + messages: {data: 'My test message text'} +} +``` + +would have the following possible outcomes: + +Success: + +```[sh] +status code: 201 +response body: +[ + { + "channel":"channel0", + "messageId":"w234r5t-fr5" + }, + { + "channel":"channel1", + "messageId":"vde4sfc0p" + }, + { + "channel":"channel2", + "messageId":"nh3exv8ih" + } +] +``` + +Common-cause failure: + +```[sh] +status code: 401 +response body: +{ + "error": { + "message":"Token expired", + "statusCode":401, + "code":40140 + } +} +``` + +Partial success: + +```[sh] +status code: 400 +response body +{ + "error": { + "message": "Batched response includes errors", + "statusCode":400, + "code":40020 + } + "batchResponse": [ + { + "channel":"channel0", + "messageId":"w234r5t-fr5" + }, + { + "channel":"channel1", + "messageId":"vde4sfc0p" + }, + { + "channel":"channel2", + "error": { + "message": "Given credentials do not have the required capability", + "statusCode": 401, + "code": 40160 + } + } + ] +} +``` + +h3(#batch-presence). Batch presence + +The REST API has been extended to include batch-mode presence operations. The API concerned is: + +h6. GET /presence + +This an API to retrieve the presence state of multiple channels and is a batch-mode API. This performs a presence get in parallel on a set of channels specified in the @request@ params. Check out the code example below on how to do this with the "@request@":/rest/usage#request method in the REST client library. + +```[javascript](code-editor:rest/batch-presence) +var ablyRest = new Ably.Rest({ key: '{{API_KEY}}' }) +var content = { "channel": } +ablyRest.request('GET', '/presence', content, null, {}, function(err, response) { + if(err) { + alert('An error occurred; err = ' + err.toString()); + } else { + alert('Success! status code was ' + response.statusCode); + } +}); +``` + +You can also use the REST API directly with the following: + +```[json](code-editor:rest/batch-presence) +curl -X GET https://rest.ably.io/presence?channel= \ + -u "{{API_KEY}}" +``` + +where: +* @@ is a @String@ of channel names separated by commas, or a @String@ of channel names separated by a given @separator@. + +Below is an example of a curl request which uses a @separator@ to separate channels @channel,1@ and @channel,2@: + +```[json](code-editor:rest/batch-presence) +curl -X GET https://rest.ably.io/presence?channel,1!channel,2=&separator=! \ + -u "{{API_KEY}}" +``` + +h4(#batch-presence-response). Batch responses + +The request: + +```[sh] +GET /presence +{ + channel: 'channel0,channel1,channel2' +} +``` + +would have the following possible outcomes: + +Success: + +```[sh] +status code: 200 +response body: +[ + { + "channel":"channel0", + "presence":[ + {"clientId": "user1", "action": "1"}, + {"clientId": "user2", "action": "1"} + ] + }, + { + "channel":"channel1", + "presence":[] + }, + { + "channel":"channel2", + "presence":[ + {"clientId": "user2", "action": "1"}, + {"clientId": "user3", "action": "1"} + ] + } +] +``` + +Common-cause failure: + +```[sh] +status code: 401 +response body: +{ + "error": { + "message":"Token expired", + "statusCode":401, + "code":40140 + } +} +``` + +Partial success: + +```[sh] +status code: 400 +response body +{ + "error": { + "message": "Batched response includes errors", + "statusCode":400, + "code":40020 + } + "batchResponse": [ + { + "channel":"channel0", + "presence":[ + {"clientId": "user1", "action": "1"}, + {"clientId": "user2", "action": "1"} + ] + }, + { + "channel":"channel1", + "presence":[] + }, + { + "channel":"channel2", + "error": { + "message": "Given credentials do not have the required capability", + "statusCode": 401, + "code": 40160 + } + } + ] +} +``` + +h4(#batch-presence-response). Batch responses with the request method + +When using the request method in a library, you'll need to handle the potential responses for success, partial success, and failure. + +For a response object @response@, @response.success@ will be true if the batch presence was successful, or @false@ if at least one presence request failed. @response.statusCode@ will be 200 for complete success, 400 for partial success, and 401 for an expected failure. @response.errorCode@ will contain the Ably error code, and @response.errorMessage@ will contain details of the error. + +For successful requests, @response.items@ will contain a list of the responses for each individual channel from which presence has been requested. + +For partially successful requests, @response.items.batchResponse@ will contain a list of the individual channel's results, be it an error or a success. You will know a partial success has occurred by the @response.errorCode@, which will be @40020@. + +Below would be an example of how to deal with these: + +```[javascript] +var ablyRest = new Ably.Rest({ key: '{{API_KEY}}' }) +var content = { "channel": "channel1,channel2" } +ablyRest.request('GET', '/presence', content, null, {}, function(err, response) { + if(err) { + // Throw error + } else { + if(response.success) { + // If complete success + for(i = 0; i < response.items.length; i++) { + // Each response item will be roughly of the style: + /* + { + "channel": "channel1", + "presence": [ + { "action": 1, "clientId": "CLIENT1" }, + { "action": 1, "clientId": "CLIENT2" } + ] + } + */ + } + } else if(response.errorCode === 40020) { + // If partial success + for(i = 0; i < response.items[0].batchResponse.length; i++) { + // Each batchResponse item will either be the same as success if it succeeded, or: + /* + { + "channel": "channel1", + "error": { + "code": 40160, + "message": "ERROR_MESSAGE", + "statusCode": 401 + } + } + */ + } + } else { + // If failed, check why + console.log(response.errorCode + ', ' + response.errorMessage); + } + } +}); +``` diff --git a/content/rest-api/versions/v1.1/index.textile b/content/rest-api/versions/v1.1/index.textile new file mode 100644 index 0000000000..8d05d31daf --- /dev/null +++ b/content/rest-api/versions/v1.1/index.textile @@ -0,0 +1,1243 @@ +--- +title: REST API Specification +section: rest +index: 500 +api_separator: +jump_to: + Help with: + - Overview#title + - Common API behavior#common + - Authentication + - Basic Authentication + - Token Authentication + Channel API: + - publish + - message history#message-history + - presence + - presence history + Push API: + - register device#post-device-registration + - update a device registration#update-device-registration + - get registered device#get-device-registration + - list registered devices#list-device-registrations + - reset a device's update token#reset-update-token + - unregister device#delete-device-registration + - unregister devices#delete-device-registrations + - subscribe to a channel#post-channel-subscription + - unsubscribe from channels#delete-channel-subscription + - list channel subscriptions#list-channel-subscriptions + - list channels#list-channels + - publish directly to device#push-publish + Authentication API: + - requestToken#request-token + Application API: + - stats + Utilities API: + - time +--- + + + +The Ably REST API provides a way for a wide range of server and client devices to communicate with the Ably service over "REST":http://en.wikipedia.org/wiki/Representational_State_Transfer. The REST API does not provide a realtime long-lived connection to Ably, but in all other respects is a simple subset of the full "realtime messaging API":/realtime. + +The primary use-case for the REST API is for servers that are part of the back-end of an application such as a web application, that publish messages, issue access tokens (temporary and client-specific) for its various clients, obtain message and presence history and retrieve statistics. + +p(tip). There are also various "beta features":/rest-api/beta for the REST API. + +The functional scope of the REST API includes: + +* authentication: authentication to the service as a client, plus creation of tokens for use by other clients; +* publication of messages; +* retrieval of message history; +* retrieval of presence state and presence; +* retrieval of statistics for application usage. + +Whilst use of our REST API directly is fully supported, we instead recommend that customers should use our "Ably REST client libraries that support a range of platforms":https://www.ably.io/download including "Javascript":https://www.ably.io/download, "iOS":https://www.ably.io/download, "Android":https://www.ably.io/download, "Java":https://www.ably.io/download, "Python":https://www.ably.io/download "Ruby":https://www.ably.io/download, ".NET":https://www.ably.io/download, "Go":https://www.ably.io/download, etc. The "client libraries are well documented":https://www.ably.io/documentation and provide additional features that improve performance and resilience that the REST API cannot deliver on its own, such as automatic re-routing around network problems by using alternative datacenters. You can use the "Rest#request()":/rest/usage#request method to make arbitrary api calls against Ably, to use endpoints (such as features in beta) that libraries do not yet have built in support for. + +h2(#common). Common API behavior + +h3(#general). General + +The REST API defaults to returning results, and expects request bodies, in JSON format. An Accept header is used to specify a specific response format - JSON or an alternative - and the following formats are allowed: + +* @application/json@: JSON; +* @application/javascript@: for JSONP. A @callback@ query parameter is also expected, which defaults simply to "callback"; +* @application/x-msgpack@: for "MessagePack":http://msgpack.org/, the efficient binary serialization format that is similar to JSON, but faster and smaller; +* @text/html@: HTML + +It is also possible to specify the response format with the @format@ query parameter (with supported values being @json@, @jsonp@, @msgpack@, @html@). Any Accept header takes precedence over a @format@ parameter. + +Similarly, POST requests may contain a request body in JSON or other formats as indicated by a @Content-Type@ header. The supported content types are: + +* @application/json@: JSON; +* @application/x-msgpack@: "MessagePack":http://msgpack.org/, the efficient binary serialization format that is similar to JSON, but faster and smaller; +* @application/x-www-form-urlencoded@: Form-encoded. + +Specific request and response data types are documented in the context of each API or route. + +A response status code of 20X (200, 201 or 204) indicates success. A successful result will typically provide a response body but certain operations (such as DELETE) may respond with a 204 response and no response body. + +All other "standard HTTP statusCodes":http://en.wikipedia.org/wiki/List_of_HTTP_status_codes signify an error. Errors from all APIs are returned as an object of the form: + +bc[json]. { + error: { + code: , + message: , + statusCode: + } +} + +Additionally, when you may not have access to the response body due to limitations of your HTTP client, we include the following custom Ably headers to work around that problem: + +bc[text]. X-Ably-ErrorCode: +X-Ably-ErrorMessage: + +The properties of an Ably error are: + +- code := A specific reason code as defined in the "public errors definition":https://github.com/ably/ably-common/blob/master/protocol/errors.json, where one is known +- statusCode := Where a code is not available, the statusCode provides a generic indication of the nature of the failure and maps to "standard HTTP statusCodes":http://en.wikipedia.org/wiki/List_of_HTTP_status_codes +- message := The message string is an English language string that aims to provide useful information to the developer. It is not necessarily intended to be an informative string for the end user + +Wherever possible, success response bodies contain links, in "HATEOS":http://en.wikipedia.org/wiki/HATEOAS style, to other resources relevant to the response; where these are present these are included as @href@ attributes on the applicable part of the response object. + +@GET@, @PUT@, @POST@ and @DELETE@ are available in all contexts where they make sense. @GET@ is always idempotent. + +h3(#versioning). Versioning + +By default, all requests receive the latest version of the API, which is currently @1.0@. + +When we make backwards-incompatible API changes to the API, we release new versions. Therefore, we encourage you to explicitly request the version you are interfacing with in all requests using one of the following mechanisms: + +1. Include the @X-Ably-Version@ header. Example: + +bc[sh]. curl https://rest.ably.io/time \ + -H "X-Ably-Version: 1.1" + +2. Include the version query string param @v@. Example: + +bc[sh]. curl https://rest.ably.io/time?v=1.1 + +h3(#pagination). Pagination + +REST APIs whose responses may have unbounded size have paginated responses; that is, if a full response to the query could exceed a limit (a default or a @limit@ given as a parameter to the call) the first response contains a subset of the results, and further "pages" of the result are available on subsequent requests. Each response (the initial response and responses to each subsequent request) is accompanied by one or more relative links relating to the current query. + +Responses contain one or more of the following relative links: + +- @first@ := a link to the first page of results for this query. This link also makes the query repeatable; any params that are resolved at query time (for example default values for omitted time-related params) have their resolved values included explicitly in the @first@ link. + +- @current@ := a stable link to the current page of results. + +- @next@ := on each page except the last in a paginated result, the next link is a link to the next page of results. + +Relative links are presented by default as an "RFC 5988 Link HTTP response header":http://tools.ietf.org/html/rfc5988; there is a separate @Link@ header for each relative link accompanying the response. The rel types used are @first@, @next@ and @current@ as specified in the RFC. + +A @Link@ header has the format: + +pre. Link: <{url}>; rel="{rel}" + +where @{url}@ is the URL of the link and @{rel}@ is the relation type. + +For example: + +pre. Link: <./stats?start=1380794880000&end=1380794881058&limit=100&unit=minute&direction=forwards +&format=json&first_start=1380794880000>; rel="first" + +In principle the link URL might be any valid URL but in practice it will always be a relative URL, and it must be interpreted relative to the original query URL. Clients should treat link URLs opaquely; in particular, params (such as @first_start@ in the example above) may be undocumented and unsupported except where a client uses the link URL in its entirety. + +Clients that are unable to process response headers may also request an @envelope@ response type. + +h3(#control-response-content). Control of response content + +By default the response body of a query response will contain the requested resource, encoded in the requested format. However, there are also query params that allow the response body to be adjusted to contain only a subset of the representation, or a restructured representation. + +The following params are supported: + +h4(#control-response-content-fields). fields + +Specifying @?fields=[,, ...]@ returns the representation containing only the specified fields. A field spec is a specifier which is either a single field - in which case the representation contains only that fields's value - or a dot-separated sequence of fields, in which case the representation contains only the fields matching those given in the field spec at each level in the hierarchy. + +h5. Example + +```[sh] + curl https://rest.ably.io/stats?fields=channels.peak,intervalId \ + -u "{{API_KEY}}" + + # Response + [{ + "channels": { "peak": 2 }, + "intervalId": "2015-11-20:15" + }] +``` + +h4(#flatten). flatten + +Specifying @?flatten=true@ will result in a flattened representation, with the returned object structure being flattened into one with a single level of long keys instead of a deep structure. When the results contain array elements, the array index of each element is included as the corresponding component of the result key. + +h5. Example + +```[sh] + curl https://rest.ably.io/stats?flatten=true&fields=channels.peak,intervalId \ + -u "{{API_KEY}}" + + # Response + { + "0.channels.peak": 2, + "0.intervalId": "2015-11-20:15" + } +``` + +h4(#select). select + +Specifying @?select=@ returns a representation of the resource containing only the fields that match the given path specification. Like the fields param this permits only specific fields to be obtained, but the path spec may contain wildcard elements, and all matching fields in the representation are returned. + +The result is a partially-flattened representation, as a map whose keys are the long keys that match the path spec, and the values are the corresponding values. When the results contain array elements, the array index of each element is included as the corresponding component of the result key. + +h5. Example + +```[sh] + curl https://rest.ably.io/stats?select=*.channels.* \ + -u "{{API_KEY}}" + + # Response + [{ + "0.channels.peak": 5, + "0.channels.min": 1, + "0.channels.mean": 3, + "0.channels.opened": 2 + }] +``` + +h3(#envelope-response-format). Envelope response format + +A client that is unable to access response headers or status code can request an @envelope@ response that contains the usual response, plus @Link@ header information and the response status code wrapped in an object as a single response body. This is useful for JSONP and may be useful in other environments. Envelope responses are only supported for JSON, JSONP and HTML formats. + +A client requests an envelope response by including an @envelope=@ param in the request. + +A JSON envelope response body response for a request with an @envelope=json@ param has the format: + +```[json] + { + "statusCode": , + "response": , + "rel": { + "first": , + ... + } + } +``` + +where the @response@ member references the API result in the usual format. The @rel@ member, present only in paginated responses, includes each of the rel links associated with the response. + +Envelope responses always are always made with a 200 status code; the status of the API request itself must be obtained from the @statusCode@ member of the response body. + +h2(#authentication). Authentication + +To understand the REST API it is easiest first to understand the various authentication methods that Ably supports. For a detailed explanation, view the "Authentication documentation":/core-features/authentication. + +Clients can access Ably, whether using REST or the Realtime service, by two methods, explained briefly below: + +h3(#basic-authentication). Basic Authentication + +Using one of the application keys created via the "application dashboard":http://support.ably.io/solution/articles/3000030053-how-do-i-access-my-app-dashboard, basic authentication provides the simplest method to authenticate with Ably, but has two important limitations: + +* the application key is transmitted over the network as part of a request; therefore it may only be used over TLS (HTTPS or "SSL") connections. This can be a performance disadvantage in some network environments where long round-trip times are amplified by the SSL handshake. +* the client using the API must be in possession of the application key, which potentially exposes the key to compromise. For example, it is not advisable to simply embed the key in a script in a public web page. + +h4(#using-basic-auth). Usage in HTTP request header + +pre. Authorization: Basic + +where @@ is the full application key string obtained through the dashboard, encoded with "RFC 4648 Base64":http://www.ietf.org/rfc/rfc4648.txt. + +Example request: + +bc[sh]. curl https://rest.ably.io/channels/rest-example/messages \ + --header "Authorization: Basic {{API_KEY_BASE64}}" + +When using a generic HTTP client library that accepts separate username and password arguments for an HTTP request, the application key can be split at the first colon, with the initial segment being used as the username, and the remaining string (without the leading colon) used as the password. + +Example request: + +bc[sh]. curl https://rest.ably.io/channels/rest-example/messages \ + --user "{{API_KEY}}" + +h3(#token-authentication). Token Authentication + +*Token Authentication* uses an Ably-compatible token to authenticate with Ably without sharing a private API key. This can be an "Ably Token":/core-features/authentication#tokens obtained via the REST API "@requestToken@":#request-token"@RequestToken@":#request-token"@request_token@":#request-token endpoint, an "Ably JWT":/core-features/authentication#ably-jwt signed by your API key, or an "External JWT":https://jwt.io object "with an embedded Ably-compatible token":/core-features/authentication#jwt-embed-process. Tokens are authentication credentials that are short-lived, and therefore they may more readily be distributed to clients where there is a risk of compromise. Tokens may also be issued with a particular scope - such as a limited set of "access rights or capabilities":/core-features/authentication#capabilities-explained or being limited to use by a specific "@clientId@@ClientId@ identity":#identified-clients - and therefore token-based authentication provides the flexibility to implement access and identity control policies in the application. See the "Token Authentication documentation":/core-features/authentication#token-authentication for more details. + +h4(#using-token-auth). Usage in HTTP request header + +pre. Authorization: Bearer + +The @@ is either the @token@ attribute of the Ably Token generated by "@requestToken@":/rest-api/#request-token encoded with "RFC 4648 Base64":http://www.ietf.org/rfc/rfc4648.txt, or an "Ably JWT":/core-features/authentication#ably-jwt. If using a generic HTTP client library it will probably be necessary to add the @Authorization@ header explicitly. + +Example request: + +bc[sh]. curl https://rest.ably.io/channels/rest-example/messages \ + --header "Authorization: Bearer {{TOKEN_BASE64}}" + +h1(#api). API routes + +h2(#channel). Channel routes + +Routes providing access to the messaging service within a channel scope. + +h3(#publish). Publish one or more messages on a channel + +h6. POST rest.ably.io/channels/@@/messages + +Publish a message on a channel. Note that since the REST API is stateless, publication using this API is outside the context of any specific connection. + +The request body contains message details and is an object of the form: + +bc[json]. { + name: , + data: , + encoding: , + clientId: , + connectionKey: , + id: , + extras: +} + +In JSON format, the accepted types for the @data@ payload are: + +* string +* any JSON-encodable Array or Object. + +MessagePack additionally "supports byte arrays":https://github.com/msgpack/msgpack/blob/master/spec.md#formats-bin + +A message may be published over REST on behalf of an existing realtime connection when a valid @connectionKey@ is present. For example, if you want to publish a message using the REST API so that it appears to come from an existing connected realtime client, then the connection's "private (secret) connection key":/realtime/connection#key must be included. See a "publish on behalf of a realtime client example":<%= JsBins.url_for('rest/publish-on-behalf-of-client') %>. + +Example request: + +bc[sh]. curl -X POST https://rest.ably.io/channels/rest-example/messages \ + -u "{{API_KEY}}" \ + -H "Content-Type: application/json" \ + --data '{ "name": "publish", "data": "example" }' + +If you're wanting to publish a message *idempotently* (multiple publishes of the same message are not duplicated), you should set the @id@ to be unique between each message. Only the first message received by Ably with a specific @id@ will then be sent to a channel and its subscribers. + +If you wish to publish a message to multiple channels at once, you should consider using our "batch publish functionality":/rest-api/beta#batch. + +h5(#idempotent-publish). Idempotent publishing + +It is possible for a client publishing through REST to not receive an acknowledgement of receipt from Ably for numerous reasons such as network failures outside of our control. In this scenario, you will likely wish to re-publish the message, but not risk duplicating it within the network. This is possible through the addition of an @id@ in the body of your POST, where the @id@ should uniquely identify the message. + +h5(#message-extras). Message extras + +Messages can include an optional @extras@ field, used by extensions to Ably's core realtime service. + +h5(#message-extras-push). Send push notification + +You can send a push notification to devices "subscribed to a channel":#post-channel-subscription by setting the @push@ field in the @extras@ object, like this: + +bc[json]. { + <... message fields ...> + extras: { + push: { + data: , + notification: { + title: , + body: , + icon: , + sound: , + collapseKey: + } + apns: , + fcm: , + web: + } + } +} + +For each underlying transport service (like APNs, FCM/GCM, etc.) an object can be provided with the same shape as the parent object, plus any transport-specific field you may want to add (e. g. @content-available@ for APNs). + +Full example of a request publishing a message with a push payload: + +bc[sh]. curl -X POST https://rest.ably.io/channels/push-enabled:rest-example/messages \ + -u "{{API_KEY}}" \ + -H "Content-Type: application/json" \ + --data \ + ' +{ + "name": "some event name for realtime receivers", + "data": "example non-push data for realtime receivers", + "extras": { + "push": { + "notification": { + "title": "Hello from Ably!", + "body": "Example push notification from Ably." + }, + "data": { + "foo": "bar", + "baz": "qux" + }, + "apns": { + "notification": { + "content-available": 1, + "sound": "ably-ios.wav" + } + } + } + } +} +' + +h5. Parameters + +- enveloped := if present and equal to @false@, does an "unenveloped publish":#unenveloped + +h5. Options + +- Content-Type := @application/json@, @application/x-msgpack@ or @application/x-www-form-urlencoded@ +- Accept := @application/json@ (the default), @application/x-msgpack@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication) + +h5. Returns + +When successful, returns an object with @channel@ and @messageId@ properties, in case you want to know the message ID assigned to correlate with messages received by realtime subscribers or Reactor recipients. When unsuccessful, returns an error as an "ErrorInfo":/rest/types#error-info object. + +h3(#unenveloped). Unenveloped publish + +If the @enveloped@ parameter is present and set to @false@, the request body is interpreted as the data payload for a message to be published, rather than (as it would normally be) as a @Message@ object or array of @Message@ objects. This can be useful if, for example, you want an Ably publish to be triggered by a webhook from some third-party service where you cannot control the format of the request body. + +Example json request: + +bc[sh]. curl -X POST https://rest.ably.io/channels/rest-example/messages?enveloped=false \ +-H 'content-type: application/json' --data '{"some":"json"}' \ +-u "{{API_KEY}}" + +Example plain text request: + +bc[sh]. curl -X POST https://rest.ably.io/channels/rest-example/messages?enveloped=false \ +-H 'content-type: text/plain' --data 'some plain text' \ +-u "{{API_KEY}}" + +h5. Headers and parameters + +- @X-Ably-MessageId@ := Optional message ID, used for "idempotent publishing":#idempotent-publish. Can also be specified as a @messageId@ querystring parameter. Equivalent to @Message.id@ +- @X-Ably-Name@ := Optional message name. Can also be specified as a @name@ querystring parameter. Equivalent to @Message.name@ +- @X-Ably-ConnectionKey@ := Optional connection key, used to publish on behalf of a realtime connection, see "documentation above":#publish. Can also be specified as a @connectionKey@ querystring parameter. Equivalent to @Message.connectionKey@ +- @X-Ably-Encoding@ := Optional message encoding. Allows you to specify the encoding of the message to allow recipient client libraries to decode it. For example, an encoding of @json@ will instruct client libraries to automatically JSON-parse the message on receipt. Also useful if sending an encrypted message, to allow client libraries to decrypt it (assuming they have the correct key). Must be in the correct format per our "client lib development guide":/client-lib-development-guide/features/#RSL4 ; if unsure, leave this unspecified. Can also be specified as an @encoding@ querystring parameter. Equivalent to @Message.encoding@ +- @X-Ably-ClientId@ := Optional client ID, base64-encoded (to allow arbitrary unicode). Can also be specified as a @clientId@ querystring parameter (_not_ base64-encoded). Equivalent to @Message.clientId@ + +h5. Options + +- Content-Type := @text/plain@ (for @utf-8@ text request bodies) or @application/octet-stream@ (for binary request bodies). @application/json@ will also work, and is equivalent to specifying a content-type of @text/plain@ together with @X-Ably-Encoding@ of @json@. +- Accept := @application/json@ (the default), @application/x-msgpack@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication) + +h5. Returns + +When successful, returns an object with @channel@ and @messageId@ properties, in case you want to know the message ID assigned to correlate with messages received by realtime subscribers or Reactor recipients. When unsuccessful, returns an error as an "ErrorInfo":/rest/types#error-info object. + +h3(#message-history). Retrieve message history for a channel + +h6. GET rest.ably.io/channels/@@/messages + +If a channel is "configured to persist messages":http://support.ably.io/support/solutions/articles/3000030057-what-are-channel-rules-and-how-can-i-use-them-in-my-app, then all messages on that channel, within "your account retention period":http://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for, are available via this API endpoint. If persistence is not configured, then there are no guarantees as to how many historical messages will be available for the channel. "Find out more about message persistence":http://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for. + +Example request: + +bc[sh]. curl https://rest.ably.io/channels/rest-example/messages \ + -u "{{API_KEY}}" + +h5. Parameters + +- start := _beginning of time_ The start of the query interval as a time in milliseconds since the epoch. A message qualifies as a member of the result set if it was received at or after this time. +- end := _now_ The end of the query interval as a time in milliseconds since the epoch. A message qualifies as a member of the result set if it was received at or before this time. +- limit := _100_ The maximum number of records to return. A limit greater than 1,000 is invalid. +- direction := _backwards_ The direction of this query. The direction determines the order of the returned result array, but also determines which end of the query interval is the start point for the search. For example, a forwards query uses @start@ as the start point, whereas a backwards query uses @end@ as the start point. + +h5. Options + +- Content-Type := not applicable +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication) + +h5. Returns + +In each case a successful result is a "paginated response":#pagination with an array containing the items that match the query (and it may be empty). + +bc[json]. [{ + id: + name: , + data: , + timestamp: +}] + +h3(#presence). Retrieve instantaneous presence status for a channel + +h6. GET rest.ably.io/channels/@@/presence + +Obtain the set of members currently present for a channel. + +Example request: + +bc[sh]. curl https://rest.ably.io/channels/rest-example/presence \ + -u "{{API_KEY}}" + +h5. Parameters + +- clientId := optional filter to restrict members present with that @clientId@ +- connectionId := optional filter to restrict members present with that @connectionId@ +- limit := _100_ The maximum number of records to return. A limit greater than 1,000 is invalid. + +h5. Options + +- Content-Type := not applicable +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication) + +h5. Returns + +A successful request returns a "paginated response":#pagination with an array containing the members that are currently present on the given channel. If there are no members present, an empty collection is returned. + +bc[json]. [{ + id: , + clientId: , + connectionId: + timestamp: + action: , + data: +}] + +h3(#presence-history). Retrieve presence state history for a channel + +h6. GET rest.ably.io/channels/@@/presence/history + +Obtain the history of presence messages for a channel. + +Example request: + +bc[sh]. curl https://rest.ably.io/channels/rest-example/presence/history \ + -u "{{API_KEY}}" + +h5. Parameters + +- start := _beginning of time_ The start of the query interval as a time in milliseconds since the epoch. A message qualifies as a member of the result set if it was received at or after this time. +- end := _now_ The end of the query interval as a time in milliseconds since the epoch. A message qualifies as a member of the result set if it was received at or before this time. +- limit := _100_ The maximum number of records to return. A limit greater than 1,000 is invalid. +- direction := _backwards_ The direction of this query. The direction determines the order of the returned result array, but also determines which end of the query interval is the start point for the search. For example, a forwards query uses @start@ as the start point, whereas a backwards query uses @end@ as the start point. + +h5. Options + +- Content-Type := not applicable +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication) + +h5. Returns + +A successful request returns a "paginated response":#pagination with an array containing the members that are currently present on the given channel. If there are no members present, an empty collection is returned. + +bc[json]. [{ + id: , + clientId: , + connectionId: + timestamp: + action: , + data: +}] + +h3(#metadata-rest). Retrieve metadata for a channel + +<%= partial partial_version('shared/_channel_metadata') %> + +h3(#enumeration-rest). Enumerate all active channels + +<%= partial partial_version('shared/_channel_enumeration') %> + +h2(#push). Push + +h3(#post-device-registration). Register a device for receiving push notifications + +Register a device's details, including the information necessary to deliver push notifications to it. + +h6. POST rest.ably.io/push/deviceRegistrations + +The request body contains device and push recipient details, and is an object of the form: + +bc[json]. { + id: , + clientId: , + platform: , + formFactor: , + metadata: , + push: { + recipient: { + transportType: , + + } + } +} + +The recipient address attributes are necessary and vary by underlying transport service. + +For APNs: + +bc[json]. { + deviceToken: +} + +For GCM/FCM: + +bc[json]. { + registrationToken: +} + +For web: + +bc[json]. { + targetUrl: + encryptionKey: +} + +Example request: + +bc[sh]. curl -X POST https://rest.ably.io/push/deviceRegistrations \ + -u "{{API_KEY}}" + -H "Content-Type: application/json" \ + --data \ +'{ + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "platform": "ios", + "formFactor": "phone", + "push": { + "recipient": { + "transportType": "apns", + "deviceToken": "740f4707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bb78ad" + } + } +}' + +h5. Parameters + +None + +h5. Options + +- Content-Type := @application/json@, @application/x-msgpack@ or @application/x-www-form-urlencoded@ +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication with @push-subscribe@ or @push-admin@ capability; when registering for a client ID with a @push-subscribe@ capability, the token must be associated with that client ID) + +A successful request returns the just-registered device details. It includes an @updateToken@, which is a token that can be used by typically a mobile device to "authenticate":#token-authentication with Ably later and "update an existing device registration":#update-device-registration (for example, for updating a GCM or FCM registration token). + +An unsuccessful request returns an error. + +h3(#update-device-registration). Update a device registration + +Device registrations can be either upserted (the existing registration is replaced entirely) with a "@PUT@":#put-device-registration operation, or specific attributes of an existing registration can be updated using a "@PATCH@":#patch-device-registration operation: + +h6(#put-device-registration). PUT rest.ably.io/push/deviceRegistrations/@@ + +The body must have the same shape as when "registering the device":#post-device-registration. The @PUT@ operation will replace the existing device registration, however please bear in mind that a registered device in Ably is largely immutable. As such, only the following attributes are currently updatable and any attempt to modify other fields will result in the update failing: + +* @clientId@ +* @metadata@ +* @push.recipient@ + +Additionally, if the @push.state@ or @updateToken@ attributes are provided, they will be accepted if they match the existing value. However if they differ (and are not @null@ or @omitted@), then the update will fail. + +If you need to make changes to any other fields, you will have to "deregister the existing device":#delete-device-registration and then "register a new one":#post-device-registration. + +Example request: + +bc[sh]. curl -X PUT https://rest.ably.io/push/deviceRegistrations/01ARZ3NDEKTSV4RRFFQ69G5FAV \ + -u "{{API_KEY}}" + -H "Content-Type: application/json" \ + --data \ +'{ + "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "platform": "ios", + "formFactor": "phone", + "metadata": { + "timezone": "PST" + }, + "push": { + "recipient": { + "transportType": "apns", + "deviceToken": "740f4707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bb78ad" + } + } +}' + +h5. Parameters + +None + +h5. Options + +- Content-Type := @application/json@, @application/x-msgpack@ or @application/x-www-form-urlencoded@ +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication with @push-admin@ capability) + +A successful request returns the updated device details. + +An unsuccessful request returns an error. + +h6(#patch-device-registration). PATCH rest.ably.io/push/deviceRegistrations/@@ + +The body must have the same shape as when "registering the device":#post-device-registration, except only fields to be changed should be provided. Any fields provided replace existing values. Please bear in mind that fields whose values are structured (JSON-like arrays or objects) types will replace existing values as opposed to be merged into existing values. @metadata@ and @push.recipient@ are examples of these types. Currently only the following attributes are currently updatable and any attempt to modify other fields will result in the update failing: + +* @clientId@ (this field is only editable with a @push-admin@ capability) +* @metadata@ (this field is only editable with a @push-admin@ capability) +* @push.recipient@ + +If you need to make changes to any other fields, you will have to "deregister the existing device":#delete-device-registration and then "register a new one":#post-device-registration. + +Example request: + +bc[sh]. curl -X PATCH https://rest.ably.io/push/deviceRegistrations/01ARZ3NDEKTSV4RRFFQ69G5FAV \ + -u "{{API_KEY}}" + -H "Content-Type: application/json" \ + --data \ +'{ + "metadata": { + "myKey": "value" + } + }' + +h5. Parameters + +None + +h5. Options + +- Content-Type := @application/json@, @application/x-msgpack@ or @application/x-www-form-urlencoded@ +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication with @push-admin@ capability, or "token":#token-authentication authentication using the device's update token) + +A successful request returns the updated device details. + +An unsuccessful request returns an error. + +h3(#get-device-registration). Get details from a registered device + +h6. GET rest.ably.io/push/deviceRegistrations/@@ + +Obtain the details for a device registered for receiving push registrations. + +Example request: + +bc[sh]. curl https://rest.ably.io/push/deviceRegistrations/01ARZ3NDEKTSV4RRFFQ69G5FAV \ + -u "{{API_KEY}}" + +h5. Parameters + +None. + +h5. Options + +- Content-Type := not applicable +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication with @push-admin@ capability) + +h5. Returns + +A JSON object like: + +bc[json]. { + id: , + clientId: + platform: + formFactor: , + metadata: , + updateToken: , + push: { + recipient: { + transportType: , + + }, + state: + } +} + +or a 404 error if a device by that ID does not exist. + +h3(#list-device-registrations). List registered devices + +h6. GET rest.ably.io/push/deviceRegistrations + +Obtain the details for devices registered for receiving push registrations. + +Example request: + +bc[sh]. curl https://rest.ably.io/push/deviceRegistrations \ + -u "{{API_KEY}}" + +h5. Parameters + +- deviceId := optional filter to restrict to devices associated with that @deviceId@ +- clientId := optional filter to restrict to devices associated with that @clientId@ +- limit := _100_ The maximum number of records to return. A limit greater than 1,000 is invalid. + +h5. Options + +- Content-Type := not applicable +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication with @push-admin@ capability) + +h5. Returns + +A successful request returns a "paginated response":#pagination of: + +bc[json]. [{ + id: , + clientId: + platform: + formFactor: , + metadata: , + updateToken: , + push: { + recipient: { + transportType: , + + }, + state: + } +}] + +h3(#reset-update-token). Reset a registered device's update token + +h6. POST rest.ably.io/push/deviceRegistrations/@@/resetUpdateToken + +Example request: + +bc[sh]. curl -X POST https://rest.ably.io/push/01ARZ3NDEKTSV4RRFFQ69G5FAV/resetUpdateToken \ + -u "{{API_KEY}}" + +h5. Parameters + +None + +h5. Options + +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication with @push-admin@ capability, or "token":#token-authentication authentication using the device's update token) + +A successful request returns the updated device details. + +A unsuccessful request returns an error. + +h3(#delete-device-registration). Unregister a single device for push notifications + +h6. DELETE rest.ably.io/push/deviceRegistrations/@@ + +Unregisters a single device by its device ID. All its subscriptions for receiving push notifications through channels will also be deleted. + +Please note that this operation is done asynchronously so immediate requests subsequent to this delete request may briefly still return the device. + +Example request: + +bc[sh]. curl -X DELETE \ + https://rest.ably.io/push/deviceRegistrations/01ARZ3NDEKTSV4RRFFQ69G5FAV \ + -u "{{API_KEY}}" + +h5. Parameters + +None + +h5. Options + +- Content-Type := not applicable +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication with @push-admin@ capability) + +h5. Returns + +A successful request returns an empty response. + +An unsuccessful request returns an error. + +h3(#delete-device-registrations). Unregister matching devices for push notifications + +h6. DELETE rest.ably.io/push/deviceRegistrations + +Unregisters devices. All their subscriptions for receiving push notifications through channels will also be deleted. + +Please note that this operation is done asynchronously so immediate requests subsequent to this delete request may briefly still return the device. + +Example request: + +bc[sh]. curl -X DELETE \ + https://rest.ably.io/push/deviceRegistrations?deviceId=01ARZ3NDEKTSV4RRFFQ69G5FAV \ + -u "{{API_KEY}}" + +h5. Parameters + +- deviceId := Filter to restrict to subscriptions for that @deviceId@. Cannot be used with @clientId@. +- clientId := Filter to restrict to subscriptions associated with that @clientId@. Cannot be used with @deviceId@. + +h5. Options + +- Content-Type := not applicable +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication with @push-admin@ capability) + +h5. Returns + +A successful request returns an empty response. + +An unsuccessful request returns an error. + +h3(#post-channel-subscription). Subscribe to a channel + +Subscribe either a single device or all devices associated with a client ID to receive push notifications from messages sent to a channel. + +h6. POST rest.ably.io/push/channelSubscriptions + +The request body contains subscription details and is an object of the form: + +bc[json]. { + channel: , + deviceId: , + clientId: +} + +bc[sh]. curl -X POST https://rest.ably.io/push/channelSubscriptions \ + -u "{{API_KEY}}" + -H "Content-Type: application/json" \ + --data \ + ' +{ + "channel": "rest-example", + "deviceId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" +} +' + +h5. Parameters + +None + +h5. Options + +- Content-Type := @application/json@, @application/x-msgpack@ or @application/x-www-form-urlencoded@ +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication with @push-subscribe@ or @push-admin@ capability; when subscribing for a client ID with a @push-subscribe@ capability, the token must be associated with that client ID) + +A successful request returns an empty object. + +A unsuccessful request returns an error. + +h3(#delete-channel-subscription). Unsubscribe from push notifications for channels + +h6. DELETE rest.ably.io/push/channelSubscriptions + +Stop receiving push notifications when push messages are published on the specified channels. + +Please note that this operation is done asynchronously so immediate requests subsequent to this delete request may briefly still return the subscription. + +Example request: + +bc[sh]. curl -X DELETE \ + https://rest.ably.io/push/channelSubscriptions?deviceId=01ARZ3NDEKTSV4RRFFQ69G5FAV \ + -u "{{API_KEY}}" + +h5. Parameters + +- channel := string, optional. If not set, all subscriptions on all channels will be deleted. +- deviceId := Filter to restrict to subscriptions for that @deviceId@. Cannot be used with @clientId@. +- clientId := Filter to restrict to subscriptions associated with that @clientId@. Cannot be used with @deviceId@. + +h5. Options + +- Content-Type := not applicable +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication with @push-subscribe@ or @push-admin@capability; when deleting by client ID with a @push-subscribe@ capability, the token must be associated with that client ID) + +h5. Returns + +A successful request returns an empty response. + +An unsuccessful request returns an error. + +h3(#list-channel-subscriptions). List channel subscriptions + +h6. GET rest.ably.io/push/channelSubscriptions + +Get a list of push notification subscriptions to channels. + +Example request: + +bc[sh]. curl https://rest.ably.io/push/channelSubscriptions \ + -u "{{API_KEY}}" + +h5. Parameters + +- channel := Filter to restrict to subscriptions associated with that @channel@. +- clientId := Filter to restrict to subscriptions associated with that @clientId@. Cannot be used with @deviceId@. +- deviceId := Filter to restrict to subscriptions for that @deviceId@. Cannot be used with @clientId@. +- limit := _100_ The maximum number of records to return. A limit greater than 1,000 is invalid. + +h5. Options + +- Content-Type := not applicable +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication with @push-admin@ capability) + +h5. Returns + +A successful request returns a "paginated response":#pagination of: + +bc[json]. [{ + channel: , + clientId: + deviceId: , +}] + +h3(#list-channels). List all channels with at least one subscribed device + +h6. GET rest.ably.io/push/channels + +Example request: + +bc[sh]. curl https://rest.ably.io/push/channels \ + -u "{{API_KEY}}" + +h5. Parameters + +None + +h5. Options + +- Content-Type := not applicable +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication with @push-admin@ capability) + +h5. Returns + +A successful request returns a "paginated response":#pagination of: + +bc[json]. [] + +h3(#push-publish). Publish a push notification to a single device + +Convenience endpoint to deliver a push notification payload to a single device or set of devices identified by their "client identifier":/core-features/authentication#identified-clients. + +If you want to send a push notification to multiple devices or use a more flexible publish-subscribe architecture so that you don't need to know about recipient devices's details, we recommend you look at "registering devices for push":#post-device-registration, then "subscribe them to channels":#post-channel-subscription, and then "send messages to the channels with push payloads":#message-extras-push. + +This direct publish endpoint is designed for customers who typically have legacy devices they wish to push directly to, or if they want to publish to all devices for a single user. + +h6. POST rest.ably.io/push/publish + +The request body is an object of the form: + +bc[json]. { + recipient: + <... rest of the fields just like a normal push-enabled message's extras.push object ...> +} + +Example request: + +bc[sh]. curl -X POST https://rest.ably.io/push/publish \ + -u "{{API_KEY}}" \ + -H "Content-Type: application/json" \ + --data \ + ' +{ + "recipient": { + "clientId": "myClientId" + }, + "notification": { + "title": "Hello from Ably!", + "body": "Example push notification from Ably." + }, + "data": { + "foo": "bar", + "baz": "qux" + } +}' + +The @recipient@ field supports delivering either to devices "registered to Ably":#post-device-registration by device ID, by their associated client ID, or directly to devices using the underlying notifications service (GCM, APNs, etc.), thus bypassing registrations to Ably altogether. + +By device ID: + +bc[json]. { + deviceId: +} + +By client ID: + +bc[json]. { + clientId: +} + +For APNs devices: + +bc[json]. { + transportType: "apns", + deviceToken: +} + +For GCM/FCM devices: + +bc[json]. { + transportType: , + registrationToken: +} + +For web: + +bc[json]. { + transportType: "web", + targetUrl: , + encryptionKey: +} + +The rest of the fields are "normal push-enabled Ably message's @push.extras@ object":#message-extras-push. + +h5. Parameters + +None + +h5. Options + +- Content-Type := @application/json@, @application/x-msgpack@ or @application/x-www-form-urlencoded@ +- Accept := not applicable +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication with @push-admin@ capability) + +A successful request returns an empty response. + +An unsuccessful request returns an error. + +h2(#authentication). Authentication + +h3(#request-token). Request an access token + + + +h6. POST rest.ably.io/keys/@keyName@/requestToken + +This is the means by which clients obtain access tokens to use the service. The construction of an Ably "@TokenRequest@":/rest-api/token-request-spec is described in the "Authentication Ably TokenRequest spec documentation":/rest-api/token-request-spec. The resulting @token response@ object contains the token properties as defined in "Ably TokenRequest spec":/rest-api/token-request-spec. + +Example request: + +bc[sh]. curl -X POST "https://rest.ably.io/keys/{{API_KEY_NAME}}/requestToken" \ + -u "{{API_KEY}}" \ + -H "Content-Type: application/json" \ + --data '{ "keyName": "{{API_KEY_NAME}}", "timestamp": {{MS_SINCE_EPOCH}} }' + +h5. Parameters + +None + +h5. Options + +- Request body := signed or unsigned Ably "@TokenRequest@":/rest-api/token-request-spec. All Ably "@TokenRequests@":/rest-api/token-request-spec require values for @keyName@ and @timestamp@ attributes. In addition, signed Ably "@TokenRequests@":/rest-api/token-request-spec require values for attributes @nonce@ and @mac@. +- Content-Type := @text/plain@ +- Accept := @application/json@ by default, or @application/x-msgpack@ +- Auth required := no (for signed Ably "@TokenRequests@":/rest-api/token-request-spec), yes (for unsigned Ably "@TokenRequests@":/rest-api/token-request-spec, "basic":#basic-authentication or "token":#token-authentication permitted) + +h5. Returns + +A successful request will return a "token details object":/realtime/types#token-details containing the token string. + +bc[json]. { + "token": "xVLyHw.CLchevH3hF....MDh9ZC_Q", // token string + "keyName": "xVLyHw.mDYnFA", + "issued": 1428356667, + "expires": 1428360267, + "capability": "{\"*\":[\"*\"]}" +} + +h2(#application). Application routes + +Routes providing access to the messaging service within an application scope. + +h3(#stats). Retrieve usage statistics for an application + +h6. GET rest.ably.io/stats + +Example request: + +bc[sh]. curl https://rest.ably.io/stats?unit=hour \ + -u "{{API_KEY}}" + +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. + +Stats queries are made by specifying a query interval and the granularity expected in the results. The query interval is expressed as a start and end time, each being a timestamp in milliseconds since the epoch. Stats are aggregated by the system in 'sub-minute' intervals of 6s (ie 0.1m), so query interval start and end times are rounded down to the nearest sub-minute boundary. + +h5. Parameters + +- start := _beginning of time_ The start of the query interval as a time in milliseconds since the epoch. +- end := _now_ The end of the query interval as a time in milliseconds since the epoch. +- limit := _100_ The maximum number of records to return. A limit greater than 1,000 is invalid. +- direction := _backwards_ The direction of this query. The direction determines the order of the returned result array, but also determines which end of the query interval is the start point for the search. +- unit := _@minute@_ One of the values @minute@, @hour@, @day@ or @month@, specifying the unit of aggregation in the returned results. + +h5. Options + +- Content-Type := not applicable +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := yes ("basic":#basic-authentication or "token":#token-authentication) + +h5. Returns + +In each case a successful result is a "paginated response":#pagination with an array containing the items that match the query (and it may be empty). + +Stats records contain a hierarchy of elements relating to messages, connections and other resources consumed in an interval. Any single record may contain a subset of the elements, omitting empty sections. + +"See a complete example of a statistics response":/general/statistics. + +h2(#utilities). Utilities + +h3(#time). Get the service time + +h6. GET rest.ably.io/time + +This returns the service time in milliseconds since the epoch. This may be used by clients that do not have local access to a sufficiently accurate time source when generating an Ably "@TokenRequest@":/rest-api/token-request-spec. (Ably "@TokenRequests@":/rest-api/token-request-spec include a timestamp and have a limited validity period to help defend against replay attacks.) + +The result is a JSON-encoded array of length 1 containing the time result as a number. + +bc[sh]. curl http://rest.ably.io/time + +h5. Parameters + +None + +h5. Options + +- Content-Type := not applicable +- Accept := @application/json@ by default, or @application/x-msgpack@, @text/html@ +- Auth required := no + +h5. Returns + +bc[json]. [ {{MS_SINCE_EPOCH}} ] diff --git a/content/rest-api/versions/v1.1/token-request-spec.textile b/content/rest-api/versions/v1.1/token-request-spec.textile new file mode 100644 index 0000000000..581829bc61 --- /dev/null +++ b/content/rest-api/versions/v1.1/token-request-spec.textile @@ -0,0 +1,162 @@ +--- +title: REST API Token Request Spec +index: 10 +jump_to: + Help with: + - Overview#title + - Examples +--- + +The "Ably REST and Realtime client libraries":https://www.ably.io/download/ aim to make things as simple as possible so it is not necessary to understand all of the details of how to interact with the service and issue tokens for clients. If you wish to issue Ably-compatible tokens or "Ably TokenRequests":/realtime/types#token-request, we recommend you start with the "client library authentication documentation":/rest/authentication. + +However, if you are using the "REST API token endpoint directly":/rest-api/#request-token, or if you are creating Ably @TokenRequests@ without the use of our client libraries, then the following specification will give you an in-depth understanding of how getting Ably Tokens works. + +h3(#api-key-format). API key format + +API keys are issued and managed from "within your account dashboard":https://support.ably.io/support/solutions/articles/3000030502-setting-up-and-managing-api-keys. The API key string available in your dashboard is structured as a triple @::@, where: + +- app ID := (public) identifier for the application +- key ID := (public) identifier for the key in question: this uniquely identifies the key and is a system-assigned, URL-safe, identifier +- key value := (private) key "secret" string, system-generated, uniquely associated with this key + +h3(#tokenrequest-format). Ably TokenRequest format + +A request for an Ably Token is made against the "@requestToken@":/rest-api#request-token endpoint, with a JSON Ably @TokenRequest@ in the request body. The Ably @TokenRequest@ comprises: + +* the @keyName@ comprising of the app ID and key ID such as @{{API_KEY_NAME}}@ +* a capability (i.e. a set of channel names/namespaces and, for each, a set of "operations":#capability-operations) which should be a subset of the set of capability associated with the key specified in @keyName@ +* optionally, a @clientId@ thus identifying clients using this token and preventing them from identifying themselves with any other @clientId@ +* optionally, an expiry time or TTL, will default to 1 hour if not specified +* a timestamp to ensure the Ably @TokenRequest@ is still valid +* a unique nonce string, randomly-generated by the client, to ensure the Ably @TokenRequest@ cannot be reused + +A signed Ably @TokenRequest@ also contains: + +* a signature, generated as an HMAC of each of the above components, using the key secret value. + +A signed Ably @TokenRequest@ can be used to request an Ably Token from Ably without an authenticated connection. The signature generated with the key secret confirms the authenticity of the token and can thus be "trusted" by Ably. As signed Ably Token Requests can be issued without a request to Ably, a server with a valid API key can issue Ably @TokenRequests@ directly to clients, and clients can in turn generate an Ably Token by sending the Ably @TokenRequest@ to Ably. + +The receiving Ably server verifies the signature if present, the timestamp (which must be within 2 minutes of the current time), verifies that the nonce/timestamp combination has not been used previously, verifies that the requested validity period is permitted, and verifies that the requested capabilities are permitted for that token based on the key capabilities. + +The server may choose to subset the capabilities based on the capabilities of the key. + +The server replies with an access token, which is essentially a signed version of the resolved set of capabilities, plus other metadata associated with the token (such as expiry time). + +This access token can then be used for subsequent REST requests or Realtime connections. If a @clientId@ was included in the request, then the token is associated with that @clientId@, and may be used to identify that client in operations that require identification (e.g. joining a channel that requires identification, or publishing a message with a verified @clientId@). + +h3(#parameters). Parameter canonicalisation + +The parameters of the Ably @TokenRequest@ are normalized/canonicalized as follows: + +- keyName := no action required +- ttl := the decimal integer representation, without leading zeros, of the requested life of the Ably Token in milliseconds, if none is specified a default of 1 hour is used +- capability := this is a canonicalized representation of the channel paths and associated "operations":#capability-operations in the capability. It is a JSON stringified value of a Javascript object of the form: +
{
+  "channel1": ["operation1a", "operation1b", "operation1c", ...],
+  "channel2": ["operation2a", "operation2b", "operation2c", ...],
+  ...
+}
+with the following constraints: +
    +
  • all white-space is removed;
  • +
  • channels are listed in forward lexicographic order;
  • +
  • operations are listed in forward lexicographic order;
  • +
  • there is no trailing comma on any list of array or object elements;
  • +
  • all strings are quoted and escaped as per the JSON standard;
  • +
  • for channel paths, the wildcard character * has special meaning. When the channel path is exactly "*", then all channels are matched. If however, a channel path ends with *, then the path before the * is used to match all channels in that namespace. For example, the channel path "user:*" matches all channels in the user namespace such as "user:john" and "user:matt". "Find out more about channel namespaces":/realtime/channels#channel-namespaces;
  • +
  • for operations, the wildcard character * has special meaning. When the operation is exactly "*", then "all operations":#capability-operations are supported. Otherwise, a "permitted operation":#capability-operations string must be provided +
+- clientId := the canonical form is the unquoted and un-escaped string. In the case that no clientId is included in the request, the empty string is used. +- timestamp := the decimal integer representation, without leading zeros, of the time of the of the request in milliseconds since the epoch. +- nonce := an unquoted, un-escaped random string of at least 16 characters. + +h3(#capability-operations). Capability operations + +<%= partial partial_version('core-features/_authentication_capabilities') %> + +h3(#hmac). HMAC calculation + +First the canonicalized request text, constructed as follows: + +* start with the empty string +* for each of the following fields in order: @keyName@, @ttl@, @capabilities@, @clientId@, @timestamp@, @nonce@, even when empty +** append the canonicalized string value for that field +** append a newline (0xa) character. +Note that a newline character is added for each field, including any empty client ID string, and the last (nonce) field. + +The resulting string must then the UTF8-encoded and then HMAC value is computed with "hmac-sha-256":http://tools.ietf.org/html/rfc4868 using the key secret value. + +The HMAC value is then "base-64 encoded":https://tools.ietf.org/html/rfc4648. + +h3(#request-body). Request body format + +In the case of a signed Ably @TokenRequest@, the request body is the JSON stringified representation of the object with the form: + +bc[json]. { + keyName: ":", + ttl: , + capability: "", + clientId: "", + timestamp: , + nonce: "", + mac: "" +} + +An unsigned Ably @TokenRequest@ is identical except that the mac property is omitted. Note that "Basic authentication":#basic-authentication must be used in order to request an Ably Token with an unsigned Ably @TokenRequest@. + +h3(#response-body). Response body format + +If successful, the authorization request returns the JSON stringified representation of an object containing the Ably Token: + +bc[json]. { + token: "", + issued: , + expires: , + capability: "", + clientId: "" +} + +h2(#examples). Example Ably TokenRequests + +h3(#unsigned-tokenrequest). Unsigned Ably TokenRequest example + +bc[sh]. curl -X POST "https://rest.ably.io/keys/{{API_KEY_NAME}}/requestToken" \ + --user "{{API_KEY}}" \ + --header "Content-Type: application/json" \ + --data '{ + "keyName": "{{API_KEY_NAME}}", + "ttl": "3600000", + "capability": + "{\"private\":[\"subscribe\",\"publish\",\"presence\"],\"*\":[\"subscribe\"]}", + "clientId": "unique_identifier", + "timestamp": {{MS_SINCE_EPOCH}}, + "nonce": "95e543b88299f6bae83df9b12fbd1ecd" +}' + +Responds with TokenDetails: + +bc[json]. { + "token": "{{APP_ID}}.HHZNjgqmC-ACW....truncated", + "keyName": "{{API_KEY_NAME}}", + "issued": 1449745478956, + "expires": 1449749078956, + "capability": + "{\"*\":[\"subscribe\"],\"private\":[\"presence\",\"publish\",\"subscribe\"]}", + "clientId": "unique_identifier" +} + +h3(#signed-tokenrequest). Signed Ably TokenRequest example + +bc[sh]. curl -X POST "https://rest.ably.io/keys/{{API_KEY_NAME}}/requestToken" \ + -H "Content-Type: application/json" \ + --data '{{SIGNED_TOKEN_REQUEST_EXAMPLE}}' + +Responds with TokenDetails: + +bc[json]. { + "token": "{{APP_ID}}.DTSukCRj1lis1sJltr...rhLRBcZgmXLf1FP8wKGrPYkkIs", + "keyName": "{{API_KEY_NAME}}", + "issued": 1449745797497, + "expires": 1449749397497, + "capability": "{\"*\":[\"*\"]}" +} diff --git a/content/rest/usage.textile b/content/rest/usage.textile index 96733c786c..7b9ebe77b2 100644 --- a/content/rest/usage.textile +++ b/content/rest/usage.textile @@ -126,13 +126,13 @@ blang[java]. For Java applications: ```[java] - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' ``` For Android apps: ```[java] - compile 'io.ably:ably-android:1.0.0' + compile 'io.ably:ably-android:1.2.0' ``` In the above example a specific version of the library is referenced, however we recommend you check which is the latest stable version and always use that. Follow links to get the latest stable release for "Java":https://bintray.com/ably-io/ably/ably-java and "Android":https://bintray.com/ably-io/ably/ably-android. diff --git a/content/rest/versions/v1.1/authentication.textile b/content/rest/versions/v1.1/authentication.textile new file mode 100644 index 0000000000..c9bdbe60bc --- /dev/null +++ b/content/rest/versions/v1.1/authentication.textile @@ -0,0 +1,665 @@ +--- +title: Authentication +section: rest +index: 48 +languages: + - javascript + - nodejs + - php + - python + - ruby + - java + - swift + - objc + - csharp + - go +api_separator: +jump_to: + Help with: + - Understanding Auth + - Basic Authentication + - Token Authentication + - Identified clients + Channel API properties: + - clientId#client-id + Channel API methods: + - authorize + - createTokenRequest#create-token-request + - requestToken#request-token + Types: + - Related Types#related-types +--- + +Ably clients can authenticate with Ably in one of two ways. They can use "Basic Authentication":/core-features/authentication#basic-authentication or "Token Authentication":/core-features/authentication#token-authentication. Basic Authentication makes use of the customer's "API key":https://support.ably.io/solution/articles/3000030054-what-is-an-app-api-key) to connect with Ably. Token Authentication requires a server to provide an "Ably Token":/core-features/authentication#tokens, an Ably "@TokenRequest@":#request-token, an "Ably JWT (JSON Web Token)":/core-features/authentication#ably-jwt, or an "External JWT":https://jwt.io with an embedded Ably-compatible token to the client. "Token Authentication":/core-features/authentication#token-authentication, in most cases, is the recommended strategy due to it providing robust access control and stringent security measures. + +h2(#understanding-auth). Understanding the Ably authentication schemes + +This page of documentation is intended to describe the REST Authentication API and is not intended to explain in depth how Ably's authentication works. If you are new to Ably and/or the authentication schemes, we strongly recommend that you get acquainted with the following first: + +* "Getting started with Ably's authentication":/core-features/authentication/#getting-started +* "Selecting the right authentication scheme":/core-features/authentication/#selecting-auth +* "Basic Authentication explained":/core-features/authentication/#basic-authentication +* "Token Authentication explained":/core-features/authentication/#token-authentication + +h3. Tutorials + +If you're the kind of person who prefers to dive into code, we have "client-server authentication tutorials":/tutorials. + +h2(#basic-authentication). Basic Authentication + +*Basic Authentication* uses one of the "API keys":https://support.ably.io/solution/articles/3000030054-what-is-an-app-api-key "configured via the application dashboard":https://support.ably.io/support/solutions/articles/3000030502-setting-up-and-managing-api-keys as the authentication token. Basic Authentication is the simplest method to use but has some important limitations described in detail in the "Basic Authentication documentation":/core-features/authentication/#basic-authentication. + +Here is a straightforward example of using Basic Authentication when instancing an Ably REST client library. + +```[jsall](code-editor:realtime/connection) + var rest = new Ably.Rest({ key: '{{API_KEY}}' }); +``` + +```[ruby] + rest = Ably::Rest.new(key: '{{API_KEY}}') +``` + +```[python] + rest = AblyRest(key='{{API_KEY}}') +``` + +```[php] + $rest = new Ably\AblyRest(array('key' => '{{API_KEY}}')); +``` + +```[java] + ClientOptions options = new ClientOptions(); + options.key = "{{API_KEY}}"; + AblyRest rest = new AblyRest(options); +``` + +```[csharp] + AblyRest rest = new AblyRest("{{API_KEY}}"); +``` + +```[swift] + let rest = ARTRest(key: "{{API_KEY}}") +``` + +```[objc] + ARTRest *rest = [[ARTRest alloc] initWithKey:@"{{API_KEY}}"]; +``` + +```[go] + client, err := ably.NewRestClient(ably.ClientOptions("{{API_KEY}}")) +``` + +h2(#token-authentication). Token Authentication + +p(tip). Look at the "general authentication documentation":/core-features/authentication#token-authentication for more in-depth information on Token Authentication. + +*Token Authentication* uses an Ably-compatible token to authenticate with Ably. This can be an "Ably Token":/core-features/authentication#ably-tokens obtained via the REST API "@requestToken@":#request-token"@RequestToken@":#request-token"@request_token@":#request-token endpoint, an "Ably JWT":/core-features/authentication#ably-jwt signed by your API key, or an "External JWT":https://jwt.io object "with an embedded Ably-compatible token":/core-features/authentication#jwt-embed-process. The best way to obtain these tokens is through the use of the "Auth object":/core-features/authentication#auth-object. + +Tokens are authentication credentials that are short-lived, and therefore they may more readily be distributed to clients where there is a risk of compromise. Tokens may also be issued with a particular scope - such as a limited set of "access rights or capabilities":/core-features/authentication#capabilities-explained or being limited to use by a specific "@clientId@@ClientId@ identity":#identified-clients - and therefore token-based authentication provides the flexibility to implement access and identity control policies in the application. See the "Token Authentication documentation":/core-features/authentication#token-authentication for more details. + +Below is a rather contrived yet straightforward example that instances a Realtime library using "Token Authentication":/core-features/authentication#token-authentication with a means to reissue tokens as required. Typically, in a browser environment, the "@authUrl@":#auth-options"@AuthUrl@":#auth-options provided would be a relative URL to a local endpoint that is used to issue tokens to trusted clients. Client requests can, for example, be trusted based on session cookies. For non-browser clients, an authentication callback is preferred thus relying on your application to communicate securely with your own servers to obtain a token. + +```[jsall](code-editor:realtime/auth-token-callback) + var rest = new Ably.Rest({ authUrl: 'https://my.website/auth' }); +``` + +```[ruby] + rest = Ably::Rest.new(auth_url: 'https://my.website/auth') +``` + +```[python] + rest = AblyRest(auth_url='https://my.website/auth') +``` + +```[php] + $rest = new Ably\AblyRest(array('authUrl' => 'https://my.website/auth')); +``` + +```[java] + ClientOptions options = new ClientOptions(); + options.authUrl = "https://my.website/auth"; + AblyRest rest = new AblyRest(options); +``` + +```[csharp] + AblyRest rest = new AblyRest(new ClientOptions { AuthUrl = new Uri("https://my.website/auth") }); +``` + +```[objc] + ARTClientOptions *options = [[ARTClientOptions alloc] init]; + options.authUrl = [NSURL URLWithString:@"https://my.website/auth"]; + ARTRest *rest = [[ARTRest alloc] initWithOptions:options]; +``` + +```[swift] + let options = ARTClientOptions() + options.authUrl = NSURL(string: "https://my.website/auth") + let rest = ARTRest(options: options) +``` + +```[go] + authOpts := &ably.AuthOptions{ + AuthURL: "http://my.website/auth", + } + clientOpts := &ably.ClientOptions{ + AuthOptions: authOpts, + } + client, err := ably.NewRestClient(clientOpts) +``` + +Ably supports both "Ably Tokens":/core-features/authentication#ably-tokens and "Ably JWTs":/core-features/authentication#ably-jwt for authentication. + +h2(#selecting-auth). Selecting an authentication mechanism + +<%= partial partial_version('core-features/_authentication_comparison') %> + +h2(#identified-clients). 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 (i.e. a "@client_id@":#token-details"@ClientId@":#token-details"@clientId@":#token-details), then they are considered to be an *identified client* and for all operations they perform with the Ably service, their "@client_id@":#token-details"@ClientId@":#token-details"@clientId@":#token-details field will be automatically populated and can be trusted by other clients. + +We encourage customers to always issue 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@":#token-details"@ClientId@":#token-details"@clientId@":#token-details that may be used by any of its clients, all other clients can rely on the validity of the "@client_id@":#token-details"@ClientId@":#token-details"@clientId@":#token-details in published messages and of members present in presence channels. + +The following example demonstrates how to issue an Ably "@TokenRequest@":#request-token with an explicit "@client_id@":#token-details"@ClientId@":#token-details"@clientId@":#token-details that, when used by a client, will then be considered an *identified client*. + +minimize. See example + ```[jsall](code-editor:realtime/auth-client-id) + var rest = new Ably.Rest({ key: '{{API_KEY}}' }); + rest.auth.createTokenRequest({ clientId: 'Bob' }, function(err, tokenRequest) { + /* ... issue the TokenRequest to a client ... */ + }) + ``` + + ```[ruby] + rest = Ably::Rest.new(key: '{{API_KEY}}') + token_request = rest.auth.create_token_request(client_id: 'Bob') + # ... issue the TokenRequest to a client ... + ``` + + ```[python] + rest = AblyRest(key='{{API_KEY}}') + token_request = rest.auth.create_token_request(client_id='Bob') + # ... issue the TokenRequest to a client ... + ``` + + ```[php] + $rest = new Ably\AblyRest(array('key' => '{{API_KEY}}')); + $tokenRequest = $rest->auth->createTokenRequest(array('clientId' => 'Bob')); + # ... issue the TokenRequest to a client ... + ``` + + ```[java] + ClientOptions options = new ClientOptions(); + options.key = "{{API_KEY}}"; + AblyRest rest = new AblyRest(options); + TokenParams tokenParams = new TokenParams(); + tokenParams.clientId = "Bob"; + TokenRequest tokenRequest; + tokenRequest = rest.auth.createTokenRequest(tokenParams, null); + /* ... issue the TokenRequest to a client ... */ + ``` + + ```[csharp] + AblyRest rest = new AblyRest(new ClientOptions {Key = "{{API_KEY}}"}); + TokenParams tokenParams = new TokenParams {ClientId = "Bob"}; + string tokenRequest = await rest.Auth.CreateTokenRequestAsync(tokenParams); + // ... issue the TokenRequest to a client ... + ``` + + ```[objc] + ARTRest *rest = [[ARTRest alloc] initWithKey:@"{{API_KEY}}"]; + ARTTokenParams *tokenParams = [[ARTTokenParams alloc] initWithClientId:@"Bob"]; + [rest.auth createTokenRequest:tokenParams options:nil + callback:^(ARTTokenRequest *tokenRequest, NSError *error) { + // ... issue the TokenRequest to a client ... + }]; + ``` + + ```[swift] + let rest = ARTRest(key: "{{API_KEY}}") + let tokenParams = ARTTokenParams(clientId: "Bob") + rest.auth.createTokenRequest(tokenParams, options: nil) { tokenRequest, error in + // ... issue the TokenRequest to a client ... + } + ``` + + ```[go] + client, err := ably.NewRealtimeClient(ably.ClientOptions{("{{API_KEY}}")) + + params := &ably.TokenParams{ + ClientID: "Bob", + } + tokenRequest, err := client.Auth.CreateTokenRequest(params, nil) + ``` +h1. Auth API Reference + +inline-toc. + Methods: + - authorize + - createTokenRequest#create-token-request + - requestToken#request-token + Properties: + - clientId#client-id + Related types: + - AuthOptions#auth-options + - TokenDetails#token-details + - TokenRequest#token-request + - TokenParams#token-params + +The Auth object is available as the "@auth@ field":/rest/usage#auth"@auth@ property":/rest/usage#auth"@Auth@ property":/rest/usage#auth"@auth@ attribute":/rest/usage#auth of an "Ably REST client instance":/rest/usage#constructor. + +h2(#properties). + default: Auth Properties + php: Ably\Auth Properties + java: io.ably.lib.rest.Auth Members + ruby: Ably::Auth Attributes + python: Auth Attributes + objc,swift: ARTAuth Properties + +The @ART@@Auth@ object exposes the following public propertiesattributesmembers: + +h6(#client-id). + default: clientId + ruby: client_id + python: client_id + csharp: ClientId + go: ClientID + +The client ID string, if any, configured for this client connection. See "identified clients":#identified-clients for more information on trusted client identifiers. + +h2(#methods). + default: Auth Methods + java: io.ably.lib.rest.Auth Methods + ruby: Ably::Auth Methods + php: Ably\Auth Methods + objc,swift: ARTAuth Methods + +h6(#authorize). + default: authorize + csharp,go: Authorize + + + +bq(definition). + default: authorize("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions, callback("ErrorInfo":/realtime/types#error-info err, "TokenDetails":#token-details tokenDetails)) + ruby: "TokenDetails":#token-details authorize("TokenParams":#token-params token_params, "AuthOptions":#auth-options auth_options) + python: "TokenDetails":#token-details authorize(token_params="TokenParams":#token-params, auth_options="AuthOptions":#auth-options) + php: "TokenDetails":#token-details authorize("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions) + java: "TokenDetails":#token-details authorize("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions) + csharp: Task AuthorizeAsync("TokenParams":#token-params tokenParams = null, "AuthOptions":#auth-options options = null); + go: "TokenDetails":#token-details Authorize("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions) + swift,objc: authorize(tokenParams: "ARTTokenParams":#token-params?, authOptions: "ARTAuthOptions":#auth-options?, callback: ("ARTTokenDetails":#token-details?, NSError?) -> Void) + +Instructs the library to get a new token immediately using the specified @token_params@ and @auth_options@@tokenParams@ and @authOptions@@TokenParams@ and @AuthOptions@ (or if none specified, the client library defaults). Also stores any @token_params@ and @auth_options@@TokenParams@ and @AuthOptions@@tokenParams@ and @authOptions@ passed in as the new defaults, to be used for all subsequent implicit or explicit token requests. + +Any @token_params@ and @auth_options@@TokenParams@ and @AuthOptions@@tokenParams@ and @authOptions@ objects passed in will entirely replace (as opposed to being merged with) the currently saved @token_params@ and @auth_options@@tokenParams@ and @authOptions@. + +h4. Parameters + +- token_paramsTokenParamstokenParams := an optional object containing the "Ably Token parameters":#token-paramsan optional "@TokenParams@":#token-params object containing the token parametersan optional Dict containing the "Ably Token parameters":#token-paramsan optional set of key value pairs containing the "Ably Token parameters":#token-paramsan optional set of key value pairs in an associative array containing the "Ably Token parameters":#token-params for the authorization request
__Type: "@TokenParams@":#token-params__ + +- auth_optionsAuthOptionsauthOptions := an optional object containing the "authentication options":#auth-optionsan optional "@AuthOptions@":#auth-options object containing the authentication optionsan optional Dict containing the "authentication options":#auth-optionsan optional set of key value pairs containing the "authentication options":#auth-optionsan optional set of key value pairs in an associative array containing the "authentication options":#auth-options for the authorization request
__Type: "@AuthOptions@":#auth-options__ + +-
callback
:= is a function of the form: @function(err, tokenDetails)@ +-
callback
:= called with a "@ARTTokenDetails@":#token-details object or an error + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @tokenDetails@ contains a "@TokenDetails@":#token-details object containing the details of the new or existing "Ably Token":/core-features/authentication#ably-tokens along with the @token@ string. + + On failure to obtain an token, @err@ contains an "@ErrorInfo@":#error-info@NSError@ object with an error response as defined in the "Ably REST API":/rest-api#common documentation. + +blang[java,ruby,php,python,go]. + h4. Returns + + On success, a "@TokenDetails@":#token-details object containing the details of the new or existing token along with the @token@ string is returned. + + Failure to obtain an token will raise an "@AblyException@":#ably-exception. + +blang[csharp]. + h4. Returns + + The method is asynchronous and returns a @Task@ which needs to be awaited. + + On success, a "@TokenDetails@":#token-details object containing the details of the new or existing token along with the @token@ string is returned. + + Failure to obtain a token will raise an "@AblyException@":#ably-exception. + +h4. Example + +bc[jsall]. client.auth.authorize({ clientId: 'bob' }, function(err, tokenDetails) { + if(err) { + console.log('An error occurred; err = ' + err.message); + } else { + console.log('Success; token = ' + tokenDetails.token); + } +}); + +bc[java]. try { + TokenParams tokenParams = new TokenParams(); + tokenParams.clientId = "bob"; + TokenDetails tokenDetails = client.auth.authorize(tokenParams, null); + System.out.println("Success; token = " + tokenDetails.token); +} catch(AblyException e) { + System.out.println("An error occurred; err = " + e.getMessage()); +} + +bc[csharp]. try +{ + TokenParams tokenParams = new TokenParams { ClientId = "bob" }; + TokenDetails tokenDetails = await client.Auth.AuthorizeAsync(tokenParams); + Console.WriteLine("Success; token = " + tokenDetails.Token); +} +catch (AblyException e) +{ + Console.WriteLine("An error occurred; err = " + e.Message); +} + +bc[ruby]. token_details = client.auth.authorize(client_id: 'bob') +puts "Success; token = #{token_details.token}" + +bc[python]. token_details = client.auth.authorize(token_params={'client_id': 'bob'}) +print("Success; token = " + str(token_details.token)) + +bc[php]. $tokenDetails = $client->auth->authorize(array('clientId' => 'bob')); +echo("Success; token = " . $tokenDetails->token); + +bc[objc]. ARTTokenParams *tokenParams = [[ARTTokenParams alloc] initWithClientId:@"Bob"]; +[client.auth authorize:tokenParams options:nil callback:^(ARTTokenDetails *tokenDetails, NSError *error) { + if (error) { + NSLog(@"An error occurred; err = %@", error); + } else { + NSLog(@"Success; token = %@", tokenDetails.token); + } +}]; + +bc[swift]. let tokenParams = ARTTokenParams(clientId: "Bob") +client.auth.authorize(tokenParams, options: nil) { tokenDetails, error in + guard let tokenDetails = tokenDetails else { + print("An error occurred; err = \(error!)") + return + } + print("Success; token = \(tokenDetails.token)") +} + +bc[go]. tokenParams := &ably.TokenParams{ + ClientID: "Bob", +} +token, err := client.Auth.Authorize(tokenParams, &ably.AuthOptions{}) +if err != nil { + fmt.Println(err) +} +fmt.Println(token) + +h6(#create-token-request). + default: createTokenRequest + csharp,go: CreateTokenRequest + ruby: create_token_request + python: create_token_request + +bq(definition). + default: createTokenRequest("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions, callback("ErrorInfo":/realtime/types#error-info err, "TokenRequest":#token-request tokenRequest)) + ruby: "TokenRequest":#token-request create_token_request("TokenParams":#token-params token_params, "AuthOptions":#auth-options auth_options) + python: "TokenRequest":#token-request create_token_request(token_params="TokenParams":#token-params, key_name=String, key_secret=String) + php: "TokenRequest":#token-request createTokenRequest("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions) + java: "TokenRequest":#token-request createTokenRequest("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions) + go: "TokenRequest":#token-request CreateTokenRequest("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions) + csharp: Task CreateTokenRequestAsync(TokenParams tokenParams = null, AuthOptions authOptions = null) + objc,swift: createTokenRequest(tokenParams: "ARTTokenParams":#token-params?, options: "ARTAuthOptions":#auth-options?, callback: ("ARTTokenRequest":#token-request?, NSError?) -> Void) + +Creates and signs an Ably "@TokenRequest@":#request-token based on the specified @token_params@ and @auth_options@@TokenParams@ and @AuthOptions@@tokenParams@ and @authOptions@. Note this can only be used when the "API @key@":https://support.ably.io/solution/articles/3000030054-what-is-an-app-api-key value is available locally, due to it being required to sign the Ably "@TokenRequest@":#request-token. Otherwise, Ably "@TokenRequests@":#request-token must be obtained from the key owner. Use this to generate Ably "@TokenRequests@":#request-token in order to implement an "Ably Token":/core-features/authentication#ably-tokens request callback for use by other clients. + +Both @auth_options@ and @token_params@@AuthOptions@ and @TokenParams@@authOptions@ and @tokenParams@ are optional. When omitted or @null@@Null@@None@@nil@, the default Ably-compatible token parameters and authentication options for the client library are used, as specified in the @ClientOptions@ when the client library was instanced, or later updated with an explicit "@authorize@@Authorize@":#authorize request. Values passed in will be used instead of (rather than being merged with) the default values. + +To understand why an Ably "@TokenRequest@":#request-token may be issued to clients in favor of an "Ably Token":/core-features/authentication#ably-tokens, see "Token Authentication explained":/core-features/authentication/#token-authentication. + +h4. Parameters + +- token_paramsTokenParamstokenParams := an optional object containing the "token parameters":#token-paramsan optional "@TokenParams@":#token-params object containing the token parametersan optional Dict containing the "token parameters":#token-paramsan optional set of key value pairs containing the "token parameters":#token-paramsan optional set of key value pairs in an associative array containing the "token parameters":#token-params for the Ably "@TokenRequest@":#request-token
__Type: "@TokenParams@":#token-params__ + +- auth_options[auth options]AuthOptionsauthOptions := an optional object containing the "authentication options":#auth-optionsan optional "@TokenParams@":#auth-options object containing the authentication optionsvarious keyword arguments with the "authentication options":#auth-optionsan optional set of key value pairs containing the "authentication options":#auth-optionsan optional set of key value pairs in an associative array containing the "authentication options":#auth-optionsan optional "@ARTTokenParams@":#token-params containing the "authentication options":#auth-options for the "Ably Token":/core-features/authentication#ably-tokens Request
__Type: "@AuthOptions@":#auth-options__ + +-
callback
:= is a function of the form: @function(err, tokenRequest)@ +-
callback
:= called with a "@ARTTokenRequest@":#token-request object or an error + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @tokenRequest@ contains a "@TokenRequest@":#token-request JSON object. + + On failure to issue a "@TokenRequest@":#token-request, @err@ contains an "@ErrorInfo@":#error-info object with an error response as defined in the "Ably REST API":/rest-api#common documentation. + +blang[java,ruby,php,python,go]. + h4. Returns + + On success, a "@TokenRequest@":#token-request object is returned. + + Failure to issue a "@TokenRequest@":#token-request will raise an "@AblyException@":#ably-exception. + +blang[csharp]. + h4. Returns + + The method is asynchronous and returns a @Task@ which needs to be awaited. + + On success, a "@TokenRequest@":#token-request object is returned. + + Failure to issue a "@TokenRequest@":#token-request will raise an "@AblyException@":#ably-exception. + +h4. Example + +bc[jsall]. client.auth.createTokenRequest({ clientId: 'bob' }, function(err, tokenRequest) { + if(err) { + console.log('An error occurred; err = ' + err.message); + } else { + console.log('Success; token request = ' + tokenRequest); + } +}); + +bc[java]. try { + TokenParams tokenParams = new TokenParams(); + tokenParams.clientId = "bob"; + TokenRequest tokenRequest = client.auth.createTokenRequest(tokenParams, null); + System.out.println("Success; token request issued"); +} catch(AblyException e) { + System.out.println("An error occurred; err = " + e.getMessage()); +} + +bc[csharp]. try +{ + TokenParams tokenParams = new TokenParams { ClientId = "bob" }; + var tokenRequest = await client.Auth.CreateTokenRequestAsync(tokenParams); + Console.WriteLine("Success; token request issued"); +} +catch (AblyException e) +{ + Console.WriteLine("An error occurred; err = " + e.Message); +} + +bc[ruby]. token_request = client.auth.create_token_request(client_id: 'bob') +puts "Success; token request = #{token_request}" + +bc[python]. token_request = client.auth.create_token_request(token_params={'client_id': 'bob'}) +print("Success; token request = ' + str(token_request) + +bc[php]. $tokenRequest = $client->auth->createTokenRequest(array('clientId' => 'bob')) +echo("Success; token request = " . $tokenRequest); + +bc[objc]. ARTTokenParams *tokenParams = [[ARTTokenParams alloc] initWithClientId:@"Bob"]; +[client.auth createTokenRequest:tokenParams options:nil callback:^(ARTTokenRequest *tokenRequest, NSError *error) { + if (error) { + NSLog(@"An error occurred; err = %@", error); + } else { + NSLog(@"Success; token request = %@", tokenRequest); + } +}]; + +bc[swift]. let tokenParams = ARTTokenParams(clientId: "Bob") +client.auth.createTokenRequest(tokenParams, options: nil) { tokenRequest, error in + guard let tokenRequest = tokenRequest else { + print("An error occurred; err = \(error!)") + return + } + print("Success; token request = \(tokenRequest)") +} + +bc[go]. tokenParams := &ably.TokenParams{ + ClientID: "Bob", +} +tokenRequest, err := client.Auth.CreateTokenRequest(tokenParams, &ably.AuthOptions{}) +if err != nil { + fmt.Println(err) +} +fmt.Println(tokenRequest) + +h6(#request-token). + default: requestToken + ruby: request_token + python: request_token + csharp: RequestToken + go: RequestToken + +bq(definition). + default: requestToken("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions, callback("ErrorInfo":/realtime/types#error-info err, "TokenDetails":#token-details tokenDetails)) + ruby: "TokenDetails":#token-details request_token("TokenParams":#token-params token_params, "AuthOptions":#auth-options auth_options) + python: "TokenDetails":#token-details request_token(token_params="TokenParams":#token-params, key_name=String, key_secret=None, auth_callback=Lambda, auth_url=String, auth_method=String, auth_headers=Dict, auth_params=Dict, query_time=Boolean) + php: "TokenDetails":#token-details requestToken("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions) + csharp: Task RequestTokenAsync(TokenParams tokenParams = null, AuthOptions options = null) + java: "TokenDetails":#token-details requestToken("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions) + objc,swift: requestToken(tokenParams: "ARTTokenParams":#token-params?, withOptions: "ARTAuthOptions":#auth-options?, callback: ("ARTTokenDetails":#token-details?, NSError?) -> Void) + go: "TokenDetails":#token-details RequestToken("TokenParams":#token-params tokenParams, "AuthOptions":#auth-options authOptions) + +Calls the "@requestToken@@RequestToken@ REST API endpoint":/rest-api#request-token to obtain an "Ably Token":/core-features/authentication#ably-tokens according to the specified @token_params@ and @auth_options@@tokenParams@ and @authOptions@. + +Both @auth_options@ and @token_params@@authOptions@ and @tokenParams@ are optional. When omitted or @null@@Null@@None@@nil@, the default token parameters and authentication options for the client library are used, as specified in the @ClientOptions@ when the client library was instanced, or later updated with an explicit "@authorize@@Authorize@":#authorize request. Values passed in will be used instead of (rather than being merged with) the default values. + +To understand why an Ably "@TokenRequest@":#request-token may be issued to clients in favor of an "Ably Token":/core-features/authentication#ably-tokens, see "Token Authentication explained":/core-features/authentication/#token-authentication. + +h4. Parameters + +- token_paramstokenParams := an optional object containing the "token parameters":#token-paramsan optional Dict containing the "token parameters":#token-paramsan optional "@TokenParams@":#token-params object containing the token parametersan optional set of key value pairs containing the "token parameters":#token-paramsan optional set of key value pairs in an associative array containing the "token parameters":#token-params for the requested "Ably Token":/core-features/authentication#ably-tokens
__Type: "@TokenParams@":#token-params__ + +- [auth options]auth_optionsauthOptions := an optional object containing the "authentication options":#auth-optionsvarious keyword arguments with the "authentication options":#auth-optionsan optional "@TokenParams@":#auth-options object containing the authentication optionsan optional set of key value pairs containing the "authentication options":#auth-optionsan optional set of key value pairs in an associative array containing the "authentication options":#auth-options for the requested "Ably Token":/core-features/authentication#ably-tokens
__Type: "@AuthOptions@":#auth-options__ + +-
callback
:= is a function of the form: @function(err, tokenDetails)@ +-
callback
:= called with a "@ARTTokenDetails@":#token-details object or an error + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @tokenDetails@ contains a "@TokenDetails@":#token-details object containing the details of the new "Ably Token":/core-features/authentication#ably-tokens along with the @token@ string. + + On failure to obtain an "Ably Token":/core-features/authentication#ably-tokens, @err@ contains an "@ErrorInfo@":#error-info@NSError@ object with an error response as defined in the "Ably REST API":/rest-api#common documentation. + +blang[java,ruby,php,python,go]. + h4. Returns + + On success, a "@TokenDetails@":#token-details object containing the details of the new "Ably Token":/core-features/authentication#ably-tokens along with the @token@ string is returned. + + Failure to obtain an "Ably Token":/core-features/authentication#ably-tokens will raise an "@AblyException@":#ably-exception. + +blang[csharp]. + h4. Returns + + The method is asynchronous and returns a @Task@ which needs to be awaited. + + On success, a "@TokenDetails@":#token-details object containing the details of the new "Ably Token":/core-features/authentication#ably-tokens along with the @token@ string is returned. + + Failure to obtain an "Ably Token":/core-features/authentication#ably-tokens will raise an "@AblyException@":#ably-exception. + +h4. Example + +bc[jsall]. client.auth.requestToken({ clientId: 'bob'}, function(err, tokenDetails){ + if(err) { + console.log('An error occurred; err = ' + err.message); + } else { + console.log('Success; token = ' + tokenDetails.token); + } +}); + +bc[ruby]. token_details = client.auth.request_token(client_id: 'bob') +puts "Success; token = #{token_details.token}" + +bc[python]. token_details = client.auth.request_token(token_params={'client_id': 'bob'}) +print("Success; token = " + str(token_details.token)) + +bc[php]. $tokenDetails = $client->auth->requestToken(array('clientId' => 'bob')) +echo("Success; token = " . $tokenDetails->token); + +bc[java]. try { + TokenParams tokenParams = new TokenParams(); + tokenParams.clientId = "bob"; + TokenDetails tokenDetails = client.auth.requestToken(tokenParams, null); + System.out.println("Success; token = " + tokenDetails.token); +} catch(AblyException e) { + System.out.println("An error occurred; err = " + e.getMessage()); +} + +bc[csharp]. try { + TokenParams tokenParams = new TokenParams { ClientId = "bob" }; + var tokenDetails = await client.Auth.RequestTokenAsync(tokenParams); + Console.WriteLine("Success; token = " + tokenDetails.Token); +} +catch (AblyException e) +{ + Console.WriteLine("An error occurred; err = " + e.Message); +} + +bc[objc]. ARTTokenParams *tokenParams = [[ARTTokenParams alloc] initWithClientId:@"Bob"]; +[client.auth requestToken:tokenParams withOptions:nil callback:^(ARTTokenDetails *tokenDetails, NSError *error) { + if (error) { + NSLog(@"An error occurred; err = %@", error); + } else { + NSLog(@"Success; token = %@", tokenDetails.token); + } +}]; + +bc[swift]. let tokenParams = ARTTokenParams(clientId: "Bob") +client.auth.requestToken(tokenParams, withOptions: nil) { tokenDetails, error in + guard let tokenDetails = tokenDetails else { + print("An error occurred; err = \(error!)") + return + } + print("Success; token = \(tokenDetails.token)") +} + +bc[go]. tokenParams := &ably.TokenParams{ + ClientID: "Bob", +} +token, err := client.Auth.RequestToken(tokenParams, &ably.AuthOptions{}) +if err != nil { + fmt.Println(err) +} +fmt.Println(token) + +h2(#related-types). Related types + +h3(#auth-options). + default: AuthOptions Object + ruby: AuthOptions Hash + java: io.ably.lib.rest.Auth.AuthOptions + +<%= partial partial_version('types/_auth_options') %> + +h3(#token-details). + default: TokenDetails + objc,swift: ARTTokenDetails + java: io.ably.lib.types.TokenDetails + ruby: Ably::Models::TokenDetails + +<%= partial partial_version('types/_token_details') %> + +h3(#token-params). + default: TokenParams Object + objc,swift: ARTTokenParams + ruby: TokenParams Hash + java: io.ably.lib.rest.Auth.TokenParams + +<%= partial partial_version('types/_token_params') %> + +h3(#token-request). + default: TokenRequest Object + objc,swift: ARTTokenRequest + ruby: Ably::Models::TokenRequest + java: io.ably.lib.rest.Auth.TokenRequest + +<%= partial partial_version('types/_token_request') %> diff --git a/content/rest/versions/v1.1/channel-status.textile b/content/rest/versions/v1.1/channel-status.textile new file mode 100644 index 0000000000..ce2527928d --- /dev/null +++ b/content/rest/versions/v1.1/channel-status.textile @@ -0,0 +1,114 @@ +--- +title: Channel Status API +section: rest +index: 21 +jump_to: + Help with: + - Overview#overview + - Metachannels#metachannels + - Permissions#permissions + - Requesting Channel Status#channel-status + - Use Cases#use-cases + - Tutorials#tutorials + Channel API Types: + - ChannelDetails#channel-details + - ChannelStatus#channel-status + - Occupancy#occupancy +--- + +h2(#overview). Overview + +The @Channel Status API@ is part of the "Channel Metadata API":/realtime/channel-metadata, and provides the capability to access information about channels via our REST endpoint. The information includes the current state of a channel, or its current occupancy. This data can be accessed as follows: + +* "Requesting":/rest/usage#request the "channel status":#channel-status of a single channel. +* "Enumerating":#enumeration-rest all currently active channels within an app, optionally providing the "channel status":#channel-status of each enumerated channel. + +At present, REST "requests":/rest/usage#request for lifecycle events will not count towards your message limit. If you are making use of "Reactor":/reactor however, then each message sent over Reactor *will* count towards your message limits. + +Note that it is also possible to use the "realtime endpoint":/realtime to subscribe to "channel lifecycle events":/realtime/channel-metadata#lifecycle-events (such as channels being created or closed) and "occupancy events":/realtime/channel-metadata#occupancy-events for any active channels (such as the counts of publishers, subscribers or presence members as they are added or removed). + +In addition, note that since the metadata of various channels is prone to change very frequently, unless you have a special use case within your app, we recommend you to subscribe to the realtime events via the "Channel Metadata API":/realtime/channel-metadata rather than poll for updates via REST, as this is inefficient and data is still likely to become stale as soon as you have received it. + +h2(#metachannels). Metachannels + +Metachannels are a namespace of channels which all start with the @[meta]@ qualifier, uniquely identifying them from regular channels. An example of a metachannel would be @[meta]channel.lifecycle@. + +There are a number of metachannels available, which are: + +- [meta]log := This metachannel is used to broadcast log messages (usually error messages) for events that occur within the application's context +- [meta]channel.lifecycle := This metachannel carries messages about channel lifecycle and metadata +- [meta]connection.lifecycle := This metachannel carries messages about the lifecycle of realtime connections + +All of the metadata associated with an app or a channel is available on one of these metachannels only. + +h2(#permissions). Permissions + +A regular Ably key has a "capability":/core-features/authentication#capabilities-explained which lists accessible resources and, for any given resource, a set of permitted operations. The wildcard resource '@*@' will match any regular channel name. + +In order to grant permission to a user to access a "metachannel":#metachannels, however, the resource name(s) in the @capability@ must include the @[meta]@ qualifier explicitly. If you are using an API Key, you can "set up capabilities in your dashboard":https://support.ably.io/solution/articles/3000073773-is-it-possible-to-restrict-which-channels-or-permissions-an-api-key-has. If you are making use of "tokens":/core-features/authentication#token-authentication, you specify it within the token. The following are examples of capabilities that will validly permit access to a metachannel: + +minimize. See an example of a capability allowing subscription to all metachannels + ```[json] + {"[meta]*":["subscribe"]} + ``` + + The above will allow for the key to @subscribe@ to any @meta@ channel. The wildcard @*@ indicates anything can follow the @[meta]@ claim, so an example of a valid channel would be @[meta]log@. However, this capability will not allow for any other actions to be performed on the metachannels, nor will it allow the key to do anything with any non-metachannels. + +minimize. See an example of a capability allowing all permissible actions on all metachannels and all regular channels + ```[json] + { + "[meta]*":["*"], + "*":["*"] + } + ``` + + The above permission provides two capabilities: the ability to perform any action on any metachannel (such as @[meta]log@) with @"[meta]*":["*"]@, and the ability to perform any action on any channel (such as @another:channel@) with @"*":["*"]@. However, you are *never* able to publish or be present in a metachannel, thus this permission in effect would result in an actual permission excluding publish and presence capabilities in @[meta]@ channels due to the "intersecting capabilities":/core-features/authentication#capabilities-explained-intersection. + +If @[meta]@ is not specified in the permissions, you will be unable to access the metachannels however. An example of this would be the following: + +minimize. See an example of a capability which is unable to use metachannels + ```[json] + { + "*":["*"] + } + ``` + + Although the above provide all capabilities in all regular channel, without a @[meta]@ permission being explicitly specified, you will be unable to perform any actions on a @[meta]@ channel. + +h2(#channel-status). Requesting Channel Status + +Through the REST library, it is possible to not only check a channel's "status":#metadata-rest and "occupancy data":#occupancy, but it is also possible to "enumerate":#enumeration-rest all channels that are currently active within an app. + +h3(#metadata-rest). Channel lifecycle status + +<%= partial partial_version('shared/_channel_metadata') %> + +h3(#enumeration-rest). Channel enumeration + +<%= partial partial_version('shared/_channel_enumeration') %> + +h2(#use-cases). Use cases + +Having access to channel metadata can provide numerous benefits. In a scenario where the number of subscribers of a channel goes well beyond a hundred, usage of other options such as "presence":/core-features/presence becomes less effective leading to an unexpected n-squared problem if all of the clients are subscribed to presence. You could instead make use of our "channel metadata":#metachannels to check the number of active subscribers. + +Equally, you may want to publish your data only if there is a subscriber for that data. The "channel lifecycle events":#lifecycle-events can notify you when a channel is opened, becomes active, or is no longer active thus giving your publisher clients an opportunity to know when the last subscriber leaves the channel. + +If you need to be able to query channel metadata at any point, you can make use of the "Channel Status API":#metadata-rest to inspect the state of individual channels, or "enumerate":#enumeration-rest all active channels in an app. + +h2(#tutorials). Tutorials + +If you wish to see an example of how to use channel metadata, you can check out our "Channel Lifecycle Events":/tutorials/channel-lifecycle-events tutorial, "Channel Occupancy Events":/tutorials/channel-occupancy-events tutorial, and the "Channel Enumeration":tutorials/channel-enumeration-rest tutorial. + +h1. Channel Status API Reference + +inline-toc. + Types: + - ChannelDetails#channel-details + - ChannelStatus#channel-status + - Occupancy#occupancy + +h2(#types). Types + +The payload of metadata events for channels is the "@ChannelDetails@":#channel-details type which contains the @channelId@ (AKA the "channel's name":/realtime/channels#name) and other static information about the channel, plus a @status@ containing a "@ChannelStatus@":#channel-status instance which contains information about the current state of the channel. + +<%= partial partial_version('types/_channel_details') %> diff --git a/content/rest/versions/v1.1/channels.textile b/content/rest/versions/v1.1/channels.textile new file mode 100644 index 0000000000..15b1159532 --- /dev/null +++ b/content/rest/versions/v1.1/channels.textile @@ -0,0 +1,598 @@ +--- +title: Channels +section: rest +index: 20 +languages: + - javascript + - nodejs + - php + - python + - ruby + - java + - swift + - objc + - csharp + - go +api_separator: +jump_to: + Help with: + - Getting started#getting-started + - Channels#channels + - Publishing#publishing + - Getting history of a channel#channel-history + - Batch publishing#batch-publish + - Channel Metadata#channel-metadata + - Channel namespaces + - Presence#presence + Channels API methods: + - get + Channel API properties: + - name + - presence + Channel API methods: + - publish + - history + Types: + - Related Types#related-types +--- + +Ably DSN organizes the message traffic within applications into named channels. Channels are the medium through which "messages":/realtime/messages are distributed; clients attach to channels to subscribe to "messages":/realtime/messages, and every "message":/realtime/messages published to a unique channel is broadcast by Ably to all subscribers. 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 REST client library provides a straightforward API for "publishing":/rest/messages#publish messages and retrieving "message history":/rest/channels#history from a channel. + +```[javascript](code-editor:rest/channel-history) + var rest = new Ably.Rest('{{API_KEY}}'); + var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.publish('example', 'message data', function() { + channel.history(function(err, resultPage) { + console.log('Last published message:' + resultPage.items[0]); + }); + }); +``` + +```[nodejs](code-editor:rest/channel-history) + var rest = new Ably.Rest('{{API_KEY}}'); + var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.publish('example', 'message data', function() { + channel.history(function(err, resultPage) { + console.log('Last published message:' + resultPage.items[0]); + }); + }); +``` + +```[ruby] + rest = Ably::Rest.new('{{API_KEY}}') + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}') + channel.publish 'example', 'message data' + result_page = channel.history() + puts "Last published message: #{result_page.items.first}" +``` + +```[python] + rest = AblyRest('{{API_KEY}}') + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}') + channel.publish(u'example', u'message data') + result_page = channel.history() + print("Last published message data: " + result_page.items[0].data) +``` + +```[php] + $rest = new Ably\AblyRest('{{API_KEY}}'); + $channel = $rest->channels->get('{{RANDOM_CHANNEL_NAME}}'); + $channel->publish('example', 'message data'); + $resultPage = $channel->history(); + echo("Last published data: " . $resultPage->items[0]->data); +``` + +```[java] + AblyRest rest = new AblyRest("{{API_KEY}}"); + Channel channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}"); + channel.publish("example", "message data"); + PaginatedResult resultPage = channel.history(null); + System.out.println("Last published message ID: " + resultPage.items[0].id); +``` + +```[csharp] + AblyRest rest = new AblyRest("{{API_KEY}}"); + var channel = rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}"); + await channel.PublishAsync("example", "message data"); + PaginatedResult resultPage = await channel.HistoryAsync(); + Console.WriteLine("Last published message ID: " + resultPage.Items[0].id); +``` + +```[objc] + ARTRest *rest = [[ARTRest alloc] initWithKey:@"{{API_KEY}}"]; + ARTRestChannel *channel = [rest.channels get:@"{{RANDOM_CHANNEL_NAME}}"]; + [channel publish:@"example" data:@"message data"]; + [channel history:^(ARTPaginatedResult *resultPage, ARTErrorInfo *error) { + NSLog(@"Last published message ID: %@", resultPage.items[0].id); + }]; +``` + +```[swift] + let rest = ARTRest(key: "{{API_KEY}}") + let channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}") + channel.publish("example", data: "message data") + channel.history { resultPage, error in + print("Last published message ID: \(resultPage!.items[0].id)") + } +``` + +```[go] + rest, err := ably.NewRestClient(ably.NewClientOptions("{{API_KEY}}")) + channel := rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}", nil) + err = channel.Publish("example", "message data") + page, err := channel.History(nil) + fmt.Println("Last published message: %s\n", page.Messages[0].Data) +``` + +If you would prefer to just dive into code and see some examples of how to use channels, then we recommend you take a look at our "REST tutorials":/tutorials. + +It is also possible to publish a message to multiple channels at once using our "batch publish beta feature":/rest-api/beta#batch. + +h2(#channels). Channels + +In order to publish, retrieve message history or access presence history, you must first obtain a REST channel instance. + +h3(#obtain-channel). Obtaining a channel instance + +A @Channel@ object is a reference to a single channel. A channel instance is obtained from the "@channels@ collection":/rest/usage/#channels of the @Rest::Client@@AblyRest@@Rest@ instance, and is uniquely identified by its unicode string name. Find out more about "channel naming":https://support.ably.io/solution/articles/3000046824-what-restrictions-exist-for-the-name-field-of-a-channel + +bc[jsall]. var channel = rest.channels.get('channelName'); + +bc[java]. Channel channel = rest.channels.get("channelName"); + +bc[csharp]. Channel channel = rest.Channels.Get("channelName"); + +bc[ruby]. channel = rest.channels.get('channelName') + +bc[python]. channel = rest.channels.get('channelName') + +bc[php]. $channel = $rest->channels->get('channelName'); + +bc[objc]. ARTRestChannel *channel = [realtime.channels get:@"channelName"]; + +bc[swift]. let channel = realtime.channels.get("channelName") + +bc[go]. channel := rest.Channels.Get("channelName", nil) + +To find out more about subscribing to messages published on channels in realtime, see the "Realtime channel API":/realtime/channels. + +h4(#set-channel). Setting channel options and encryption + +A set of "channel options":#channel-options may also be passed to configure a channel for encryption. Find out more about "symmetric message encryption":/rest/encryption. + +
+p(alert). *Currently @ChannelOptions@ and @CipherParams@ are not supported in Go.* +
+ +bc[jsall]. Crypto.generateRandomKey(function(err, key) { + var options = { cipher: { key: key } }; + var channel = rest.channels.get('channelName', options); +}); + +bc[java]. CipherParams params = Crypto.getDefaultParams(key); +ChannelOptions options = new ChannelOptions(); +options.encrypted = true; +options.cipherParams = params; +Channel channel = rest.channels.get("channelName", options); + +bc[csharp]. CipherParams cipherParams = Crypto.GetDefaultParams(key); +var options = new ChannelOptions(cipherParams); +var encryptedChannel = rest.Channels.Get("channelName", options); + +bc[ruby]. key = Ably::Util::Crypto.generate_random_key +options = { cipher: { key: key } } +channel = rest.channels.get('channelName', options) + +bc[python]. key = ably.util.crypto.generate_random_key() +channel = rest.channels.get('channelName', cipher={'key': key}) + +bc[php]. $key = Ably\Crypto->generate_random_key(); +$options = array('cipher' => array('key' => key)); +$channel = $rest->channels->get('channelName', $options); + +bc[objc]. NSData *key = [ARTCrypto generateRandomKey]; +ARTChannelOptions *options = [[ARTChannelOptions alloc] initWithCipherKey:key]; +ARTRestChannel *channel = [rest.channels get:@"channelName" options:options]; + +bc[swift]. let key = ARTCrypto.generateRandomKey() +let options = ARTChannelOptions(cipherKey: key) +let channel = rest.channels.get("channelName", options: options) + +h3(#publishing). Publishing to a channel + +To publish to a channel, make use of the "publish":#publish method of the channel: + +```[javascript](code-editor:rest/channel-history) + var rest = new Ably.Rest('{{API_KEY}}'); + var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.publish('example', 'message data'); +``` + +```[nodejs](code-editor:rest/channel-history) + var rest = new Ably.Rest('{{API_KEY}}'); + var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.publish('example', 'message data'); +``` + +```[ruby] + rest = Ably::Rest.new('{{API_KEY}}') + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}') + channel.publish 'example', 'message data' +``` + +```[python] + rest = AblyRest('{{API_KEY}}') + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}') + channel.publish(u'example', u'message data') +``` + +```[php] + $rest = new Ably\AblyRest('{{API_KEY}}'); + $channel = $rest->channels->get('{{RANDOM_CHANNEL_NAME}}'); + $channel->publish('example', 'message data'); +``` + +```[java] + AblyRest rest = new AblyRest("{{API_KEY}}"); + Channel channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}"); + channel.publish("example", "message data"); +``` + +```[csharp] + AblyRest rest = new AblyRest("{{API_KEY}}"); + var channel = rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}"); + await channel.PublishAsync("example", "message data"); +``` + +```[objc] + ARTRest *rest = [[ARTRest alloc] initWithKey:@"{{API_KEY}}"]; + ARTRestChannel *channel = [rest.channels get:@"{{RANDOM_CHANNEL_NAME}}"]; + [channel publish:@"example" data:@"message data"]; +``` + +```[swift] + let rest = ARTRest(key: "{{API_KEY}}") + let channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}") + channel.publish("example", data: "message data") +``` + +```[go] + rest, err := ably.NewRestClient(ably.NewClientOptions("{{API_KEY}}")) + channel := rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}", nil) + err = channel.Publish("example", "message data") +``` + +h3(#channel-history). Getting history of a channel + +To get the history of a channel, make use of the "history":#history method of the channel: + +```[javascript](code-editor:rest/channel-history) + var rest = new Ably.Rest('{{API_KEY}}'); + var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.history(function(err, resultPage) { + console.log('Last published message:' + resultPage.items[0]); + }); +``` + +```[nodejs](code-editor:rest/channel-history) + var rest = new Ably.Rest('{{API_KEY}}'); + var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.history(function(err, resultPage) { + console.log('Last published message:' + resultPage.items[0]); + }); +``` + +```[ruby] + rest = Ably::Rest.new('{{API_KEY}}') + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}') + result_page = channel.history() + puts "Last published message: #{result_page.items.first}" +``` + +```[python] + rest = AblyRest('{{API_KEY}}') + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}') + result_page = channel.history() + print("Last published message data: " + result_page.items[0].data) +``` + +```[php] + $rest = new Ably\AblyRest('{{API_KEY}}'); + $channel = $rest->channels->get('{{RANDOM_CHANNEL_NAME}}'); + $resultPage = $channel->history(); + echo("Last published data: " . $resultPage->items[0]->data); +``` + +```[java] + AblyRest rest = new AblyRest("{{API_KEY}}"); + Channel channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}"); + PaginatedResult resultPage = channel.history(null); + System.out.println("Last published message ID: " + resultPage.items[0].id); +``` + +```[csharp] + AblyRest rest = new AblyRest("{{API_KEY}}"); + var channel = rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}"); + PaginatedResult resultPage = await channel.HistoryAsync(); + Console.WriteLine("Last published message ID: " + resultPage.Items[0].id); +``` + +```[objc] + ARTRest *rest = [[ARTRest alloc] initWithKey:@"{{API_KEY}}"]; + ARTRestChannel *channel = [rest.channels get:@"{{RANDOM_CHANNEL_NAME}}"]; + [channel history:^(ARTPaginatedResult *resultPage, ARTErrorInfo *error) { + NSLog(@"Last published message ID: %@", resultPage.items[0].id); + }]; +``` + +```[swift] + let rest = ARTRest(key: "{{API_KEY}}") + let channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}") + channel.history { resultPage, error in + print("Last published message ID: \(resultPage!.items[0].id)") + } +``` + +```[go] + rest, err := ably.NewRestClient(ably.NewClientOptions("{{API_KEY}}")) + channel := rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}", nil) + page, err := channel.History(nil) + fmt.Println("Last published message: %s\n", page.Messages[0].Data) +``` + +h3(#batch-publish). Batch publishing + +It is common for a single message to be intended for multiple channels. If you wish to send a message to multiple channels within a single operation, you can make use of the "REST batch API":/rest-api/beta#batch. + +h3(#channel-metadata). Channel metadata + +Ably provides a "REST API":/realtime/channel-metadata to query your app for metadata about channels, as well as a "realtime API":/realtime/channel-metadata to subscribe to channel lifecycle events. Using the "REST API":/rest-api, you can enumerate all active channels, or obtain the status of an individual channel. Using our Realtime API, you can subscribe to "channel lifecycle":/realtime/channel-metadata events (such as being created or closed etc), or subscribe to periodic "occupancy":/realtime/channel-metadata#occupancy-rest updates for all active channels (such as how many people are subscribed to a channel). + +h3(#namespaces). Channel namespaces + +<%= partial partial_version('shared/_channel_namespaces') %> + +h2(#presence). Presence + +Channels expose a @presence@@Presence@ member which a client can use to obtain present members and presence event history for the channel itself. See the "REST presence":/rest/presence documentation for details. + +h1. API Reference + +inline-toc. + Channels Methods: + - get#get + Channel Properties: + - name#name + - presence#presence + - push#push + Channel Methods: + - publish()#publish + - history(options)#history + Related types: + - Message#message + - ChannelOptions#channel-options + - PaginatedResult#paginated-result + - Param + +h2(#channels-object). + default: Channels + +The @Channels@ object, accessed from the "rest library client constructor":/rest/usage#channels, is used to create and destroy @Channel@ objects. It exposes the following public methods: + +h3(#channels-methods). Channels Methods + +h6(#get). + default: get + csharp: Get + +bq(definition). + default: "Channel":#properties get(String channelName, "ChannelOptions":#channel-options channelOptions) + csharp: "Channel":#properties Get(String channelName, "ChannelOptions":#channel-options channelOptions) + objc,swift: "ARTRealtimeChannel":#properties get(String channelName, "ARTChannelOptions":#channel-options channelOptions); + +Creates a new "Channel":#properties object if none for the channel exists, or returns the existing channel object. + +h2(#channel-object). Channel + +The Channel object, created via the "Channels":#channels-object object, is used to interact with a specific channel. + +h3(#properties). + default: Channel Properties + python: Channel Attributes + php: Ably\Channel Properties + objc,swift: ARTRestChannel Properties + ruby: Ably::Rest::Channel Attributes + java: io.ably.lib.rest.Channel Members + csharp: IO.Ably.Rest.RestChannel Members + +The @Channel@ object exposes the following public propertiesattributesmembers: + +h6(#name). + default: name + csharp,go: Name + +The name @String@ unique to this channel. + +h6(#presence). + default: presence + csharp,go: Presence + +Provides access to the "REST Presence":/rest/presence object for this channel which can be used to get members present on the channel, or retrieve presence event history. + +h6(#push). + default: push + +Provides access to the "PushChannel":/general/push/activate-subscribe#push-channel object for this channel which can be used to access members present on the channel, or participate in presence. + +h3. Channel Methods + +h6(#publish). + default: publish + csharp,go: Publish + +There are two overloaded versions of this method: + +bq(definition#publish-data). + default: publish(String name, Object data, callback("ErrorInfo":/realtime/types#error-info err)) + ruby,php: publish(String name, Object data) + python: publish(name=Unicode, data=Object) + java: void publish(String name, Object data) + csharp: Task PublishAsync(string name, object data, string clientId = null) + objc,swift: publish(name: String?, data: AnyObject?, callback: (("ARTErrorInfo":/realtime/types#error-info?) -> Void)?) + go: (c *RestChannel) Publish(name string, data interface{}) (error "ErrorInfo":#error-info) + +Publish a single message on this channel based on a given event name and payload. A callbacklistener may optionally be passed in to this call to be notified of success or failure of the operation. + +It is also possible to publish a message to multiple channels at once using our "batch publish feature":/rest-api/beta#batch. + +bq(definition#publish-msg-array). + default: publish(Object[] messages, callback("ErrorInfo":/realtime/types#error-info err)) + ruby,php: publish("Message":#message[] messages) + python: publish(messages=List<"Message":#message>) + java: void publish("Message":#message[] messages) + csharp: Task PublishAsync(IEnumerable<"Message":#message> messages) + objc,swift: publish(messages: [ "ARTMessage":#message ], callback: (("ARTErrorInfo":/realtime/types#error-info?) -> Void)?) + go: (c *RestChannel) PublishAll(messages []*proto.Message) (error "ErrorInfo":#error-info) + +Publish several messages on this channel. A callbacklistener may optionally be passed in to this call to be notified of success or failure of the operation. It is worth noting that "there are additional considerations and constraints if you want to publish multiple messages idempotently in one publish operation with client-supplied IDs.":https://support.ably.io/solution/articles/3000088357-how-can-i-publish-multiple-messages-in-a-single-rest-request-with-idempotency + +h4. Parameters + +- name := event name for the published message
__Type: @String@____Type: @Unicode@ for Python 2, @String@ for Python 3__ +-
data
:= data payload for the message. The supported payload types are Strings, JSON objects and arrays, buffers containing arbitrary binary data, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are Strings, JsonObject, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are strings, plain .Net objects, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are Strings, Hash or Array objects that can be serialized to JSON using @to_json@, binary data as @ASCII-8BIT@ byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are unicode Strings, Dict, or List objects that can be serialized to JSON using @json.dumps@, binary data as @bytearray@ (in Python 3, @bytes@ also works), and None.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are @NS@@String@, @NS@@Dictionary@ or @NS@@Array@ objects that can be serialized to JSON, binary data as @NSData@, and @nil@.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload types are Strings, Associative Array or Array objects that can be serialized to JSON, binary data as byte arrays, and null.
__Type: @Object@__ +-
data
:= data payload for the message. The supported payload type are strings, objects that can be serialized to JSON, binary data as byte arrays, and nil.
__Type: @interface@ + +- messages := An array of message objects to publish
__Type: "@Message []@":#message__ + +-
callback
:= is a function of the form @function(err)@ which is called upon completion +-
callback
:= called upon publishing the message, or with an error + +blang[jsall,swift,objc]. + h4. Callback result + + On successful publish of the message, @err@ is null. On failure to publish the message, @err@ contains an "@ErrorInfo@":#error-info object describing the failure reason. + +blang[java,ruby,php]. + h4. Failure + + On failure to publish the message, an "@AblyException@":/rest/types#ably-exception will be raised. + +blang[csharp]. + h4. Returns + + The method is asynchronous and returns a Task that can be awaited. + + On failure to publish the message, an "@AblyException@":/rest/types#ably-exception will be raised. + +blang[go]. + h4. Returns + + The function returns "@ErrorInfo@":#error-info if an error has occurred, otherwise it returns @nil@. + +h6(#history). + default: history + csharp,go: History + +bq(definition). + default: history(Object options, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":#paginated-result<"Message":#message> resultPage)) + ruby: "PaginatedResult":#paginated-result<"Message":#message> history(Hash options) + python: "PaginatedResult":#paginated-result<"Message":#message> history(kwargs_options) + php: "PaginatedResult":#paginated-result<"Message":#message> history(Array options) + java: "PaginatedResult":#paginated-result<"Message":#message> history("Param":#param[] options) + csharp: Task<"PaginatedResult":#paginated-result<"Message":#message>> HistoryAsync("PaginatedRequestParams":#paginated-request-params dataQuery) + objc,swift: history(query: ARTRealtimeHistoryQuery?, callback: ("ARTPaginatedResult":#paginated-result<"ARTMessage":#message>?, ARTErrorInfo?) -> Void) throws + go: (c *RestChannel) History(options *PaginateParams) (*"PaginatedResult":#paginated-result, "error":#error-info) + +Gets a "paginated":#paginated-result set of historical messages for this channel. If the "channel is configured to persist messages to disk":https://support.ably.io/support/solutions/articles/3000030059-how-long-are-messages-stored-for, then message history will "typically be available for 24 - 72 hours":https://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for. If not, messages are only retained in memory by the Ably service for two minutes. + +h4. Parameters + +- optionsquery"Param":#param[] options := an optional object containing the query parametersoptional keyword arguments containing the query parametersan optional set of key value pairs containing the query parametersan optional Associative Array containing the query parameters, as specified in the "message history API documentation":/rest/history#channel-history. + +-
callback
:= is a function of the form: @function(err, resultPage)@ +-
callback
:= called with a "ARTPaginatedResult":#paginated-result<"ARTMessage":#message> object or an error + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @resultPage@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + On failure to retrieve message history, @err@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +blang[java,ruby,php,python]. + h4. Returns + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + Failure to retrieve the message history will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[csharp]. + h4. Returns + + The method is asynchronous and return a Task that has to be awaited to get the result. + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@NextAsync@":#paginated-result and "@FirstAsync@":#paginated-result methods. + + Failure to retrieve the message history will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[go]. + h4. Returns + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. @error@ will be nil. + + On failure to retrieve message history, the @error@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +h2(#related-types). Related types + +h3(#message). + default: Message + objc,swift: ARTMessage + ruby: Ably::Models::Message + php: Ably\Models\Message + java: io.ably.lib.types.Message + csharp: IO.Ably.Message + +<%= partial partial_version('types/_message') %> + +h3(#channel-options). + default: ChannelOptions Object + objc,swift: ARTChannelOptions + ruby: ChannelOptions Hash + python: ChannelOptions keyword arguments + php: ChannelOptions Array + java: io.ably.lib.types.ChannelOptions + csharp: IO.Ably.ChannelOptions + +<%= partial partial_version('types/_channel_options') %> + +h3(#paginated-request-params). + csharp: PaginatedRequestParams + +blang[csharp]. + <%= partial partial_version('types/_history_request_params'), indent: 2, skip_first_indent: true %> + +h3(#paginated-result). + default: PaginatedResult + objc,swift: ARTPaginatedResult + ruby: Ably::Models::PaginatedResult + php: Ably\Models\PaginatedResult + java: io.ably.lib.types.PaginatedResult + csharp: IO.Ably.PaginatedResult + +<%= partial partial_version('types/_paginated_result') %> + +h3(#param). + java: io.ably.lib.types.Param + +blang[java]. + <%= partial partial_version('types/_param'), indent: 2, skip_first_indent: true %> diff --git a/content/rest/versions/v1.1/encryption.textile b/content/rest/versions/v1.1/encryption.textile new file mode 100644 index 0000000000..01e8b125b2 --- /dev/null +++ b/content/rest/versions/v1.1/encryption.textile @@ -0,0 +1,327 @@ +--- +title: Encryption +section: rest +index: 70 +languages: + - javascript + - nodejs + - php + - python + - ruby + - java + - swift + - objc + - csharp + - go +api_separator: +jump_to: + Help with: + - Getting started#getting-started + - Understanding encryption#encryption + API Reference: + - getDefaultParams#get-default-params + - generateRandomKey#generate-random-key + Related types: + - Channel Options + - CipherParams#cipher-params +--- + +Ably client libraries support built-in symmetric encryption of message content, making it easier to build apps that encrypt content fully end-to-end. Whilst "TLS is enabled by default":https://support.ably.io/solution/articles/3000045208-are-messages-sent-to-and-received-from-ably-sent-securely-using-tls and ensures that data is securely sent to and received from Ably, messages are not encrypted within the Ably system. Using the encryption feature of our client libraries ensures that message payloads are opaque, can never be decrypted by Ably, and can only be decrypted by other clients that share your secret key. + +h2(#getting-started). Getting started + +"Channels":/rest/channels can be easily configured to automatically encrypt and decrypt all message payloads using the secret @key@ provided in the "channel options":/rest/types#channel-options. Below is a simple example: + +
+p(alert). *Unfortunately at present it is not possible to setup channel options in Go.* +
+ +```[jsall] + Ably.Rest.Crypto.generateRandomKey(function(err, key) { + var channelOpts = { cipher: { key: key } }; + var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}', channelOpts); + channel.publish('unencrypted', 'encrypted secret payload'); + }); +``` + +```[ruby] + key = Ably::Util::Crypto.generateRandomKey() + channel_opts = { cipher: { key: key } } + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}', channel_opts) + channel.publish 'unencrypted', 'encrypted secret payload' +``` + +```[python] + key = ably.util.crypto.generate_random_key() + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}', cipher={'key': key}) + channel.publish(u'unencrypted', u'encrypted secret payload') +``` + +```[php] + $key = Ably\Utils\Crypto->generateRandomKey(); + $channelOpts = array('cipher' => array('key' => $key)); + $channel = rest->channels->get('{{RANDOM_CHANNEL_NAME}}', $channelOpts); + $channel->publish('unencrypted', 'encrypted secret payload'); +``` + +```[java] + ChannelOptions options = ChannelOptions.withCipherKey(); + Channel channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}", options); + channel.publish("unencrypted", "encrypted secret payload"); +``` + +```[csharp] + AblyRest rest = new Ably.Rest('{{API_KEY}}'); + byte[] key = Crypto.GenerateRandomKey(); + ChannelOptions options = new ChannelOptions(key); + IRestChannel channel = rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}", options); + await channel.PublishAsync("unencrypted", "encrypted secret payload"); +``` + +```[objc] + ARTChannelOptions *options = [[ARTChannelOptions alloc] initWithCipherKey:]; + ARTRestChannel *channel = [rest.channels get:@"{{RANDOM_CHANNEL_NAME}}" options:options]; + [channel publish:@"unencrypted" data:@"encrypted secret payload"]; +``` + +```[swift] + let options = ARTChannelOptions(cipherKey: ) + let channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}", options: options) + channel.publish("unencrypted", data: "encrypted secret payload") +``` + +Note that the @key@ should not be a pass-phrase, but a cryptographic key - generated from a secure random source, 128 or 256 bits long, binary or base64-encoded. If you wish to encrypt messages with a pass-phrase (for example, one entered by a user), you should use a "key derivation function":https://en.wikipedia.org/wiki/Key_derivation_function to transform that into a key. The client libraries are also capable of "generating a random key":#generate-random-key for you. + +blang[python]. + If you are using Python 2 and you wish to pass in a base64-encoded key, make sure you pass it in as a @unicode@ string, not a @str@, or the library will interpret it as a binary. + +h2(#understanding-encryption). Understanding encryption + +The libraries support encryption purely as a convenience; the libraries ensure interoperability between environments by having compatible implementations of encryption algorithms and by making common choices on format, mode, padding etc. However, Ably intentionally does not manage the distribution of keys between clients, and end-to-end encryption is enabled without exposing keys to the Ably service at all. This has the advantage that Ably demonstrably has no access to the un-encrypted contents of your messages, but also means that each app is responsible for enabling the distribution of keys to clients independently of Ably. + +The client library support for encryption supports symmetric encryption only, and requires each participating client to each specify the correct "@CipherParams@":#cipher-params secret @key@ when creating a @Channel@ instance. Clients that do not specify a key will be delivered the still-encrypted message payloads that they may then still wish to decrypt offline. + +The client libraries are designed to be extensible, but initially only support the AES algorithm (with a default key length of 256 bits) and CBC mode. These defaults are intended to ensure that encryption support can be provided in all target environments and platforms. + +Encryption is supported for the @data@ attribute (payload) of "published messages":/rest/types#messages and "presence member messages":/rest/types#presence-message on a channel, over both REST and realtime publish operations. Decryption is supported in realtime "message":/rest/messages and "presence message":/rest/presence subscriptions and in "REST history":/rest/history, "REST presence get":/rest/presence#get, and "REST presence history":/rest/presence#history operations. + +All other attributes of "messages":/rest/types#messages and "presence messages":/rest/types#presence-message, such as event @name@ or @clientId@@client_id@@ClientId@ remain un-encrypted. All sensitive data, when using the library's symmetric encryption, must be placed in the @data@ attribute to ensure it is encrypted before it is transmitted to Ably. + +The key in use at any given time is known by the client library, but the Ably service has no visibility of the key; it knows only that a given message payload was encrypted. When accessing messages via the "history API":/rest/history, it is the caller's responsibility to ensure that the correct key is configured for the channel before the history request is made. + +Encryption options (algorithm, key, etc) are specified on a per-channel basis; it is expected that apps may wish to have both un-encrypted and encrypted channels on a single connection. + + +h1. Crypto API Reference + +inline-toc. + Methods: + - getDefaultParams#get-default-params + - generateRandomKey#generate-random-key + Related types: + - Channel Options + - CipherParams#cipher-params + +The @Ably.Rest.Crypto@@Ably::Util::Crypto@@Ably\Utils\Crypto@@ably.util.crypto@@io.ably.lib.util.crypto@@ARTCrypto@ @IO.Ably.Encryption.Crypto@ object exposes the following public methods: + +h2(#methods). Methods + +h6(#get-default-params). + default: getDefaultParams + ruby: get_default_params + python: get_default_params + csharp: GetDefaultParams + go: DefaultCipherParams + +bq(definition). + jsall: "CipherParams":#cipher-params Crypto.getDefaultParams(Object params) + ruby: "CipherParams":#cipher-params Crypto.get_default_params(Hash params) + python: "CipherParams":#cipher-params Crypto.get_default_params(Dict params) + php: "CipherParams":#cipher-params Crypto.getDefaultParams(Array params) + java: "CipherParams":#cipher-params Crypto.getDefaultParams(Param[] params) + csharp: "CipherParams":#cipher-params GetDefaultParams(byte[] key = null, byte[] iv = null, CipherMode? mode = null) + swift,objc: getDefaultParams(values: [NSObject : AnyObject]) -> "ARTCipherParams":#cipher-params + go: DefaultCipherParams() (*"CipherParams":#cipher-params, error) + +This call obtains a "@CipherParams@":#cipher-params object using the values passed in (which must be a subset of @CipherParams@ fields that at a minimum includes a @key@), filling in any unspecified fields with default values, and checks that the result is a valid and self-consistent.This call takes a key, an initialization vector (iv) and a Cipher mode. There is also on override which accepts the @key@ and @iv@ as base64 encoded strings. It will validate the passed values and generate @CipherParams@returns a "@CipherParams@":#cipher-params object with fields set to default values. This generates random secret key and initialization vector (iv) values. + +You will rarely need to call this yourself, since the client library will handle it for you if you specify @cipher@ params when initializing a channel (as in the example "at the top":#getting-started) or when setting channel options with @channel#setOptions@. + +
+h4. Parameters + +- paramsarguments := The cipher paramsarguments that you want to specify. It must at a minimum include a @key@, which should be either a binary (@byte[]@@ArrayBuffer@ or @WordArray@@Buffer@byte array@NSData@) or a base64-encoded @NS@@String@. +
+
+ +h4. Returns + +On success, the method returns a complete "@CipherParams@":#cipher-params object. Failure will raise an "@AblyException@":/rest/types/#ably-exception exception. + +h4. Example + +```[jsall](code-editor:realtime/channel-encrypted) + var cipherParams = Ably.Rest.Crypto.getDefaultParams({key: }); + var channelOpts = { cipher: cipherParams }; + var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}', channelOpts); +``` + +```[ruby] + cipher_params = Ably::Util::Crypto.get_default_params({key: }) + channel_opts = { cipher: cipher_params } + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}', channel_opts) +``` + +```[python] + cipher_params = ably.util.crypto.get_default_params({'key': }) + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}', cipher=cipher_params) +``` + +```[php] + $cipherParams = Ably\Utils\Crypto->getDefaultParams(array('key' => )); + $channelOpts = array('cipher' => $cipherParams); + $channel = $rest->channels->get('{{RANDOM_CHANNEL_NAME}}', $channelOpts); +``` + +```[java] + CipherParams params = Crypto.getDefaultParams(new Param[]{ new Param("key", ) }); + ChannelOptions options = new ChannelOptions(); + options.encrypted = true; + options.cipherParams = params; + Channel channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}", options); +``` + +```[csharp] + CipherParams cipherParams = Crypto.GetDefaultParams(); + var channel = rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}", new ChannelOptions(cipherParams)); +``` + +```[objc] +ARTCipherParams *params = [ARTCrypto getDefaultParams:@{@"key": }]; +ARTChannelOptions *options = [[ARTChannelOptions alloc] initWithCipher:params]; +ARTRealtimeChannel *channel = [rest.channels get:@"{{RANDOM_CHANNEL_NAME}}" options:options]; +``` + +```[swift] +let params = ARTCrypto.getDefaultParams(["key": ]) +let options = ARTChannelOptions(cipher: params) +let channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}", options: options) +``` + +```[go] +params, err := Crypto.DefaultCipherParams() +``` + +h6(#generate-random-key). + default: generateRandomKey + ruby: generate_random_key + python: generate_random_key + csharp,go: GenerateRandomKey + +bq(definition). + default: Crypto.generateRandomKey(Int keyLength?, callback("ErrorInfo":/rest/types#error-info err, @Buffer@ key)) + ruby: byte array Crypto.generate_random_key(Int key_length?) + python: byte array Crypto.generate_random_key(Int key_length?) + php: string Crypto.generateRandomKey(Int keyLength?) + java: @byte[]@ Crypto.generateRandomKey(Int keyLength?) + csharp: byte[] GenerateRandomKey(int? keyLength = null, CipherMode? mode = null) + objc,swift: generateRandomKey(length?: UInt) -> NSData + go: GenerateRandomKey(keyLength ...int) ([]byte, error) + +This call obtains a randomly-generated binary key of the specified key length and optional CipherMode. + +h4. Parameters + +- keyLengthkey_length := Optional @Int@ with the length of key to generate. For AES, this should be either 128 or 256. If unspecified, defaults to 256. + +-
mode
:= Optional AES @CipherMode@ which is used when the key is generated +-
callback
:= is a function of the form @function(err, key)@ which is called upon completion + +blang[jsall]. + h4. Callback result + + On successfully generating a key, the callback is called with that key as a @WordArray@@Buffer@, and @err@ is @null@. On failure to create a key, @err@ contains an "@ErrorInfo@":#error-info object describing the failure reason. + +blang[java,ruby,objc,swift,php,python,csharp,go]. + h4. Returns + + On success, the method returns the generated key as a @byte[]@ array@bytes@byte arraybinary string@NSData@@[]byte@ array. Failure will raise an "@AblyException@":/rest/types/#ably-exceptionFailure will cause error to contain an "@ErrorInfo@":#error-info object describing the failure reason. + +h4. Example + +```[jsall] + Ably.Rest.Crypto.generateRandomKey(256, function(err, key) { + if(err) { + console.log("Key generation failed: " + err.toString()); + } else { + var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}', {cipher: {key: key}}); + } + }); +``` + +```[ruby] + key = Ably::Util::Crypto.generate_random_key(256) + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}', { cipher: {key: key}}) +``` + +```[python] + cipher_params = ably.util.crypto.generate_random_key(256) + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}', cipher={'key': key}) +``` + +```[php] + $key = Ably\Utils\Crypto->generateRandomKey(256); + $channel = $rest->channels->get('{{RANDOM_CHANNEL_NAME}}', array('cipher' => array('key' => $key))); +``` + +```[java] + byte[] key = Crypto.generateRandomKey(256); + ChannelOptions options = ChannelOptions.withCipher(key); + Channel channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}", options); +``` + +```[csharp] + byte[] key = Crypto.GenerateRandomKey(256); + ChannelOptions options = new ChannelOptions(key); + var channel = rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}", options); +``` + +```[objc] + NSData *key = [ARTCrypto generateRandomKey:256]; + ARTChannelOptions *options = [[ARTChannelOptions alloc] initWithCipherKey:key]; + ARTRealtimeChannel *channel = [rest.channels get:@"{{RANDOM_CHANNEL_NAME}}" options:options]; +``` + +```[swift] +let key = ARTCrypto.generateRandomKey(256) +let options = ARTChannelOptions(cipherWithKey: key) +let channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}", options: options) +``` + +```[go] +key, err := Crypto.GenerateRandonKey(256) +``` + +h2(#related-types). Related types + +h3(#channel-options). + default: ChannelOptions Object + objc,swift: ARTChannelOptions + ruby: ChannelOptions Hash + python: ChannelOptions Dict + php: ChannelOptions Array + csharp: IO.Ably.ChannelOptions + java: io.ably.lib.types.ChannelOptions + +<%= partial partial_version('types/_channel_options') %> + +h3(#cipher-params). + default: CipherParams + objc,swift: ARTCipherParams + ruby: CipherParams Hash + python: CipherParams Dict + php: CipherParams Array + csharp: IO.Ably.CipherParams + java: io.ably.lib.util.Crypto.CipherParams + +<%= partial partial_version('types/_cipher_params') %> diff --git a/content/rest/versions/v1.1/history.textile b/content/rest/versions/v1.1/history.textile new file mode 100644 index 0000000000..272ca7dd0e --- /dev/null +++ b/content/rest/versions/v1.1/history.textile @@ -0,0 +1,338 @@ +--- +title: History +section: rest +index: 50 +languages: + - javascript + - nodejs + - php + - python + - ruby + - java + - swift + - objc + - csharp + - go +api_separator: +jump_to: + Help with: + - Getting started + - Channel and Presence history#channel-and-presence + - Persisted history + API reference: + - Message history#channel-history + - Presence history#presence-history + Types: + - Related Types#related-types +--- + +The REST client library provides message and presence event history for channels. Channel history combines both instantaneous "live" history as well as the longer term "persisted history":#persisted-history. If "persisted history":#persisted-history is enabled for the channel, then messages will "typically be stored for 24 - 72 hours on disk":https://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for. If persisted history is not enabled, Ably retains the last two minutes of instantaneous "live" message history in memory. + +h2(#getting-started). Getting started + +The Ably REST client library provides a straightforward API to retrieve "paginated":#paginated-result message or presence event history. Each page of history, by default, contains up to 100 messages. Message ordering, by default, is from most recent to oldest. + +```[javascript](code-editor:rest/channel-history) + var rest = new Ably.Rest('{{API_KEY}}'); + var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.publish('example', 'message data', function(err) { + channel.history(function(err, resultPage) { + var recentMessage = resultPage.items[0]; + alert('Most recent message: ' + recentMessage.id + ' - ' + recentMessage.data); + }); + }); +``` + +```[nodejs](code-editor:rest/channel-history) + var rest = new Ably.Rest('{{API_KEY}}'); + var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.publish('example', 'message data', function(err) { + channel.history(function(err, resultPage) { + var recentMessage = resultPage.items[0]; + console.log('Most recent message: ' + recentMessage.id + ' - ' + recentMessage.data); + }); + }); +``` + +```[ruby] + rest = Ably::Rest.new('{{API_KEY}}') + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}') + channel.publish 'example', 'message data' + result_page = channel.history + recent_message = result_page.items.first + puts "Most recent message: #{recent_message.message.id} - #{recent_message.data}") +``` + +```[python] + rest = AblyRest('{{API_KEY}}') + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}') + result_page = channel.history() + recent_message = result_page.items[0] + print('Most recent message: ' + str(recent_message.message.id) + ' - ' + recent_message.data) +``` + +```[php] + $rest = new Ably\AblyRest('{{API_KEY}}'); + $channel = $rest->channels->get('{{RANDOM_CHANNEL_NAME}}'); + $channel->publish('example', 'message data'); + $resultPage = $channel->history(); + $recentMessage = $resultPage->items[0]; + echo("Most recent message data: " . $recentMessage->data); +``` + +```[java] + AblyRest rest = new AblyRest("{{API_KEY}}"); + Channel channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}"); + channel.publish("example", "message data") + PaginatedResult resultPage = channel.history(null); + Message recentMessage = resultPage.items[0]; + System.out.println("Most recent message: " + recentMessage.id + " - " + recentMessage.data); +``` + +```[csharp] + AblyRest rest = new AblyRest("{{API_KEY}}"); + IRestChannel channel = rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}"); + await channel.PublishAsync("example", "message data"); + PaginatedResult resultPage = await channel.HistoryAsync(); + Message recentMessage = resultPage.Items[0]; + Console.WriteLine("Most recent message: " + recentMessage.Id + " - " + recentMessage.Data); +``` + +```[objc] + ARTRest *rest = [[ARTRest alloc] initWithKey:@"{{API_KEY}}"]; + ARTRestChannel *channel = [rest.channels get:@"{{RANDOM_CHANNEL_NAME}}"]; + [channel publish:@"example" data:@"message data"]; + [channel history:^(ARTPaginatedResult *resultPage, ARTErrorInfo *error) { + ARTMessage *recentMessage = resultPage.items[0]; + NSLog(@"Most recent message: %@ - %@", recentMessage.id, recentMessage.data); + }]; +``` + +```[swift] + let rest = ARTRest(key: "{{API_KEY}}") + let channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}") + channel.publish("example", data: "message data") + channel.history { resultPage, error in + let recentMessage = resultPage!.items[0] as! ARTMessage + print("Most recent message: \(recentMessage.id) - \(recentMessage.data)") + } +``` + +```[go] + rest, err := ably.NewRestClient(ably.NewClientOptions("{{API_KEY}}")) + channel := rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}", nil) + err = channel.Publish("example", "message data") + page, err := channel.History(nil) + fmt.Println("Last published message: %s\n", page.Messages[0].Data) +``` + +If you would prefer to just dive into code and see some examples of how to use history via the REST API, then we recommend you take a look at our "REST tutorials":/tutorials. + +h2(#channel-and-presence). Channel & Presence history + +Both the "@Channel@":/rest/channels and "@Presence@":/rest/presence objects provide history. The "@Channel@":/rest/channels object provides the history of "@Message@":#message objects published on the channel, whereas the "@Presence@":/rest/presence object provides presence event history of that channel i.e. members entering, updating or leaving the channel as "@PresenceMessage@":#presence-message objects. + +h3(#persisted-history). Enabling persistent history + +By default, persisted history on channels is disabled and messages are only stored by the Ably service for two minutes in memory. If persisted history is enabled for the channel, then messages will "typically be stored for 24 - 72 hours on disk":https://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for. + +Every message that is persisted to or retrieved from disk counts as an extra message towards your monthly quote. 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. + +h1. API reference + +inline-toc. + Channel: + - history(options)#channel-history + Presence: + - history(options)#presence-history + Related types: + - Message + - PresenceMessage#presence-message + - Presence action + - PaginatedResult#paginated-result + - Param + +h2. Channel object + +The "Rest @Channel@ object":/rest/channels exposes the following public method to obtain "@Message@":#message history. + +h3. Methods + +h6(#channel-history). + default: history + csharp,go: History + +bq(definition). + default: history(Object option, callback("ErrorInfo":/rest/types#error-info err, "PaginatedResult":#paginated-result<"Message":#message> resultPage)) + ruby: "PaginatedResult":#paginated-result<"Message":#message> history(Hash option) + python: "PaginatedResult":#paginated-result<"Message":#message> history(kwargs_options) + php: "PaginatedResult":#paginated-result<"Message":#message> history(Array option) + java: "PaginatedResult":#paginated-result<"Message":#message> history("Param":#param[] option) + csharp: Task<"PaginatedResult":#paginated-result<"Message":#message>> HistoryAsync("PaginatedRequestParams":#data-request query); + objc,swift: history(query: ARTDataQuery?, callback: ("ARTPaginatedResult":#paginated-result<"ARTMessage":#message>?, ARTErrorInfo?) -> Void) throws + go: (c *RestChannel) History(option *PaginateParams) (*"PaginatedResult":#paginated-result, error) + +Gets a "paginated":#paginated-result set of historical messages for this channel. + +h4. Parameters + +- optionquery"Param":#param[] option := an optional object containing the query parametersoptional keyword arguments containing the query parametersan optional set of key value pairs containing the query parametersan Associate Array containing the query parameters, as specified below. + +-
callback
:= is a function of the form: @function(err, resultPage)@ +-
callback
:= called with a "ARTPaginatedResult":#paginated-result<"ARTMessage":#message> object or an error + +h4. @options@ parameters@ARTDataQuery@ properties"@PaginatedRequestParams@":#paginated-request-params properties + +- start:startStart := _beginning of time_ earliest @DateTimeOffset@ or @Time@ or time in milliseconds since the epoch for any messages retrieved
__Type: @Long@@Int or @Time@@DateTimeOffset@__ +- end:endEnd := _current time_ latest @DateTimeOffset@ or @Time@ or time in milliseconds since the epoch for any messages retrieved
__Type: @Long@@Int or @Time@@DateTimeOffset@__ +- direction:directionDirection := _backwards_ @:@@forwards@ or @:@@backwards@
__Type: @String@@Symbol@@Direction@ enum__ +- limit:limitLimit := _100_ maximum number of messages to retrieve up to 1,000
__Type: @Integer@__ + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @resultPage@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + On failure to retrieve message history, @err@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +blang[java,ruby,php,python]. + h4. Returns + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + Failure to retrieve the message history will raise an "@AblyException@":/rest/types/#ably-exception + +blang[go]. + h4. Returns + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + Failure to retrieve the message history will mean @error@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +blang[csharp]. + h4. Returns + + This method returns a @Task@ and needs to be awaited. + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@Message@":#message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@NextAsync@":#paginated-result and "@FirstAsync@":#paginated-result methods. + + Failure to retrieve the message history will raise an "@AblyException@":/rest/types/#ably-exception + +h2. Presence object + +"Rest @Presence@ object":/rest/presence exposes the following public method to obtain presence event history such as enter, update and leave events. These events are represented as "@PresenceMessage@":#presence-message objects. + +h3. Methods + +h6(#presence-history). + default: history + csharp,go: History + +bq(definition). + default: history(Object option, callback("ErrorInfo":/rest/types#error-info err, "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> resultPage)) + ruby: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> history(Hash option) + python: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> history(kwargs_options) + php: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> history(Array option) + java: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> history("Param":#param[] option) + csharp: Task<"PaginatedResult":#paginated-result<"PresenceMessage":#presence-message>> HistoryAsync("PaginatedRequestParams":#data-request query); + objc,swift: history(query: ARTDataQuery?, callback: ("ARTPaginatedResult":#paginated-result<"ARTPresenceMessage":#presence-message>?, ARTErrorInfo?) -> Void) throws + go: (p *RestPresence) History(option *PaginateParams) (*PaginatedResult, error) + +Gets a "paginated":#paginated-result set of historical presence events for this channel. + +h4. Parameters + +- option"Param":#param[] option"PaginatedRequestParams":#paginated-request-params query := an optional object containing the query parametersoptional keyword arguments containing the query parametersan optional set of key value pairs containing the query parametersan Associate Array containing the query parameters, as specified below. + +-
callback
:= is a function of the form: @function(err, resultPage)@ +-
callback
:= called with a "ARTPaginatedResult":#paginated-result<"ARTPresenceMessage":#presence-message> object or an error + +h4. @options@ parameters@ARTDataQuery@ properties"@PaginatedRequestParams@":#paginated-request-params properties + +- start:startStart := _beginning of time_ earliest @DateTimeOffset@ or @Time@ or time in milliseconds since the epoch for any presence events retrieved
__Type: @Long@@Int or @Time@@DateTimeOffset@__ +- end:endEnd := _current time_ latest @DateTimeOffset@ or @Time@ or time in milliseconds since the epoch for any presence events retrieved
__Type: @Long@@Int or @Time@@DateTimeOffset@__ +- direction:directionDirection := _backwards_ @:@@forwards@ or @:@@backwards@
__Type: @String@@Symbol@@Direction@ enum__ +- limit:limitLimit := _100_ maximum number of presence events to retrieve up to 1,000
__Type: @Integer@__ + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @resultPage@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + On failure to retrieve presence event history, @err@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +blang[java,ruby,php,python]. + h4. Returns + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + Failure to retrieve the presence event history will raise an "@AblyException@":/rest/types/#ably-exception + +blang[go]. + h4. Returns + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@":#paginated-result and "@first@":#paginated-result methods. + + Failure to retrieve the message history will mean @error@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +blang[csharp]. + h4. Returns + + This method returns a @Task@ and needs to be awaited. + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@NextAsync@":#paginated-result and "@FirstAsync@":#paginated-result methods. + + Failure to retrieve the presence event history will raise an "@AblyException@":/rest/types/#ably-exception + +h2(#related-types). Related types + +h3(#message). + default: Message + swift,objc: ARTMessage + ruby: Ably::Models::Message Enum + java: io.ably.lib.types.Message + csharp: IO.Ably.Message + +<%= partial partial_version('types/_message') %> + +h3(#presence-message). + default: PresenceMessage + swift,objc: ARTPresenceMessage + ruby: Ably::Models::PresenceMessage Enum + java: io.ably.lib.types.PresenceMessage + csharp: IO.Ably.PresenceMessage + +<%= partial partial_version('types/_presence_message') %> + +h3(#presence-action). + default: Presence action + python: PresenceAction + swift,objc: ARTPresenceAction + java: io.ably.lib.types.PresenceMessage.Action + ruby: Ably::Models::PresenceMessage::ACTION + csharp: IO.Ably.PresenceAction + +<%= partial partial_version('types/_presence_action') %> + +h3(#paginated-request-params). + csharp: IO.Ably.PaginatedRequestParams + +blang[csharp]. + <%= partial partial_version('types/_history_request_params'), indent: 2, skip_first_indent: true %> + +h3(#paginated-result). + default: PaginatedResult + swift,objc: ARTPaginatedResult + ruby: Ably::Models::PaginatedResult + java: io.ably.lib.types.PaginatedResult + csharp: IO.Ably.PaginatedResult + +<%= partial partial_version('types/_paginated_result') %> + +h3(#param). + java: io.ably.lib.types.Param + +blang[java]. + <%= partial partial_version('types/_param'), indent: 2, skip_first_indent: true %> diff --git a/content/rest/versions/v1.1/index.textile b/content/rest/versions/v1.1/index.textile new file mode 100644 index 0000000000..35a0d4a32f --- /dev/null +++ b/content/rest/versions/v1.1/index.textile @@ -0,0 +1,76 @@ +--- +title: REST Client Library API +section: rest +index: 0 +--- + +The Ably REST client libraries offer a simple stateless API to interact directly with "Ably's REST API":/rest-api/. All official client library APIs are consistent across every language offering "message publishing":/rest/messages and "message history":/rest/history, "presence state":/rest/presence and "historical event retrieval":/rest/history, "token generation for clients":/rest/authentication and "authentication":/rest/authentication, "intelligent datacenter routing":https://support.ably.io/solution/articles/3000044636-routing-around-network-and-dns-issues, "symmetric encryption":/rest/encryption and access to "realtime metrics":/rest/statistics. + +The Ably REST client library is available in "most popular languages and platforms":https://www.ably.io/download including "Javascript":https://github.com/ably/ably-js, "iOS":https://github.com/ably/ably-cocoa, "Android":https://github.com/ably/ably-java, "Java":https://github.com/ably/ably-java, ".NET":https://github.com/ably/ably-dotnet, "Node.js":https://github.com/ably/ably-js, "Python":https://github.com/ably/ably-python, "Ruby":https://github.com/ably/ably-ruby-rest, "PHP":https://github.com/ably/ably-php, "Go":https://github.com/ably/ably-go and "more...":https://www.ably.io/download + +"Download one of our REST client libraries now »":https://www.ably.io/download + +h2(#intro). Quick intro to the REST library + +The REST library communicates with the Ably service using the HTTP protocol for all operations and is effectively stateless. A stateless library is often preferred server-side as it allows communication to consist of independent pairs of "request and response":https://en.wikipedia.org/wiki/Request%E2%80%93response without the need to retain session information or any in-flight request state. Whilst Ably's REST libraries cannot subscribe to data published in real time, they can publish and retrieve realtime data. + +Ably organizes realtime data (messages) within applications into named channels that are the "unit" of distribution. Every message published on a channel by a REST or Realtime client is broadcast by Ably to all realtime subscribers on that channel. This scalable and resilient messaging pattern is commonly called "pub/sub":https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern. + +Data published on a channel is packaged as a message by the library before being sent to the Ably service. The message can contain, in addition to a binary, string or JSON payload, an event @name@ and additional metadata in the @extras@ field. + +Token authentication is the recommended authentication scheme for client-side devices because tokens are short-lived, their privileges are configured when issued and secret keys are never shared. Typically tokens are issued by your servers to clients that authenticate with your servers first. + +These concepts are illustrated in the diagram below: + + + REST client diagram + + +An Ably REST client library is responsible for: + +- Channel requests := Providing "publish and history":/realtime/messages functionality on channels. +- Presence requests := Providing "channel presence state and presence history retrieval":/realtime/presence on channels. +- 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. +- Tokens := "Issuing either tokens or token requests":/realtime/encryption with appropriate privileges to authenticated or anonymous clients. +- Intelligent routing := Ensuring that "requests are automatically serviced by an available datacenter":https://support.ably.io/solution/articles/3000044636-routing-around-network-and-dns-issues. +- 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(#rest-vs-realtime). When to use REST vs Realtime libraries + +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. + +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. + +h4(#other-libraries). 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":/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 Webhooks":/general/events and "Reactor Functions":/general/events. +* If you want to use another realtime protocol such as "MQTT":/concepts/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 documentation API is structured as follows: + +* "Constructor & usage examples":/rest/usage +* "Channels":/rest/channels +* "Messages":/rest/messages +* "Presence":/rest/presence +* "Authentication":/rest/authentication +* "History":/rest/history +* "Encryption":/rest/encryption +* "Statistics":/rest/statistics +* "Types":/rest/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. +"View the Ably tutorials »":/tutorials + diff --git a/content/rest/versions/v1.1/messages.textile b/content/rest/versions/v1.1/messages.textile new file mode 100644 index 0000000000..98b0982218 --- /dev/null +++ b/content/rest/versions/v1.1/messages.textile @@ -0,0 +1,383 @@ +--- +title: Messages +section: rest +index: 24 +languages: + - javascript + - nodejs + - php + - python + - ruby + - java + - swift + - objc + - csharp + - go +api_separator: +jump_to: + Help with: + - Getting started#getting-started + - Channels#channels + - Channel namespaces + - Subscribing to messages#message-subscription + - Publishing messages#message-publish + - Retrieving message history#message-history + Channel API properties: + - name + - presence + Channel API methods: + - publish + - history + Types: + - Related Types#related-types +--- + +Ably DSN allows for clients to send information with @messages@, which contain data the client wishes to communicate. These messages are "published":#message-publish through "channels":/rest/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 REST client library provides a straightforward API for "publishing":/rest/channels#publish messages and retrieving "message history":/rest/channels#history from a "channel":/rest/channels. + +```[javascript](code-editor:rest/channel-history) + var rest = new Ably.Rest('{{API_KEY}}'); + var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.publish('example', 'message data', function() { + channel.history(function(err, resultPage) { + console.log('Last published message:' + resultPage.items[0]); + }); + }); +``` + +```[nodejs](code-editor:rest/channel-history) + var rest = new Ably.Rest('{{API_KEY}}'); + var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}'); + channel.publish('example', 'message data', function() { + channel.history(function(err, resultPage) { + console.log('Last published message:' + resultPage.items[0]); + }); + }); +``` + +```[ruby] + rest = Ably::Rest.new('{{API_KEY}}') + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}') + channel.publish 'example', 'message data' + result_page = channel.history() + puts "Last published message: #{result_page.items.first}" +``` + +```[python] + rest = AblyRest('{{API_KEY}}') + channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}') + channel.publish(u'example', u'message data') + result_page = channel.history() + print("Last published message data: " + result_page.items[0].data) +``` + +```[php] + $rest = new Ably\AblyRest('{{API_KEY}}'); + $channel = $rest->channels->get('{{RANDOM_CHANNEL_NAME}}'); + $channel->publish('example', 'message data'); + $resultPage = $channel->history(); + echo("Last published data: " . $resultPage->items[0]->data); +``` + +```[java] + AblyRest rest = new AblyRest("{{API_KEY}}"); + Channel channel = rest.channels.get("{{RANDOM_CHANNEL_NAME}}"); + channel.publish("example", "message data"); + PaginatedResult resultPage = channel.history(null); + System.out.println("Last published message ID: " + resultPage.items[0].id); +``` + +```[csharp] + AblyRest rest = new AblyRest("{{API_KEY}}"); + var channel = rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}"); + await channel.PublishAsync("example", "message data"); + PaginatedResult resultPage = await channel.HistoryAsync(); + Console.WriteLine("Last published message ID: " + resultPage.Items[0].id); +``` + +```[objc] + ARTRest *rest = [[ARTRest alloc] initWithKey:@"{{API_KEY}}"]; + ARTRestChannel *channel = [rest.channels get:@"{{RANDOM_CHANNEL_NAME}}"]; + [channel publish:@"example" data:@"message data"]; + [channel history:^(ARTPaginatedResult *resultPage, ARTErrorInfo *error) { + NSLog(@"Last published message ID: %@", resultPage.items[0].id); + }]; +``` + +```[go] + rest, err := ably.NewRestClient(ably.NewClientOptions("{{API_KEY}}")) + channel := rest.Channels.Get("{{RANDOM_CHANNEL_NAME}}", nil) + err = channel.Publish("example", "message data") + page, err := channel.History(nil) + fmt.Println("Last published message: %s\n", page.Messages[0].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 "REST 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, plain c# objects which are converted to json, 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 REST client library does not offer message realtime subscription but instead provides access to the "live" history using the "REST history API":/rest/history. Find out more about "subscribing to messages in realtime using the Realtime API":/realtime/messages#message-subscription. + +The @name@ propertymemberattribute of published messages does not affect the distribution of a channel message to clients but may be used as a subscription filter, allowing a client to register a listener that only sees a subset of the messages received on the channel. Find out more about "registering listeners using the Realtime API":/realtime/channels#subscribe. + +h3(#message-publish). Publishing messages + +Channels expose a @publish@ method whereby a client can publish either a single message or an array of messages to a channel over "REST":/rest-api#publish. + +bc[jsall](code-editor:rest/channel-publish). channel.publish('event', 'This is my payload', function(err) { + if(err) { + console.log('Unable to publish message; err = ' + err.message); + } else { + console.log('Message successfully sent'); + } +}); + +bc[ruby]. channel.publish('event', 'This is my payload') + +bc[python]. channel.publish(u'event', u'This is my payload') + +bc[php]. $channel->publish('event', 'This is my payload'); + +bc[java]. channel.publish("event", "This is my payload"); + +bc[csharp]. channel.PublishAsync("event", "This is my payload"); + +bc[objc]. [channel publish:@"event" data:@"This is my payload"]; + +bc[swift]. channel.publish("event", data: "This is my payload") + +bc[go]. channel.Publish("event", "This is my payload") + +h4(#batch-publish). Batch publishing + +It is common for a single message to be intended for multiple channels. If you wish to send a message to multiple channels within a single operation, you can make use of the "REST batch API":/rest-api/beta#batch. With a "realtime":/realtime connection, you can also effectively send a message to multiple channels at once by allowing multiple concurrent publish operations. + +h4(#idempotent). Enabling idempotent publishing + +Idempotency ensures that multiple publishes of the same message cannot result in duplicate messages. "Find out more about what idempotency is, and how we provide idempotency in our REST operations.":https://www.ably.io/concepts/idempotency + +It is possible for a client publishing through REST to not receive an acknowledgement of receipt from Ably for numerous reasons such as network failures outside of our control. This can lead to the client automatically re-publishing the message, which is a "feature of our SDKs to help route around network failures":https://ably.freshdesk.com/support/solutions/articles/3000044636-routing-around-network-and-dns-issues. We recommend that you "enable idempotent publishing":/rest/usage#client-options in our REST SDKs if you want to avoid these retry attempts potentially resulting in duplicate messages. When enabled, the REST SDK automatically assigns a "unique ID":#id to each message ensuring subsequent retries cannot result in duplicates. + +If you provide a "unique ID":#id with each message published, all publishes will be idempotent - that is you can freely publish the message from one or more workers without worrying about duplicates. Please note that publishing multiple messages in a single publish operation with client-specified "ids":#id "has many constraints you need to be aware of":https://support.ably.io/solution/articles/3000088357-how-can-i-publish-multiple-messages-in-a-single-rest-request-with-idempotency. + +Below is an example of how to provide a unique ID: + +```[javascript] +var rest = new Ably.Rest('{{API_KEY}}'); +var channel = rest.channels.get('{{RANDOM_CHANNEL_NAME}}'); +channel.publish([{data: 'payload', id: 'unique123'}]); +``` + +h4(#publish-on-behalf). Publishing on behalf of realtime connection + +Message published using the REST API may be done so on behalf of an existing realtime connection when a valid @connectionKey@ is present in the published message. For example, if you want to publish a message using the REST client library so that it appears to come from an existing connected realtime client, then the connection's "private (secret) connection key":/realtime/connection#key must be included. See a "publish on behalf of a realtime client example":<%= JsBins.url_for('rest/publish-on-behalf-of-client') %>. + +If the @connectionKey@ is invalid or belongs to a connection that has since been closed, then the publish operation will fail. + +h3(#message-history). Retrieving message history + +Channels expose a "@history@@History@":/rest/channels#history method providing a means for clients to obtain messages previously sent on the channel. Channel history can be used to return continuous message history up to the exact point a realtime channel was attached. + +History provides access to instantaneous "live" history as well as the longer term persisted history for attached channels. If "persisted history":/rest/history#persisted-history is enabled for the channel, then messages will "typically be stored for 24 - 72 hours":https://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for. If persisted history is not enabled, Ably retains the last two minutes of message history in memory. + +The following example retrieves the first two pages of historical messages published up until the point the channel was attached. + +bc[jsall]. channel.history(function(err, resultPage) { + if(err) { + console.log('Unable to get channel history; err = ' + err.message); + } else { + console.log(resultPage.items.length + ' messages received in first page'); + if(resultPage.hasNext()) { + resultPage.next(function(err, nextPage) { ... }); + } + } +}); + +bc[java]. PaginatedResult resultPage = channel.history(null); +System.out.println(resultPage.items().length + " messages received in first page"); +if(resultPage.hasNext()) { + PaginatedResult nextPage = resultPage.next(); + System.out.println(nextPage.items().length + " messages received in second page"); +} + +bc[csharp]. PaginatedResult resultPage = await channel.HistoryAsync(); +Console.WriteLine(resultPage.Items.Count + " messages received in first page"); +if(resultPage.HasNext) { + PaginatedResult nextPage = await resultPage.NextAsync(); + Console.WriteLine(nextPage.Items.Count + " messages received in second page"); +} + +bc[ruby]. result_page = channel.history +puts "#{result_page.items.length} messages received in first page" +if result_page.has_next? + next_page = result_page.next + puts "#{next_page.items.length} messages received in second page" +end + +bc[python]. result_page = channel.history() +print str(len(result_page.items)) + ' messages received in first page' +if result_page.has_next(): + next_page = result_page.next() + print str(len(next_page.items)) + ' messages received in second page' + +bc[php]. $resultPage = channel->history(); +echo(count($resultPage->items) . 'messages received in first page'); +if($resultPage->hasNext()) { + $nextPage = $resultPage.next(); + echo(count($resultPage->items) . 'messages received in second page'); +} + +bc[objc]. [channel history:^(ARTPaginatedResult *resultPage, ARTErrorInfo *error) { + NSLog(@"%lu messages received in first page", (unsigned long)[resultPage.items count]); + if (resultPage.hasNext) { + [resultPage next:^(ARTPaginatedResult *nextPage, ARTErrorInfo *error) { + // ... + }]; + } +}]; + +bc[swift]. channel.history { resultPage, error in + let resultPage = resultPage! + print("\(resultPage.items.count) messages received in first page") + if resultPage.hasNext { + resultPage.next { nextPage, error in + // ... + } + } +} + +bc[go]. page, err := channel.History(nil) +fmt.Println("%d messages received in first page\n", len(page.Messages())) +if page.hasNext() { + page2, err := page.Next() + //... +} + + + +See the "history documentation":/rest/history for further details of the supported query parameters. + +h2. Presence + +Channels expose a @presence@@Presence@ member which a client can use to obtain present members and presence event history for the channel itself. See the "REST presence":/rest/presence documentation for details. + +h1. Message API Reference + +inline-toc. + Properties: + - name#name + - data#data + - id#id + - clientId#client-id + - connectionId#connection-id + - timestamp#timestamp + - encoding#encoding + Methods: + - fromEncoded#message-from-encoded + - fromEncodedArray#message-from-encoded-array + +h2(#properties). + default: Properties + java: Members + ruby: Attributes + python: Attributes + +A @Message@ represents an individual message that is sent to or received from Ably. + +h6(#name). + default: name + csharp,go: Name + +The event name, if provided.
__Type: @String@__ + +h6(#data). + default: data + csharp,go: Data + +The message payload, if provided.
__Type: @String@, @StringBuffer@, @JSON Object@@String@, @ByteArray@, @JSONObject@, @JSONArray@@String@, @byte[]@, @plain C# object that can be serialized to Json@@String@, @Binary@ (ASCII-8BIT String), @Hash@, @Array@@String@, @Bytearray@, @Dict@, @List@@String@, @Binary String@, @Associative Array@, @Array@@NSString *@, @NSData *@, @NSDictionary *@, @NSArray *@@String@, @NSData@, @Dictionary@, @Array@__ + +h6(#extras). + default: extras + csharp: Extras + +Metadata and/or ancillary payloads, if provided. The only currently valid payload for extras is the "@push@":/general/push/publish#channel-broadcast-example object.
__Type: @JSONObject@, @JSONArray@plain C# object that can be converted to Json@JSON Object@@Hash@, @Array@@Dict@, @List@@Dictionary@, @Array@@NSDictionary *@, @NSArray *@@Associative Array@, @Array@__ + +h6(#id). + default: id + csharp,go: Id + +A Unique ID assigned by Ably to this message. Can optionally be assigned by the client as part of "idempotent publishing":#idempotent.
__Type: @String@__ + +h6(#client-id). + default: clientId + csharp,go: ClientId + ruby: client_id + python: client_id + +The client ID of the publisher of this message.
__Type: @String@__ + +h6(#connection-id). + default: connectionId + csharp,go: ConnectionId + ruby: connection_id + python: connection_id + +The connection ID of the publisher of this message.
__Type: @String@__ + +h6(#timestamp). + default: timestamp + csharp,go: Timestamp + +Timestamp when the message was received by the Ably, as milliseconds since the epocha @Time@ object
.__Type: @Integer@@Long Integer@@DateTimeOffset@@Time@@NSDate@__ + +h6(#encoding). + default: encoding + csharp,go: Encoding + +This will typically be empty as all messages received from Ably are automatically decoded client-side using this value. However, if the message encoding cannot be processed, this attribute will contain the remaining transformations not applied to the @data@ payload.
__Type: @String@__ + +h2(methods). + default: Message methods + +h6(#message-from-encoded). + default: Message.fromEncoded + +bq(definition). + default: Message.fromEncoded(Object encodedMsg, ChannelOptions channelOptions?) -> Message + +A static factory method to create a "@Message@":/rest/types#message from a deserialized @Message@-like object encoded using Ably's wire protocol. + +h4. Parameters + +- encodedMsg := a @Message@-like deserialized object.
__Type: @Object@__ +- channelOptions := an optional "@ChannelOptions@":/rest/types#channel-options. If you have an encrypted channel, use this to allow the library can decrypt the data.
__Type: @Object@__ + +h4. Returns + +A "@Message@":/rest/types#message object + +h6(#message-from-encoded-array). + default: Message.fromEncodedArray + +bq(definition). + default: Message.fromEncodedArray(Object[] encodedMsgs, ChannelOptions channelOptions?) -> Message[] + +A static factory method to create an array of "@Messages@":/rest/types#message from an array of deserialized @Message@-like object encoded using Ably's wire protocol. + +h4. Parameters + +- encodedMsgs := an array of @Message@-like deserialized objects.
__Type: @Array@__ +- channelOptions := an optional "@ChannelOptions@":/rest/types#channel-options. If you have an encrypted channel, use this to allow the library can decrypt the data.
__Type: @Object@__ + +h4. Returns + +An @Array@ of "@Message@":/rest/types#message objects diff --git a/content/rest/versions/v1.1/presence.textile b/content/rest/versions/v1.1/presence.textile new file mode 100644 index 0000000000..d58f356da4 --- /dev/null +++ b/content/rest/versions/v1.1/presence.textile @@ -0,0 +1,392 @@ +--- +title: Presence +section: rest +index: 40 +languages: + - javascript + - nodejs + - php + - python + - ruby + - java + - swift + - objc + - csharp + - go +api_separator: +jump_to: + Help with: + - Presence + - Member data + - Presence member list#member-list + - Presence history + Presence API reference: + - get + - history + Types: + - Related Types#related-types +--- + +Presence enables clients to be aware of other clients that are currently "present" on a channel. Each member present on a channel has a unique self-assigned client identifier and system-assigned connection identifier, along with an optional payload that can be used to describe the member's status or attributes. Presence allows you to quickly build apps such as chat rooms and multiplayer games by automatically keeping track of who is present in real time across any device. + +h2(#presence). Presence + +Using the "Ably REST API":/rest-api it is possible to obtain the set of members currently present on a channel, or obtain the presence history for the channel, if persistence is enabled for that channel. + +Since the "Ably REST API":/rest-api is stateless, and REST clients do not have realtime connections to the Ably service, it is not possible to enter or leave a channel via the REST API. Find out more about "using presence with the Realtime API":/realtime/presence. + +A single "@clientId@":/rest/usage#client-id"@client_id@":/rest/usage#client-id"@ClientId@":/rest/usage#client-id may be present multiple times on the same channel via different client connections. As far as Ably is concerned, these are different members of the presence set for the channel, however they will be differentiated by their unique "@connectionId@@ConnectionId@@connection_id@":/realtime/connection/#id. For example, if a client with ID "Sarah" is connected to a chat channel on both a desktop and a mobile device simultaneously, "Sarah" will be present twice in the presence member set with the same client ID, yet will have two unique connection IDs. A member of the presence set is therefore unique by the combination of the "@clientId@":/rest/usage#client-id"@client_id@":/rest/usage#client-id"@ClientId@":/rest/usage#client-id and "@connectionId@@ConnectionId@@connection_id@":/realtime/connection/#id strings. + +If you would prefer to just dive into code and see some examples of how to use presence via the REST API, then we recommend you take a look at our "REST tutorials":/tutorials. + +h3(#presence-states). Presence states and events + +<%= partial partial_version('shared/_presence_states') %> + +"View a realtime presence states and events example":<%= JsBins.url_for('realtime/presence-events') %> + +h3(#member-data). Member data + +In addition to the "@clientId@":/rest/usage#client-id"@client_id@":/rest/usage#client-id"@ClientId@":/rest/usage#client-id for members on a channel, it is also possible to include data when entering a channel. Clients can "update":/realtime/presence#update their data at any point which will be broadcasted to all presence subscribers as a @:update@@PresenceAction.UPDATE@@PresenceAction.Update@@Action.UPDATE@@update@ event. + +See the "Realtime Presence Member data documentation":/realtime/presence#member-data for more info. + +h3(#member-list). Presence member list + +The @Presence@ object exposes a "@get@@Get@":#get method allowing a client to retrieve an array of all members currently present on the channel. In the REST client library this method directly queries "Ably's REST presence API":/rest-api#presence. No presence state is cached in the library itself, unlike in the "Realtime client library":/realtime. + +```[jsall] +channel.presence.get(function(err, membersPage) { + console.log(membersPage.items.length + ' presence members in first page'); + if(membersPage.hasNext()) { + membersPage.next(function(err, nextPage) { ... }); + } +}); +``` + +```[ruby] +members_page = channel.presence.get +puts "#{members_page.items.length} presence members in first page" +if members_page.has_next? + next_page = members_page.next +end +``` + +```[python] +members_page = channel.presence.get() +print str(len(members_page.items)) + ' members present' +if members_page.has_next(): + next_page = members_page.next() +``` + +```[php] +$membersPage = $channel->presence->get(); +echo(count($membersPage->items) . ' presence members in first page'); +if ($membersPage->hasNext()) { + $nextPage = $membersPage.next(); +} +``` + +```[java] +PaginatedResult membersPage = channel.presence.get(null); +System.out.println(membersPage.items().length + " members in first page"); +if(membersPage.hasNext()) { + PaginatedResult nextPage = membersPage.next(); + System.out.println(nextPage.items().length + " members on 2nd page"); +} +``` + +```[csharp] + PaginatedResult membersPage = await channel.Presence.GetAsync(); + Console.WriteLine(membersPage.Items.Count + " members in first page"); + if(membersPage.HasNext) + { + PaginatedResult nextPage = await membersPage.NextAsync(); + Console.WriteLine(nextPage.Items.Count + " members on 2nd page"); + } +``` + +```[objc] +[channel.presence get:^(ARTPaginatedResult *membersPage, ARTErrorInfo *error) { + NSLog(@"%lu members in first page", [membersPage.items count]); + if (membersPage.hasNext) { + [membersPage next:^(ARTPaginatedResult *nextPage, ARTErrorInfo *error) { + NSLog(@"%lu members on 2nd page", [nextPage.items count]); + }]; + } +}]; +``` + +```[swift] +channel.presence.get { membersPage, error in + let membersPage = membersPage! + print("\(membersPage.items.count) in first page") + if membersPage.hasNext { + membersPage.next { nextPage, error in + print("\(nextPage!.items.count) members on 2nd page") + } + } +} +``` + +```[go] +page, err := channel.Presence.Get(nil) +fmt.Println("%d messages in first page\n", len(page.PresenceMessages())) +if page.hasNext() { + page2, err := page.Next() + fmt.Println("%d messages on 2nd page!\n", len(page2.PresenceMessages())) +} +``` + +h4(#batch-publish). Batch presence + +It is common for the presence of multiple channels to be needed. If you wish to obtain the presence of multiple channels within a single operation, you can make use of the "REST batch API":/rest-api/beta#batch. + +h3(#presence-history). Presence History + +The @Presence@ object exposes a "@history@@History@":#history method allowing a client to retrieve historical presence events on the channel. + +History provides access to instantaneous "live" history as well as the longer term persisted history for presence channels. If "persisted history":/realtime/history#persisted-history is enabled for the channel, then presence events will "typically be stored for 24 - 72 hours":https://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for. If persisted history is not enabled, Ably retains the last two minutes of presence event history in memory. + +The following example retrieves the first two pages of historical presence events published. + +bc[jsall]. var presence = channel.presence; +presence.history(function(err, eventsPage) { + if(err) { + console.log('Unable to get presence history; err = ' + err.message); + } else { + console.log(eventsPage.items.length + ' presence events received in first page'); + if(eventsPage.hasNext()) { + eventsPage.next(function(err, nextPage) { ... }); + } +}); + +bc[ruby]. events_page = channel.presence.history +puts "#{events_page.items.length} presence events received in first page" +if events_page.has_next? + next_page = events_page.next + puts "#{next_page.items.length} presence events received on second page" +end + +bc[php]. $eventsPage = $channel->presence->history(); +echo(count($eventsPage->items) . ' presence events received in first page'); +if ($eventsPage.hasNext()) { + $nextPage = $eventsPage->next(); + echo(count($nextPage->items) . ' presence events received in second page'); +} + +```[python] +events_page = channel.presence.history() +print str(len(events_page.items)) + " presence events received" +if events_page.has_next(): + next_page = events_page.next() +``` + +```[java] +PaginatedResult eventsPage = channel.presence.history(null); +System.out.println(eventsPage.items().length + " presence events received in first page"); +if(eventsPage.hasNext()) { + PaginatedResult nextPage = eventsPage.next(); + System.out.println(nextPage.items().length + " presence events received in 2nd page"); +} +``` + +```[csharp] + PaginatedResult eventsPage = await channel.Presence.HistoryAsync(); + Console.WriteLine(eventsPage.Items.Count + " presence events received in first page"); + if (eventsPage.HasNext) + { + PaginatedResult nextPage = await eventsPage.NextAsync(); + Console.WriteLine(nextPage.Items.Count + " presence events received in 2nd page"); + } +``` + +```[objc] +[channel.presence history:^(ARTPaginatedResult *eventsPage, ARTErrorInfo *error) { + NSLog(@"%lu presence events received in first page", [eventsPage.items count]); + if (eventsPage.hasNext) { + [eventsPage next:^(ARTPaginatedResult *nextPage, ARTErrorInfo *error) { + NSLog(@"%lu presence events received in 2nd page", [nextPage.items count]); + }]; + } +}]; +``` + +```[swift] +channel.presence.history { eventsPage, error in + let eventsPage = eventsPage! + print("\(eventsPage.items.count) presence events received in first page") + if eventsPage.hasNext { + eventsPage.next { nextPage, error in + print("\(nextPage!.items.count) presence events received in 2nd page") + } + } +} +``` + +```[go] +page, err := channel.Presence.History(nil) +fmt.Println("%d messages in first page\n", len(page.PresenceMessages())) +if page.hasNext() { + page2, err := page.Next() + fmt.Println("%d messages on 2nd page!\n", len(page2.PresenceMessages())) +} +``` + +See the "presence history documentation":/rest/history#presence-history for further details of the supported query parameters. + +h1. API Reference + +inline-toc. + Methods: + - get(options)#get + - history(options)#history + Related types: + - PresenceMessage#presence-message + - Presence action + - PaginatedResult#paginated-result + - Param + +h2. Methods + +h6(#get). + default: get + csharp,go: Get + +Get the current presence member set for this channel. In the REST client library this method directly queries "Ably's REST presence API":/rest-api#presence + +bq(definition). + default: get(Object options, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> resultPage)) + ruby: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> get(Hash options) + python: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> get(kwargs_options) + php: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> get(Array options) + java: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> get("Param":#param[] options) + csharp: Task<"PaginatedResult":#paginated-result<"PresenceMessage":#presence-message>> GetAsync(int? limit = null, string clientId = null, string connectionId = null); + swift,objc: get(query: ARTPresenceQuery, callback: (["ARTPresenceMessage":#presence-message], ARTErrorInfo?) -> Void) + go: (p *RestPresence) Get(options *PaginateParams) (*"PaginatedResult":#paginated-result, error) + +Gets an array of members present on the channel as "PresenceMessage":#presence-message objects. + +blang[jsall,ruby,python,php,java,swift,objc,go]. + h4. Parameters + + - optionsquery"Param":#param[] options := an optional object containing query parametersoptional keyword arguments containing the query parametersan optional set of symbol key and value pairs containing the query parametersan optional Associate Array containing the query parameters as specified below. + + -
callback
:= is a function of the form: @function(err, "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> resultPage)@ + -
callback
:= called with a "@PaginatedResult@":#paginated-result@<@"@PresenceMessage@":#presence-message@>@, or with an error + +h4. @options@ parameters@ARTPresenceQuery@ propertiesArguments + +- clientIdclient_id:client_id := when provided, will filter array of members returned that match the provided "@clientId@":/rest/usage#client-id"@client_id@":/rest/usage#client-id"@ClientId@":/rest/usage#client-id string +- connectionIdconnection_id:connection_id := when provided, will filter array of members returned that match the provided @connectionId@@ConnectionId@@connection_id@":/realtime/connection/#id string + +blang[jsall]. + h4. Callback result + + On success, @resultPage@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of members currently present on the channel. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + On failure to retrieve the current presence member set, @err@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +blang[java,ruby,php,python,csharp]. + h4. Returns + + This method is asynchronous and needs to be awaited. + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of members currently present on the channel. "@PaginatedResult@":#paginated-result supports pagination using "@next@@NextAsync@":#paginated-result and "@first@@FirstAsync@":#paginated-result methods. + + Failure to retrieve the current presence member set will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[java,ruby,php,python,csharp]. + h4. Returns + + This method is asynchronous and needs to be awaited. + + On success, @resultPage@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of members currently present on the channel. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + Failure to retrieve the current presence member, the @error@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +h6(#history). History + +bq(definition). + default: history(Object options, callback("ErrorInfo":/rest/types#error-info err, "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> resultPage)) + ruby: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> history(Hash options) + python: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> history(kwargs_options) + php: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> history(Array options) + java: "PaginatedResult":#paginated-result<"PresenceMessage":#presence-message> history("Param":#param[] options) + csharp: Task<"PaginatedResult":#paginated-result<"PresenceMessage":#presence-message>> HistoryAsync("PaginatedRequestParams":#paginated-request-params query); + objc,swift: history(query: ARTDataQuery?, callback: ("ARTPaginatedResult":#paginated-result<"ARTPresenceMessage":#presence-message>?, ARTErrorInfo?) -> Void) throws + go: (p *RestPresence) History(params *PaginateParams) (*PaginatedResult, error) + +Gets a "paginated":#paginated-result set of historical presence message events for this channel. If the "channel is configured to persist messages to disk":https://support.ably.io/support/solutions/articles/3000030059-how-long-are-messages-stored-for, then the presence message event history will "typically be available for 24 - 72 hours":https://support.ably.io/solution/articles/3000030059-how-long-are-messages-stored-for. If not, presence message events are only retained in memory by the Ably service for two minutes. + +h4. Parameters + +- options"Param":#param[] optionsquery"PaginatedRequestParams":#paginated-request-params query := an optional object containing query parametersoptional keyword arguments containing the query parametersan optional set of key value pairs containing query parametersan optional Associate Array containing the query parameters, as specified in the "presence history API documentation":/rest/history#presence-history. + +-
callback
:= is a function of the form: @function(err, resultPage)@ +-
callback
:= called with a "ARTPaginatedResult":#paginated-result<"ARTPresenceMessage":#presence-message> object or an error + +blang[jsall,swift,objc]. + h4. Callback result + + On success, @resultPage@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + On failure to retrieve message history, @err@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +blang[java,ruby,php,python,csharp]. + h4. Returns + + This method is asynchronous and needs to be awaited. + + On success, the returned "@PaginatedResult@":#paginated-result encapsulates an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next@@NextAsync@":#paginated-result and "@first@@FirstAsync@":#paginated-result methods. + + Failure to retrieve the message history will raise an "@AblyException@":/realtime/types/#ably-exception + +blang[go]. + h4. Returns + + On success, @resultPage@ contains a "@PaginatedResult@":#paginated-result encapsulating an array of "@PresenceMessage@":#presence-message objects corresponding to the current page of results. "@PaginatedResult@":#paginated-result supports pagination using "@next()@":#paginated-result and "@first()@":#paginated-result methods. + + Upon failure to retrieve the message history, the @error@ contains an "@ErrorInfo@":#error-info object with the failure reason. + +h2(#related-types). Related types + +h3(#presence-message). + default: PresenceMessage + swift,objc: ARTPresenceMessage + ruby: Ably::Models::PresenceMessage + java: io.ably.lib.types.PresenceMessage + csharp: IO.Ably.PresenceMessage + +<%= partial partial_version('types/_presence_message') %> + +h3(#presence-action). + default: Presence action + python: PresenceAction + swift,objc: ARTPresenceAction + java: io.ably.lib.types.PresenceMessage.Action + ruby: Ably::Models::PresenceMessage::ACTION + csharp: IO.Ably.PresenceAction + +<%= partial partial_version('types/_presence_action') %> + +h3(#paginated-request-params). + csharp: PaginatedRequestParams + +blang[csharp]. + <%= partial partial_version('types/_history_request_params'), indent: 2, skip_first_indent: true %> + +h3(#paginated-result). + default: PaginatedResult + swift,objc: ARTPaginatedResult + ruby: Ably::Models::PaginatedResult + java: io.ably.lib.types.PaginatedResult + csharp: IO.Ably.PaginatedResult + +<%= partial partial_version('types/_paginated_result') %> + +h3(#param). + java: io.ably.lib.types.Param + +blang[java]. + <%= partial partial_version('types/_param'), indent: 2, skip_first_indent: true %> diff --git a/content/rest/versions/v1.1/push.textile b/content/rest/versions/v1.1/push.textile new file mode 100644 index 0000000000..12b52e97a0 --- /dev/null +++ b/content/rest/versions/v1.1/push.textile @@ -0,0 +1,84 @@ +--- +title: Push +section: rest +index: 55 +languages: + - javascript + - java + - ruby + - php + - android + - nodejs + - swift + - objc +api_separator: +jump_to: + Help with: + - Delivery push notifications#deliver + - Activating and subscribing a device#activate-device + - Managing devices and subscriptions#admin + - Platform support#platform-support +--- + +<%= partial partial_version('general/push/_push_intro') %> + +h1. Push API reference + +inline-toc. + Push Admin: + - Methods: + - publish#publish + - Properties: + - deviceRegistrations#device-registrations + - channelSubscriptions#channel-subscriptions + - DeviceRegistrations: + - get#device-get-id + - list(params)#device-list + - save(DeviceDetails)#device-save + - remove#device-remove-id + - removeWhere(params)#device-remove-where + - PushChannelSubscriptions: + - list(params)#push-channel-sub-list + - listChannels(params)#push-channel-sub-list-channels + - save(PushChannelSubscription)#push-channel-sub-save + - remove(PushChannelSubscription)#push-channel-sub-remove + - removeWhere(params)#push-channel-sub-remove-where + Related types: + - DeviceDetails#device-details + - PushChannelSubscription#push-channel-subscription + - PushChannel#push-channel + - PaginatedResult#paginated-result + +<%= partial partial_version('types/_push_admin') %> + +h2(#related-types). Related types + +h3(#device-details). + default: DeviceDetails + ruby: Ably::Models::DeviceDetails + swift,objc: ARTDeviceDetails + +<%= partial partial_version('types/_device_details') %> + +h3(#push-channel-subscription). + default: PushChannelSubscription + ruby: Ably::Models::PushChannelSubscription + java,android: ChannelSubscription + swift,objc: ArtPushChannelSubscription + +<%= partial partial_version('types/_push_channel_subscription') %> + +h3(#push-channel). + default: PushChannel + ruby: Ably::Models::PushChannel + +<%= partial partial_version('types/_push_channel') %> + +h3(#paginated-result). + default: PaginatedResult + swift,objc: ARTPaginatedResult + ruby: Ably::Models::PaginatedResult + java,android: io.ably.lib.types.PaginatedResult + csharp: IO.Ably.PaginatedResult + +<%= partial partial_version('types/_paginated_result') %> diff --git a/content/rest/versions/v1.1/statistics.textile b/content/rest/versions/v1.1/statistics.textile new file mode 100644 index 0000000000..b4155bf03a --- /dev/null +++ b/content/rest/versions/v1.1/statistics.textile @@ -0,0 +1,166 @@ +--- +title: Statistics +section: rest +index: 100 +languages: + - javascript + - nodejs + - php + - python + - ruby + - java + - swift + - objc + - csharp + - go +api_separator: +jump_to: + Help with: + - Statistics#title + - Getting started + API reference: + - stats#stats + Types: + - Related Types#related-types +--- + +The Ably service retains usage statistics per application and per account at 1 minute intervals. Your application statistics are available programmatically through our client libraries at 1 minute intervals, or aggregated up to the hour, day, or month. + +p(tip). Your most recent statistics are delayed by up to 6 seconds. + +Your application and account statistics are also available as graphs, tabular data or downloads in your "application dashboard":https://support.ably.io/solution/articles/3000030053-how-do-i-access-my-app-dashboard. + +The Ably service aggregates your application statistics by minute, hour, day and month, both at an application level and at an account level. Account level statistics are not available via the client library APIs and must be viewed using your "account dashboard":https://support.ably.io/solution/articles/3000048664-how-do-i-access-my-account-dashboard. Your monthly quota and any potential overages are calculated using the account level statistics. "Find out what happens if you exceed your package limits":https://support.ably.io/solution/articles/3000044717-what-happens-if-i-exceed-my-package-limits. + +h2(#getting-started). Getting started + +The Ably REST client library provides a straightforward API for retrieving application statistics: + +```[jsall](code-editor:rest/stats) + var rest = new Ably.Rest('{{API_KEY}}'); + rest.stats({ unit: 'hour' }, function(err, resultPage) { + var thisHour = resultPage.items[0]; + console.log(thisHour); // => {all: a, inbound: f, outbound: f, …} + }); +``` + +```[ruby] + rest = Ably::Rest.new('{{API_KEY}}') + result_page = rest.stats(unit: 'hour') + this_hour = result_page.items.first + puts this_hour # => # ably.types.stats.Stats +``` + +```[php] + $rest = new Ably\AblyRest('{{API_KEY}}'); + $resultPage = $rest->stats(array('unit' => 'hour')); + $thisHour = $resultPage->items[0]; + echo('Published this hour ' . $thisHour->inbound->all->all->count); +``` + +```[java] + AblyRest rest = new AblyRest("{{API_KEY}}"); + Params options = new Param[]{ new Param("unit", "hour") } + PaginatedResult results = rest.stats(options); + Stats thisHour = results.items[0]; + System.out.println("Published this hour " + thisHour.inbound.all.all.count); +``` + +```[csharp] + AblyRest rest = new AblyRest("{{API_KEY}}"); + StatsRequestParams query = new StatsRequestParams() { Unit = StatsIntervalGranularity.Hour }; + PaginatedResult results = await rest.StatsAsync(query); + Stats thisHour = results.Items[0]; + Console.WriteLine("Published this hour " + thisHour.Inbound.All.All.Count); +``` + +```[objc] +ARTRest *rest = [[ARTRest alloc] initWithKey:@"{{API_KEY}}"]; +ARTStatsQuery *query = [[ARTStatsQuery alloc] init]; +query.unit = ARTStatsGranularityHour; +[rest stats:query callback:^(ARTPaginatedResult *result, ARTErrorInfo *error) { + ARTStats *thisHour = result.items[0]; + NSLog(@"Published this hour %lu", thisHour.inbound.all.all.count); +} error:nil]; +``` + +```[swift] +let rest = ARTRest(key: "{{API_KEY}}") +let query = ARTStatsQuery() +query.unit = .Hour +try! rest.stats(query) { results, error in + let thisHour = results!.items[0] + print("Published this hour \(thisHour.inbound.all.all.count)") +} +``` + +```[go] +rest, err := ably.NewRestClient(ably.NewClientOptions("{{API_KEY}}")) +query := &ably.PaginateParams{ + ScopeParams: ably.ScopeParams{ + Unit: 'hour', + }, +} +page, err := rest.Stats(query) +if err != nil { + // Error +} +fmt.Println("Published this hour: %d", len(page2.Items[0])) +``` + +h1. Realtime API Reference + +h6(#stats). + default: stats + csharp,go: Stats + +bq(definition). + jsall: stats(Object options, callback("ErrorInfo":/rest/types#error-info err, "PaginatedResult":/rest/types#paginated-result<"Stats":/rest/types#stats> results)) + ruby: "PaginatedResult":/rest/types#paginated-result<"Stats":/rest/types#stats> stats(Hash options) + python: "PaginatedResult":/rest/types#paginated-result<"Stats":/rest/types#stats> stats(kwargs_options) + php: "PaginatedResult":/rest/types#paginated-result<"Stats":/rest/types#stats> stats(Array options) + java: "PaginatedResult":/rest/types#paginated-result<"Stats":/rest/types#stats-type> stats("Param":#param[] options) + csharp: Task<"PaginatedResult":/rest/types#paginated-result<"Stats":/rest/types#stats-type>> StatsAsync(@StatsRequestParams@ query) + swift,objc: stats(query: ARTStatsQuery?, callback: ("ARTPaginatedResult":#/realtime/types#paginated-result<"ARTStats":/realtime/types#stats>?, ARTErrorInfo?) -> Void) throws + go: (c *RestClient) Stats(params *PaginateParams) (*PaginatedResult, error) + +This call queries the "Ably REST @/stats@ API endpoint":/rest-api#stats-type and retrieves your application's usage statistics. A "PaginatedResult":/rest/types#paginated-result is returned, containing an array of "Stats":/rest/types#stats-type for the first page of results. "PaginatedResult":/rest/types#paginated-result objects are iterable providing a means to page through historical statistics. "See an example set of raw stats returned via the REST API":/general/statistics. + +<%= partial partial_version('rest/_stats') %> + +h2(#related-types). Related types + +h3(#stats-type). + default: Stats object + objc,swift: ARTStats + java: io.ably.lib.types.Stats + ruby: Ably::Models::Stats + php: Ably\Models\Stats + csharp: IO.Ably.Stats + +<%= partial partial_version('types/_stats') %> + +h3(#stats-request-params). + csharp: IO.Ably.StatsRequestParams + +blang[csharp]. + <%= partial partial_version('types/_stats_request_params'), indent: 2, skip_first_indent: true %> + +h3(#stats-granularity). + objc,swift: ARTStatsGranularity + csharp: IO.Ably.StatsGranularity + +<%= partial partial_version('types/_stats_granularity') %> + +h3(#param). + java: io.ably.lib.types.Param + +blang[java]. + <%= partial partial_version('types/_param'), indent: 2, skip_first_indent: true %> diff --git a/content/rest/versions/v1.1/types.textile b/content/rest/versions/v1.1/types.textile new file mode 100644 index 0000000000..641a6f12c1 --- /dev/null +++ b/content/rest/versions/v1.1/types.textile @@ -0,0 +1,230 @@ +--- +title: Types +section: rest +index: 500 +languages: + - javascript + - nodejs + - php + - python + - ruby + - java + - swift + - objc + - csharp + - go +--- + +The Ably REST client library defines both data types and option types. Data types are used to represent object such as messages; Option types are used in method arguments. + +Where client libraries support both Realtime and REST APIs, the types are shared between both clients. + +All types are always classes or their respective equivalent for each language implementation. Options on the other hand, may often support both typed option classes or more flexible key value objects such as a Hash or plain Javascript object. + +If you are interested in finding out more about the exact types and options definitions in each language, we recommend you download "our open source libraries":https://www.ably.io/download and review the code. + +h1. API Reference + +inline-toc. + REST Data types: + - AblyException#ably-exception + - Channel Details + - Error Info + - Message + - Presence Message + - Presence action + - Paginated Result + - HTTP Paginated Response + - Param + - Token Details + - Token Request + - Stats + - Stats Granularity + - History Request Params + REST Other Types: + - Auth Options + - Client Options + - Channel Options + - Cipher Params + - Stats Request Params + - Token Params + +h2. REST Data types + +h3(#ably-exception). + java: io.ably.lib.types.AblyException + ruby: Ably::Exceptions::BaseAblyException + python: AblyException + php: Ably\Exceptions\AblyException + csharp: IO.Ably.AblyException + +blang[java,php,csharp]. + <%= partial partial_version('types/_ably_exception'), indent: 2, skip_first_indent: true %> + +blang[ruby,python]. + <%= partial partial_version('types/_base_ably_exception'), indent: 2, skip_first_indent: true %> + +<%= partial partial_version('types/_channel_details') %> + +h3(#error-info). + default: ErrorInfo + objc,swift: ARTErrorInfo + java: io.ably.lib.types.ErrorInfo + ruby: Ably::Models::ErrorInfo + php: Ably\Models\ErrorInfo + csharp: IO.Ably.ErrorInfo + +blang[jsall,ruby,php,java,objc,swift]. + <%= partial partial_version('types/_error_info'), indent: 2, skip_first_indent: true %> + +h3(#message). + default: Message + objc,swift: ARTMessage + java: io.ably.lib.types.Message + ruby: Ably::Models::Message + php: Ably\Models\Message + csharp: IO.Ably.Message + +<%= partial partial_version('types/_message') %> + +h3(#presence-message). + default: PresenceMessage + objc,swift: ARTPresenceMessage + java: io.ably.lib.types.PresenceMessage + ruby: Ably::Models::PresenceMessage + php: Ably\Models\PresenceMessage + csharp: IO.Ably.PresenceMessage + +<%= partial partial_version('types/_presence_message') %> + +h3(#presence-action). + default: Presence action + objc,swift: ARTPresenceAction + java: io.ably.lib.types.PresenceMessage.Action + ruby: Ably::Models::PresenceMessage::ACTION + php: Ably\Models\PresenceMessage Action + csharp: IO.Ably.PresenceAction + +<%= partial partial_version('types/_presence_action') %> + +h3(#paginated-result). + default: PaginatedResult + objc,swift: ARTPaginatedResult + java: io.ably.lib.types.PaginatedResult + ruby: Ably::Models::PaginatedResult + php: Ably\Models\PaginatedResult + csharp: IO.Ably.PaginatedResult + +<%= partial partial_version('types/_paginated_result') %> + +h3(#http-paginated-response). + default: HttpPaginatedResponse + +<%= partial partial_version('types/_http_paginated_response') %> + +h3(#param). + java: io.ably.lib.types.Param + +blang[java]. + <%= partial partial_version('types/_param'), indent: 2, skip_first_indent: true %> + +h3(#token-details). + default: TokenDetails + objc,swift: ARTTokenDetails + java: io.ably.lib.types.TokenDetails + ruby: Ably::Models::TokenDetails + php: Ably\Models\TokenDetails + csharp: IO.Ably.TokenDetails + +<%= partial partial_version('types/_token_details') %> + +h3(#token-request). + default: TokenRequest + objc,swift: ARTTokenRequest + java: io.ably.lib.types.TokenRequest + ruby: Ably::Models::TokenRequest + php: Ably\Models\TokenRequest + csharp: IO.Ably.TokenRequest + +<%= partial partial_version('types/_token_request') %> + +h3(#stats). + default: Stats object + objc,swift: ARTStats + java: io.ably.lib.types.Stats + ruby: Ably::Models::Stats + php: Ably\Models\Stats + csharp: IO.Ably.Stats + +<%= partial partial_version('types/_stats') %> + +h3(#stats-granularity). + objc,swift: ARTStatsGranularity + +<%= partial partial_version('types/_stats_granularity') %> + +h3(#paginated-request-params). + csharp: IO.Ably.PaginatedRequestParams + +blang[csharp]. + <%= partial partial_version('types/_history_request_params'), indent: 2, skip_first_indent: true %> + +h2. REST Other types + +h3(#auth-options). + default: AuthOptions Object + objc,swift: ARTAuthOptions + ruby: AuthOptions Hash + php: AuthOptions Array + java: io.ably.lib.rest.Auth.AuthOptions + csharp: IO.Ably.AuthOptions + +<%= partial partial_version('types/_auth_options') %> + +h3(#client-options). + default: ClientOptions Object + objc,swift: ARTClientOptions + ruby: ClientOptions Hash + php: ClientOptions Array + java: io.ably.types.ClientOptions + csharp: IO.Ably.ClientOptions + +<%= partial partial_version('types/_rest_client_options_intro') %> +<%= partial partial_version('types/_client_options') %> +<%= partial partial_version('types/_rest_client_options') %> + +h3(#channel-options). + default: ChannelOptions Object + objc,swift: ARTChannelOptions + ruby: ChannelOptions Hash + php: ChannelOptions Array + java: io.ably.lib.types.ChannelOptions + csharp: IO.Ably.ChannelOptions + +<%= partial partial_version('types/_channel_options') %> + +h3(#cipher-params). + default: CipherParams + objc,swift: ARTCipherParams + ruby: CipherParams Hash + php: CipherParams Array + java: io.ably.lib.util.Crypto.CipherParams + csharp: IO.Ably.CipherParams + +<%= partial partial_version('types/_cipher_params') %> + +h3(#stats-request-params). + csharp: IO.Ably.StatsRequestParams + +blang[csharp]. + <%= partial partial_version('types/_stats_request_params'), indent: 2, skip_first_indent: true %> + +h3(#token-params). + default: TokenParams Object + objc,swift: ARTTokenParams + ruby: TokenParams Hash + php: TokenParams Array + java: io.ably.lib.rest.Auth.TokenParams + csharp: IO.Ably.TokenParams + +<%= partial partial_version('types/_token_params') %> diff --git a/content/rest/versions/v1.1/usage.textile b/content/rest/versions/v1.1/usage.textile new file mode 100644 index 0000000000..18da291425 --- /dev/null +++ b/content/rest/versions/v1.1/usage.textile @@ -0,0 +1,404 @@ +--- +title: Using the REST library +section: rest +index: 10 +languages: + - javascript + - nodejs + - php + - python + - ruby + - java + - swift + - objc + - csharp + - go +api_separator: +jump_to: + Help with: + - Usage#title + REST API reference: + - constructor + Attributes: + - auth + - channels + - client_id#client-id + Methods: + - stats + - time + - request + Types: + - Related Types#related-types +--- + +The Ably REST client libraries offer a simple stateless API to interact directly with "Ably's REST API":/rest-api/. The REST library is typically used server-side to issue tokens, publish messages, and retrieve message history. If you are building a client-side application, you may want to consider using our "stateful Ably Realtime client libraries":/realtime. + +The Ably REST client library is available for "most popular languages and platforms":https://www.ably.io/download with a consistent API across all platforms. You can view this documentation with your preferred language API and code examples using the language selector navigation above. If your preferred language is not listed in the language selector, we recommend you "download a library in your preferred language":https://www.ably.io/download and use this documentation as an API reference. + +blang[javascript]. + The REST library for browser Javascript environments should be loaded from the Ably CDN as follows: + + ```[javascript] + + ``` + + You can also obtain the library as an "NPM module":https://www.npmjs.com/package/ably, or download the source directly from "Github":https://github.com/ably/ably-js + + The script and module both contain the Realtime and REST libraries as well as all their dependencies. To instance the REST library: + + ```[javascript] + var rest = new Ably.Rest({ key: apiKey }); + ``` + + When including the client library from our CDN, we recommend you lock into major version @1@ of the library. According to the "semantic versioning scheme":http://semver.org/ we adopt, you will then automatically receive minor and patch updates but you will never receive breaking changes. For example, if you lock into major version @1@ of the library by including "@https://cdn.ably.io/lib/ably.min-1.js@":https://cdn.ably.io/lib/ably.min-1.js, you will receive all minor updates and patch fixes automatically (i.e @1.*.*@). If you want to lock into minor version @1.0@ and receive all patch fixes automatically (i.e. @1.0.*@), then use "@https://cdn.ably.io/lib/ably.min-1.0.js@":https://cdn.ably.io/lib/ably.min-1.0.js. Additionally, the @.min@ suffix can be dropped if you want the non-minified version for debugging. + + "View a complete list of the Javascript library releases":https://github.com/ably/ably-js/releases + +blang[nodejs]. + The REST library for Node.js is suitable for clients and servers and is "hosted on Github":https://github.com/ably/ably-js and is obtainable as an NPM module "directly from npm":https://npmjs.org/package/ably. Install with: + + ```[sh] + npm install ably + ``` + + The Ably Node.js module contains both the REST and Realtime libraries; each is exported separately by the module. To instance the REST library: + + ```[nodejs] + var Ably = require('ably'); + var rest = new Ably.Rest({ key: apiKey }); + ``` + +blang[ruby]. + The REST library for Ruby is "hosted on Github":https://github.com/ably/ably-ruby-rest and is "published as a RubyGem":https://rubygems.org/gems/ably-rest and can be installed as follows: + + ```[sh] + gem install ably-rest + ``` + + If using bundler, simply add the following to your Gemfile and run @bundle install@: + + ```[ruby] + gem 'ably-rest' + ``` + + The "Ably Ruby REST gem":https://rubygems.org/gems/ably-rest contains only the REST API and limited dependencies. To instance the REST library: + + ```[ruby] + ably = Ably::Rest.new(key: api_key) + ``` + +blang[python]. + The REST library for Python is "hosted on Github":https://github.com/ably/ably-python and is "published on PyPI":https://pypi.python.org/pypi/ably and can be installed as follows: + + ```[sh] + pip install ably + ``` + + Then it can be imported and instantiated as follows: + + ```[python] + from ably import AblyRest + client = AblyRest(api_key) + ``` + + h4. Note on string encodings + + Since Ably supports both string and binary payloads, to avoid ambiguity, we recommend that strings passed to the library for publishing to Ably (eg as an event name or payload data) should be unicode strings. In Python 3 this is the normal string type, but in Python 2 it is not, so we suggest you prefix string literals with @u@ prefix (eg @u'eventname'@ - or alternatively, use @from __future__ import unicode_literals@, which will make this automatic), and to explicitly decode any user input (eg @raw_input().decode(sys.stdin.encoding@). + +blang[php]. + The REST library for PHP is "hosted on Github":https://github.com/ably/ably-php and is available as a "composer package on packagist":https://packagist.org/packages/ably/ably-php which can be installed as follows: + + bc[sh]. composer require ably/ably-php --update-no-dev + + Then simply require composer's autoloader: + + bc[php]. require_once __DIR__ . '/../vendor/autoload.php'; + + To instance the REST library: + + ```[php] + $ably = new Ably\AblyRest(apiKey); + ``` + +blang[java]. + The REST library for Java and Android is "hosted on Github":https://github.com/ably/ably-java and can be used by adding one line to @build.gradle@ dependencies section. + + For Java applications: + + ```[java] + compile 'io.ably:ably-java:1.1.0' + ``` + + For Android apps: + + ```[java] + compile 'io.ably:ably-android:1.1.0' + ``` + + In the above example a specific version of the library is referenced, however we recommend you check which is the latest stable version and always use that. Follow links to get the latest stable release for "Java":https://bintray.com/ably-io/ably/ably-java and "Android":https://bintray.com/ably-io/ably/ably-android. + + Ensure the library is included in your classpath as follows: + + ```[java] + import io.ably.lib.types.*; + import io.ably.lib.rest.*; + ClientOptions options = new ClientOptions(apiKey); + AblyRest rest = new AblyRest(options); + ``` + +blang[csharp]. + The REST library for .Net is available as a Nuget Package. Open the nuget console in visual studio and type. + + PM> Install-Package ably.io + + Alternatively, search for the 'ably.io' package through the Nuget UI + + ```[csharp] + using IO.Ably; + + ClientOptions clientOptions = new ClientOptions(""); + AblyRest rest = new AblyRest(clientOptions); + ``` + +blang[objc,swift]. + The Ably library is "hosted on Github":https://github.com/ably/ably-cocoa and is "available as a CocoaPod":https://cocoapods.org/pods/Ably. Add this line to your application's Podfile: + + bc[text]. pod 'Ably' + + And install with @pod install@. Then in your files: + + ```[objc] + #import "Ably/Ably.h" + + ARTRest rest = [[ARTRest alloc] initWithKey:apiKey]; + ``` + + ```[swift] + import Ably + + let rest = ARTRest(key: apiKey) + ``` + +blang[go]. + The Ably library is "hosted on Github":https://github.com/ably/ably-go. Simply run the following to get access to the library: + + bc[text]. go get -u github.com/ably/ably-go/ably + +"View our client library SDKs feature support matrix":https://www.ably.io/download/sdk-feature-support-matrix to see the list of all the available features. + +The supported "client options are described below":#client-options. + +h2(#tutorials). Tutorials + +If you would like to see examples of using the Ably REST client library, then we recommend you take a look at our "REST tutorials":/tutorials. + +h1. + default: REST API Reference + ruby: REST::Client API reference + +inline-toc. + Properties: + - auth#auth + - push#push + - device#device + - channels#channels + Methods: + - stats(options)#stats + - time()#time + - request()#request + Related types: + - ClientOptions#client-options + - Stats + - Stats Granularity#stats-granularity + - HttpPaginatedResponse#http-paginated-response + - Params#param + +h2(#constructor). Constructor + +The Ably REST library constructor is overloaded allowing it to be instanced using a "@ClientOptions@":#client-options object, or more simply using a string containing an "API key":/core-features/authentication/#basic-authentication or "Token":/core-features/authentication/#token-authentication. + +bq(definition). + default: new Ably.Rest(String keyOrTokenId) + ruby: Ably::Rest.new(String key_or_token_id) + php: new Ably\AblyRest(String key_or_token_id) + java: new io.ably.lib.AblyRest(String keyOrTokenIdString) + csharp: new IO.Ably.AblyRest(string key); + objc: - (instancetype)initWithKey:(NSString *)key
(instancetype)initWithToken:(NSString *)token + swift: init(key: String)
init(token: String) + python: AblyRest(String api_key) + go: NewRestClient(keyOrTokenId string) (*RestClient, error) + +This will instance the REST library with the provided API key or Token ID string. + +bq(definition). + default: new Ably.Rest("ClientOptions":#client-options clientOptions) + ruby: Ably::Rest.new("ClientOptions":#client-options client_options) + php: new Ably\AblyRest("ClientOptions":#client-options client_options) + java: new io.ably.lib.AblyRest("ClientOptions":#client-options clientOptions) + csharp: new IO.Ably.AblyRest("ClientOptions":#client-options clientOptions) + objc: - (instancetype)initWithOptions:("ARTClientOptions":#client-options *)options; + swift: init(options: "ARTClientOptions":#client-options) + python: AblyRest("ClientOptions":#client-options client_options) + go: NewRestClient(opts *"ClientOptions":#client-options) (*RestClient, error) + +blang[default]. + This will instance the library using the specified "ClientOptions":#client-options. + +blang[ruby]. + This will instance the library and create a new @Ably::Rest::Client@ using the specified "@ClientOptions@":#client-options. + +The REST constructor is used to instance the library. The REST library may be instanced multiple times with the same or different "@ClientOptions@":#client-options in any given context. Except where specified otherwise, instances operate independently of one another. + +h3(#authentication). Authentication + +The REST library needs to have credentials to be able to authenticate with the Ably service. Ably supports both Basic and Token based authentication schemes. Read more on "authentication":/rest/authentication. + +h4. Basic Authentication + +A private API key string for "@ClientOptions#key@@ClientOptions#Key@":#client-options or the constructor, as obtained from the "application dashboard":http://support.ably.io/solution/articles/3000030053-how-do-i-access-my-app-dashboard, is required for "Basic Authentication":/core-features/authentication/#basic-authentication. Use this option if you wish to use "Basic authentication":/core-features/authentication/#basic-authentication, or if you want to be able to "request Ably Tokens":/rest/authentication/#request-token without needing to defer to a separate entity to sign Ably TokenRequests. Note that initializing the library with a @key@@Key@ does not necessarily mean that the library will use Basic auth; using the private key it is also able to create and sign Ably TokenRequests and use token authentication when necessary. + +h4. Token Authentication + +The "@ClientOptions#token@@ClientOptions#Token@":#client-options option takes a @token@ string, and assumes that the Ably-compatible token has been obtained from some other instance that requested the token. Use the token option if you are provided with a token to use and you do not have a key (or do not have a key with the capabilities that you require). + +Since tokens are short-lived, it is rarely sufficient to start with a token without the means for refreshing it. The "@authUrl@ and @authCallback@@:auth_url@ and @:auth_callback@@auth_url@ and @auth_callback@@AuthUrl@ and @AuthCallback@ options":#client-options are provided to allow a user of the library to provide new Ably-compatible tokens or Ably TokenRequests to the library as required; using these options allows the library to be instanced without a @key@ or @token@@Key@ or @Token@, and an initial token will be obtained automatically when required. + +Read more on "authentication":/rest/authentication. + +h2(#properties). + default: AblyRest Properties + jsall: Ably.Rest Properties + java: io.ably.lib.AblyRest Members + ruby: Ably::Rest::Client Attributes + objc,swift: ARTRest Properties + +The REST client exposes the following public attributesmembersproperties: + +h6(#auth). + default: auth + csharp,go: Auth + +A reference to the "@Auth@":/rest/authentication authentication object configured for this client library. + +h6(#push). + default: push + +A reference to the "@Push@@ARTPush@":/rest/push object in this client library. + +
+h6(#device). + default: device + +A reference to the "@LocalDevice@@ARTLocalDevice@":/general/push/activate-subscribe#local-device object. +
+ +h6(#channels). + default: channels + csharp,go: Channels + +"@Channels@":/rest/channels#channels-object is a reference to the "@Channel@":/rest/channels collection instance for this library indexed by the channel name. You can use the "@Get@":/rest/channels#get method of this to get a @Channel@ instance. See "channels":/rest/channels and "messages":/rest/messages/ for more information. + +h2(#methods). + default: AblyRest Methods + jsall: Ably.Rest Methods + java: io.ably.lib.AblyRest Methods + ruby: Ably::Rest::Client Methods + objc,swift: ARTRealtime Methods + +h6(#stats). + default: stats + csharp,go: Stats + +bq(definition). + default: stats(Object options, callback("ErrorInfo":/rest/types#error-info err, "PaginatedResult":/rest/types#paginated-result<"Stats":#stats> results)) + ruby: "PaginatedResult":/rest/types#paginated-result<"Stats":#stats> stats(Hash options) + python: "PaginatedResult":/rest/types#paginated-result<"Stats":#stats> stats(kwargs_options) + php: "PaginatedResult":/rest/types#paginated-result<"Stats":#stats> stats(Array options) + java: "PaginatedResult":/rest/types#paginated-result<"Stats":#stats> stats("Param":#param[] options) + csharp: Task<"PaginatedResult":#paginated-result<"Stats":#stats>> StatsAsync("StatsRequestParams":/rest/types#data-request query) + swift,objc: stats(query: ARTStatsQuery?, callback: ("ARTPaginatedResult":#/realtime/types#paginated-result<"ARTStats":/realtime/types#stats>?, ARTErrorInfo?) -> Void) throws + go: (c *RestClient) Stats(params *PaginateParams) (*PaginatedResult, error) + +This call queries the "REST @/stats@ API":/rest-api#stats and retrieves your application's usage statistics. A "PaginatedResult":/rest/types#paginated-result is returned, containing an array of "Stats":#stats for the first page of results. "PaginatedResult":/rest/types#paginated-result objects are iterable providing a means to page through historical statistics. "See an example set of raw stats returned via the REST API":/general/statistics. + +See "statistics":/rest/statistics for more information. + +<%= partial partial_version('rest/_stats') %> + +h6(#time). + default: time + csharp: Time + +bq(definition). + default: time(callback("ErrorInfo":/rest/types#error-info err, Number time)) + ruby: Time time + python: Int time() + php: Integer time() + java: long time() + csharp: Task TimeAsync() + objc,swift: time(callback: (NSDate?, NSError?) -> Void) + go: (c *RestClient) Time() (time.Time, "error":/rest/types#error-info) + +Obtains the time from the Ably service as a @Time@ objecta @DateTimeOffset@ objectmilliseconds since epoch. (Clients that do not have access to a sufficiently well maintained time source and wish to issue Ably "TokenRequests":/rest/authentication#token-authentication with a more accurate timestamp should use the @queryTime@ "clientOptions":#client-options instead of this method). + +blang[jsall,objc,swift]. + h4. Callback result + + On success, @time@ is a number containing the number of milliseconds since the epoch. + + On failure to retrieve the Ably server time, @err@ contains an "@ErrorInfo@":#error-info object with an error response as defined in the "Ably REST API":/rest-api#common documentation. + +blang[java,ruby,php,csharp]. + h4. Returns + + On success, milliseconds since epochthe @Time@the @DateTimeOffset@ is returned. + + Failure to retrieve the Ably server time will raise an "@AblyException@":/rest/types#ably-exception. + +blang[go]. + h4. Returns + + On success, milliseconds since epochthe @Time@the @DateTimeOffset@ is returned. + + On failure to retrieve the Ably server time, @error@ contains an "@ErrorInfo@":#error-info object with an error response as defined in the "Ably REST API":/rest-api#common documentation. + +<%= partial partial_version('rest/_request') %> + +h2(#related-types). Related types + +h3(#client-options). + default: ClientOptions + swift,objc: ARTClientOptions + java: io.ably.types.ClientOptions + csharp: IO.Ably.ClientOptions + +<%= partial partial_version('types/_rest_client_options_intro') %> +<%= partial partial_version('types/_client_options') %> +<%= partial partial_version('types/_rest_client_options') %> + +h3(#stats-type). + default: Stats object + swift,objc: ARTStats + java: io.ably.lib.types.Stats + ruby: Ably::Models::Stats + php: Ably\Models\Stats + csharp: IO.Ably.Stats + +<%= partial partial_version('types/_stats') %> + +h3(#stats-granularity). + objc,swift: ARTStatsGranularity + csharp: StatsGranularity + +<%= partial partial_version('types/_stats_granularity') %> + +h3(#http-paginated-response). + default: HttpPaginatedResponse + +<%= partial partial_version('types/_http_paginated_response') %> + +h3(#param). + java: io.ably.lib.types.Param + +blang[java]. + <%= partial partial_version('types/_param'), indent: 2, skip_first_indent: true %> diff --git a/content/root/quick-start-guide.textile b/content/root/quick-start-guide.textile index fed7a14a53..f1503b19e3 100644 --- a/content/root/quick-start-guide.textile +++ b/content/root/quick-start-guide.textile @@ -62,13 +62,13 @@ blang[java]. For Java applications: ```[java] - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' ``` For Android apps: ```[java] - compile 'io.ably:ably-android:1.0.0' + compile 'io.ably:ably-android:1.2.0' ``` In the above example a specific version of the library is referenced, however we recommend you check which is the latest stable version and always use that. Follow links to get the latest stable release for "Java":https://bintray.com/ably-io/ably/ably-java and "Android":https://bintray.com/ably-io/ably/ably-android. diff --git a/content/sse/index.textile b/content/sse/index.textile index 28005c6331..8dd36120ed 100644 --- a/content/sse/index.textile +++ b/content/sse/index.textile @@ -25,7 +25,7 @@ SSE is incredibly simple to get started with. The code sample below provides an ```[javascript](code-editor:sse/sse) var key ='{{API_KEY}}'; -var url ='https://realtime.ably.io/event-stream?channels=myChannel&v=1.1&key=' + key; +var url ='https://realtime.ably.io/event-stream?channels=myChannel&v=1.2&key=' + key; var eventSource = new EventSource(url); eventSource.onmessage = function(event) { @@ -112,7 +112,7 @@ The @/event-stream@ endpoint will give an SSE response if the @Accept@ header is h5. Request parameters - channels := **mandatory**. One or more channel names, separated by commas (or the @separator@ if specified). Non-url-safe characters should be URL-encoded (for example, @?channels=foo%3Fbar@ will subscribe to the channel @foo?bar@). Alias: @channel@. -- v := **mandatory**. The version of the api you are requesting. The current (and only) version of the API is 1.1, so only @v=1.1@ is accepted. +- v := **mandatory**. The version of the api you are requesting. The current version of the API is 1.2, so @v=1.2@ is recommended. - separator := **optional**. A separator, to enable easy subscriptions to channels with commas in their name. For example, @?separator=|&channel=fo,o|ba,r@ will subscribe to the two channels @fo,o@ and @ba,r@. - key := **optional**. An Ably API key to use, if using basic auth. - accessToken := **optional** An Ably auth token to use, if using token auth. @@ -131,7 +131,7 @@ h5. Code example ```[javascript](code-editor:sse/sse) var apiKey = '{{API_KEY}}'; -var url = 'https://realtime.ably.io/event-stream?channels=myChannel&v=1.1&key=' + apikey; +var url = 'https://realtime.ably.io/event-stream?channels=myChannel&v=1.2&key=' + apikey; var eventSource = new EventSource(url); eventSource.onmessage = function(event) { @@ -145,7 +145,7 @@ import json import sseclient api_key='{{API_KEY}}' -url = "https://realtime.ably.io/sse?channels=myChannel&v=1.1&key=%s" % (api_key) +url = "https://realtime.ably.io/sse?channels=myChannel&v=1.2&key=%s" % (api_key) def with_urllib3(url): import urllib3 @@ -160,7 +160,7 @@ for event in client.events(): ``` ```[curl] -curl "https://rest.ably.io/sse?channel=example&v=1.1" \ +curl "https://rest.ably.io/sse?channel=example&v=1.2" \ --user "{{API_KEY}}" ⏎ id: cbfKayrzgAXDWM:1556806691343-0 @@ -196,7 +196,7 @@ When available, we recommend using an SSE library as opposed to the raw HTTP str h5. Request parameters - channels := **mandatory**. One or more channel names, separated by commas (or the @separator@ if specified). Non-url-safe characters should be URL-encoded (for example, @?channels=foo%3Fbar@ will subscribe to the channel @foo?bar@). Alias: @channel@. -- v := **mandatory**. The version of the api you are requesting. The current (and only) version of the API is 1.1, so only @v=1.1@ is accepted. +- v := **mandatory**. The version of the api you are requesting. The current version of the API is 1.2, so @v=1.2@ is recommended. - separator := **optional**. A separator, to enable easy subscriptions to channels with commas in their name. For example, @?separator=|&channel=fo,o|ba,r@ will subscribe to the two channels @fo,o@ and @ba,r@. - key := **optional**. An Ably API key to use, if using basic auth. - accessToken := **optional** An Ably auth token to use, if using token auth. @@ -233,7 +233,7 @@ Note that failures on opening the connection (for example, invalid authenticatio h5. Code example ```[curl] -curl "https://rest.ably.io/event-stream?channel=example&v=1.1" \ +curl "https://rest.ably.io/event-stream?channel=example&v=1.2" \ --user "{{API_KEY}}" { @@ -263,7 +263,7 @@ curl "https://rest.ably.io/event-stream?channel=example&v=1.1" \ ```[nodejs] const request = require('request'); const apiKey = '{{API_KEY}}'; -const url = 'https://realtime.ably.io/event-stream?channels=myChannel&v=1.1&key=' + apikey; +const url = 'https://realtime.ably.io/event-stream?channels=myChannel&v=1.2&key=' + apikey; request .get(url) diff --git a/content/sse/versions/v1.1/index.textile b/content/sse/versions/v1.1/index.textile new file mode 100644 index 0000000000..28005c6331 --- /dev/null +++ b/content/sse/versions/v1.1/index.textile @@ -0,0 +1,276 @@ +--- +title: SSE and Raw HTTP Streaming API +section: sse +index: 0 +api_separator: +jump_to: + Help with: + - Authentication#authentication + API Routes: + - Server-sent events#sse + - Plain event stream#event-stream +--- + +The Ably SSE and raw HTTP streaming API provides a way to get a realtime stream of events from Ably in circumstances where using a full Ably Realtime client library, or even an "MQTT":/concepts/mqtt library, is impractical. + +HTTP streaming allows for a request from a client to be held by a server, allowing it to push data to the client without further requests. This, much like WebSockets, help avoid the overhead involved in normal HTTP requests. "Server-sent events":https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events (SSE) provide a thin layer on top of HTTP streaming. A common use of SSE is through the use of "the EventSource API":https://developer.mozilla.org/en-US/docs/Web/API/EventSource in all modern web browsers. + +It is subscribe-only: you can not interact with the channel, including to publish, enter presence, query the presence set, attach and detach from channels (without closing and re-opening the stream), or anything else. + +Customers who do not want to use a client library on platforms that support SSE, and only require simple subscribe-only streams, may choose to use SSE because it's an open standard, simple, and requires no SDKs on the client-side. HTTP Streaming may be considered on platforms without an SSE client. However, where possible, we strongly recommend the use of one of our "Realtime client libraries":https://www.ably.io/download, which provide "more features and higher reliability":https://support.ably.io/solution/articles/3000061848, and the full use of our "normal realtime messaging API":/realtime. + +h2(#getting-started). Getting Started + +SSE is incredibly simple to get started with. The code sample below provides an example of how to use it with Ably. + +```[javascript](code-editor:sse/sse) +var key ='{{API_KEY}}'; +var url ='https://realtime.ably.io/event-stream?channels=myChannel&v=1.1&key=' + key; +var eventSource = new EventSource(url); + +eventSource.onmessage = function(event) { + var message = JSON.parse(event.data); + console.log('Message: ' + message.name + ' - ' + message.data); +}; +``` + +h2(#authentication). Authentication + +It is possible to use either "basic auth":https://www.ably.io/documentation/core-features/authentication#basic-authentication (with an "API key":https://support.ably.io/support/solutions/articles/3000030054) or "token auth":https://www.ably.io/documentation/core-features/authentication#basic-authentication (using a "token issued from your server":/core-features/authentication#token-request-process) with SSE. We recommend token auth on the client side for "security reasons":https://support.ably.io/support/solutions/articles/3000038607, so you have control over who can connect. Basic auth, while lacking this control, is simpler (it doesn't require you to run an auth server), and you don't have to worry about the client obtaining a new token when the old one expires. + +If using basic auth, you can use a querystring parameter of @key@ or an @Authorization: Basic @ header. If using token auth, you can use an @accessToken@ querystring parameter or an @Authorization: Bearer @ header. See "REST API authentication":/rest-api#authentication for more information. + +Connection state is only retained for two minutes. See "Connection state explained":/realtime/connection#connection-state-explained for full documentation. + +The SSE protocol and the EventSource API are designed so that a dropped connection is resumed transparently; the client implementation will reconnect and supply a @lastEventId@ param that ensures that the resuming connection delivers any events that have arisen since the connection was dropped. Ably uses this mechanism to reattach all channels in a new connection to the exact point that had been reached in the prior connection. + +When a token expires the connection will end. However, the default EventSource behaviour of automated reconnection will not work, because the (expired) credentials are part of the connection URL. What is needed is for a new connection to be established, with an updated @accessToken@. The question then arises as to how to do that with continuity - that is, how to establish a new connection but supply the correct @lastEventId@ so that the new connection resumes from the point that the prior connection became disconnected. + +h3(#message-continuity-token-auth). Implementing message continuity with Token Auth + +Implementing transparent connection resumes when tokens need to be renewed requires a few additional steps - detecting token expiry and resuming the connection from the point of the last delivered message using the @lastEventId@ attribute: + +h4(#detecting-token-expiry). Detecting token expiry + +When a connection is closed as a result of any error (that is, it's not just a dropped connection), then the @error@ event will occur on the @EventSource@ instance, and the data attribute of the event will contain an Ably error body with the information about the nature of error. In the case of a token error - that is an error arising from a problem with the auth token - the code in the error body will indicate that. Token errors have a code in the range @40140 <= code < 40150@. In such cases, the authentication can be retried with a new @accessToken@. + +In the future we plan to send an event on the connection that indicates that the token will expire imminently, which will allow a new connection to be established prior to the closure of the previous connection. + +h4(#specifying-lasteventid). Specifying the lastEventId + +Each message received will have a @lastEventId@ attribute containing the last id of any message received on the connection. When constructing a new connection, this value can be specified as a @lastEvent@ param in the URL. + +Here's example of implementing message continuity with Token Auth: + +```[javascript] +let lastEvent; + +const connectToAbly = () => { + // obtain a token + const token = + + // establish a connection with that token + const lastEventParam = lastEvent ? ('&lastEvent=' + lastEvent) : ''; + eventSource = new EventSource(`https://realtime.ably.io/sse?v=1.1&accessToken=${token}&channels=${channel}${lastEventParam}`); + + // handle incoming messages + eventSource.onmessage = msg => { + lastEvent = msg.lastEventId; + // ... normal message processing + } + + // handle connection errors + eventSource.onerror = msg => { + const err = JSON.parse(msg.data); + const isTokenErr = err.code >= 40140 && err.code < 40150; + if(isTokenErr) { + eventSource.close(); + connectToAbly(); + } else { + // ... handle other types of error -- for example, retry on 5xxxx, close on 4xxxx + } + } +} + +connectToAbly(); +``` + +An important thing to note here is that the EventSource API tries to auto-reconnect and re-subscribe to the SSE endpoint when any error occurs, even the token expiry error like in this case. This means that upon manually re-subscribing to the SSE endpoint with a new token, there will be two active subscriptions to the endpoint - one with the old token which would continue to throw an error due to expired credentials and another with the new token. Hence, it is important to close the previous @EventSource@ subscription using @eventSource.close()@ before re-subscribing with the new token as shown in the snippet above. + +You can take a look at a "demo app":https://sse-token-auth.glitch.me and "a complete code example for implementing message continuity in an SSE subscription when using token auth":https://glitch.com/edit/#!/sse-token-auth. + +h1(#api). API routes + +h3(#sse). Server-sent events + +h6. GET realtime.ably.io/sse + +Start a streaming HTTP request that conforms to the "Server-Sent Events":https://www.w3.org/TR/eventsource/ spec, for ease of consuming with an SSE library. + +The @/event-stream@ endpoint will give an SSE response if the @Accept@ header is set to @text/event-stream@. The @/sse@ endpoint is also provided as an easier way of forcing an SSE response. + +h5. Request parameters + +- channels := **mandatory**. One or more channel names, separated by commas (or the @separator@ if specified). Non-url-safe characters should be URL-encoded (for example, @?channels=foo%3Fbar@ will subscribe to the channel @foo?bar@). Alias: @channel@. +- v := **mandatory**. The version of the api you are requesting. The current (and only) version of the API is 1.1, so only @v=1.1@ is accepted. +- separator := **optional**. A separator, to enable easy subscriptions to channels with commas in their name. For example, @?separator=|&channel=fo,o|ba,r@ will subscribe to the two channels @fo,o@ and @ba,r@. +- key := **optional**. An Ably API key to use, if using basic auth. +- accessToken := **optional** An Ably auth token to use, if using token auth. +- lastEvent := **optional**. An @id@ to resume from. Only required when starting a new SSE connection which resumes from a previous connection. +- rewind := **optional**. An integer which, if specified, will send a backlog of the number of messages specified once the connection opens. For example, @rewind=1@ will give you the most recent message sent on the channel. This is best-effort — only messages from within the last two minutes will be available, and only if the channel has been continuously active since the message was sent; it is not a replacement for the "history API":/rest/history. It only has an effect for new connections; when resuming a previous connection using @lastEvent@, it is ignored in favour of sending you the messages you missed since you were last connected. +- enveloped := **optional**. Default is @true@. If @true@, the @data@ from each event envelope for a @message@ event will be a "Message":/realtime/types#message object. If @false@, it will be the payload from the message directly. See "Envelope format":#envelope-format below. +- heartbeats := **optional**. Default is @false@. if @true@ will use an explicit heartbeat event rather than a newline as a keepalive packet. + +h5(#envelope-format). Envelope format + +See an example of a "plain event stream":#event-stream below, except instead of a JSON object with @id@, @event@, @data@ members, you get an SSE event. + +Keepalive packets are sent as SSE comments (@:keepalive@). + +h5. Code example + +```[javascript](code-editor:sse/sse) +var apiKey = '{{API_KEY}}'; +var url = 'https://realtime.ably.io/event-stream?channels=myChannel&v=1.1&key=' + apikey; +var eventSource = new EventSource(url); + +eventSource.onmessage = function(event) { + var message = JSON.parse(event.data); + console.log('Message: ' + message.name + " - " + message.data); +}; +``` + +```[python] +import json +import sseclient + +api_key='{{API_KEY}}' +url = "https://realtime.ably.io/sse?channels=myChannel&v=1.1&key=%s" % (api_key) + +def with_urllib3(url): + import urllib3 + http = urllib3.PoolManager() + return http.request('GET', url, preload_content=False) + +response = with_urllib3(url) +client = sseclient.SSEClient(response) +for event in client.events(): + message = json.loads(event.data) + print("Channel: %s - Message: %s - %s " % (message['channel'], message['name'], message['data'])) +``` + +```[curl] +curl "https://rest.ably.io/sse?channel=example&v=1.1" \ + --user "{{API_KEY}}" +⏎ +id: cbfKayrzgAXDWM:1556806691343-0 +event: message +data: { + "id":"YqigX7VFsR:0:0", + "name":"foo", + "timestamp":1556806691341, + "encoding":"json", + "channel":"channel", + "data":"{\"foo\":1}" +} +⏎ +:keepalive +⏎ +event: error +data:{ + "message":"Token expired. (See https://help.ably.io/error/40142 for help.)", + "code":40142, + "statusCode":401, + "href":"https://help.ably.io/error/40142" +} +``` + +h3(#event-stream). Plain HTTP event stream + +h6. GET realtime.ably.io/event-stream + +Starts a streaming HTTP request. This allows for messages to be easily consumed with any HTTP library that supports streaming. This is similar to the SSE endpoint, but uses JSON envelopes instead of SSE events. + +When available, we recommend using an SSE library as opposed to the raw HTTP stream as SSE libraries automatically handle reconnecting and resuming from the last received ID. + +h5. Request parameters + +- channels := **mandatory**. One or more channel names, separated by commas (or the @separator@ if specified). Non-url-safe characters should be URL-encoded (for example, @?channels=foo%3Fbar@ will subscribe to the channel @foo?bar@). Alias: @channel@. +- v := **mandatory**. The version of the api you are requesting. The current (and only) version of the API is 1.1, so only @v=1.1@ is accepted. +- separator := **optional**. A separator, to enable easy subscriptions to channels with commas in their name. For example, @?separator=|&channel=fo,o|ba,r@ will subscribe to the two channels @fo,o@ and @ba,r@. +- key := **optional**. An Ably API key to use, if using basic auth. +- accessToken := **optional** An Ably auth token to use, if using token auth. +- lastEvent := **optional**. An @id@ to resume from. Only required when starting a new SSE connection which resumes from a previous connection. +- rewind := **optional**. An integer which, if specified, will send a backlog of the number of messages specified once the connection opens. For example, @rewind=1@ will give you the most recent message sent on the channel. This is best-effort — only messages from within the last two minutes will be available, and only if the channel has been continuously active since the message was sent; it is not a replacement for the "history API":/rest/history. It only has an effect for new connections; when resuming a previous connection using @lastEvent@, it is ignored in favour of sending you the messages you missed since you were last connected. +- enveloped := **optional**. Default is @true@. If @true@, the @data@ from each event envelope for a @message@ event will be a "Message":/realtime/types#message object. If @false@, it will be the payload from the message directly. See "Envelope format":#envelope-format below. +- heartbeats := **optional**. Default is @false@. If @true@ will use an explicit heartbeat event rather than a newline as a keepalive packet. + +h5(#envelope-format). Envelope format + +Once a streaming response is established, every line (other than empty lines sent as keepalive packets) will be a simple JSON object of the following form: + +bc[json]. { + event: , + data: , + id: +} + +If @enveloped@ is true (the default), the @data@ will be a JSON-stringified object of a type determined by the event: + +- For a @message@ event it will be a "Message":/realtime/types#message object +- For a @presence@ event it will be a "PresenceMessage":/realtime/types#presence-message object +- For an @error@ event it will be an "ErrorInfo":/realtime/types#error-info object + +For a payload that is anything other than a string, as there is no client library to decode the payload of a @Message@ or @PresenceMessage@, you will have to decode it yourself. Objects and arrays will be json-encoded; binary payloads will be base64-encoded. The @encoding@ field of the @Message@ will specify what encoding has been used for the @data@ payload. + +If @enveloped@ is false, the @data@ will for a @message@ or @presence@ event be the @data@ payload from the @Message@ or @PresenceMessage@. Other events are unaffected. + +Non-string payloads will be encoded as before, but without enveloping you will not have the benefit of the @encoding@ field to tell you what encoding has been done. + +Note that failures on opening the connection (for example, invalid authentication details) may be sent as a non-streamed http response (with a response body of the form @{"error": }@), not an @error@ event in a streamed response. + +h5. Code example + +```[curl] +curl "https://rest.ably.io/event-stream?channel=example&v=1.1" \ + --user "{{API_KEY}}" + +{ + "id":"cbfKayrzgAXDWM:1556804156735-0", + "event":"message", + "data":{ + "id":"oZs6XaGYx8:0:0", + "name":"message-name", + "timestamp":1556804156730, + "encoding":"json", + "channel":"example", + "data":"{\"foo\":1}" + } +} +⏎ +{ + "event":"error", + "data":{ + "message":"Token expired. (See https://help.ably.io/error/40142 for help.)", + "code":40142, + "statusCode":401, + "href":"https://help.ably.io/error/40142" + } +} +``` + +```[nodejs] +const request = require('request'); +const apiKey = '{{API_KEY}}'; +const url = 'https://realtime.ably.io/event-stream?channels=myChannel&v=1.1&key=' + apikey; + +request + .get(url) + .on('response', function(response) { + console.log(response.statusCode) // stream started, 200 + }) + .on('data', function(data) { + console.log('Envelope:', data.toString()) + }); +``` diff --git a/content/tutorials/channel-rewind.textile b/content/tutorials/channel-rewind.textile index a964ce5105..054c556267 100644 --- a/content/tutorials/channel-rewind.textile +++ b/content/tutorials/channel-rewind.textile @@ -165,7 +165,7 @@ Let us now look at consuming the same data stream with the rewind parameter, but subscribeOpenWeather($('select#city-id').val()); function subscribeOpenWeather(id) { var channelWeather = "[product:ably-openweathermap/weather]weather:" + id; - var URL = `https://realtime.ably.io/sse?v=1.1&key=${apiKey}&channels=${encodeURIComponent(channelWeather)}&rewind=1`; + var URL = `https://realtime.ably.io/sse?v=1.2&key=${apiKey}&channels=${encodeURIComponent(channelWeather)}&rewind=1`; eventSource = new EventSource(URL); eventSource.onmessage = function (msg) { var message = JSON.parse(msg.data); diff --git a/content/tutorials/encryption.textile b/content/tutorials/encryption.textile index e8409e2750..eddc270fc3 100644 --- a/content/tutorials/encryption.textile +++ b/content/tutorials/encryption.textile @@ -52,7 +52,7 @@ blang[java]. } dependencies { - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' } ``` @@ -138,7 +138,7 @@ blang[nodejs]. { ... "dependencies": { - "ably": ">=1.1" + "ably": ">=1.2" ... } } diff --git a/content/tutorials/history.textile b/content/tutorials/history.textile index 3b2234cf24..2a2acf73ba 100644 --- a/content/tutorials/history.textile +++ b/content/tutorials/history.textile @@ -80,7 +80,7 @@ blang[java]. } dependencies { - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' } ``` @@ -119,7 +119,7 @@ blang[android]. ... dependencies { ... - compile 'io.ably:ably-android:1.0.0' + compile 'io.ably:ably-android:1.2.0' } ``` diff --git a/content/tutorials/publish-subscribe.textile b/content/tutorials/publish-subscribe.textile index 3c82930c94..16d3001cfe 100644 --- a/content/tutorials/publish-subscribe.textile +++ b/content/tutorials/publish-subscribe.textile @@ -61,7 +61,7 @@ blang[java]. } dependencies { - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' } ``` @@ -101,7 +101,7 @@ blang[android]. ... dependencies { ... - compile 'io.ably:ably-android:1.0.0' + compile 'io.ably:ably-android:1.2.0' } ``` diff --git a/content/tutorials/queue-amqp-neutrino-profanity.textile b/content/tutorials/queue-amqp-neutrino-profanity.textile index 08ef8ef83c..609d36512b 100644 --- a/content/tutorials/queue-amqp-neutrino-profanity.textile +++ b/content/tutorials/queue-amqp-neutrino-profanity.textile @@ -87,7 +87,7 @@ First we'll add the @express@ NPM module and create a @package.json@: "start": "node ./server.js" }, "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "express": "~4.0" } } @@ -250,7 +250,7 @@ We're now ready to start consuming messages from the queue. There are "plenty of ```[json] "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "amqplib": "^0.4", "express": ">=4.14.0" } @@ -355,7 +355,7 @@ Now that you have an API key you can use to communicate with the Neutrino API, y ```[json] "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "amqplib": "^0.4", "express": ">=4.14.0", "request": ">=2.79.0" diff --git a/content/tutorials/queue-amqp-wolfram-alpha.textile b/content/tutorials/queue-amqp-wolfram-alpha.textile index 57b74eb016..677be037e1 100644 --- a/content/tutorials/queue-amqp-wolfram-alpha.textile +++ b/content/tutorials/queue-amqp-wolfram-alpha.textile @@ -87,7 +87,7 @@ First we'll add the @express@ NPM module and create a @package.json@: "start": "node ./server.js" }, "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "express": "~4.0" } } @@ -251,7 +251,7 @@ We're now ready to start consuming messages from the queue. There are "plenty of ```[json] "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "amqplib": "^0.4", "express": ">=4.14.0" } @@ -357,7 +357,7 @@ Now that you have an AppID you can use to communicate with the Wolfram Alpha API ```[json] "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "amqplib": "^0.4", "express": ">=4.14.0", "request": ">=2.79.0" diff --git a/content/tutorials/queue-stomp-neutrino-profanity.textile b/content/tutorials/queue-stomp-neutrino-profanity.textile index 1f7f6d719f..4434a66bdf 100644 --- a/content/tutorials/queue-stomp-neutrino-profanity.textile +++ b/content/tutorials/queue-stomp-neutrino-profanity.textile @@ -87,7 +87,7 @@ First we'll add the @express@ NPM module and create a @package.json@: "start": "node ./server.js" }, "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "express": "~4.0" } } @@ -250,7 +250,7 @@ We're now ready to start consuming messages from the queue. There are "plenty of ```[json] "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "amqplib": "^0.4", "express": ">=4.14.0" } @@ -371,7 +371,7 @@ Now that you have an API key you can use to communicate with the Neutrino API, y ```[json] "dependencies": { - "ably": "~1.0", + "ably": ">=1.2", "express": ">=4.14.0", "request": ">=2.79.0", "stompit": "^0.24" diff --git a/content/tutorials/reactor-event-azure.textile b/content/tutorials/reactor-event-azure.textile index 78d2a6830e..825fe62ae4 100644 --- a/content/tutorials/reactor-event-azure.textile +++ b/content/tutorials/reactor-event-azure.textile @@ -178,7 +178,7 @@ Create a folder on your local machine, then navigate to that folder from your co "version": "1.0.0", "description": "Ably Reactor Function Tutorial", "dependencies": { - "ably": "^1.0.11", + "ably": ">=1.2", "compromise": "^11.2.1", "fuzzyset": "0.0.4" } diff --git a/content/tutorials/reactor-event-google.textile b/content/tutorials/reactor-event-google.textile index 25ebb68ac8..09e1b8889a 100644 --- a/content/tutorials/reactor-event-google.textile +++ b/content/tutorials/reactor-event-google.textile @@ -86,7 +86,7 @@ Create a folder on your local machine, then navigate to that folder from your co "version": "1.0.0", "description": "Ably Reactor Function Tutorial", "dependencies": { - "ably": "^1.0.11", + "ably": ">=1.2", "compromise": "^11.2.1", "fuzzyset": "0.0.4" } diff --git a/content/tutorials/sse-and-http-streaming.textile b/content/tutorials/sse-and-http-streaming.textile index 80a99b87b8..3be96f190c 100644 --- a/content/tutorials/sse-and-http-streaming.textile +++ b/content/tutorials/sse-and-http-streaming.textile @@ -128,7 +128,7 @@ blang[javascript]. throw 'Cannot proceed without api key'; } - const url = `https://realtime.ably.io/sse?v=1.1&key=${apiKey}&channels=${channels}` + const url = `https://realtime.ably.io/sse?v=1.2&key=${apiKey}&channels=${channels}` const eventSource = new EventSource(url); eventSource.onopen = function() { addEvent(`Connected and subscribed to channels: ${channels.split(',').join(', ')}`); @@ -177,7 +177,7 @@ blang[python]. print("Please replace your API key") exit() channels = 'date-channel,time-channel' - url = 'https://realtime.ably.io/sse?v=1.1&key=%s&channels=%s' % (apiKey, channels) + url = 'https://realtime.ably.io/sse?v=1.2&key=%s&channels=%s' % (apiKey, channels) def with_urllib3(url): """Get a streaming response for the given event feed using urllib3.""" @@ -200,7 +200,7 @@ blang[python]. h3(#with-sse). With Event Stream -For the Event Stream endpoint, just replace the URL with @https://realtime.ably.io/event-stream?v=1.1&key=${apiKey}&channels=${channels}@. The rest of the code is exactly the same as that of SSE. +For the Event Stream endpoint, just replace the URL with @https://realtime.ably.io/event-stream?v=1.2&key=${apiKey}&channels=${channels}@. The rest of the code is exactly the same as that of SSE. bq. The @/event-stream@ endpoint will give an SSE response if the @Accept@ header is set to @text/event-stream@. The @/sse@ endpoint is provided as an easier way of forcing an SSE response. diff --git a/content/tutorials/token-authentication.textile b/content/tutorials/token-authentication.textile index 721bcb73db..47fd9870bc 100644 --- a/content/tutorials/token-authentication.textile +++ b/content/tutorials/token-authentication.textile @@ -55,7 +55,7 @@ blang[java]. } dependencies { - compile 'io.ably:ably-java:1.0.0' + compile 'io.ably:ably-java:1.2.0' } ``` @@ -299,7 +299,7 @@ blang[nodejs,android]. "start": "node ./server.js" }, "dependencies": { - "ably": ">=0.8", + "ably": ">=1.2", "express": "~4.0" } } @@ -637,7 +637,7 @@ blang[android]. ... dependencies { ... - compile 'io.ably:ably-android:1.0.0' + compile 'io.ably:ably-android:1.2.0' } ``` @@ -1360,7 +1360,7 @@ blang[nodejs]. { ... "dependencies": { - "ably": ">=0.8", + "ably": ">=1.2", "express": "~4.0", "cookie-parser": "~1.4" } diff --git a/data/jsbins.yaml b/data/jsbins.yaml index d019b9dc9d..5c14ebb605 100644 --- a/data/jsbins.yaml +++ b/data/jsbins.yaml @@ -29,7 +29,6 @@ jsbin_hash: OtnWoay3QvApAKwR4sW+nqmwHTw=: ebizix R1DMwbF6puNE2KAN8YmbN3adxn0=: oqasav "+7edQ0/+RVdXbSaumOltjdMtYW8=": udenus - kFsT2eCL2NRWubzQ8OC3BMNZFYg=: ozecar J6B4SjIgGXie5DHoSF30sGPzb64=: akutul dqNcZL3zwOeS3PmMAzJ54wVDkNg=: utizit fDq9P84O672WgwAsIhYmPxnPLRA=: azifaq @@ -40,9 +39,10 @@ jsbin_hash: f9UaJsgoEvwrF4XhUjmJeUAZCnE=: imecoq KLOsf4s5KuRyHZaVRl+L6X9hsnA=: omihuk abZx53u8UDccCug5pAKKuGT5euI=: asasev - 0mral5B3E8UfMEPBq8MsCmeOGzc=: ipojey Q0XIw74KKfK3Zv34fqKSck4ip5U=: ivihud Sf9QPfpwxOvNcKy+KTQKXHq8dbc=: umikub + 6voT8sjZ4GnNONoP3sW/Uaq2GkE=: aqalul + 5SY9KnghzlKVnF/NlXih5jySnug=: utulow jsbin_id: adapters/pusher-pub-sub: eBxLJU1YqdX+JW9JQY70S4iQfmU= adapters/pubnub-pub-sub: pzHKfCW4/o2wDnCfbg1m0Pkl7v8= @@ -81,8 +81,8 @@ jsbin_id: website-examples/simple-chat: rnwPhTS4IMwf/LJyYfbrJnK4Axc= api-streamer/tfl: J6B4SjIgGXie5DHoSF30sGPzb64= rest/batch-publish: f9UaJsgoEvwrF4XhUjmJeUAZCnE= - sse/sse: 0mral5B3E8UfMEPBq8MsCmeOGzc= - sse/eventstream: kFsT2eCL2NRWubzQ8OC3BMNZFYg= + sse/sse: 5SY9KnghzlKVnF/NlXih5jySnug= + sse/eventstream: 6voT8sjZ4GnNONoP3sW/Uaq2GkE= rest/batch-presence: cHvxfaSCnHR6qzdutJZ/TZWK0FE= realtime/rewind: fDq9P84O672WgwAsIhYmPxnPLRA= authentication/jwt-token: Q0XIw74KKfK3Zv34fqKSck4ip5U= diff --git a/docs.gemspec b/docs.gemspec index dd1e8bee6f..6e9e0bfced 100644 --- a/docs.gemspec +++ b/docs.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = 'ably-docs' - spec.version = '1.0.0' + spec.version = '1.2.0' spec.authors = ["Matthew O'Riordan"] spec.email = ['support@ably.io'] spec.description = %q{Documentation repository Gem for Ably} diff --git a/lib/helpers/versions_helper.rb b/lib/helpers/versions_helper.rb index 97e24e3788..c298cbad0c 100644 --- a/lib/helpers/versions_helper.rb +++ b/lib/helpers/versions_helper.rb @@ -1,11 +1,12 @@ module VersionsHelper - CURRENT_VERSION = '1.1' unless defined?(CURRENT_VERSION) + CURRENT_VERSION = '1.2' unless defined?(CURRENT_VERSION) CURRENT_SPEC_VERSION = '1.2' unless defined?(CURRENT_SPEC_VERSION) VERSIONED_FOLDERS = %w( client-lib-development-guide general + sse realtime rest rest-api From 1f699f26bc4177e6fbd819c568eb2dc79e644291 Mon Sep 17 00:00:00 2001 From: Tom Camp Date: Tue, 9 Jun 2020 12:29:13 +0100 Subject: [PATCH 3/3] Deltas documentation (#880) Added deltas documentation, and restructured Channel Parameter sections. Co-authored-by: Mark Woulfe Co-authored-by: MarkWoulfeAbly <63646324+MarkWoulfeAbly@users.noreply.github.com> Co-authored-by: Mark Woulfe Co-authored-by: Kenneth Kalmer --- app/assets/images/realtime/delta-messages.png | Bin 0 -> 38214 bytes .../code/realtime/channel-deltas-size.code | 152 +++++++++++ content/code/realtime/channel-deltas.code | 74 +++++ content/code/realtime/rewind.code | 2 +- content/concepts/long-polling.textile | 2 +- content/mqtt/index.textile | 8 +- .../partials/types/_channel_options.textile | 5 +- content/realtime/channel-metadata.textile | 2 +- content/realtime/channel-params.textile | 247 ----------------- content/realtime/channels.textile | 43 ++- .../channel-parameters/deltas.textile | 253 ++++++++++++++++++ .../channel-parameters/overview.textile | 188 +++++++++++++ .../channel-parameters/rewind.textile | 145 ++++++++++ content/realtime/history.textile | 4 +- content/tutorials/reactor-event-aws.textile | 22 +- content/tutorials/reactor-event-azure.textile | 24 +- .../tutorials/reactor-event-google.textile | 24 +- data/jsbins.yaml | 8 +- 18 files changed, 902 insertions(+), 301 deletions(-) create mode 100644 app/assets/images/realtime/delta-messages.png create mode 100644 content/code/realtime/channel-deltas-size.code create mode 100644 content/code/realtime/channel-deltas.code delete mode 100644 content/realtime/channel-params.textile create mode 100644 content/realtime/channels/channel-parameters/deltas.textile create mode 100644 content/realtime/channels/channel-parameters/overview.textile create mode 100644 content/realtime/channels/channel-parameters/rewind.textile diff --git a/app/assets/images/realtime/delta-messages.png b/app/assets/images/realtime/delta-messages.png new file mode 100644 index 0000000000000000000000000000000000000000..3069f3f53ec915829b467388cc938e5822620570 GIT binary patch literal 38214 zcmbrmcT`hd_ca>2f`D|9Du_TrQ4kQ24$`C(dO%8Op$O88B1#J#DbjmQ=)Ftty>|sd zFCtAq=Itfu%l_w^ANC*Ofh!quNH9;U; z9uNpy@i%es`PvK^+Q)pd)0ED zqKmQXUr1ll{4mF;Z8fWD(Nn}-?lNNr@}@yUgr<=#H@sLnY_+yvBk@shni#C3lAxj7Z z0);~HHvMd$tyT*2^z`J++kNaAkc;qTl6e0oH^F$P(XTFFbuH-S$*7DQHLR?H2BWZO z0&K)b%n7qm=gt&L$bNp^u+Fq2a5D5pmY8*SrZk~Lzt#a}!n>X3eGvUIQOJ=o@{VMd z#lq~Y9u$fZz**9W-CDi;n3VL8yY=dzaiWGGKC77s^NO0=z+#HHA&u$p&mdCq-*0Y~ zf-mkf=mdG4%~`q5>Mh^^>&>iXsjjXTb=!L&U>j@H!8y*#Tb!xv*HqR-^5S>>oG4n}sq~9kxT526LHKlz=e-J5nB#-S zL)G%a%KUu!5E0h*%%Ae{zadYEW(+$j7^yBwen956d(5O>e`htz9EiaPf9*HFJ zZj>=HocW&aOf`I+B#74wN8?#I@G^`p#H_Vv$Y$$jU=9;nF+uDv}>WT3E);S)io7z(mD2W2DQ(9c?`>T%~$2Ho)57pwGO#-@;PUZCa z>r<(_hHSL~WP}g70t|m<4i+>t%*SB}{m^O&T`Yt|MuVB$u&F{JzXw!ZG#WO%qQhwY z$`cs7${5l^hCY*34;CE1>*Z3oY`-y-?0$VZb6h3mmN6?rzF%X(Ifjr!ztlO}V|>r; z#HqXT#gL3arH|b+v*eB>!B|^y^JHq5)xE03b{e{_lB&Xyi{s5625st@N4I;XXcT|t z+HYFbNT-{3Pxd16lzIY>3)V2{m7b(iyX2NkH%%T&()*SY#6}BeUOrRvrTrGN=kn9{ z>WnkstkU-T*@C0HuT-1|llR`U0|v>h;@~M21BOpZUdNBA4fWmy>C`uw16wkT)SJz0 z`o59u7@w|RZ5ucJPpNEvLWxjQ-{@D` zuUmMd5nQgzfg4&Nn!7K?E4P$f#0P$)va-ijZHPE~^Hal#99{?aU)RMyIh%|(;j~hZ zU5!u_1xuEpgJWFQEe?rL1KOHlr=$lax{pku#Yo{n@a%I@WA?TEvdk3gl@?!}kEYv; zLw{*^jqNNoMWaay=sVl*96v?AbSZ!}y6JOl6rWd1HTOF-aSMa=zwMT`Iz%^IBZ>*Y zX|P}A;#9|3vM;Y6u%?lGxpW@juKj%})2)Ns*`=-@W$^RT30 zS#HRnX(z^$emzT&(<4yb?b;_P`LxwLEi@SZ=4g0+b0CR4bzbPQ3v*9#UL%Aa1c`aZ zWvgMiUQAxrFIO8bj77tv_YOHg)6}87dWP*j_pw5FK)`|>2tK>Y7b11PNPNM%<8v5A zoniyHRk_hC#L!2*^Ls>rdlUGl;93@;k6ay^>EV~^29;GAvN>kUgmztvN@l}}*2**7 z{hC3<%wx`RZOf~R5$L*?7kGL}d>u>|za`y7TQHzyl0==Q45CU{taCHBFyNqR8K5p7 zf5+m8jfV94k{<>IgHkjZLyJ}3{c0B6d-jiwSt*aIE|qZi3wEQcE4yj0?_+eGt>OHUj_clO`8$t8`1)lPy_QVqOk(U z>MSu6E7z99Yh!9p8Owh&uiBgkd%wy(BVh7TPYi(WUK*RPioo>DLrEN9;w~bn^J@B` znZ^$1`*II1rC(y{2YT0LB=5ETIK~m&Eom-ZOn9|-C&9=$gzAE)@2i?Zx&7l94`YVX zL?>?clHqoqsWVNa+p{3$fqoG#On<}dER`H%iCO?G=P;+_T_IdFUaD)_Vao)Te62|o zLV2&xFR`|KZ8!9=ixX5Im`jpt#3qTi)v~pMv_yP7;n7%q$ka8S1j)bpa0IY z@U8jnV0VoTR7E8mV<1RBJe)OY%bh+A+T9g%8WB71b=B*P*_v#0+piEAf|S&?SCe~x zkXv-q_FFXd$Z%!~#qkmEl%_C5o!C8#>LOf*lBtOX?!It9ndSd*zJ87-jZ=Sm)WH#g z%_Za~?p(1_blyA+gy>o@b%F$V;>VFcR|npz5;l@FRvV7*bJwjumNWXxsZV@{kyq8E zY2UQwbP*KA#mNn8cz!xihWp?g8fbtS46AnJuvwsLz)Xj2^IVN{9H^g>zsekKTO~*N zDmaS~vMxgF*E%;qXB!j)U%8a76KN_X|iT2Y!O#77kx zddm+9mDMeU(Mgie{4fS*4_Q`FL5q8QHKpg(<~5D34{OgxX7$=ttkbq8-8}n49UAuq z5iwKsgU)qs+PlKtFP?ov;!@7EE>^z*7aG1b=Ufz2cW}PiuU#;3w9BbpHqg|)`-8|N z7Ng%7bUIXK?v;;mwdaku;+z?cpXQaj2tB1d--ul64qv-9h?xUbmh&N4Anv}=q3n!~ zjbcV&VP95I3od zHQCs2bt+JP03%Gmx zJNEXR4fytU!bt&eH+mCU>?i@fk&am_!(6mVJciq-xFe?a&)KDC<#WN9&ISXvq|=g8 zPq|M1$^aGHe25dypbvNYfmq*$HqdnRKZyTj_(d-C^dHlF0Br)mVcy*Ywo&Ti8+uBczg^0xWXihBRU%1%(*A;_q)bV zJ7f64jnx)Fy41mmd0cwz<`bNJ*flNCDOQa~F8HFenV>J+Ol>@ZFp4#W5sA_k%wdrr zFIKGn*+pK1M=#hT_qdIjB6+eM`oRE(cC<#@zgg32KI^zzBEe*Fpo&9M=tT~R23$C3 zRzViwq>7Xl5DQ)O%H(8D&`Q^((`NsPs(G&L$yRpc<&)g4O%IioUROL^!d+M^U-Id2 zW3w;^tbU=}Fu}AuVB~?G+sOb&^cNYOMK~m_%-FPi#i4S)XTF?Z2ebz)*6oBf}$el z{kwvbq9ryOIR`3{4uK)PeG$Zk9>428wun}TlL(D4n^;WUSBfhy=Fv$tUFIAUTqe9z z`f0qh%u~X*@pp=%U9l3{rR8NOjWE`_rNdg_*G8}V%D>Oz8SC#D7CeU+hZ;l7j<*-K zLhC+$%$4T=4o?1K!&rL``;W6m) z6O__m-%mivW5vSuczym#Xt60lr}&MdX04gn*gfx8=CQqfg~zcrvyEYKbibE%>^hXm zp&#^M`GQX=fUtb|7f5e&xLYkP#z1bza{g?}u9_l=2kLzxSQ~cO97HRh5W5x74|=NL zvB1H?&OYgUiZ^}#N2+eO2%=BJ`l50~9HqA+L}(=Vjaa~`nQriN$}clP`+3S*w{cjQ z!1r0ABy+V?r6*td^(b!{@3%LmVm#W65@)mK1z4c7_%ekmlW8hlqp=b3k+XKG^HNv; z2T_H5pj%S;qQ1`dlG+ske(l|jC}b=wHRerCGB

c*D2@`)BJGyc%fx`LA%bmj#m*EvuQn% zK9iNa2hSvS78!1g#I987C1U!a#B|RrXL~E_MSaPMu~oBNewf#dFWIn4#M66GtbLvs zZk$ZV#{!kHINeGQhK7a^<)^lj9F@4?8Z8$?j`6XvC#Tl)PS=;G!*pBUMScOggN5U& z!;M`md%6!_IHVRO_VznSjeK1x$NR+h_z{n`(z7?%eo{_cVn<1sm8;Vu_nI_gkEx$y zUz%M3qv%Dy`13PJB33Qx==YwL1VY(@F`fqomX8jka@t#IZOzN;_EPIxfBcN@dK<$g z#8OKUwNMC&E*@8F`FpvW`V|CU-?I z;#Q@Xgsx#0QQxb#H>C{=a9m6)8pSrq}?1J|4cFq#l4IKZaCzqEDo$nW0Uk)kIR zJBjiq)RqwBbq4n1S=^REae?7hA zInG$}m3dKBI$j@edRek3gXR7UF(KG*z1O(<{_sqs`i~zbIo`E@TjlK+e5L53vxrGi1$am%oQ6e%BVj8xVf_WC1fe0j2j)g@uY(mw-@m zD4NG&$IxFh(=z|Njesevb5{>e45T_pI9)}Sxxu^7syRlQW37p}1J8;#UZGYkWe7;< zqo~RcH=es#z(URhWU3J}JXf$YZ;YhNeOOLC$pscg8;|jgNRWBnz~AbWdAC{wKfmR^ zv;%#F=RMuStY$!a$_bsRA9VeiU0KAij{u}q&^;{hy!Jg{v8dl#PW7f8_;G>DHa6M@ za#>JUC-++HW;J_GouzF^3zWn*q}E&dA;Ded@o7u^aWzpmDm1+Q6e@qVeFtP(%|h|y zwYpc!W|}u8VPht1JZIfYY2nxb<|?DR9jKH6hPbrT z(xxx*);J9M!<|X`=+llfSMmO^L{HLdLPU_$T-61Hcm5U48)Al--uM3SqXq+CC*P?( z=J{DnzDT1e0|>|xmGFUhd}2EY$%K{^>d`* z2QMR(;b#x2j;6W)#HuRdl@GB#4DCO-RmSAEb|uwyJ5avGdE?x=QENLE%`&gg<&Q#` zym9@ws#wnIAV-aPAV)4n;kn1FuM(5h%Bt-5NgZV67hfuq)l0wAPuEO7v%Y?+Aw0n8~QHu=f~rilw5s%5l-GTU?hV$IEX!h%c%7e=Rn3OmCS6 zRhwAg2v&viyg68`=Q*5^n=kzt!5HGvjcz1yoY$}_pC)JM;q$6356`V%MMCVnVS!Ci z5$JGN3;Z8gpl?x`4|L&Kge#9TYoLi2Sw2F?;cm4Gl6Z-zdZFX{?p67&$QnJPm3Jpv~~GM_Okeon(W=U{r3qYfi|9dAnlot?eAk@MI@+C zq9KEK@S6JWl(8@kgq+;>rl=p8Ger5H)@xpt524$n3Ux!;_P2JZQuxcqeu~tEU+5IM zF_d$`!HYr&Q(_t=Z3EOv;phs?Zy3(go*belQ6Q@$yDYPy3x(?Y&yaD|8*K7ml107; zXc#qC(vh0aWUzAZzN?RnzjZ%@08cy_IxKaZbcNSez4_Mgz*-sRhw2FvQ{Vy@7AFLeWF1l$SYL>pFJ=$$cf3iJa`AL{ zUYH2EGK1F=XHONL(y>Lq(;wcfv!o}Aa6l0?Qthz$B)A=XAw+i{B{L_`InVC*-GIyymuXifb{(x81E+PcMROb0%$C{F^dssA zX!fiHkxTJ<0-s`{*xsYyPN}6$8jUf{75u5XjEc+MBJer<%aVY2tL>uInu2+2!Ch)N zfiAL|no9S^wEnBW*ID>1$GaH`r3H?6Rl*`C5WD)}0wzNvqb8Sa%`2+@?xm5ju0jdFi;?D&+>R)zjeJi;}`Zr|lu)@wbIAx2{dM#jhR<-wdtZ zy#HpOXHke-h@1D>qGtJ(S8vu}y2K4Y5G$u+>eBj25c}NH8&X6t{PmdJ(~$A5Em;Sb z#&dP0o%wD9<);2EB{X4Y;0GXL(N9a;uc_sP{Nkf~w(2aopSz|kG+-SI_2j>EENrk! zK@21*bqx*ceVp4wJ2hIEq6vDF`xW{07_1XtSmk`3+LT*HRWWTdH~;#?Mypa!>yyfx z?115_x<^SZZRyjVZ7g<&ISEdrT z>A}ognE3RM_GZeq=_D6}EqkL1>ozk|Ay|cv_Tzak^8u=HJ>^gV;=7KIzen5`o`Wpg zg{kNjsTo>}DLrm^UtQ8hpW#|Y5R5tbZjz;$bb56{lJ!=McW4wceV!*oH++DNfZOX( znF>Gz|HzOm$29M6F@#{o>%&Py_iJroIWB|)G%iuHGSlm+MnlL%g2PzKvw4byo6DrM zVu6klS%R_cJx%V`i9-9#>sPyQ#1y+oXjMh}lVsBgmR;T6FS8z^{B$jz3TU3rUo=-X zO}YR!3w%JOSHkNTZc~MA5G}drKfa*YXBg}_TPB6D_twWwHeNCFoxvJxcMrX9+*67+v z*v>dDJ&cG^wr_64T)NOsn|8W#)&73e*HVK0oH|*5(kFvKy>&I#5s8CVU>@ywxbQ=B zq{^`@mQK6)jmQBAfiYS(ARdrHbB`xuS1pT&0D<`YhjPr0FB*&|#L=$p-5v$M zwG`}$xcg^lbYl%CJ|^S!P`Q;5#h*G=FcJ#>$nnGhcd2zPS~QkP;88D z)I6WDc3DU14d6YxWmlH%)iuLms7LJIf2j+-GBH7$_K0|S!g@G+TLm&D!V#FvNZ_5AR4hfyb4X{iwxu&@LqIub@C~vhyo%{Z^b% zdOLiEu4zCUsNkRPRXc=tiYde}Wre2j!I5rbrrerBCZ++U#F0(}wIwP#H(Qlf26~2u zjkZ(Z+bqU}XwKZ*G~6;1lQ+v|P5)GpKp>lyT4lZXr=_K}m>`9dlBU%v=-`NA-fVZ;lvnfSsCPy^_AT-0#s}_f4$rO3 zATKX3w0wNK(IFRa31#J9Z0bVDPiQRqg~v<@>}=dLE^@ihx{0g?m4PlQ8kchM2@M(Oi*(p2eNOWC4R-S$Rwk%4ZpqbY zR6KdJw=e<<`-kTT7=80{^JE8MDSV0)S%|Db%M<)`08e~NFu+I^zC zu)%qXo-*r+U^r+tKI@=DBb2+&ySloJDcB~=8XJvm`$C&lU+TO+Fq$EI7usD1xdmo_5fL<8VZj}5{=xk|SFxvrq z$VEEr=;SCcCNPpDFz~AU{Pe}ab>OrPzhC9*ylAR;dd03=nzW2Zf$ z4J@GGu1D6ZPp(hqm$~lpxYuma!sj0LtvJ970OhE=Aq-ILr?I18Y>=;?4w>J5DbyVh zu2`}(Ht-DqqW-5d;Qy$&A1tdnvbTM{H#adln!zh4Cs$)}vNe{Pn(FY$W+Kn#?)lwf zb*|mHre@!z5d)%OI)S6braw(&6Hg+i+NaNCU}Ey8xo?bGbi(`Tp1F?eRY^bl%+m7; z0vyr3pbYlxl9GeiWGZTEYD&u4e8a7@{N;+r4K<}!j`sGa;=zIOThZK{oQi9H)&XXj zI$V`6&*vR$t6M6$1GtYzM9g>A-+4Q#Jg++`0fUorM<|%~Lg^*4c=^T7$3~7=gGm24 zY#zw`^`3lJReWtuWMv{Q{8b8!d^E`^JXR^eB@7Z4G|1F*;M?mAp`d-~3!_}Rd#GaT z;Bc^M>KhrIot-TsBp%1wOJAy2F3ltSqLvU?>m8rTx%XA@>o@cl5g1;uF&6~F@7e6N zLjs13d@pUe*?^Qb?tGDIq~QouFMPX!MyjuKug?P0uU8B-l-m>L;N|5->UOLX-d)mI znpt_q1yZe5u5F{?<+X9Q@sb&jo zQHoLT+TgFg)nM8)+OBrg18F>ZXS-W_DtsU1t_sTIP}K5h${%vihZROC4a_GAAmVvV zO89q}A9;z75_Yw#8eTQJnP}D{ILT}EU!Jn+$r-@mx#CoO%S<7-U<=u2pZghl%f|@2 z%2+|JZj%1gX+L$OyP zpD&@knO5iv-pd2)my8J4;h=Wodzt8HSF9?DN92rKV^RS zjJ8y+wd+DsWy?pte{+i1HTEHbtKZ86W`XqZ^I)b!D_Hgrb$2Z+T8;#@^p;uvxsZfV z4&eCa8QeM-3NbB8aH-a z?r~)1tKZ_|iL9*5gmza;mzU{BJNQ!5&=C9)QC3#oe=pWlGG}H)NmOTchl=GN(>Xt7 z?9HJn?=IY)$R|*zF{zanqxTA~ev9dkv!;3rcu|;(>{=Hnp28~%$WJAt5n^LM@sW5Y z&ps34{o^dcbfgx}_}s@n7Z(>FHFL_!)Ya^Po(4p3<=kBVz(93VON;o$`iXU%6EIk; z&bs7V)W-xaeLfLnBf^d8Lre2@OLD1fxgR7@J=2e#4)~JQ+Q-)hWZsdlN6UgTo+&L7 z8yFfAynUqr7{-qyUcaV#pY%I)4#DIyVAq>{SmNsY7Hxl?S@@+pOD~E_rr@pitADL( z`!Y%qV)hP9KwLi_PAAme;-&Tc`E!e+b4G*A2Nvt^f$G&HmE|&Bl|>)9uiH7j92=z! z2jHMAjLeL8a;a==Z0@3YoJ^WIZ;}dwaDa)?+;JvWm!hO3Dg?~;_FwY}GCpAZSNrVc zgqR&B;Q{^)0y*0O+unh^$2t8tb(zlUAsrpijYCnqzqdDOaG1>HTUAq|1BG5}sEe}E z(qBw674xy^cFTvHYL#cQe_h*=Zi``4j(NFEe%4LrXlHG$5F@fNnD|BOGFuNxpLQbz zqxz6;y)WPR!F{VJhIRKwEsHp!<>!PuFZa0RW6f2pf0vL&^=$lW(V(WXuWxhsX$X9O zzS;maA}g;~W0y#7VX|VCcvRxOt+E?WR*yYC^h}oG$20ZEVwBGonu0=+X`YWygM~0V`DXW7 zD1{Od5_XjVIIuS#5kV4{G%qRVxajpnY|3kIpud0hAi}I3{WYaK(ZrB4M9`tLB<^ms z@)&;W$`Pt=pHYK!LfCoPgvs~EO^j*?!6?!LM`t5)Yf`1=ug~* z59Oe}OB+_%(qi2y@PTYbxwqh6`bmg}z&R-$|C2HNd0k_J1j5Z7Zb)5(o-&BbR^l%|b(@#-Y(ATsmxbGd6dTvxIP_DQZpqC8u}LrIka+9# z^wf)wh>#Hd_H=4}+l0G{8f1Teq43+2*OT~2EvgUa)iYSg0O|6R&oNZd(NS{`oQ7^h zF5A_7eXCANsrw7I8U;fcc%1j9OnN`|2Bo~;&UcM^yr$^q0V)nAC}W{z71QNjbKq?2 zm)@=4Qn3?3YlgPa(9rCfTzEJ-U9B%xd6d%*?!Stz$YJgIB8N%93qrA|e zucc*vQ}lL9YsXhP)?Qsl$5PFI^wSUDgcK;nFaUep&Wq*BxzvF*u#>nmemK_6hyB|A zVHFwDnBZFH{LZRRyH_K{X^N_am;^RO7vG24QX7^{zG&)i>(jD7-u1rM3Vf8ZRbZ`-~XqUAxPZ?diaR^>x8Ak_fQ1)o@Co zvv%Xm^fdm>_G>j%N*qc8DHU9nuS;5kHvyJ9MSGbeb`d!NXAidKbfi#beQBof^UIPt zqXom4#XX{@ZKNF3#$B(Bj%hBHD?WyG(&}E%>8~`%q(^`Ga`tMeH@%*C_|JS+^zq-z z6J-g8keZJ!GcUg-%akAp1X`8|BUD8E*52X_)-imj=*fn%-C}-Jt-n3LMYuv@) zfWy#M+xR+kaIpO5H>W=R5y$`wdGSI_b_RU2-@qxV?bctoaDT9-M$p8TtajN8rOd75 zs`S2v*w)ro)6>;@tAWl^y=2rT&Pg3z+S;6*%_``i)4l&n)ArMBPh(TlB6rn+VheGs z2moZ%xdteAzo;|q|AnC^q7q(QU4)-qdt5oBPXx;@h^e;z8d=1jR$XQLa3PWYVv!aL zxM{E{iAt$7`t=)JY|tuIoDAs6Di;jov(ZuEXPQQDWDCCwEQ_B**Ui0->7KJudE&&# z2eSz}-y$q9H}`2oeVDH}1Por?SabN4pXLR2!=Xcko9Sl*#^{slWKdjfFrRCbjj?+> zOhjicR5rH1?h5!i9tX7?}q7`{4&b zO|8!bP!Z-#Zsa3M01;y$O@SWb%dXDjSC6oPk3c}vaM=uiU}cziH{P9V-1`SnFcVPG zr8#AG2d6jNkEys6AG`W@5s_!w;}{03$DRHg>t?+i@c^ zA4muA^gLmdL`i9K^+EX2n1AmYkH@AG}XAp=R!+O3?7~3a04KNs?od@ptbk7 zr9pY1dOD5HzOWh$1B1a8SJN!ICK{mfw?Cx&;8>rt2=D$LSLU5F=wbq3tei%yOtDeD zX1DqFcy5i(FD1!2(qbqF>k(V33(;O=D!-NLW0BN9t6ia=-ijrtiGDEL6(#46D`Mv4 zvTt@*>TK3J1EK8Ry4%iGz!5VguI~NI<7^D$bsPR+5i*hh7Kh*NWqlcoO!xspSvj;h zPk!X{Unp0ue_NKA7?zi{4qDGtEt2Yifpxt0O_2u&4^K_LJZXGd<=%VoCsonzIIb>{X>{bo9RC_RXYspO} za$r^M!eb$|VkYS7Aod{pAXc0^X;STYne!SYnj3Ckd=Q(}ce2_Qvbm9|rw3p>!n#GS z+o}4iJ?TN7Z08Ec5A15W3prPetN^{GiX^VqOiX;KD;4O>!X8@jAmDHsOJ?p%i-)0Qb#@K9xL81}T4hnl zkRWJ>qv6mCpKF$IYzQzLg-rI`W1TW2a$Eo38f zte8=|{3=b%J11Y<7ZLZt#@>q+8u%a)1;F2(X~W721e}DZL=zk*IyE--!?4kAuA=Ht zTY=~SaB;4I|8?=q$Hxp+K+L&}RWURX8(7^OOw@wG_PYrX#cB7%aHF>#nE95wJwG(K zD#@3HhfJTfnjm4X7uyF}A7nuS}LY*xDAM(f0-vL_^7% z-HU+oFfpY#Ce;~+s7mzPM?nM4NrBA(d zQC#TblftHsBKG|t@8V$GXr(8i6<}E8fRKvfKAo_zzlsEV92si}B|if>eD1nOn0m<} z`BFv~#bjt~%;+OS?wx;f&wH)wv3vji?m?`kyw}oY2bF2|18Fz~5FX=H$|pG2u+R$X z9KmL6pT$pe++18{k4@9PUf9_K+*T;Cl1!mQVCeE!-g{&34Koc|it`uC`}o4(M2=iB`CvM46IvTKeZi zmwUbQTyipz=L?#PoLw|oFyx}LxOB;XoL!q7KHb3N;12%YybVxT|JQkz>#RTovO}h- z(vkvtkpDq*W9c+)x1lcrv45f0=vM3)CpIyCvO(wcRj_cqDL-sy(IR?|TlZV_ujvr* z)#zdM#qTDG#b(aFRZ_UxZNd{GC@*)Q9!22Lb%@|LoKnDSq0qQ@T&6ZEs@JO9SZDoP z44@1sj%z+J0q5hf{>d<(TPv|fQtLc^%l6s9-rU?=OY6%oaj~IgBen7jykNnJ=tJS@ z7K7RG@m~O7)r$-k3Lad=rm7$B{gm^yU)xuSXT`$DpivMxz0+8G6PMiX2p|&jY0JsG zza!(W_3E8-p_e;7@o{l+@$rH1y5K`qsE*E)daY$uKDYdMZbQq`Y{IToK>Q)(0#FRU z)o|6EGSp(0nU?T{Kh}DG;Vh?pV9)__znTluI`p)Ks-XH$taV_Ppg5>&|2>0S01fqKsl9${9#A{6c zWq5h`K@zRz(=ooMbij)T(=(Es(Kx9uVg5PO*A4h&r|8}|F<14P}|N5jj)j+VM3 zV~^8CJWHv!2i*H*>B=fq~t4Eod{#$zh5x2|{=FZu22|&VDoI~mX z0R^LC5ci3fz5jdacxBR->h@ejs-3n}&Jt(w&duWfLWdyZLjcTS^H>qRsFE)EMqZ&q zCWhP4gP2Ai4fSUJRvS?PywYx>OpJ$4$bmlU=$(j$#I?ggFBMNQeZRcRTzRRc&iW%U z^RDM(0MZ#pkoIABNxS50zt`5$*|5tM0G1>ub-N_6Zb=u;BLtO=tu2_7GlU!eWR#Mc zx_dvX2#WI}Qoe54KT*G4`m$S{Q{BMeq5DM@M+;_XXb8}WueTW)+`90i#80DD4pqCB z*u)GLNdG|$1$&N)$4?<_^ zg1~)VRM+@~ZNrd+M0q}1dqvouS}oxsPFu^mPpLZYrESPSSfv#`SP%VIE1G{Z#VLm2NBWm7*$w=RJ@;?=a)%RJ_vAjJD^RsrmD&#ySs(FM5-?_4w8dYm+ME_c+^d&g5_JH zSKiL|P_gPw8}@EX;h)>5bn-?E3knK~i|wtg)n8;`$h8Ri7;ZNO41pkQZFhEeuRL!9 z`+cMbvn+tt1C(#=Cv7Qr!twHSQVV&pE`Az3+hK~*)-HGeuys^?f%e-D-;*tCC=m0J zL5ciUVB6P+lOo^087y1bY?s_{UB@~{<7%oX|5GgX^7CA`B_&5Iw+S|LgES~5&5hb2cJZ9FKin`vJ zpRu=zY8$~C>+oT5{t4AxqO8v5(z$13QV)sU>bZ0uFFjEWS@Smc6M7Z0FlqQ8;QO0k z#WhvVS9>=<%^=|U`4yRRJLtX4V1iL_vj4{xaG;Tl;)c~Ip)#xX{j{fmYH64Iy-DLy z!+e!I3jp--ZpK&usL#wOSq}Js>Bl!@#p-rHzj`FktZBax6ft%)c|YF#V1i zxV{my(W#{U{$bWX==UE99;mc@_8T5n^)6Icv69x@!- zyj;9oU?niuwaL`6`7{e%@Sd**=Tla`W_FN4QjDBp14w5^wca7PWlkTPh*EdD7IH z?BRNvGmcJh_?M3CYdvB9kOjqwy9vjNQQ@@q%2!D;KbemPkLm2yRD3*j zaRp}nbt8vfVQ`j@()%awlyn?G3m66$JG7+mcjekThQ;s*B zXI?tbbfPelEglD92|R_1%aw*GyZXVw^Sr_IXSNk?lL?21z8deJ>@smzGAYgUvvX6t zE!3Q|Tzt(eA+bWQ45x%+6T&@YLkRrW66|8E<)(DpH|VQNOi(8&2&jzACq|o4f)im8 zWSXZko)U>LIVtWxOAjPi?E=n6)w=qfZR>g~1DEyeetoMZ(7GQpNgc<2O$Vp#1Uxj_%xSj#C_Hlj^%)3EyrWKTy z^78ORTr~o9mg0knp80A75fRZu$<&*NJX@rw^VZuiz)SDB3~UON&vJfZqV;H;EzX_l zk0-iNuli=H6QD|JSR z^V^P}pPxnGTuhg^j%BiLdY&brVJG>-Ekf}3Jh(|MuQRHuo@+c&S6BZI!|;9V@9$4R z_CESu@NK@*Dsrf&%&d#Bb}`Mg*w)Uj%3)y{CL$tIbz2m}U@*M_MxSsQs+?Jb3thCG zf8`M;q}YWC^!1tAO947izctX-ykp3wZ#Pe$zS27~Qp-88e%@pz?Ka_5Ci<1;3Dda8 zti)hn-c~ba29+*+lCGw96X-bQ-FjGFU)kdR!)K*ve`(*~w5vS*s3Cz%3w@L5r&Is> z+pcki9>*Or@Ci`3Kk+I0{7b|brKRQ6>T}^NBvHN4Wr@_nN88J=ROmrE zhP*3*g_zI~8Rn+vL0gs)K!x;|YYYmAV>6TkHW23w$X6OS-L~`<=-X-BtUc)8j22yRtTdZOgkXBNggx?@ie_7g9=#m5bD zF)d!t;}Fg>Uc=_;XK3CfJ`P>Lvt34a4E30hp7_3fdG+<4ZT@&IgiTsc*e~LNh?19@ z(q0gknf?dRsJgCyu(7d)=qN55IyP5J)T?<=(cR}sItfaU*mBB^RJx)1k{Zl#m>Ccx zekGPZ@wCb*3%Zib*XpHdvk0BLw9ZYq3jjeLP zP9XD%G=napfmHlYg1!elPjUOwi*BJPai~t*Z;t@@0w}8Zzt!I;Q;4+kbD$~-G`asn z8lJd}VuQB$tIPnr0H~EARCfnb-~g_tE7U`=(~5fH`|YPRA^vnO!2OzZ-YmoN7&vUh3qA)uY5 zZ>p~sb~Al@a9dm!l=?>-MI1>676%_xRmH1S3mu&b44jeGu9xM5`;5WiP5gNme~BYd zw?$}&lGdFs3Ubb=fzlJM&TWY@C8(wLSG;7nnmnn_)ZyQ55 zPKRpB3$o9YfQMuC9Q5n&WXC=m#`vWnhABdmy z(9PU-M?{5Aos{}IB0kjaelo{-e>RYxdf4KPqwg5zy)vYWzk?6h?Bc~c9l5$y@>{G^ zuHK0=XDK({y=zj;paS@6^~uZaLZl%X#uuv|HG#a+`87)*bSm<+im(*u0k{w0W9FD# z@GdhPGw%6VQb$J_Ho*JBG1fQzM+n(FYRZ8pnXin0-P{|XODC%R^e?l-2I;FlD83`f z!*6!k^dp-+8wZ3-@#GlqpI!go`bmoa@%+l|(>?1fYLhoJ3Bal!p&q|SX5CfCWLUsZx4)JV_3RZUToQL3XiNpD?>BXRpcO_| z68I&O|JyQ{y|;ISk^gIeOiQ43CD_jhaBwn^-@T}{waAHNa}-aiYGA{?d*xD$*o)7Nsawy-Sh zwbc>%>K>EZaAJpi%Jfws#(aeSePF(E)%nUq;DuY5_qX#1AD8FLN#;FjTU>=k)&o-`7KvPSrH{P-EJVdHut_Wgl_ltvfLoKZ1*)H zew}%dlQ5AaDi!HwK*Pf-_Sr9~KZPvwkgPx8XLUY${sc?hs5KNzoW>=5IOg8t2b3~V z7d#{|ZRE6HBxLYk&YRR1y!_lIHw|mWd#<(#OYDBvaYUAtz0w0!5322*3(w=0_jbsq z)BZkNLLY{`n8hHk{Eg=C6Z_6xe@ccW9^d-i4QRZ6vJ9R!&&=gDk(hPqH+%VvZ+M!7Gm#Z`BQS!ez<6$&dPY0O{D&MTL|4}AQuA`V}{o* z{qdYF_`$isQOGqP`-@dLJhB*Ob9@BKk?Cl_zbVGjG2UN=H`G>d`y9}ejvRn4eMe`V zVbcQFZnPUmbB^cPpcnU=pSxYY=UpWdRk;Z49B4iGQm{-|RbDb=|eE_&=+*GqUXynin7MoA>Z3jyA~H88gXO2ZCm)iG8xG1_BlkFDQ+ zKY9N9hjqQPKC!LO@0}16`f+7!_NMl{EKb2pQp=IWGZYPa3E3LB`$}p1DHq&Bd+7dV z!i`7b!(92gdc)b$RbrF%*i2+sN;_hccXrYE(~F86KEsTID4Qv?BYK*o%3~mXBk0At znp4T5snnkd!(+zMNBXU`HBRl6tA>92{Toe14u+^jEl6FzkAl!Kb)UbDX_*^SShSzr z{CL{(8Cz~7J@xYMY5)APu?y@cI=9^9pL}Jbc?&$ml`^mtJJdK%YLgus`>VD;Ep+w`|)>0p|`Qi4EuY^-2+~H*js-_Fim(-Uvjh&wZ z-2h8$+OcEYCW}+!? z8mY{7#x$#cyTG2|XMZRC!b-QP(R)d0aAV6970|tzbw3k zae(6d{Py$|{LSaJl{DHd$H0O}Z?C1h1)+|l_IHAAqc*3spjqnzg3${x^Gvf(ATnL8 zJ2K;ZV&}b(s;uir2&kx{jG8C%^|=q|Y%=phMz_gh^d`d{1WG9}SS6No@{-K&pHb<2 zi+}_*nvQHZHPS+9)7jOJt)JKVp4~Vlz~A!7{v=FH&EbR|&>BqR)P{j$7X#L8IJ#`! zF^M2LuIx;juUpQ*>#T=!iNS#PTreB!b<}@~)7Pp0QBZ@c_TMNsc=`C~o@Y!YDAgoH zVE@3sWjR=g(`LTCC5o!lry)4ePBTQzDGj8ERIeYBBf`ZHgZgI|2Y!Tyqc7{~HlkIM z1bIq8h>#5RsA~f+QX)=-GI+jyR3~5oH-BoPu#6AGl1+dqbvWt|*}5Zz|7A z;HI5q-JyEwFV0_ktg$MU3Hln9akPm(wRmGv@@ernA?@rPfhb~;yk~}!gmR`prB|HP|{+?K|WUyVns9rxM)jjlDj${*rUnmy9HAN>BD&%mGkVBf}HYUX?% zb<7-j@kcF^x8*x(+v&c!vpX*&o-M?dn50_l5P3cgo1V9oVy|28I~7@ML)NX+3h|1S z;rXm2F&^*sx1?B-S>#progXw2GCJ{`g{xWm`}h9~v3O_TWf8}|JFSa8)k5vdIB@8X z$t#IGsa%*-Nq)+y&c$+{J#ka0EMnxMO%PFq$KLF_oIiXrVy1J_g0xk8WQ6lD0wX{r z@(nFbDpK~B8TDr}DJ{A<0PCl^j)tIjCyvVOM|8Jfa~tmEx59hK3?FStik{|3)v}{| z)|%^>ddOEKPntlz)hkA|u~AXEo+$QjhH{zDF1cnkA>HdA%`E#a&E$E^?)=mAZ(@Eb zb?Dv^rA^m_fo9FyNf#S^E9@!v|Mht6L=XQ-uPF~aJLv48$%j=#wUT{bmqqQ>eM!G6+W>#2$BW>P33e_}h@lJ*`Y)IQP?{k+6YfF)*;7+5)0palP zM!x7Bcp3?QSJ&s{eA@ZWn91mQ2c3N#DH2#;@FZ4Tbf8Cyh(jjZsIg20h5gL7zwP

c9!;lw+r^M|7q8L7~B3+4BquVU}+_m`l9TKMmHHj`X#h|NS%&AQB4m|5gKO@jO3d zMfXe)fE~HzH!;-cdg5l7fX+LD;buC&6XqTEgv;Q_Yd6MnUI~_hX-!>XFW70u1!aygb%|AFLlt})&w%ay4y2j zxynAU(p2;bp`nIwiKDh?D=)v##V@cobZMe0!@FYNIfmUs$fYLw!=0X`hNKJ)t|pXV zCsN|Jpy9l!!yTt!DoxnL)DrHIp9N%VfQef`u%bV}#7{pi;#**ip8PU&+xyAzEgBDG z6S5-BzoM3PqU-#_REQb9-O_q~WU0seJTvQ`3z~z6O^1TT#s3ayN(McA@(vFoK03*K z@`q~6e5c95w&5nvtA$n%#2@|0vh*%nV1@Z-mLS#+=zp->YQP45k83C-4fzSUlE^l{ zjw!9wZSU-E&x@OCq>?_8%fOB4Ed9(5vt)|UdW{4}dF@%Fq;c)Vhw#=hHly5X107Q9CVtW|$eFq&q=glV6r z!$ot*aracg8fIyknwPZc=YKMnmsIakuTALk^_jkk6h8hlHDR+5F)-98)gLll`3igG zcqXhgX$>Xfb$mCNq2Q8hnXCojrV4}p8gyxHDj4>_e)v0FkG;h8GRHo~i9x&B&}ZEq|>b zxpI^gH+l^m#UbGfLW~z~u7%E_iVPUD3_UCiP_i?QD#aXsyv%jBE9{FK{ZZAm^IJYS zS-e8$%aC&G3Oi!6-FxgTU)k1xE8q3dFZne}EL1=xf37oNhI7XOyEsx%0j)e#E6iY*YSfJp zCTWteU(YeW$YbhcdGj)+-biJnoSz&&BM=1dD6{Lhw|}3M{JdCV_TSr??#Fe!xLalB z*J|<8zd)jlzuf*Vsx$wRRzEAC9&m@A&t>Zd zX_(Z_tCxQl0c0uv{ zg(i?wfuH}Mp%k>SdZKQ^PiQHw@_o#={->36HA)k+OqielnQ!smyh|c?Oer1kyaE(_ zKhmoor104+)_Wnm=yDYw6$O^SgMWBOY|6LR`Ib<`@tFV^94AnDVQ~-D1ZliTX;x_d z=d8=c#esgUlrUq|7F$3E=yfEZI|Up!78j_sknVsQHlsSpg)ZjKeIr`Sw;d6*G_N5g zRe9vK_`xq~N#@AhLPe!5i%JZH%3b#z(PiD)ssE#FQ8pHl2qcgSEfa2HIdjv99vFPY z=hR;pVi~!Mbj8#6MFit_Ds68~ox-O#@D;)deSQ$oObWTJu8r{10x3s6DF&4nrxMJH zJ2N*o2b8h1Kf}Z2mP2V`#hcr9_V#YN?tYF6<#o-!f|iu*yqA{n47SmM8;Ped-xW74 zKjMtGr|Q3%n3;$1ozc8}eCNELAhIK?TUK-%S-c#{8!jA=_(3CqRY?r7;zN`6eC5ey zYbXdL9Rb&}GBZtpuINtIUQ9*}AL=9nze+nAp9)NSa9Lw{_G~l{Lgw)WlqK=daq>z_ z1?l|XcSvtcL#y@ELF-s*UnkuJGS0mFm9I|0**wY$Jal**_5z6(!%*+36Dr3@oGUO~j6WA{A)AbQy{l^mG=&_wD3)C0(E z<`?;Z_4hXEY`dt%YiA5btNtYrm-c0|`O7dez?(ShDPLm5SpSrQ|NN(G;olpMwe|F< z_6$q7-1Zkc&JOl|Sh(Dje5MnD6X!=lN_w;8G3zj!&qjd^;VvvpGXr#0iJd^F>GGfW z+_dcTZT*w<G zeG8g^YK-Ah`B=E5RN!z>{w%8a_8p^@If2_GUu=+Dk-q(J5w!=atVaMOVFU9kV=@3l z08!8y%p{h0y7pSh<2hss43jlA>|g^-65^(C%98g$k927@0pveDPVo6}st~d0N!)zr z*D!tE$KJ`XV{Udfr2vCjKf^Pc6vQ~6pUnKiXvhy1l<4quq&s@&x5iVMadi4CN-DpF zyj=sG>;YxsOSkoMd3*oX|?5G+$Qa>s_K@! zp_J5Qp6pEX>{}#&T`ZAs>);_SCxF^k_!cQh?UjLuuJT^(To%yAd}{ECx9%8;3;N$O z57o54MH#-&Cc0M z!lK6WjhuvKJ8=Y~4`Us?hi4lc3QJ4xG2dV)^twz#b-QFt$r7e z3f7I*@@o+5;c{(XI3C>7s7dp>#j0Kl>s!*lO*cP@kF>P@tIR zEgR$+Q(AdYcS%RD^B260x_eR}uT*q5dLI>R&1&Ixf6(*Qj68a=ou;JXDNv08=~K7) z^#l5fC%h>NXcVe?qFN4b?ppQiSDD5*+H2|lTkG7OFbaV=v9&i}wy?t8;ctnu80(=0{o4u)ecia+9>O|S?|28?KhM9@! z_YOc(TO)Tz0F+i#7Me7&cRVc!)EB+-lO8S<^SwAxT28^{B6Ksa$$M-XpD(_c31r)VGYgc!7 zO-;@Cgaq4?Hf6u4Q^%MS8xQix%}pD)d4uhTvor5oWgj%8e+lA0MOUW7tYDF~yAjj| znd<3Ue$Z(A1B}_F-oQA-4^jsi)IcYNUPev~0P^C*?XDd^-^2UIXg3=SG@BqLuV4P+ zqntYSrbVBuD)wf?m;76?RnI*&Up;7RFav061r4v~TuM;w6FqdVU0wM`d4uk`Z2GM- zr9`ec%BVPSi;fKL2pjXdAVDJR$6iQ9~5C(2zn{!BJ(rv!QAJ}Y|(2piSA zTx{hHdH*Kqy(@(PlL`~t-oM`tHZj>HmKy5uz{qOXcZ>M+PR1(GQPfY4+&LboG8u z`?&IRS_@kH4rj%DWp3kMDKi4Vx4q1sR6(*XTE5XT(KHFze-mlwwGjY^IAu=nhpcgm z^F9SQ@Ksrm_jkUF4`S<=2iRtYY{(I{W*Om~x9&jj&`olm{o4=ftBin1r&-fm+j6LC zikpG%X6QXnxQ~w|kf(lH{nR%~djq4-21UhPEMB7P%6FgbZH{~=mx>1r$?2T$-@n(@ zP4c-xTw-6lvarGQbf=|?>|TIj($BZ57dNi{;!y$sWLrv(sY&x6dk-7GWRP~CRUcOa z1Lh13Imj|}8{Og4AfT3LSLS~E_VdZ$Q~CHhzK4LIW6fRSb_>e<(Y8F4w z%3J|SL8(gw3hY17u7(BmiubfG@aQ9v9rSU4!M2Fj+({9o^5tu~#y;l|dCk)8N!z-CVg zIvxb%HNcM7lbJoO;N#^TA0M|#UWV8YQIueHT)#;TGD0lcw@21S<`9c?gR9>&2JOfFnWq6%mY;;X|#`Gc4tXF5Zx+SEuIs#aL{CIIR`DSsClhj zMe3x}u4@vYeZ_XN5|@|$S>(NLa+>BfGp6cZ0-%M*}9{Sqaq{44IjL<@5oUzGLK>B%6Sh&}+$Ktg(wgDinN zVXteY8N_*eQv3e4`F6>P2Dejude1c8u9yxqt01;>vYMnHJg(79`!%gJ8{te_Y8yX* zmvZKHe*mrPIxq`*{E_;i#=YvNeg_@gCZ=0`7KU5Rk^OzA9qVMI0|dJw5=HM+uXqxB zw}I9BwKGV+r7JTY*lq_?fzl%6geN%z&ByXu^n263oNj z=Ajc6QUOx}ihzixya(#X9mn?e`_I*hKLE8u(+uMK)h|AOc zzvKLqd3_}I6%LQt+QA$8F=&c>IUp}x06|(T5JDs*taZNERp;pS^C~_JBeJ|QL8XK-vHG@}aSx(v8D~ATilk4&RKe{JXYppIw$uH7 zipip>U0~S8<#xWLr&D8jJo=mNyxe)E7X(KaT96HbobYJy`sL|1ySw$rWjMS1bySB0 za0=3*uf#Hd?Gj`+Ha3dEA5Uui_Xam&0pwQ$GP+XR!6C|b%I`=E(SSEX3IY*$;J7&a zSXR6+pJj?Rx77bm>SxV+EMeMIPd|_uNp9vTCa>CtZ>+CRR$3|<8)u|ML&&zmVCf!% z$}ND6Gp^)01B#iuH)qISMks%fwzr1^?J!8}KoPK|KmYqT5C_*h3-zd12&*Xr^JUn9 zh7Skl3-c8aK}6T`Sg0K2h%h1q*tM@rd&*_uCbTykOa+j~ z)&U`?O|H(HxVDBy0^`wCmn8wu+az!Ek29qtjYk65mb;^`+I<919LfG?xuwAOJ&viQ zFy9Bg$IVKSFJqsnZ%3PX9cWCIXheLt3>H`c9~J8Us`<^C14An3@UC6iIo@O5iGCDJ zxQ!pCNzSMS`X@%l7_W-;G4ZBK{4;G;P98(@CR1$ui9-;rc3b^kax`VjoW3oTk?k}m!6?`fdkzI+X?v1y(SA)>;l zFrY1$fXdafyCaI9zp=j0Xyw7}XFb(XuU-hs@{Z3ud{@51hZOe_hOC>@5A>a?jb@J_ z6^8n07A~M8NLTEiW)JIJ1D`18hkWDDpv|CKbZ_A1;!+^sOs;D5M|!*1WrKNTX?&Tn znTi`58`EGUK;}w$(~&3pjTF0lQc}E-tS49jchF+$$Dan^>8iN6JQ9y0Evu~Qm-Q86 z6~=WXG=(^Inuf>0psQR*^&41=k%u)^{NA;#3A~xg<(m0#M~3gwXyJzjK9?-Xf2eW; z>v@55%j%~r51+ME57qpK93)l`3P?%mfKwt?zOKg;C-M0G$Cl1})tM%*Foo{hQF$N5 ztG^kIAH%_{zSzUgl#3IiS)^3vO?mbe#ed|q$51lLfR8gjx^6$g;NL(*s`nBPYf+<7 zq4~wv8LFnGXy$e6_rS4&F0e$NCo?{oz3Jr<~i}T2wk?JThZ-XdbE)iMo6$i=10_XI7Ea zuSXMR&l(sXAYp5M$^NImT*x-_*@#pP?O?<+R9!gVSI7UT$~5!NrFqXRM0XwK|1#la z4xUXDlu7R8mfzf+p5~wQUpC3&2vzW~8rj~cJ@Q8-J#dHZ&uIzo23_FN`P+gQ1e@fD z-GQi;p6uoOM)!<_i0#hv+P6KwM49{27lW_&?B3@1=o|WoQqo>-TW5bpisV{AcKkvj zxv{N+AND5Xr;k7`E@>r0i!vvWvhse3in`ZV{YPwBkp^BK@!lZ6+0k`cOlw`Ku08oI zPsAsPu0MCE!eW;AfO%CYjK7eyMIUVZzWwx(dgvD{1~{cMl#PsC)e9INQ1!h7=kRUm z80Fk9(rJBZMh%iIf_#8%5cOR%J@(Mh0Mp9JSx#JAPVKi~o>;Iu=^x;>686&@h;a8u z&nZPa!Ru)iFW~B6Ns~)aO_4n2rLLd+cfuIHDNNBsHY%cS$X^&Y=Asqc_-o)56dTv5 zmzv$H|5>s9nas_gzx*m+dHFhiyy$o{)DX%_S-D%zHWouMiyU$-bE8C?B4f{k)bAw)yclvGQ7!bw4Bw*SKZwcC601ER{sW*2Qq*o_VU*@ z-#7Mzuoh}uHv|i`T`l*I7i?}^4E-1Kv|j?LuZ!Jm{yh08{r#MW?*#n=v?7%1jDPl? zRsgUx*#TedA8b#*%YSdVra_r8NN1$>3nEV0b%CERHq`YY=i*qINkI_FO5l~Rw#9)$ z^j(u}|NYM~*??UykBHd${&SraK=TR#ztJ$~5cT(%4slC8S;8Ot3>*;;Sh-Upwfhhj zREUJ7b$Ml_!$&S;C-+7v*w*{+z`-GchOQbk=m*Wf2}Fy6A>uM`nT#${-dO7CMTF?6 zlu!NwOZG`@!?a1i)65@OQz848^hc0WQrT#sJM?L#HIYo$uUv!pGr4HIWwrclo)?o! z_kgRyxDb#~Vm?2s7Sf4C%)>_qUuJ5me+KW2MZ_a;Q;Osr_!aaoNv>fj@Ru4$Wnf{J z<4#UNK?rb=AD8z%7Z?4$hNXc`%F%REVP$z>TUUmI^{bpBOnb1ePhLrhaJ?QRDqPQ2 z|0qx_xe8*t6VE{w=NvrlqG-edLxnJ1T-GpO+488162&)_lM^GdC5!((!Ke2v^=mLM z(MQ`Cp5l=firqrSQ}`Z|lI`6d4k$5Cdb%;e$O{5JHt1%KGN*3-WR%+5;`yT5yvmjq zF*Bnus{zMnGM$=uV}e=PQJ;OoDRj zpFZ^~ZBpl5{<|xyCt>a$MKlA>F6WHc>G~mOMI2x%=t%{1Yd0M?4YgyaoG;5hT3iMR z5B!=AZ+5OZKgdtxTBhE%u|F|%=@I%pH%`@9T~uP;E%dyaX0NA-F?Vka_-x9^b6m@n zY(>UUucKg^8J7f7t2bOW2FNT=Qj;S>yOhyiiMREk8xK>@|`HbKj!|n4i6I%kEezU8shNA7B2eL@t5A4w849_drG??kwBj zMIn{wToCc%zyA-c4r6#&Uw&7t(jNy#1~q;db+joWTe?-|i!n~DJ6q%0YE#aykQBxD z{UG7%J1w;{_tkh5+BQ49N$Q0auD-LnamVfbIHj0Zl|O8V(Q0jHDcGjyQRrgAzCfUq zI)`y#g3;yEWX13>))rNJy?0NuPQ?XElss?DDG6APUP=_LhR{L{MjeQvlHl7mwn=qw z<|Y(me9+*qaH_Uvml*V)9TB^L*T7nQs!2M_xlEn-?asjJ=Woqcuo^HhGpA zEI!6OdCzl~O^edx6W5DTzKLC*;BA_7KG{eY+N87J|e23x(fojt}^6l3tV zW-%%q)uuC27NMAWn@|@m zv$PgRHc#|-SXQ$hFY5gEoH)46)Bjm(t;xJA885!HKfUEO>&#$_SfGW=r087b;4dNnyzDdTPsCAfyG=OF3cVfdMlh z6hYb)(kX^llh4|wW*<$Tq8T~_6}$c2bhT{5ruVntr~o%Xwel093r@x#L91i!q|bYb zZfD{ItS9pNjVnfMb2QGq+p@HHBR_DYJBW)oTF97R=<{3h!7dn~5_rrv7t5K@LQ9`) z3_0B!dwfnav8N2KhUFS56-JTq!Y&~PpPI&qgx>}d2& z^jL(HKUgV-~<3r^t|FNg+xl*4pVac(3STXYaw3p5D z%`M-2ob=(8`#erOpOwW6$(DZHDqGD~Y~|Sjr$svH2Gtbz?pfsMwNIqvh{sxnaD;f$ zau~{;g&r@Vv~eNv!YgchrC8u`?*05S_(_9q;Ez8e!KcOAjo=j8!q;F!wSth-eaW+Z z;3q8v{`jpO7B}(!+wSq07iwIaiQ~u?1SPC!g~vfMvRMd8+OO-{Bbbq2+ZB_pYU5to zwNta2*#UbI^JG*!x&8q-1%x6^Bj~KD-A1IeB@HQufuI1-U3Q~rEkaCs9Nm2+fLDxe zVsxKZjWF$94K=e^yG66|kZr;cx&}Ybdtw1SZ|eO`dPSr4GrQ)8e<4d-MAV#v^BT zCyJ`r_C4HD%gEIp*#A=7V&$a$F|P>zMzQbp-|-~ikufe2cwZu^T!v6bL$HQeSZbHv zn4vw616&h=@#cxHWE@daSVS1V_y764{NiQSfE6&jX=5+&&%_+roUVo&8id8o1f1W` zUjlw+O6LgaP4Qu(&zN(Hb>GVByI~~aP@egw6!!leo38$R>7ucSA7}w(3s#~y@Fw@2 zIP)z|3%I*ry}g42DKW{L-QCPge|k%6;vbhm+G;f|2ApyQ|G40WMK@Z7=rdi$pEfzS zY{g#=V*G9j1K#I|sK$4$BhX~`S!hh(v73m$_r~T*(RiexN8n7)%8(ig*3n!#z8Y|x z(1<-}Z>X(ZRcCi$??)0AKN)zrkVX|reRHJ-P175$HmythKx?0SAOJuIBKTCnY6? z?D{f1cq0zV(9dB2smG$lJv%=iK)l`@^?*;uMMXJnAE(zx(qwXxTt4#`t0(CmFA)8s zjw5D}nHojB<8y;bkU%Q$t6VMdlZ~w%h4S22k%tNycQgzP3_z1Gu#zz{RRlp07`h^X ziS}5;nuT;b9^oQf*<4n8J@uByQCEA=2ee*J-VRqU7 zL)L?6ZO}ZAq49;^`)K{_cne4aB_5sT7Zs@yXg7HuhumjudrF?rrvU~aI*30Z zRg^Uy&!SW-{y^EPODYko`r_{);b5TfjnTwhT0>L>!rlCrT7*YzYD@J zsGk^@g>L{^Ydtok(AR&LPkcy2bJ(9m%cTCwC_Wd<*X#&o@N*Vg9-p4FiZNMOX?sa# zJgFnqygrO|yd(Fl6}w8B;|CZpEi$*w_88Ax=HAUaf#FGg=*@$D!`mrYIW*O(FQxh@ z9ti6%_xiMGTvs7TDmQ9&CpH$9<+`kre#MlI$Cr!jgQ2;-gmU*DMRskt^78O#sH^`2 za!4($yc?NRnTp<{k(is)(2uI5l_uU3he_{DmS@$}xG#g0(SIYBOzw{H_p`ueH>f50 zj#r}uvjiQf1c^Ydxkc{V!wIC)2-m_Z>=(}7C_VTq>6C+q9h_i{7QuC+b z0VTbR)DK*$USU@W*SMrJy!R8E!YCr zJ6axDzwrEKNb)YClTAsuv@3Rt3|{@Gn{WGC<)gX8v%j#xx6j2o1aEP@MZWjLSpgd~ z=6n_b{p}f}6l+8B?q49`&pSHcxZP(uc=?$pj>BxA2X134@K4nooc7o5lQT=??K98S zGWqN#jcB^wf}2tA(4Pb}HVT(iMSf(ewaQ7At|}hQ_-3Ni^g96iHo@n;oGgv1C@nj? zK(5%QB;4D7jRzhgxg* z2`*djqkFfQYMJ5{USU&bSkaYDPU4y~;tK^otQFpu^eO!$OM%KW4KdwXI>CZJ{=0$8 zhRdzxe{74A^38b`3x2DSgjBPJVIiIURJ$0HPUF$!7DuXRV_t`!4Sn9D@RLI2N0E%Q zue09<^K8da80AL&o2jv%`S~+os<10SWL;!?x|&r)#4kFGw=gt(;XvC^@1L6EALaON z!4vsP-)f|U_@CG15kCF@!XT5_{Z7Qt4Iz|qU0FJm0ORWddbsGxnCw>~~ zYHzp2wgel#y*2Hn@xSQbuLb4E4zec;uTS%tb|o{n5T7Rmarhu7dz&2YyALMZ1=;n& zk=rx19G(G5bEP-)MzjqLX@qJ2K9?U2<6Wk1^lxZa!R&}!`CtvMK>9CH>b_Zd`nG_n zE-1P1&whus^vg`a=-}wY6_lb~(C{B?-AV#J2Z^X>+6_)U(~2rygnYvM&r#Q+R!B&~ z^Q>a@%Da;mf!IXdL@Meuj>mm7U7vWS)jbHnC(7ZhqI-9a- z%Frw$Di05mqIBsXZurmtMAQfC`}bbDN4N{-6|;7nsm@kk2$imEwavA0*uG&WrIhVp`Y)82h1b_`hyTfg*v@ z{Cs8TwsZZcE|_j1dOGVuH>(41Mlk8cWDW*0;&?(T{^vobDLkZk^5Heg48cr3On&vx zz|gSYk>FaFXJj^on9ep9EqhYD2R+^GTeoV!!IyCnmkenWd-H0(kOo|ERyM(u?Ifjm)_Ncb)?=q8rcLIjsJUAINw{Hb&pelC5fnEESk-lU*hp#3TJ1BTQ}ToT=qH zHpk;O1zVe&;8kVn+`wcmZ*Z~BhoAyFEdtKlvcwu8IUvHOtEcO0OCd_B;}pJMcGnh;Y5Js2%1CiNJF#p$o%iX|Ec#n=^=qgP z6BpkguG1^Ujt>gD#dcdS zhObSNJL?CSiwAcvYMQSw5A#zgxKSFn`FG#*C^0HZMMHx$POgmxbWuV56ga1(rKPnC zMmwnMV%#~AGk_&*V(QxDzzk}y$A?axRi7su~Nq&q3fG9!QZA~-_ki~#CsWL5J?Y`=B^2gHyW{oU!xR5~Wi)x*9id zFOxT{C*_dW*RTHkbD6WU6QuXdH?O|_t2X6*W}VLv=pcc{df-=j><)b z?ZIx~)S*3~P$)3zM}2*L*bZ5x$3Wb!LmtUTO74>JYiXZK#QR0ZkBJNx-srC%SvlGk z@x&?$f7Y(kaeMu=hzW3^dA6~Gog>v zx9bRp=Z==atDm61ixp7!qA>YW!<<97VYQ9MUT#oO5GGfq4Ng3M8lT@d11g<5rdZ@u z`xuLv;*(gui2v5*)8~ckg1?QNHOHpUm*J6F65%BbD$Elk53Y)ib(eZQo7jPmMD1xq z$_54wtHrm=JR0WQXhix84rl>kDOD!nj+g+wo?=@ZU-|!}_4#51eAR-qAOG>KETz;tP#l5M z?O$OWmvo(^_<{{_M&lj5{QawUO6lnx4A1fq3Y@r}d*#nxsja4XZhqfwJbZ&oy7XUU zxEAg;e&at|yy}-e*wK4=<&P7u1JiLja%h;=`9IcsXTK{W62NVuO`G_aI9u+Qp+DaC z)aW9R#4qM+g4%F3_epDVm1?rYyk|4GI%`&o9$+?au1qz9Nw`1FNxpy%23^U7dNn)k z>10kO`V9euBUxn5_#gxfXQ<~$?@wF!r?P{b0SEziXH;0Yt=NT3rLWJXy?5_dUHO=d z0N_f%wRg|^*b~5DuHNSUogjS5Sib&)&9S0dhdCPur+nW`MTT@M{8}BF$Co|YUrF2V z7jbkw+?&r&7+VP6YA5-$5O;qqiyMb&jIB3)-zF65_YRCtz?+E6U17DhT8JC;0Kni# z1WoX+uL7QTZ|C;Y(5JYJA?o>#3IN*!1x5ZNF#mVEovxQsjuwy}fq9(mwG&qN6e}qy z@rC5{X3}Q~w{x&yDjnM4e&=kNNgD#E8_ERrB5o19ahCC;uW;|nS zN{8kK0-)v;ppmQer2xV6duTQ@t0Cq*kDd+uvEK-PylbRD%0l%NKQI)9^3NNo_d9nN z*@3md>~uf^=s(KPL7Ec2OsA7``kvfrx7L>DTX{?x;q*H@0q zCQCEvFHitKsNv<^*vwx+zudexM0!$e20A-@L;xkcCL;ZmyiwyqeELi zs%aQmZT_>7eEZC{2U#z42oR|JKgDLpMk>A5c{}b0PF!f-d zy3(N8Pyc6bXREut4tP>IX2UtxJ^)SHpIHs<2=SifC2+38Ly{ZPl3j`li;5^IC_v)y z=)0`IFFhgKt?`m;$+41E1z?`FSQEWtM(1T)7r;>%BrjvIkLysJgb`q~`YvksuJF6% zU(sNxa}|MQ+FkIGZftFQPN0}3YXQ+q>!+8dg_?oEJcvTqEk0PO$;fm)kdOYav!UmV zTQP6_2U)2a&Yk)KRB6fM2}bdZC(4~5vJ(pI0+xyJ zEV#>zC6LIXD3S`(@W%R;q%Jc(nfCCp`o|fPI4UlUIYc5JDMBRDUhUa`QSR5T>g@lL zzW*18c}*qjc_@12%U_#;UJ}VWi4Kk(4CRxk%;uSe03c(Fh=_b!V`paW`JO1xZT)#< z~5gD(U3Z*hb z=M!o@Pa#PA%1)>Dm$-3Xa`6cX4h#3(?7fZ|oy!}C z@f2G|4h(Xrd`CI|e*GD;og@z7o~xwxj6}d&K0Z zLAzHftgrc*zO6h!7C>k9W9>uE55yqj`4!geV2IPh%oIv=Yn#&N{Cz8;GcGxu0xFpv zm)fwsKm$695+^(MYx5Ir>P1N&K~0SBT;ULArl0 zO{2kW?rSd@!`kY)cRdJ9vGmQ>BA32zHSx{#Q2}TgH;o&A0o}m|&-4odKXCZ1kYrtW z<@8b(Sd#;|##l?YPf`i~qNL>Hn<7Bm_cs1%QqMvMl#)*LS>xEN+KUD1r(^Y% zsVYLm4thr@DgiJeKko3SFKLa1izi)xG-D&(YG(<;Lbkz%2!7!g3ejpNQeK;Ph@{p! zD%OrT+P{HK3hytQ--PezHD0uuO#E>)J(_f0T?ReO{;%eQVxoc7mwnH_@*v5Ik)&_< z?g{$lppS3k=;I@-Q)vd5W_|bUhbX_k*AmzQo92?qz2>hQUs63@hvGBL)PcIV9|Jk@ z?(*lmYqO3eBpX#CqkMuJkrbF3>a-(mD_CYQ%~z%@^No(}MY1(qW=fisEWSj&0X?|I zfscP;n~3Jb713fYvT{m7_pVDtNy<5(f}Lht1hW_abzvWpSAV0*iSkbO9><}3XOebH z7vZ>45ezjdQ~&YXP$tyvbN=KpOd?33$qIELB|mQ0pAw_{91_(NdUpO}nHnd-;4%SE z6vp5QVr=XG^^ZMF* z>$<#oIsBET$?69J?3}kX&j5UFz6^G{iMy4};Wl=B?!N==jhPRkaFW7vF}hdSPT zEn+S|@RD?jDqBvmZ%@hZ4CgFa&drSe&;I?~5^P}bKWYw}dmi2#HzvAycWcH2R(o(l zwdHObAYhSctW#DqnU-EcGsYC$uoM47*YM;!L0di{?}+fIE-M~_Mq|A?w}5HJYG~C> zakqJUp$pFGt+km^cJuT5Cn6nY^x-NBn*hyx{k**@23YtJ0T1owis~ukX{Lz}K@+pl zunDX$3Bl`q=nc&7rJAatXXU(VH{$uVSV~y5y**KL{A*ko`&H zne?@}Kc=8pF8L|$>n*iXnx#MIFUV!3)ZA4aINPfZ`TP&^D?;@R{)3(SlF&K1ngTEN z%P737vp18+ezzQ+$y)>n5IckZcEP~dcZ*Z>Xe7W9p9?Ji4HHu*5CAGbES?dY)-jY# zfb|H{+X0pXiQM)b@0Upc=wFiPJXJoY%q@u>UhbI!e;tVGt{z8*vCi+?`+iiX%xvcj zIV7_goGL9=aTKQjmYE)GDpPU=1%7pI1Dq}}Mmxf_`e1Y8I8^Q?t~>_$#4S3@|L&pJ zGCN-F#WkKCp}fGR|6yQ8lY%oi?U5IXtQUf6e<2+uc`jy}+H200n6?E~mWP@%kTCP- zyghuc@Y82GujOrpdzN$9rd?Y|4a;Vtu;gW%fZkf{v{~R+Mj8r$ZAX@e5g#@K8p?(k z%PpP>J2c1f4TDr0vDxGgD=HX|CFH4F`0+O9J@5_RN^C~pWqbdtl5>xSYK!BzN>X!C zW)MSe!liC`OI~@0FrGt1E|F(>jsOPA0>EUh7?jmBWv%{eJwa~y&$fJg|9d1{3~Xn=N=wO6)}H)Ci{b1ppw z&mKHVLA(1p%B@tnZR$_I^gSDtfuIf zUC!XfddE0TIaj~Vj(D7K8a8{1*6!SrrmfX)iHZZ=1CvW$lsY4^Rj}jgPIWV6%oWyC zWj^J45-%F;`ip)X&0W;EzR?XDE|W;qf6z3Xa|DD_L;rRznE?-Z5^9bCq29Al!&M9; z?*2*gm7BfT_~ra!4lbE0tK>mosdnC$T(zNSaF!H-<7d4>A)Q46O#-B#R&tl%pnrswnqSTaF>K~vP%4`k))(Kt%I{S@aPq3^N;Y` ziTyc8-Rwq4?Vde%8!k1iaB(F zrvfu-;Ri8~iL`Uwb=sBh08xKvFt{_Ja;K*Z0XQ>NoPyd`u@ieEte;5HaJL~v0p7O# zJa#{8S&9*pL_Q6Pb}m-ieIKt#Ueev9Idm1jfsjAEJfc~(z`g;$LWrwiF9JRom5wb| z!zqH#o5h}4#d8W(mRmhsl$$}>fE^$q4#Ycb39&y-eFpBK8`ZbLr}rUx#Ti`ISS~F{ zZ1&WaZO&MhL`WlW>;SOZRaaPVAb2fbaK)6&_1Krlc5AUpAtBfDY^7xT^1*h-8uIYm zB-EGskYr+q`N6(=h;1UEi)icey-uTY&>U3f6tdvFSM91s)dS zTs~gq<9Q**eg;H^Om*e0qohcRPTflHkZCPZ!?j?(^W!9#G7$oKvl0=G8!=I97EQ0V zpRIH8LVc>S<%oWE`n3clp0QiVii-{_lRL+|1te(Jrg{g4_APuNalscUR&Rh6Bo=6u zywzfl1E1Vx1cOV=ow49{<<^)S@;FGzH;QEe8ECg~gE$uwP!APO%$AOqruy-Q?FR>T z42XD%PsgD*P$^~#O$R2$2zJ27j?NwAVEYU+KZANUf=%k%_gBA0hAg#AE`)?;ek53B z(t#ua7KA`*X&8%cSXk;90AGX$#?QStzTNd)7jxPp>b(U3F=BF7fE6b+uBd^F+z$MaPF?yzs-$}0tnn)NXjS?lzTy`U z-rrTr$d#px@-M#S;op$(?*QSCwJl#00(DaOr`-r-32e5PplH*bfF(6dinOZ}wifcH zHSn$Yyz2EZ<~7+~dvBWBn)_gBxuyYwdQ2u*>w;}Z0#$^HdDPVEqE!b&8)a*mUQ*-j zDMn>_mQyn{!{g&`b@K)9&&Nc3q{|arNCT9*4c!dHwnX8;jQx0|KN{mkHh{0_`H!&K x{_m1(z5x7N^#5I$Z%guBNB^lijYS?vT>#oz&3l!l#D_=%HkJ++I5XeWzW}4yS0MlZ literal 0 HcmV?d00001 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', '

  • ' + JSON.stringify(message.data) + '
  • '); +}); + +/* Subscribe to a channel using deltas */ +deltaChannel.subscribe(function(message) { + receivedLogDeltas.insertAdjacentHTML('afterbegin', '
  • ' + JSON.stringify(message.data) + '
  • '); +}); + +/* Recursively work out the size of the message's elements */ +function getMessageSize(message) { + var bytes = 0; + if (typeof message === 'boolean') { + bytes += 4; + } else if (typeof message === 'string') { + bytes += message.length * 2; + } else if(typeof message === 'object') { + for (var i in message) { + /* Calculate the size of components of the object */ + bytes += i.length * 2; + bytes += getMessageSize(message[i]); + } + } else if (typeof message == 'number') { + bytes += 8; + } else if (message === undefined) { + bytes += 1; + } + return bytes; +} + +/* Wrapper for the vcdiff decoder, allowing us to check the size of the original diff */ +function decodeAndCountSize(delta, source) { + totalWithDeltas.innerHTML = parseInt(totalWithDeltas.innerHTML, 10) + delta.byteLength; + var result = decoder.decode(delta, source); + return result; +} +[--- /Javascript ---] + +[--- HTML ---] + + + + + + +

    Ably realtime deltas comparison

    +

    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

    +
    +

    No deltas output

    +
      + +

      Deltas output

      +
        + +
        + + +[--- /HTML ---] + +[--- CSS ---] +body { + font-family: Arial, Sans Serif; + font-size: 13px; + min-width: 700px; +} + +h1 { + font-family: Arial, Sans Serif; + font-size: 18px; +} + +a, a:visited, a:active { + color: #ed760a; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +section { + padding: 10px 4px; + column-count: 2; + column-gap: 5px; +} + +h2 { + margin: 0 auto 4px; + text-align: center; +} + +ul { + display: block; + height: calc(100vh - 230px); + margin:0; + padding:0; + border: 1px solid #CCC; + overflow: scroll; + background-color: #EEE; + +} + +li { + padding: 2px 5px; + list-style: none; + line-height: 2em; +} + +li:nth-child(even) { + background:#ccc; +} + +section { + column-count: 2; +} +[--- /CSS ---] diff --git a/content/code/realtime/channel-deltas.code b/content/code/realtime/channel-deltas.code new file mode 100644 index 0000000000..0ef0572dc2 --- /dev/null +++ b/content/code/realtime/channel-deltas.code @@ -0,0 +1,74 @@ +[--- Javascript ---] +var apiKey = '{{API_KEY}}'; +var channelName = '{{RANDOM_CHANNEL_NAME}}'; +var realtimePublisher = new Ably.Realtime({ key: apiKey }); + +/* Initialize subscriber with delta plugin. Makes use of the vcdiff-decoder we've included in the HTML */ +var realtimeSubscriber = new Ably.Realtime({ key: apiKey, + plugins: { + vcdiff: vcdiffDecoder + } + }); +var deltaChannelOptions = { + params: { + delta: 'vcdiff' + } + }; + +var channelPublisher = realtimePublisher.channels.get(channelName); + +/* Specify in the channel options to use deltas for this channel */ +var channelSubscriber = realtimeSubscriber.channels.get(channelName, deltaChannelOptions); + +$('input#publish').on('click', function() { + show('Publishing message', 'orange'); + channelPublisher.publish('event', 'data', function(err) { + if (err) { + show('✗ Publish failed: ' + err.message, 'red'); + } else { + show('✓ Publish successful', 'green'); + } + }); +}); + +channelSubscriber.subscribe(function(message) { + show('⬅ Received message on subscription', 'green'); +}); + +function show(status, color) { + $('#channel-status').append($('
      • ').text(status).css('color', color)); +} +[--- /Javascript ---] + +[--- HTML ---] + + + +

        Ably Deltas Example

        + +

        In this example, we demonstrate the simplest way to subscribe to deltas on a channel. See our deltas documentation for more details. + +

        + +
        + +
          +[--- /HTML ---] + +[--- CSS ---] +body { + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +h1 { + background: url('//jsbin-files.ably.io/images/logo.png') no-repeat; + font-size: 18px; + font-weight: bold; + padding: 8px 0 0 120px; + height: 42px; +} + +.row { + margin-bottom: 1em; +} +[--- /CSS ---] \ No newline at end of file diff --git a/content/code/realtime/rewind.code b/content/code/realtime/rewind.code index f6186c7539..6665bb5f93 100644 --- a/content/code/realtime/rewind.code +++ b/content/code/realtime/rewind.code @@ -58,7 +58,7 @@ function show(status, color, box) {

          Ably Rewind demo

          -

          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.

          diff --git a/content/concepts/long-polling.textile b/content/concepts/long-polling.textile index 07c125cf01..1b4765cb71 100644 --- a/content/concepts/long-polling.textile +++ b/content/concepts/long-polling.textile @@ -112,7 +112,7 @@ From the readme, "Pollymer is a general-purpose AJAX library that provides conve Optional extras include support for JSON-P and logging. -```[js] +```[javascript] var req = new Pollymer.Request(); req.on('finished', function(code, result, headers) { ... }); req.on('error', function(reason) { ... }); 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/types/_channel_options.textile b/content/partials/types/_channel_options.textile index 2ff18322ef..0275861b4a 100644 --- a/content/partials/types/_channel_options.textile +++ b/content/partials/types/_channel_options.textile @@ -1,4 +1,4 @@ -Currently the supported channel options are only used for "configuring encryption":/realtime/encryption. +Channel options are used for setting "channel parameters":/realtime/channels/channel-parameters/overview and "configuring encryption":/realtime/encryption. blang[jsall]. @ChannelOptions@, a plain Javascript object, may optionally be specified when instancing a "@Channel@":/realtime/channels, and this may be used to specify channel-specific options. The following attributes can be defined on the object: @@ -20,6 +20,9 @@ h4. java: Members ruby: Attributes +blang[jsall,java,swift,objc,dotnet]. + - paramsParams := Optional "parameters":/realtime/channels/channel-parameters/overview which specify behaviour of the channel.
          __Type: @Map@@JSON Object@__ + - cipher:cipherCipherParams := Requests encryption for this channel when not null, and specifies encryption-related parameters (such as algorithm, chaining mode, key length and key). See "an example":/realtime/encryption#getting-started
          __Type: "@CipherParams@":/realtime/encryption#cipher-params or an options objecta @Param[]@ listan options hashan Associative Array containing at a minimum a @key@__ blang[java]. diff --git a/content/realtime/channel-metadata.textile b/content/realtime/channel-metadata.textile index 8af2ac9b14..724532d489 100644 --- a/content/realtime/channel-metadata.textile +++ b/content/realtime/channel-metadata.textile @@ -1,7 +1,7 @@ --- title: Channel Metadata API section: realtime -index: 31 +index: 3 jump_to: Help with: - Overview#overview diff --git a/content/realtime/channel-params.textile b/content/realtime/channel-params.textile deleted file mode 100644 index d2e4d76e7f..0000000000 --- a/content/realtime/channel-params.textile +++ /dev/null @@ -1,247 +0,0 @@ ---- -title: Channel Parameters -section: realtime -index: 32 -jump_to: - Help with: - - Overview#overview - - Using params with v1.1 or earlier Ably libraries#using-params-with-lib-v11 - - Using params with non-Ably transports#using-params-with-other-transports - - Examples#examples - Parameters: - - rewind#rewind ---- - -h2(#overview). Overview - -Channel parameters are a general mechanism by which a client can express properties of a channel, or of its attachment to a channel. A set of channel parameters is simply a set of key/value pairs, where both keys and values are strings; the keys correspond to specific features that Ably defines. - -The methods provided for specifying channel parameters, and the currently available features, are outlined below. - -h2(#supported-params). Currently supported channel params - -- rewind := Used to request that an attachment start from some number of messages or point in time in the past. See "rewind":#rewind for more information -- delta := **In an experimental state**. Used to request that data payloads should be sent as deltas to the previous payload. "Contact us":https://www.ably.io/contact for more information and supported values - -h2(#using-params-with-lib-v11). Using channel params with v1.1 or earlier Ably client libraries - -The current Ably libraries, at version 1.1, do not expose an API for expressing channel parameters. This means that it is necessary to specify parameters in a way that is opaque to the library. - -A set of params is expressed by including a query string, using standard URL query syntax and encoding, within the qualifier part of a channel name. The qualifier part is in square brackets at the start of the channel name. - -For example, to specify the parameter @foo@ with value @bar@ on channel @baz@, the qualified channel name would be @[?foo=bar]baz@. If the channel name already has a qualifier, such as @[meta]log@, then the query string follows the existing qualifier, as in @[meta?foo=bar]log@. - -Using this syntax with the Ably library means that channel parameters are specified for the lifetime of the @Channel@ instance; in order to reference the same channel, but with different channel parameters, it is necessary to get a new @Channel@ instance, using a qualified name that includes the new channel parameters. - -h3. Example - -For example, to specify the @rewind@ channel param with the value @"1"@: - -```[javascript] - const realtime = new Ably.Realtime('{{API_KEY}}'); - const channel = realtime.channels.get('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); -``` - -h2(#using-params-with-other-transports). Using params with non-Ably transports - -It is possible to interact with Ably channels using transports that do not involve using an Ably library; for example using SSE without any library, or using a supported non-Ably protocol such as MQTT. In these cases, it is also necessary to use a qualified channel name. - -A set of params is expressed by including a query string, using standard URL query syntax and encoding, within the qualifier part of a channel name. The qualifier part is in square brackets at the start of the channel name. - -For example, to specify the parameter @foo@ with value @bar@ on channel @baz@, the qualified channel name would be @[?foo=bar]baz@. If the channel name already has a qualifier, such as @[meta]log@, then the query string follows the existing qualifier, as in @[meta?foo=bar]log@. - -In an SSE connection, it is also possible to specify channel parameters as a query string in the connection URL, instead of as a qualifier on an individual channel name. In this case, the given channel parameters apply to all channel attachments associated with that connection. - -h3. SSE example - -For example, to specify the @rewind@ channel param with the value @"1"@ using a querystring parameter, where it will apply to all channels: - -```[javascript] -var querystring = 'v=1.2&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; -var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); -``` - -Or to specify the same parameter but only applying to one channel of two, using a qualified channel name: - -```[javascript] - var channelOne = encodeURIComponent('[?rewind=1]channel1'); - var channelTwo = 'channel2'; - var channels = channelOne + ',' + channelTwo; - var querystring = 'v=1.2&key={{API_KEY}}&channels=' + channels'; - var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); -``` - -h2(#rewind). Rewind parameter - -The @rewind@ channel parameter relates to the initial attachment of a connection to a channel, and expresses the intent to attach to the channel at a position, or a point in time, in the past (that is, effectively "rewinding" the channel for the purposes of the present attachment). - -A @rewind@ parameter can express a channel position in terms of a number of messages, or a time interval. - -A @rewind@ value that is simply a number @n@ (eg @rewind=1@) is a request to attach to the channel at a position @n@ messages before the present position. If that attachment is successful, and one or more messages exist on the channel prior to the present position, then those messages will be delivered to the subscriber immediately after the attachment has completed, and before any subsequent messages that arise in real time. - -If fewer than the requested number of messages exists on the channel (including the case that there are no prior messages), then the available messages are sent; this does not constitute an error. - -A @rewind@ value can also be a string that is a time interval specifier. Supported specifier values express an integral number of seconds (eg @15s@) or minutes (eg @2m@). If that attachment is successful, and one or more messages exist on the channel in the given time interval prior to the present time, then those messages will be delivered to the subscriber immediately after the attachment has completed, and before any subsequent messages that arise in real time. - -If you wish to use a time interval rewind but additionally specify a limit on the number of messages to be returned, you can use the @rewindLimit@ channel param. For example, to request up to 10 messages in a window 5m before the present time, specify a channel parameter string of @rewind=5m&rewindLimit=10@. If fewer than the requested number of messages exists on the channel in that interval (including the case that there are no messages), then the available messages are sent; this does not constitute an error. - -At most 100 messages will be sent in a rewind request. If the number of messages within the specified interval is greater than that limit, then only the most recent messages up to that limit are sent. The attachment succeeds, but truncation of the message backlog is indicated as a non-fatal error in the attachment response. - -By default, a maximum of two minutes of channel history is available when attaching. This means that a rewind time specifier of greater than two minutes will only be able to rewind by two minutes. If a channel has persistence enabled, then it is possible to rewind back in time by up to the persistence TTL on the channel. - -The channel position expressed by a @rewind@ parameter has an effect only on an initial channel attachment. Any subsequent reattachment of the same channel on the same connection, in order to resume the connection, will attempt to resume with continuity from the point at which the connection dropped. (There are a few exceptions to this: in particular, client libraries earlier than v1.2 that have been disconnected for over two minutes, and all clients when using "@recover@ mode":/realtime/connection#connection-state-recovery ; in both cases the previous attachment state is not preserved). - -Any @rewind@ parameter value that cannot be parsed either as a number or a time specifier represents an error, and any attachment request will fail with an error. - -h3(#rewind-example-ably). Rewind example with an Ably client library - -To subscribe to a channel, getting the most recent message if available: - -```[jsall] - // only with ably-js v1.2 or later - const realtime = new Ably.Realtime('{{API_KEY}}'); - realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', { - rewind: '1' - }).subscribe(msg => console.log("Received message: ", msg)); -``` - -```[jsall] - // with ably-js v1.1 or below - const realtime = new Ably.Realtime('{{API_KEY}}'); - const channel = realtime.channels.get('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); - channel.subscribe(msg => console.log("Received message: ", msg)); -``` - -h3(#rewind-example-sse). Rewind example with SSE - -To subscribe to a channel, getting the most recent message if available: - -```[javascript] - var querystring = 'v=1.2&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; - var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); -``` - -h3(#rewind-examples-mqtt). Rewind example with MQTT - -```[nodejs] - var mqtt = require('mqtt'); - var options = { - keepalive: 30, - username: 'FIRST_HALF_OF_API_KEY', - password: 'SECOND_HALF_OF_API_KEY', - port: 8883 - }; - var client = mqtt.connect('mqtts:mqtt.ably.io', options); - client.on('connect', () => { - client.subscribe('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); - }); - client.on('message', (topic, message) => { - ... - }); -``` - -h2(#delta). Delta parameter - -The @delta@ channel parameter allows subscribers to expresse their desire to receive deltas (diffs) between the previous and current message instead of the current message in full on a given channel. The effects of this parameter are invisible to message senders and only affect subscribers that specify it - i.e. the sender sends messages in full and Ably computes and sends the message deltas to any subscriber which subscribed using this parameter. - -Note that requesting deltas does not guarantee that every message received will be a delta, as the server may chose to send some messages in full depending on system load and other factors. Therefore a subscriber using the @delta@ parameter should be prepared to handle a random mix of delta and full messages. - -The @delta@ parameter can take only the value @vcdiff@. VCDIFF is the format of the deltas. It is an open format specified in "RFC 3284":https://tools.ietf.org/html/rfc3284. - -Ably provides a JavaScript "delta codec library":https://github.com/ably/delta-codec-js to help you avoid writing boilerplate VCDIFF handling code. Some examples follow. - -h3(#delta-example-sse). Delta example with enveloped SSE - -```[jsall] -const key = '{{API_KEY}}'; -const channel = 'sample-app-sse'; -const url = `https://realtime.ably.io/event-stream?channels=${channel}&v=1.2&key=${key}&delta=vcdiff`; -const eventSource = new EventSource(url); -const channelDecoder = new DeltaCodec.CheckedVcdiffDecoder(); - -eventSource.onmessage = (event) => { - /* event.data is JSON-encoded Ably Message (see https://www.ably.io/documentation/realtime/types#message) */ - const message = JSON.parse(event.data); - const { id, extras } = message; - let { data } = message; - - try { - if (extras && extras.delta) { - data = channelDecoder.applyBase64Delta(data, id, extras.delta.from).asUtf8String(); - } else { - channelDecoder.setBase(data, id); - } - } catch(e) { - /* Delta decoder error */ - console.log(e); - } - - /* Process decoded data */ - console.log(data); -}; -``` - -h3(#delta-example-unenv-sse). Delta example with unenveloped SSE - -```[jsall] -const key = '{{API_KEY}}'; -const channel = 'sample-app-sse'; -const url = `https://realtime.ably.io/event-stream?channels=${channel}&v=1.2&key=${key}&delta=vcdiff&enveloped=false`; -const eventSource = new EventSource(url); -const channelDecoder = new DeltaCodec.VcdiffDecoder(); - -eventSource.onmessage = (event) => { - let data = event.data; - - try { - if (DeltaCodec.VcdiffDecoder.isBase64Delta(data)) { - data = channelDecoder.applyBase64Delta(data).asUtf8String(); - } else { - channelDecoder.setBase(data); - } - } catch(e) { - /* Delta decoder error */ - console.log(e); - } - - /* Process decoded data */ - console.log(data); -}; -``` - -h3(#delta-example-mqtt). Delta example with MQTT - -```[jsall] -const mqtt = require('mqtt'); -const { VcdiffDecoder } = require('./lib'); - -const options = { - keepalive: 30, - username: 'FIRST_HALF_OF_API_KEY', - password: 'SECOND_HALF_OF_API_KEY', - port: 8883 -}; -const client = mqtt.connect('mqtts:mqtt.ably.io', options); -const channelName = 'sample-app-mqtt'; -const channelDecoder = new VcdiffDecoder(); - -client.on('message', (_, payload) => { - let data = payload; - - try { - if (VcdiffDecoder.isDelta(data)) { - data = channelDecoder.applyDelta(data).asUint8Array(); - } else { - channelDecoder.setBase(data); - } - } catch(e) { - /* Delta decoder error */ - console.log(e); - } - - /* Process decoded data */ - console.log(data); -}); - -client.subscribe(`[?delta=vcdiff]${channelName}`); -``` diff --git a/content/realtime/channels.textile b/content/realtime/channels.textile index 0332e35ed4..ceb83bb5d6 100644 --- a/content/realtime/channels.textile +++ b/content/realtime/channels.textile @@ -1,7 +1,7 @@ --- title: Channels section: realtime -index: 30 +index: 2 languages: - javascript - nodejs @@ -140,9 +140,34 @@ bc[objc]. ARTRealtimeChannel *channel = [realtime.channels get:@"channelName"]; bc[swift]. let channel = realtime.channels.get("channelName") -h4(#setting-channel). Setting channel options and encryption +h4(#setting-channel-options). Setting channel options -A set of "channel options":#channel-options may also be passed to configure a channel for encryption. Find out more about "symmetric message encryption":/realtime/encryption. +You can set "channel options":#channel-options in one of two ways: + +* Initially, when you first obtain a channel using "channels.get":#obtaining-channel. +* After creation, when you update a channel using "channels.setOptions":#modifying-options. + +h5(#setting-channel-parameters). Setting channel parameters + +Channel parameters allow a client to set the properties of a channel. The are two parameters that can be specified: + +- rewind := Used to request that an attachment 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 := Used to request that data payloads should be sent as deltas to the previous payload. See "deltas":/realtime/channels/channel-parameters/deltas for more information. + +bc[jsall]. const realtime = new Ably.Realtime('{{API_KEY}}'); +const channelOptions = { + params: { + delta: 'vcdiff', + rewind: '1' + } +}; +const channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', channelOptions); + +h5(#setting-encryption-options). Setting channel encryption options + +You can enable encryption on a channel using the "channel options":#channel-options. + +Find out more about "symmetric message encryption":/realtime/encryption. bc[jsall]. Ably.Realtime.Crypto.generateRandomKey(function(err, key) { var options = { cipher: { key: key } }; @@ -238,14 +263,14 @@ channel.subscribe { message in h4(#rewind). Subscribing to a channel with the rewind option enabled -It is possible to subscribe to a channel and obtain messages from that channel's "history":/realtime/history with a single API request. To do so you would use the "rewind":/realtime/channel-params#rewind feature. This will result in the specified number of messages from "history":/realtime/history being returned as part of the subscription process. +It is possible to subscribe to a channel and obtain messages from that channel's "history":/realtime/history with a single API request. To do so you would use the "rewind":/realtime/channels/channel-parameters/rewind feature. This will result in the specified number of messages from "history":/realtime/history being returned as part of the subscription process. minimize. View details - "Rewind":/realtime/channel-params has two methods of obtaining messages from history. You can retrieve a specified number of messages from history or messages from a set period of time into the past. + "Rewind":/realtime/channels/channel-parameters has two methods of obtaining messages from history. You can retrieve a specified number of messages from history or messages from a set period of time into the past. - In current Ably libraries, or when using the "service without a library":https://www.ably.io/adapters, this is done by qualifying the channel name. There is a future library release planned in which it will be possible to specify channel params directly via the API. As an example, if you have no metadata and wish to subscribe to channel @my_channel@ and fetch the most recent message from "history":/realtime/history, you would specify the channel as @[?rewind=1]my_channel@. If the channel had some metadata, @[some_metadata]my_channel@, you would apply "rewind":/realtime/channel-params with @[some_metadata?rewind=1]my_channel@. For more details, look at our "channel parameter":/realtime/channel-params documentation. + In current Ably libraries, or when using the "service without a library":https://www.ably.io/adapters, this is done by qualifying the channel name. There is a future library release planned in which it will be possible to specify channel params directly via the API. As an example, if you have no metadata and wish to subscribe to channel @my_channel@ and fetch the most recent message from "history":/realtime/history, you would specify the channel as @[?rewind=1]my_channel@. If the channel had some metadata, @[some_metadata]my_channel@, you would apply "rewind":/realtime/channels/channel-parameters/rewind with @[some_metadata?rewind=1]my_channel@. For more details, look at our "channel parameter":/realtime/channels/channel-parameters documentation. - *Note* that "rewind":/realtime/channel-params is limited to a maximum of @100 messages@, and can only get messages no older than 2 minutes by default. If a channel has persistence enabled, then it is possible to rewind back in time by up to the persistence TTL on the channel. + *Note* that "rewind":/realtime/channels/channel-parameters/rewind is limited to a maximum of @100 messages@, and can only get messages no older than 2 minutes by default. If a channel has persistence enabled, then it is possible to rewind back in time by up to the persistence TTL on the channel. In addition, rewind will only apply upon *attaching* to a channel, so any subsequent subscriptions post-attach will not fetch old messages. @@ -515,6 +540,10 @@ channel.publish("action", data: "boom!") Normally, errors in attaching to a channel are communicated through the attach callback. For implicit attaches (and other cases where a channel is attached or reattached automatically, e.g. following the library reconnecting after a period in the @suspended@ state), there is no callback, so if you want to know what happens, you'll need to listen for channel state changes. +h3(#modifying-options). Modifying channel options + +You can modify the @ChannelOptions@ associated with a given channel instance by calling @setOptions@ and passing a new @ChannelOptions@. The modified options will either take effect at the time of attachment (if an attach for that channel has not yet been initiated), or the @setOptions@ call will trigger an immediate attach operation to apply the modified options. Success or failure of any triggered attach operation triggered is indicated in the result of the @setOptions@ call. + h3(#multi-publish). Publishing to multiple channels Often it is necessary to publish a single message in multiple channels at the same time. In the realtime API, this is achieved simply by making multiple separate "publish":#publish requests. If a separate publish is made in each of the channels in question, the realtime protocol will allow for those concurrent requests to be in-flight simultaneously. This ensures that a publish on a channel is not delayed waiting for completion of operations in other channels. diff --git a/content/realtime/channels/channel-parameters/deltas.textile b/content/realtime/channels/channel-parameters/deltas.textile new file mode 100644 index 0000000000..cbeaaabcae --- /dev/null +++ b/content/realtime/channels/channel-parameters/deltas.textile @@ -0,0 +1,253 @@ +--- +title: Delta compression +section: realtime +index: 6 +languages: + - javascript + - nodejs + - java + - swift + - csharp +jump_to: + Help with: + - Overview#overview + - Delta processing#delta-processing + - Using deltas#using-deltas +--- + +h2(#overview). Overview + +The @delta@ parameter enables delta compression. It is applied on the channel you are subscribing to, enabling delta mode. + +Delta mode is 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. + +This is useful for channels that carry messages representing a series of updates to a particular object or document with a significant degree of similarly between successive messages. The client can apply the delta to the previous message to obtain the full payload. Using delta mode can significantly reduce the encoded size of each message in the case that message payloads change by differences that are small relative to the size of the value. This reduction in size can reduce bandwidth costs, reduce transit latencies, and enable greater message throughput on a connection. + +The delta mode implementation supports a single representation of a delta, "VCDIFF":https://tools.ietf.org/html/rfc3284. + + + Deltas explanation + + +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 params = new HashMap<>(); + params.put("rewind", "1"); + final ChannelOptions options = new ChannelOptions(); + options.params = params; + final Channel channel = ably.channels.get("{{RANDOM_CHANNEL_NAME}}", options); +``` + +```[swift] + let options = ARTClientOptions(key: key) + let client = ARTRealtime(options: options) + let channelOptions = ARTRealtimeChannelOptions() + channelOptions.params = [ + "rewind": "1" + ] + + 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("rewind", "1"); + var channelOptions = new ChannelOptions(); + channelOptions.Params = channelParams; + var channel = ably.Channels.Get("{{RANDOM_CHANNEL_NAME}}", channelOptions); + + channel.Subscribe(message => { + Console.WriteLine(message.Data.ToString()); + }); +``` + +To modify the @rewind@ channel parameters with the value @"15s"@: + +```[jsall] + var realtime = new Ably.Realtime('{{API_KEY}}'); + var channelOpts = {params: {rewind: '15s'}} + channel.setOptions(channelOpts, (err) => { + if(!err) { + console.log('channel params updated'); + } + }); +``` + +```[java] + final Map params = new HashMap<>(); + params.put("rewind", "15s"); + final ChannelOptions options = new ChannelOptions(); + options.params = params; + final Channel channel = ably.channels.get("{{RANDOM_CHANNEL_NAME}}", options); +``` + +```[swift] + let options = ARTClientOptions(key: key) + let client = ARTRealtime(options: options) + let channelOptions = ARTRealtimeChannelOptions() + channelOptions.params = [ + "rewind": "15s" + ] + + 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("rewind", "15s"); + var channelOptions = new ChannelOptions(); + channelOptions.Params = channelParams; + var channel = ably.Channels.Get("{{RANDOM_CHANNEL_NAME}}", channelOptions); + + channel.Subscribe(message => { + Console.WriteLine(message.Data.ToString()); + }); +``` + +h2(#using-parameters-non-ably). Using channel parameters without Ably library support + +For the client libraries that do not currently expose the API, or transports that do not involve using an Ably library, a set of channel parameters can be expressed by including a query string with standard URL query syntax and encoding, within the qualifier part of a channel name. The qualifier part is in square brackets at the start of the channel name. + +Examples of transports that do not use Ably libraries include using SSE without any library, or using a supported non-Ably protocol such as MQTT + +To specify the parameter @foo@ with value @bar@ on channel @baz@, the qualified channel name would be @[?foo=bar]baz@. If the channel name already has a qualifier, such as @[meta]log@, then the query string follows the existing qualifier, as in @[meta?foo=bar]log@. + +h3. Example of Ably library without channel parameters support + +Using this syntax with a non-supported Ably library means that channel parameters are specified for the lifetime of the @Channel@ instance; in order to reference the same channel, but with different channel parameters, it is necessary to get a new @Channel@ instance, using a qualified name that includes the new channel parameters. + +For example, to specify the @rewind@ channel parameter with the value @"1"@: + +```[javascript] + const realtime = new Ably.Realtime('{{API_KEY}}'); + const channel = realtime.channels.get('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); +``` + +h3. SSE example + +In an SSE connection, it is also possible to specify channel parameters as a query string in the connection URL, instead of as a qualifier on an individual channel name. In this case, the given channel parameters apply to all channel attachments associated with that connection. + +For example, to specify the @rewind@ channel parameter with the value @"1"@ using a querystring parameter, where it will apply to all channels: + +```[javascript] + var querystring = 'v=1.2&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; + var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); +``` + +Or to specify the same parameter but only applying to one channel of two, using a qualified channel name: + +```[javascript] + var channelOne = encodeURIComponent('[?rewind=1]channel1'); + var channelTwo = 'channel2'; + var channels = channelOne + ',' + channelTwo; + var querystring = 'v=1.2&key={{API_KEY}}&channels=' + channels'; + var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); +``` + +h2(#next-steps). Next steps + +* Request that an attachment start from a given number of messages or point in time in the past using "rewind":/realtime/channels/channel-parameters/rewind. +* Request that data payloads should be sent as deltas to the previous payload using "deltas":/realtime/channels/channel-parameters/deltas. + +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/rewind.textile b/content/realtime/channels/channel-parameters/rewind.textile new file mode 100644 index 0000000000..06aed44910 --- /dev/null +++ b/content/realtime/channels/channel-parameters/rewind.textile @@ -0,0 +1,145 @@ +--- +title: Rewind +section: realtime +index: 5 +languages: + - javascript + - nodejs + - java + - csharp + - swift +jump_to: + Help with: + - Overview#overview + - Examples#examples + - Additional information#additional-info +--- + +h2(#overview). Overview + +The @rewind@ parameter allows you to specify, at the time of attaching to a channel, where to start the attachment from. + +You can specify either: + +* A given number of messages. +* A point in time in the past, as a time interval. + +The parameter relates to the initial attachment of a connection to a channel, and expresses the intent to attach to the channel at a position, or a point in time, in the past (that is, effectively "rewinding" the channel for the purposes of the present attachment). + +The @rewind@ parameter is specified using "channel parameters":/realtime/channels/channel-parameters/overview. + +h2(#examples). Examples + +h3(#rewind-example-ably). Rewind example with an Ably client library + +A @rewind@ value that is a number @n@ (eg @rewind=1@) is a request to attach to the channel at a position @n@ messages before the present position. If the attachment is successful, and one or more messages exist on the channel prior to the present position, then those messages will be delivered to the subscriber immediately after the attachment has completed, and before any subsequent messages that arise in real time. + +If fewer than the requested number of messages exists on the channel (including the case that there are no prior messages), then the available messages are sent; this does not constitute an error. + +To subscribe to a channel, getting the most recent message if available: + +```[jsall] + var realtime = new Ably.Realtime('{{API_KEY}}'); + realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', { + rewind: '1' + }).subscribe(msg => console.log("Received message: ", msg)); +``` + +```[java] + final Map params = new HashMap<>(); + params.put("rewind", "1"); + final ChannelOptions options = new ChannelOptions(); + options.params = params; + final Channel channel = ably.channels.get("{{RANDOM_CHANNEL_NAME}}", options); + + 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: "{{API_KEY}}") + let client = ARTRealtime(options: options) + let channelOptions = ARTRealtimeChannelOptions() + channelOptions.params = [ + "rewind": "1" + ] + + 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("rewind", "1"); + var channelOptions = new ChannelOptions(); + channelOptions.Params = channelParams; + var channel = ably.Channels.Get("{{RANDOM_CHANNEL_NAME}}", channelOptions); + + channel.Subscribe(message => { + Console.WriteLine(message.Data.ToString()); + }); +``` + +A @rewind@ value can also be a string that is a time interval specifier. Supported specifier values express an integral number of seconds (eg @15s@) or minutes (eg @2m@). If that attachment is successful, and one or more messages exist on the channel in the given time interval prior to the present time, then those messages will be delivered to the subscriber immediately after the attachment has completed, and before any subsequent messages that arise in real time. + +If you wish to use a time interval rewind but additionally specify a limit on the number of messages to be returned, you can use the @rewindLimit@ channel parameter. For example, to request up to 10 messages in a window 5m before the present time, specify a channel parameter string of @rewind=5m&rewindLimit=10@. If fewer than the requested number of messages exists on the channel in that interval (including the case that there are no messages), then the available messages are sent; this does not constitute an error. + +h3(#rewind-example-sse). Rewind example with SSE + +To subscribe to a channel, getting the most recent message if available: + +```[javascript] + var querystring = 'v=1.2&channels={{RANDOM_CHANNEL_NAME}}&rewind=1&key={{API_KEY}}'; + var eventSource = new EventSource('https://realtime.ably.io/event-stream?' + querystring); +``` + +h3(#rewind-examples-mqtt). Rewind example with MQTT + +```[nodejs] + var mqtt = require('mqtt'); + var options = { + keepalive: 30, + username: '{{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); + client.on('connect', () => { + client.subscribe('[?rewind=1]{{RANDOM_CHANNEL_NAME}}'); + }); + client.on('message', (topic, message) => { + ... + }); +``` + +h2(#additional-info). Additional information + +At most 100 messages will be sent in a rewind request. If the number of messages within the specified interval is greater than that limit, then only the most recent messages up to that limit are sent. The attachment succeeds, but truncation of the message backlog is indicated as a non-fatal error in the attachment response. + +By default, a maximum of two minutes of channel history is available when attaching. This means that a rewind time specifier of greater than two minutes will only be able to rewind by two minutes. If a channel has persistence enabled, then it is possible to rewind back in time by up to the persistence TTL on the channel. + +The channel position expressed by a @rewind@ parameter only has an effect on an initial channel attachment. Any subsequent reattachment of the same channel on the same connection, in order to resume the connection, will attempt to resume with continuity from the point at which the connection dropped. (There are a few exceptions to this: in particular, client libraries earlier than v1.2 that have been disconnected for over two minutes, and all clients when using "@recover@ mode":/realtime/connection#connection-state-recovery ; in both cases the previous attachment state is not preserved). + +Any @rewind@ parameter value that cannot be parsed either as a number or a time specifier will cause the attachment request to fail and return an error. + +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/history.textile b/content/realtime/history.textile index 64bea8d240..343a8c8b7e 100644 --- a/content/realtime/history.textile +++ b/content/realtime/history.textile @@ -142,11 +142,11 @@ To enable history on a channel, it is necessary to add a channel rule in the set h3(#continuous-history). Continuous history -By using "rewind":/realtime/channel-params#rewind or History's @untilAttach@, it is possible to obtain message history that is continuous with the realtime messages received on an attached channel. +By using "rewind":/realtime/channels/channel-parameters/rewind or History's @untilAttach@, it is possible to obtain message history that is continuous with the realtime messages received on an attached channel. h4(#rewind). Rewind -If you wish to obtain history as part of attaching to a channel, you can use the "rewind channel parameter":/realtime/channel-params#rewind. This will act as though you had attached to a channel from a certain message or time in the past, and play through all messages since that point. Check out the main "rewind documentation":/realtime/channel-params#rewind for further details. +If you wish to obtain history as part of attaching to a channel, you can use the "rewind channel parameter":/realtime/channels/channel-parameters/rewind. This will act as though you had attached to a channel from a certain message or time in the past, and play through all messages since that point. Check out the main "rewind documentation":/realtime/channels/channel-parameters/rewind for further details. In current Ably libraries, or when using the "service without a library":https://www.ably.io/adapters, this is done by qualifying the channel name. There is a future library release planned in which it will be possible to specify channel params directly via the API. As an example, if the channel were @[some_metadata]my_channel@, you would specify the use of rewind via @[some_metadata?rewind=1]my_channel@. You can specify either a number of messages up to 100 to rewind to (for example @10@), or a time specifier of either seconds (@10s@), or minutes (@2m@) up to 2 minutes. diff --git a/content/tutorials/reactor-event-aws.textile b/content/tutorials/reactor-event-aws.textile index 44dccade04..67e213fb6f 100644 --- a/content/tutorials/reactor-event-aws.textile +++ b/content/tutorials/reactor-event-aws.textile @@ -198,7 +198,7 @@ Here we're going to enter some preliminary code just to test that everything is # Scroll down and edit @index.js@ in the _Function Code_ section. Enter the following code into the editor: -```[js] +```[javascript] const AWS = require('aws-sdk'); exports.handler = (event, context, callback) => { @@ -383,7 +383,7 @@ The application script has a few parts to it, but isn't too complex. The script The first thing to do is to instantiate the "Ably client library":https://www.ably.io/download (already linked for you in the above HTML) and verify that we can send and receive messages successfully. The following code will define a channel, subscribe to it, and use it to publish customer messages. If everything is working, the fact that we're subscribed to the same channel that we're publishing to means that our messages will simply be echoed back to us via the @receiveMessage@ handler function, which will then append them to the chat log in the HTML. -```[js] +```[javascript] // This Ably client library is available due to the script in the HTML header: const ably = Ably.Realtime('YOUR_API_KEY_GOES_HERE'); @@ -456,7 +456,7 @@ If two different customers are trying to order a pizza at the same time, we don' Here's some code to generate a random customer ID. This will be run when the page first loads, and you'll be able to then use the customerID in your channel names: -```[js] +```[javascript] // Generate a customer id, being sure to sandbox the setup code inside a closure const customerId = (function () { @@ -484,7 +484,7 @@ const customerId = (function () { Split your channel initialization code to generate two channels instead of one: -```[js] +```[javascript] // The customer's messages will be posted here const outboundChannel = ably.channels.get('pizza:customer:' + customerId); @@ -497,7 +497,7 @@ inboundChannel.subscribe(receiveMessage); Let's refactor the message publishing code into its own function. We'll also modify what we're sending so that it includes the customer ID in the message data. This will make it easy for the Lambda function to reply on the correct channel without having to extract the customer ID from the name of the inbound channel: -```[js] +```[javascript] function postMessage(message) { // Note the additional call to appendMessageElement, which previously was only // being used by the receiveMessage handler. Because we want to visually @@ -516,7 +516,7 @@ function postMessage(message) { Don't forget to also update your @processInput@ function to use @postMessage@ rather than publishing directly: -```[js] +```[javascript] function processInput(e) { if (e.which !== 13) { // Character code 13 is the ENTER key return; @@ -535,7 +535,7 @@ We could test the above now, and you certainly can do so if you want to be sure Go back and edit your Lambda function with the following code ("which you can find in the tutorial repository":https://github.com/ably/tutorials/tree/reactor-event-aws-javascript/lambda-function.js): -```[js] +```[javascript] // We'll need the Node.js HTTPS module to post a response back to Ably const https = require("https") @@ -663,7 +663,7 @@ Also, the UI is not very useful unless the text input box is focused at all time Modify the @appendMessage@ function to return a reference to the HTML element it has just appended to the DOM: -```[js] +```[javascript] function appendMessageElement(type, message) { const div = document.createElement('div'); div.classList.add(type, 'message'); @@ -679,7 +679,7 @@ function appendMessageElement(type, message) { Next, we'll define a special "waiting" message that we add when the user types a message, and remove when a reply is received: -```[js] +```[javascript] let waitingElement; // persist a reference to the HTML element function setWaiting(isWaiting) { // isWaiting should be true or false @@ -707,7 +707,7 @@ function setWaiting(isWaiting) { // isWaiting should be true or false Update the code to call the above function when sending and receiving messages: -```[js] +```[javascript] function receiveMessage(message) { setWaiting(false); // The bot has replied, so remove the waiting message appendMessageElement('bot', message.data); @@ -722,7 +722,7 @@ function postMessage(message) { Finally, at the end of the script where your text box event listener is defined, hook up a couple of extra event listeners to keep the user focused on the input text box: -```[js] +```[javascript] function focusInputField() { inputField.focus(); } diff --git a/content/tutorials/reactor-event-azure.textile b/content/tutorials/reactor-event-azure.textile index 825fe62ae4..102102e00d 100644 --- a/content/tutorials/reactor-event-azure.textile +++ b/content/tutorials/reactor-event-azure.textile @@ -126,7 +126,7 @@ In the interface displayed, you'll see a space to edit your function, and a tab In the code editor, update the code editor with the following script, **making sure to update the line containing "YOUR_API_KEY_HERE" with your actual API key:** -```[js] +```[javascript] const Ably = require('ably'); const processMessage = require('./pizza'); @@ -195,7 +195,7 @@ Next, click the "View files" tab to the far right of the Azure function code edi When the script files are uploaded, click the "Test" option right above the file list. In the "Request body" editor, enter the following JSON code: -```[js] +```[javascript] { "customerId": "abc123", "message": "I'd like a pepperoni pizza, please" @@ -311,7 +311,7 @@ The first thing to do is to instantiate the "Ably client library":https://www.ab *Don't forget to replace @YOUR_API_KEY_GOES_HERE@ with your actual API key!* -```[js] +```[javascript] // This Ably client library is available due to the script in the HTML header: const ably = Ably.Realtime('YOUR_API_KEY_GOES_HERE'); @@ -384,7 +384,7 @@ If two different customers are trying to order a pizza at the same time, we don' Here's some code to generate a random customer ID. This will be run when the page first loads, and you'll be able to then use the customerID in your channel names: -```[js] +```[javascript] // Generate a customer id, being sure to sandbox the setup code inside a closure const customerId = (function () { @@ -412,7 +412,7 @@ const customerId = (function () { Split your channel initialization code to generate two channels instead of one: -```[js] +```[javascript] // The customer's messages will be posted here const outboundChannel = ably.channels.get('pizza:customer:' + customerId); @@ -425,7 +425,7 @@ inboundChannel.subscribe(receiveMessage); We're not using any special server-side persistence features, so we'll need to keep track of the conversation context and order details ourselves, and pass them to the chat bot as the conversation progresses. Only the bot will be updating the context; we'll just keep track of it on the bot's behalf, so that it isn't lost from one message to the next. -```[js] +```[javascript] let context = null; // We start with an uninitialized conversation context function receiveMessage(message) { @@ -436,7 +436,7 @@ function receiveMessage(message) { Let's refactor the message publishing code into its own function. We'll also modify what we're sending so that it includes the customer ID in the message data. This will make it easy for the Azure function to reply on the correct channel without having to extract the customer ID from the name of the inbound channel: -```[js] +```[javascript] function postMessage(message) { // Note the additional call to appendMessageElement, which previously was only // being used by the receiveMessage handler. Because we want to visually @@ -455,7 +455,7 @@ function postMessage(message) { Don't forget to also update your @processInput@ function to use @postMessage@ rather than publishing directly: -```[js] +```[javascript] function processInput(e) { if (e.which !== 13) { // Character code 13 is the ENTER key return; @@ -480,7 +480,7 @@ Also, the UI is not very useful unless the text input box is focused at all time Modify the @appendMessage@ function to return a reference to the HTML element it has just appended to the DOM: -```[js] +```[javascript] function appendMessageElement(type, message) { const div = document.createElement('div'); div.classList.add(type, 'message'); @@ -496,7 +496,7 @@ function appendMessageElement(type, message) { Next, we'll define a special "waiting" message that we add when the user types a message, and remove when a reply is received: -```[js] +```[javascript] let waitingElement; // persist a reference to the HTML element function setWaiting(isWaiting) { // isWaiting should be true or false @@ -524,7 +524,7 @@ function setWaiting(isWaiting) { // isWaiting should be true or false Update the code to call the above function when sending and receiving messages: -```[js] +```[javascript] function receiveMessage(message) { setWaiting(false); // The bot has replied, so remove the waiting message context = message.data.context; @@ -540,7 +540,7 @@ function postMessage(message) { Finally, at the end of the script where your text box event listener is defined, hook up a couple of extra event listeners to keep the user focused on the input text box: -```[js] +```[javascript] function focusInputField() { inputField.focus(); } diff --git a/content/tutorials/reactor-event-google.textile b/content/tutorials/reactor-event-google.textile index 09e1b8889a..71f8193707 100644 --- a/content/tutorials/reactor-event-google.textile +++ b/content/tutorials/reactor-event-google.textile @@ -99,7 +99,7 @@ From the "tutorial repository":https://github.com/ably/tutorials/tree/reactor-ev Create a new file in your project folder, and name it @index.js@. Copy the following script into the file, **making sure to update the line containing "YOUR_API_KEY_HERE" with your actual API key:** -```[js] +```[javascript] const Ably = require('ably'); const processMessage = require('./pizza'); @@ -184,7 +184,7 @@ When your function has been deployed successfully, it's time to test it and make Back in the Google Cloud interface for your deployed function, you'll see a set of tabs, one of which is labelled "Testing". Enter the following JSON code into the field labelled "Triggering event", then click the blue "Test the function" button: -```[js] +```[javascript] { "customerId": "test", "message": "I'd like a pepperoni pizza, please" @@ -304,7 +304,7 @@ The code below should go in your @app.js@ file. *Don't forget to replace @YOUR_API_KEY_GOES_HERE@ with your actual API key!* -```[js] +```[javascript] // This Ably client library is available due to the script in the HTML header: const ably = Ably.Realtime('YOUR_API_KEY_GOES_HERE'); @@ -377,7 +377,7 @@ If two different customers are trying to order a pizza at the same time, we don' Here's some code to generate a random customer ID. This will be run when the page first loads, and you'll be able to then use the customerID in your channel names: -```[js] +```[javascript] // Generate a customer id, being sure to sandbox the setup code inside a closure const customerId = (function () { @@ -405,7 +405,7 @@ const customerId = (function () { Split your channel initialization code to generate two channels instead of one: -```[js] +```[javascript] // The customer's messages will be posted here const outboundChannel = ably.channels.get('pizza:customer:' + customerId); @@ -418,7 +418,7 @@ inboundChannel.subscribe(receiveMessage); We're not using any special server-side persistence features, so we'll need to keep track of the conversation context and order details ourselves, and pass them to the chat bot as the conversation progresses. Only the bot will be updating the context; we'll just keep track of it on the bot's behalf, so that it isn't lost from one message to the next. -```[js] +```[javascript] let context = null; // We start with an uninitialized conversation context function receiveMessage(message) { @@ -429,7 +429,7 @@ function receiveMessage(message) { Let's refactor the message publishing code into its own function. We'll also modify what we're sending so that it includes the customer ID in the message data. This will make it easy for the Google function to reply on the correct channel without having to extract the customer ID from the name of the inbound channel: -```[js] +```[javascript] function postMessage(message) { // Note the additional call to appendMessageElement, which previously was only // being used by the receiveMessage handler. Because we want to visually @@ -448,7 +448,7 @@ function postMessage(message) { Don't forget to also update your @processInput@ function to use @postMessage@ rather than publishing directly: -```[js] +```[javascript] function processInput(e) { if (e.which !== 13) { // Character code 13 is the ENTER key return; @@ -473,7 +473,7 @@ Also, the UI is not very useful unless the text input box is focused at all time Modify the @appendMessage@ function to return a reference to the HTML element it has just appended to the DOM: -```[js] +```[javascript] function appendMessageElement(type, message) { const div = document.createElement('div'); div.classList.add(type, 'message'); @@ -489,7 +489,7 @@ function appendMessageElement(type, message) { Next, we'll define a special "waiting" message that we add when the user types a message, and remove when a reply is received: -```[js] +```[javascript] let waitingElement; // persist a reference to the HTML element function setWaiting(isWaiting) { // isWaiting should be true or false @@ -517,7 +517,7 @@ function setWaiting(isWaiting) { // isWaiting should be true or false Update the code to call the above function when sending and receiving messages: -```[js] +```[javascript] function receiveMessage(message) { setWaiting(false); // The bot has replied, so remove the waiting message context = message.data.context; @@ -533,7 +533,7 @@ function postMessage(message) { Finally, at the end of the script where your text box event listener is defined, hook up a couple of extra event listeners to keep the user focused on the input text box: -```[js] +```[javascript] function focusInputField() { inputField.focus(); } diff --git a/data/jsbins.yaml b/data/jsbins.yaml index 5c14ebb605..0fd3784b7e 100644 --- a/data/jsbins.yaml +++ b/data/jsbins.yaml @@ -31,7 +31,6 @@ jsbin_hash: "+7edQ0/+RVdXbSaumOltjdMtYW8=": udenus J6B4SjIgGXie5DHoSF30sGPzb64=: akutul dqNcZL3zwOeS3PmMAzJ54wVDkNg=: utizit - fDq9P84O672WgwAsIhYmPxnPLRA=: azifaq 1vHSWAcT6EJzqWwQU4oqY1onx1o=: akimez eBxLJU1YqdX+JW9JQY70S4iQfmU=: omedab pzHKfCW4/o2wDnCfbg1m0Pkl7v8=: ovucin @@ -43,6 +42,9 @@ jsbin_hash: Sf9QPfpwxOvNcKy+KTQKXHq8dbc=: umikub 6voT8sjZ4GnNONoP3sW/Uaq2GkE=: aqalul 5SY9KnghzlKVnF/NlXih5jySnug=: utulow + UjxZZTfXMcghYm5u6mmtFfDVdHc=: iyoqim + Mxz+xzHdWwWQBNl+Nkhr/yalpmg=: ajosuv + mZcTN8McM4HXm+nDeiZqLj9gq8w=: izopuj jsbin_id: adapters/pusher-pub-sub: eBxLJU1YqdX+JW9JQY70S4iQfmU= adapters/pubnub-pub-sub: pzHKfCW4/o2wDnCfbg1m0Pkl7v8= @@ -84,6 +86,8 @@ jsbin_id: sse/sse: 5SY9KnghzlKVnF/NlXih5jySnug= sse/eventstream: 6voT8sjZ4GnNONoP3sW/Uaq2GkE= rest/batch-presence: cHvxfaSCnHR6qzdutJZ/TZWK0FE= - realtime/rewind: fDq9P84O672WgwAsIhYmPxnPLRA= + realtime/rewind: Mxz+xzHdWwWQBNl+Nkhr/yalpmg= authentication/jwt-token: Q0XIw74KKfK3Zv34fqKSck4ip5U= realtime/connection-recover: Sf9QPfpwxOvNcKy+KTQKXHq8dbc= + realtime/channel-deltas: UjxZZTfXMcghYm5u6mmtFfDVdHc= + realtime/channel-deltas-size: mZcTN8McM4HXm+nDeiZqLj9gq8w=