npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

The package ships compiled CommonJS with type declarations and works from both require and import.

Prerequisites

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 authenticate handshake carrying the token, client string, instance ID and the machine's public IP (looked up via https://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 — a SubscribeType (see below).
  • customer — the customer ID the subscription applies to. Required for customer-scoped types, not allowed for ADMIN* 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

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.