From 0179416572cc039c483295a76e5a930adde463ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Tue, 20 Oct 2026 17:39:18 +0200 Subject: [PATCH 1/2] TS: Transactions --- src/{Transactions.js => Transactions.ts} | 340 +++++++++++++++++------ 1 file changed, 251 insertions(+), 89 deletions(-) rename src/{Transactions.js => Transactions.ts} (63%) diff --git a/src/Transactions.js b/src/Transactions.ts similarity index 63% rename from src/Transactions.js rename to src/Transactions.ts index 8cc73a28..49f31116 100644 --- a/src/Transactions.js +++ b/src/Transactions.ts @@ -1,8 +1,10 @@ -const EventEmitter = require('events').EventEmitter; -const { Logger } = require('./Logger'); -const JsSIP_C = require('./Constants'); -const SIPMessage = require('./SIPMessage'); -const Timers = require('./Timers'); +import { EventEmitter } from 'events'; + +import * as JsSIP_C from './Constants'; +import { Logger } from './Logger'; +import * as SIPMessage from './SIPMessage'; +import * as Timers from './Timers'; +import type { Transport } from './Transport'; const loggernict = new Logger('NonInviteClientTransaction'); const loggerict = new Logger('InviteClientTransaction'); @@ -10,7 +12,7 @@ const loggeract = new Logger('AckClientTransaction'); const loggernist = new Logger('NonInviteServerTransaction'); const loggerist = new Logger('InviteServerTransaction'); -const C = { +export const C = { // Transaction states. STATUS_TRYING: 1, STATUS_PROCEEDING: 2, @@ -27,8 +29,74 @@ const C = { INVITE_SERVER: 'ist', }; -class NonInviteClientTransaction extends EventEmitter { - constructor(ua, transport, request, eventHandlers) { +/** + * The callbacks a client transaction reports to whoever sent the request. + */ +export interface ClientTransactionEventHandlers { + onRequestTimeout(): void; + onTransportError(): void; + onReceiveResponse(response: SIPMessage.IncomingResponse): void; +} + +/** + * Any transaction held in the UA transaction tables. + */ +export type Transaction = + | NonInviteClientTransaction + | InviteClientTransaction + | NonInviteServerTransaction + | InviteServerTransaction; + +/** + * The UA transaction tables, indexed by transaction id (the Via branch). + */ +export interface TransactionRegistry { + nict: Record; + nist: Record; + ict: Record; + ist: Record; +} + +/** + * The `UA` surface transactions depend on. + * + * TODO: Replace with `import type { UA } from './UA'` once `UA` is ported to + * TypeScript. It is declared structurally (on top of the `UALike` that + * `SIPMessage` already needs) so that this module does not depend on the + * still JavaScript `UA` module. + */ +export interface TransactionUA extends SIPMessage.UALike { + readonly configuration: SIPMessage.UALike['configuration'] & { + via_host: string; + }; + + newTransaction(transaction: Transaction): void; + destroyTransaction(transaction: Transaction): void; +} + +export class NonInviteClientTransaction extends EventEmitter { + type: string; + id: string; + ua: TransactionUA; + transport: Transport; + request: SIPMessage.OutgoingRequest; + eventHandlers: ClientTransactionEventHandlers; + + /* NOTE: `declare`d (and so not emitted) because the original implementation + * does not create these own properties in the constructor. `state` is + * created by `stateChanged()` and the timer handles when their timers are + * armed. + */ + declare state?: number; + declare F?: ReturnType; + declare K?: ReturnType; + + constructor( + ua: TransactionUA, + transport: Transport, + request: SIPMessage.OutgoingRequest, + eventHandlers: ClientTransactionEventHandlers + ) { super(); this.type = C.NON_INVITE_CLIENT; @@ -47,16 +115,16 @@ class NonInviteClientTransaction extends EventEmitter { this.ua.newTransaction(this); } - get C() { + get C(): typeof C { return C; } - stateChanged(state) { + stateChanged(state: number): void { this.state = state; this.emit('stateChanged'); } - send() { + send(): void { this.stateChanged(C.STATUS_TRYING); this.F = setTimeout(() => { this.timer_F(); @@ -67,7 +135,7 @@ class NonInviteClientTransaction extends EventEmitter { } } - onTransportError() { + onTransportError(): void { loggernict.debug( `transport error occurred, deleting transaction ${this.id}` ); @@ -78,19 +146,19 @@ class NonInviteClientTransaction extends EventEmitter { this.eventHandlers.onTransportError(); } - timer_F() { + timer_F(): void { loggernict.debug(`Timer F expired for transaction ${this.id}`); this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); this.eventHandlers.onRequestTimeout(); } - timer_K() { + timer_K(): void { this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } - receiveResponse(response) { + receiveResponse(response: SIPMessage.IncomingResponse): void { const status_code = response.status_code; if (status_code < 200) { @@ -128,8 +196,30 @@ class NonInviteClientTransaction extends EventEmitter { } } -class InviteClientTransaction extends EventEmitter { - constructor(ua, transport, request, eventHandlers) { +export class InviteClientTransaction extends EventEmitter { + type: string; + id: string; + ua: TransactionUA; + transport: Transport; + request: SIPMessage.OutgoingRequest; + eventHandlers: ClientTransactionEventHandlers; + + /* NOTE: `declare`d (and so not emitted) because the original implementation + * does not create these own properties in the constructor. `state` is + * created by `stateChanged()` and the timer handles when their timers are + * armed. + */ + declare state?: number; + declare B?: ReturnType; + declare D?: ReturnType; + declare M?: ReturnType; + + constructor( + ua: TransactionUA, + transport: Transport, + request: SIPMessage.OutgoingRequest, + eventHandlers: ClientTransactionEventHandlers + ) { super(); this.type = C.INVITE_CLIENT; @@ -149,16 +239,16 @@ class InviteClientTransaction extends EventEmitter { this.ua.newTransaction(this); } - get C() { + get C(): typeof C { return C; } - stateChanged(state) { + stateChanged(state: number): void { this.state = state; this.emit('stateChanged'); } - send() { + send(): void { this.stateChanged(C.STATUS_CALLING); this.B = setTimeout(() => { this.timer_B(); @@ -169,7 +259,7 @@ class InviteClientTransaction extends EventEmitter { } } - onTransportError() { + onTransportError(): void { clearTimeout(this.B); clearTimeout(this.D); clearTimeout(this.M); @@ -186,7 +276,7 @@ class InviteClientTransaction extends EventEmitter { } // RFC 6026 7.2. - timer_M() { + timer_M(): void { loggerict.debug(`Timer M expired for transaction ${this.id}`); if (this.state === C.STATUS_ACCEPTED) { @@ -197,7 +287,7 @@ class InviteClientTransaction extends EventEmitter { } // RFC 3261 17.1.1. - timer_B() { + timer_B(): void { loggerict.debug(`Timer B expired for transaction ${this.id}`); if (this.state === C.STATUS_CALLING) { this.stateChanged(C.STATUS_TERMINATED); @@ -206,28 +296,32 @@ class InviteClientTransaction extends EventEmitter { } } - timer_D() { + timer_D(): void { loggerict.debug(`Timer D expired for transaction ${this.id}`); clearTimeout(this.B); this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } - sendACK(response) { + sendACK(response: SIPMessage.IncomingResponse): void { + /* NOTE: The non-null assertions below just reproduce the original + * behaviour: the header getters are typed as possibly `undefined` but + * their value is forwarded unconditionally. + */ const ack = new SIPMessage.OutgoingRequest( JsSIP_C.ACK, this.request.ruri, this.ua, { route_set: this.request.getHeaders('route'), - call_id: this.request.getHeader('call-id'), + call_id: this.request.getHeader('call-id') as string, cseq: this.request.cseq, } ); - ack.setHeader('from', this.request.getHeader('from')); - ack.setHeader('via', this.request.getHeader('via')); - ack.setHeader('to', response.getHeader('to')); + ack.setHeader('from', this.request.getHeader('from')!); + ack.setHeader('via', this.request.getHeader('via')!); + ack.setHeader('to', response.getHeader('to')!); this.D = setTimeout(() => { this.timer_D(); @@ -236,7 +330,7 @@ class InviteClientTransaction extends EventEmitter { this.transport.send(ack); } - cancel(reason) { + cancel(reason?: string): void { // Send only if a provisional response (>100) has been received. if (this.state !== C.STATUS_PROCEEDING) { return; @@ -248,14 +342,14 @@ class InviteClientTransaction extends EventEmitter { this.ua, { route_set: this.request.getHeaders('route'), - call_id: this.request.getHeader('call-id'), + call_id: this.request.getHeader('call-id') as string, cseq: this.request.cseq, } ); - cancel.setHeader('from', this.request.getHeader('from')); - cancel.setHeader('via', this.request.getHeader('via')); - cancel.setHeader('to', this.request.getHeader('to')); + cancel.setHeader('from', this.request.getHeader('from')!); + cancel.setHeader('via', this.request.getHeader('via')!); + cancel.setHeader('to', this.request.getHeader('to')!); if (reason) { cancel.setHeader('reason', reason); @@ -264,7 +358,7 @@ class InviteClientTransaction extends EventEmitter { this.transport.send(cancel); } - receiveResponse(response) { + receiveResponse(response: SIPMessage.IncomingResponse): void { const status_code = response.status_code; if (status_code >= 100 && status_code <= 199) { @@ -313,8 +407,18 @@ class InviteClientTransaction extends EventEmitter { } } -class AckClientTransaction extends EventEmitter { - constructor(ua, transport, request, eventHandlers) { +export class AckClientTransaction extends EventEmitter { + id: string; + transport: Transport; + request: SIPMessage.OutgoingRequest; + eventHandlers: ClientTransactionEventHandlers; + + constructor( + ua: TransactionUA, + transport: Transport, + request: SIPMessage.OutgoingRequest, + eventHandlers: ClientTransactionEventHandlers + ) { super(); this.id = `z9hG4bK${Math.floor(Math.random() * 10000000)}`; @@ -329,28 +433,46 @@ class AckClientTransaction extends EventEmitter { this.request.setHeader('via', via); } - get C() { + get C(): typeof C { return C; } - send() { + send(): void { if (!this.transport.send(this.request)) { this.onTransportError(); } } - onTransportError() { + onTransportError(): void { loggeract.debug(`transport error occurred for transaction ${this.id}`); this.eventHandlers.onTransportError(); } } -class NonInviteServerTransaction extends EventEmitter { - constructor(ua, transport, request) { +export class NonInviteServerTransaction extends EventEmitter { + type: string; + id: string; + ua: TransactionUA; + transport: Transport; + request: SIPMessage.IncomingRequest; + last_response: string; + state: number; + + /* NOTE: `declare`d (and so not emitted) because the original implementation + * does not create these own properties in the constructor. + */ + declare transportError?: boolean; + declare J?: ReturnType; + + constructor( + ua: TransactionUA, + transport: Transport, + request: SIPMessage.IncomingRequest + ) { super(); this.type = C.NON_INVITE_SERVER; - this.id = request.via_branch; + this.id = request.via_branch!; this.ua = ua; this.transport = transport; this.request = request; @@ -362,22 +484,22 @@ class NonInviteServerTransaction extends EventEmitter { ua.newTransaction(this); } - get C() { + get C(): typeof C { return C; } - stateChanged(state) { + stateChanged(state: number): void { this.state = state; this.emit('stateChanged'); } - timer_J() { + timer_J(): void { loggernist.debug(`Timer J expired for transaction ${this.id}`); this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } - onTransportError() { + onTransportError(): void { if (!this.transportError) { this.transportError = true; @@ -391,7 +513,12 @@ class NonInviteServerTransaction extends EventEmitter { } } - receiveResponse(status_code, response, onSuccess, onFailure) { + receiveResponse( + status_code: number, + response: string, + onSuccess?: () => void, + onFailure?: () => void + ): void { if (status_code === 100) { /* RFC 4320 4.1 * 'A SIP element MUST NOT @@ -446,12 +573,33 @@ class NonInviteServerTransaction extends EventEmitter { } } -class InviteServerTransaction extends EventEmitter { - constructor(ua, transport, request) { +export class InviteServerTransaction extends EventEmitter { + type: string; + id: string; + ua: TransactionUA; + transport: Transport; + request: SIPMessage.IncomingRequest; + last_response: string; + state: number; + resendProvisionalTimer: ReturnType | null; + + /* NOTE: `declare`d (and so not emitted) because the original implementation + * does not create these own properties in the constructor. + */ + declare transportError?: boolean; + declare H?: ReturnType; + declare I?: ReturnType; + declare L?: ReturnType; + + constructor( + ua: TransactionUA, + transport: Transport, + request: SIPMessage.IncomingRequest + ) { super(); this.type = C.INVITE_SERVER; - this.id = request.via_branch; + this.id = request.via_branch!; this.ua = ua; this.transport = transport; this.request = request; @@ -467,16 +615,16 @@ class InviteServerTransaction extends EventEmitter { request.reply(100); } - get C() { + get C(): typeof C { return C; } - stateChanged(state) { + stateChanged(state: number): void { this.state = state; this.emit('stateChanged'); } - timer_H() { + timer_H(): void { loggerist.debug(`Timer H expired for transaction ${this.id}`); if (this.state === C.STATUS_COMPLETED) { @@ -487,13 +635,13 @@ class InviteServerTransaction extends EventEmitter { this.ua.destroyTransaction(this); } - timer_I() { + timer_I(): void { this.stateChanged(C.STATUS_TERMINATED); this.ua.destroyTransaction(this); } // RFC 6026 7.1. - timer_L() { + timer_L(): void { loggerist.debug(`Timer L expired for transaction ${this.id}`); if (this.state === C.STATUS_ACCEPTED) { @@ -502,7 +650,7 @@ class InviteServerTransaction extends EventEmitter { } } - onTransportError() { + onTransportError(): void { if (!this.transportError) { this.transportError = true; @@ -524,14 +672,19 @@ class InviteServerTransaction extends EventEmitter { } } - resend_provisional() { + resend_provisional(): void { if (!this.transport.send(this.last_response)) { this.onTransportError(); } } // INVITE Server Transaction RFC 3261 17.2.1. - receiveResponse(status_code, response, onSuccess, onFailure) { + receiveResponse( + status_code: number, + response: string, + onSuccess?: () => void, + onFailure?: () => void + ): void { if (status_code >= 100 && status_code <= 199) { switch (this.state) { case C.STATUS_PROCEEDING: { @@ -556,21 +709,30 @@ class InviteServerTransaction extends EventEmitter { }, Timers.PROVISIONAL_RESPONSE_INTERVAL); } } else if (status_code >= 200 && status_code <= 299) { - switch (this.state) { - case C.STATUS_PROCEEDING: { - this.stateChanged(C.STATUS_ACCEPTED); - this.last_response = response; - this.L = setTimeout(() => { - this.timer_L(); - }, Timers.TIMER_L); + /* NOTE: The original implementation lets the `C.STATUS_PROCEEDING` + * case fall through into the `C.STATUS_ACCEPTED` one. TypeScript's + * `noFallthroughCasesInSwitch` forbids that, so the proceeding part + * is guarded by the state read before entering the switch, which is + * exactly when the original evaluates the switch discriminant. + */ + const was_proceeding = this.state === C.STATUS_PROCEEDING; - if (this.resendProvisionalTimer !== null) { - clearInterval(this.resendProvisionalTimer); - this.resendProvisionalTimer = null; - } - } - // falls through + switch (this.state) { + case C.STATUS_PROCEEDING: case C.STATUS_ACCEPTED: { + if (was_proceeding) { + this.stateChanged(C.STATUS_ACCEPTED); + this.last_response = response; + this.L = setTimeout(() => { + this.timer_L(); + }, Timers.TIMER_L); + + if (this.resendProvisionalTimer !== null) { + clearInterval(this.resendProvisionalTimer); + this.resendProvisionalTimer = null; + } + } + // Note that this point will be reached for proceeding this.state also. if (!this.transport.send(response)) { this.onTransportError(); @@ -630,12 +792,18 @@ class InviteServerTransaction extends EventEmitter { * _true_ retransmission * _false_ new request */ -function checkTransaction({ _transactions }, request) { - let tr; - +export function checkTransaction( + { _transactions }: { _transactions: TransactionRegistry }, + request: SIPMessage.IncomingRequest +): boolean | undefined { + /* NOTE: The original implementation uses a single `let tr` declared before + * the switch. It is declared per case here so that each one gets the + * matching transaction type; the variable was never read across cases. + */ switch (request.method) { case JsSIP_C.INVITE: { - tr = _transactions.ist[request.via_branch]; + const tr = _transactions.ist[request.via_branch!]; + if (tr) { switch (tr.state) { case C.STATUS_PROCEEDING: { @@ -655,7 +823,7 @@ function checkTransaction({ _transactions }, request) { break; } case JsSIP_C.ACK: { - tr = _transactions.ist[request.via_branch]; + const tr = _transactions.ist[request.via_branch!]; // RFC 6026 7.1. if (tr) { @@ -677,7 +845,8 @@ function checkTransaction({ _transactions }, request) { break; } case JsSIP_C.CANCEL: { - tr = _transactions.ist[request.via_branch]; + const tr = _transactions.ist[request.via_branch!]; + if (tr) { request.reply_sl(200); if (tr.state === C.STATUS_PROCEEDING) { @@ -693,7 +862,8 @@ function checkTransaction({ _transactions }, request) { } default: { // Non-INVITE Server Transaction RFC 3261 17.2.2. - tr = _transactions.nist[request.via_branch]; + const tr = _transactions.nist[request.via_branch!]; + if (tr) { switch (tr.state) { case C.STATUS_TRYING: { @@ -711,14 +881,6 @@ function checkTransaction({ _transactions }, request) { break; } } -} -module.exports = { - C, - NonInviteClientTransaction, - InviteClientTransaction, - AckClientTransaction, - NonInviteServerTransaction, - InviteServerTransaction, - checkTransaction, -}; + return undefined; +} From 43980d9f5089bcfc6ce8fe182be95cd9383a52ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Tue, 20 Oct 2026 17:50:29 +0200 Subject: [PATCH 2/2] More types --- src/Dialog.js | 12 +- src/Dialog/RequestSender.js | 9 +- src/RTCSession.js | 4 +- src/Transactions.ts | 249 +++++++++++++++++------------------- 4 files changed, 129 insertions(+), 145 deletions(-) diff --git a/src/Dialog.js b/src/Dialog.js index 70ec5860..e2d25969 100644 --- a/src/Dialog.js +++ b/src/Dialog.js @@ -266,12 +266,10 @@ module.exports = class Dialog { const stateChanged = () => { if ( + request.server_transaction.state === Transactions.Status.ACCEPTED || request.server_transaction.state === - Transactions.C.STATUS_ACCEPTED || - request.server_transaction.state === - Transactions.C.STATUS_COMPLETED || - request.server_transaction.state === - Transactions.C.STATUS_TERMINATED + Transactions.Status.COMPLETED || + request.server_transaction.state === Transactions.Status.TERMINATED ) { request.server_transaction.removeListener( 'stateChanged', @@ -288,7 +286,7 @@ module.exports = class Dialog { if (request.hasHeader('contact')) { request.server_transaction.on('stateChanged', () => { if ( - request.server_transaction.state === Transactions.C.STATUS_ACCEPTED + request.server_transaction.state === Transactions.Status.ACCEPTED ) { this._remote_target = request.parseHeader('contact').uri; } @@ -299,7 +297,7 @@ module.exports = class Dialog { if (request.hasHeader('contact')) { request.server_transaction.on('stateChanged', () => { if ( - request.server_transaction.state === Transactions.C.STATUS_COMPLETED + request.server_transaction.state === Transactions.Status.COMPLETED ) { this._remote_target = request.parseHeader('contact').uri; } diff --git a/src/Dialog/RequestSender.js b/src/Dialog/RequestSender.js index adeeb393..95ab5b10 100644 --- a/src/Dialog/RequestSender.js +++ b/src/Dialog/RequestSender.js @@ -59,19 +59,18 @@ module.exports = class DialogRequestSender { if ( (this._request.method === JsSIP_C.INVITE || (this._request.method === JsSIP_C.UPDATE && this._request.body)) && - request_sender.clientTransaction.state !== - Transactions.C.STATUS_TERMINATED + request_sender.clientTransaction.state !== Transactions.Status.TERMINATED ) { this._dialog.uac_pending_reply = true; const stateChanged = () => { if ( request_sender.clientTransaction.state === - Transactions.C.STATUS_ACCEPTED || + Transactions.Status.ACCEPTED || request_sender.clientTransaction.state === - Transactions.C.STATUS_COMPLETED || + Transactions.Status.COMPLETED || request_sender.clientTransaction.state === - Transactions.C.STATUS_TERMINATED + Transactions.Status.TERMINATED ) { request_sender.clientTransaction.removeListener( 'stateChanged', diff --git a/src/RTCSession.js b/src/RTCSession.js index 97e17459..f6c8b8b2 100644 --- a/src/RTCSession.js +++ b/src/RTCSession.js @@ -843,7 +843,7 @@ module.exports = class RTCSession extends EventEmitter { this._status === C.STATUS_WAITING_FOR_ACK && this._direction === 'incoming' && this._request.server_transaction.state !== - Transactions.C.STATUS_TERMINATED + Transactions.Status.TERMINATED ) { // Save the dialog for later restoration. const dialog = this._dialog; @@ -863,7 +863,7 @@ module.exports = class RTCSession extends EventEmitter { this._request.server_transaction.on('stateChanged', () => { if ( this._request.server_transaction.state === - Transactions.C.STATUS_TERMINATED + Transactions.Status.TERMINATED ) { this.sendRequest(JsSIP_C.BYE, { extraHeaders, diff --git a/src/Transactions.ts b/src/Transactions.ts index 49f31116..b94d9ea5 100644 --- a/src/Transactions.ts +++ b/src/Transactions.ts @@ -12,22 +12,29 @@ const loggeract = new Logger('AckClientTransaction'); const loggernist = new Logger('NonInviteServerTransaction'); const loggerist = new Logger('InviteServerTransaction'); -export const C = { - // Transaction states. - STATUS_TRYING: 1, - STATUS_PROCEEDING: 2, - STATUS_CALLING: 3, - STATUS_ACCEPTED: 4, - STATUS_COMPLETED: 5, - STATUS_TERMINATED: 6, - STATUS_CONFIRMED: 7, - - // Transaction types. - NON_INVITE_CLIENT: 'nict', - NON_INVITE_SERVER: 'nist', - INVITE_CLIENT: 'ict', - INVITE_SERVER: 'ist', -}; +/** + * Transaction states. + */ +export enum Status { + TRYING = 1, + PROCEEDING = 2, + CALLING = 3, + ACCEPTED = 4, + COMPLETED = 5, + TERMINATED = 6, + CONFIRMED = 7, +} + +/** + * Transaction types, each also being the key of the corresponding registry in + * the UA. + */ +export enum Type { + NON_INVITE_CLIENT = 'nict', + NON_INVITE_SERVER = 'nist', + INVITE_CLIENT = 'ict', + INVITE_SERVER = 'ist', +} /** * The callbacks a client transaction reports to whoever sent the request. @@ -75,7 +82,7 @@ export interface TransactionUA extends SIPMessage.UALike { } export class NonInviteClientTransaction extends EventEmitter { - type: string; + type: Type; id: string; ua: TransactionUA; transport: Transport; @@ -87,9 +94,9 @@ export class NonInviteClientTransaction extends EventEmitter { * created by `stateChanged()` and the timer handles when their timers are * armed. */ - declare state?: number; - declare F?: ReturnType; - declare K?: ReturnType; + state?: Status; + F?: ReturnType; + K?: ReturnType; constructor( ua: TransactionUA, @@ -99,7 +106,7 @@ export class NonInviteClientTransaction extends EventEmitter { ) { super(); - this.type = C.NON_INVITE_CLIENT; + this.type = Type.NON_INVITE_CLIENT; this.id = `z9hG4bK${Math.floor(Math.random() * 10000000)}`; this.ua = ua; this.transport = transport; @@ -115,17 +122,13 @@ export class NonInviteClientTransaction extends EventEmitter { this.ua.newTransaction(this); } - get C(): typeof C { - return C; - } - - stateChanged(state: number): void { + stateChanged(state: Status): void { this.state = state; this.emit('stateChanged'); } send(): void { - this.stateChanged(C.STATUS_TRYING); + this.stateChanged(Status.TRYING); this.F = setTimeout(() => { this.timer_F(); }, Timers.TIMER_F); @@ -141,20 +144,20 @@ export class NonInviteClientTransaction extends EventEmitter { ); clearTimeout(this.F); clearTimeout(this.K); - this.stateChanged(C.STATUS_TERMINATED); + this.stateChanged(Status.TERMINATED); this.ua.destroyTransaction(this); this.eventHandlers.onTransportError(); } timer_F(): void { loggernict.debug(`Timer F expired for transaction ${this.id}`); - this.stateChanged(C.STATUS_TERMINATED); + this.stateChanged(Status.TERMINATED); this.ua.destroyTransaction(this); this.eventHandlers.onRequestTimeout(); } timer_K(): void { - this.stateChanged(C.STATUS_TERMINATED); + this.stateChanged(Status.TERMINATED); this.ua.destroyTransaction(this); } @@ -163,18 +166,18 @@ export class NonInviteClientTransaction extends EventEmitter { if (status_code < 200) { switch (this.state) { - case C.STATUS_TRYING: - case C.STATUS_PROCEEDING: { - this.stateChanged(C.STATUS_PROCEEDING); + case Status.TRYING: + case Status.PROCEEDING: { + this.stateChanged(Status.PROCEEDING); this.eventHandlers.onReceiveResponse(response); break; } } } else { switch (this.state) { - case C.STATUS_TRYING: - case C.STATUS_PROCEEDING: { - this.stateChanged(C.STATUS_COMPLETED); + case Status.TRYING: + case Status.PROCEEDING: { + this.stateChanged(Status.COMPLETED); clearTimeout(this.F); if (status_code === 408) { @@ -188,7 +191,7 @@ export class NonInviteClientTransaction extends EventEmitter { }, Timers.TIMER_K); break; } - case C.STATUS_COMPLETED: { + case Status.COMPLETED: { break; } } @@ -197,7 +200,7 @@ export class NonInviteClientTransaction extends EventEmitter { } export class InviteClientTransaction extends EventEmitter { - type: string; + type: Type; id: string; ua: TransactionUA; transport: Transport; @@ -209,10 +212,10 @@ export class InviteClientTransaction extends EventEmitter { * created by `stateChanged()` and the timer handles when their timers are * armed. */ - declare state?: number; - declare B?: ReturnType; - declare D?: ReturnType; - declare M?: ReturnType; + state?: Status; + B?: ReturnType; + D?: ReturnType; + M?: ReturnType; constructor( ua: TransactionUA, @@ -222,7 +225,7 @@ export class InviteClientTransaction extends EventEmitter { ) { super(); - this.type = C.INVITE_CLIENT; + this.type = Type.INVITE_CLIENT; this.id = `z9hG4bK${Math.floor(Math.random() * 10000000)}`; this.ua = ua; this.transport = transport; @@ -239,17 +242,13 @@ export class InviteClientTransaction extends EventEmitter { this.ua.newTransaction(this); } - get C(): typeof C { - return C; - } - - stateChanged(state: number): void { + stateChanged(state: Status): void { this.state = state; this.emit('stateChanged'); } send(): void { - this.stateChanged(C.STATUS_CALLING); + this.stateChanged(Status.CALLING); this.B = setTimeout(() => { this.timer_B(); }, Timers.TIMER_B); @@ -264,14 +263,14 @@ export class InviteClientTransaction extends EventEmitter { clearTimeout(this.D); clearTimeout(this.M); - if (this.state !== C.STATUS_ACCEPTED) { + if (this.state !== Status.ACCEPTED) { loggerict.debug( `transport error occurred, deleting transaction ${this.id}` ); this.eventHandlers.onTransportError(); } - this.stateChanged(C.STATUS_TERMINATED); + this.stateChanged(Status.TERMINATED); this.ua.destroyTransaction(this); } @@ -279,9 +278,9 @@ export class InviteClientTransaction extends EventEmitter { timer_M(): void { loggerict.debug(`Timer M expired for transaction ${this.id}`); - if (this.state === C.STATUS_ACCEPTED) { + if (this.state === Status.ACCEPTED) { clearTimeout(this.B); - this.stateChanged(C.STATUS_TERMINATED); + this.stateChanged(Status.TERMINATED); this.ua.destroyTransaction(this); } } @@ -289,8 +288,8 @@ export class InviteClientTransaction extends EventEmitter { // RFC 3261 17.1.1. timer_B(): void { loggerict.debug(`Timer B expired for transaction ${this.id}`); - if (this.state === C.STATUS_CALLING) { - this.stateChanged(C.STATUS_TERMINATED); + if (this.state === Status.CALLING) { + this.stateChanged(Status.TERMINATED); this.ua.destroyTransaction(this); this.eventHandlers.onRequestTimeout(); } @@ -299,7 +298,7 @@ export class InviteClientTransaction extends EventEmitter { timer_D(): void { loggerict.debug(`Timer D expired for transaction ${this.id}`); clearTimeout(this.B); - this.stateChanged(C.STATUS_TERMINATED); + this.stateChanged(Status.TERMINATED); this.ua.destroyTransaction(this); } @@ -332,7 +331,7 @@ export class InviteClientTransaction extends EventEmitter { cancel(reason?: string): void { // Send only if a provisional response (>100) has been received. - if (this.state !== C.STATUS_PROCEEDING) { + if (this.state !== Status.PROCEEDING) { return; } @@ -363,42 +362,42 @@ export class InviteClientTransaction extends EventEmitter { if (status_code >= 100 && status_code <= 199) { switch (this.state) { - case C.STATUS_CALLING: { - this.stateChanged(C.STATUS_PROCEEDING); + case Status.CALLING: { + this.stateChanged(Status.PROCEEDING); this.eventHandlers.onReceiveResponse(response); break; } - case C.STATUS_PROCEEDING: { + case Status.PROCEEDING: { this.eventHandlers.onReceiveResponse(response); break; } } } else if (status_code >= 200 && status_code <= 299) { switch (this.state) { - case C.STATUS_CALLING: - case C.STATUS_PROCEEDING: { - this.stateChanged(C.STATUS_ACCEPTED); + case Status.CALLING: + case Status.PROCEEDING: { + this.stateChanged(Status.ACCEPTED); this.M = setTimeout(() => { this.timer_M(); }, Timers.TIMER_M); this.eventHandlers.onReceiveResponse(response); break; } - case C.STATUS_ACCEPTED: { + case Status.ACCEPTED: { this.eventHandlers.onReceiveResponse(response); break; } } } else if (status_code >= 300 && status_code <= 699) { switch (this.state) { - case C.STATUS_CALLING: - case C.STATUS_PROCEEDING: { - this.stateChanged(C.STATUS_COMPLETED); + case Status.CALLING: + case Status.PROCEEDING: { + this.stateChanged(Status.COMPLETED); this.sendACK(response); this.eventHandlers.onReceiveResponse(response); break; } - case C.STATUS_COMPLETED: { + case Status.COMPLETED: { this.sendACK(response); break; } @@ -433,10 +432,6 @@ export class AckClientTransaction extends EventEmitter { this.request.setHeader('via', via); } - get C(): typeof C { - return C; - } - send(): void { if (!this.transport.send(this.request)) { this.onTransportError(); @@ -450,19 +445,19 @@ export class AckClientTransaction extends EventEmitter { } export class NonInviteServerTransaction extends EventEmitter { - type: string; + type: Type; id: string; ua: TransactionUA; transport: Transport; request: SIPMessage.IncomingRequest; last_response: string; - state: number; + state: Status; /* NOTE: `declare`d (and so not emitted) because the original implementation * does not create these own properties in the constructor. */ - declare transportError?: boolean; - declare J?: ReturnType; + transportError?: boolean; + J?: ReturnType; constructor( ua: TransactionUA, @@ -471,7 +466,7 @@ export class NonInviteServerTransaction extends EventEmitter { ) { super(); - this.type = C.NON_INVITE_SERVER; + this.type = Type.NON_INVITE_SERVER; this.id = request.via_branch!; this.ua = ua; this.transport = transport; @@ -479,23 +474,19 @@ export class NonInviteServerTransaction extends EventEmitter { this.last_response = ''; request.server_transaction = this; - this.state = C.STATUS_TRYING; + this.state = Status.TRYING; ua.newTransaction(this); } - get C(): typeof C { - return C; - } - - stateChanged(state: number): void { + stateChanged(state: Status): void { this.state = state; this.emit('stateChanged'); } timer_J(): void { loggernist.debug(`Timer J expired for transaction ${this.id}`); - this.stateChanged(C.STATUS_TERMINATED); + this.stateChanged(Status.TERMINATED); this.ua.destroyTransaction(this); } @@ -508,7 +499,7 @@ export class NonInviteServerTransaction extends EventEmitter { ); clearTimeout(this.J); - this.stateChanged(C.STATUS_TERMINATED); + this.stateChanged(Status.TERMINATED); this.ua.destroyTransaction(this); } } @@ -526,14 +517,14 @@ export class NonInviteServerTransaction extends EventEmitter { * Status-Code other than 100 to a non-INVITE request.' */ switch (this.state) { - case C.STATUS_TRYING: { - this.stateChanged(C.STATUS_PROCEEDING); + case Status.TRYING: { + this.stateChanged(Status.PROCEEDING); if (!this.transport.send(response)) { this.onTransportError(); } break; } - case C.STATUS_PROCEEDING: { + case Status.PROCEEDING: { this.last_response = response; if (!this.transport.send(response)) { this.onTransportError(); @@ -548,9 +539,9 @@ export class NonInviteServerTransaction extends EventEmitter { } } else if (status_code >= 200 && status_code <= 699) { switch (this.state) { - case C.STATUS_TRYING: - case C.STATUS_PROCEEDING: { - this.stateChanged(C.STATUS_COMPLETED); + case Status.TRYING: + case Status.PROCEEDING: { + this.stateChanged(Status.COMPLETED); this.last_response = response; this.J = setTimeout(() => { this.timer_J(); @@ -565,7 +556,7 @@ export class NonInviteServerTransaction extends EventEmitter { } break; } - case C.STATUS_COMPLETED: { + case Status.COMPLETED: { break; } } @@ -574,22 +565,22 @@ export class NonInviteServerTransaction extends EventEmitter { } export class InviteServerTransaction extends EventEmitter { - type: string; + type: Type; id: string; ua: TransactionUA; transport: Transport; request: SIPMessage.IncomingRequest; last_response: string; - state: number; + state: Status; resendProvisionalTimer: ReturnType | null; /* NOTE: `declare`d (and so not emitted) because the original implementation * does not create these own properties in the constructor. */ - declare transportError?: boolean; - declare H?: ReturnType; - declare I?: ReturnType; - declare L?: ReturnType; + transportError?: boolean; + H?: ReturnType; + I?: ReturnType; + L?: ReturnType; constructor( ua: TransactionUA, @@ -598,7 +589,7 @@ export class InviteServerTransaction extends EventEmitter { ) { super(); - this.type = C.INVITE_SERVER; + this.type = Type.INVITE_SERVER; this.id = request.via_branch!; this.ua = ua; this.transport = transport; @@ -606,7 +597,7 @@ export class InviteServerTransaction extends EventEmitter { this.last_response = ''; request.server_transaction = this; - this.state = C.STATUS_PROCEEDING; + this.state = Status.PROCEEDING; ua.newTransaction(this); @@ -615,11 +606,7 @@ export class InviteServerTransaction extends EventEmitter { request.reply(100); } - get C(): typeof C { - return C; - } - - stateChanged(state: number): void { + stateChanged(state: Status): void { this.state = state; this.emit('stateChanged'); } @@ -627,16 +614,16 @@ export class InviteServerTransaction extends EventEmitter { timer_H(): void { loggerist.debug(`Timer H expired for transaction ${this.id}`); - if (this.state === C.STATUS_COMPLETED) { + if (this.state === Status.COMPLETED) { loggerist.debug('ACK not received, dialog will be terminated'); } - this.stateChanged(C.STATUS_TERMINATED); + this.stateChanged(Status.TERMINATED); this.ua.destroyTransaction(this); } timer_I(): void { - this.stateChanged(C.STATUS_TERMINATED); + this.stateChanged(Status.TERMINATED); this.ua.destroyTransaction(this); } @@ -644,8 +631,8 @@ export class InviteServerTransaction extends EventEmitter { timer_L(): void { loggerist.debug(`Timer L expired for transaction ${this.id}`); - if (this.state === C.STATUS_ACCEPTED) { - this.stateChanged(C.STATUS_TERMINATED); + if (this.state === Status.ACCEPTED) { + this.stateChanged(Status.TERMINATED); this.ua.destroyTransaction(this); } } @@ -667,7 +654,7 @@ export class InviteServerTransaction extends EventEmitter { clearTimeout(this.H); clearTimeout(this.I); - this.stateChanged(C.STATUS_TERMINATED); + this.stateChanged(Status.TERMINATED); this.ua.destroyTransaction(this); } } @@ -687,7 +674,7 @@ export class InviteServerTransaction extends EventEmitter { ): void { if (status_code >= 100 && status_code <= 199) { switch (this.state) { - case C.STATUS_PROCEEDING: { + case Status.PROCEEDING: { if (!this.transport.send(response)) { this.onTransportError(); } @@ -700,7 +687,7 @@ export class InviteServerTransaction extends EventEmitter { if ( status_code > 100 && status_code <= 199 && - this.state === C.STATUS_PROCEEDING + this.state === Status.PROCEEDING ) { // Trigger the resendProvisionalTimer only for the first non 100 provisional response. if (this.resendProvisionalTimer === null) { @@ -709,19 +696,19 @@ export class InviteServerTransaction extends EventEmitter { }, Timers.PROVISIONAL_RESPONSE_INTERVAL); } } else if (status_code >= 200 && status_code <= 299) { - /* NOTE: The original implementation lets the `C.STATUS_PROCEEDING` - * case fall through into the `C.STATUS_ACCEPTED` one. TypeScript's + /* NOTE: The original implementation lets the `Status.PROCEEDING` + * case fall through into the `Status.ACCEPTED` one. TypeScript's * `noFallthroughCasesInSwitch` forbids that, so the proceeding part * is guarded by the state read before entering the switch, which is * exactly when the original evaluates the switch discriminant. */ - const was_proceeding = this.state === C.STATUS_PROCEEDING; + const was_proceeding = this.state === Status.PROCEEDING; switch (this.state) { - case C.STATUS_PROCEEDING: - case C.STATUS_ACCEPTED: { + case Status.PROCEEDING: + case Status.ACCEPTED: { if (was_proceeding) { - this.stateChanged(C.STATUS_ACCEPTED); + this.stateChanged(Status.ACCEPTED); this.last_response = response; this.L = setTimeout(() => { this.timer_L(); @@ -747,7 +734,7 @@ export class InviteServerTransaction extends EventEmitter { } } else if (status_code >= 300 && status_code <= 699) { switch (this.state) { - case C.STATUS_PROCEEDING: { + case Status.PROCEEDING: { if (this.resendProvisionalTimer !== null) { clearInterval(this.resendProvisionalTimer); this.resendProvisionalTimer = null; @@ -759,7 +746,7 @@ export class InviteServerTransaction extends EventEmitter { onFailure(); } } else { - this.stateChanged(C.STATUS_COMPLETED); + this.stateChanged(Status.COMPLETED); this.H = setTimeout(() => { this.timer_H(); }, Timers.TIMER_H); @@ -806,14 +793,14 @@ export function checkTransaction( if (tr) { switch (tr.state) { - case C.STATUS_PROCEEDING: { + case Status.PROCEEDING: { tr.transport.send(tr.last_response); break; } // RFC 6026 7.1 Invite retransmission. - // Received while in C.STATUS_ACCEPTED state. Absorb it. - case C.STATUS_ACCEPTED: { + // Received while in Status.ACCEPTED state. Absorb it. + case Status.ACCEPTED: { break; } } @@ -827,10 +814,10 @@ export function checkTransaction( // RFC 6026 7.1. if (tr) { - if (tr.state === C.STATUS_ACCEPTED) { + if (tr.state === Status.ACCEPTED) { return false; - } else if (tr.state === C.STATUS_COMPLETED) { - tr.state = C.STATUS_CONFIRMED; + } else if (tr.state === Status.COMPLETED) { + tr.state = Status.CONFIRMED; tr.I = setTimeout(() => { tr.timer_I(); }, Timers.TIMER_I); @@ -849,7 +836,7 @@ export function checkTransaction( if (tr) { request.reply_sl(200); - if (tr.state === C.STATUS_PROCEEDING) { + if (tr.state === Status.PROCEEDING) { return false; } else { return true; @@ -866,11 +853,11 @@ export function checkTransaction( if (tr) { switch (tr.state) { - case C.STATUS_TRYING: { + case Status.TRYING: { break; } - case C.STATUS_PROCEEDING: - case C.STATUS_COMPLETED: { + case Status.PROCEEDING: + case Status.COMPLETED: { tr.transport.send(tr.last_response); break; }