Repository navigation
Conversation
Replace the `ably` dependency with `@ably/pubsub-server`, the split v3 package for servers, so every client the CLI builds declares itself a server: server traffic is exempt from MAU counting. The CLI always connects as a server, whatever it authenticates with. Types come from the same package, which re-exports the core's type surface. `createAblyRestClient()` / `createAblyRealtimeClient()` build clients with its `createHttpClient` / `createRealtimeClient`. `@ably/chat` and `@ably/spaces` move to their canaries built on the v3 core (chat 0.0.0-canary.20261001T1209.783b80e6, spaces 0.0.0-canary.20261001T1221.ca08adce), constructed with `createChatClient` and `createSpacesClient` around the CLI's own realtime client, so rooms and spaces traffic is server traffic too. Nothing depends on `ably` v2 any more. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
feat(mau): construct Pub/Sub clients with @ably/pubsub-server
Every client the CLI built used to mint a random `ably-cli-<uuid8>`, so a 30-command session acted as 30 different clients and left random names in message history and presence. The CLI now resolves its client ID once per process with the precedence --client-id > ABLY_CLIENT_ID > `client.id` in the config > an install default generated once and persisted in the config. Every client, every interactive command and every minted token share it. - Reject "" (which silently fell back to a random ID) and "*" with messages naming where the value came from; deprecate "none". - Under token auth the identity is the token's and is never overridden; a JWT without an `x-ably-clientId` claim is rejected up front. - Show the resolved client ID in the "Using:" banner. - bench suffixes its default identity per process so concurrent bench processes stay under the per-client-ID connection cap. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Resolve the acting identity once per command, bench suffix
included, so the banner shows the ID the clients connect as
- Route explicit --client-id on the token commands through the
shared resolver: "none" now warns as deprecated, and "*" is
allowed so wildcard tokens can still be minted. With an explicit
--client-id the issuing client acts as the token's identity, so
the default identity is never resolved
- Under a wildcard ("*") JWT, act as --client-id, ABLY_CLIENT_ID or
the default client ID, since the token lets its holder choose
- Keep the generated default ID in memory if the config can't be
written, instead of failing every command
- Name the client.id config setting correctly in errors, say which
source a deprecated "none" came from, and update the --client-id
descriptions
Tests cover the banner matching the suffixed ID, wildcard tokens
when minting and connecting, source names in messages, and the
default ID's first-write and read-only-config cases.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis integration PR adds MAU (Monthly Active Users) account support by replacing the Changes
Review Notes
|
There was a problem hiding this comment.
Review: Integration/MAU — SDK migration to @ably/pubsub-server
This is a well-scoped SDK migration PR. The mechanical changes are correct and complete — no leftover ably package imports remain anywhere in src/ or test/. The overall approach is solid.
One item worth flagging
test/helpers/ably-event-emitter.ts — module-level client instantiation with undocumented internal access
The new code creates a real createRealtimeClient(...) call at module import time to reach the internal EventEmitter constructor:
const realtimeClient = createRealtimeClient({
key: "appId.keyId:keySecret",
autoConnect: false,
});
export const EventEmitter = (
realtimeClient.constructor as unknown as { EventEmitter: new () => AblyEventEmitter }
).EventEmitter;
realtimeClient.close();Two concerns:
-
Fragility:
.constructor.EventEmitteris an undocumented SDK internal. The old code accessedAbly.Realtime.EventEmitter(a static on the class — also undocumented, but at least reachable without instantiation). The new pattern's access path breaks silently at runtime if the SDK restructures its class hierarchy — TypeScript won't catch it because the cast tounknown as { EventEmitter: ... }bypasses type checking. A runtimeundefined is not a constructorerror in tests could be hard to trace. -
Module-level side effect: The client is created (and closed) when the module is first imported, not lazily inside a test. While
autoConnect: falseprevents WebSocket connections, the SDK still runs initialization code. This is a minor concern but worth being aware of.
Why it had to change: The old approach worked because ably exported the Realtime constructor as a named export, making Ably.Realtime.EventEmitter directly accessible. @ably/pubsub-server only exports factories (createRealtimeClient), not the class itself, so there's no static property to access without an instance. This is a genuine API surface limitation.
Suggested path: Ask the @ably/pubsub-server team whether EventEmitter can be exported directly, or expose a test helper API. Alternatively, a simple hand-rolled EventEmitter shim in test helpers that satisfies the AblyEventEmitter interface would remove the SDK dependency entirely.
This doesn't block the PR — it's test helper code, not production code, and the behavior should work correctly in practice. But it's worth tracking.
Everything else looks good
- All
new Ably.Rest(...)→createHttpClient(...)✓ - All
new Ably.Realtime(...)→createRealtimeClient(...)✓ - All
new ChatClient(...)→createChatClient(...)✓ - The dynamic-import hack in
spaces-base-command.ts(20+ lines ofmoduleAsRecord.default.default || ...fallbacks) replaced with a clean synchronouscreateSpacesClient()call — net improvement - The
x-ably-clientType: "server"JWT claim added to the E2E revocation test is a correct fix;@ably/pubsub-serveralways connects as a server and token auth enforces this spaces-base-command.createSpacesClientcorrectly changed fromasyncto sync to match the new synchronous factory- Type renames (
Ably.Rest→Ably.PubSubHttpClient,Ably.Realtime→Ably.PubSubRealtimeClient) are consistent throughout
feat(mau): resolve one stable client identity per process
Push and token commands declare their own --client-id to name a target or filter, but the base command read `flags["client-id"]` regardless of which definition supplied it. `ably push devices remove-where --client-id user123` therefore also connected as user123, billing an end user who never connected and misattributing an admin write. The base command now honours --client-id as the CLI's own identity only when the command declares the shared `clientIdFlag` definition. Flag names are unchanged; the eleven local target flags get descriptions that say what they target. A guard test loads every command and fails if any local --client-id reaches the client options. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> � Conflicts: � src/base-command.ts � src/commands/auth/issue-ably-token.ts � src/commands/auth/issue-jwt-token.ts
fix(mau): stop target --client-id flags from becoming the CLI's identity
- Token output always states the identity: `Client ID: anonymous` in human output and `clientId: null` in JSON, rather than omitting it. - `auth issue-jwt-token --client-type server` adds the signed `x-ably-clientType=server` claim, which is the only way a token-authenticated client can be classified as a server. - `auth revoke-token` goes through the SDK's `auth.revokeTokens` instead of a hand-rolled HTTPS call to a hardcoded rest.ably.io, so it honours the configured endpoint and reports per-target failures. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
feat(mau): make token issuance and revocation identity-safe
…them - Add a hint for 40012 (invalid client ID), and make the client ID hints (40012, 40161, 91000) context-aware: under token auth they point at re-issuing the token, and they only suggest --client-id on commands that have it, falling back to ABLY_CLIENT_ID. - Spaces connection and cursor-attach failures keep the Ably error code and status instead of wrapping them in a plain Error. - Long-running commands fail with a non-zero exit when their connection dies mid-run: on `failed`, or when it drops five times in a row without staying connected for 10s, which is how a server-side rejection such as an eviction can present. Previously a subscribe that lost its connection logged a warning and exited 0. - The --duration exit path force-exits before finally() runs, so it now emits the standard `completed` JSON status itself instead of an ad-hoc `complete` line, and prints its human message to stderr. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> � Conflicts: � src/base-command.ts
fix(mau): surface identity and connection failures instead of hiding them
Integration branch collecting the changes required for the next CLI release to
work with MAU-based (monthly active users) Ably accounts. It also brings the
Pub/Sub, Chat and Spaces SDKs up to date, since the newer SDK versions are
needed for MAU-based account support.