Commit de7d4ad for jssip.net

commit de7d4ad7d721cc90c977073fe572482725201860
Author: José Luis Millán <jmillan@aliax.net>
Date:   Tue Oct 20 11:23:10 2026 +0200

    TS: move Timers, Logger, Exceptions, Constants

    Move some files from JS to TS.

diff --git a/src/Constants.d.ts b/src/Constants.d.ts
deleted file mode 100644
index b12d171..0000000
--- a/src/Constants.d.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-export const USER_AGENT: string;
-export const SIP = 'sip';
-export const SIPS = 'sips';
-
-export declare enum causes {
-	CONNECTION_ERROR = 'Connection Error',
-	REQUEST_TIMEOUT = 'Request Timeout',
-	SIP_FAILURE_CODE = 'SIP Failure Code',
-	INTERNAL_ERROR = 'Internal Error',
-	BUSY = 'Busy',
-	REJECTED = 'Rejected',
-	REDIRECTED = 'Redirected',
-	UNAVAILABLE = 'Unavailable',
-	NOT_FOUND = 'Not Found',
-	ADDRESS_INCOMPLETE = 'Address Incomplete',
-	INCOMPATIBLE_SDP = 'Incompatible SDP',
-	MISSING_SDP = 'Missing SDP',
-	AUTHENTICATION_ERROR = 'Authentication Error',
-	BYE = 'Terminated',
-	WEBRTC_ERROR = 'WebRTC Error',
-	CANCELED = 'Canceled',
-	NO_ANSWER = 'No Answer',
-	EXPIRES = 'Expires',
-	NO_ACK = 'No ACK',
-	DIALOG_ERROR = 'Dialog Error',
-	USER_DENIED_MEDIA_ACCESS = 'User Denied Media Access',
-	BAD_MEDIA_DESCRIPTION = 'Bad Media Description',
-	RTP_TIMEOUT = 'RTP Timeout',
-}
-
-export const SIP_ERROR_CAUSES: {
-	REDIRECTED: [300, 301, 302, 305, 380];
-	BUSY: [486, 600];
-	REJECTED: [403, 603];
-	NOT_FOUND: [404, 604];
-	UNAVAILABLE: [480, 410, 408, 430];
-	ADDRESS_INCOMPLETE: [484, 424];
-	INCOMPATIBLE_SDP: [488, 606];
-	AUTHENTICATION_ERROR: [401, 407];
-};
-export const ACK = 'ACK';
-export const BYE = 'BYE';
-export const CANCEL = 'CANCEL';
-export const INFO = 'INFO';
-export const INVITE = 'INVITE';
-export const MESSAGE = 'MESSAGE';
-export const NOTIFY = 'NOTIFY';
-export const OPTIONS = 'OPTIONS';
-export const REGISTER = 'REGISTER';
-export const REFER = 'REFER';
-export const UPDATE = 'UPDATE';
-export const SUBSCRIBE = 'SUBSCRIBE';
-
-export declare enum DTMF_TRANSPORT {
-	// eslint-disable-next-line no-shadow
-	INFO = 'INFO',
-	RFC2833 = 'RFC2833',
-}
-
-export const REASON_PHRASE: Record<number, string>;
-export const ALLOWED_METHODS =
-	'INVITE,ACK,CANCEL,BYE,UPDATE,MESSAGE,OPTIONS,REFER,INFO,NOTIFY,SUBSCRIBE';
-export const ACCEPTED_BODY_TYPES = 'application/sdp, application/dtmf-relay';
-export const MAX_FORWARDS = 69;
-export const SESSION_EXPIRES = 90;
-export const MIN_SESSION_EXPIRES = 60;
-export const CONNECTION_RECOVERY_MAX_INTERVAL = 30;
-export const CONNECTION_RECOVERY_MIN_INTERVAL = 2;
diff --git a/src/Constants.js b/src/Constants.js
deleted file mode 100644
index 0212f56..0000000
--- a/src/Constants.js
+++ /dev/null
@@ -1,160 +0,0 @@
-const pkg = require('../package.json');
-
-module.exports = {
-	USER_AGENT: `${pkg.title} ${pkg.version}`,
-
-	// SIP scheme.
-	SIP: 'sip',
-	SIPS: 'sips',
-
-	// End and Failure causes.
-	causes: {
-		// Generic error causes.
-		CONNECTION_ERROR: 'Connection Error',
-		REQUEST_TIMEOUT: 'Request Timeout',
-		SIP_FAILURE_CODE: 'SIP Failure Code',
-		INTERNAL_ERROR: 'Internal Error',
-
-		// SIP error causes.
-		BUSY: 'Busy',
-		REJECTED: 'Rejected',
-		REDIRECTED: 'Redirected',
-		UNAVAILABLE: 'Unavailable',
-		NOT_FOUND: 'Not Found',
-		ADDRESS_INCOMPLETE: 'Address Incomplete',
-		INCOMPATIBLE_SDP: 'Incompatible SDP',
-		MISSING_SDP: 'Missing SDP',
-		AUTHENTICATION_ERROR: 'Authentication Error',
-
-		// Session error causes.
-		BYE: 'Terminated',
-		WEBRTC_ERROR: 'WebRTC Error',
-		CANCELED: 'Canceled',
-		NO_ANSWER: 'No Answer',
-		EXPIRES: 'Expires',
-		NO_ACK: 'No ACK',
-		DIALOG_ERROR: 'Dialog Error',
-		USER_DENIED_MEDIA_ACCESS: 'User Denied Media Access',
-		BAD_MEDIA_DESCRIPTION: 'Bad Media Description',
-		RTP_TIMEOUT: 'RTP Timeout',
-	},
-
-	SIP_ERROR_CAUSES: {
-		REDIRECTED: [300, 301, 302, 305, 380],
-		BUSY: [486, 600],
-		REJECTED: [403, 603],
-		NOT_FOUND: [404, 604],
-		UNAVAILABLE: [480, 410, 408, 430],
-		ADDRESS_INCOMPLETE: [484, 424],
-		INCOMPATIBLE_SDP: [488, 606],
-		AUTHENTICATION_ERROR: [401, 407],
-	},
-
-	// SIP Methods.
-	ACK: 'ACK',
-	BYE: 'BYE',
-	CANCEL: 'CANCEL',
-	INFO: 'INFO',
-	INVITE: 'INVITE',
-	MESSAGE: 'MESSAGE',
-	NOTIFY: 'NOTIFY',
-	OPTIONS: 'OPTIONS',
-	REGISTER: 'REGISTER',
-	REFER: 'REFER',
-	UPDATE: 'UPDATE',
-	SUBSCRIBE: 'SUBSCRIBE',
-
-	// DTMF transport methods.
-	DTMF_TRANSPORT: {
-		INFO: 'INFO',
-		RFC2833: 'RFC2833',
-	},
-
-	/* SIP Response Reasons
-	 * DOC: https://www.iana.org/assignments/sip-parameters
-	 * Copied from https://github.com/versatica/OverSIP/blob/master/lib/oversip/sip/constants.rb#L7
-	 */
-	REASON_PHRASE: {
-		100: 'Trying',
-		180: 'Ringing',
-		181: 'Call Is Being Forwarded',
-		182: 'Queued',
-		183: 'Session Progress',
-		199: 'Early Dialog Terminated', // draft-ietf-sipcore-199
-		200: 'OK',
-		202: 'Accepted', // RFC 3265
-		204: 'No Notification', // RFC 5839
-		300: 'Multiple Choices',
-		301: 'Moved Permanently',
-		302: 'Moved Temporarily',
-		305: 'Use Proxy',
-		380: 'Alternative Service',
-		400: 'Bad Request',
-		401: 'Unauthorized',
-		402: 'Payment Required',
-		403: 'Forbidden',
-		404: 'Not Found',
-		405: 'Method Not Allowed',
-		406: 'Not Acceptable',
-		407: 'Proxy Authentication Required',
-		408: 'Request Timeout',
-		410: 'Gone',
-		412: 'Conditional Request Failed', // RFC 3903
-		413: 'Request Entity Too Large',
-		414: 'Request-URI Too Long',
-		415: 'Unsupported Media Type',
-		416: 'Unsupported URI Scheme',
-		417: 'Unknown Resource-Priority', // RFC 4412
-		420: 'Bad Extension',
-		421: 'Extension Required',
-		422: 'Session Interval Too Small', // RFC 4028
-		423: 'Interval Too Brief',
-		424: 'Bad Location Information', // RFC 6442
-		428: 'Use Identity Header', // RFC 4474
-		429: 'Provide Referrer Identity', // RFC 3892
-		430: 'Flow Failed', // RFC 5626
-		433: 'Anonymity Disallowed', // RFC 5079
-		436: 'Bad Identity-Info', // RFC 4474
-		437: 'Unsupported Certificate', // RFC 4744
-		438: 'Invalid Identity Header', // RFC 4744
-		439: 'First Hop Lacks Outbound Support', // RFC 5626
-		440: 'Max-Breadth Exceeded', // RFC 5393
-		469: 'Bad Info Package', // draft-ietf-sipcore-info-events
-		470: 'Consent Needed', // RFC 5360
-		478: 'Unresolvable Destination', // Custom code copied from Kamailio.
-		480: 'Temporarily Unavailable',
-		481: 'Call/Transaction Does Not Exist',
-		482: 'Loop Detected',
-		483: 'Too Many Hops',
-		484: 'Address Incomplete',
-		485: 'Ambiguous',
-		486: 'Busy Here',
-		487: 'Request Terminated',
-		488: 'Not Acceptable Here',
-		489: 'Bad Event', // RFC 3265
-		491: 'Request Pending',
-		493: 'Undecipherable',
-		494: 'Security Agreement Required', // RFC 3329
-		500: 'JsSIP Internal Error',
-		501: 'Not Implemented',
-		502: 'Bad Gateway',
-		503: 'Service Unavailable',
-		504: 'Server Time-out',
-		505: 'Version Not Supported',
-		513: 'Message Too Large',
-		580: 'Precondition Failure', // RFC 3312
-		600: 'Busy Everywhere',
-		603: 'Decline',
-		604: 'Does Not Exist Anywhere',
-		606: 'Not Acceptable',
-	},
-
-	ALLOWED_METHODS:
-		'INVITE,ACK,CANCEL,BYE,UPDATE,MESSAGE,OPTIONS,REFER,INFO,NOTIFY,SUBSCRIBE',
-	ACCEPTED_BODY_TYPES: 'application/sdp, application/dtmf-relay',
-	MAX_FORWARDS: 69,
-	SESSION_EXPIRES: 90,
-	MIN_SESSION_EXPIRES: 60,
-	CONNECTION_RECOVERY_MAX_INTERVAL: 30,
-	CONNECTION_RECOVERY_MIN_INTERVAL: 2,
-};
diff --git a/src/Constants.ts b/src/Constants.ts
new file mode 100644
index 0000000..df84da3
--- /dev/null
+++ b/src/Constants.ts
@@ -0,0 +1,163 @@
+// `package.json` lives outside `rootDir` so it cannot be `import`ed. A plain
+// `require()` is not resolved by TypeScript against `rootDir`, so it is used
+// here instead.
+// eslint-disable-next-line @typescript-eslint/no-require-imports
+const pkg = require('../package.json') as { title: string; version: string };
+
+export const USER_AGENT = `${pkg.title} ${pkg.version}`;
+
+// SIP scheme.
+export const SIP = 'sip';
+export const SIPS = 'sips';
+
+// End and Failure causes.
+export enum causes {
+	// Generic error causes.
+	CONNECTION_ERROR = 'Connection Error',
+	REQUEST_TIMEOUT = 'Request Timeout',
+	SIP_FAILURE_CODE = 'SIP Failure Code',
+	INTERNAL_ERROR = 'Internal Error',
+
+	// SIP error causes.
+	BUSY = 'Busy',
+	REJECTED = 'Rejected',
+	REDIRECTED = 'Redirected',
+	UNAVAILABLE = 'Unavailable',
+	NOT_FOUND = 'Not Found',
+	ADDRESS_INCOMPLETE = 'Address Incomplete',
+	INCOMPATIBLE_SDP = 'Incompatible SDP',
+	MISSING_SDP = 'Missing SDP',
+	AUTHENTICATION_ERROR = 'Authentication Error',
+
+	// Session error causes.
+	BYE = 'Terminated',
+	WEBRTC_ERROR = 'WebRTC Error',
+	CANCELED = 'Canceled',
+	NO_ANSWER = 'No Answer',
+	EXPIRES = 'Expires',
+	NO_ACK = 'No ACK',
+	DIALOG_ERROR = 'Dialog Error',
+	USER_DENIED_MEDIA_ACCESS = 'User Denied Media Access',
+	BAD_MEDIA_DESCRIPTION = 'Bad Media Description',
+	RTP_TIMEOUT = 'RTP Timeout',
+}
+
+export const SIP_ERROR_CAUSES: Record<string, number[]> = {
+	REDIRECTED: [300, 301, 302, 305, 380],
+	BUSY: [486, 600],
+	REJECTED: [403, 603],
+	NOT_FOUND: [404, 604],
+	UNAVAILABLE: [480, 410, 408, 430],
+	ADDRESS_INCOMPLETE: [484, 424],
+	INCOMPATIBLE_SDP: [488, 606],
+	AUTHENTICATION_ERROR: [401, 407],
+};
+
+// SIP Methods.
+export const ACK = 'ACK';
+export const BYE = 'BYE';
+export const CANCEL = 'CANCEL';
+export const INFO = 'INFO';
+export const INVITE = 'INVITE';
+export const MESSAGE = 'MESSAGE';
+export const NOTIFY = 'NOTIFY';
+export const OPTIONS = 'OPTIONS';
+export const REGISTER = 'REGISTER';
+export const REFER = 'REFER';
+export const UPDATE = 'UPDATE';
+export const SUBSCRIBE = 'SUBSCRIBE';
+
+// DTMF transport methods.
+export enum DTMF_TRANSPORT {
+	// eslint-disable-next-line no-shadow
+	INFO = 'INFO',
+	RFC2833 = 'RFC2833',
+}
+
+/* SIP Response Reasons
+ * DOC: https://www.iana.org/assignments/sip-parameters
+ * Copied from https://github.com/versatica/OverSIP/blob/master/lib/oversip/sip/constants.rb#L7
+ */
+export const REASON_PHRASE: Record<number, string> = {
+	100: 'Trying',
+	180: 'Ringing',
+	181: 'Call Is Being Forwarded',
+	182: 'Queued',
+	183: 'Session Progress',
+	199: 'Early Dialog Terminated', // draft-ietf-sipcore-199
+	200: 'OK',
+	202: 'Accepted', // RFC 3265
+	204: 'No Notification', // RFC 5839
+	300: 'Multiple Choices',
+	301: 'Moved Permanently',
+	302: 'Moved Temporarily',
+	305: 'Use Proxy',
+	380: 'Alternative Service',
+	400: 'Bad Request',
+	401: 'Unauthorized',
+	402: 'Payment Required',
+	403: 'Forbidden',
+	404: 'Not Found',
+	405: 'Method Not Allowed',
+	406: 'Not Acceptable',
+	407: 'Proxy Authentication Required',
+	408: 'Request Timeout',
+	410: 'Gone',
+	412: 'Conditional Request Failed', // RFC 3903
+	413: 'Request Entity Too Large',
+	414: 'Request-URI Too Long',
+	415: 'Unsupported Media Type',
+	416: 'Unsupported URI Scheme',
+	417: 'Unknown Resource-Priority', // RFC 4412
+	420: 'Bad Extension',
+	421: 'Extension Required',
+	422: 'Session Interval Too Small', // RFC 4028
+	423: 'Interval Too Brief',
+	424: 'Bad Location Information', // RFC 6442
+	428: 'Use Identity Header', // RFC 4474
+	429: 'Provide Referrer Identity', // RFC 3892
+	430: 'Flow Failed', // RFC 5626
+	433: 'Anonymity Disallowed', // RFC 5079
+	436: 'Bad Identity-Info', // RFC 4474
+	437: 'Unsupported Certificate', // RFC 4744
+	438: 'Invalid Identity Header', // RFC 4744
+	439: 'First Hop Lacks Outbound Support', // RFC 5626
+	440: 'Max-Breadth Exceeded', // RFC 5393
+	469: 'Bad Info Package', // draft-ietf-sipcore-info-events
+	470: 'Consent Needed', // RFC 5360
+	478: 'Unresolvable Destination', // Custom code copied from Kamailio.
+	480: 'Temporarily Unavailable',
+	481: 'Call/Transaction Does Not Exist',
+	482: 'Loop Detected',
+	483: 'Too Many Hops',
+	484: 'Address Incomplete',
+	485: 'Ambiguous',
+	486: 'Busy Here',
+	487: 'Request Terminated',
+	488: 'Not Acceptable Here',
+	489: 'Bad Event', // RFC 3265
+	491: 'Request Pending',
+	493: 'Undecipherable',
+	494: 'Security Agreement Required', // RFC 3329
+	500: 'JsSIP Internal Error',
+	501: 'Not Implemented',
+	502: 'Bad Gateway',
+	503: 'Service Unavailable',
+	504: 'Server Time-out',
+	505: 'Version Not Supported',
+	513: 'Message Too Large',
+	580: 'Precondition Failure', // RFC 3312
+	600: 'Busy Everywhere',
+	603: 'Decline',
+	604: 'Does Not Exist Anywhere',
+	606: 'Not Acceptable',
+};
+
+export const ALLOWED_METHODS =
+	'INVITE,ACK,CANCEL,BYE,UPDATE,MESSAGE,OPTIONS,REFER,INFO,NOTIFY,SUBSCRIBE';
+export const ACCEPTED_BODY_TYPES = 'application/sdp, application/dtmf-relay';
+export const MAX_FORWARDS = 69;
+export const SESSION_EXPIRES = 90;
+export const MIN_SESSION_EXPIRES = 60;
+export const CONNECTION_RECOVERY_MAX_INTERVAL = 30;
+export const CONNECTION_RECOVERY_MIN_INTERVAL = 2;
diff --git a/src/Dialog.js b/src/Dialog.js
index 6b72c62..70ec586 100644
--- a/src/Dialog.js
+++ b/src/Dialog.js
@@ -1,4 +1,4 @@
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const SIPMessage = require('./SIPMessage');
 const JsSIP_C = require('./Constants');
 const Transactions = require('./Transactions');
