Skip to content

ccs-chat-user-client

@central-code-station/ccs-chat-user-client makes the signed-in user’s own Chat calls from your web or mobile app. It lists conversations, reads and sends messages, edits and deletes them, reacts, marks read, signals typing and reports presence. It is promise-based, framework-agnostic and has no runtime dependencies.

An embedded Chat integration has three parts. Your backend creates conversations with ccs-chat-client. Your app reads and writes in them with this package, using the user’s token with scope chat:access. Live updates and push come from ccs-chat-notifications-client.

The package is private on GitHub Packages. Point the scope at it in your .npmrc, then install. See SDKs overview for how to get access.

.npmrc
@central-code-station:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_TOKEN}
Terminal window
npm install @central-code-station/ccs-chat-user-client

GITHUB_PACKAGES_TOKEN is a GitHub token with read:packages that can read the organization’s packages. The package uses only the global fetch, so it runs in browsers and Node.js 20 or later. The Angular and React adapters need @angular/core and rxjs, or react. These are optional peer dependencies: install only what your app uses.

You need the Chat API URL, the tenant id, the application id and a getAccessToken function. Chat accepts a user token only when its audience is chat-api and its scope contains chat:access. The library never signs a user in. Your backend swaps the user’s token for a Chat token and your app returns it from getAccessToken. See Get the user’s Chat token.

The token lasts at most 10 minutes and has no refresh token, so return a cached token and fetch a new one shortly before it expires. A token works only in your application’s context. Any other context answers 403 client_context_mismatch. To switch tenant or application, create a new client.

User scope: chat:access
chat.ts
import { ChatUserClient, ChatUnprocessableError } from '@central-code-station/ccs-chat-user-client';
import { getChatAccessToken } from './fe-guide-chat-token';
const chat = new ChatUserClient({
chatApiUrl: 'https://chat.centralcodestation.com/api/v1',
tenantId: 'fitness',
applicationId: 'fitness-app',
getAccessToken: getChatAccessToken, // sync or async, called before every request
});
export async function morning(conversationId: string): Promise<void> {
const { capabilities } = await chat.capabilities();
console.log('can start chats:', capabilities.includes('conversations.create'));
const { items, next_cursor } = await chat.conversations.list({ limit: 30 }); // most recently active first
console.log(items.length, next_cursor);
const page = await chat.messages.list(conversationId, { limit: 50 }); // newest first
try {
await chat.messages.send(conversationId, { body: 'See you at 9!' });
} catch (error) {
if (error instanceof ChatUnprocessableError && error.code === 'conversation_archived') return;
throw error;
}
await chat.messages.markRead(conversationId, page.items[0]?.message_id);
await chat.presence.heartbeat();
console.log(await chat.presence.get(['coach-ana', 'client-john']));
}

With ccs-chat-notifications-client on the same token, append each message.created event’s message to the open thread. It has the same ChatMessage shape as the items of messages.list.

When your backend owns who may talk to whom, give your roles only the permissions they need inside existing conversations: conversations.read, messages.read, messages.send and messages.update. Leave out conversations.create, groups.create and users.lookup, because a permission applies to every holder of the role. See Grant permissions to roles.

Chat checks every call against the user’s permissions and membership. Types are the API’s snake_case wire format, the same as ccs-chat-client and the realtime events. See the generated reference for every type.

Resource Method Calls Needs
me() Get my profile Cached for the life of the client.
capabilities() Application capabilities Returns { roles, capabilities }.
conversations list({ type?, before?, limit? }) List conversations Members’ conversations plus PUBLIC channels.
get(id) Get conversation
create(input) Create conversation conversations.create, and groups.create or channels.create.
update(id, patch) Update conversation GROUP or CHANNEL owner or admin, or groups.manage or channels.manage.
delete(id) Delete conversation conversations.delete.
listMembers(id, options?) List members
addMember(id, userId) / removeMember(id, userId) Add member, Remove member Per the GROUP and CHANNEL rules.
join(id) / leave(id) Add member, Remove member, for the user’s own id join a PUBLIC channel needs conversations.read. leave is always allowed.
messages list(id, options?) List messages Newest first.
send(id, input) Send a message messages.send.
edit(id, messageId, body) / delete(id, messageId) Edit, Delete The author with messages.update, or a moderator with messages.delete.
addReaction / removeReaction Add, Remove Users only.
markRead(id, messageId?) / typing(id) Mark read, Signal typing Users only.
users lookup(query) Look up users users.lookup.
presence heartbeat() / offline() / get(userIds) Heartbeat, Go offline, Presence Tenant-wide.

Lists return { items, next_cursor }. A page holds 50 items by default and 100 items at most.

ChatUserClientOptions:

Field Required Description
chatApiUrl yes The Chat API base URL, such as https://chat.centralcodestation.com/api/v1.
tenantId yes The tenant the user is acting in.
applicationId yes Your tenant application, the one whose client issued the token.
getAccessToken yes Returns the user’s Chat token, sync or async. Called before every request.
fetch no Override fetch for tests or runtimes without one.

Every failure is a ChatApiError with status, code and body. The subclasses are:

Class Status Typical codes
ChatUnauthorizedError 401 invalid_token, or no_access_token when getAccessToken returned nothing.
ChatForbiddenError 403 missing_capability, not_conversation_member, client_context_mismatch.
ChatNotFoundError 404 not_found.
ChatUnprocessableError 422 conversation_archived, invalid_reply.
ChatRateLimitedError 429 rate_limited, with retryAfterSeconds.

See Troubleshooting.

The package ships ES modules and CommonJS with type declarations for each. import loads dist/esm, require() loads dist/cjs. It works with any bundler and with the node16, nodenext, bundler and legacy node module resolution. Load it one way per app: the two builds are separate copies, so instanceof checks do not cross them.