Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/data/nav/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ export default {
name: 'Livestream chat',
link: '/docs/guides/chat/build-livestream',
},
{
name: 'Handling discontinuity',
link: '/docs/guides/chat/handling-discontinuity',
},
],
},
],
Expand Down
4 changes: 4 additions & 0 deletions src/data/nav/pubsub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,10 @@ export default {
name: 'Dashboards and visualizations',
link: '/docs/guides/pub-sub/dashboards-and-visualizations',
},
{
name: 'Handling discontinuity',
link: '/docs/guides/pub-sub/handling-discontinuity',
},
],
},
],
Expand Down
58 changes: 58 additions & 0 deletions src/pages/docs/chat/rooms/messages.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,64 @@ Messages are sent to users as soon as they [attach](/docs/chat/rooms#attach) to
The [`detach()`](/docs/chat/rooms#detach) method detaches a user from the room. At that point a user will no longer receive any messages that are sent to the room.
</Aside>

## Handle discontinuity <a id="discontinuity"/>

If a client experiences a period of disconnection longer than two minutes, some messages may be missed. The Chat SDK provides a Chat-specific `onDiscontinuity()` handler at the room level to detect when message continuity is lost, so that you can recover missed messages using [history](/docs/chat/rooms/history).

Register the handler when setting up your room:

<Code>
```javascript
const { off } = room.onDiscontinuity((reason) => {
console.log('Discontinuity detected:', reason);
// Re-fetch messages from the point of re-subscription
room.messages.historyBeforeSubscribe({ limit: 50 }).then((history) => {
// Refresh your message list with recovered messages
});
});

// To remove the listener
off();
```

```react
import { useMessages } from '@ably/chat/react';

const MyComponent = () => {
useMessages({
onDiscontinuity: (error) => {
console.log('Discontinuity detected:', error);
// Trigger recovery, for example re-fetch message history
},
});

return <div>...</div>;
};
```

```swift
let subscription = room.onDiscontinuity()
for await error in subscription {
print("Discontinuity detected: \(error)")
// Fetch missed messages and merge into your message list
}
```

```kotlin
val (off) = room.onDiscontinuity { reason: ErrorInfo ->
println("Discontinuity detected: $reason")
// Fetch missed messages and merge into your message list
}

// To remove the listener
off()
```
</Code>

<Aside data-type='note'>
`onDiscontinuity` is a Chat-specific handler. It fires at the room level, covering messages, presence, reactions, and typing indicators. Do not use the Pub/Sub `resumed` flag directly on Chat room channels. For complete recovery patterns covering both Chat and Pub/Sub, see the [handling discontinuity](/docs/guides/handling-discontinuity) guide.
</Aside>

## Send a message <a id="send"/>

<If lang="javascript,swift,kotlin,android">
Expand Down
109 changes: 109 additions & 0 deletions src/pages/docs/guides/chat/handling-discontinuity.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
title: "Guide: Handle discontinuity in Chat"
meta_description: "Detect and recover from message discontinuity in Ably Chat applications. Learn to use the onDiscontinuity handler and historyBeforeSubscribe to recover missed messages."
meta_keywords: "discontinuity, message continuity, onDiscontinuity, Chat, message recovery, missed messages, historyBeforeSubscribe"
---

When a client experiences a period of disconnection longer than the two-minute recovery window, or when Ably signals a loss of message continuity, your application may have missed messages. This is called a *discontinuity*. This guide explains how to detect and recover from discontinuities in Chat applications.

<Aside data-type="note">
If you are using [Pub/Sub](/docs/pub-sub) directly, see the [Pub/Sub discontinuity guide](/docs/guides/pub-sub/handling-discontinuity) instead. Pub/Sub uses the `resumed` flag rather than the Chat `onDiscontinuity()` handler.
</Aside>

## What causes discontinuity <a id="causes"/>

Discontinuity occurs when the Ably SDK cannot guarantee that all messages have been delivered to the client. The most common causes are:

- Network disconnection lasting longer than two minutes. Ably preserves [connection state](/docs/connect/states#connection-state-recovery) for up to two minutes. Beyond this window, Ably cannot guarantee message continuity.
- Server-initiated continuity loss. Operational events such as cluster rebalancing may cause a partial loss of message continuity, even if the client remained connected.
- Outbound rate limits exceeded. If a connection's outbound message rate exceeds the [per-connection limit](/docs/platform/pricing/limits#connection), messages may be dropped, resulting in a loss of continuity.
- Client app backgrounded for an extended period. Mobile apps suspended by the operating system may exceed the two-minute recovery window.

For disconnections shorter than two minutes, the SDK automatically [resumes](/docs/connect/states#resume) the connection and replays missed messages without any action from you.

## Detect discontinuity <a id="detect"/>

The [Chat SDK](/docs/chat) provides an `onDiscontinuity()` handler at the room level. This is a Chat-specific mechanism, separate from the Pub/Sub `resumed` flag.

<Aside data-type="important">
Chat is a separate SDK built on top of Pub/Sub. Use the Chat-specific `onDiscontinuity()` handler when working with Chat rooms. Do not use the Pub/Sub `resumed` flag directly on the underlying Pub/Sub channels used by Chat rooms.
</Aside>

Register the handler when setting up your room:

<Code>
```javascript
const { off } = room.onDiscontinuity((reason) => {
console.log('Discontinuity detected:', reason);
recoverChatMessages(room);
});

// Clean up when done
off();
```

```react
import { useMessages } from '@ably/chat/react';

const MyComponent = () => {
useMessages({
onDiscontinuity: (error) => {
console.log('Discontinuity detected:', error);
// Trigger recovery, for example re-fetch message history
},
});

return <div>...</div>;
};
```

```swift
let subscription = room.onDiscontinuity()
for await error in subscription {
print("Discontinuity detected: \(error)")
// Recover missed messages
}
```

```kotlin
val (off) = room.onDiscontinuity { reason: ErrorInfo ->
println("Discontinuity detected: $reason")
// Recover missed messages
}

// Clean up when done
off()
```
</Code>

<Aside data-type="further-reading">
See [handle connection discontinuity](/docs/chat/connect#discontinuity) for full details on the Chat `onDiscontinuity()` handler, including cleanup and React hook integration.
</Aside>

## Recover missed messages <a id="recover"/>

Use [`historyBeforeSubscribe()`](/docs/chat/rooms/history#subscribe) to retrieve messages from the point of re-subscription. This is preferred over `messages.history()` for discontinuity recovery because the attachment point changes after a resume, and `historyBeforeSubscribe` guarantees no gap between historical and live messages:

<Code>
```javascript
async function recoverChatMessages(room) {
const history = await room.messages.historyBeforeSubscribe({ limit: 50 });

// Refresh your message list with recovered messages
for (const msg of history.items.reverse()) {
appendMessageToUI(msg);
}
}
```
</Code>

<Aside data-type="important">
During a discontinuity, message updates and deletes may have occurred that are not captured by simply appending new messages to the UI. To ensure a consistent state, refresh the entire Chat message list rather than merging new messages into the existing state.
</Aside>

## Best practices <a id="best-practices"/>

- Set up the `onDiscontinuity` handler before subscribing to messages. This ensures you detect any continuity loss that occurs during the initial attachment.
- Use `historyBeforeSubscribe()` for recovery. It is designed to work with the Chat discontinuity detection mechanism and guarantees no gap between historical and live messages.
- The `onDiscontinuity` handler fires at the room level, covering messages, presence, reactions, and typing indicators. You do not need to register separate handlers for each Chat feature.
- Decide how to present recovered messages to the user. Options include refreshing the message list, showing a "new messages" indicator, or displaying a notification that messages were recovered.
94 changes: 94 additions & 0 deletions src/pages/docs/guides/pub-sub/handling-discontinuity.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
title: "Guide: Handle discontinuity in Pub/Sub"
meta_description: "Detect and recover from message discontinuity in Ably Pub/Sub applications. Learn to use the resumed flag and history API to recover missed messages."
meta_keywords: "discontinuity, message continuity, resumed flag, message recovery, missed messages, reconnection, history, untilAttach"
redirect_from:
- /docs/guides/handling-discontinuity
---

When a client experiences a period of disconnection longer than the two-minute recovery window, or when Ably signals a loss of message continuity, your application may have missed messages. This is called a *discontinuity*. This guide explains how to detect and recover from discontinuities in Pub/Sub applications.

<Aside data-type="note">
If you are using the [Chat SDK](/docs/chat), see the [Chat discontinuity guide](/docs/guides/chat/handling-discontinuity) instead. Chat has its own discontinuity handling mechanism.
</Aside>

## What causes discontinuity <a id="causes"/>

Discontinuity occurs when the Ably SDK cannot guarantee that all messages have been delivered to the client. The most common causes are:

- Network disconnection lasting longer than two minutes. Ably preserves [connection state](/docs/connect/states#connection-state-recovery) for up to two minutes. Beyond this window, Ably cannot guarantee message continuity.
- Server-initiated continuity loss. Operational events such as cluster rebalancing may cause a partial loss of message continuity, even if the client remained connected.
- Outbound rate limits exceeded. If a connection's outbound message rate exceeds the [per-connection limit](/docs/platform/pricing/limits#connection), messages may be dropped, resulting in a loss of continuity.
- Client app backgrounded for an extended period. Mobile apps suspended by the operating system may exceed the two-minute recovery window.

For disconnections shorter than two minutes, the SDK automatically [resumes](/docs/connect/states#resume) the connection and replays missed messages without any action from you.

## Detect discontinuity <a id="detect"/>

When continuity is lost on a Pub/Sub channel, the client receives an `ATTACHED` or [`UPDATE`](/docs/channels/states#update) event with the `resumed` flag set to `false`. This flag is part of the [`ChannelStateChange`](/docs/api/realtime-sdk/types#channel-state-change) object.

Register listeners for both `attached` and `update` events to detect all discontinuity scenarios:

<Code>
```javascript
channel.on('attached', (stateChange) => {
if (!stateChange.resumed) {
// Continuity was lost - messages may have been missed
recoverMissedMessages(channel);
}
});

channel.on('update', (stateChange) => {
if (!stateChange.resumed) {
// Mid-session continuity loss
recoverMissedMessages(channel);
}
});
```
</Code>

- An `attached` event with `resumed` set to `false` occurs when a channel reattaches after the connection was [suspended](/docs/connect/states#connection-state-recovery), for example after a disconnection longer than two minutes.
- An `update` event with `resumed` set to `false` occurs when there is a partial loss of continuity on a channel that remains attached, such as after a partially successful [resume](/docs/connect/states#resume).

<Aside data-type="further-reading">
See [channel states](/docs/channels/states) for a full description of channel lifecycle events and the `resumed` flag.
</Aside>

## Recover missed messages <a id="recover"/>

Use [`channel.history()`](/docs/storage-history/history#until-attach) with `untilAttach` set to `true` to retrieve messages up to the point of reattachment. The response is paginated, so you may need to iterate through multiple pages. You are responsible for determining how far back to go, for example by using time bounds or checking message serials against the last message you processed:

<Code>
```javascript
async function recoverMissedMessages(channel, lastSeenSerial) {
let page = await channel.history({ untilAttach: true });

while (page) {
for (const msg of page.items) {
if (msg.serial <= lastSeenSerial) {
// Reached messages already processed
return;
}
processMessage(msg);
}

page = page.hasNext() ? await page.next() : null;
}
}
```
</Code>

<Aside data-type="note">
The `untilAttach` parameter sets an upper bound on the history query at the point of reattachment. It does not set a lower bound. You must determine how far back to paginate, for example by tracking the serial of the last message you processed before the disconnection.
</Aside>

<Aside data-type="important">
Subscribe to the channel before making a history request with `untilAttach` set to `true`. By default, calling `subscribe()` implicitly attaches the channel (when `attachOnSubscribe` is `true`, which is the default), which populates the serial number used by the `untilAttach` parameter. If you have set `attachOnSubscribe` to `false`, you must explicitly call `channel.attach()` before making the history request.
</Aside>

## Best practices <a id="best-practices"/>

- Set up discontinuity handlers before subscribing to messages or attaching to channels. This ensures you detect any continuity loss that occurs during the initial attachment.
- Use `untilAttach: true` with `channel.history()` for recovery. This is designed to work with the `resumed` flag detection mechanism.
- History results may overlap with messages already received via the live subscription. Design your message processing to tolerate duplicates, for example by tracking message IDs or serials.
- Decide how to present recovered messages to the user. Options include silently inserting them into the message list, showing a "new messages" indicator, or displaying a notification that messages were recovered.