diff --git a/src/DigestAuthentication.js b/src/DigestAuthentication.js
index 4758cad..fb90d81 100644
--- a/src/DigestAuthentication.js
+++ b/src/DigestAuthentication.js
@@ -1,4 +1,4 @@
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const Utils = require('./Utils');

 const logger = new Logger('DigestAuthentication');
diff --git a/src/Exceptions.d.ts b/src/Exceptions.d.ts
deleted file mode 100644
index 818bf4a..0000000
--- a/src/Exceptions.d.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-declare class BaseError extends Error {
-	code: number;
-}
-
-export class ConfigurationError extends BaseError {
-	parameter: string;
-	// eslint-disable-next-line @typescript-eslint/no-explicit-any
-	value: any;
-
-	// eslint-disable-next-line @typescript-eslint/no-explicit-any
-	constructor(parameter: string, value?: any);
-}
-
-export class InvalidStateError extends BaseError {
-	status: number;
-
-	constructor(status: number);
-}
-
-export class NotSupportedError extends BaseError {
-	constructor(message: string);
-}
-
-export class NotReadyError extends BaseError {
-	constructor(message: string);
-}
diff --git a/src/Exceptions.js b/src/Exceptions.js
deleted file mode 100644
index 00d9ebc..0000000
--- a/src/Exceptions.js
+++ /dev/null
@@ -1,51 +0,0 @@
-class ConfigurationError extends Error {
-	constructor(parameter, value) {
-		super();
-
-		this.code = 1;
-		this.name = 'CONFIGURATION_ERROR';
-		this.parameter = parameter;
-		this.value = value;
-		this.message = !this.value
-			? `Missing parameter: ${this.parameter}`
-			: `Invalid value ${JSON.stringify(this.value)} for parameter "${this.parameter}"`;
-	}
-}
-
-class InvalidStateError extends Error {
-	constructor(status) {
-		super();
-
-		this.code = 2;
-		this.name = 'INVALID_STATE_ERROR';
-		this.status = status;
-		this.message = `Invalid status: ${status}`;
-	}
-}
-
-class NotSupportedError extends Error {
-	constructor(message) {
-		super();
-
-		this.code = 3;
-		this.name = 'NOT_SUPPORTED_ERROR';
-		this.message = message;
-	}
-}
-
-class NotReadyError extends Error {
-	constructor(message) {
-		super();
-
-		this.code = 4;
-		this.name = 'NOT_READY_ERROR';
-		this.message = message;
-	}
-}
-
-module.exports = {
-	ConfigurationError,
-	InvalidStateError,
-	NotSupportedError,
-	NotReadyError,
-};
diff --git a/src/Exceptions.ts b/src/Exceptions.ts
new file mode 100644
index 0000000..16e736e
--- /dev/null
+++ b/src/Exceptions.ts
@@ -0,0 +1,53 @@
+export class ConfigurationError extends Error {
+	code = 1;
+	override name = 'CONFIGURATION_ERROR';
+	parameter: string;
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
+	value: any;
+
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
+	constructor(parameter: string, value?: any) {
+		super();
+
+		this.parameter = parameter;
+		this.value = value;
+		this.message = !this.value
+			? `Missing parameter: ${this.parameter}`
+			: `Invalid value ${JSON.stringify(this.value)} for parameter "${this.parameter}"`;
+	}
+}
+
+export class InvalidStateError extends Error {
+	code = 2;
+	override name = 'INVALID_STATE_ERROR';
+	status: number;
+
+	constructor(status: number) {
+		super();
+
+		this.status = status;
+		this.message = `Invalid status: ${status}`;
+	}
+}
+
+export class NotSupportedError extends Error {
+	code = 3;
+	override name = 'NOT_SUPPORTED_ERROR';
+
+	constructor(message: string) {
+		super();
+
+		this.message = message;
+	}
+}
+
+export class NotReadyError extends Error {
+	code = 4;
+	override name = 'NOT_READY_ERROR';
+
+	constructor(message: string) {
+		super();
+
+		this.message = message;
+	}
+}
diff --git a/src/Logger.js b/src/Logger.js
deleted file mode 100644
index 4abb8e7..0000000
--- a/src/Logger.js
+++ /dev/null
@@ -1,34 +0,0 @@
-const debug = require('debug');
-
-const APP_NAME = 'JsSIP';
-
-module.exports = class Logger {
-	constructor(prefix) {
-		if (prefix) {
-			this._debug = debug.default(`${APP_NAME}:${prefix}`);
-			this._warn = debug.default(`${APP_NAME}:WARN:${prefix}`);
-			this._error = debug.default(`${APP_NAME}:ERROR:${prefix}`);
-		} else {
-			this._debug = debug.default(APP_NAME);
-			this._warn = debug.default(`${APP_NAME}:WARN`);
-			this._error = debug.default(`${APP_NAME}:ERROR`);
-		}
-		/* eslint-disable no-console */
-		this._debug.log = console.info.bind(console);
-		this._warn.log = console.warn.bind(console);
-		this._error.log = console.error.bind(console);
-		/* eslint-enable no-console */
-	}
-
-	get debug() {
-		return this._debug;
-	}
-
-	get warn() {
-		return this._warn;
-	}
-
-	get error() {
-		return this._error;
-	}
-};
diff --git a/src/Logger.ts b/src/Logger.ts
new file mode 100644
index 0000000..f9927fd
--- /dev/null
+++ b/src/Logger.ts
@@ -0,0 +1,39 @@
+import debug from 'debug';
+
+const APP_NAME = 'JsSIP';
+
+export class Logger {
+	#_debug: debug.Debugger;
+	#_warn: debug.Debugger;
+	#_error: debug.Debugger;
+
+	constructor(prefix?: string) {
+		if (prefix) {
+			this.#_debug = debug(`${APP_NAME}:${prefix}`);
+			this.#_warn = debug(`${APP_NAME}:WARN:${prefix}`);
+			this.#_error = debug(`${APP_NAME}:ERROR:${prefix}`);
+		} else {
+			this.#_debug = debug(APP_NAME);
+			this.#_warn = debug(`${APP_NAME}:WARN`);
+			this.#_error = debug(`${APP_NAME}:ERROR`);
+		}
+
+		/* eslint-disable no-console */
+		this.#_debug.log = console.info.bind(console);
+		this.#_warn.log = console.warn.bind(console);
+		this.#_error.log = console.error.bind(console);
+		/* eslint-enable no-console */
+	}
+
+	get debug(): debug.Debugger {
+		return this.#_debug;
+	}
+
+	get warn(): debug.Debugger {
+		return this.#_warn;
+	}
+
+	get error(): debug.Debugger {
+		return this.#_error;
+	}
+}
diff --git a/src/Message.js b/src/Message.js
index 801f823..1c15b7d 100644
--- a/src/Message.js
+++ b/src/Message.js
@@ -1,5 +1,5 @@
 const EventEmitter = require('events').EventEmitter;
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const JsSIP_C = require('./Constants');
 const SIPMessage = require('./SIPMessage');
 const Utils = require('./Utils');
