diff --git a/.circleci/config.yml b/.circleci/config.yml
index cf2aa70fbf..c2efa287b4 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -67,6 +67,17 @@ jobs:
- config
- public
+ validate-llms-txt:
+ executor:
+ name: default
+ steps:
+ - checkout
+ - attach_workspace:
+ at: .
+ - run:
+ name: Validate llms.txt
+ command: yarn validate-llms-txt
+
test-nginx:
docker:
- image: heroku/heroku:24-build
@@ -143,4 +154,7 @@ workflows:
- test-nginx:
requires:
- build
+ - validate-llms-txt:
+ requires:
+ - build
diff --git a/.env.example b/.env.example
index 5df5a8df0d..12a537f6cb 100644
--- a/.env.example
+++ b/.env.example
@@ -11,8 +11,7 @@ ASSET_PREFIX=
INKEEP_CHAT_ENABLED=
INKEEP_CHAT_API_KEY=
-INKEEP_CHAT_INTEGRATION_ID=
-INKEEP_CHAT_ORGANIZATION_ID=
+CONVERSATIONS_API_URL='http://localhost:3000/api/conversations'
INSIGHTS_ENABLED=false
INSIGHTS_DEBUG=true
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000000..cdd56bd501
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,3 @@
+# Ignore all mdx files in src/pages - we need to be able to render components on one line for inlining
+src/pages/**/*.mdx
+src/pages/**/*.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3607502668..804bed99e3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -180,7 +180,7 @@ There are three types of admonition that can be used; `note`, `important` and `f
#### Feature changes
-Two additional types of admonition are used to display features that have been added or updated in a version update; `new` and `updated`.
+Three additional types of admonition are used to display features that have been added or updated in a version update; `new`, `updated` and `experimental`.
```html
-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@@String@, @Map@, @List@__
-
-h6(#extras).
- default: extras
- csharp: Extras
-
-Metadata and/or ancillary payloads, if provided. The only currently valid payloads for extras are the "@push@":/docs/push/publish#sub-channels, "@ref@":/docs/channels/messages#interactions and "@privileged@":/docs/integrations/webhooks#skipping objects. __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(#connection-key).
- default: connectionKey
- csharp,go: ConnectionKey
- ruby,python: connection_key
-
-A connection key, which can optionally be included for a REST publish as part of the "publishing on behalf of a realtime client functionality":/docs/pub-sub/advanced#publish-on-behalf. __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@":/docs/api/rest-sdk/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@":/docs/api/rest-sdk/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@":/docs/api/rest-sdk/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@":/docs/api/rest-sdk/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@":/docs/api/rest-sdk/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@":/docs/api/rest-sdk/types#message objects
+<%= partial partial_version('types/_message') %>
diff --git a/content/channels/options/deltas.textile b/content/channels/options/deltas.textile
index 16839dbed3..1421a0881d 100644
--- a/content/channels/options/deltas.textile
+++ b/content/channels/options/deltas.textile
@@ -24,8 +24,8 @@ As @delta@ only applies to channel subscriptions, it is only available when usin
Using delta mode can significantly reduce the encoded size of each message when the difference between successive message payload sizes are small, relative to the overall size. The reduction can reduce bandwidth costs and transit latencies, and enable greater message throughput on a connection.
-
-
+
+
h2(#processing). Delta processing
diff --git a/content/chat/connect.textile b/content/chat/connect.textile
index c2036095c0..0263b8891d 100644
--- a/content/chat/connect.textile
+++ b/content/chat/connect.textile
@@ -37,7 +37,7 @@ const error = chatClient.connection.error;
```
```[react]
-import { useChatConnection } from '@ably/chat';
+import { useChatConnection } from '@ably/chat/react';
const MyComponent = () => {
const { currentStatus } = useChatConnection({
@@ -50,7 +50,7 @@ const MyComponent = () => {
```
```[swift]
-let status = await chatClient.connection.status
+let status = chatClient.connection.status
```
```[kotlin]
@@ -61,7 +61,7 @@ blang[react].
Hooks related to chat features, such as @useMessages@ and @useTyping@, also return the current @connectionStatus@ in their response.
```[react]
- import { useMessages } from '@ably/chat';
+ import { useMessages } from '@ably/chat/react';
const MyComponent = () => {
const { connectionStatus } = useMessages({
@@ -84,7 +84,7 @@ const { off } = chatClient.connection.onStatusChange((change) => console.log(cha
```
```[react]
-import { useOccupancy } from '@ably/chat';
+import { useOccupancy } from '@ably/chat/react';
const MyComponent = () => {
useOccupancy({
@@ -159,7 +159,7 @@ const { off } = room.messages.onDiscontinuity((reason?: ErrorInfo) => {
```[react]
import { useState } from 'react';
-import { useMessages } from '@ably/chat';
+import { useMessages } from '@ably/chat/react';
const MyComponent = () => {
useMessages({
diff --git a/content/chat/getting-started/javascript.textile b/content/chat/getting-started/javascript.textile
new file mode 100644
index 0000000000..2940999c72
--- /dev/null
+++ b/content/chat/getting-started/javascript.textile
@@ -0,0 +1,251 @@
+---
+title: "Getting started: Chat in JavaScript / TypeScript"
+meta_description: "Get started with Ably's JavaScript Chat SDK. Build scalable, real-time chat applications using live chat APIs and realtime messaging."
+meta_keywords: "chat js, javascript chat sdk, live chat javascript api, javascript chat api, chat api js, chat api javascript, chat javascript api, real time chat javascript, real time chat application in javascript, javascript real time chat, ably chat sdk, typescript chat sdk, typescript chat api, real time messaging javascript, realtime chat javascript, realtime chat application javascript"
+---
+
+This guide will get you started with Ably Chat using TypeScript.
+
+It will take you through the following steps:
+
+* Create a client and establish a realtime connection to Ably.
+* Create a room and subscribe to its messages.
+* Send a message to the room, and then edit that message.
+* Retrieve historical messages to provide context to new joiners.
+* Set up a typing indicator to see which clients are typing.
+* Display the online status of clients to the room.
+* Subscribe and send reactions to the room.
+* Close a connection to Ably when it is no longer needed.
+
+h2(#prerequisites). Prerequisites
+
+* Sign up for an Ably account.
+** Create a new app, and get your first API key.
+** You can use the root API key that is provided by default to get started.
+
+* Install the Ably CLI:
+
+```[sh]
+npm install -g @ably/cli
+```
+
+* Run the following to log in to your Ably account and set the default app and API key:
+
+```[sh]
+ably login
+
+ably apps switch
+ably auth keys switch
+```
+
+* Install "Node.js":https://nodejs.org/en version 18 or greater.
+* Install "ts-node":https://www.npmjs.com/package/ts-node to execute TypeScript files:
+
+```[sh]
+npm install ts-node
+```
+
+* Create a new project in your IDE and install the Ably Chat JavaScript SDK. This will also install the Ably Pub/Sub SDK as it is a dependency:
+
+```[sh]
+npm init -y
+tsc --init
+
+npm install @ably/chat
+```
+
+
+
+h2(#step-1). Step 1: Connect to Ably
+
+Clients establish a connection with Ably when they instantiate an SDK. This enables them to send and receive messages in realtime across channels.
+
+Create an @index.ts@ file in your project and add the following function to instantiate a realtime client with the Pub/Sub SDK and then pass that client into the Chat SDK constructor. Provide an API key and a @clientId@ to identify the client. In production, use token authentication so that your API keys are not exposed publicly.
+
+```[javascript]
+import * as Ably from 'ably';
+import { ChatClient, AllFeaturesEnabled, MessageEvent, Message } from '@ably/chat';
+
+async function getStarted() {
+
+ const realtimeClient = new Ably.Realtime({ key: '{{API_KEY}}', clientId: 'my-first-client' });
+
+ const chatClient = new ChatClient(realtimeClient);
+
+ chatClient.connection.onStatusChange((change) => console.log(`Connection status is currently ${change.current}!`));
+
+};
+
+getStarted();
+```
+
+You can monitor the lifecycle of clients' connections by registering a listener that will emit an event every time the connection state changes. For now, run the function with @npx ts-node index.ts@ to log a message to the console to know that the connection attempt was successful. You'll see a message saying @Connection status is currently connected!@ printed to your console.
+
+h2(#step-2). Step 2: Create a room and send a message
+
+Messages are how your clients interact with one another. Use rooms to separate and organize clients and messages into different topics, or 'chat rooms'. Rooms are the entry object into Chat, providing access to all of its features, such as messages, presence and reactions.
+
+Add the following lines to your @getStarted()@ function to create an instance of a room, attach to the room instance, and then register a listener to subscribe to messages sent to the room. You then also send your first message. Afterwards, run it with @npx ts-node index.ts@:
+
+```[javascript]
+const room = await chatClient.rooms.get('my-first-room', AllFeaturesEnabled);
+
+await room.attach();
+
+room.messages.subscribe((messageEvent: MessageEvent) => {
+ console.log(`Received message: ${ messageEvent.message.text }`);
+});
+
+const myFirstMessage = await room.messages.send({ text: 'My first message!' });
+```
+
+Use the Ably CLI to send a message to your first room. The message will be received by the client you've subscribed to the room, and be logged to the console:
+
+```[sh]
+ably rooms messages send my-first-room 'Hello!'
+```
+
+h2(#step-3). Step 3: Edit a message
+
+If your client makes a typo, or needs to update their original message then they can edit it.
+
+Add the following line to your @getStarted()@ function. Now, when you run the function again and send another message, that message will be updated and you should see both in your terminal. Run it with @npx ts-node index.ts@.
+
+```[javascript]
+await room.messages.update(myFirstMessage.copy({ text: 'My 2nd message! (edited)' }));
+```
+
+h2(#step-4). Step 4: Message history and continuity
+
+Ably Chat provides a method for retrieving messages that have been previously sent in a room, up until the point that a client joins (attaches) to it. This enables clients joining a room part way through a conversation to receive the context of what has happened, and what is being discussed, without having to ask another client for context.
+
+Use the Ably CLI to send some additional messages to your room, for example:
+
+```[sh]
+ably rooms messages send my-first-room 'Old message #1'
+```
+
+Create a new @clientTwo.ts@ file in your project, and run the following in a separate terminal instance. It imitates a second client, @my-second-client@ joining the room and receiving the context of the previous 10 messages. Run it with @npx ts-node clientTwo.ts@
+
+```[javascript]
+import * as Ably from 'ably';
+import { ChatClient, AllFeaturesEnabled } from '@ably/chat';
+
+async function getStartedLate() {
+
+ const realtimeClient = new Ably.Realtime({ key: '{{API_KEY}}', clientId: 'my-second-client' });
+ const chatClient = new ChatClient(realtimeClient);
+
+
+ const room = await chatClient.rooms.get('my-first-room', AllFeaturesEnabled);
+
+ await room.attach();
+
+ const { getPreviousMessages } = room.messages.subscribe(() => {
+ console.log('New message received');
+ });
+
+
+ const historicalMessages = await getPreviousMessages({ limit: 10 });
+ console.log(historicalMessages.items.map((message) => `${message.clientId}: ${message.text}`));
+
+};
+
+getStartedLate();
+```
+
+h2(#step-5). Step 5: Show who is typing a message
+
+Typing indicators enable you to display messages to clients when someone is currently typing. An event is emitted when someone starts typing, when they press a keystroke, and then another event is emitted after a configurable amount of time has passed without a key press.
+
+In practice the @typing.start()@ method would be called on keypress, however for demonstration purposes we will call it and wait for the default period of time to pass for a stop event to be emitted. Using your original client, add the following lines to @getStarted()@ to subscribe to typing events and emit one, then run it with @npx ts-node index.ts@:
+
+```[javascript]
+room.typing.subscribe((typingEvent) => {
+ if (typingEvent.currentlyTyping.size === 0) {
+ console.log('No one is currently typing');
+ } else {
+ console.log(`${Array.from(typingEvent.currentlyTyping).join(", ")} is typing`);
+ }
+});
+
+await room.typing.start()
+// A client types
+await room.typing.stop()
+```
+
+Use the Ably CLI to subscribe to typing events. You will see that the client using the SDK starts to type after re-running @getStarted()@, with the message @my-first-client is typing...@:
+
+```[sh]
+ably rooms typing subscribe my-first-room
+```
+
+h2(#step-6). Step 6: Display online status
+
+Display the online status of clients using the presence feature. This enables clients to be aware of one another if they are present in the same room. You can then show clients who else is online, provide a custom status update for each, and notify the room when someone enters it, or leaves it, such as by going offline.
+
+Add the following lines to your @getStarted()@ function to subscribe to, and join, the presence set of the room. It also sets a short wait before leaving the presence set. Run it with @npx ts-node index.ts@:
+
+```[javascript]
+room.presence.subscribe((member) => {
+ console.log(`Event type: ${ member.action } from ${ member.clientId } with the data ${ JSON.stringify(member.data) }`)
+});
+
+await room.presence.enter("I'm here!");
+
+setTimeout( () => {
+ room.presence.leave("I'm leaving!");
+}, 3000);
+```
+
+You can have another client join the presence set using the Ably CLI:
+
+```[sh]
+ably rooms presence enter my-first-room --client-id "my-cli" --data '{"status":"learning about Ably!"}'
+```
+
+h2(#step-7). Step 7: Send a reaction
+
+Clients can send an ephemeral reaction to a room to show their sentiment for what is happening, such as a point being scored in a sports game.
+
+Add the following lines to your @getStarted()@ function to subscribe to room reactions. Then run it with @npx ts-node index.ts@:
+
+```[javascript]
+room.reactions.subscribe((reaction) => {
+ console.log(`${ reaction.type } to that!`)
+});
+```
+
+Use the Ably CLI to send some reactions to the room, such as:
+
+```[sh]
+ably rooms reactions send my-first-room 👍
+```
+
+h2(#step-8). Step 8: Close the connection
+
+Connections are automatically closed approximately 2 minutes after no heartbeat is detected by Ably. Explicitly closing connections when they are no longer needed is good practice to help save costs. It will also remove all listeners that were registered by the client.
+
+Connections to Ably are handled by the underlying Pub/Sub SDK. To close the connection you need to call @connection.close()@ on the original realtime client instance.
+
+Add the following to @getStarted()@ to close the connection after a simulated 10 seconds. Run it with @npx ts-node index.ts@:
+
+```[javascript]
+setTimeout(() => realtimeClient.close(), 10000);
+```
+
+h2(#next). Next steps
+
+Continue to explore the documentation with JavaScript as the selected language:
+
+Read more about the concepts covered in this guide:
+* Read more about using "rooms":/docs/chat/rooms and sending "messages":/docs/chat/rooms/messages.
+* Find out more regarding "online status":/docs/chat/rooms/presence.
+* Understand how to use "typing indicators":/docs/chat/rooms/typing.
+* Send "reactions":/docs/chat/rooms/reactions to your rooms.
+* Read into pulling messages from "history":/docs/chat/rooms/history and providing context to new joiners.
+* Understand "token authentication":/docs/authentication/token-authentication before going to production.
+
+Explore the Ably CLI further, or visit the Chat "API references":https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/modules/chat-js.html.
diff --git a/content/chat/index.textile b/content/chat/index.textile
index 8e749a5c9b..a81bf59772 100644
--- a/content/chat/index.textile
+++ b/content/chat/index.textile
@@ -1,6 +1,7 @@
---
title: About Chat
meta_description: "Learn more about Ably Chat and the features that enable you to quickly build functionality into new and existing applications."
+product: chat
redirect_from: /docs/products/chat
---
@@ -39,4 +40,4 @@ h3(#reactions). Room reactions
h2. Demo
-Take a look at a "livestream basketball game":https://ably-livestream-chat-demo.vercel.app with some simulated users chatting built using the Chat SDK. The "source code":https://github.com/ably/ably-chat-js/tree/main/demo is available in GitHub.
+Take a look at a "livestream basketball game":https://ably-livestream-chat-demo.vercel.app with some simulated users chatting built using the Chat SDK. The "source code":https://github.com/ably/ably-chat-js/tree/main/demo is available in GitHub.
\ No newline at end of file
diff --git a/content/chat/rooms/history.textile b/content/chat/rooms/history.textile
index d788f49aa9..ba816d135d 100644
--- a/content/chat/rooms/history.textile
+++ b/content/chat/rooms/history.textile
@@ -32,7 +32,7 @@ if (historicalMessages.hasNext()) {
```
```[react]
-import { useMessages } from '@ably/chat';
+import { useMessages } from '@ably/chat/react';
const MyComponent = () => {
const { get } = useMessages();
@@ -110,7 +110,7 @@ if (historicalMessages.hasNext()) {
```[react]
import { useEffect, useState } from 'react';
-import { useMessages } from '@ably/chat';
+import { useMessages } from '@ably/chat/react';
const MyComponent = () => {
const [loading, setLoading] = useState(true);
diff --git a/content/chat/rooms/index.textile b/content/chat/rooms/index.textile
index 794656727e..d63cac6e5a 100644
--- a/content/chat/rooms/index.textile
+++ b/content/chat/rooms/index.textile
@@ -35,7 +35,8 @@ const room = await chatClient.rooms.get('basketball-stream', AllFeaturesEnabled)
```[react]
import * as Ably from 'ably';
-import { ChatClientProvider, ChatRoomProvider, LogLevel, AllFeaturesEnabled } from '@ably/chat';
+import { LogLevel, AllFeaturesEnabled } from '@ably/chat';
+import { ChatClientProvider, ChatRoomProvider } from '@ably/chat/react';
const realtimeClient = new Ably.Realtime({ key: '{{API_KEY}}', clientId: 'clientId' });
const chatClient = new ChatClient(realtimeClient);
@@ -177,7 +178,7 @@ await room.attach();
```
```[react]
-import { useRoom } from '@ably/chat';
+import { useRoom } from '@ably/chat/react';
const MyComponent = () => {
const { attach } = useRoom();
@@ -251,7 +252,7 @@ const currentError = room.error;
```
```[react]
-import { useMessages } from '@ably/chat';
+import { useMessages } from '@ably/chat/react';
const MyComponent = () => {
const { roomStatus, roomError } = useMessages({
@@ -270,7 +271,7 @@ const MyComponent = () => {
```
```[swift]
-let status = try await room.status
+let status = room.status
```
```[kotlin]
@@ -288,7 +289,7 @@ blang[javascript,swift,kotlin].
```
```[swift]
- let statusSubscription = try await room.onStatusChange()
+ let statusSubscription = room.onStatusChange()
for await status in statusSubscription {
print("Room status: \(status)")
}
@@ -334,7 +335,7 @@ blang[react].
Listeners can also be registered to monitor the changes in room status. Any hooks that take an optional listener to monitor their events, such as typing indicator events in the @useTyping@ hook, can also register a status change listener. Changing the value provided for a listener will cause the previously registered listener instance to stop receiving events. All messages will be received by exactly one listener.
```[react]
- import { useOccupancy } from '@ably/chat';
+ import { useOccupancy } from '@ably/chat/react';
const MyComponent = () => {
useOccupancy({
diff --git a/content/chat/rooms/messages.textile b/content/chat/rooms/messages.textile
index 5d8ab159bc..fe2dafbc73 100644
--- a/content/chat/rooms/messages.textile
+++ b/content/chat/rooms/messages.textile
@@ -30,7 +30,7 @@ const {unsubscribe} = room.messages.subscribe((event) => {
```[react]
import { useState } from 'react';
-import { useMessages } from '@ably/chat';
+import { useMessages } from '@ably/chat/react';
const MyComponent = () => {
useMessages({
@@ -157,7 +157,7 @@ await room.messages.send({text: 'hello'});
```
```[react]
-import { useMessages } from '@ably/chat';
+import { useMessages } from '@ably/chat/react';
const MyComponent = () => {
const { send } = useMessages();
@@ -202,7 +202,8 @@ await room.messages.update(updatedMessage, { description: "Message update by use
```
```[react]
-import { Message, useMessages } from '@ably/chat';
+import { Message } from '@ably/chat';
+import { useMessages } from '@ably/chat/react';
const MyComponent = () => {
const { update } = useMessages();
@@ -273,7 +274,8 @@ const {unsubscribe} = room.messages.subscribe((event) => {
```
```[react]
-import { MessageEvents, useMessages } from '@ably/chat';
+import { MessageEvents } from '@ably/chat';
+import { useMessages } from '@ably/chat/react';
const MyComponent = () => {
useMessages({
@@ -386,7 +388,8 @@ await room.messages.delete(messageToDelete, { description: 'Message deleted by u
```
```[react]
-import { Message, useMessages } from '@ably/chat';
+import { Message } from '@ably/chat';
+import { useMessages } from '@ably/chat/react';
const MyComponent = () => {
const { deleteMessage } = useMessages();
@@ -458,7 +461,8 @@ const {unsubscribe} = room.messages.subscribe((event) => {
```
```[react]
-import { MessageEvents, useMessages } from '@ably/chat';
+import { MessageEvents } from '@ably/chat';
+import { useMessages } from '@ably/chat/react';
const MyComponent = () => {
useMessages({
@@ -569,4 +573,4 @@ Applying an action to a message produces a new version, which is uniquely identi
The @Message@ object also has convenience methods "@isOlderVersionOf@":https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Message.html#isolderversionof, "@isNewerVersionOf@":https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Message.html#isnewerversionof and "@isSameVersionAs@":https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/interfaces/chat-js.Message.html#issameversionas which provide the same comparison.
-Update and Delete events provide the full message payload, so may be used to replace the entire earlier version of the message.
+Update and Delete events provide the full message payload, so may be used to replace the entire earlier version of the message.
\ No newline at end of file
diff --git a/content/chat/rooms/occupancy.textile b/content/chat/rooms/occupancy.textile
index 0916c7f438..ccbcf93e31 100644
--- a/content/chat/rooms/occupancy.textile
+++ b/content/chat/rooms/occupancy.textile
@@ -30,7 +30,7 @@ const {unsubscribe} = room.occupancy.subscribe((event) => {
```
```[react]
-import { useOccupancy } from '@ably/chat';
+import { useOccupancy } from '@ably/chat/react';
const MyComponent = () => {
const { connections, presenceMembers } = useOccupancy({
@@ -49,7 +49,7 @@ const MyComponent = () => {
```
```[swift]
-let occupancySubscription = try await room.occupancy.subscribe()
+let occupancySubscription = room.occupancy.subscribe()
for await event in occupancySubscription {
occupancyInfo = "Connections: \(event.presenceMembers) (\(event.connections))"
}
diff --git a/content/chat/rooms/presence.textile b/content/chat/rooms/presence.textile
index 01d55510b8..bce7ea67a0 100644
--- a/content/chat/rooms/presence.textile
+++ b/content/chat/rooms/presence.textile
@@ -33,7 +33,7 @@ const { unsubscribe } = room.presence.subscribe((event) => {
```[react]
import React from 'react';
-import { usePresenceListener } from '@ably/chat';
+import { usePresenceListener } from '@ably/chat/react';
const MyComponent = () => {
const { presenceData, error } = usePresenceListener({
@@ -60,7 +60,7 @@ const MyComponent = () => {
```
```[swift]
-let presenceSubscription = try await room.presence.subscribe(events: [.enter, .leave, .update])
+let presenceSubscription = room.presence.subscribe(events: [.enter, .leave, .update])
for await event in presenceSubscription {
print("Presence event `\(event.action)` from `\(event.clientId)` with data `\(event.data)`")
}
@@ -89,10 +89,10 @@ blang[javascript,swift].
```[swift]
// Subscribe to only 'enter' events:
- let presenceSubscription = try await room.presence.subscribe(event: .enter)
+ let presenceSubscription = room.presence.subscribe(event: .enter)
// Subscribe to 'update' and 'leave' events:
- let presenceSubscription = try await room.presence.subscribe(events: [.leave, .update])
+ let presenceSubscription = room.presence.subscribe(events: [.leave, .update])
```
blang[react,kotlin].
@@ -180,7 +180,7 @@ await room.presence.enter({ status: 'available' });
```[react]
import React from 'react';
-import { usePresence } from '@ably/chat';
+import { usePresence } from '@ably/chat/react';
const MyComponent = () => {
const { leave, isPresent } = usePresence({
@@ -220,7 +220,7 @@ await room.presence.update({ status: 'busy' });
```[react]
import React from 'react';
-import { usePresence } from '@ably/chat';
+import { usePresence } from '@ably/chat/react';
const MyComponent = () => {
const { update, isPresent } = usePresence({
@@ -264,7 +264,7 @@ await room.presence.leave({ status: 'Be back later!' });
```[react]
import React from 'react';
-import { usePresence } from '@ably/chat';
+import { usePresence } from '@ably/chat/react';
const MyComponent = () => {
const { leave, isPresent } = usePresence({
diff --git a/content/chat/rooms/reactions.textile b/content/chat/rooms/reactions.textile
index dbf8a9fa15..232b9e792d 100644
--- a/content/chat/rooms/reactions.textile
+++ b/content/chat/rooms/reactions.textile
@@ -33,7 +33,7 @@ const {unsubscribe} = room.reactions.subscribe((reaction) => {
```[react]
import React, { useCallback } from 'react';
-import { useRoomReactions } from '@ably/chat';
+import { useRoomReactions } from '@ably/chat/react';
const MyComponent = () => {
useRoomReactions({
@@ -45,7 +45,7 @@ const MyComponent = () => {
```
```[swift]
-let reactionSubscription = try await room.reactions.subscribe()
+let reactionSubscription = room.reactions.subscribe()
for await reaction in reactionSubscription {
print("Received a reaction of type \(reaction.type), and metadata \(reaction.metadata)")
}
@@ -139,7 +139,7 @@ await room.reactions.send({type: "heart", metadata: {"effect": "fireworks"}});
```
```[react]
-import { useRoomReactions } from '@ably/chat';
+import { useRoomReactions } from '@ably/chat/react';
const MyComponent = () => {
const { send } = useRoomReactions();
diff --git a/content/chat/rooms/typing.textile b/content/chat/rooms/typing.textile
index 59dc51a51b..c4cef60554 100644
--- a/content/chat/rooms/typing.textile
+++ b/content/chat/rooms/typing.textile
@@ -30,7 +30,7 @@ const {unsubscribe} = room.typing.subscribe((event) => {
```
```[react]
-import { useTyping } from '@ably/chat';
+import { useTyping } from '@ably/chat/react';
const MyComponent = () => {
const {currentlyTyping, error } = useTyping({
@@ -49,7 +49,7 @@ const MyComponent = () => {
```
```[swift]
-let typingSubscription = try await room.typing.subscribe()
+let typingSubscription = room.typing.subscribe()
for await typing in typingSubscription {
typingInfo = typing.currentlyTyping.isEmpty ? "" :
"Typing: \(typing.currentlyTyping.joined(separator: ", "))..."
@@ -137,7 +137,7 @@ await room.typing.start();
```
```[react]
-import { useTyping } from '@ably/chat';
+import { useTyping } from '@ably/chat/react';
const MyComponent = () => {
const { start, currentlyTyping, error } = useTyping();
@@ -174,7 +174,7 @@ await room.typing.stop();
```
```[react]
-import { useTyping } from '@ably/chat';
+import { useTyping } from '@ably/chat/react';
const MyComponent = () => {
const { stop, error } = useTyping();
diff --git a/content/chat/setup.textile b/content/chat/setup.textile
index a41901ab65..f52fea8295 100644
--- a/content/chat/setup.textile
+++ b/content/chat/setup.textile
@@ -62,7 +62,8 @@ blang[javascript,react].
```[react]
import * as Ably from 'ably';
- import { ChatClient, ChatClientProvider} from '@ably/chat';
+ import { ChatClient } from '@ably/chat';
+ import { ChatClientProvider } from '@ably/chat/react';
```
blang[swift,kotlin].
@@ -205,7 +206,8 @@ const chatClient = new ChatClient(ably, {logHandler: logWriteFunc, logLevel: 'de
```[react]
import * as Ably from 'ably';
-import { ChatClientProvider, LogLevel } from '@ably/chat';
+import { LogLevel } from '@ably/chat';
+import { ChatClientProvider } from '@ably/chat/react';
const ably = new Ably.Realtime({ key: '{{API_KEY}}', clientId: ''});
const chatClient = new ChatClient(ably, {logHandler: logWriteFunc, logLevel: 'debug' });
diff --git a/content/errors/codes.textile b/content/errors/codes.textile
new file mode 100644
index 0000000000..569fb83e3b
--- /dev/null
+++ b/content/errors/codes.textile
@@ -0,0 +1,721 @@
+---
+title: Error codes
+meta_description: "Understand Ably error codes and their causes, to resolve them efficiently."
+languages:
+ - javascript
+---
+
+Use the error codes provided, which include descriptions, possible causes, and solutions, to troubleshoot issues effectively.
+
+
+
+h2(#500). 500: Internal error
+
+This error occurs when there is an issue within Ably's system.
+
+h2(#5000). 50000: Internal error
+
+This error occurs when there is an issue within Ably's system.
+
+h2(#10000). 10000: No error
+
+This error code indicates that the attempted action was successful and no issues occurred; there will be additional info in the @message@ property about the successful action.
+
+h2(#20000). 20000: General error code
+
+This error code is a generic response used when the Ably "protocol":/docs/protocols requires an @errorInfo@ object, even though no actual issue has occurred. The message property may contain additional details about the successful action.
+
+h2(#40000). 40000: Bad request
+
+This error occurs when Ably cannot understand the request due to a malformed structure or other issues with the request format.
+
+An example of this error is when a message is published while the connection is in a "suspended state":/docs/connect/states#connection-states.
+
+h2(#40001). 40001: Invalid request body
+
+This error occurs when a request contains invalid content, such as incorrect "data types":/docs/api/realtime-sdk/types#data-types or missing required objects. It is most likely caused by missing required fields or incorrect data formats when sending a request to Ably.
+
+An example of this error is a missing field, such as a @keyName@ in a "token request":/docs/api/token-request-spec#tokenrequest-format.
+
+h2(#40003). 40003: Invalid parameter value
+
+This error occurs when an invalid value is sent in the request. It may be caused by either user input or the SDK sending incorrect parameters.
+
+Additional examples of the @40003@ error code output include:
+
+* *40003: Excessive value for TTL*
+This error occurs when the "TTL":/docs/api/rest-sdk/authentication#token-request set for a token parameter exceeds the maximum allowed value of 24 hours.
+* *40003: Excessive value for TTL (revocation is enabled on this key)*
+This error occurs when "revocation":/docs/auth/revocation#revocable-tokens is enabled, revocable tokens are limited to a TTL of 1 hour instead of 24 hours.
+
+h2(#40005). 40005: Invalid credentials
+
+This error occurs when the "API key":/auth#api-keys or "token":/docs/auth/token used to initialize the library is invalid.
+*Resolution:* The following steps can help resolve this issue:
+
+* API key initialization:
+** Ensure you are the account's *admin* or *owner*.
+** In your "Ably dashboard":https://ably.com/accounts/any to the *API keys* tab.
+** Use one of these API keys instead of your current one or create a "new API key":/docs/account/app/api#create with the necessary permissions and privileges and use that to instance the SDK.
+* Token initialization:
+** If you are using "token authentication":/docs/auth/token, ensure you are correctly requesting a valid token before using it with Ably.
+
+h2(#40006). 40006: Invalid connectionId
+
+This error occurs when a message is published with an invalid or mismatched "@connectionId@":/docs/messages#properties.
+
+Examples of this error are when:
+* A client attempts to publish a message using a malformed @connectionId@.
+* A client publishes a realtime message with a @connectionId@ that does not match the currently active connection.
+* A connection is identified, but the resolved @clientId@ is missing or incorrect (due to authentication issues or an explicitly provided, but incorrect, @clientId@).
+
+h2(#40009). 40009: Maximum message length exceeded
+
+This error occurs when the message being published exceeds the maximum size allowed set by the "limits":/docs/limits#app-limits for your current "package":/docs/pricing#packages.
+
+h2(#40010). 40010: Invalid channel name
+
+This error occurs when a "channel name":/docs/channels#use does not meet the required format or contains invalid characters.
+
+h2(#40011). 40011: Stale ring state
+
+This error occurs when using the channel "enumeration":/docs/api/rest-api#enumeration-rest API and the state of the cluster changes between successive calls, causing a pagination sequence to become invalid. Note: This issue does not occur if the enumeration request is fully satisfied within the first response page.
+
+h2(#40012). 40012: Invalid @clientId@
+
+This error occurs when a @clientId@ is invalid due to disallowed characters or a mismatch between the @clientId@ specified in the request and the one assigned in the authentication "token":/docs/auth/token.
+
+Examples of this error are when:
+
+* The client attempts to use @*@ as a @clientId@, which is not allowed because @*@ is used as a "wildcard":/docs/auth/capabilities#wildcards in capability expressions.
+* A client tries to assume an ID of @foo@ for an operation, but the token they are using specifies an ID of @bar@.
+
+h2(#40013). 40013: Invalid message data or encoding
+
+This error occurs when the SDK is unable to encode the "message payload":/docs/messages due to an unsupported data type.
+
+An example of this error is when a client attempts to publish a payload that includes something that isn't serializable by an Ably SDK.
+
+*Resolution:* Try marshalling the payload as JSON or MsgPack before publishing.
+
+h2(#40015). 40015: Invalid deviceId
+
+This error occurs when the ID property in a "DeviceDetails":/docs/api/rest-sdk/push-admin#device-details object, sent during a push notification registration request, is invalid. The ID must be a string.
+
+h2(#40016). 40016: Invalid message name
+
+This error occurs when the @name@ "property":/docs/messages#properties of a message is not a string. The @name@ field is used to categorize messages within channels, and it must always be a string.
+
+h2(#40017). 40017: Unsupported protocol version or Invalid API version specified
+
+This error occurs when a request does not specify a protocol version or provides an invalid version in the request parameters of a "Server-Sent Events (SSE)":/docs/protocols/sse request. This error also occurs when a WebSocket connection to Ably specifies an invalid API version in the request parameters.
+
+*Resolution*: Ensure that the request specifies a valid protocol version. Note: You will not see this error when using an Ably SDK, as SDKs automatically use valid versions.
+
+h2(#40020). 40020: Partial failure in batch operation requests
+
+This error occurs when a "batch":/docs/api/rest-api#batch request partially fails, meaning that some operations succeed while others fail.
+
+This error can occur for the following endpoints:
+
+* Revoking tokens: "@POST/revokeTokens@":/docs/api/rest-api#revoke-tokens
+* Publishing messages: "@POST/messages@":/docs/api/rest-api#batch-publish
+* Retrieving presence data: "@GET/presence@":/docs/api/rest-api#batch-presence
+
+The following example is a batch operation response that includes partial failures. The @error@ field indicates that some requests within the batch were unsuccessful, while the @batchResponse@ contains individual results for each operation:
+
+```[json]
+{
+ "error": "new ErrorInfo('Batched response includes errors', 40020, 400)",
+ "batchResponse": "results"
+}
+```
+
+h2(#40022). 40022: Invalid resource
+
+This error occurs when the requested resource is invalid or already exists. It is typically returned by requests to the "Control API":/docs/account/control-api.
+
+Examples of this error include attempting to create a resource that already exists or providing an invalid resource name.
+
+*Resolution*: The following steps can help resolve this issue:
+* Ensure that an app with the requested name does not already exist in your account. If it does, use a different name.
+* Verify that the "capabilities":/docs/auth/capabilities#capability-operations listed in the request are valid when creating an API key.
+
+h2(#40030). 40030: Invalid publish request (unspecified)
+
+This error occurs when a "publish request":/docs/pub-sub/basic#publish is malformed.
+
+h2(#40032). 40032: Invalid publish request (impermissible extras field)
+
+This error occurs when a published message contains arbitrary fields in the "@extras@":/docs/api/realtime-sdk/messages#extras payload that are not allowed by the Ably service. The @extras@ field is reserved for specific objects related to Ably.
+
+*Resolution*: Place any additional data inside the @headers@ field within @extras@. The @headers@ field must be a flat object (@map@ or equivalent) with no nested structures. If using unenveloped Reactor rules, ensure that header names are in @ASCII@, as they will be converted into HTTP, AMQP, or other protocol headers.
+
+The following example is a valid @extras@ object:
+
+```[javascript]
+const extras = {
+ headers: {
+ header1: 'value1',
+ header2: 'value2'
+ }
+};
+```
+
+h2(#40100). 40100: Unauthorized
+
+This error occurs when an action cannot be performed due to a lack of "authentication":/docs/auth. There will be additional info in the @message@ property about the reason the action could not be performed.
+
+h2(#40101). 40101: Invalid credentials
+
+This error occurs when "authentication":/auth credentials are invalid. The specific error message may provide further details about the cause.
+
+An example of this error is when a signed token request is invalid due to a mismatched cryptographic signature (MAC does not match) or when the @clientId@ specified in the Ably SDK does not match the @clientId@ in the access token.
+
+*Resolution*: Review your authentication setup and token request implementation. Ensure the following:
+
+* The cryptographic signature (MAC) is correct - If a signed token request is invalid, Ably will reject it.
+* The API key used to generate the token request is accurate - Double-check for missing or extra characters.
+* The token request JSON is correctly formatted and unaltered, including:
+** @keyName@: Must match the API key used in the request.
+** @clientId@: If provided, it must match the one in the client constructor.
+** @ttl@: Must be an integer in milliseconds.
+** @timestamp@: Must be an integer in milliseconds.
+** @capability@: Must be stringified JSON, not raw JSON.
+** @nonce@: Must be a randomly generated string value.
+** @mac@: Must be correctly generated using the secret key.
+* The @clientId@ in the SDK matches the one in the token. If a @clientId@ is provided in the token, it must align with the @clientId@ set in the Ably client options.
+* A @clientId@ cannot be changed on an existing connection. Ensure consistency in authentication.
+* For JWT authentication, confirm that @clientId@ is correctly set in @x-ably-clientId@.
+
+h2(#40102). 40102: Incompatible credentials
+
+This error occurs when "authentication":/auth credentials do not match the existing connection, causing the connection to enter the "failed state":/connect/states.
+
+Examples of this error include:
+
+* Attempting to authenticate with an API key from a different app than the one used for the original connection
+* Resuming a connection using credentials tied to a different app
+* Calling @auth.authorize@ with an API key that differs from the one originally used to establish the connection
+
+h2(#40103). 40103: Invalid use of Basic auth over non-TLS transport
+
+This error occurs when an API key is used over a non-TLS (unencrypted) connection, which is not permitted due to security risks. API keys are long-lived credentials, making them more vulnerable if exposed. Unlike short-lived tokens, API keys remain valid indefinitely, meaning a compromised key presents a significant security risk.
+
+Non-TLS transports can be inspected by network devices routing traffic between the client and Ably. Ably does not allow API key authentication over non-TLS connections. However, Ably supports "basic authentication":/docs/auth/basic over TLS and "token authentication":/docs/auth/token over non-TLS connections.
+
+*Resolution*: Select the appropriate "authentication mechanism":/docs/auth#selecting-auth for your use case.
+
+h2(#40104). 40104: Timestamp not current
+
+This error occurs when a signed token request sent to Ably is too old. Ably enforces timestamp validation to ensure "@TokenRequest@":/docs/auth/token#token-request remain valid for a limited time, reducing the risk of interception and misuse.
+
+*Resolutions*: The following steps can help resolve this issue:
+* Ensure that the authentication servers clock is accurate when generating signed token requests.
+* If time synchronization is unreliable, set the @queryTime@ "ClientOptions":/docs/api/rest-sdk/types#client-options object to true when initializing the Ably client. This ensures Ably's server time is used.
+* Do not cache signed token requests on the authentication server or client. Each token request must be freshly generated and used immediately.
+* If using Next.js 13, prevent caching issues by setting revalidate to 0 or changing the request method from @GET@ to @POST@ using the @authMethod@ "ClientOption":/docs/api/rest-sdk/types#client-options.
+
+h2(#40105). 40105: Nonce value replayed
+
+This error occurs when a signed "token request":/docs/api/realtime-sdk/types#token-request has been used more than once. Ably enforces nonce (number used once) checks to ensure that each signed token request is unique, as a security measure.
+
+Examples of this error include:
+* A client sends a signed token request to Ably but does not receive the response due to network issues. If the client automatically retries the HTTP request in a fallback data center, Ably detects the duplicate nonce and rejects it.
+* An authentication server caches and reuses signed token requests instead of generating a unique one for each request.
+* A misconfigured "@authCallback@":/docs/auth/token#auth-callback function reuses the same token request on every invocation instead of generating a fresh one.
+* The @tokenRequest@ is not renewed. A new token request should be requested from your server at this point:
+
+```[javascript]
+var tokenRequest = "";
+var ably = new Ably.Realtime({
+ authCallback: function(tokenParams, callback) {
+ // This is a mistake. The tokenRequest is not renewed.
+ A new token request should be requested from your server at this point */
+ callback(null, tokenRequest);
+ }
+});
+```
+
+*Resolution*: The following steps can help resolve this issue:
+* Ensure that each token request is unique and never cached by the authentication server or client.
+* Always generate a new token request each time authCallback is invoked.
+* If retrieving a token request over HTTP, prevent caching by using the Cache-Control header (no-cache, no-store, must-revalidate) or by adding a cache-busting query string parameter, where the number is regenerated for every request, for example, @?rnd=73849275@.
+* If network issues are suspected, check logs and debug the token request process to confirm proper request handling.
+
+h2(#40106). 40106: Unable to obtain credentials from given parameters
+
+This error occurs when invalid "authentication":/docs/auth options (key or token) are provided to the Ably SDK, preventing successful authentication.
+
+An example of this error is when no "API key":/docs/auth#api-keys or "token":/docs/auth/token is supplied, or when an authentication request is made using a token to request another token, instead of an API key.
+
+h2(#40111). 40111: Connection limits exceeded
+
+This error occurs when the peak "connection limit":/docs/pricing/limits#connection for your account has been exceeded, preventing new connections from being established until existing ones disconnect.
+
+h2(#40112). 40112: Account blocked (message limits exceeded)
+
+This error occurs when your account has exceeded the allocated "message limit":/docs/pricing/limits#message based on your "package":/docs/pricing#packages. Once this limit is reached, further message publishing is blocked.
+
+h2(#40114). 40114: Account-wide peak channel limit exceeded
+
+This error occurs when your account has exceeded the concurrent "channel limit":/docs/pricing/limits#channel, preventing additional channels from being created.
+
+Examples of this error are when the application attempts to open more channels than the account allows, causing new channel creation to be blocked. Also, during development, an implementation error or bug causes unintended channel creation, leading to the limit being reached.
+
+*Resolution*: Detach unused channels to free up space for new ones.
+
+h2(#40115). 40115: Account restricted (request limit exceeded)
+
+This error occurs when your account has exceeded the allocated "limits":/docs/pricing/limits based on your "package":/docs/pricing#packages.
+
+h2(#40125). 40125: Maximum number of rules per application exceeded
+
+This error occurs when the application has reached the maximum number of "integration rules":/docs/integrations set by the "limits":/docs/limits#app-limits for your current package.
+
+h2(#40127). 40127: Maximum number of keys per application exceeded
+
+This error occurs when the application has reached the maximum number of "API keys":/docs/account/app/api set by the "limits":/docs/limits#app-limits for your current package.
+
+h2(#40131). 40131: Key revoked
+
+This error occurs when the "Ably API key":/docs/auth#api-keys used to initialize the SDK is no longer valid because it has been "revoked":/docs/auth/revocation by an admin of the application.
+
+*Resolution*: The following steps can help resolve this issue:
+* If you are an admin, go to the "API keys tab":/docs/account/app/api in the Ably dashboard to check for valid keys. Use an existing valid key or create a new one with the necessary permissions.
+* If you are not an admin, request a new API key from an administrator or obtain a token request generated with a valid key.
+
+h2(#40133). 40133: Wrong key; cannot revoke tokens with a different key than the one that issued them
+
+This error occurs when the "Ably API key":/docs/auth#api-keys used to authorize a token "revocation":/docs/auth/revocation request does not match the key that originally issued the token.
+
+*Resolution*: The following steps can help resolve this issue:
+* Ensure that the public API key ID used in the request matches the key that originally issued the token.
+* Verify that the @keyname@ in the request path corresponds with the API key used for authentication.
+
+h2(#40141). 40141: Token revoked
+
+This error occurs when attempting to authenticate with a "token":/docs/auth/token that has been "revoked":/docs/auth/revocation and is no longer valid.
+
+*Resolution*: Ensure that you are using a valid, "non-revoked":/auth/token-revocation. token for authentication. If needed, generate a new token and use it for authorization.
+
+h2(#40142). 40142: Token expired
+
+This error occurs when the authentication "token":/docs/auth/token#refresh has expired and is no longer valid for use.
+
+An example of this error is when a client attempts to authenticate with a token that has exceeded its time-to-live (TTL).
+
+*Resolution*: The following steps can help resolve this issue:
+* Use "@authUrl@":/docs/auth/token#auth-url or "@authCallback@":/docs/auth/token#auth-callback in your client configuration to enable automatic token renewal.
+* If @authUrl@ or @authCallback@ is correctly configured, the client SDK will automatically renew the token when needed, so you may see this error temporarily before renewal occurs.
+
+h2(#40143). 40143: Token unrecognized
+
+This error occurs when the provided token is not recognized as a valid Ably "token":/docs/auth/token-authentication, Ably "JWT":/docs/auth/jwt, or a JWT containing a valid Ably token.
+
+An example of this error is when a JWT is incorrectly formatted or when an Ably token does not follow the expected structure, for example: @.@. Any token that does not adhere to the correct format will not be recognized.
+
+*Resolution*: Validate the token format before using it for authentication. If creating a "JWT":/docs/auth/jwt, use a standard JWT library to ensure correct generation. If using an Ably token, verify that it matches the expected format as returned from the "@requestToken@":/docs/api/token-request-spec#examples endpoint.
+
+h2(#40144). 40144: Unexpected error decoding JWT; decode exception
+
+This error occurs when the "JWT":/auth/jwt. provided for authentication cannot be validated by Ably due to incorrect formatting, missing claims, or unsupported configurations.
+
+This error occurs when the "JWT":/auth/jwt provided for authentication cannot be validated by Ably. The specific reason for the failure will be available in the reason property of a "@ConnectionStateChange@":/docs/api/realtime-sdk/types#connection-state-change object or in an error response from a REST request.
+
+Examples of this error include:
+* Missing or invalid payload claims, such as iat (issued at) or exp (expiration).
+* Using a deprecated or unsupported signing algorithm.
+* Providing an empty string for @x-ably-capability@.
+* Using an empty string or non-string value for "@x-ably-revocation-key@":/docs/auth/revocation#revocation-key.
+* Missing kid (Key ID) when using "JWT authentication with an API key":/docs/api/realtime-sdk/authentication#ably-jwt.
+
+h2(#40160). 40160: Action not permitted
+
+This error occurs when the client does not have permission to perform the requested action.
+
+An example of this error is when a token request includes an empty "capability object":/docs/auth/capabilities#capabilities-token, for example: @({})@, meaning the client has no assigned permissions, or when the requested "resource":/docs/auth/capabilities#wildcards does not match the API key's allowed capabilities.
+
+*Resolution*: Ensure the "API key":/docs/auth#api-keys supports the required "capabilities":/docs/auth/capabilities#capabilities-key for the requested action.
+
+h2(#40161). 40161: Access denied to channel: namespace requires identified clients
+
+This error occurs when a "non-identified client":/auth/identified-clients attempts to access a channel that requires an identified client. Each application's channel namespaces configuration can be found in its application settings. By default, the identified namespace requires a client to be identified.
+
+h2(#40170). 40170: Error from client token callback
+
+This error occurs when an authentication attempt using "@authUrl@":/docs/auth/token#auth-url or "@authCallback@":/docs/auth/token#auth-callback fails due to a timeout, network issue, invalid token format, or another authentication error condition.
+
+Examples of this error include when a @TokenRequest@ callback times out after the default 10-second limit. Also, when he @authUrl@ response is missing a Content-Type header has an unsupported Content-Type.
+
+*Resolution*: The following steps can help resolve this issue:
+* Check for network latency between the client and the authentication server.
+* Ensure the authentication server returns a valid Content-Type header and one of the supported content types:
+** @application/json@
+** @text/plain@
+** @application/jwt@
+* Additional error details can be found in the @error.message@ field.
+
+h2(#40171). 40171: No means provided to renew auth token
+
+This error occurs when no "@authUrl@":/docs/auth/token#auth-url or "@authCallback@":/docs/auth/token#auth-callback is provided in "@clientOptions@":/docs/getting-started/setup#options when initializing the Ably REST or Realtime SDK.
+
+Tokens have a set expiration time, and once expired, they are no longer valid for communication with Ably. If @authUrl@ or @authCallback@ is configured, the SDK will automatically request a new token before expiration, ensuring uninterrupted operation.
+
+*Resolution*: Ensure that @authUrl@ or @authCallback@ is set in your client configuration. This allows the SDK to automatically request a new token before the current one expires.
+
+h2(#40300). 40300: Forbidden
+
+This error occurs when a requested action is not permitted. It serves as the default response for forbidden actions and can be triggered by various issues.
+
+The following are example for this error:
+* Mismatched SDK versions, occurring if an application uses multiple versions of the Ably SDK, leading to inconsistencies in connections.
+* A disabled account, indicating that the app belongs to an account that has been manually disabled by Ably.
+* An incorrect URL for a private cluster.
+
+h2(#40311). 40311: Operation requires TLS connection
+
+By default, Ably apps require "TLS":/docs/account/app/settings#channel-rule-configuration for connections. This error occurs when a realtime SDK attempts to connect with TLS disabled (tls: false) while using token authentication.
+
+*Resolution*: The following steps can help resolve this issue:
+* Ensure that the @tls@ "@ClientOption@":/docs/api/realtime-sdk?#client-options is set to true when connecting.
+* If using "API key":/docs/account/app/api#create authentication, note that this scenario will result in a "@40103@":#40103 error instead.
+
+h2(#40330). 40330: Unable to activate account due to placement constraint (unspecified)
+
+This error occurs when an app belonging to a dedicated (private) "cluster":/docs/platform-customization#dedicated-and-isolated-clusters is accessed using an incorrect endpoint. "Enterprise":/docs/pricing/enterprise customers with private clusters receive "custom":/docs/platform-customization#setting-up-a-custom-environment environment endpoints specific to their deployment.
+
+An example of this error is when an app configured for a private cluster tries to connect via the default global endpoint.
+
+*Resolution*: Check your custom environment settings for all connecting clients to ensure they point to the correct private cluster endpoints.
+
+h2(#40331). 40331: Unable to activate account due to placement constraint (incompatible environment)
+
+This error occurs when an app that belongs to a dedicated (private) "cluster":/docs/platform-customization#dedicated-and-isolated-clusters is accessed using an incorrect URL. This often happens when the correct environment is not specified when initializing a client library. "Enterprise":/docs/pricing/enterprise customers with private clusters receive "custom":/docs/platform-customization#setting-up-a-custom-environment environment endpoints specific to their deployment.
+
+If a request arrives at an unexpected dedicated cluster or incorrect region, the account in that scope may be disabled, triggering this error.
+
+An example of this error is when a client intended for a dedicated environment, such as acme, connects to the default global endpoint (@realtime.ably.io@) instead of the correct dedicated cluster endpoint (@acme-realtime.ably.io@). If the expected environment is not configured, requests may be rejected.
+
+*Resolution*: Verify that all connecting clients are configured with the correct custom environment settings to ensure they are pointing to the intended dedicated cluster.
+
+h2(#40332). 40332: Unable to activate account due to placement constraint (incompatible site)
+
+This error occurs when attempting to connect to an app for an account restricted to a specific region.
+
+An example of this error is when a client attempts to connect to an Ably app restricted to the EU region but does not specify the EU environment in the SDK configuration.
+
+*Resolution*: The following steps can help resolve this issue:
+* Ensure you are configured with the correct environment for your "region-restricted":/https://faqs.ably.com/do-you-have-an-option-to-keep-my-data-in-europe-or-the-united-states account.
+* If your account has a regional constraint, you should have been provided with a "custom":/docs/platform-customization#setting-up-a-custom-environment environment to pass to the "@ClientOptions@":/getting-started/setup#options.
+* Verify that your connection settings match the region assigned to your account.
+
+h2(#40400). 40400: Not found
+
+This error occurs when the requested resource does not exist. This can apply to an Ably app, client, device, connection, API key, or token. The accompanying error message provides more details about the missing resource.
+
+Examples of this error include:
+* Attempting to authenticate using an Ably API key or token, but the specified appId cannot be found. This may happen if:
+** The appId is incorrect.
+** The appId does not exist in the current environment (especially in a custom deployment).
+* Attempting to authenticate with an incorrect API key ID, which may be due to:
+* An invalid API key ID.
+* The key not being found in the specified environment.
+* Incorrect formatting, particularly case sensitivity issues.
+
+h2(#42910). 42910: Rate limit exceeded; request rejected
+
+This error occurs when a "limit":/docs/pricing/limits on your account has been reached, preventing further requests until the limit resets. The duration of the limit depends on the type of rate limit:
+
+* Instantaneous rate limits typically last up to six seconds before allowing message publishing to resume.
+* Other limits may apply on an hourly or monthly basis.
+
+An example of this error is when a client exceeds the maximum allowed message rate on a channel.
+
+In the following example, the metric @channel.maxRate@ represents the maximum rate of messages allowed to be published on a channel per second. The permitted rate is 5000 messages per second, but the client is attempting to publish 5015 messages per second, triggering the limit.
+
+```[text]
+Rate limit exceeded; request rejected (nonfatal);
+metric = channel.maxRate;
+interval = 2018-01-05:10:10:3;
+permitted rate = 5000;
+current rate = 5015;
+scope = channel:[YOUR APP ID]:[YOUR CHANNEL]
+(status: 429, code: 42910) (code: 42910, http status: 429)
+```
+
+*Resolution*: Wait for the rate limit period to reset before retrying. Optimize your message publishing to stay within allowed limits. Upgrade your package if higher throughput is required. Review your account limits to determine which restriction has been hit.
+
+h2(#42911). 42911: Maximum account-wide instantaneous messages rate exceeded
+
+This error occurs when the number of "messages":/docs/pricing/limits#message sent per second exceeds the limit imposed on an account. To maintain service reliability for all users, Ably enforces usage limits at different levels, including monthly, hourly, and per-second thresholds.
+
+h2(#42912). 42912: Channel iteration call already in progress
+
+This error occurs when multiple concurrent "metadata REST requests":/docs/metadata-stats/metadata#rest are made to retrieve a list of active channels. The API is rate-limited, allowing only one in-flight call at a time. Additional concurrent requests will be rejected with this error.
+
+*Resolution*: The following steps can help resolve this issue:
+* Ensure that only one request is in progress at any given time.
+* Implement request queuing or backoff strategies to avoid sending concurrent calls.
+* If you require enhanced channel "enumeration capabilities":/docs/api/rest-api#enumeration-rest, visit this page to request access to the preview API.
+
+h2(#42922). 42922: Rate limit exceeded; too many requests
+
+This error occurs when a client has made too many requests within a 5-minute time window, causing the request to be rejected. The "limit":/docs/pricing/limits#hitting remains in effect for up to 30 seconds but may persist longer if request volume remains above the threshold from the same IP address.
+
+This rate limit is in place to protect the Ably platform and is not expected during normal use.
+
+h2(#50001). 50001: Internal channel error
+
+This error occurs when there is an internal issue on an Ably channel.
+
+h2(#50002). 50002: Internal connection error
+
+This error occurs when there is an internal connection issue.
+
+h2(#50003). 50003: Timeout error
+
+This error occurs when a "connection":/docs/connect request to Ably times out.
+
+An example of this error is when a client attempts to publish a message to a channel, but the operation fails to complete within the allowed time.
+
+Examples of this error include:
+* Poor network conditions affecting connectivity.
+* Improper usage of the Ably SDKs leading to unexpected delays.
+* Temporary Ably server issues impacting response times.
+
+h2(#50305). 50305: Ably's routing layer was unable to service this request
+
+This error occurs as a result of a request not being handled due to an internal routing error within the Ably service.
+
+h2(#61002). 61002: Activation failed: Present clientId is not compatible with existing device registration
+
+This error occurs when you previously "activated":/docs/push/configure/device#activate-devices a device for push notifications with a specific @clientId@, but then changed the @clientId@ used for authentication. The mismatch causes the error because the push notification setup tracks the @clientId@ and other details to prevent accidental changes between app launches. The @clientId@ is linked to registrations, such as subscribing by @clientId@.
+
+*Resolution:* If you need to change your client's @clientId@, deactivate and reactivate the device. This process triggers an internal @device.reset()@ call, which clears and resets the old device details.
+
+h2(#70001). 70001: Reactor operation failed (POST operation failed)
+
+This error occurs when a Reactor rule fails due to an issue with the configured endpoint. Ably attempts to send a POST request, but the response is unexpected or unsuccessful.
+
+h2(#70002). 70002: Reactor operation failed (POST operation returned unexpected code)
+
+This error occurs when Ably sends a webhook to your server, but the server refuses or returns an unexpected response code. While Ably will retry the request multiple times, repeated failures indicate an issue on the server side.
+
+h2(#72000). 72000: Ingress operation failed
+
+This error occurs due to an internal error with the ingress worker. It is an unexpected issue that happens when the worker attempts to execute a rule but encounters an error during the process.
+
+h2(#72002). 72002: Ingress table is unhealthy
+
+This error occurs when the rule worker is unable to access or retrieve data from either the "outbox":/docs/livesync/postgres#outbox-table or "nodes":/docs/livesync/postgres#nodes-table table in the database as expected.
+
+An example of this error is misconfigurations in the database setup or inconsistencies between the provided configuration and the actual database schema or table names.
+
+*Resolution*: The following steps can help resolve this issue:
+* Verify the configuration of the outbox and nodes tables in the database to ensure they are correctly set up and match the rule definition.
+* Check for database connectivity issues and confirm that the database is accessible.
+* Ensure that the schema and table names align with the expected configuration.
+
+h2(#72004). 72004: Ingress cannot identify channel, no _ablyChannel field
+
+This error occurs when a "MongoDB Change Stream":https://www.mongodb.com/docs/manual/changeStreams/#change-streams event does not contain the required @_ablyChannel@ field after being processed through the Change Stream pipeline. This field is essential for identifying the channel where the change event message will be published.
+
+*Resolution*: The following steps can help resolve this issue:
+* Ensure that the @_ablyChannel@ field is present at the root level of the change event.
+* Avoid nesting it inside other sections, such as @$fullDocument._ablyChannel@.
+* The field must be part of the main structure of the change event to allow proper identification.
+
+h2(#72005). 72005: Ingress invalid pipeline
+
+This error occurs when the "MongoDB Change Stream":https://www.mongodb.com/docs/manual/changeStreams/#change-streams fails to start due to an invalid pipeline. An example of this error is when the pipeline syntax does not conform to MongoDB's requirements or contains unrecognized operators.
+
+*Resolution*: The following steps can help resolve this issue:
+* Review the pipeline syntax for errors and ensure all operators are valid.
+* Adjust the pipeline to match MongoDB's accepted structure and syntax guidelines.
+
+h2(#72006). 72006: Unable to resume from change stream
+
+This error occurs when the "MongoDB Change Stream":https://www.mongodb.com/docs/manual/changeStreams/#change-streams cannot be resumed because the resume token document stored in MongoDB is not in the correct format.
+
+*Resolution*: The following steps can help resolve this issue:
+* Verify the format of the stored resume token document in MongoDB.
+* Ensure the token meets the expected structure and format required for MongoDB Change Stream resumption.
+* Refer to the MongoDB documentation for guidelines on properly storing and using resume tokens.
+
+h2(#72007). 72007: Unable to store change stream resume token
+
+This error occurs when the "MongoDB Change Stream":https://www.mongodb.com/docs/manual/changeStreams/#change-streams resume token cannot be stored in the @ably@ collection of the database.
+
+*Resolution*: The following steps can help resolve this issue:
+* Verify that the integration rule and MongoDB connection string are correctly configured.
+* Ensure the database user has the necessary read and write permissions for the @ably@ collection.
+* Adjust permissions if needed to allow the token to be successfully stored.
+
+h2(#80000). 80000: Connection failed
+
+This error occurs when the SDK is having trouble "connecting":/docs/connect/states#connection-states to Ably, likely due to client-side network connectivity issues. Note: The SDK will automatically retry the connection after 30 seconds.
+
+h2(#80002). 80002: Connection suspended
+
+This error occurs when the SDK is having trouble "connecting":/docs/connect/states#connection-states to Ably. This is likely due to client-side network connectivity issues, and has failed to establish a connection within 2 minutes. Note: The SDK will automatically retry the connection after 15 seconds.
+
+h2(#80003). 80003: Generic disconnection error
+
+This error occurs when the SDK is having trouble "connecting":/docs/connect/states#connection-states to Ably, likely due to client-side network connectivity issues. Note: The SDK will automatically retry the connection after 15 seconds.
+
+h2(#80008). 80008: Unable to recover connection (connection expired)
+
+This error occurs when a "connection":/docs/connect/states#connection-states resume attempt fails because the original connection has expired. This typically happens when the "@resume@":/docs/connect/states#resume attempt occurs after the two-minute window has passed.
+
+If this error occurs, the client establishes a new connection instead of resuming the old one. Any messages sent during the gap will be missed, unless channel persistence is enabled.
+
+*Resolution*: Ensure that resume attempts occur within the two-minute window to successfully recover a connection. If message loss is a concern, use "history":/docs/api/realtime-sdk/history to retrieve missed messages.
+
+h2(#80014). 80014: Connection timed out
+
+This error occurs when a realtime "connection":/docs/connect/states#connection-states times out after waiting for the default 10-second @realtimeRequestTimeout@ in certain Ably SDKs.
+
+The request will be automatically retried by the SDK.
+
+*Resolution*: If the client never connects to the "primary or fallback endpoints":https://faqs.ably.com/routing-around-network-and-dns-issues, check any firewall rules that may be blocking access to Ably's "endpoints":https://faqs.ably.com/if-i-need-to-whitelist-ablys-servers-from-a-firewall-which-ports-ips-and/or-domains-should-i-add.
+
+h2(#80016). 80016: Operation on superseded connection
+
+This error occurs when a browser "connection":/docs/connect/states#connection-states upgrades from an HTTP (Comet) transport to WebSockets. It is logged by the client library when operations are performed on the older transport.
+
+*Resolution*: the following steps can help resolve this issue:
+* If this error only appears in logs, it is harmless and does not affect your application. No action is needed.
+* If you receive this error as a response to a request, contact Ably support with relevant logs and details, and they will investigate the issue.
+
+h2(#80017). 80017: Connection closed
+
+This error occurs when a "connection":/docs/connect/states#connection-states has been closed and an operation is attempted on it, such as calling a channel or presence method, for example, @channel.presence.update@ while the connection is still in a closed state.
+
+*Resolution*: The following steps can help resolve this issue:
+* Check the client's connection state before performing operations to ensure the connection is active.
+* If the connection is closed, reconnect before making requests to avoid this error.
+
+h2(#80018). 80018: Invalid connection ID (invalid format)
+
+This error occurs when an invalid @connectionId@ is supplied.
+
+*Resolution*: If you are seeing resumes failing in ably-js, this was a known bug in ably-js versions 1.2.30 through 1.2.33. Upgrading to the latest version of ably-js should resolve the issue.
+
+h2(#80019). 80019: Auth server rejecting request
+
+This error occurs when the client library fails to obtain a token using the client-supplied "@authUrl@":/docs/auth/token#auth-url or "@authCallback@":/docs/auth/token#auth-callback. It is raised when the request to the authentication server fails due to an error or exception in the callback.
+
+h2(#80020). 80020: Continuity loss due to maximum subscribe message rate exceeded
+
+This error occurs when a client exceeds the "outbound":/docs/pricing/limits#connection subscribe message rate on a realtime connection.
+
+*Resolution*: The subscriber will receive an @UPDATE@ "channel state change":/docs/api/realtime-sdk/channels#channel-state-change event, indicating that continuity is lost. Use the "@resume@":/docs/connect/states#resume flag to determine whether to recover missing messages or handle the failure accordingly.
+
+h2(#80021). 80021: Max new connections rate exceeded
+
+This error occurs when the maximum allowed rate of new connections for an account has been "exceeded":/docs/pricing/limits#hitting.
+
+h2(#80022). 80022: Unable to find connection
+
+This error can occur when using the Comet transport, where a @send@ or @recv@ request is sent to the system but reaches a different frontend instance than the one hosting the connection. This can happen due to a disruption on a frontend instance.
+
+*Resolution*: This is a non-fatal error, and no action is required. The transport will automatically drop and be re-initiated without any need for manual intervention.
+
+h2(#80023). 80023: Unable to resume connection from a different site
+
+This error occurs when a disconnected client attempts to resume a "connection":/docs/connect from a different site than the original connection. This typically happens when a client tries to "@resume@":/docs/connect/states#resume via a fallback host.
+
+*Resolution*: Channel message continuity will not be possible in this scenario. Any messages sent while the client was disconnected will need to be retrieved using "history":/docs/storage-history/history.
+
+h2(#90001). 90001: Channel operation failed (invalid channel state)
+
+This error occurs when an application attempts to perform an operation on a channel that is in a "state":/docs/channels/states#connection-state that does not permit it.
+
+An example of this error is when an application tries to "publish":/docs/pub-sub/basic#publish a message or attach to a channel that is in a failed state due to a prior error. As a result, the operation fails because actions cannot be performed on a channel in this state.
+
+*Resolution*: The following steps can help resolve this issue:
+* Ensure the channel is in an appropriate state before performing any operations.
+* Use an Ably SDK to listen for channel state changes and handle operations accordingly.
+* Implement state change callbacks to trigger the intended operation when the channel is in a valid "state":/docs/channels/states.
+
+h2(#90004). 90004: Unable to recover channel (message limit exceeded)
+
+This error occurs when using the rewind feature with a specified time period, and the total number of messages within the selected timeframe exceeds the maximum "limit":/docs/channels/options/rewind#limits that can be retrieved in a single request.
+
+h2(#90007). 90007: Channel didn't attach within 00:00:10
+
+This error occurs when a channel fails to "attach":/docs/channels/states#attach within the default 10-second timeout. It is most commonly encountered by clients with poor internet connections, where the @ACK@ response to an @ATTACH@ request does not return within the expected timeframe.
+
+Note: In older versions of the ably-java SDK, this error was incorrectly assigned to error code @91200@.
+
+*Resolution*: Adjust the @realtimeRequestTimeout@ or @channelRetryTimeout@ (depending on the SDK) to a higher value to allow more time for the attachment process.
+
+h2(#90010). 90010: Maximum number of channels per connection exceeded
+
+This error occurs when a Realtime client "attaches":/docs/channels/states#attach to more channels than the account allows on a single connection. This happens when channels are attached but never explicitly detached, causing the limit to be reached.
+
+*Resolution*: Review your channel "limits":/docs/pricing/limits#channel and ensure that channels are explicitly detached when no longer needed.
+
+h2(#90021). 90021: Max channel creation rate exceeded
+
+This error occurs when the "maximum":/docs/pricing/limits#channel rate of channel creation has been exceeded, across an account. Until the rate returns below the limit, new channels may not be created immediately. The Ably SDK will automatically retry every 10 seconds until the request succeeds.
+
+h2(#91000). 91000: Unable to enter presence channel (no clientId)
+
+This error occurs when a client attempts to "enter":/docs/chat/rooms/presence#set the "presence":/docs/presence-occupancy/presence set of a channel without specifying a @clientId@.
+
+A client can be identified in several ways:
+* If using "token":/docs/auth/token authentication, ensure the token is associated with a @clientId@ by setting the @clientId@ field in @tokenParams@ when creating a token request or requesting a token.
+* If using basic authentication or token authentication with a wildcard @clientId@, set the @clientId@ in the client options when initializing the Ably SDK.
+* Specify a @clientId@ at the time of entering presence using "@enterClient()@":/docs/api/realtime-sdk/presence#enterclient.
+
+h2(#91003). 91003: Maximum member limit exceeded
+
+This error occurs when the "maximum":/docs/pricing/limits#hitting number of clients in the "presence":/docs/presence-occupancy/presence set for a channel has been reached, preventing additional clients from joining.
+
+h2(#91005). 91005: Presence state is out of sync
+
+This is a client-side issue that occurs when an up-to-date "presence":/docs/presence-occupancy/presence set cannot be retrieved due to connection issues. It typically happens when calling @presence.get()@ while the channel is in a suspended state, often caused by an interruption in the client's internet connection.
+
+*Resolution*: The following steps can help resolve this issue:
+* Ensure the client has an active connection before calling @presence.get()@.
+* If an outdated presence set is acceptable, set @waitForSync@ to @false@ to retrieve the presence data even when out of sync.
+
+h2(#101000). 101000: Space name missing
+
+This error occurs when calling "@spaces.get()@":/docs/spaces/space#options without specifying a space name. The name parameter is required to retrieve a space.
+
+*Resolution*: Ensure that a valid space name is provided when calling @spaces.get()@:
+
+```[javascript]
+const space = await spaces.get('mySpaceName');
+```
+
+h2(#101001). 101001: Not entered space
+
+This error occurs when calling a function that requires the client to be "entered":/docs/spaces/space#enter into a space, but the client has not yet done so.
+
+*Resolution*: Ensure that @space.enter()@ is called before performing operations that require the client to be inside the space:
+
+```[javascript]
+const space = await spaces.get('mySpace');
+space.enter({
+ username: 'Claire Lemons',
+ avatar: 'https://slides-internal.com/users/clemons.png',
+});
+```
+
+h2(#101002). 101002: Lock request exists
+
+This error occurs when an existing "lock request":/docs/spaces/locking#states is still pending or locked. Nested locks are not supported, so a new lock cannot be requested until the previous one is resolved.
+
+h2(#101003). 101003: Lock is locked
+
+This error occurs when a lock is already in the "locked state":/docs/spaces/locking#states, and a pending request did not override or release the lock.
+
+h2(#101004). 101004: Lock invalidated
+
+This error occurs when a "lock request":/docs/spaces/locking#states invalidates an existing lock that was already in the locked state.
diff --git a/content/errors/index.textile b/content/errors/index.textile
new file mode 100644
index 0000000000..f451f16243
--- /dev/null
+++ b/content/errors/index.textile
@@ -0,0 +1,131 @@
+---
+title: Debugging
+meta_description: "Debugging in Ably supported apps, including troubleshooting techniques, logging options, and tools for error analysis."
+languages:
+- javascript
+---
+
+Errors can occur in various scenarios when using Ably, such as invalid inputs in requests, authentication issues, or connection problems caused by network disruptions. Proper debugging is essential for building a reliable application and troubleshooting.
+
+You can debug issues in your Ably-supported app using the following:
+
+* Set up a custom log handler to capture and manage errors in a way that suits your requirements.
+* Meta channels allow you to monitor errors that might not otherwise be visible to clients, providing additional insights into issues.
+* The "Dev console":https://ably.com/accounts/any/apps/any/console in your Ably dashboard is a quick and easy way to inspect errors and events, especially during development or debugging.
+
+h2(#format). Error info
+
+All errors returned by Ably are standardized and use the "@ErrorInfo@":/docs/api/rest-sdk/types#error-info object:
+
+```[text]
+{
+ code: 40005,
+ statusCode: 400,
+ cause: Authentication,
+ nonfatal: false,
+ href: 'https://help.ably.io/error/40005'
+}
+```
+
+The following explains each property of an @ErrorInfo@ object:
+
+- @code@ := Ably-specific numeric code indicating the error type.
+- @statusCode@ := An HTTP status code providing broader context, such as 400 for a bad request.
+- @cause@ := A brief description of the issue.
+- @nonfatal@ := A boolean indicating whether the error is critical.
+- @Href@ := A direct link to Ably's documentation or for quick troubleshooting references.
+
+h2(#logging). Logging
+
+Ably SDKs allow you to customize the function that handles logging. This function is usually set in the options when configuring a client, such as the @ClientOptions@ object for Pub/Sub.
+
+Two separate properties can be set:
+
+- @logHandler@ := Provides a custom function for each line of log output.
+- @logLevel@ := The verbosity of the logging output, from silent to trace. In some SDKs this is numeric, and in others string.
+
+The following table explains the @logLevel@ setting for the Ably client, which controls how much logging output is shown. Higher levels include more detailed information:
+
+|_. Level |_. Description |
+| @0@ | No logs |
+| @1@ | Errors only |
+| @2@ | Errors plus connection and channel state changes |
+| @3@ | Abbreviated debug output |
+| @4@ | Full debug output |
+
+The following example configures the Ably client to log only error messages using @logLevel:1@ and processes them with the function @logWriteFun()@:
+
+```[javascript]
+const ablyClient = new Ably.Realtime({
+ key: {{API_KEY}},
+ logHandler: logWriteFunc,
+ logLevel: 1 // Errors only
+});
+```
+
+The following example is a log output for an Ably client configured to log messages using @logLevel:1@:
+
+```[text]
+LOG [Level 1]: Ably: ConnectionManager.failQueuedMessages(): failing 1 queued messages, err = [_ErrorInfo: Application Ptz0jg disabled.
+(See https://help.ably.io/error/40300 for help.); statusCode=403; code=40300]
+```
+
+h2(#meta). Metachannels
+
+Ably provides a set of "metachannels":/docs/metadata-stats/metadata/subscribe#metachannels that expose internal events from the Ably system. These metachannels are be useful for debugging and monitoring, especially when investigating issues not surfaced directly to clients.
+
+
+
+There are two metachannels related to "logging:":/docs/metadata-stats/metadata/subscribe#log
+
+- @Meta[log]@ := Publishes errors that aren't visible to clients, except those related to push notifications.
+- @Meta[log:push]@ := Similar to @[meta]log@, but only publishes errors related to the delivery of push notifications.
+
+The following example subscribes to the @[meta]log@ channel:
+
+```[javascript]
+const channel = realtime.channels.get('[meta]log');
+channel.subscribe((msg) => {
+ console.log(msg);
+});
+```
+
+The following is an example event published to the @[meta]log@ channel as an "@ErrorInfo@":#format object:
+
+```[json]
+{
+ code: 40005,
+ statusCode: 400,
+ cause: Authentication,
+ nonfatal: false,
+ href: 'https://help.ably.io/error/40005'
+}
+```
+
+h2(#console). Dev console
+
+The "Dev console":https://ably.com/accounts/any/apps/any/console in your Ably dashboard is a quick and easy way to inspect errors. It provides a live stream of all events in your application, which is especially useful during early-stage development or low-traffic periods when events are easier to track:
+
+* Monitor all live events in your application for detailed insights.
+* Test publishing and subscribing to channels to identify and resolve issues with these functions.
+
+
+
+h2(#support). Support tickets
+
+If the provided information does not resolve your issue, contact Ably "support":https://ably.com/support. When contacting, include details such as your app ID, the error code, and any relevant logs to help troubleshoot.
+
+The following information is essential for effective troubleshooting, include as much of the following information as possible:
+
+* Provide timestamps in UTC format.
+* Include complete SDK logs from the time of the failure. Ensure these logs show activity before and after the timeout, as SDKs retry failed requests by default.
+* Specify which endpoints were accessed. Mention if you use custom client options, the environment setting, and the failing SDK operation.
+* State the SDK/s you use, including the platform and versions.
+* Include any stack traces related to the error.
+* Indicate whether the issue occurs consistently or was a one-time event.
+* Provide details to confirm whether the issue was related to your network or Ably's availability. For example, note whether other internet operations were succeeding simultaneously.
+* Include the @appID@ associated with the request.
diff --git a/content/getting-started/javascript.textile b/content/getting-started/javascript.textile
new file mode 100644
index 0000000000..a6593dfcbf
--- /dev/null
+++ b/content/getting-started/javascript.textile
@@ -0,0 +1,186 @@
+---
+title: "Getting started: Pub/Sub in JavaScript"
+meta_description: "Get started with Pub/Sub in JavaScript using Ably. Learn how to publish, subscribe, track presence, fetch message history, and manage realtime connections."
+meta_keywords: "Pub/Sub JavaScript, JavaScript PubSub, Ably JavaScript SDK, realtime messaging JavaScript, publish subscribe JavaScript, Ably Pub/Sub guide, JavaScript realtime communication, Ably tutorial JavaScript, JavaScript message history, presence API JavaScript, Ably Pub/Sub example, realtime Pub/Sub JavaScript, subscribe to channel JavaScript, publish message JavaScript, Ably CLI Pub/Sub"
+---
+
+This guide will get you started with Ably Pub/Sub in JavaScript.
+
+It will take you through the following steps:
+
+* Create a client and establish a realtime connection to Ably.
+* Attach to a channel and subscribe to its messages.
+* Publish a message to the channel for your client to receive.
+* Join and subscribe to the presence set of the channel.
+* Retrieve the messages you sent in the guide from history.
+* Close a connection to Ably when it is no longer needed.
+
+h2(#prerequisites). Prerequisites
+
+* Sign up for an Ably account.
+** Create a new app, and create your first API key.
+** Your API key will need the @publish@, @subscribe@, @presence@ and @history@ capabilities.
+
+* Install the Ably CLI:
+
+```[sh]
+npm install -g @ably/cli
+```
+
+* Run the following to log in to your Ably account and set the default app and API key:
+
+```[sh]
+ably login
+
+ably apps switch
+ably auth keys switch
+```
+
+* Install "Node.js":https://nodejs.org/en version 16 or greater.
+* Create a new project in your IDE and install the Ably Pub/Sub JavaScript SDK:
+
+```[sh]
+npm init
+
+npm install ably
+```
+
+* Set @"type": "module"@ in your @package.json@ file.
+
+
+
+h2(#step-1). Step 1: Connect to Ably
+
+Clients establish a connection with Ably when they instantiate an SDK. This enables them to send and receive messages in realtime across channels.
+
+* Open up the "dev console":https://ably.com/accounts/any/apps/any/console of your first app before instantiating your client so that you can see what happens.
+
+* Create an @index.js@ file in your project and add the following function to instantiate the SDK and establish a connection to Ably. At the minimum you need to provide an authentication mechanism. Use an API key for simplicity, but you should use token authentication in a production app. A @clientId@ ensures the client is identified, which is required to use certain features, such as presence:
+
+```[javascript]
+import * as Ably from 'ably';
+
+async function getStarted() {
+
+ const realtimeClient = new Ably.Realtime({ key: '{{API_KEY}}', clientId: 'my-first-client' });
+
+ await realtimeClient.connection.once('connected');
+ console.log(`Made my first connection!`);
+
+};
+
+getStarted();
+```
+
+You can monitor the lifecycle of clients' connections by registering a listener that will emit an event every time the connection state changes. For now, run the function with @node index.js@ to log a message to the console to know that the connection attempt was successful. You'll see the message printed to your console, and you can also inspect the connection event in the dev console of your app.
+
+h2(#step-2). Step 2: Subscribe to a channel and publish a message
+
+Messages contain the data that a client is communicating, such as a short 'hello' from a colleague, or a financial update being broadcast to subscribers from a server. Ably uses channels to separate messages into different topics, so that clients only ever receive messages on the channels they are subscribed to.
+
+* Add the following lines to your @getStarted()@ function to create a channel instance and register a listener to subscribe to the channel. Then run it with @node index.js@:
+
+```[javascript]
+const channel = realtimeClient.channels.get('my-first-channel');
+
+await channel.subscribe((message) => {
+ console.log(`Received message: ${message.data}`);
+});
+```
+
+* Use the Ably CLI to publish a message to your first channel. The message will be received by the client you've subscribed to the channel, and be logged to the console.
+
+```[sh]
+ably channels publish my-first-channel 'Hello!'
+```
+
+* In a new terminal tab, subscribe to the same channel using the CLI:
+
+```[sh]
+ably channels subscribe my-first-channel
+```
+
+Publish another message using the CLI and you will see that it's received instantly by the client you have running locally, as well as the subscribed terminal instance.
+
+h2(#step-3). Step 3: Join the presence set
+
+Presence enables clients to be aware of one another if they are present on the same channel. You can then show clients who else is online, provide a custom status update for each, and notify the channel when someone goes offline.
+
+* Add the following lines to your @getStarted()@ function to subscribe to, and join, the presence set of the channel. Then run it with @node index.js@:
+
+```[javascript]
+await channel.presence.subscribe((member) => {
+ console.log(`Event type: ${member.action} from ${member.clientId} with the data ${JSON.stringify(member.data)}`)
+});
+
+await channel.presence.enter("I'm here!");
+```
+
+* In the "dev console":https://ably.com/accounts/any/apps/any/console of your first app, attach to @my-first-channel@. Enter a @clientId@, such as @my-dev-console@, and then join the presence set of the channel. You'll see that @my-first-client@ is already present in the channel. In the console of your browser or IDE you'll see that an event was received when the dev console client joined the channel.
+
+* You can have another client join the presence set using the Ably CLI:
+
+```[sh]
+ably channels presence enter my-first-channel --client-id "my-cli" --data '{"status":"learning about Ably!"}'
+```
+
+h2(#step-4). Step 4: Retrieve message history
+
+You can retrieve previously sent messages using the history feature. Ably stores all messages for 2 minutes by default in the event a client experiences network connectivity issues. This can be extended for longer if required.
+
+If more than 2 minutes has passed since you published a regular message (excluding the presence events), then you can publish some more before trying out history. You can use the Pub/Sub SDK, Ably CLI or the dev console to do this.
+
+For example, using the Ably CLI to publish 5 messages:
+
+```[sh]
+ably channels publish --count 5 my-first-channel "Message number {{.Count}}"
+```
+
+* Add the following lines to your @getStarted()@ function to retrieve any messages that were recently published to the channel. Then run it with @node index.js@:
+
+```[javascript]
+const history = await channel.history();
+console.log(history.items.map((message) => message.data));
+```
+
+The output will look similar to the following:
+
+```[json]
+[
+ 'Message number 5',
+ 'Message number 4',
+ 'Message number 3',
+ 'Message number 2',
+ 'Message number 1'
+]
+```
+
+h2(#step-5). Step 5: Close the connection
+
+Connections are automatically closed approximately 2 minutes after no heartbeat is detected by Ably. Explicitly closing connections when they are no longer needed is good practice to help save costs. It will also remove all listeners that were registered by the client.
+
+Note that messages are streamed to clients as soon as they attach to a channel, as long as they have the necessary capabilities. Clients are implicitly attached to a channel when they call @subscribe()@. Detaching from a channel using the @detach()@ method will stop the client from being streamed messages by Ably.
+
+Listeners registered when subscribing to a channel are registered client-side. Unsubscribing by calling @unsubscribe()@ will remove previously registered listeners for that channel. Detaching from a channel has no impact on listeners. As such, if a client reattaches to a channel that they previously registered listeners for, then those listeners will continue to function upon reattachment.
+
+* Add the following to either of the clients to close the connection after a simulated 10 seconds. Run it with @node index.js@:
+
+```[javascript]
+setTimeout(() => realtimeClient.close(), 10000);
+```
+
+h2(#next). Next steps
+
+Continue to explore the documentation with JavaScript as the selected language:
+
+Read more about the concepts covered in this guide:
+
+* Revisit the basics of "Pub/Sub":/docs/pub-sub
+* Explore more "advanced":/docs/pub-sub/advanced Pub/Sub concepts
+* Understand realtime "connections":/docs/connect to Ably
+* Read more about how to use "presence":/docs/presence-occupancy/presence in your apps
+* Fetch message "history":/docs/storage-history/history in your apps
+
+You can also explore the Ably CLI further, or visit the Pub/Sub "API references":/docs/api/realtime-sdk.
diff --git a/content/integrations/index.textile b/content/integrations/index.textile
index a6b911c143..bd1b6bb893 100644
--- a/content/integrations/index.textile
+++ b/content/integrations/index.textile
@@ -26,6 +26,7 @@ The following pre-built Webhooks can be configured:
* "Zapier":/docs/integrations/webhooks/zapier
* "Cloudflare Workers":/docs/integrations/webhooks/cloudflare
* "IFTTT":/docs/integrations/webhooks/ifttt
+* "Datadog":/docs/integrations/webhooks/datadog
h2(#continuous). Outbound streaming
diff --git a/content/integrations/streaming/datadog.textile b/content/integrations/streaming/datadog.textile
new file mode 100644
index 0000000000..5c8398690f
--- /dev/null
+++ b/content/integrations/streaming/datadog.textile
@@ -0,0 +1,84 @@
+---
+title: Datadog
+meta_description: "Connect Ably and Datadog to monitor messages, channels, and connections in realtime, integrating your Ably statistics with your existing Datadog setup."
+meta_keywords: "Datadog, integrations, statistics, metrics, monitoring, analytics, enterprise"
+---
+
+The Ably "Datadog":https://docs.datadoghq.com/integrations/ably/ integration enables you to monitor your application's statistics. Every 60 seconds, Ably streams a comprehensive set of "statistics":/docs/metadata-stats/stats#metrics to the Datadog API.
+
+h2(#setup). Setup the Datadog integration
+
+To connect Ably with Datadog, you must "Raise a support ticket":https://ably.com/support to gain access to the integration via your Ably dashboard. Once granted, you can authorize the integration through Datadog's "OAuth":https://docs.datadoghq.com/developers/integrations/oauth_for_integrations/ flow. This process requires the @api_keys_write@ scope, allowing Ably to push data to your Datadog account.
+
+Once the integration is active, Datadog provides a specific Ably "dashboard":https://docs.datadoghq.com/integrations/ably/, enabling you to monitor key metrics without extra setup.
+
+The following steps setup the Datadog integration:
+
+# In Datadog, go to *Integrations*, find the *Ably* tile, and click *Install Integration*.
+# Click *Connect Accounts* to start the authorization process. Datadog redirects you to Ably.
+# Log in to your *Ably* account.
+# Select your application from the *Your Apps* page.
+# Navigate to *Integrations*, and select *Connect to Datadog*.
+# Datadog authorization page, authorize Ably using *OAuth* to grant access. The required authorization scope is: @api_keys_write@.
+# After completing authorization, you will be redirected to the *Ably dashboard*, and the process is complete.
+
+h2(#remove). Remove access
+
+Removing access disconnects Ably from Datadog, stopping data transmission and revoking authorization. Follow the steps remove the Ably and Datadog integration using either platform.
+
+h3(#in-ably). Remove access using Ably
+
+* Open your application's integration settings.
+* Click *Remove* next to the Datadog integration.
+* Ably revokes OAuth credentials and stops metric transmission.
+
+h3(#in-datadog). Remove access using Datadog
+
+* Remove associated Ably API keys via *Integrations* or *API Keys*.
+* Adjust scopes or entirely revoke OAuth tokens if necessary.
+
+h2(#lite). Datadog lite
+
+Datadog Lite is a lightweight version of the full Datadog integration that sends a reduced set of "statistics":/docs/metadata-stats/stats#metrics to the Datadog API. This integration is designed for use cases where full statistics are not required, such as when you only need to monitor a limited number channels or connections.
+
+The following statistics are streamed from Ably to Datadog using the Lite integration:
+
+|_. Metric Name |_. Description |
+|@messages.all.all.count@|Total number of messages that were successfully sent, summed over all message types and transports.|
+|@messages.all.all.billableCount@|Total number of billable messages that were successfully sent, summed over all message types and transports.|
+|@messages.all.all.data@|Total message size of all messages that were successfully sent, summed over all message types and transports.|
+|@messages.all.all.uncompressedData@|Total uncompressed message size, excluding delta compression.|
+|@messages.all.all.failed@|Total number of messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as they were rejected by an external integration target, or a service issue on Ably's side.|
+|@messages.all.all.refused@|Total number of messages that were refused by Ably. For example, due to rate limiting, malformed messages, or incorrect client permissions.|
+|@messages.all.messages.count@|Total message count, excluding presence and state messages.|
+|@messages.all.messages.billableCount@|Total billable message count, excluding presence and state messages.|
+|@messages.all.messages.data@|Total message size, excluding presence and state messages.|
+|@messages.all.messages.uncompressedData@|Total number of messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as they were rejected by an external integration target, or a service issue on Ably's side.|
+|@messages.all.messages.failed@|Total number of messages excluding presence and state messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as they were rejected by an external integration target, or a service issue on Ably’s side.|
+|@messages.all.messages.refused@|Total number of messages excluding presence and state messages that were refused by Ably. For example, due to rate limiting, malformed messages, or incorrect client permissions.|
+|@messages.all.presence.count@|Total presence message count.|
+|@messages.all.presence.billableCount@|Total billable presence message count.|
+|@messages.all.presence.data@|Total presence message size.|
+|@messages.all.presence.uncompressedData@|Total uncompressed presence message size, excluding delta compression.|
+|@messages.all.messages.failed@|Total number of presence messages excluding presence and state messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as they were rejected by an external integration target, or a service issue on Ably's side.|
+|@messages.all.messages.refused@|Total number of presence messages excluding presence and state messages that were refused by Ably. For example, due to rate limiting, malformed messages, or incorrect client permissions.|
+|@messages.inbound.all.all.count@|Total inbound message count, received by Ably from clients.|
+|@messages.inbound.all.all.data@|Total inbound message size, received by Ably from clients.|
+|@messages.inbound.all.all.uncompressedData@|Total uncompressed inbound message size, excluding delta compression, received by Ably from clients.|
+|@messages.inbound.all.all.failed@|Total number of inbound messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as a service issue on Ably's side.|
+|@messages.inbound.all.all.refused@|Total number of inbound messages that were refused by Ably. For example, due to rate limiting, malformed messages, or incorrect client permissions.|
+|@messages.outbound.all.all.count@|Total outbound message count, sent from Ably to clients.|
+|@messages.outbound.all.all.billableCount@|Total billable outbound message count, sent from Ably to clients.|
+|@messages.outbound.all.all.data@|Total outbound message size, sent from Ably to clients.|
+|@messages.outbound.all.all.uncompressedData@|Total uncompressed outbound message size, excluding delta compression, sent from Ably to clients.|
+|@messages.outbound.all.all.failed@|Total number of outbound messages that failed. These are messages which did not succeed for some reason other than Ably explicitly refusing them, such as rejection by an external integration target, or a service issue on Ably's side.|
+|@messages.outbound.all.all.refused@|Total number of outbound messages that were refused by Ably. This is generally due to rate limiting.|
+|@connections.all.peak@ |Peak connection count.|
+|@channels.peak@ |Peak active channel count.|
+|@push.channelMessages@ |Total number of channel messages published over Ably that contained a push payload. Each of these may have triggered notifications to be sent to a device with a matching registered push subscription.|
+|@messages.persisted.messages.count@ |Total count of persisted messages, excluding presence and state messages.|
+|@messages.persisted.messages.data@ |Total size of persisted messages, excluding presence and state messages.|
+|@messages.persisted.messages.uncompressedData@ |Total uncompressed persisted message size, excluding delta compression, and presence and state messages.|
+|@messages.persisted.presence.count@ |Total count of persisted presence messages.|
+|@messages.persisted.presence.data@ |Total size of persisted presence messages.|
+|@messages.persisted.presence.uncompressedData@ |Total uncompressed persisted presence message size, excluding delta compression.|
diff --git a/content/livesync/mongodb/index.textile b/content/livesync/mongodb/index.textile
index 5149c982ff..fec10c2b06 100644
--- a/content/livesync/mongodb/index.textile
+++ b/content/livesync/mongodb/index.textile
@@ -23,10 +23,6 @@ The integration rule exists as a "database connector" component that is entirely
When a change event is received over the Change Streams API it is published to an Ably channel. When you configure the integration rule, you can specify how change events are mapped to individual Ably channels. Clients can then subscribe to database changes by subscribing to Ably channels. Ably "Auth":/docs/auth and "Capabilities":/docs/auth/capabilities control which channels a client can interact with.
-h2(#development-status). Development status
-
-The MongoDB database connector is currently in beta status. Your "feedback":https://docs.google.com/forms/d/e/1FAIpQLSd00n1uxgXWPGvMjKwMVL1UDhFKMeh3bSrP52j9AfXifoU-Pg/viewform will help prioritize improvements and fixes for subsequent releases.
-
h2(#integration-rule). Integration rule
h3(#create). Create a rule
diff --git a/content/metadata-stats/metadata/subscribe.textile b/content/metadata-stats/metadata/subscribe.textile
index e15b4d7a8b..3e536886b8 100644
--- a/content/metadata-stats/metadata/subscribe.textile
+++ b/content/metadata-stats/metadata/subscribe.textile
@@ -36,7 +36,7 @@ It is never possible to publish or be present on a metachannel, however you can
The following is an example of a capability that provides access to subscribe to all metachannels:
```[json]
-{"[meta]*":["subscribe"]}
+{"[meta]*":["subscribe"]}
```
h2(#connection-lifecycle). Connection lifecycle events
diff --git a/content/metadata-stats/stats.textile b/content/metadata-stats/stats.textile
index 70e2b205d6..a5d2d97f83 100644
--- a/content/metadata-stats/stats.textile
+++ b/content/metadata-stats/stats.textile
@@ -36,45 +36,29 @@ Statistics are available as:
h2(#account). Account statistics
-Account statistics provide information about all applications in your account.
-
-You can retrieve account statistics from:
-
-* The "Control API or REST API":#account-api
-* Your "account dashboard":#account-dashboard
+Account statistics aggregate metrics from all applications in your account with additional "account-only metrics":#account-only relating to peak rates monitored and enforced at an account level.
h3(#account-api). Account statistics via API
-You can retrieve account statistics from the "Control API":/docs/account/control-api or the "REST API":/docs/api/rest-api.
-
-h4(#account-control). Account statistics via Control API
-
You can retrieve account statistics using the "Control API":/docs/account/control-api by making a call to the @accounts@ endpoint using your @accountId@ and @accessToken@. For example, to retrieve the last two minutes of account statistics:
```[curl]
-curl --location --request POST 'https://control.ably.net/v1/accounts/${ACCOUNT_ID}/stats' \
---header 'Content-Type: application/json' \
---header 'Authorization: Bearer ${ACCESS_TOKEN}' \
---data-raw '{
- "unit": "minute",
- "limit": 2
-}'
-```
-
-Use the @/me@ endpoint to find your @accountId@, or alternatively find it in the "*Settings*":https://ably.com/accounts/any/edit page of your account dashboard:
-
-```[curl]
-curl --location --request GET 'https://control.ably.net/v1/me' \
---header 'Authorization: Bearer ${ACCESS_TOKEN}'
+curl --request GET \
+ --url 'https://control.ably.net/v1/accounts/${ACCOUNT_ID}/stats?unit=minute&limit=2' \
+ --header 'Authorization: Bearer ${ACCESS_TOKEN}' \
+ --header 'Content-Type: application/json'
```
-h4(#account-rest). Account statistics via REST API
+This endpoint returns a "statistics response type":#payload including "account-only metrics":#account-only.
-You can use the "REST API":/docs/api/rest-api to retrieve account statistics by making a GET request to the "@stats@ endpoint":/docs/api/rest-api#stats. For example, to retrieve account statistics aggregated by hour:
+
```[curl]
-curl https://rest.ably.io/stats?unit=hour \
- -u "{{API_KEY}}"
+curl 'https://control.ably.net/v1/me' \
+--header 'Authorization: Bearer ${ACCESS_TOKEN}'
```
h3(#account-dashboard). Account statistics from the dashboard
@@ -83,21 +67,19 @@ Your account statistics are available as graphs, tabular data, and for download
h2(#app). App statistics
-App statistics provide information about a specific application.
-
-You can retrieve account statistics:
+App statistics provide metrics scoped to each application. You can retrieve account statistics:
* Via the "Control API":#app-control
* Programmatically using the "Pub/Sub SDK":#app-sdk
* In realtime by subscribing to a "meta channel":#app-meta
* From your "application dashboard":#app-dashboard
-h3(#app-control). App statistics via API
+h3(#app-control). App statistics via Control API
-You can retrieve app statistics using the "Control API":/docs/account/control-api by making a call to the @apps@ endpoint using your @appId@ and @accessToken@:
+You can retrieve app statistics using the "Control API":/docs/account/control-api by making a call to the @apps@ endpoint using your @APP_ID@ and @ACCESS_TOKEN@:
```[curl]
-curl --location --request POST 'https://control.ably.net/v1/apps/${APP_ID}/stats' \
+curl 'https://control.ably.net/v1/apps/${APP_ID}/stats' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ${ACCESS_TOKEN}' \
--data-raw '{
@@ -106,13 +88,19 @@ curl --location --request POST 'https://control.ably.net/v1/apps/${APP_ID}/stats
}'
```
-Your @appId@ can be found in the "*Settings*":https://ably.com/accounts/any/apps/any/edit tab of an application within your application dashboard.
+This endpoint returns a "statistics response type":#payload.
-h3(#app-sdk). App statistics programmatically
+
-App-level statistics are also available programmatically using an Ably SDK at one minute intervals, or aggregated up to the hour, day or month.
+h3(#app-sdk). App statistics via SDK
-The following is an example of querying app-level statistics:
+App-level statistics are also available programmatically using an "Ably SDK":/docs/sdks at one minute intervals, or aggregated up to the hour, day or month. Whilst it is possible to obtain statistics via the SDKs, this API is not actively maintained and may be deprecated in future. We recommend using "app-level stats via the Control API":#app-control.
+
+The following is an example of querying app-level statistics using the Ably SDK "@stats@ method":/docs/api/rest-sdk/statistics.
```[realtime_javascript]
const realtime = new Ably.Realtime('{{API_KEY}}');
@@ -274,7 +262,7 @@ h3(#app-meta). Subscribe to app statistics
You can subscribe to app statistics using the "@[meta]stats:minute@ metachannel":/docs/metadata-stats/metadata/subscribe#stats. Events are published at one minute intervals and contain the statistics for the previous minute.
-The following is an example of subscribing to the @[meta]stats@ channel:
+The following is an example of subscribing to the @[meta]stats:minute@ channel:
```[realtime_javascript]
const channel = ably.channels.get("[meta]stats:minute");
@@ -331,40 +319,48 @@ h3(#app-dashboard). App statistics from the dashboard
App statistics are available as graphs, tabular data, and for download from each "application dashboard":https://ably.com/accounts/any/apps/any in your account.
-h2(#payload). Statistics response payload
+h2(#payload). Statistics response
-Statistics responses are sparse to reduce the size of the JSON and improve performance.
+Statistic API and SDK requests return an array of "Statistics entry types":#metrics.
-This means that if a metric is empty, or contains only zero values then the metric will be omitted completely from the response.
+An example simplified response with one stats entry is shown below:
```[json]
-{
- entries: {
- 'messages.all.all.count': 245,
- 'messages.all.all.data': 58654,
- 'messages.all.all.uncompressedData': 58654,
- 'messages.all.all.billableCount': 245,
- 'messages.all.messages.count': 245,
- 'messages.all.messages.data': 58654,
- 'messages.all.messages.uncompressedData': 58654,
- 'messages.all.messages.billableCount': 245,
- 'messages.inbound.realtime.all.count': 145,
- 'messages.inbound.realtime.all.data': 29127,
- 'messages.inbound.realtime.all.uncompressedData': 29127,
- 'messages.inbound.realtime.messages.count': 145,
- ...
- },
- schema: 'https://schemas.ably.com/json/app-stats-0.0.5.json',
- appId: '',
- inProgress: '2025-01-20:15:11',
- unit: 'hour',
- intervalId: '2025-01-20:15'
-}
+[
+ {
+ entries: {
+ 'messages.all.all.count': 245,
+ 'messages.all.all.data': 58654,
+ 'messages.all.all.uncompressedData': 58654,
+ 'messages.all.all.billableCount': 245,
+ 'messages.all.messages.count': 245,
+ 'messages.all.messages.data': 58654,
+ 'messages.all.messages.uncompressedData': 58654,
+ 'messages.all.messages.billableCount': 245,
+ 'messages.inbound.realtime.all.count': 145,
+ 'messages.inbound.realtime.all.data': 29127,
+ 'messages.inbound.realtime.all.uncompressedData': 29127,
+ 'messages.inbound.realtime.messages.count': 145,
+ ...
+ },
+ schema: 'https://schemas.ably.com/json/app-stats-0.0.5.json',
+ appId: '',
+ inProgress: '2025-01-20:15:11',
+ unit: 'hour',
+ intervalId: '2025-01-20:15'
+ }
+]
```
-h2(#metrics). Statistics metrics
+
+
+h2(#metrics). Statistics entry type
-The following metadata is returned for each request:
+The following metadata is returned for each entry:
|_. Property |_. Description |
| appId | The ID of the Ably application the statistics are for. Only present when querying app statistics. |
@@ -376,7 +372,7 @@ The following metadata is returned for each request:
h3(#messages). All messages
-The following metrics can be returned for all messages. 'All messages' includes totals for all messages sent and received by Ably and clients.
+All messages metrics include all messages types, such as those sent and received by Ably and clients, messages delivered via integrations and push notifications delivered to devices.
|_. Metric |_. Description |
| messages.all.all.count | Total number of messages that were successfully sent, summed over all message types and transports. |
@@ -404,7 +400,7 @@ The following metrics can be returned for all messages. 'All messages' includes
h3(#inbound). Inbound messages
-The following metrics can be returned for inbound messages. Inbound messages are messages that are sent by clients and received by Ably.
+Inbound messages metrics include those messages that are sent by clients and received inbound from those clients by Ably.
|_. Metric |_. Description |
| messages.inbound.realtime.all.count | Total inbound realtime message count, received by Ably from clients. |
@@ -464,7 +460,7 @@ The following metrics can be returned for inbound messages. Inbound messages are
h3(#outbound). Outbound messages
-The following metrics can be returned for outbound messages. Outbound messages are messages that are sent from Ably to a client.
+Outbound message metrics include those messages that are sent outbound from Ably to either clients that subscribe or request messages (such as history requests), to "integrations":docs/integrations, or to "push notification":/docs/push targets.
|_. Metric |_. Description |
| messages.outbound.realtime.all.count | Total outbound realtime message count, sent from Ably to clients. |
@@ -600,7 +596,7 @@ The following metrics can be returned for outbound messages. Outbound messages a
h3(#persisted). Persisted messages
-The following metrics can be returned for persisted messages. Persisted messages are messages "stored":/docs/storage-history/storage by Ably.
+Persisted messages metrics are calculated from the number of "messages stored":/docs/storage-history/storage by Ably.
|_. Metric |_. Description |
| messages.persisted.all.count | Total count of persisted messages. |
@@ -615,7 +611,7 @@ The following metrics can be returned for persisted messages. Persisted messages
h3(#deltas). Message deltas
-The following metrics can be returned for message deltas. Message deltas are messages that are compressed by using the "delta":/docs/channels/options/deltas feature.
+Message deltas metrics are calculated from the number of "messages delta":/docs/channels/options/deltas generated.
|_. Metric |_. Description |
| messages.processed.delta.xdelta.succeeded | Total number of message deltas successfully generated. |
@@ -624,7 +620,7 @@ The following metrics can be returned for message deltas. Message deltas are mes
h3(#connections). Connections
-The following metrics can be returned for connections. Connections are all client "connections":/docs/connect made to Ably.
+Connection metrics include all realtime "connections":/docs/connect made to Ably.
|_. Metric |_. Description |
| connections.all.peak | Peak connection count. |
@@ -635,7 +631,7 @@ The following metrics can be returned for connections. Connections are all clien
h3(#channels). Channels
-The following metrics can be returned for channels. "Channels":/docs/channels are used to separate messages into different topics throughout Ably.
+Channel metrics include all active "channels":/docs/channels that are used to separate messages into different topics throughout Ably.
|_. Metric |_. Description |
| channels.peak | Peak active channel count. |
@@ -646,7 +642,7 @@ The following metrics can be returned for channels. "Channels":/docs/channels ar
h3(#api). API requests
-The following metrics can be returned for API requests. API requests are all HTTP requests made to Ably, including those related to "token authentication":/docs/auth/token and "push notifications":/docs/push. They do not include those requests made to the "Control API":/docs/account/control-api.
+API request metrics include all HTTP requests made to Ably, including those related to "token authentication":/docs/auth/token and "push notifications":/docs/push. They do not include those requests made to the "Control API":/docs/account/control-api.
|_. Metric |_. Description |
| apiRequests.all.succeeded | Total number of API requests made. |
@@ -664,7 +660,7 @@ The following metrics can be returned for API requests. API requests are all HTT
h3(#push). Push notifications
-The following metrics can be returned for push notifications. "Push notifications":/docs/push are all notifications sent to devices via a push transport.
+Push notification metrics include all "notifications":/docs/push sent to devices via one of the push transports such as APNS, FCM or web.
|_. Metric |_. Description |
| push.channelMessages | Total number of channel messages published over Ably that contained a @push@ payload. Each of these may have triggered notifications to be sent to a device with a matching registered push subscription. |
@@ -694,10 +690,10 @@ The following metrics can be returned for push notifications. "Push notification
h3(#account-only). Account-only metrics
-The following metrics can only be returned when querying account statistics:
+The following metrics will only be returned when querying account statistics.
|_. Metric |_. Description |
-| peakRates.messages | ThePeak rate of messages. |
+| peakRates.messages | Peak rate of messages. |
| peakRates.apiRequests | Peak rate of api requests. |
| peakRates.tokenRequests | Peak rate of token requests. |
| peakRates.connections | Peak rate of opened connections. |
diff --git a/content/partials/types/_message.textile b/content/partials/types/_message.textile
index fe718fe437..33724929f7 100644
--- a/content/partials/types/_message.textile
+++ b/content/partials/types/_message.textile
@@ -1,31 +1,68 @@
A @Message@ represents an individual message that is sent to or received from Ably.
-h4.
- default: Properties
- java: Members
- ruby: Attributes
- python: Attributes
+h6(#name).
+ default: name
+ csharp: Name
-- nameName := Event name, if provided __Type: @String@__
+The 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@@Object@__
+h6(#data).
+ default: data
+ csharp: Data
-- extrasExtras := Metadata and/or ancillary payloads, if provided. The only currently valid payloads for extras are the "@push@":/docs/push/publish#payload, "@ref@":/docs/channels/messages#interactions and "@privileged@":/docs/integrations/webhooks#skipping objects. __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@@Map@, @List@__
+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@@String@, @Map@, @List@__
-- idId := Unique ID assigned by Ably to this message. Can optionally be assigned by the client as part of "idempotent publishing":/docs/pub-sub/advanced#idempotency __Type: @String@__
+h6(#extras).
+ default: extras
+ csharp: Extras
-- clientIdclient_idClientId := The client ID of the publisher of this message __Type: @String@__
+Metadata and/or ancillary payloads, if provided. Valid payloads include "@push@":/docs/push/publish#payload, "@headers@" (a map of strings to strings for arbitrary customer-supplied metadata), "@ephemeral@":/docs/pub-sub/advanced#ephemeral, and "@privileged@":/docs/integrations/webhooks#skipping objects. __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@__
-- connectionIdconnection_idConnectionId := The connection ID of the publisher of this message __Type: @String@__
+h6(#id).
+ default: id
+ csharp: Id
-- timestampTimestamp := Timestamp when the message was received by the Ably service, as milliseconds since the epocha @Time@ object __Type: @Integer@@Long Integer@@DateTimeOffset@@Time@@NSDate@@DateTime@__
+A Unique ID assigned by Ably to this message. __Type: @String@__
-- 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@__
+h6(#client-id).
+ default: clientId
+ csharp: ClientId
+ ruby: client_id
+ python: client_id
-h3.
+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(#connection-key).
+ default: connectionKey
+ csharp,go: ConnectionKey
+ ruby,python: connection_key
+
+A connection key, which can optionally be included for a REST publish as part of the "publishing on behalf of a realtime client functionality":/docs/pub-sub/advanced#publish-on-behalf. __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@__
+
+h3(constructors).
default: Message constructors
-h4(#message-from-encoded).
+h6(#message-from-encoded).
default: Message.fromEncoded
bq(definition).
@@ -42,7 +79,7 @@ h4. Returns
A "@Message@":/docs/api/realtime-sdk/types#message object
-h4(#message-from-encoded-array).
+h6(#message-from-encoded-array).
default: Message.fromEncodedArray
bq(definition).
diff --git a/content/platform/architecture/index.textile b/content/platform/architecture/index.textile
new file mode 100644
index 0000000000..2354ba9c3b
--- /dev/null
+++ b/content/platform/architecture/index.textile
@@ -0,0 +1,143 @@
+---
+title: Architecture overview
+meta_description: "Learn more about Ably's platform architecture."
+meta_keywords: "architecture"
+---
+
+Ably's platform architecture is built to deliver dependable realtime experiences at global scale.
+
+As the definitive realtime experience platform, Ably serves billions of devices monthly and delivers over half a trillion messages monthly.
+
+h2. At a glance
+
+Ably's globally distributed infrastructure forms the foundation of the platform, allowing Ably to maintain exceptional performance, while serving massive scale and providing industry-leading quality of service guarantees.
+
+Ably characterizes the system across "4 pillars of dependability":https://ably.com/four-pillars-of-dependability :
+
+* **Performance**: Ably focuses on predictability of latencies to provide certainty in uncertain operating conditions.
+** <30ms round trip latency within datacenter (99th percentile)
+** <65ms global round trip latency (99th percentile)
+* **Integrity**: Guarantees for message ordering and delivery.
+** Exactly-once delivery semantics
+** Guaranteed message ordering from publishers to subscribers
+** Automatic connection recovery with message continuity
+* **Reliability**: Fault tolerant architecture at regional and global levels to survive multiple failures without outages.
+** 99.999999% message survivability
+** 99.99999999% persisted data survivability
+** Edge network failure resolution by the client SDKs within 30s
+** Automated routing of all traffic away from an abrupt failure of datacenter in less than two minutes
+* **Availability**: Meticulously designed to provide continuity of service even in the case of instance or whole datacenter failures.
+** 99.9999% global service availability (5 minutes 15 seconds of downtime per year)
+** 50% global capacity margin for instant demand surges
+
+h2. Design objectives
+
+Ably's platform is a global service that supports all realtime messaging and associated services. It is architected to achieve horizontal scalability with "no effective ceiling":https://ably.com/blog/ablys-four-pillars-no-scale-ceiling on application scale, while maintaining consistent latency, message integrity, and system reliability across the global network.
+
+The platform has been designed with the following primary objectives in mind:
+
+* **Horizontal scalability**: As more nodes are added, load is automatically redistributed across the cluster so that global capacity increases linearly with the number of instances Ably runs.
+* **No single point of congestion**: As the system scales, there is no single point of congestion for any data path, and data within the system is routed peer-to-peer, ensuring no single component becomes overloaded as traffic scales for an individual app or across the cluster.
+* **Fault tolerance**: Faults in the system are expected, and the system must have redundancy at every layer in the stack to ensure availability and reliability.
+* **Autonomy**: Each component in the system should be able to operate fully without reliance on a global controller. For example, two isolated data centers should continue to service realtime requests while isolated.
+* **Consistent low latencies**: Within data centers, Ably aims for latencies to be in the low 10s of milliseconds and less than 100ms globally. Consistently achieving low latencies requires careful consideration of the placement of data and services across the system as well as prioritisation of the computation performed by each service.
+* **Quality of service**: Ably intentionally designs for high QoS targets to enable sophisticated realtime applications that would be impossible on platforms with weaker guarantees.
+
+h2. Cluster architecture
+
+Ably's platform runs on AWS EC2 infrastructure with a globally distributed architecture. Ably's "clusters":/docs/platform-customization#dedicated-and-isolated-clusters typically span multiple regions, usually between two and ten. This multi-region approach maximizes availability and is a critical aspect of providing a fault tolerant service.
+
+Each regional deployment operates independently, handling its own subscriber connections, REST traffic, channel management and message routing. When activity occurs on a channel across multiple regions, messages flow peer-to-peer between regions directly, eliminating central bottlenecks and single points of failure.
+
+Ably's architecture consists of four primary layers:
+
+* **Routing Layer**: Provides intelligent, latency optimized routing for robust end client connectivity.
+* **Gossip Layer**: Distributes network topology information and facilitates service discovery.
+* **Frontend Layer**: Handles REST requests and maintains realtime connections (such as WebSocket, Comet and SSE).
+* **Core Layer**: Performs all central message processing for channels.
+
+
+
+
+
+Each component scales independently in each region based on demand. Ably continuously monitors CPU, memory, and other key metrics, triggering autoscaling based on aggregated performance indicators.
+
+The key to Ably's horizontal scalability is intelligent load distribution that efficiently utilizes new capacity as it becomes available:
+
+* In the frontend layer, new instances join the load balancer pool and begin handling their share of incoming connections and REST requests.
+* In the core layer, Ably employs consistent hashing to distribute channels across core processes - each core maintains a set of pseudo-randomly generated hashes that determine channel placement. As the cluster scales, channels automatically relocate to maintain even load distribution.
+
+h3. Routing layer
+
+Ably's latency-optimized routing infrastructure provides multiple layers of resilience to ensure reliable connectivity even during partial system failures.
+
+h4. Intelligent routing
+
+Ably's custom routing layer provides sophisticated traffic management through deep integration with the application architecture.
+
+The routing layer is cluster-aware and implements advanced retry strategies, enables zero-downtime deployments, and distributes instrumentation across the cluster.
+
+This intelligent routing ensures traffic is directed optimally even as the system scales or during partial failures.
+
+h4. DNS and edge protection
+
+Ably uses latency-based routing to ensure clients are consistently routed to their closest datacenter.
+
+Ably employs multiple DNS providers and global load balancing strategies for resilient connectivity, allowing client libraries to route around regional issues and ensuring consistent connectivity.
+
+The routing layer automatically removes unhealthy regions from DNS resolution and offers advanced DDoS protection at the edge.
+
+h4. Connection management
+
+Ably's SDKs implement sophisticated connection management with automatic failover capabilities.
+
+Clients seamlessly handle transient disconnections and the system preserves message continuity if the connection is re-established within two minutes.
+
+Clients automatically select the best available transport (such as WebSocket or Comet) and seamlessly migrate connections across transports to provide the best possible service.
+
+Re-connection attempts cycle through up to 6 globally distributed endpoints. Clients automatically attempt to reconnect via alternative endpoints if a connection attempt fails.
+
+Progressive connection timeout strategies ensure rapid recovery from transient issues, while continuous connection quality monitoring triggers proactive reconnection when performance degrades.
+
+h3. Gossip layer
+
+Every cluster Ably operates includes a network of gossip nodes, spanning all regions, that participate in a (Scuttlebutt-inspired) gossip protocol to facilitate service discovery and even distribution of work across the cluster. Other nodes in the cluster communicate with the gossip layer to broadcast and receive information about the state of the cluster. As nodes are added and removed, or fail abruptly, the gossip layer ensures a single consistent view of the network is shared by all. The gossip layer also allows node health to be consistently determined system-wide without the need for any single coordinator.
+
+h3. Frontend layer
+
+Ably's frontend infrastructure consists of several components that handle individual requests and connections from realtime subscribers. Each component scales independently according to demand, ensuring requests are processed in the optimal location based on client location and system state.
+
+h4. Request handling
+
+Nodes in the frontend layer process all incoming REST and realtime requests. They participate in the active service discovery mechanism between all nodes, maintaining realtime awareness of channel locations across the cluster, even as channels migrate during scaling events. Message fan-out is achieved by frontend nodes efficiently processing published messages and distributing them to all subscribed clients. The frontend layer also handles authentication, enforces rate limits, and provides additional DDoS protection.
+
+h4. Protocol adapters
+
+Ably's "protocol adapters":/docs/protocols enable interoperability with multiple industry protocols. These adapters translate between external protocols and Ably's internal protocols in both directions. Ably supports the MQTT standard as well as proprietary protocols like PubNub and Pusher.
+
+h3. Core layer
+
+Ably's core infrastructure handles all messages as they transit through the system. It is designed to scale elastically according to demand while managing all associated channel and system state.
+
+h4. Resource placement
+
+Ably uses "consistent hashing":https://ably.com/blog/implementing-efficient-consistent-hashing to distribute resources such as channels, apps and accounts across available core compute capacity.
+
+Each compute instance within the core layer has a set of pseudo-randomly generated hashes which determines which resources are located at that instance. As the cluster scales, channels relocate to maintain an even load distribution. Any number of channels can exist as long as sufficient compute capacity is available. Whether handling many lightly-loaded channels or heavily-loaded ones, Ably's scaling and placement strategies ensure capacity is added as required and load is effectively distributed.
+
+When a core node fails, the system detects the failure through the cluster-wide gossip protocol, and its resources are automatically redistributed to healthy nodes.
+
+h4. Message processing
+
+Nodes in the core layer are responsible for all channel message processing and persistence and apply operations such as "delta":/docs/channels/options/deltas computation, "batching":/docs/messages/batch and "integration rule":/docs/integrations invocation. They also aggregate and persist account and app "statistics":/docs/metadata-stats/stats and enforce "limits":/docs/pricing/limits. Nodes in the core layer communicate cross-regionally to facilitate inter-data-center message transit.
+
+h4. Message persistence
+
+Messages are persisted in multiple locations to ensure that message availability and continuity are maintained even during individual node or data center failures.
+
+Once a message is acknowledged, it is stored in multiple physical locations, providing statistical guarantees of 99.999999% (8 nines) for message availability and survivability. This redundancy enables Ably to maintain its quality of service guarantees even during infrastructure failures.
+
+Messages are stored in two ways:
+
+* **Ephemeral Storage**: Messages are held for 2 minutes in an in-memory database (Redis). This data is distributed according to Ably's consistent hashing mechanism and relocated when channels move between nodes. This short-term storage enables low-latency message delivery and retrieval and supports features like "connection recovery":/docs/connect/states.
+* **Persisted Storage**: Messages can optionally be stored persistently on disk if longer term retention is required. Ably uses a globally distributed and clustered database (Cassandra) for this purpose, deployed across multiple data centers with message data replicated to three regions to ensure integrity and availability even if a region fails.
diff --git a/content/platform/deprecate/index.textile b/content/platform/deprecate/index.textile
new file mode 100644
index 0000000000..988734a6e1
--- /dev/null
+++ b/content/platform/deprecate/index.textile
@@ -0,0 +1,37 @@
+---
+title: Deprecation policy
+meta_description: "A policy detailing how Ably deprecates SDKs and APIs."
+meta_keywords: "Ably, deprecation policy, deprecation, sunset"
+---
+
+Ably's SDKs are regularly updated to add support for new features, bugfixes, and to accommodate version or API changes in dependencies. Whilst Ably ensures changes are backwards-compatible wherever possible, there are some instances where the previous API structure is no longer suitable.
+
+In all cases, updates to SDKs conform to "semantic versioning":https://semver.org/ guidelines. It is recommended that you always update to the latest version of an SDK when it is available.
+
+h2(#applicability). Applicability
+
+This policy applies to the following:
+
+Ably SDKs and associated APIs, including;
+
+* HTTP APIs
+* Ably's realtime protocol
+
+h2(#timelines). Deprecation timelines
+
+Ably will support all releases for at least 12 months from the date of release of a version that supersedes it. For example, if version @1.1.0@ is released on 1st January 2025, version @1.0.X@ will be supported until at least 1st January 2026.
+
+Releases will be always be deprecated within 24 months of being superseded.
+
+h3(#sunset). Sunset date
+
+The sunset date is the date on which a version of an SDK or API will no longer function, at the end of the deprecation period. Ably will usually sunset features by a process of rejecting just a (gradually increasing) proportion of requests to avoid large-scale disruption where possible. However, this won't always be the case, so you should assume that features or versions will stop working on the sunset date.
+
+h2(#support). Supportability
+
+Ably will no longer provide the following when a version is deprecated:
+
+* Technical support for that version.
+* Bugfixes for that version.
+
+Ably also reserves the right to withhold Service Level Agreement (SLA) remedies in the case of service failure, if the use of the deprecated version is believed to be a contributing factor in that failure.
diff --git a/content/platform/deprecate/protocol-v1.textile b/content/platform/deprecate/protocol-v1.textile
new file mode 100644
index 0000000000..8776b87b01
--- /dev/null
+++ b/content/platform/deprecate/protocol-v1.textile
@@ -0,0 +1,33 @@
+---
+title: Deprecation of protocol version 1 - November 2025
+meta_description: "A policy detailing how Ably deprecates SDKs and APIs."
+meta_keywords: "Ably, deprecation policy, deprecation, sunset"
+---
+
+SDKs using version 1 of Ably's realtime protocol will be sunset on 1st November 2025.
+
+This version was superseded by version 2 in January 2023. It introduced new functionality, with a more efficient and scalable backend implementation. New features are not available using version 1 of the protocol, nor in any SDKs implementing it.
+
+h2(#sdks). Affected SDKs
+
+SDK versions earlier than those listed below are now considered deprecated. If you are using a version older than those listed, upgrade before the 1st November 2025.
+
+|_. Product |_. Language |_. Versions |
+| Pub/Sub | JavaScript | < "1.2.36":https://github.com/ably/ably-js/releases/tag/1.2.36 |
+| Pub/Sub | Java | < "1.2.35":https://github.com/ably/ably-java/releases/tag/v1.2.35 |
+| Pub/Sub | Swift / Objective-C | < "1.2.24":https://github.com/ably/ably-cocoa/releases/tag/1.2.24 |
+| Pub/Sub | .NET | < "1.2.12":https://github.com/ably/ably-dotnet/releases/tag/1.2.12 |
+| Pub/Sub | Go | < "1.2.14":https://github.com/ably/ably-go/releases/tag/v1.2.14 |
+| Pub/Sub | Python | < "2.0.0-beta.6":https://github.com/ably/ably-python/releases/tag/v2.0.0-beta.6 |
+| Pub/Sub | Ruby | < "1.2.5":https://github.com/ably/ably-ruby/releases/tag/v1.2.5 |
+| Pub/Sub | PHP | < "1.1.9":https://github.com/ably/ably-php/releases/tag/1.1.9 |
+| Pub/Sub | Flutter | < "1.2.25":https://github.com/ably/ably-flutter/releases/tag/v1.2.25 |
+| Kafka Connect | - | < "2.1.4":https://github.com/ably/kafka-connect-ably/releases/tag/v2.1.4 |
+
+Contact "support":https://ably.com/support if you have any questions.
+
+h2(#requests). Request failures
+
+On the 1st November 2025 attempts to connect to Ably using an SDK that uses version 1 of the protocol will start to fail. Failures will be phased, with only a fraction of traffic being rejected at first, until 100% of requests are rejected after several weeks.
+
+Requests that are rejected will contain an error message and code referencing this deprecation notice and the associated "deprecation policy.":/docs/platform/deprecate
diff --git a/content/pricing/billing.textile b/content/pricing/billing.textile
index 71765a7da8..9ffb35b734 100644
--- a/content/pricing/billing.textile
+++ b/content/pricing/billing.textile
@@ -33,3 +33,16 @@ To update your billing details:
You can update your payment details and view your invoices in this screen.
It's also possible to have your invoices sent to an accounts department by filling in the *Optional billing email address*.
+
+h2(#alerts). Billing alerts
+
+Billing alerts notify you by email when your monthly spending exceeds a set amount if you're on a Standard or Pro package. This feature helps you monitor costs and avoid unexpected charges.
+
+The following steps guide you through setting up billing alerts:
+
+* Log in to your Ably "account.":https://ably.com/login
+* In the "Create new alert":https://ably.com/accounts/any/package#billing-alerts-section section, select the email address from the dropdown list where you want to receive the alert. The available choices depend on your "user role":/docs/account/users#roles.
+** If you are an owner: Yourself and billing users associated with the account.
+** If you are a user with a billing role: Yourself only.
+* Enter a value greater than your base package fee in the *Amount* field. This amount sets the threshold at which you will receive a notification.
+* Click *Create alert* to save your changes.
diff --git a/content/pricing/enterprise.textile b/content/pricing/enterprise.textile
index bc3f7a4d9d..cb9e0c3670 100644
--- a/content/pricing/enterprise.textile
+++ b/content/pricing/enterprise.textile
@@ -14,7 +14,7 @@ Enterprise packages can include features such as:
- Custom CNAMEs := use your own custom domain, or a subdomain of Ably for your application or service.
- SAML and SCIM := authenticate with Ably using your own Identity Provider (IdP) and automatically provision users within your Ably account.
- Active traffic management := Ably can monitor the health of your own endpoints and proactively take steps to isolate, or move, traffic to ensure business continuity.
-- DataDog := send metrics from Ably into your DataDog account.
+- DataDog := send metrics from Ably into your "DataDog":/docs/integrations/streaming/datadog account.
- PrivateLink := enable traffic from your users and servers into Ably without using the public internet via "AWS PrivateLink":https://aws.amazon.com/privatelink/.
- Private clusters := guaranteed capacity and isolation from all other Ably users.
diff --git a/content/pricing/faqs.textile b/content/pricing/faqs.textile
index b0043c04c7..719063edc1 100644
--- a/content/pricing/faqs.textile
+++ b/content/pricing/faqs.textile
@@ -43,7 +43,7 @@ For example, if you have 10,000 users, and at your busiest time of the month the
h3(#message-size). How is maximum message size measured?
-The maximum message size is 5KiB. The size is calculated as the sum of the @name@, @clientId@ and @data@ "properties":/docs/api/realtime-sdk/messages#properties. This is before any compression or expansion occurs in the serialization process.
+The maximum message size is based on the "package type":/docs/pricing#packages. The size is calculated as the sum of the @name@, @clientId@ and @data@ "properties":/docs/api/realtime-sdk/messages#properties. This is before any compression or expansion occurs in the serialization process.
* @name@ and @clientId@ are calculated as the size in bytes of their UTF-8 representation.
* @data@ is calculated as the size in bytes if it is in binary, or its UTF-8 byte length if it is a string.
diff --git a/content/pricing/free.textile b/content/pricing/free.textile
index 192dd5d95f..3e1a33cb17 100644
--- a/content/pricing/free.textile
+++ b/content/pricing/free.textile
@@ -31,9 +31,9 @@ h2(#support). Support
Free package support includes Ably's best effort Service Level Objective (SLO) and access to community support, including "Discord":https://discord.gg/g8yqePUVDn and "Stack Overflow":https://stackoverflow.com/questions/tagged/ably-realtime.
-h2(#upgrade). Upgrade
+h2(#upgrade). Upgrade from Free to Standard or Pro
-To upgrade your account from a Free package:
+To upgrade your account from a Free package to a Standard or Pro package:
1. Ensure you are the "account owner":/docs/account/users.
2. Log in to your "account":https://ably.com/login and select *Billing* from the *Account* menu.
@@ -41,6 +41,20 @@ To upgrade your account from a Free package:
Note that your first invoice will be issued at the start of the following calendar month. It covers consumption from the point of upgrade up until the end of that month. All subsequent invoices will follow the same pattern of billing for the previous month's consumption. The base package price will be charged pro-rata from the point in the month that you upgraded.
-Once you upgrade to a "Standard":/docs/pricing/standard, "Pro":/docs/pricing/pro or "Enterprise":/docs/pricing/enterprise package, your consumption is counted from that point onwards. For example, if you upgrade in the middle of the month, you are charged for the first message you send after upgrading, not after the 6,000,000 messages allowed on the Free package.
+Once you upgrade to a "Standard":/docs/pricing/standard or "Pro":/docs/pricing/pro package, your consumption is counted from that point onwards. For example, if you upgrade in the middle of the month, you are charged for the first message you send after upgrading, not after the 6,000,000 messages allowed on the Free package.
+
+h2(#free-enterprise). Upgrade from Free to Enterprise
+
+To upgrade your account from a Free package to an "Enterprise package":/docs/pricing/enterprise, "contact us":https://ably.com/contact?cta=enterprise_package to discuss your options.
+
+h2(#downgrade). Downgrade
+
+To downgrade your account to a Free package:
+
+1. Ensure you are the "account owner":/docs/account/users.
+2. Log in to your "account":https://ably.com/login and select *Billing* from the *Account* menu.
+3. Click the *Downgrade* button.
+
+If you downgrade to a Free package, you will finish the month on your current package with its associated limits and benefits. At the beginning of the following month you will receive a final invoice covering your last month's consumption and package fee. Once the final invoice has been paid, your payment details are removed from the system.
If you wish to cancel your account, have the account owner "contact us":https://ably.com/support. Be aware that this will permanently delete all the information associated with your account.
diff --git a/content/pricing/pro.textile b/content/pricing/pro.textile
index 812b753af1..7df639d00a 100644
--- a/content/pricing/pro.textile
+++ b/content/pricing/pro.textile
@@ -18,7 +18,7 @@ The Pro package includes the following limits on the key units of consumption:
Pro package users also have access to the following features:
* Google and GitHub SSO
-* DataDog Lite
+* "DataDog Lite":/docs/integrations/streaming/datadog#lite
h2(#pricing). Pricing and billing
@@ -34,7 +34,29 @@ h2(#support). Support
Standard package support includes access to Ably support via email with a Service Level Agreement (SLA) of less than 1 business day for general guidance and less than 2 business hours for system impediments or system down.
-h2(#upgrade). Upgrade
+h2(#free-pro). Upgrade from Free to Pro
+
+To upgrade your account from a Free package to a Pro package:
+
+1. Ensure you are the "account owner":/docs/account/users.
+2. Log in to your "account":https://ably.com/login and select *Billing* from the *Account* menu.
+3. Choose the plan you wish to upgrade to and follow the instructions. Upgrades take effect immediately.
+
+Note that your first invoice will be issued at the start of the following calendar month. It covers consumption from the point of upgrade up until the end of that month. All subsequent invoices will follow the same pattern of billing for the previous month's consumption. The base package price will be charged pro-rata from the point in the month that you upgraded.
+
+Once you upgrade to a Pro, your consumption is counted from that point onwards. For example, if you upgrade in the middle of the month, you are charged for the first message you send after upgrading, not after the 6,000,000 messages allowed on the Free package.
+
+h2(#standard-pro). Upgrade from Standard to Pro
+
+To upgrade your account from a Standard package to a Pro package:
+
+1. Ensure you are the "account owner":/docs/account/users.
+2. Log in to your "account":https://ably.com/login and select *Billing* from the *Account* menu.
+3. Choose the plan you wish to upgrade to and follow the instructions. Upgrades take effect immediately.
+
+The base package price will be charged pro-rata from the point in the month that you upgraded. This is true for the current Standard package base price, as well as the upgraded Pro package base price.
+
+h2(#pro-enterprise). Upgrade from Pro to Enterprise
To upgrade your account from a Pro package, "contact us":https://ably.com/contact?cta=enterprise_package to discuss "Enterprise package":/docs/pricing/enterprise options.
@@ -48,6 +70,6 @@ To downgrade your account from a Pro package:
If you downgrade to a "Standard package":/docs/pricing/standard, you will finish the month on the Pro package with its associated limits and benefits. At the beginning of the following month you will receive a final invoice for the Pro package. Subsequent invoices will be for your Standard package.
-If you downgrade to a "Free package":/docs/pricing/free, you will finish the month on the Pro package with its associated limits and benefits. At the beginning of the following month you will receive a final invoice covering your last month's consumption. Once the final invoice has been paid, your payment details are removed from the system.
+If you downgrade to a "Free package":/docs/pricing/free, you will finish the month on the Pro package with its associated limits and benefits. At the beginning of the following month you will receive a final invoice covering your last month's consumption and package fee. Once the final invoice has been paid, your payment details are removed from the system.
If you wish to cancel your account, downgrade to the Free package first and then have the account owner "contact us":https://ably.com/support. Be aware that this will permanently delete all the information associated with your account.
diff --git a/content/pricing/standard.textile b/content/pricing/standard.textile
index fb4703836f..d13afb6ab0 100644
--- a/content/pricing/standard.textile
+++ b/content/pricing/standard.textile
@@ -18,7 +18,7 @@ The Standard package includes the following limits on the key units of consumpti
Standard package users also have access to the following features:
* Google and GitHub SSO
-* DataDog Lite (30 day trial)
+* "DataDog Lite":/docs/integrations/streaming/datadog#lite (30 day trial)
h2(#pricing). Pricing and billing
@@ -34,9 +34,21 @@ h2(#support). Support
Standard package support includes access to Ably support via email with a Service Level Agreement (SLA) of less than 1 business day for general guidance and for system impediments or system down.
-h2(#upgrade). Upgrade
+h2(#free-standard). Upgrade from Free to Standard
-To upgrade your account from a Standard package:
+To upgrade your account from a Free package to a Standard package:
+
+1. Ensure you are the "account owner":/docs/account/users.
+2. Log in to your "account":https://ably.com/login and select *Billing* from the *Account* menu.
+3. Choose the plan you wish to upgrade to and follow the instructions. Upgrades take effect immediately.
+
+Note that your first invoice will be issued at the start of the following calendar month. It covers consumption from the point of upgrade up until the end of that month. All subsequent invoices will follow the same pattern of billing for the previous month's consumption. The base package price will be charged pro-rata from the point in the month that you upgraded.
+
+Once you upgrade to a Standard, your consumption is counted from that point onwards. For example, if you upgrade in the middle of the month, you are charged for the first message you send after upgrading, not after the 6,000,000 messages allowed on the Free package.
+
+h2(#standard-pro). Upgrade from Standard to Pro
+
+To upgrade your account from a Standard package to a Pro package:
1. Ensure you are the "account owner":/docs/account/users.
2. Log in to your "account":https://ably.com/login and select *Billing* from the *Account* menu.
@@ -44,6 +56,10 @@ To upgrade your account from a Standard package:
The base package price will be charged pro-rata from the point in the month that you upgraded. This is true for the current Standard package base price, as well as the upgraded "Pro package":/docs/pricing/pro base price.
+h2(#standard-enterprise). Upgrade from Standard to Enterprise
+
+To upgrade your account from a Standard package to an "Enterprise package":/docs/pricing/enterprise, "contact us":https://ably.com/contact?cta=enterprise_package to discuss your options.
+
h2(#downgrade). Downgrade
To downgrade your account from a Standard package to a Free package:
@@ -52,6 +68,6 @@ To downgrade your account from a Standard package to a Free package:
2. Log in to your "account":https://ably.com/login and select *Billing* from the *Account* menu.
3. Click the *Downgrade* button.
-If you downgrade to a "Free package":/docs/pricing/free, you will finish the month on the Standard package with its associated limits and benefits. At the beginning of the following month you will receive a final invoice covering your last month's consumption. Once the final invoice has been paid, your payment details are removed from the system.
+If you downgrade to a "Free package":/docs/pricing/free, you will finish the month on the Standard package with its associated limits and benefits. At the beginning of the following month you will receive a final invoice covering your last month's consumption and package fee. Once the final invoice has been paid, your payment details are removed from the system.
If you wish to cancel your account, downgrade to the Free package first and then have the account owner "contact us":https://ably.com/support. Be aware that this will permanently delete all the information associated with your account.
diff --git a/content/pub-sub/advanced.textile b/content/pub-sub/advanced.textile
index 19bfa09a45..fc3d0af148 100644
--- a/content/pub-sub/advanced.textile
+++ b/content/pub-sub/advanced.textile
@@ -901,6 +901,36 @@ channel := realtime.Channels.Get("chatroom")
channel.Publish(context.Background(), "action", "boom!")
```
+h3(#ephemeral). Ephemeral messages
+
+A message can be marked as *ephemeral* to exempt it from:
+- being stored in "persisted history":/docs/storage-history/storage
+- being sent in "attachment rewinds":/docs/channels/options/rewind
+- being sent to clients "resuming over a period of disconnection":/docs/connect/states
+- being sent to "firehose, webhooks, and queue integrations":/docs/integrations
+
+In other words, it will be exempt from everything except being delivered to currently-connected realtime connections.
+
+This is useful for events that are relevant only at the time they are published, and have no value when stale; examples might be streaming of continuously changing values such as realtime telemetry, position information, etc. Since ephemeral messages can be interspersed with other non-ephemeral messages on a channel, it is possible to use a single channel to convey all relevant events for some entity, including a mix of some that need to be persisted and others that are only ephemeral.
+
+To mark a message as ephemeral, either include @ephemeral: true@ in the message's extras object, or (for REST publishes) include @ephemeral: true@ in the publish params.
+
+The following is an example of publishing an ephemeral message:
+
+```[realtime_javascript]
+const channel = realtime.channels.get('chatroom');
+await channel.publish({name: 'emote', data: ':heart:', extras: { ephemeral: true }});
+```
+
+```[rest_javascript]
+const channel = rest.channels.get('chatroom');
+await channel.publish('emote', ':heart:', { ephemeral: true });
+// or
+await channel.publish({ name: 'emote', data: ':heart:' }, { ephemeral: true });
+```
+
+Note that if using the form of publish that takes an array of messages to be published atomically, either all the messages must be marked ephemeral or none of them. If they are mixed, the publish will be rejected.
+
h3(#idempotency). Idempotent publishing
Idempotency ensures that multiple publishes of the same message cannot result in duplicate messages.
diff --git a/content/push/index.textile b/content/push/index.textile
index 9e3350fb96..dbea62a9f2 100644
--- a/content/push/index.textile
+++ b/content/push/index.textile
@@ -99,7 +99,6 @@ Each push target device or browser is associated with a unique @deviceId@ and au
The service credential management is handled by the "Ably SDK":https://ably.com/docs/sdks, removing the need for the client application to manage device credentials unless accessing the "push admin API":/docs/api/realtime-sdk/push-admin directly via HTTP.
-
h4(#Error). Error handling
Metachannels, such as @[meta]log:push@, publish events and errors that aren't otherwise available to clients. It's important to note that client-returned errors will not be published to this channel.
diff --git a/content/push/publish.textile b/content/push/publish.textile
index e41e8f5c3a..a1a65f91fc 100644
--- a/content/push/publish.textile
+++ b/content/push/publish.textile
@@ -141,6 +141,8 @@ h2(#direct-publishing). Publish directly
Direct publishing sends push notifications directly to individual devices via the "Ably SDK":https://ably.com/docs/sdks, bypassing the intermediary of channels. This approach delivers personalized or precise notifications customized for individual users. Direct publishing proves beneficial during the transition phase to Ably's platform and when the objective is to engage existing push notification devices.
+Direct publishing is also available in "batch mode":#via-batch-push-api, enabling you to publish to a large number of devices in one request.
+
Push notifications are targeted explicitly towards devices identified by:
* "@deviceId@":#device-id
@@ -163,7 +165,8 @@ var recipient = {
var data = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -178,7 +181,8 @@ var recipient = {
var data = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -193,7 +197,8 @@ recipient = {
data = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 # Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
}
@@ -201,20 +206,18 @@ realtime.push.admin.publish(recipient, data)
```
```[realtime_java]
-
JsonObject payload = JsonUtils.object()
.add("notification", JsonUtils.object()
.add("title", "Hello from Ably!")
.add("body", "Example push notification from Ably.")
- )
- .add("data", JsonUtils.object()
- .add("foo", "bar")
- .add("baz", "qux")
+ .add("ttl", 3600) // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
)
.toJson();
-
-realtime.push.admin.publish(new Param[]{new Param("deviceId", "xxxxxxxxxxxx")}, payload);
+realtime.push.admin.publish(
+ new Param[] { new Param("deviceId", "xxxxxxxxxxxx") },
+ payload
+);
```
```[realtime_python]
@@ -225,7 +228,8 @@ recipient = {
message = {
'notification': {
'title': 'Hello from Ably!',
- 'body': 'Example push notification from Ably.'
+ 'body': 'Example push notification from Ably.',
+ 'ttl': 3600 # Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
}
@@ -243,7 +247,8 @@ var data = new JObject
["notification"] = new JObject
{
{ "title", "Hello from Ably!" },
- { "body", "Example push notification from Ably." }
+ { "body", "Example push notification from Ably." },
+ { "ttl", 3600 } // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -258,7 +263,8 @@ var recipient = {
var data = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -273,7 +279,8 @@ var recipient = {
var data = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -288,7 +295,8 @@ recipient = {
data = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 # Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
}
@@ -300,15 +308,14 @@ JsonObject payload = JsonUtils.object()
.add("notification", JsonUtils.object()
.add("title", "Hello from Ably!")
.add("body", "Example push notification from Ably.")
- )
- .add("data", JsonUtils.object()
- .add("foo", "bar")
- .add("baz", "qux")
+ .add("ttl", 3600) // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
)
.toJson();
-
-rest.push.admin.publish(new Param[]{new Param("deviceId", "xxxxxxxxxxxx")}, payload);
+rest.push.admin.publish(
+ new Param[] { new Param("deviceId", "xxxxxxxxxxxx") },
+ payload
+);
```
```[rest_python]
@@ -319,7 +326,8 @@ recipient = {
message = {
'notification': {
'title': 'Hello from Ably!',
- 'body': 'Example push notification from Ably.'
+ 'body': 'Example push notification from Ably.',
+ 'ttl': 3600 # Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
}
@@ -334,14 +342,15 @@ $recipient = [
$data = [
'notification' => [
'title' => 'Hello from Ably!',
- 'body' => 'Example push notification from Ably.'
+ 'body' => 'Example push notification from Ably.',
+ 'ttl' => 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
]
];
$rest->push->admin->publish($recipient, $data);
-var channel = rest.channels.get('pushenabled:foo');
-channel.publish({'name': 'example', 'data': 'data', 'extras': extras});
+$channel = $rest->channels->get('pushenabled:foo');
+$channel->publish(['name' => 'example', 'data' => 'data', 'extras' => $extras]);
```
```[rest_csharp]
@@ -355,7 +364,8 @@ var data = new JObject
["notification"] = new JObject
{
{ "title", "Hello from Ably!" },
- { "body", "Example push notification from Ably." }
+ { "body", "Example push notification from Ably." },
+ { "ttl", 3600 } // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -372,13 +382,14 @@ The following example publishes a push notification using the @clientId@:
```[realtime_javascript]
var recipient = {
- clientId: 'bob'
+ clientId: 'xxxxxxxxxxxx'
};
var notification = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -387,13 +398,14 @@ realtime.push.admin.publish(recipient, notification);
```[realtime_nodejs]
var recipient = {
- clientId: 'bob'
+ clientId: 'xxxxxxxxxxxx'
};
var notification = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -402,13 +414,14 @@ realtime.push.admin.publish(recipient, notification);
```[realtime_ruby]
recipient = {
- clientId: 'bob'
+ clientId: 'xxxxxxxxxxxx'
}
notification = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 # Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
}
@@ -420,10 +433,7 @@ JsonObject payload = JsonUtils.object()
.add("notification", JsonUtils.object()
.add("title", "Hello from Ably!")
.add("body", "Example push notification from Ably.")
- )
- .add("data", JsonUtils.object()
- .add("foo", "bar")
- .add("baz", "qux")
+ .add("ttl", 3600) // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
)
.toJson();
@@ -438,7 +448,8 @@ recipient = {
message = {
'notification': {
'title': 'Hello from Ably!',
- 'body': 'Example push notification from Ably.'
+ 'body': 'Example push notification from Ably.',
+ 'ttl': 3600 # Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
}
@@ -446,14 +457,15 @@ realtime.push.admin.publish(recipient, message)
```
```[realtime_csharp]
-var recipient = new { clientId = "bob" };
+var recipient = new { clientId = "xxxxxxxxxxxx" };
var notification = new
{
notification = new
{
title = "Hello from Ably!",
- body = "Example push notification from Ably."
+ body = "Example push notification from Ably.",
+ ttl = 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -462,13 +474,14 @@ realtime.Push.Admin.Publish(recipient, notification);
```[rest_javascript]
var recipient = {
- clientId: 'bob'
+ clientId: 'xxxxxxxxxxxx'
};
var notification = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -477,13 +490,14 @@ rest.push.admin.publish(recipient, notification);
```[rest_nodejs]
var recipient = {
- clientId: 'bob'
+ clientId: 'xxxxxxxxxxxx'
};
var notification = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -492,13 +506,14 @@ rest.push.admin.publish(recipient, notification);
```[rest_ruby]
recipient = {
- clientId: 'bob'
+ clientId: 'xxxxxxxxxxxx'
}
notification = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 # Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
}
@@ -510,10 +525,7 @@ JsonObject payload = JsonUtils.object()
.add("notification", JsonUtils.object()
.add("title", "Hello from Ably!")
.add("body", "Example push notification from Ably.")
- )
- .add("data", JsonUtils.object()
- .add("foo", "bar")
- .add("baz", "qux")
+ .add("ttl", 3600) // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
)
.toJson();
@@ -528,7 +540,8 @@ recipient = {
message = {
'notification': {
'title': 'Hello from Ably!',
- 'body': 'Example push notification from Ably.'
+ 'body': 'Example push notification from Ably.',
+ 'ttl': 3600 # Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
}
@@ -543,25 +556,27 @@ $recipient = [
$data = [
'notification' => [
'title' => 'Hello from Ably!',
- 'body' => 'Example push notification from Ably.'
+ 'body' => 'Example push notification from Ably.',
+ 'ttl' => 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
]
];
$rest->push->admin->publish($recipient, $data);
-var channel = rest.channels.get('pushenabled:foo');
-channel.publish({'name': 'example', 'data': 'data', 'extras': extras});
+$channel = $rest->channels->get('pushenabled:foo');
+$channel->publish(['name' => 'example', 'data' => 'data', 'extras' => $extras]);
```
```[rest_csharp]
-var recipient = new { clientId = "bob" };
+var recipient = new { clientId = "xxxxxxxxxxxx" };
var notification = new
{
notification = new
{
title = "Hello from Ably!",
- body = "Example push notification from Ably."
+ body = "Example push notification from Ably.",
+ ttl = 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -586,7 +601,8 @@ var recipient = {
var notification = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -602,7 +618,8 @@ var recipient = {
var notification = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -618,7 +635,8 @@ recipient = {
notification = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 # Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
}
@@ -630,14 +648,16 @@ Message message = new Message("example", "rest data");
message.extras = 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"));
-
-realtime.push.admin.publish(arrayOf(Param("transportType", "apns"), Param("deviceToken", deviceToken)), message);
+ .add("body", "Example push notification from Ably.")
+ .add("ttl", 3600)); // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
-ably.push.admin.publish(new Param[]{new Param("transportType","fcm"),new Param("registrationToken","")},payload);
+realtime.push.admin.publish(
+ new Param[] {
+ new Param("transportType", "apns"),
+ new Param("deviceToken", deviceToken)
+ },
+ message
+);
```
```[realtime_python]
@@ -649,7 +669,8 @@ recipient = {
message = {
'notification': {
'title': 'Hello from Ably!',
- 'body': 'Example push notification from Ably.'
+ 'body': 'Example push notification from Ably.',
+ 'ttl': 3600 # Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
}
@@ -657,14 +678,15 @@ realtime.push.admin.publish(recipient, message)
```
```[realtime_csharp]
-var recipient = new { transport_type: 'apns' };
+var recipient = new { transport_type = "apns", deviceToken = "XXXXXXXXXX" };
var notification = new
{
notification = new
{
title = "Hello from Ably!",
- body = "Example push notification from Ably."
+ body = "Example push notification from Ably.",
+ ttl = 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -680,7 +702,8 @@ var recipient = {
var notification = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -696,7 +719,8 @@ var recipient = {
var notification = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
@@ -712,7 +736,8 @@ recipient = {
notification = {
notification: {
title: 'Hello from Ably!',
- body: 'Example push notification from Ably.'
+ body: 'Example push notification from Ably.',
+ ttl: 3600 # Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
}
@@ -724,12 +749,16 @@ Message message = new Message("example", "rest data");
message.extras = 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"));
+ .add("body", "Example push notification from Ably.")
+ .add("ttl", 3600)); // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
-rest.push.admin.publish(arrayOf(Param("transportType", "apns"), Param("deviceToken", deviceToken)), message);
+rest.push.admin.publish(
+ new Param[] {
+ new Param("transportType", "apns"),
+ new Param("deviceToken", deviceToken)
+ },
+ message
+);
```
```[rest_python]
@@ -741,7 +770,8 @@ recipient = {
message = {
'notification': {
'title': 'Hello from Ably!',
- 'body': 'Example push notification from Ably.'
+ 'body': 'Example push notification from Ably.',
+ 'ttl': 3600 # Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
}
@@ -751,33 +781,100 @@ rest.push.admin.publish(recipient, message)
```[rest_php]
$recipient = [
'transportType' => 'apns',
- 'deviceToken' => 'XXXXXXX',
+ 'deviceToken' => 'XXXXXXX'
+];
$data = [
'notification' => [
'title' => 'Hello from Ably!',
- 'body' => 'Example push notification from Ably.'
+ 'body' => 'Example push notification from Ably.',
+ 'ttl' => 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
]
];
-$rest->push->admin->publish( $recipient, $data );
+$rest->push->admin->publish($recipient, $data);
```
```[rest_csharp]
-var recipient = new { transport_type: 'apns' };
+var recipient = new { transport_type = "apns", deviceToken = "XXXXXXXXXX" };
var notification = new
{
notification = new
{
title = "Hello from Ably!",
- body = "Example push notification from Ably."
+ body = "Example push notification from Ably.",
+ ttl = 3600 // Required for Web Push on some platforms and browsers like Microsoft Edge (WNS)
}
};
rest.Push.Admin.Publish(recipient, notification);
```
+h3(#via-batch-push-api). Publish via batch push API
+
+The batch push API enables you to publish push notifications to multiple devices or browsers in a single request.
+
+This is useful when you need to send a large number of distinct push notifications to multiple recipients. If you are publishing the same notification to multiple recipients, prefer publishing "via channels":#via-channels.
+
+The batch push endpoint accepts a JSON array of @PushPublishSpec@ objects, each of which contain a @recipient@ or array of recipients, and a @payload@, where @payload@ is the same as the payload you would use in a normal direct publish request.
+
+Currently, the batch push endpoint allows a maximum of 10,000 notifications per request (each recipient for a given payload counts as a separate notification).
+
+The following example shows how to publish multiple push notifications in one request using the batch API with the generic REST "@request()@":/docs/api/rest-sdk#request method:
+
+```[rest_javascript]
+await rest.request('POST', '/push/batch/publish', null, [
+ {
+ recipient: {
+ deviceId: 'xxxxxxxxxxx'
+ },
+ payload: {
+ notification: { title: 'Message 1', body: 'Example push notification from Ably.' }
+ }
+ },
+ {
+ recipient: [
+ {
+ deviceId: 'xxxxxxxxxxx'
+ },
+ {
+ deviceId: 'xxxxxxxxxxx',
+ }
+ ],
+ payload: {
+ notification: { title: 'Message 2', body: 'Example push notification from Ably.' }
+ }
+ }
+])
+```
+
+```[rest_nodejs]
+await rest.request('POST', '/push/batch/publish', null, [
+ {
+ recipient: {
+ deviceId: 'xxxxxxxxxxx'
+ },
+ payload: {
+ notification: { title: 'Message 1', body: 'Example push notification from Ably.' }
+ }
+ },
+ {
+ recipient: [
+ {
+ deviceId: 'xxxxxxxxxxx'
+ },
+ {
+ deviceId: 'xxxxxxxxxxx',
+ }
+ ],
+ payload: {
+ notification: { title: 'Message 2', body: 'Example push notification from Ably.' }
+ }
+ }
+])
+```
+
h2(#via-channels). Publish via channels
Publishing via channels is modeled on Ably's "channel":#channels infrastructure, facilitating the delivery of push notifications across a network of subscribed devices or browsers. This process publishes messages through predefined channels, which devices or browsers must "subscribe":#sub-channels to in order to receive updates. This process ensures registered devices or browsers in the specified channels receive the correct push notifications. Publishing via channels is particularly useful for publishing notifications to multiple groups with varying privileges.
diff --git a/data/createPages/index.ts b/data/createPages/index.ts
index b20f250c24..c960e02f77 100644
--- a/data/createPages/index.ts
+++ b/data/createPages/index.ts
@@ -12,11 +12,13 @@ import { DEFAULT_LANGUAGE } from './constants';
import { writeRedirectToConfigFile } from './writeRedirectToConfigFile';
import { siteMetadata } from '../../gatsby-config';
import { GatsbyNode } from 'gatsby';
+import { examples, DEFAULT_EXAMPLE_LANGUAGES } from '../../src/data/examples/';
+import { Example } from '../../src/data/examples/types';
const writeRedirect = writeRedirectToConfigFile('config/nginx-redirects.conf');
const documentTemplate = path.resolve(`src/templates/document.tsx`);
const apiReferenceTemplate = path.resolve(`src/templates/apiReference.tsx`);
-
+const examplesTemplate = path.resolve(`src/templates/examples.tsx`);
interface Edge {
node: {
slug: string;
@@ -70,6 +72,18 @@ interface ApiReferenceQueryResult {
};
}
+interface ExampleQueryResult {
+ allExampleFile: {
+ nodes: {
+ project: string;
+ projectRelativePath: string;
+ language: string;
+ extension: string;
+ content: string;
+ }[];
+ };
+}
+
export const createPages: GatsbyNode['createPages'] = async ({ graphql, actions: { createPage, createRedirect } }) => {
/**
* It's not ideal to have:
@@ -142,6 +156,20 @@ export const createPages: GatsbyNode['createPages'] = async ({ graphql, actions:
}
`);
+ const examplesResult = await graphql(`
+ query {
+ allExampleFile {
+ nodes {
+ project
+ projectRelativePath
+ language
+ extension
+ content
+ }
+ }
+ }
+ `);
+
const retrievePartialFromGraphQL = maybeRetrievePartial(graphql);
const documentCreator =
@@ -201,7 +229,7 @@ export const createPages: GatsbyNode['createPages'] = async ({ graphql, actions:
contentOrderedList,
contentMenu: contentMenuObject,
script,
- layout: { sidebar: true, searchBar: true, template: 'base' },
+ layout: { leftSidebar: true, rightSidebar: true, searchBar: true, template: 'base' },
},
});
return slug;
@@ -214,14 +242,6 @@ export const createPages: GatsbyNode['createPages'] = async ({ graphql, actions:
redirectInBrowser: true,
});
- // TODO: remove when examples are ready to be released
- createRedirect({
- fromPath: '/docs/examples',
- toPath: '/docs',
- isPermanent: true,
- redirectInBrowser: true,
- });
-
if (!documentResult.data) {
throw new Error('Document result is undefined');
}
@@ -230,8 +250,44 @@ export const createPages: GatsbyNode['createPages'] = async ({ graphql, actions:
throw new Error('API reference result is undefined');
}
+ const exampleCreator = async (exampleDatum: Example) => {
+ if (!examplesResult.data) {
+ throw new Error('Examples result is undefined');
+ }
+
+ const relatedFiles = examplesResult.data.allExampleFile.nodes.filter((node) => node.project === exampleDatum.id);
+ const languageFiles: Record> = {};
+
+ for (const language of exampleDatum.languages ?? DEFAULT_EXAMPLE_LANGUAGES) {
+ const filesForLanguage = relatedFiles.reduce>((acc, file) => {
+ if (file.language === language && language === 'react' && file.projectRelativePath.startsWith('src/')) {
+ acc[file.projectRelativePath.replace('src/', '')] = file.content;
+ } else if (file.language === language) {
+ acc[file.projectRelativePath] = file.content;
+ }
+ return acc;
+ }, {});
+ languageFiles[language] = filesForLanguage;
+ }
+
+ const example = {
+ ...exampleDatum,
+ files: languageFiles,
+ };
+
+ createPage({
+ path: `/examples/${example.id}`,
+ component: examplesTemplate,
+ context: {
+ example,
+ layout: { sidebar: false, searchBar: false, template: 'examples' },
+ },
+ });
+ };
+
await Promise.all([
...documentResult.data.allFileHtml.edges.map(documentCreator(documentTemplate)),
...apiReferenceResult.data.allFileHtml.edges.map(documentCreator(apiReferenceTemplate)),
+ ...examples.map(exampleCreator),
]);
};
diff --git a/data/onCreateNode/create-graphql-schema-customization.ts b/data/onCreateNode/create-graphql-schema-customization.ts
index 39e493d1c5..a583b3c3ab 100644
--- a/data/onCreateNode/create-graphql-schema-customization.ts
+++ b/data/onCreateNode/create-graphql-schema-customization.ts
@@ -51,14 +51,13 @@ export const createSchemaCustomization: GatsbyNode['createSchemaCustomization']
oneTrustTest: String
inkeepEnabled: String
inkeepApiKey: String
- inkeepIntegrationId: String
- inkeepOrganizationId: String
insightsEnabled: Boolean
insightsDebug: Boolean
mixpanelApiKey: String
mixpanelAutoCapture: Boolean
posthogApiKey: String
posthogApiHost: String
+ conversationsUrl: String
}
type SiteSiteMetadata implements Node {
diff --git a/data/onCreateNode/index.ts b/data/onCreateNode/index.ts
index ee6add3f3a..b80bfafd9f 100644
--- a/data/onCreateNode/index.ts
+++ b/data/onCreateNode/index.ts
@@ -74,4 +74,32 @@ export const onCreateNode: GatsbyNode['onCreateNode'] = async ({
createNode(fields);
}
}
+
+ if (node.sourceInstanceName === 'examples' && node.extension) {
+ const content = await loadNodeContent(node);
+ const contentDigest = createContentDigest(content);
+ const type = 'ExampleFile';
+ const { relativePath, extension, id } = node;
+ const [project, potentialLanguage] = (relativePath as string).split('/');
+ const language =
+ potentialLanguage && ['react', 'javascript', 'server'].includes(potentialLanguage) ? potentialLanguage : null;
+
+ if (!project || !language) {
+ return;
+ }
+
+ const fields = {
+ id: createNodeId(`${id} >>> Example`),
+ extension,
+ project,
+ language,
+ projectRelativePath: (relativePath as string).replace(`${project}/${language}/`, ''),
+ content,
+ internal: {
+ contentDigest,
+ type,
+ },
+ };
+ createNode(fields);
+ }
};
diff --git a/data/onCreatePage.ts b/data/onCreatePage.ts
index b82aedb854..79eaad029a 100644
--- a/data/onCreatePage.ts
+++ b/data/onCreatePage.ts
@@ -1,27 +1,68 @@
import { GatsbyNode } from 'gatsby';
+import path from 'path';
+import fs from 'fs';
-export type LayoutOptions = { sidebar: boolean; searchBar: boolean; template: string };
+export type LayoutOptions = {
+ leftSidebar: boolean;
+ rightSidebar: boolean;
+ searchBar: boolean;
+ template: string;
+};
+
+const mdxWrapper = path.resolve('src/components/Layout/MDXWrapper.tsx');
const pageLayoutOptions: Record = {
- '/docs': { sidebar: false, searchBar: false, template: 'index' },
- '/docs/api/control-api': { sidebar: false, searchBar: true, template: 'control-api' },
- '/docs/sdks': { sidebar: false, searchBar: true, template: 'sdk' },
- '/docs/examples': { sidebar: false, searchBar: true, template: 'examples' },
- '/docs/how-to/pub-sub': { sidebar: true, searchBar: true, template: 'how-to' },
- '/docs/404': { sidebar: false, searchBar: false, template: '404' },
+ '/docs': { leftSidebar: true, rightSidebar: false, searchBar: true, template: 'index' },
+ '/docs/api/control-api': { leftSidebar: false, rightSidebar: false, searchBar: true, template: 'control-api' },
+ '/docs/sdks': { leftSidebar: false, rightSidebar: false, searchBar: true, template: 'sdk' },
+ '/examples': { leftSidebar: false, rightSidebar: false, searchBar: true, template: 'examples' },
+ '/docs/how-to/pub-sub': { leftSidebar: true, rightSidebar: true, searchBar: true, template: 'how-to' },
+ '/docs/404': { leftSidebar: false, rightSidebar: false, searchBar: false, template: '404' },
};
-export const onCreatePage: GatsbyNode['onCreatePage'] = ({ page, actions }) => {
- const { createPage } = actions;
+// Function to extract code element classes from an MDX file
+const extractCodeLanguages = async (filePath: string): Promise> => {
+ try {
+ // Check if the file exists
+ if (!fs.existsSync(filePath)) {
+ return new Set();
+ }
- const pathOptions = Object.entries(pageLayoutOptions).find(([path]) => page.path === path);
+ // Read the file content
+ const fileContent = fs.readFileSync(filePath, 'utf8');
+
+ // Find all instances of code blocks with language specifiers (```language)
+ const codeBlockRegex = /```(\w+)/g;
+ let match;
+ const languages = new Set();
- if (pathOptions) {
- page.context = {
- ...page.context,
- layout: pathOptions[1],
- };
+ while ((match = codeBlockRegex.exec(fileContent)) !== null) {
+ if (match[1] && match[1].trim()) {
+ languages.add(match[1].trim());
+ }
+ }
+ return languages;
+ } catch (error) {
+ console.error(`Error extracting code element classes from ${filePath}:`, error);
+ return new Set();
+ }
+};
+
+export const onCreatePage: GatsbyNode['onCreatePage'] = async ({ page, actions }) => {
+ const { createPage } = actions;
+ const pathOptions = Object.entries(pageLayoutOptions).find(([path]) => page.path === path);
+ const isMDX = page.component.endsWith('.mdx');
+ const detectedLanguages = isMDX ? await extractCodeLanguages(page.component) : new Set();
- createPage(page);
+ if (pathOptions || isMDX) {
+ createPage({
+ ...page,
+ context: {
+ ...page.context,
+ layout: pathOptions ? pathOptions[1] : { sidebar: true, searchBar: true, template: 'base' },
+ ...(isMDX ? { languages: Array.from(detectedLanguages) } : {}),
+ },
+ component: isMDX ? `${mdxWrapper}?__contentFilePath=${page.component}` : page.component,
+ });
}
};
diff --git a/data/onPostBuild/index.ts b/data/onPostBuild/index.ts
new file mode 100644
index 0000000000..844c0bf000
--- /dev/null
+++ b/data/onPostBuild/index.ts
@@ -0,0 +1,109 @@
+import { GatsbyNode } from 'gatsby';
+import * as path from 'path';
+import * as fs from 'fs';
+
+/**
+ * This script is used to create a file called llms.txt that contains a list of all the pages in the site.
+ * It is heavily inspired by the gatsby-plugin-sitemap plugin, and stripped down to only to what we need.
+ */
+
+const LLMS_TXT_PREAMBLE = `# https://ably.com/docs llms.txt\n`;
+
+const REPORTER_PREFIX = 'onPostBuild:';
+
+interface DocumentQueryResult {
+ site: {
+ siteMetadata: {
+ siteUrl: string;
+ };
+ };
+ allFileHtml: {
+ nodes: {
+ slug: string;
+ meta: {
+ title: string;
+ meta_description: string;
+ };
+ }[];
+ };
+}
+
+const withoutTrailingSlash = (path: string) => (path === `/` ? path : path.replace(/\/$/, ``));
+
+const prefixPath = ({ url, siteUrl, pathPrefix = `` }: { url: string; siteUrl: string; pathPrefix?: string }) => {
+ return new URL(pathPrefix + withoutTrailingSlash(url), siteUrl).toString();
+};
+
+const escapeMarkdown = (text: string) => {
+ // backslash-escape Markdown special chars: \ ` * _ { } [ ] ( ) # + !
+ return text.replace(/([\\`*_{}\[\]()#+!])/g, '\\$1');
+};
+
+export const onPostBuild: GatsbyNode['onPostBuild'] = async ({ graphql, reporter, basePath }) => {
+ const query = `
+ query {
+ site {
+ siteMetadata {
+ siteUrl
+ }
+ }
+
+ allFileHtml(filter: { articleType: { in: ["document", "apiReference"] } }) {
+ nodes {
+ slug
+ meta {
+ title
+ meta_description
+ }
+ }
+ }
+ }
+ `;
+ const { data: queryRecords, errors } = await graphql(query);
+
+ if (errors) {
+ reporter.panicOnBuild(`Error while running GraphQL query.`);
+ throw errors;
+ }
+
+ if (!queryRecords) {
+ reporter.panicOnBuild(`No documents found.`);
+ throw new Error('No documents found.');
+ }
+
+ const siteUrl = queryRecords.site.siteMetadata.siteUrl;
+
+ if (!siteUrl) {
+ reporter.panicOnBuild(`${REPORTER_PREFIX} Site URL not found.`);
+ throw new Error('Site URL not found.');
+ }
+
+ const allPages = queryRecords.allFileHtml.nodes;
+
+ reporter.info(`${REPORTER_PREFIX} Found ${allPages.length} pages to place into llms.txt`);
+
+ const serializedPages = [LLMS_TXT_PREAMBLE];
+
+ for (const page of allPages) {
+ const { slug, meta } = page;
+ const { title, meta_description } = meta;
+
+ try {
+ const url = prefixPath({ url: slug, siteUrl, pathPrefix: basePath });
+ const safeTitle = escapeMarkdown(title);
+ const link = `[${safeTitle}](${url})`;
+ const line = `- ${[link, meta_description].join(': ')}`;
+ serializedPages.push(line);
+ } catch (err) {
+ reporter.panic(`${REPORTER_PREFIX} Error serializing pages`, err as Error);
+ }
+ }
+
+ const llmsTxtPath = path.join(process.cwd(), 'public', 'llms.txt');
+ try {
+ fs.writeFileSync(llmsTxtPath, serializedPages.join('\n'));
+ reporter.info(`${REPORTER_PREFIX} Successfully wrote llms.txt with ${serializedPages.length} pages`);
+ } catch (err) {
+ reporter.panic(`${REPORTER_PREFIX} Error writing llms.txt file`, err as Error);
+ }
+};
diff --git a/data/yaml/page-content/homepage.yaml b/data/yaml/page-content/homepage.yaml
deleted file mode 100644
index 3ceaf0afd9..0000000000
--- a/data/yaml/page-content/homepage.yaml
+++ /dev/null
@@ -1,68 +0,0 @@
-name: Homepage
-meta:
- title: Docs | Ably Realtime
- description: Ably documentation for 25+ web, mobile, and IoT SDKs, quickstart guides and tutorials, and realtime concepts.
- image: https://files.ably.io/website/images/meta-tags/ably-generic%402x.jpg
- twitter: '@ablyrealtime'
-sections:
- - columns: 1
- bottomMargin: 48
- cards:
- - title: Ably Documentation
- type: hero
- content: Realtime experience infrastructure that just works at any scale.
- - columns: 2
- bottomMargin: 80
- cards:
- - title: Platform
- type: feature
- content: Understand the core concepts of Ably's platform, its pricing and integrations.
- image: 'platform.png'
- links:
- - text: Get started
- href: /platform
- - title: Pub/Sub
- type: feature
- content: Quickly create realtime digital experiences with Ably's core building blocks.
- image: 'pub-sub.png'
- links:
- - text: Get started
- href: /basics
- - title: Chat
- type: feature
- content: Quickly add chat features into any application.
- image: 'chat.png'
- links:
- - text: Get started
- href: /chat
- - title: Spaces
- type: feature
- content: Build collaborative environments in just a few lines of code.
- image: 'spaces.png'
- links:
- - text: Get started
- href: /spaces
- - title: LiveSync
- type: feature
- content: Seamlessly sync database changes with frontend clients.
- image: 'livesync.png'
- links:
- - text: Get started
- href: /livesync
- - title: Asset Tracking
- type: feature
- content: Track the location of assets in realtime.
- image: 'asset-tracking.png'
- links:
- - text: Get started
- href: /asset-tracking
- - columns: 1
- bottomMargin: 160
- cards:
- - title: SDKs
- content: Ably SDKs provide a consistent and idiomatic API across a variety of supported platforms.
- image: 'sdks@2x.png'
- type: sdk
- callToAction:
- text: View all SDKs
- href: /sdks
diff --git a/examples/.env.example b/examples/.env.example
index 22af5f7ebd..58243868bb 100644
--- a/examples/.env.example
+++ b/examples/.env.example
@@ -1,2 +1 @@
-VITE_PUBLIC_ABLY_KEY=
-NEXT_PUBLIC_ABLY_KEY=
+VITE_ABLY_KEY=
diff --git a/examples/auth-generate-jwt/javascript/README.md b/examples/auth-generate-jwt/javascript/README.md
index 29399485c1..0d8b0c56c8 100644
--- a/examples/auth-generate-jwt/javascript/README.md
+++ b/examples/auth-generate-jwt/javascript/README.md
@@ -20,56 +20,56 @@ Find out more about [authentication](https://ably.com/docs/auth/token?lang=javas
1. Clone the [Ably docs](https://github.com/ably/docs) repository where this example can be found:
-```sh
-git clone git@github.com:ably/docs.git
-```
+ ```sh
+ git clone git@github.com:ably/docs.git
+ ```
2. Change directory:
-```sh
-cd /examples/
-```
+ ```sh
+ cd /examples/
+ ```
3. Install dependencies:
-```sh
-yarn install
-```
+ ```sh
+ yarn install
+ ```
4. Run the frontend client:
-```sh
-yarn run auth-generate-jwt-javascript
-```
+ ```sh
+ yarn run auth-generate-jwt-javascript
+ ```
5. In a new tab, change directory:
-```sh
-cd /examples/
-```
+ ```sh
+ cd /examples/
+ ```
6. Rename the environment file:
-```sh
-mv .env.example .env.local
-```
+ ```sh
+ mv .env.example .env.local
+ ```
-7. In `.env.local` update the value of `VITE_PUBLIC_ABLY_KEY` to be your Ably API key.
+7. In `.env.local` update the value of `VITE_ABLY_KEY` to be your Ably API key.
8. Install dependencies:
-```sh
-yarn install
-```
+ ```sh
+ yarn install
+ ```
9. Run the backend server:
-```sh
-yarn run auth-generate-jwt-server
-```
+ ```sh
+ yarn run auth-generate-jwt-server
+ ```
10. Try it out by opening a tab to [http://localhost:5173/](http://localhost:5173/) with your browser to see the result.
## Open in CodeSandbox
-In CodeSandbox, rename the `.env.example` file to `.env.local` and update the value of your `VITE_PUBLIC_ABLY_KEY` variable to use your Ably API key.
+In CodeSandbox, rename the `.env.example` file to `.env.local` and update the value of your `VITE_ABLY_KEY` variable to use your Ably API key.
diff --git a/examples/auth-generate-jwt/javascript/postcss.config.ts b/examples/auth-generate-jwt/javascript/postcss.config.ts
deleted file mode 100644
index 5dd35ae633..0000000000
--- a/examples/auth-generate-jwt/javascript/postcss.config.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import baseConfig from '../../postcss.config';
-
-const config = {
- ...baseConfig,
-};
-
-export default config;
diff --git a/examples/auth-generate-jwt/javascript/src/styles.css b/examples/auth-generate-jwt/javascript/src/styles.css
index 3196adf029..8c7be91507 100644
--- a/examples/auth-generate-jwt/javascript/src/styles.css
+++ b/examples/auth-generate-jwt/javascript/src/styles.css
@@ -30,7 +30,7 @@ input {
--card-foreground: 20 14.3% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 20 14.3% 4.1%;
- --primary: 20 91% 54%;
+ --primary: 220 13% 33%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 209 100% 43%;
--secondary-foreground: 60 9.1% 97.8%;
@@ -58,7 +58,7 @@ input {
--card-foreground: 60 9.1% 97.8%;
--popover: 20 14.3% 4.1%;
--popover-foreground: 60 9.1% 97.8%;
- --primary: 20 91% 54%;
+ --primary: 220 13% 33%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 209 100% 43%;
--secondary-foreground: 60 9.1% 97.8%;
diff --git a/examples/auth-generate-jwt/javascript/tailwind.config.ts b/examples/auth-generate-jwt/javascript/tailwind.config.ts
index 238d7ed443..1c86e1c371 100644
--- a/examples/auth-generate-jwt/javascript/tailwind.config.ts
+++ b/examples/auth-generate-jwt/javascript/tailwind.config.ts
@@ -3,6 +3,7 @@ import type { Config } from 'tailwindcss';
const config: Config = {
...baseConfig,
+ content: ['./src/**/*.{js,ts,tsx}', './index.html'],
};
export default config;
diff --git a/examples/auth-generate-jwt/javascript/vite-env.d.ts b/examples/auth-generate-jwt/javascript/vite-env.d.ts
index 9454b92dea..449e61aa75 100644
--- a/examples/auth-generate-jwt/javascript/vite-env.d.ts
+++ b/examples/auth-generate-jwt/javascript/vite-env.d.ts
@@ -1,5 +1,5 @@
interface ImportMetaEnv {
- readonly VITE_PUBLIC_ABLY_KEY: string;
+ readonly VITE_ABLY_KEY: string;
// Add other environment variables here if needed
}
diff --git a/examples/auth-generate-jwt/javascript/vite.config.ts b/examples/auth-generate-jwt/javascript/vite.config.ts
index b7961c3d30..3b1cf13b4f 100644
--- a/examples/auth-generate-jwt/javascript/vite.config.ts
+++ b/examples/auth-generate-jwt/javascript/vite.config.ts
@@ -1,10 +1,7 @@
-import baseConfig from '../../vite.config';
import { defineConfig } from 'vite';
-import dotenv from 'dotenv';
-import path from 'path';
-
-dotenv.config({ path: path.resolve(__dirname, '../../.env.local') });
+import baseConfig from '../../vite.config';
export default defineConfig({
...baseConfig,
+ envDir: '../../',
});
diff --git a/examples/auth-generate-jwt/react/README.md b/examples/auth-generate-jwt/react/README.md
index 495829ed7e..1067730fe9 100644
--- a/examples/auth-generate-jwt/react/README.md
+++ b/examples/auth-generate-jwt/react/README.md
@@ -20,56 +20,56 @@ Find out more about [authentication](https://ably.com/docs/auth/token?lang=javas
1. Clone the [Ably docs](https://github.com/ably/docs) repository where this example can be found:
-```sh
-git clone git@github.com:ably/docs.git
-```
+ ```sh
+ git clone git@github.com:ably/docs.git
+ ```
2. Change directory:
-```sh
-cd /examples/
-```
+ ```sh
+ cd /examples/
+ ```
3. Install dependencies:
-```sh
-yarn install
-```
+ ```sh
+ yarn install
+ ```
4. Run the frontend client:
-```sh
-yarn run auth-generate-jwt-react
-```
+ ```sh
+ yarn run auth-generate-jwt-react
+ ```
5. In a new tab, change directory:
-```sh
-cd /examples/
-```
+ ```sh
+ cd /examples/
+ ```
6. Rename the environment file:
-```sh
-mv .env.example .env.local
-```
+ ```sh
+ mv .env.example .env.local
+ ```
-7. In `.env.local` update the value of `VITE_PUBLIC_ABLY_KEY` to be your Ably API key.
+7. In `.env.local` update the value of `VITE_ABLY_KEY` to be your Ably API key.
8. Install dependencies:
-```sh
-yarn install
-```
+ ```sh
+ yarn install
+ ```
9. Run the backend server:
-```sh
-yarn run auth-generate-jwt-server
-```
+ ```sh
+ yarn run auth-generate-jwt-server
+ ```
10. Try it out by opening a tab to [http://localhost:3000/](http://localhost:3000/) with your browser to see the result.
## Open in CodeSandbox
-In CodeSandbox, rename the `.env.example` file to `.env.local` and update the value of your `VITE_PUBLIC_ABLY_KEY` variable to use your Ably API key.
+In CodeSandbox, rename the `.env.example` file to `.env.local` and update the value of your `VITE_ABLY_KEY` variable to use your Ably API key.
diff --git a/examples/auth-generate-jwt/react/index.html b/examples/auth-generate-jwt/react/index.html
new file mode 100644
index 0000000000..231a299f0e
--- /dev/null
+++ b/examples/auth-generate-jwt/react/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Ably Example
+
+
+
+
+
+
diff --git a/examples/auth-generate-jwt/react/next.config.ts b/examples/auth-generate-jwt/react/next.config.ts
deleted file mode 100644
index 4678774e6d..0000000000
--- a/examples/auth-generate-jwt/react/next.config.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-/** @type {import('next').NextConfig} */
-const nextConfig = {};
-
-export default nextConfig;
diff --git a/examples/auth-generate-jwt/react/package.json b/examples/auth-generate-jwt/react/package.json
index eb6ca6c27f..6e1cc1e7f2 100644
--- a/examples/auth-generate-jwt/react/package.json
+++ b/examples/auth-generate-jwt/react/package.json
@@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
- "dev": "next dev",
- "build": "next build",
- "start": "next start",
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
"lint": "next lint"
}
}
diff --git a/examples/auth-generate-jwt/react/postcss.config.ts b/examples/auth-generate-jwt/react/postcss.config.ts
deleted file mode 100644
index 7b42b36c42..0000000000
--- a/examples/auth-generate-jwt/react/postcss.config.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import baseConfig from '../../next.config';
-import type { NextConfig } from 'next';
-
-const config: NextConfig = {
- ...baseConfig,
-};
-
-export default config;
diff --git a/examples/auth-generate-jwt/react/src/App.tsx b/examples/auth-generate-jwt/react/src/App.tsx
new file mode 100644
index 0000000000..110c1a55d9
--- /dev/null
+++ b/examples/auth-generate-jwt/react/src/App.tsx
@@ -0,0 +1,84 @@
+import React, { useState, useEffect } from 'react';
+import * as Ably from 'ably';
+import { AblyProvider, useConnectionStateListener } from 'ably/react';
+import './styles/styles.css';
+
+interface StatusMessage {
+ id: number;
+ success: boolean;
+ message: string;
+}
+
+interface StatusMessagesProps {
+ messages: StatusMessage[];
+ setMessages: React.Dispatch>;
+}
+
+const StatusMessages = ({ messages, setMessages }: StatusMessagesProps) => {
+ useConnectionStateListener((stateChange) => {
+ if (stateChange.current === 'connected') {
+ setMessages((prevMessages) => prevMessages.map((msg) => (msg.id === 3 ? { ...msg, success: true } : msg)));
+ }
+ });
+
+ return (
+
+
+
+
Authentication to Ably with a JWT
+
The Ably client has been successfully initialized.