Skip to content

ccs-chat-notifications-client

@central-code-station/ccs-chat-notifications-client brings the signed-in user’s live updates and web push to your web app. Its realtime part opens the realtime stream and emits events. Its /push part registers the browser for web push. It has no runtime dependencies.

It does not create conversations, send messages or manage members. Your backend does that with ccs-chat-client, and the user reads and writes with ccs-chat-user-client. Event payloads use the same snake_case shapes: the message of a message.created event is a ChatMessage.

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-notifications-client

GITHUB_PACKAGES_TOKEN is a GitHub token with read:packages that can read the organization’s packages. There are no required peer dependencies. @angular/core, @angular/common and rxjs (for /angular) and react (for /react) are optional: install what your app uses.

Entry point Use
@central-code-station/ccs-chat-notifications-client Plain realtime client.
.../angular Signals and Observables.
.../react Hooks.
.../push and .../push/react Web push.

Each user needs their own Chat token: audience chat-api, scope chat:access, issued to your application’s client. The token for your own API is rejected with 401. See Get the user’s Chat token. The token works only in your application’s context. Any other context answers 403 client_context_mismatch. The library does not sign users in. It only reads the token through getAccessToken when it connects.

User scope: chat:access
live.ts
import { ChatNotificationsClient } from '@central-code-station/ccs-chat-notifications-client';
import { getChatAccessToken } from './fe-guide-chat-token';
const notifications = new ChatNotificationsClient({
chatApiUrl: 'https://chat.centralcodestation.com/api/v1',
tenantId: 'fitness',
applicationId: 'fitness-app',
getAccessToken: getChatAccessToken, // sync or async, read again on every (re)connect
// debug: true, // log the connection lifecycle while you develop
});
const stopListening = notifications.on('message.created', ({ message }) => {
console.log(`${message.sender?.display_name ?? message.sender_user_id}: ${message.body}`);
});
notifications.on('connectionStateChange', (state) => console.log('connection:', state));
notifications.connect();
// While a PUBLIC channel you are not a member of is open:
export function viewChannel(conversationId: string): () => void {
notifications.subscribeToConversation(conversationId);
return () => notifications.unsubscribeFromConversation(conversationId);
}
// On sign-out or unmount:
export function stop(): void {
stopListening();
notifications.disconnect();
}
Member Description
connect() Opens the stream. Does nothing if the client is already started.
disconnect() Closes the stream and stops retries.
subscribeToConversation(id) Adds one conversation’s channel, for a PUBLIC channel you can read but are not a member of. A new call replaces the previous one.
unsubscribeFromConversation(id) Removes it.
on(event, handler) Subscribes to an event. Returns a function that unsubscribes.
connectionState 'disconnected', 'connecting', 'connected' or 'error'.

Handlers receive the payload as the Chat API publishes it, in snake_case, with tenant_id and application_id. See Realtime events for every payload and when it fires.

Event Payload type Fires when
message.created, message.updated MessageCreatedEvent A message is sent or edited. message is the full message.
message.deleted MessageDeletedEvent A message is deleted. Render a tombstone.
reaction.added, reaction.removed ReactionEvent A reaction changes.
typing TypingEvent Another member types. Never stored.
member.added, member.removed MemberAddedEvent, MemberRemovedEvent Membership changes.
conversation.created, .updated, .deleted ConversationChangedEvent A conversation of yours changes.
conversation.access_revoked ConversationEventBase The user lost access. Drop the conversation.
presence.updated PresenceUpdatedEvent A fellow member goes online or offline.
connectionStateChange ConnectionState The connection state changes.
  • connect() fetches a stream credential from the realtime token operation and opens it with EventSource. One connection carries the user’s personal events and their channels, up to 100 channels.
  • The credential lasts at most 10 minutes. The client reopens the stream tokenRefreshBufferSeconds (30 by default) before it expires.
  • When the user creates or is added to a CHANNEL, the client reopens the stream so that channel’s events arrive. You call nothing.
  • A short network drop is retried by the browser. A fatal failure, such as a rejected token, is retried with backoff of 1, 2, 4, 8, 16 and 30 seconds. After that the state stays error. Call disconnect() and then connect() to start again.
  • Reopening the stream is not seamless. Events published in between are not replayed, so reload after each return to connected. See Live updates.
  • Errors never throw into your code. Set debug: true to log them.

ChatNotificationsClientOptions:

Field Required Description
chatApiUrl yes The Chat API base URL, such as https://chat.centralcodestation.com/api/v1.
tenantId yes The tenant to listen in.
applicationId yes The application to listen in.
getAccessToken yes Returns the user’s Chat token, sync or async. Read again on every (re)connect.
fetch no Override fetch for tests or runtimes without one.
eventSourceFactory no Create the stream connection yourself, for tests or a runtime that needs an EventSource polyfill.
tokenRefreshBufferSeconds no Seconds before expiry to reconnect with a fresh credential. Default 30.
debug no Log the connection lifecycle to the console with the prefix [ChatNotificationsClient]. Default false.

To switch tenant or application, disconnect and create a new client.

React Native is not covered. EventSource and the Push API are not universally available there. You can pass eventSourceFactory for realtime, which we have not tested. See Mobile and React Native.

Every entry point 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.