diff --git a/src/Notifier.js b/src/Notifier.js
index deea8bb..0596533 100644
--- a/src/Notifier.js
+++ b/src/Notifier.js
@@ -1,6 +1,6 @@
 const EventEmitter = require('events').EventEmitter;
 const Exceptions = require('./Exceptions');
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const JsSIP_C = require('./Constants');
 const Utils = require('./Utils');
 const Dialog = require('./Dialog');
diff --git a/src/Options.js b/src/Options.js
index f01c35d..07a70de 100644
--- a/src/Options.js
+++ b/src/Options.js
@@ -1,5 +1,5 @@
 const EventEmitter = require('events').EventEmitter;
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const JsSIP_C = require('./Constants');
 const SIPMessage = require('./SIPMessage');
 const Utils = require('./Utils');
diff --git a/src/Parser.js b/src/Parser.js
index 79013d9..a2e67be 100644
--- a/src/Parser.js
+++ b/src/Parser.js
@@ -1,4 +1,4 @@
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const Grammar = require('./Grammar');
 const SIPMessage = require('./SIPMessage');

diff --git a/src/RTCSession.js b/src/RTCSession.js
index 9aa40ac..97e1745 100644
--- a/src/RTCSession.js
+++ b/src/RTCSession.js
@@ -3,7 +3,7 @@

 const EventEmitter = require('events').EventEmitter;
 const sdp_transform = require('sdp-transform');
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const JsSIP_C = require('./Constants');
 const Exceptions = require('./Exceptions');
 const Transactions = require('./Transactions');
