Device
Device is the entry point to the SDK. A Device owns the SIP user-agent, the WebSocket transport, and the registration lifecycle. Place outbound calls with device.connect(...) and receive inbound ones via the incoming event.
import { Device } from "@2chat/voice-sdk";
const device = new Device({ token: myJwt, logLevel: "info" });
await device.register();
Constructor
new Device(options: DeviceOptions)
DeviceOptions
| Option | Required | Type | Default | Description |
|---|---|---|---|---|
token | ✅ | string | — | Short-lived SDK JWT minted by your backend. See Authentication. |
logLevel | "silent" \| "error" \| "warn" \| "info" \| "debug" | "warn" | Verbosity of the SDK's internal logger. | |
allowIncomingWhileBusy | boolean | false | When false, additional incoming calls are rejected with SIP 486 Busy Here while a call is active. | |
iceServers | RTCIceServer[] | from credentials | Override the ICE servers entirely. If omitted, the SDK uses the STUN/TURN servers returned by the credentials endpoint. | |
media.inputDeviceId | string | — | Preferred microphone device ID (from enumerateDevices()). | |
media.outputDeviceId | string | — | Preferred speaker device ID. Requires browser support for setSinkId. | |
ringtoneUrl | string | — | URL of a looped audio file played on incoming calls. | |
defaultExtraHeaders | string[] | — | Raw SIP headers appended to every outbound INVITE. | |
tokenExpiryLeadMs | number | 60000 | Fires tokenWillExpire this many ms before the JWT's exp claim. |
Methods
register()
await device.register(): Promise<void>
Fetches SIP credentials with the current token, starts the SIP UA, and sends the initial REGISTER. Resolves when the server confirms registration; rejects with a VoiceSDKError on failure.
Must be called before connect().
connect(params)
await device.connect(params: ConnectParams): Promise<Call>
Place an outbound call. Resolves with a Call once the INVITE has been sent.
| Field | Type | Required | Description |
|---|---|---|---|
to | string | yes | Destination — E.164 (+15551234567) or full sip: URI. |
from | string | no | Caller ID in E.164. |
callerId | string | no | UUID of a registered caller ID (VCI…). Use this or from. |
params | Record<string, string> | no | Each key/value becomes an X-2Chat-<Key> SIP header on the INVITE. Useful for tagging calls with campaign IDs, ticket numbers, etc. |
extraHeaders | string[] | no | Raw SIP headers appended to the INVITE. |
updateToken(newToken)
await device.updateToken(newToken: string): Promise<void>
Swap the JWT in place. The new token must be for the same user_uuid as the current one — switching users requires a new Device. Typically called from the tokenWillExpire handler.
unregister()
await device.unregister(): Promise<void>
Send an un-REGISTER and close the WebSocket. The Device can be re-registered with register() afterwards (assuming the token is still valid).
destroy()
device.destroy(): void
Unrecoverable teardown — closes transport, drops all listeners. Call on page unload. After destroy(), any further method call throws DESTROYED.
isRegistered()
device.isRegistered(): boolean
true between a successful register() and unregister() / destroy() / fatal error.
enumerateDevices()
await device.enumerateDevices(): Promise<{
inputs: MediaDeviceInfo[];
outputs: MediaDeviceInfo[];
}>
Convenience over navigator.mediaDevices.enumerateDevices() — splits the list into audio inputs and outputs. The browser only returns device labels after the user has granted microphone permission at least once.
setInputDevice(deviceId) / setOutputDevice(deviceId)
device.setInputDevice(deviceId: string): void
device.setOutputDevice(deviceId: string): void
Set the preferred mic / speaker for future calls. To change devices on an in-progress call, use the matching methods on Call instead.
Properties
| Property | Type | Description |
|---|---|---|
identity | Readonly<TokenClaims> | Parsed JWT claims — account_uuid, user_uuid, sip_username, label, iat, exp. |
label | string \| undefined | Convenience accessor for identity.label. |
tokenExpiresAt | Date | When the current JWT expires. |
Events
Device extends a typed event emitter. Subscribe with device.on(name, handler) and unsubscribe with device.off(name, handler).
| Event | Payload | When it fires |
|---|---|---|
registered | void | SIP UA successfully registered with the server. |
unregistered | { reason?: string } | UA was unregistered (cleanly or after a fatal error). |
incoming | Call | New inbound call — inspect, accept, or reject the provided Call. |
tokenWillExpire | { expiresAt: Date } | JWT is within tokenExpiryLeadMs of exp. Call updateToken(). |
reconnecting | { attempt: number } | WebSocket dropped — the SDK is retrying. |
reconnected | void | WebSocket came back online after a drop. |
error | VoiceSDKError | Non-fatal Device-level error. Fatal failures during register() reject the promise instead. |
Lifecycle
new Device()
│
▼
register() ──► registered ──► connect() / incoming ──► Call lifecycle
│ │
│◄──────── reconnecting ◄── transport drop ───────────────┘
│
unregister() / destroy()
│
▼
unregistered
A typical app keeps one Device per user session — registering once, refreshing the token on tokenWillExpire, and tearing down on unload:
window.addEventListener("beforeunload", () => device.destroy());
See also
- Call API — what
connect()returns and how to control calls. - Errors — the
VoiceSDKErrorcodes you'll see in theerrorevent. - Authentication — token claims and refresh flow.