@per_moeller/tcx-realtime-client
v1.0.34
Published
Telecom X Realtime Client
Readme
@per_moeller/tcx-realtime-client
TypeScript client for the Telecom X realtime event channel. It wraps a socket.io connection and exposes the server's event stream as strongly-typed event emitters, so your application can react to configuration changes, PBX activity, calls, SIP registrations and more — as they happen.
Used by, among others, Communicator Desktop.
- Automatic reconnect and automatic re-subscription after network outages
- Seven typed event emitters (mitt) — one per event channel
- Point-to-point request/reply messaging between realtime clients
- Full TypeScript definitions for all event payloads, importable from the package root
Payload documentation: This README describes how to use the client. The authoritative documentation for the format and meaning of every realtime event payload is on the Telecom X wiki: https://wiki.telecomx.dk/doku.php?id=realtime
Installation
The package is published to npm as a private scoped package. You need to be authenticated against the npm registry with an account that has been granted access:
npm install @per_moeller/tcx-realtime-clientThe package ships compiled CommonJS with type declarations and works from both require and import.
Prerequisites
- Node.js (or Electron). The library targets ES2022.
- A Telecom X API token from a validated login — see https://wiki.telecomx.dk/doku.php?id=api:auth:login. The token determines which channels you are allowed to subscribe to (see Access levels).
Quick start
import Realtime, { SubscribeType } from '@per_moeller/tcx-realtime-client'
const realtime = new Realtime(
'https://api.telecomx.dk', // server URL
'my-unique-instance-id', // unique per client installation
'My App 1.0.0 on Linux', // client identification string
'my-api-token' // token from a validated login
)
// Subscribe once connected and authenticated
realtime.globalEvents.on('Connected', () => {
realtime.subscribe(SubscribeType.PBX, '52f4fd7734697b28ccaf77ec')
realtime.subscribe(SubscribeType.CONFIG, '52f4fd7734697b28ccaf77ec', ['EMPLOYEE_UPDATED'])
})
// React to events
realtime.pbxEvents.on('CALL_START', event => {
console.log(`Call started on extension ${event.extension}`)
})
realtime.configEvents.on('EMPLOYEE_UPDATED', event => {
console.log(`Employee ${event.id} was updated`)
})
await realtime.start()Call realtime.stop() to disconnect, or realtime.restart() to cycle the connection.
The Realtime class
Constructor
new Realtime(server, instanceId, client, token, logger?)| Parameter | Type | Description |
|---|---|---|
| server | string | Server URL, e.g. https://api.telecomx.dk. A URL containing api.telecomx.dk is automatically rewritten to realtime.telecomx.dk. |
| instanceId | string | Unique ID for this client instance on this machine. Identifies the instance among an employee's realtime clients, and is the address used by request/reply messaging. |
| client | string | Client identification string, e.g. Communicator Desktop 4.0.0-30 on MacOS. Shown in the Telecom X administration and helps debugging. |
| token | string \| null | Telecom X API token. Pass null if not yet known — you can supply it later via start(token). |
| logger | Logger (optional) | Logger implementing debug, info, warning and error (each taking a string). Defaults to a no-op logger. |
Methods
| Method | Returns | Description |
|---|---|---|
| start(token?, client?) | Promise<boolean> | Connects and authenticates. Optionally sets/replaces the token and client string first. Resolves false if no token is available, otherwise true once the connection attempt is initiated. |
| stop() | void | Disconnects and clears all subscriptions. Emits Disconnected with true (terminal — no reconnection). |
| restart() | Promise<void> | stop() followed by start(). |
| subscribe(type, customer?, events?) | void | Subscribe to a channel. See Subscriptions. Throws if not connected. |
| unsubscribe(type, customer?, events?) | void | Unsubscribe. All parameters must match the original subscription. Throws if not connected. |
| sendRequest(...) | Promise<string \| undefined> | Send a request to another realtime client. See Request / reply. |
| sendReply(...) | Promise<boolean> | Reply to a received request. |
| command(command, data) | void | Send an administrative command to the server, e.g. REALTIME_CLIENT_DISCONNECT with { server, _id }. Throws if not connected. |
Properties
| Property | Type | Description |
|---|---|---|
| connected | boolean | true while connected and authenticated. |
| run | boolean | true between start() and stop(). |
| globalEvents, configEvents, callEvents, registerEvents, trunkEvents, pbxEvents, requestReplyEvents | Emitter<...> | The typed event emitters — see Receiving events. |
Connection behaviour
- Transport is WebSocket (no HTTP long-polling fallback).
- On connect, the client authenticates with a custom
authenticatehandshake carrying the token, client string, instance ID and the machine's public IP (looked up viahttps://api.telecomx.dk/tools/myip; failures are silently ignored). - On connection loss, socket.io reconnects automatically and the client re-authenticates and re-subscribes to every active subscription — you do not need to handle outages yourself. With more than 10 subscriptions, re-subscribes are throttled to avoid flooding the server.
- Subscribing to a channel you are already subscribed to does not create a duplicate; the client just re-emits
Subscribed.
Subscriptions
realtime.subscribe(type, customer?, events?)type— aSubscribeType(see below).customer— the customer ID the subscription applies to. Required for customer-scoped types, not allowed forADMIN*types.events— optional whitelist of event names to receive (customer-scoped subscriptions only), e.g.['CALL_START', 'CALL_END']. Omit to receive all events on the channel.
Subscription types and access levels
| SubscribeType | Scope | Minimum access level | Description |
|---|---|---|---|
| PBX | customer | PERSONAL | PBX events (calls, queues, presence, voicemail, …) for one customer |
| CONFIG | customer | PERSONAL | Configuration events for one customer |
| CONFIG-NOT-IPTV | customer | PERSONAL | Configuration events for one customer, excluding IPTV events |
| CALL | customer | VIEWER | Call events for inbound/outbound calls on one customer |
| REGISTER | customer | VIEWER | SIP trunk / SIP phone registration events on one customer |
| TRUNK | — | | SIP trunk session events |
| ADMIN | global | ADMIN | Everything — the firehose |
| ADMIN:CONFIG | global | ADMIN | All configuration events |
| ADMIN:CALL | global | ADMIN | All call events |
| ADMIN:REGISTER | global | ADMIN | All registration events |
| ADMIN:PBX | global | ADMIN | All PBX events |
| ADMIN:REALTIME | global | ADMIN | Realtime client connect/disconnect events |
Access levels are tied to the employee behind the token (EmployeeAccessLevel). If a subscription is rejected, globalEvents emits Forbidden with a message.
Receiving events
Each channel has its own mitt emitter. Subscribe with .on(eventName, handler), unsubscribe with .off(...), or listen to everything on a channel with the '*' wildcard:
realtime.pbxEvents.on('*', (eventName, payload) => {
console.log(`PBX event: ${eventName}`, payload)
})Every payload carries an event field with its own name, plus channel-specific fields — see the wiki for the exact format of each payload, and the bundled .d.ts files for the same information as TypeScript types.
globalEvents — connection state
| Event | Payload | Description |
|---|---|---|
| Connected | — | Connected to the server and authenticated |
| Disconnected | boolean | Disconnected. true = terminal (after stop()), false = connection lost, reconnection will be attempted |
| Subscribed | SubscribedResponse | A subscription was confirmed by the server |
| Unsubscribed | SubscribedResponse | An unsubscription was confirmed |
| AccessDenied | — | Connection rejected due to an invalid token |
| Forbidden | string \| null | The last subscribe/unsubscribe was rejected due to insufficient access level |
configEvents — configuration changes
Emitted for CONFIG, CONFIG-NOT-IPTV and ADMIN:* subscriptions. Payloads follow ConfigEventCommon (event, customer, employee, id, data), with typed extensions for some events. There are ~200 event names covering:
customers & employees, SIP/MVNO accounts, portings & numbers, products & destinations, roaming, SBC (peers, servers, hosts, LCR rules), fraud lists, invoicing, Call Enhancer, presence proxies, IPTV (channels, packages, devices, recordings, …), internet accounts, PBX dialplan objects (groups, extensions, conferences, trunks, queues, time/URL/value/menu routers, playbacks, audio, music-on-hold), phonebook & calendar, SIP phones, dashboards & app settings, beacons, DNS, jobs, helpcenter, ZeroTier, realtime client connections and chat.
The complete list with payload fields is on the wiki; the TypeScript map is EventEmitterConfigEvents.
pbxEvents — hosted PBX activity
Emitted for PBX and ADMIN:PBX/ADMIN subscriptions:
CALL_START, CALL_RING, CALL_ANSWER, CALL_UPDATE, CALL_HOLD, CALL_UNHOLD, CALL_END, CALL_VARIABLE, VARIABLE, SWITCH, EMPLOYEE_PRESENCE_CHANGED, CONFERENCE_ENTERED, CONFERENCE_LEFT, CONFERENCE_CHANGED, RECORDING_START, RECORDING_END, RECORDING_UPDATE, RECORDING_DELETED, QUEUE_ENTERED, QUEUE_LEFT, QUEUE_UPDATED, QUEUE_DELETED, QUEUE_MASTER, QUEUE_MEMBER_JOIN, QUEUE_MEMBER_UPDATE, QUEUE_MEMBER_LEFT, PERSONAL_QUEUE_JOIN, PERSONAL_QUEUE_LEAVE, PERSONAL_QUEUE_CLEANUP, VOICEMAIL_NEW, VOICEMAIL_OLD, VOICEMAIL_DELETED, AUDIO_RECORD, SIP_PHONE_HOST
callEvents — SBC call events
Emitted for CALL and ADMIN:CALL/ADMIN subscriptions: START, ANSWER, PREEND, END, MAXCHANNELS, PEER_ERROR, FRAUD
registerEvents — SIP registrations
Emitted for REGISTER and ADMIN:REGISTER/ADMIN subscriptions: REGISTER, UNREGISTER, ERROR
trunkEvents — SIP trunk sessions
Emitted for TRUNK subscriptions: SESSION_CREATE, SESSION_DELETE
requestReplyEvents — messages from other clients
Emitted when another realtime client sends this instance a request or a reply — see the next section.
Request / reply between clients
Realtime clients can message each other point-to-point, addressed by employee ID and optionally instance ID. This is used, for example, to remote-control Communicator Desktop instances (fetch logs, restart, perform call actions, …). Available request types are defined in the RealtimeRequestType enum (CommunicatorReload, CommunicatorGetStatus, CommunicatorCallAction, CommunicatorZeroTierStatus, …).
import { RealtimeRequestType } from '@per_moeller/tcx-realtime-client'
// Ask one of an employee's clients for its status, and route the reply back to us
const requestId = await realtime.sendRequest(
targetEmployeeId,
targetInstanceId, // undefined = all of the employee's instances
RealtimeRequestType.CommunicatorGetStatus,
{} as RealtimeRequestData,
myEmployeeId, // replyTo employee
myInstanceId // replyTo instance
)
// Receiving side: answer incoming requests, match replies by requestId
realtime.requestReplyEvents.on(RealtimeRequestType.CommunicatorGetStatus, async message => {
if (message.type === 'REQUEST' && message.replyTo) {
await realtime.sendReply(
message.replyTo.employee,
message.replyTo.instanceId,
message.request,
message.requestId ?? '',
{ success: true, /* ... */ }
)
}
if (message.type === 'REPLY') {
console.log('Got reply for request', message.requestId, message.data)
}
})sendRequest resolves with a server-assigned request ID (for matching the reply) or undefined on failure. sendReply resolves true/false. Both requests and replies arrive on requestReplyEvents keyed by the request type — check message.type ('REQUEST' or 'REPLY') to tell them apart.
Logging
Pass an object implementing the Logger interface to get diagnostics out of the client:
const logger = {
debug: (msg: string) => console.debug(msg),
info: (msg: string) => console.info(msg),
warning: (msg: string) => console.warn(msg),
error: (msg: string) => console.error(msg),
}
const realtime = new Realtime(server, instanceId, client, token, logger)Without a logger, all log output is discarded.
TypeScript
All payload interfaces and enums are exported from the package root: SubscribeType, Logger, EmployeeAccessLevel, EmployeePresence, PbxEventCall, ConfigEventCommon, CallEvent, RealtimeRequest, RealtimeReply, the EventEmitter*Events emitter maps, and many more. If your editor can jump to the type definitions, they double as payload reference documentation.
Further documentation
- Realtime channel & event payload reference: https://wiki.telecomx.dk/doku.php?id=realtime
- Authentication / obtaining a token: https://wiki.telecomx.dk/doku.php?id=api:auth:login
- Wiki root: https://wiki.telecomx.dk
Releasing
Releases are automated. Every pull request to master must raise version in
package.json; two required checks enforce it (build and version-bump), and
version-bump also rejects a version that is already on the npm registry.
On merge, .github/workflows/publish.yml publishes the new version to npm and
creates a matching v<version> release. It authenticates with npm trusted
publishing (OIDC), so no publish token is stored in the repository. A merge that
does not change the version publishes nothing.
Publishing from a workstation is refused: prepublishOnly runs a guard that
only lets npm publish proceed inside GitHub Actions.