diff --git a/src/RTCSession/DTMF.js b/src/RTCSession/DTMF.js
index abacc0f..1f96950 100644
--- a/src/RTCSession/DTMF.js
+++ b/src/RTCSession/DTMF.js
@@ -1,5 +1,5 @@
 const EventEmitter = require('events').EventEmitter;
-const Logger = require('../Logger');
+const { Logger } = require('../Logger');
 const JsSIP_C = require('../Constants');
 const Exceptions = require('../Exceptions');
 const Utils = require('../Utils');
diff --git a/src/RTCSession/ReferNotifier.js b/src/RTCSession/ReferNotifier.js
index 4af0ea0..8369e65 100644
--- a/src/RTCSession/ReferNotifier.js
+++ b/src/RTCSession/ReferNotifier.js
@@ -1,4 +1,4 @@
-const Logger = require('../Logger');
+const { Logger } = require('../Logger');
 const JsSIP_C = require('../Constants');

 const logger = new Logger('RTCSession:ReferNotifier');
diff --git a/src/RTCSession/ReferSubscriber.js b/src/RTCSession/ReferSubscriber.js
index 84d82e9..e8aca9b 100644
--- a/src/RTCSession/ReferSubscriber.js
+++ b/src/RTCSession/ReferSubscriber.js
@@ -1,5 +1,5 @@
 const EventEmitter = require('events').EventEmitter;
