-
Notifications
You must be signed in to change notification settings - Fork 104
Description
Describe the problem
here is short description of issue:
I noticed a difference in SIP transfer behavior between livekit-cli and livekit-server-sdk (Node.js)
We are investigating SIP cold transfer behavior in LiveKit and noticed a discrepancy between the LiveKit CLI and the Node.js livekit-server-sdk.
What we tested
We tested SIP cold transfer in two ways:
- Using LiveKit CLI (v2.4.12)
- Using Node.js livekit-server-sdk (SipClient.transferSipParticipant)
Observed behavior
Using LiveKit CLI
When performing a SIP transfer via the CLI, the transfer command appears to wait for the actual transfer outcome.
In cases where the destination party does not answer or rejects the call, the CLI command returns an error that can be unwrapped into a SIP status (e.g. busy, no-answer, etc.).
This aligns with the CLI implementation:
// TransferSIPParticipant will wait for call to be transferred and that can take some time.
// Default deadline is too short, thus, we must set a higher deadline for it.
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
return s.sipClient.TransferSIPParticipant(ctx, in)
and the presence of:
func SIPStatusFrom(err error) *livekit.SIPStatus
which allows extracting SIP-level failure details.
Using Node.js livekit-server-sdk
When calling:
sipClient.transferSipParticipant(roomName, identity, transferTo)
The promise resolves successfully as soon as the transfer request is accepted, even if:
• The destination does not answer,
• The call is rejected.
• or the SIP transfer ultimately fails.
No SIP status (busy / no-answer / failed) is returned or surfaced via the Node.js SDK call, which makes it difficult to determine whether the transfer actually succeeded.
here is thread: https://livekit-users.slack.com/archives/C07FVFM5NA1/p1767686742279039
Describe the proposed solution
async transferSipParticipant(
roomName: string,
participantIdentity: string,
transferTo: string,
opts?: TransferSipParticipantOptions,
): Promise<any> {
if (opts === undefined) {
opts = {};
}
const req = new TransferSIPParticipantRequest({
participantIdentity: participantIdentity,
roomName: roomName,
transferTo: transferTo,
playDialtone: opts.playDialtone,
headers: opts.headers,
}).toJson();
try {
return await this.rpc.request(
svc,
'TransferSIPParticipant',
req,
await this.authHeader({ roomAdmin: true, room: roomName }, { call: true }),
);
} catch (e) {
if ((e as any)?.metadata?.sip_status || (e as any)?.metadata?.sip_status_code) {
(e as any).sipStatus = (e as any).metadata.sip_status;
(e as any).sipStatusCode = (e as any).metadata.sip_status_code;
}
throw e;
}
}
here is what have been changed:
Alternatives considered
No response
Importance
I cannot use LiveKit without it
Additional Information
No response