Skip to main content

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

OptionRequiredTypeDefaultDescription
tokenstringShort-lived SDK JWT minted by your backend. See Authentication.
logLevel"silent" \| "error" \| "warn" \| "info" \| "debug""warn"Verbosity of the SDK's internal logger.
allowIncomingWhileBusybooleanfalseWhen false, additional incoming calls are rejected with SIP 486 Busy Here while a call is active.
iceServersRTCIceServer[]from credentialsOverride the ICE servers entirely. If omitted, the SDK uses the STUN/TURN servers returned by the credentials endpoint.
media.inputDeviceIdstringPreferred microphone device ID (from enumerateDevices()).
media.outputDeviceIdstringPreferred speaker device ID. Requires browser support for setSinkId.
ringtoneUrlstringURL of a looped audio file played on incoming calls.
defaultExtraHeadersstring[]Raw SIP headers appended to every outbound INVITE.
tokenExpiryLeadMsnumber60000Fires 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.

FieldTypeRequiredDescription
tostringyesDestination — E.164 (+15551234567) or full sip: URI.
fromstringnoCaller ID in E.164.
callerIdstringnoUUID of a registered caller ID (VCI…). Use this or from.
paramsRecord<string, string>noEach key/value becomes an X-2Chat-<Key> SIP header on the INVITE. Useful for tagging calls with campaign IDs, ticket numbers, etc.
extraHeadersstring[]noRaw 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

PropertyTypeDescription
identityReadonly<TokenClaims>Parsed JWT claims — account_uuid, user_uuid, sip_username, label, iat, exp.
labelstring \| undefinedConvenience accessor for identity.label.
tokenExpiresAtDateWhen the current JWT expires.

Events

Device extends a typed event emitter. Subscribe with device.on(name, handler) and unsubscribe with device.off(name, handler).

EventPayloadWhen it fires
registeredvoidSIP UA successfully registered with the server.
unregistered{ reason?: string }UA was unregistered (cleanly or after a fatal error).
incomingCallNew 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.
reconnectedvoidWebSocket came back online after a drop.
errorVoiceSDKErrorNon-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 VoiceSDKError codes you'll see in the error event.
  • Authentication — token claims and refresh flow.