-const Logger = require('../Logger');
+const { Logger } = require('../Logger');
 const JsSIP_C = require('../Constants');
 const Grammar = require('../Grammar');
 const Utils = require('../Utils');
diff --git a/src/Registrator.js b/src/Registrator.js
index 7ce227b..0085139 100644
--- a/src/Registrator.js
+++ b/src/Registrator.js
@@ -1,4 +1,4 @@
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const Utils = require('./Utils');
 const JsSIP_C = require('./Constants');
 const SIPMessage = require('./SIPMessage');
diff --git a/src/RequestSender.js b/src/RequestSender.js
index c7ddc35..50d4faa 100644
--- a/src/RequestSender.js
+++ b/src/RequestSender.js
@@ -1,4 +1,4 @@
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const JsSIP_C = require('./Constants');
 const DigestAuthentication = require('./DigestAuthentication');
 const Transactions = require('./Transactions');
diff --git a/src/SIPMessage.js b/src/SIPMessage.js
index e2d205f..44b08e7 100644
--- a/src/SIPMessage.js
+++ b/src/SIPMessage.js
@@ -1,5 +1,5 @@
 const sdp_transform = require('sdp-transform');
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const JsSIP_C = require('./Constants');
 const Utils = require('./Utils');
 const NameAddrHeader = require('./NameAddrHeader');
