@signalwire/js 4.0.0-dev-20260305000713 → 4.0.0-dev-20260310150416
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.mjs +184 -22
- package/dist/browser.mjs.map +1 -1
- package/dist/browser.umd.js +184 -22
- package/dist/browser.umd.js.map +1 -1
- package/dist/index.cjs +174 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +14 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +175 -22
- package/dist/index.mjs.map +1 -1
- package/dist/operators/index.cjs +1 -1
- package/dist/operators/index.mjs +1 -1
- package/dist/{operators-BHQMSEzq.mjs → operators-BI5Gskex.mjs} +11 -2
- package/dist/operators-BI5Gskex.mjs.map +1 -0
- package/dist/{operators-DT4UB24-.cjs → operators-DJmUGfZo.cjs} +16 -1
- package/dist/operators-DJmUGfZo.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/operators-BHQMSEzq.mjs.map +0 -1
- package/dist/operators-DT4UB24-.cjs.map +0 -1
package/dist/browser.mjs
CHANGED
|
@@ -6915,7 +6915,7 @@ var require_startWith = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
6915
6915
|
var concat_1$2 = require_concat$1();
|
|
6916
6916
|
var args_1$2 = require_args();
|
|
6917
6917
|
var lift_1$14 = require_lift();
|
|
6918
|
-
function startWith() {
|
|
6918
|
+
function startWith$1() {
|
|
6919
6919
|
var values = [];
|
|
6920
6920
|
for (var _i = 0; _i < arguments.length; _i++) values[_i] = arguments[_i];
|
|
6921
6921
|
var scheduler = args_1$2.popScheduler(values);
|
|
@@ -6923,7 +6923,7 @@ var require_startWith = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
6923
6923
|
(scheduler ? concat_1$2.concat(values, source, scheduler) : concat_1$2.concat(values, source)).subscribe(subscriber);
|
|
6924
6924
|
});
|
|
6925
6925
|
}
|
|
6926
|
-
exports.startWith = startWith;
|
|
6926
|
+
exports.startWith = startWith$1;
|
|
6927
6927
|
}));
|
|
6928
6928
|
|
|
6929
6929
|
//#endregion
|
|
@@ -9122,6 +9122,15 @@ var JSONRPCError = class extends Error {
|
|
|
9122
9122
|
this.name = "JSONRPCError";
|
|
9123
9123
|
}
|
|
9124
9124
|
};
|
|
9125
|
+
var RPCError = class extends Error {
|
|
9126
|
+
constructor(code, requestId, message, data, options) {
|
|
9127
|
+
super(message, options);
|
|
9128
|
+
this.code = code;
|
|
9129
|
+
this.requestId = requestId;
|
|
9130
|
+
this.data = data;
|
|
9131
|
+
this.name = "RPCError";
|
|
9132
|
+
}
|
|
9133
|
+
};
|
|
9125
9134
|
var InvalidParams = class extends Error {
|
|
9126
9135
|
constructor(message, options) {
|
|
9127
9136
|
super(message, options);
|
|
@@ -9660,7 +9669,7 @@ var PreferencesContainer = class PreferencesContainer {
|
|
|
9660
9669
|
skipDeviceMonitoring: false,
|
|
9661
9670
|
savePreferences: false
|
|
9662
9671
|
};
|
|
9663
|
-
this.receiveVideo =
|
|
9672
|
+
this.receiveVideo = false;
|
|
9664
9673
|
this.receiveAudio = true;
|
|
9665
9674
|
this.preferredAudioInput = null;
|
|
9666
9675
|
this.preferredAudioOutput = null;
|
|
@@ -12630,12 +12639,53 @@ var CallEventsManager = class extends Destroyable {
|
|
|
12630
12639
|
|
|
12631
12640
|
//#endregion
|
|
12632
12641
|
//#region src/helpers/SDPHelper.ts
|
|
12642
|
+
/** Valid SDP direction attribute values. */
|
|
12643
|
+
const SDP_DIRECTIONS = new Set([
|
|
12644
|
+
"sendrecv",
|
|
12645
|
+
"sendonly",
|
|
12646
|
+
"recvonly",
|
|
12647
|
+
"inactive"
|
|
12648
|
+
]);
|
|
12633
12649
|
/**
|
|
12634
|
-
*
|
|
12650
|
+
* Extracts the media directions (audio/video) from an SDP string.
|
|
12651
|
+
*
|
|
12652
|
+
* Parses each media section (`m=audio` / `m=video`) and reads the `a=` direction
|
|
12653
|
+
* attribute (`sendrecv`, `sendonly`, `recvonly`, `inactive`).
|
|
12654
|
+
* If no explicit direction attribute is found for a media section, defaults to `sendrecv`
|
|
12655
|
+
* per RFC 4566.
|
|
12656
|
+
*
|
|
12657
|
+
* @param sdp - The SDP string to parse
|
|
12658
|
+
* @returns The extracted audio and video directions
|
|
12635
12659
|
*
|
|
12636
|
-
*
|
|
12637
|
-
*
|
|
12660
|
+
* @example
|
|
12661
|
+
* ```typescript
|
|
12662
|
+
* const sdp = `v=0\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\na=sendrecv\r\nm=video 9 UDP/TLS/RTP/SAVPF 96\r\na=recvonly`;
|
|
12663
|
+
* extractMediaDirectionsFromSDP(sdp);
|
|
12664
|
+
* // { audio: 'sendrecv', video: 'recvonly' }
|
|
12665
|
+
* ```
|
|
12638
12666
|
*/
|
|
12667
|
+
function extractMediaDirectionsFromSDP(sdp) {
|
|
12668
|
+
const result = {
|
|
12669
|
+
audio: "inactive",
|
|
12670
|
+
video: "inactive"
|
|
12671
|
+
};
|
|
12672
|
+
if (!sdp) return result;
|
|
12673
|
+
const lines = sdp.split(/\r?\n/);
|
|
12674
|
+
let currentMediaKind = null;
|
|
12675
|
+
let currentDirection = null;
|
|
12676
|
+
for (const line of lines) if (line.startsWith("m=")) {
|
|
12677
|
+
if (currentMediaKind) result[currentMediaKind] = currentDirection ?? "sendrecv";
|
|
12678
|
+
if (line.startsWith("m=audio")) currentMediaKind = "audio";
|
|
12679
|
+
else if (line.startsWith("m=video")) currentMediaKind = "video";
|
|
12680
|
+
else currentMediaKind = null;
|
|
12681
|
+
currentDirection = null;
|
|
12682
|
+
} else if (currentMediaKind && line.startsWith("a=")) {
|
|
12683
|
+
const attr = line.substring(2).trim();
|
|
12684
|
+
if (SDP_DIRECTIONS.has(attr)) currentDirection = attr;
|
|
12685
|
+
}
|
|
12686
|
+
if (currentMediaKind) result[currentMediaKind] = currentDirection ?? "sendrecv";
|
|
12687
|
+
return result;
|
|
12688
|
+
}
|
|
12639
12689
|
/**
|
|
12640
12690
|
* Validates that an SDP string has at least one non-host ICE candidate
|
|
12641
12691
|
* for each media section (m= line).
|
|
@@ -12938,6 +12988,15 @@ var LocalStreamController = class extends Destroyable {
|
|
|
12938
12988
|
track.addEventListener("ended", this.mediaTrackEndedHandler);
|
|
12939
12989
|
}
|
|
12940
12990
|
/**
|
|
12991
|
+
* Update the controller options (e.g., when media overrides are applied).
|
|
12992
|
+
*/
|
|
12993
|
+
updateOptions(options) {
|
|
12994
|
+
this.options = {
|
|
12995
|
+
...this.options,
|
|
12996
|
+
...options
|
|
12997
|
+
};
|
|
12998
|
+
}
|
|
12999
|
+
/**
|
|
12941
13000
|
* Stop all local tracks and clean up.
|
|
12942
13001
|
*/
|
|
12943
13002
|
stopAllTracks() {
|
|
@@ -13281,6 +13340,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
13281
13340
|
this._initialized$ = this.createReplaySubject(1);
|
|
13282
13341
|
this._remoteDescription$ = this.createReplaySubject(1);
|
|
13283
13342
|
this._remoteStream$ = this.createBehaviorSubject(null);
|
|
13343
|
+
this._remoteOfferMediaDirections = null;
|
|
13284
13344
|
this.deviceController = deviceController ?? {};
|
|
13285
13345
|
this.id = options.callId ?? v4_default();
|
|
13286
13346
|
this._type = remoteSessionDescription ? "answer" : "offer";
|
|
@@ -13288,10 +13348,19 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
13288
13348
|
type: "offer",
|
|
13289
13349
|
sdp: remoteSessionDescription
|
|
13290
13350
|
} : void 0;
|
|
13351
|
+
this._remoteOfferMediaDirections = remoteSessionDescription ? extractMediaDirectionsFromSDP(remoteSessionDescription) : null;
|
|
13352
|
+
const offerDefaults = this._remoteOfferMediaDirections ? {
|
|
13353
|
+
audio: this._remoteOfferMediaDirections.audio.includes("recv"),
|
|
13354
|
+
video: this._remoteOfferMediaDirections.video.includes("recv"),
|
|
13355
|
+
receiveAudio: this._remoteOfferMediaDirections.audio.includes("send"),
|
|
13356
|
+
receiveVideo: this._remoteOfferMediaDirections.video.includes("send")
|
|
13357
|
+
} : {};
|
|
13291
13358
|
this.options = {
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13359
|
+
...options,
|
|
13360
|
+
audio: options.audio ?? offerDefaults.audio,
|
|
13361
|
+
video: options.video ?? offerDefaults.video,
|
|
13362
|
+
receiveAudio: options.receiveAudio ?? offerDefaults.receiveAudio ?? PreferencesContainer.instance.receiveAudio,
|
|
13363
|
+
receiveVideo: options.receiveVideo ?? offerDefaults.receiveVideo ?? PreferencesContainer.instance.receiveVideo
|
|
13295
13364
|
};
|
|
13296
13365
|
this.localStreamController = new LocalStreamController({
|
|
13297
13366
|
propose: this.propose,
|
|
@@ -13458,12 +13527,12 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
13458
13527
|
default: return {
|
|
13459
13528
|
...options,
|
|
13460
13529
|
offerToReceiveAudio: true,
|
|
13461
|
-
offerToReceiveVideo: Boolean(this.inputVideoDeviceConstraints)
|
|
13530
|
+
offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
|
|
13462
13531
|
};
|
|
13463
13532
|
}
|
|
13464
13533
|
}
|
|
13465
13534
|
get answerOptions() {
|
|
13466
|
-
return {};
|
|
13535
|
+
return { iceRestart: this.firstSDPExchangeCompleted ? true : void 0 };
|
|
13467
13536
|
}
|
|
13468
13537
|
/**
|
|
13469
13538
|
* Initialize the RTCPeerConnection and setup event listeners.
|
|
@@ -13498,11 +13567,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
13498
13567
|
});
|
|
13499
13568
|
await this.updateSelectedInputDevice(kind, deviceInfo);
|
|
13500
13569
|
});
|
|
13501
|
-
await this.setupTrackHandling();
|
|
13502
|
-
this._initialized$.next(true);
|
|
13503
13570
|
if (this.type === "answer" && this.sdpInit) {
|
|
13571
|
+
await this.setupRemoteTracks();
|
|
13572
|
+
this._initialized$.next(true);
|
|
13504
13573
|
this.setupEventListeners();
|
|
13505
|
-
|
|
13574
|
+
this._isNegotiating$.next(true);
|
|
13575
|
+
await this._setRemoteDescription(this.sdpInit);
|
|
13576
|
+
} else {
|
|
13577
|
+
await this.setupTrackHandling();
|
|
13578
|
+
this._initialized$.next(true);
|
|
13506
13579
|
}
|
|
13507
13580
|
} catch (error) {
|
|
13508
13581
|
logger$11.error("[RTCPeerConnectionController] Initialization error:", error);
|
|
@@ -13600,6 +13673,35 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
13600
13673
|
default:
|
|
13601
13674
|
}
|
|
13602
13675
|
}
|
|
13676
|
+
/**
|
|
13677
|
+
* Accept an inbound call by creating the SDP answer.
|
|
13678
|
+
* Optionally override media options before the answer is generated.
|
|
13679
|
+
* Must be called after initialization for inbound (answer-type) connections.
|
|
13680
|
+
*/
|
|
13681
|
+
async acceptInbound(mediaOverrides) {
|
|
13682
|
+
if (mediaOverrides) {
|
|
13683
|
+
const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
|
|
13684
|
+
this.options = {
|
|
13685
|
+
...this.options,
|
|
13686
|
+
...audio !== void 0 ? { audio } : {},
|
|
13687
|
+
...video !== void 0 ? { video } : {},
|
|
13688
|
+
...receiveAudio !== void 0 ? { receiveAudio } : {},
|
|
13689
|
+
...receiveVideo !== void 0 ? { receiveVideo } : {}
|
|
13690
|
+
};
|
|
13691
|
+
this.transceiverController?.updateOptions({
|
|
13692
|
+
receiveAudio: this.receiveAudio,
|
|
13693
|
+
receiveVideo: this.receiveVideo
|
|
13694
|
+
});
|
|
13695
|
+
this.localStreamController.updateOptions({
|
|
13696
|
+
inputAudioDeviceConstraints: this.inputAudioDeviceConstraints,
|
|
13697
|
+
inputVideoDeviceConstraints: this.inputVideoDeviceConstraints
|
|
13698
|
+
});
|
|
13699
|
+
}
|
|
13700
|
+
await this.setupLocalTracks();
|
|
13701
|
+
const { answerOptions } = this;
|
|
13702
|
+
logger$11.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
13703
|
+
await this.createAnswer(answerOptions);
|
|
13704
|
+
}
|
|
13603
13705
|
async handleOfferReceived() {
|
|
13604
13706
|
if (!this.sdpInit) throw new DependencyError("SDP initialization parameters are not set");
|
|
13605
13707
|
this._isNegotiating$.next(true);
|
|
@@ -13820,7 +13922,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
13820
13922
|
});
|
|
13821
13923
|
}
|
|
13822
13924
|
get mediaDirections() {
|
|
13823
|
-
return this.transceiverController?.getMediaDirections() ?? {
|
|
13925
|
+
return this.transceiverController?.getMediaDirections() ?? this._remoteOfferMediaDirections ?? {
|
|
13824
13926
|
audio: "inactive",
|
|
13825
13927
|
video: "inactive"
|
|
13826
13928
|
};
|
|
@@ -14151,6 +14253,36 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
14151
14253
|
this.subscribeTo(rtcPeerConnController.errors$, (error) => {
|
|
14152
14254
|
this.onError?.(error);
|
|
14153
14255
|
});
|
|
14256
|
+
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
14257
|
+
}
|
|
14258
|
+
async handleInboundAnswer(rtcPeerConnController) {
|
|
14259
|
+
logger$10.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
|
|
14260
|
+
const vertoByeOrAccepted = await (0, import_cjs$10.firstValueFrom)((0, import_cjs$10.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$10.takeUntil)(this.destroyed$))).catch(() => null);
|
|
14261
|
+
if (vertoByeOrAccepted === null) {
|
|
14262
|
+
logger$10.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
14263
|
+
return;
|
|
14264
|
+
}
|
|
14265
|
+
if (isVertoByeMessage(vertoByeOrAccepted)) {
|
|
14266
|
+
logger$10.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
14267
|
+
this.callSession?.destroy();
|
|
14268
|
+
} else if (!vertoByeOrAccepted) {
|
|
14269
|
+
logger$10.info("[WebRTCManager] Inbound call rejected by user.");
|
|
14270
|
+
try {
|
|
14271
|
+
await this.bye("USER_BUSY");
|
|
14272
|
+
} finally {
|
|
14273
|
+
this._signalingStatus$.next("disconnected");
|
|
14274
|
+
this.callSession?.destroy();
|
|
14275
|
+
}
|
|
14276
|
+
} else {
|
|
14277
|
+
logger$10.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
|
|
14278
|
+
const answerOptions = this.webRtcCallSession.answerMediaOptions;
|
|
14279
|
+
try {
|
|
14280
|
+
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
14281
|
+
} catch (error) {
|
|
14282
|
+
logger$10.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
14283
|
+
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
14284
|
+
}
|
|
14285
|
+
}
|
|
14154
14286
|
}
|
|
14155
14287
|
setupVertoAttachHandler() {
|
|
14156
14288
|
this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
|
|
@@ -14168,7 +14300,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
14168
14300
|
});
|
|
14169
14301
|
}
|
|
14170
14302
|
initObservables(rtcPeerConnController) {
|
|
14171
|
-
this.mediaDirections$ = rtcPeerConnController.connectionState$.pipe((0, import_cjs$10.filter)((state) => state === "connected"), (0, import_cjs$10.
|
|
14303
|
+
this.mediaDirections$ = rtcPeerConnController.connectionState$.pipe((0, import_cjs$10.filter)((state) => state === "connected"), (0, import_cjs$10.map)(() => rtcPeerConnController.mediaDirections), (0, import_cjs$10.startWith)(rtcPeerConnController.mediaDirections), (0, import_cjs$10.takeUntil)(this.destroyed$));
|
|
14172
14304
|
this.localStream$ = rtcPeerConnController.localStream$.pipe(filterNull(), (0, import_cjs$10.takeUntil)(this.destroyed$));
|
|
14173
14305
|
this.remoteStream$ = rtcPeerConnController.remoteStream$.pipe(filterNull(), (0, import_cjs$10.takeUntil)(this.destroyed$));
|
|
14174
14306
|
}
|
|
@@ -14850,10 +14982,15 @@ var WebRTCCall = class extends Destroyable {
|
|
|
14850
14982
|
async sendDigits(dtmf) {
|
|
14851
14983
|
return this.vertoManager.sendDigits(dtmf);
|
|
14852
14984
|
}
|
|
14853
|
-
/** Accepts an inbound call. */
|
|
14854
|
-
answer() {
|
|
14985
|
+
/** Accepts an inbound call, optionally overriding media options for the answer. */
|
|
14986
|
+
answer(options) {
|
|
14987
|
+
this._answerMediaOptions = options;
|
|
14855
14988
|
this._answered$.next(true);
|
|
14856
14989
|
}
|
|
14990
|
+
/** Media options provided when answering. Used internally by the VertoManager. */
|
|
14991
|
+
get answerMediaOptions() {
|
|
14992
|
+
return this._answerMediaOptions;
|
|
14993
|
+
}
|
|
14857
14994
|
/** Rejects an inbound call. */
|
|
14858
14995
|
reject() {
|
|
14859
14996
|
this._answered$.next(false);
|
|
@@ -15292,9 +15429,15 @@ var PendingRPC = class PendingRPC {
|
|
|
15292
15429
|
return () => signal.removeEventListener("abort", abortHandler);
|
|
15293
15430
|
}) : import_cjs$6.NEVER).subscribe({
|
|
15294
15431
|
next: (response) => {
|
|
15295
|
-
logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
|
|
15296
15432
|
isSettled = true;
|
|
15297
|
-
|
|
15433
|
+
if (response.error) {
|
|
15434
|
+
const rpcError = new RPCError(response.error.code, request.id, response.error.message, response.error.data);
|
|
15435
|
+
logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
|
|
15436
|
+
reject(rpcError);
|
|
15437
|
+
} else {
|
|
15438
|
+
logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
|
|
15439
|
+
resolve(response);
|
|
15440
|
+
}
|
|
15298
15441
|
subscription.unsubscribe();
|
|
15299
15442
|
},
|
|
15300
15443
|
error: (error) => {
|
|
@@ -16343,7 +16486,12 @@ var SignalWire = class extends Destroyable {
|
|
|
16343
16486
|
this._subscriber$.next(new Subscriber(this._deps.http));
|
|
16344
16487
|
if (!this._options.skipConnection) await this.connect();
|
|
16345
16488
|
if (!this._options.reconnectAttachedCalls && this._attachManager) await this._attachManager.flush();
|
|
16346
|
-
if (!this._options.skipRegister)
|
|
16489
|
+
if (!this._options.skipRegister) try {
|
|
16490
|
+
await this.register();
|
|
16491
|
+
} catch (error) {
|
|
16492
|
+
logger$1.error("[SignalWire] Registration failed:", error);
|
|
16493
|
+
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16494
|
+
}
|
|
16347
16495
|
this.handleAttachments();
|
|
16348
16496
|
}
|
|
16349
16497
|
async handleAttachments() {
|
|
@@ -16506,8 +16654,22 @@ var SignalWire = class extends Destroyable {
|
|
|
16506
16654
|
}));
|
|
16507
16655
|
this._isRegistered$.next(true);
|
|
16508
16656
|
} catch (error) {
|
|
16509
|
-
logger$1.
|
|
16657
|
+
logger$1.debug("[SignalWire] Failed to register subscriber, trying reauthentication...");
|
|
16658
|
+
if (this._deps.credential.token) this._clientSession.reauthenticate(this._deps.credential.token).then(async () => {
|
|
16659
|
+
logger$1.debug("[SignalWire] Reauthentication successful, retrying register()");
|
|
16660
|
+
await this._transport.execute(RPCExecute({
|
|
16661
|
+
method: "subscriber.online",
|
|
16662
|
+
params: {}
|
|
16663
|
+
}));
|
|
16664
|
+
this._isRegistered$.next(true);
|
|
16665
|
+
}).catch((reauthError) => {
|
|
16666
|
+
logger$1.error("[SignalWire] Reauthentication failed during register():", reauthError);
|
|
16667
|
+
const registerError = new InvalidCredentialsError("Failed to register subscriber, and reauthentication attempt also failed. Please check your credentials.", { cause: reauthError instanceof Error ? reauthError : new Error(String(reauthError), { cause: reauthError }) });
|
|
16668
|
+
this._errors$.next(registerError);
|
|
16669
|
+
throw registerError;
|
|
16670
|
+
});
|
|
16510
16671
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16672
|
+
throw error;
|
|
16511
16673
|
}
|
|
16512
16674
|
}
|
|
16513
16675
|
/** Unregisters the subscriber, going offline for inbound calls. */
|