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: 2 additions & 2 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- main

env:
BASE_NODE_VERSION: 24
BASE_NODE_VERSION: 22

permissions:
contents: read
Expand Down Expand Up @@ -81,7 +81,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [20, 22, 24]
node-version: [22, 24]
name: test node ${{ matrix.node-version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ jobs:
submodules: true
persist-credentials: false

- name: Use Node.js 20.x
- name: Use Node.js 22.x
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # zizmor: ignore[cache-poisoning] no cache input is set, so npm cache is not enabled
with:
node-version: 20.x
node-version: 22.x

- name: Install Package Dependencies
run: npm ci
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/publish.cdn.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ jobs:
aws-region: us-east-1
role-to-assume: arn:aws:iam::${{ secrets.ABLY_AWS_ACCOUNT_ID_SDK }}:role/prod-ably-sdk-cdn
role-session-name: '${{ github.run_id }}-${{ github.run_number }}'
- name: Use Node.js 20.x
- name: Use Node.js 22.x
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # zizmor: ignore[cache-poisoning] no cache input is set, so npm cache is not enabled
with:
node-version: 20.x
node-version: 22.x
- run: npm ci
- run: npm run build:cdn
- name: Deploy to CDN
env:
RELEASE_TAG: ${{ github.ref_name }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # zizmor: ignore[cache-poisoning] no cache input is set, so npm cache is not enabled
with:
node-version: '20.10.0'
node-version: '22.23.4'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies and publish
env:
Expand Down
17 changes: 9 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ npm run build # Build all (chat, core, react)
npm run build:chat # Main chat bundle
npm run build:core # Core SDK (no React)
npm run build:react # React bindings
npm run build:cdn # CDN bundle with @ably/pubsub-core bundled in (run by the CDN publish workflow)
```

### Test
Expand All @@ -34,7 +35,7 @@ npm test -- test/core/messages.test.ts
npm test -- -t "should send a message"
```

Unit tests mock `ably` via `vi.mock('ably')` with mocks in `__mocks__/`. Integration tests (`*.integration.test.ts`) connect to Ably sandbox - no mocking.
Unit tests mock the Pub/Sub client via `vi.mock('@ably/pubsub-device')` with mocks in `__mocks__/@ably/pubsub-device/`, and build clients with its `createClient()`. Integration tests (`*.integration.test.ts`) connect to Ably sandbox - no mocking.

### Lint & Format
```bash
Expand All @@ -60,16 +61,16 @@ The SDK ships three packages from a single repo (not a monorepo - single `packag

| Export | Source | Output | Dependencies |
|---|---|---|---|
| `@ably/chat` | `src/core/` + `src/index.ts` | `dist/chat/` (ESM + UMD) | `ably` (peer) |
| `@ably/chat` (core only) | `src/core/` | `dist/core/` | `ably` (external) |
| `@ably/chat/react` | `src/react/` | `dist/react/` (ESM + UMD) | `ably`, `react` (peers) |
| `@ably/chat` | `src/core/` + `src/index.ts` | `dist/chat/` (ESM + UMD) | `@ably/pubsub-core` (external) |
| `@ably/chat` (core only) | `src/core/` | `dist/core/` | `@ably/pubsub-core` (external) |
| `@ably/chat/react` | `src/react/` | `dist/react/` (ESM + UMD) | `@ably/pubsub-core` (external), `react` (peer) |

Each has its own `vite.config.ts` using Vite library mode with `vite-plugin-dts` for type generation.

### Core SDK (`src/core/`)

Key class hierarchy:
- **`ChatClient`** → entry point, provides `rooms` collection and manages Ably Realtime connection
- **`ChatClient`** → entry point (interface, created via `createChatClient`; implemented by `DefaultChatClient`), provides `rooms` collection and manages Ably Realtime connection
- **`Rooms`** → manages `Room` instances (get/release)
- **`Room`** → chat room with feature accessors: `messages`, `presence`, `reactions`, `typing`, `occupancy`
- **`RoomLifecycleManager`** → orchestrates room state (attach/detach) coordinating features via `ChannelManager`
Expand All @@ -91,7 +92,7 @@ All public types/interfaces must be exported from `src/core/index.ts` or `src/re
## Coding Conventions

### TypeScript
- Import ably as: `import * as Ably from 'ably'`
- Import the Pub/Sub SDK as: `import * as Ably from '@ably/pubsub-core'`
- Use relative imports within the project
- File extension in imports: `import { Foo } from './foo.js'` (enforced by `import/extensions` rule)
- Prefer arrow functions (enforced by eslint)
Expand Down Expand Up @@ -123,7 +124,7 @@ Code is backed by a feature spec with points like `CHA-M10a`. `@[Testable]@` spe

### Unit Tests
- Framework: Vitest (`describe`, `it`, `expect`)
- Mock ably: `vi.mock('ably')` at top of file
- Mock Pub/Sub: `vi.mock('@ably/pubsub-device')` at top of file, and create clients with `createClient()` from `@ably/pubsub-device`
- Custom matchers from `test/helper/expectations.ts`: `toBeErrorInfo()`, `toThrowErrorInfo()`, `toBeErrorInfoWithCode()`, `toThrowErrorInfoWithCode()`, `toBeErrorInfoWithCauseCode()`
- For async rejection testing: `await expect(() => asyncFn()).rejects.toBeErrorInfo({ code: 40000 })`
- Arrange-Act-Assert pattern; data-driven tests with `.each()`
Expand Down Expand Up @@ -151,7 +152,7 @@ src/react/ React hooks and providers
test/core/ Core SDK tests (unit + integration)
test/react/ React SDK tests
test/helper/ Test utilities, custom matchers, setup
__mocks__/ Module mocks (ably)
__mocks__/ Module mocks (@ably/pubsub-device)
demo/ Demo React app (separate package.json)
ably-common/ Git submodule - shared Ably protocol resources (errors.json)
scripts/ Build and validation scripts
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ This SDK supports the following platforms:
| Platform | Support |
|--------------|---------|
| Browsers | All major desktop and mobile browsers, including Chrome, Firefox, Edge, Safari (iOS/macOS), Opera, and Android. Internet Explorer is not supported. |
| Node.js | Version 20 or newer. |
| Node.js | Version 22 or newer. |
| TypeScript | Fully supported, the library is written in TypeScript. |
| React | Includes providers and hooks for deep integration with the React ecosystem. |
| React Native | All React Native platforms. Issues can be reported or support requested. |
Expand All @@ -57,7 +57,7 @@ The Chat SDK is built on top of the Ably Pub/Sub SDK and uses that to establish
Install the Pub/Sub SDK and the Chat SDK:

```sh
npm install ably @ably/chat
npm install @ably/pubsub-device @ably/chat
```

---
Expand All @@ -69,19 +69,19 @@ npm install ably @ably/chat
The following code connects to Ably's chat service, subscribes to a chat room, and sends a message to that room:

```typescript
import * as Ably from 'ably';
import { ChatClient, RoomStatus, RoomStatusChange } from '@ably/chat';
import { createClient } from '@ably/pubsub-device';
import { createChatClient, RoomStatus, RoomStatusChange } from '@ably/chat';

// Initialize Ably Realtime client
// Note: For client-side applications, token authentication is recommended.
// See: https://ably.com/docs/auth
const realtimeClient = new Ably.Realtime({
const realtimeClient = createClient({
key: '<your-ably-api-key>',
clientId: 'your-client-id',
});

// Create a chat client
const chatClient = new ChatClient(realtimeClient);
const chatClient = createChatClient(realtimeClient);

// Get a chat room
const room = await chatClient.rooms.get('my-room');
Expand Down Expand Up @@ -120,18 +120,18 @@ await room.messages.send({ text: 'Hello, World!' });
For React applications, the SDK provides hooks and providers for seamless integration:

```tsx
import * as Ably from 'ably';
import { ChatClient } from '@ably/chat';
import { createClient } from '@ably/pubsub-device';
import { createChatClient } from '@ably/chat';
import { ChatClientProvider, ChatRoomProvider, useMessages } from '@ably/chat/react';

// Initialize clients
// Note: For client-side applications, token authentication is recommended.
// See: https://ably.com/docs/auth
const realtimeClient = new Ably.Realtime({
const realtimeClient = createClient({
key: '<your-ably-api-key>',
clientId: 'your-client-id',
});
const chatClient = new ChatClient(realtimeClient);
const chatClient = createChatClient(realtimeClient);

// Wrap your app with providers
function App() {
Expand Down
91 changes: 91 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,97 @@

This guide provides detailed instructions on how to upgrade between major versions of the Chat SDK.

## 1.x to 2.0.0

### Chat Now Depends On Ably Pub/Sub v3

**Expected Impact: High**

The Chat SDK no longer uses the `ably` package. It now builds on Ably Pub/Sub v3, and you create the Pub/Sub client that you pass to the Chat SDK with one of:

- `@ably/pubsub-device`, for code that runs on an end user's device, such as a browser or a mobile app.
- `@ably/pubsub-server`, for backend services.

Replace `ably` with one of these packages and create your client with its factory function instead of `new Ably.Realtime()`.

**Before**

```sh
npm install ably @ably/chat
```

```ts
import * as Ably from 'ably';
import { ChatClient } from '@ably/chat';

const realtimeClient = new Ably.Realtime({ key: '<API-key>', clientId: '<client-ID>' });
const chatClient = new ChatClient(realtimeClient);
```

**After**

```sh
npm install @ably/pubsub-device @ably/chat
```

```ts
import { createClient } from '@ably/pubsub-device';
import { createChatClient } from '@ably/chat';

const pubsubClient = createClient({ key: '<API-key>', clientId: '<client-ID>' });
const chatClient = createChatClient(pubsubClient);
```

On a server, install `@ably/pubsub-server` instead and create the client with its `createRealtimeClient` function.

In React, `AblyProvider` from `ably/react` is replaced by `PubSubClientProvider` from `@ably/pubsub-device/react`, which takes the client the same way: `<PubSubClientProvider client={pubsubClient}>`.

### Node.js 22 Or Newer Is Required

**Expected Impact: Medium**

The minimum supported Node.js version is now 22, the oldest Node.js LTS release line that is still supported. Node.js 20 is no longer supported.

### ChatClient Is Now Created With `createChatClient`

**Expected Impact: High**

`ChatClient` is now an interface rather than a class, so it can no longer be constructed with `new`. Use the `createChatClient` factory function instead; it takes the same arguments. `ChatClient` remains available as a type.

**Before**

```ts
import { ChatClient } from '@ably/chat';

const chatClient = new ChatClient(pubsubClient, { logLevel: LogLevel.Debug });
```

**After**

```ts
import { ChatClient, createChatClient } from '@ably/chat';

const chatClient: ChatClient = createChatClient(pubsubClient, { logLevel: LogLevel.Debug });
```

### `ChatClient.realtime` Is Renamed To `ChatClient.pubsubClient`

**Expected Impact: Low**

The getter that returns the underlying Pub/Sub client has been renamed from `realtime` to `pubsubClient`.

**Before**

```ts
const pubsubClient = chatClient.realtime;
```

**After**

```ts
const pubsubClient = chatClient.pubsubClient;
```

## <=1.0.0 to 1.1.0

### Additional Connection Status Enum Values
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as Ably from 'ably';
import * as Ably from '@ably/pubsub-core';

const MOCK_CLIENT_ID = 'MOCK_CLIENT_ID';

Expand Down Expand Up @@ -46,7 +46,7 @@ type anyType = ((arg: unknown) => void)[];
type eventType = { [event: string]: ((arg: unknown) => void)[] };

function createMockEmitter() {
return new (Ably.Realtime as any).EventEmitter();
return new Ably.EventEmitter();
}

function createMockChannel(name: string) {
Expand Down Expand Up @@ -155,10 +155,11 @@ class MockRealtime {
}

public request() {}

static EventEmitter = (Ably.Realtime as any).EventEmitter;
}

class MockErrorInfo extends Ably.ErrorInfo {}
const createClient = (options: Ably.ClientOptions | string): Ably.PubSubRealtimeClient =>
new MockRealtime(typeof options === 'string' ? {} : options) as unknown as Ably.PubSubRealtimeClient;

const ErrorInfo = Ably.ErrorInfo;

export { MockErrorInfo as ErrorInfo, MockRealtime as Realtime };
export { createClient, ErrorInfo };
Loading
Loading