diff --git a/src/Socket.js b/src/Socket.js
index e502360..5c9ffc1 100644
--- a/src/Socket.js
+++ b/src/Socket.js
@@ -1,4 +1,4 @@
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const Utils = require('./Utils');
 const Grammar = require('./Grammar');

diff --git a/src/Subscriber.js b/src/Subscriber.js
index 13f8bde..50bb0b8 100644
--- a/src/Subscriber.js
+++ b/src/Subscriber.js
@@ -1,6 +1,6 @@
 const EventEmitter = require('events').EventEmitter;
 const Exceptions = require('./Exceptions');
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const JsSIP_C = require('./Constants');
 const Utils = require('./Utils');
 const Grammar = require('./Grammar');
diff --git a/src/Timers.js b/src/Timers.js
deleted file mode 100644
index 2af8bfa..0000000
--- a/src/Timers.js
+++ /dev/null
@@ -1,19 +0,0 @@
-const T1 = 500,
-	T2 = 4000,
-	T4 = 5000;
-
-module.exports = {
-	T1,
-	T2,
-	T4,
-	TIMER_B: 64 * T1,
-	TIMER_D: 0 * T1,
-	TIMER_F: 64 * T1,
-	TIMER_H: 64 * T1,
-	TIMER_I: 0 * T1,
-	TIMER_J: 0 * T1,
-	TIMER_K: 0 * T4,
-	TIMER_L: 64 * T1,
-	TIMER_M: 64 * T1,
-	PROVISIONAL_RESPONSE_INTERVAL: 60000, // See RFC 3261 Section 13.3.1.1
-};
diff --git a/src/Timers.ts b/src/Timers.ts
new file mode 100644
index 0000000..563edef
--- /dev/null
+++ b/src/Timers.ts
@@ -0,0 +1,16 @@
+export const T1 = 500;
+export const T2 = 4000;
+export const T4 = 5000;
+
+export const TIMER_B = 64 * T1;
+export const TIMER_D = 0 * T1;
+export const TIMER_F = 64 * T1;
+export const TIMER_H = 64 * T1;
+export const TIMER_I = 0 * T1;
+export const TIMER_J = 0 * T1;
+export const TIMER_K = 0 * T4;
+export const TIMER_L = 64 * T1;
+export const TIMER_M = 64 * T1;
+
+// See RFC 3261 Section 13.3.1.1.
+export const PROVISIONAL_RESPONSE_INTERVAL = 60000;
diff --git a/src/Transactions.js b/src/Transactions.js
index 4978bc9..8cc73a2 100644
--- a/src/Transactions.js
+++ b/src/Transactions.js
@@ -1,5 +1,5 @@
 const EventEmitter = require('events').EventEmitter;
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const JsSIP_C = require('./Constants');
 const SIPMessage = require('./SIPMessage');
 const Timers = require('./Timers');
diff --git a/src/Transport.js b/src/Transport.js
index 258382c..008d335 100644
--- a/src/Transport.js
+++ b/src/Transport.js
@@ -1,4 +1,4 @@
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const Socket = require('./Socket');
 const JsSIP_C = require('./Constants');

diff --git a/src/UA.js b/src/UA.js
index 687ff37..2c51bb6 100644
--- a/src/UA.js
+++ b/src/UA.js
@@ -1,7 +1,7 @@
 /* eslint-disable no-invalid-this */

 const EventEmitter = require('events').EventEmitter;
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const JsSIP_C = require('./Constants');
 const Registrator = require('./Registrator');
 const RTCSession = require('./RTCSession');
diff --git a/src/WebSocketInterface.js b/src/WebSocketInterface.js
index 9dc582e..852de85 100644
--- a/src/WebSocketInterface.js
+++ b/src/WebSocketInterface.js
@@ -1,4 +1,4 @@
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const Grammar = require('./Grammar');

 const logger = new Logger('WebSocketInterface');
diff --git a/src/sanityCheck.js b/src/sanityCheck.js
index ef3d661..5ff60ac 100644
--- a/src/sanityCheck.js
+++ b/src/sanityCheck.js
@@ -1,4 +1,4 @@
-const Logger = require('./Logger');
+const { Logger } = require('./Logger');
 const JsSIP_C = require('./Constants');
 const SIPMessage = require('./SIPMessage');
 const Utils = require('./Utils');
diff --git a/tsconfig.json b/tsconfig.json
index 76528d3..cc59ba4 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,11 +7,11 @@
 		"outDir": "lib",
 		"rootDir": "src",
 		"allowJs": true,
-		// Do not generate declaration files automatically yet.
-		// "declaration": true,
-		// "declarationMap": true,
-		// "emitDeclarationOnly": false,
-		// "declarationDir": "lib",
+		// Declaration files are generated for .ts sources. Modules not yet ported
+		// to TypeScript still ship the hand-written .d.ts files in src/, which
+		// npm-scripts.mjs copies over the generated ones.
+		"declaration": true,
+		"declarationMap": true,
 		"isolatedModules": true,
 		"verbatimModuleSyntax": false,
 		"useDefineForClassFields": true,