@dora-cell/sdk 1.0.2 → 2.0.0
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/index.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +128 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +128 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -23033,7 +23033,30 @@ var ApiTokenAuthProvider = class {
|
|
|
23033
23033
|
if (!this.sessionToken) {
|
|
23034
23034
|
throw new AuthenticationError("No session token returned after key verification");
|
|
23035
23035
|
}
|
|
23036
|
-
|
|
23036
|
+
console.log("SDK: Performing secondary validation...");
|
|
23037
|
+
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
|
23038
|
+
const validateResponse = await fetch(`${baseUrl}/sdk/v1/auth/validate`, {
|
|
23039
|
+
method: "GET",
|
|
23040
|
+
headers: {
|
|
23041
|
+
"Authorization": `Bearer ${this.sessionToken}`,
|
|
23042
|
+
"x-dora-public-key": this.publicKey,
|
|
23043
|
+
"Origin": origin,
|
|
23044
|
+
"Accept": "application/json"
|
|
23045
|
+
}
|
|
23046
|
+
});
|
|
23047
|
+
if (!validateResponse.ok) {
|
|
23048
|
+
throw new AuthenticationError(
|
|
23049
|
+
`Secondary validation failed: ${validateResponse.status}`,
|
|
23050
|
+
{ status: validateResponse.status }
|
|
23051
|
+
);
|
|
23052
|
+
}
|
|
23053
|
+
const validateData = await validateResponse.json();
|
|
23054
|
+
console.log(`SDK: Secondary validation successful for "${validateData.app_name}"`);
|
|
23055
|
+
if (validateData.features && !validateData.features.includes("voice")) {
|
|
23056
|
+
console.warn('SDK: App token does not have the "voice" feature enabled.');
|
|
23057
|
+
}
|
|
23058
|
+
const actualResponseData = authData.data && typeof authData.data === "object" ? authData.data : authData;
|
|
23059
|
+
this.credentials = this.parseCredentials(actualResponseData);
|
|
23037
23060
|
return this.credentials;
|
|
23038
23061
|
} catch (error) {
|
|
23039
23062
|
if (error instanceof AuthenticationError) {
|
|
@@ -23072,6 +23095,8 @@ var ApiTokenAuthProvider = class {
|
|
|
23072
23095
|
const ext = extensions[0].extension;
|
|
23073
23096
|
sipUri = `sip:${ext}@${sipDomain}`;
|
|
23074
23097
|
console.log(`SDK: Constructed SIP URI from extension: ${sipUri}`);
|
|
23098
|
+
} else if (!sipUri) {
|
|
23099
|
+
sipUri = "";
|
|
23075
23100
|
}
|
|
23076
23101
|
return {
|
|
23077
23102
|
wsUrl,
|
|
@@ -23172,6 +23197,7 @@ var CallSession = class {
|
|
|
23172
23197
|
// JsSIP RTCSession
|
|
23173
23198
|
this._isMuted = false;
|
|
23174
23199
|
this.remoteStreamValue = null;
|
|
23200
|
+
this.lastKnownSSRCs = /* @__PURE__ */ new Set();
|
|
23175
23201
|
this.id = this.generateCallId();
|
|
23176
23202
|
this.session = session;
|
|
23177
23203
|
this.direction = direction;
|
|
@@ -23189,6 +23215,9 @@ var CallSession = class {
|
|
|
23189
23215
|
if (code === 180 || code === 183) {
|
|
23190
23216
|
this.status = "ringing";
|
|
23191
23217
|
this.events.emit("call:ringing", this);
|
|
23218
|
+
if (this.session.connection) {
|
|
23219
|
+
setTimeout(() => this.reattachFromReceivers(this.session.connection), 200);
|
|
23220
|
+
}
|
|
23192
23221
|
}
|
|
23193
23222
|
});
|
|
23194
23223
|
this.session.on("confirmed", () => {
|
|
@@ -23196,28 +23225,81 @@ var CallSession = class {
|
|
|
23196
23225
|
this.startTime = Date.now();
|
|
23197
23226
|
this.startDurationTimer();
|
|
23198
23227
|
this.events.emit("call:connected", this);
|
|
23228
|
+
if (this.session.connection) {
|
|
23229
|
+
this.reattachFromReceivers(this.session.connection);
|
|
23230
|
+
this.startSSRCWatch(this.session.connection);
|
|
23231
|
+
}
|
|
23199
23232
|
});
|
|
23200
23233
|
this.session.on("peerconnection", (evt) => {
|
|
23201
|
-
evt.peerconnection
|
|
23234
|
+
const pc = evt.peerconnection;
|
|
23235
|
+
pc.addEventListener("track", (event) => {
|
|
23236
|
+
setTimeout(() => this.reattachFromReceivers(pc), 150);
|
|
23202
23237
|
if (event.streams && event.streams[0]) {
|
|
23203
23238
|
this.remoteStreamValue = event.streams[0];
|
|
23239
|
+
this.events.emit("call:stream", this, event.streams[0]);
|
|
23240
|
+
}
|
|
23241
|
+
});
|
|
23242
|
+
pc.oniceconnectionstatechange = () => {
|
|
23243
|
+
if (pc.iceConnectionState === "connected" || pc.iceConnectionState === "completed") {
|
|
23244
|
+
this.reattachFromReceivers(pc);
|
|
23204
23245
|
}
|
|
23205
23246
|
};
|
|
23206
23247
|
});
|
|
23207
23248
|
this.session.on("ended", (evt) => {
|
|
23249
|
+
console.log(`SDK: Call ended. Cause: ${evt?.cause || "Normal"}`);
|
|
23208
23250
|
this.handleCallEnd(evt?.cause);
|
|
23209
23251
|
});
|
|
23210
23252
|
this.session.on("failed", (evt) => {
|
|
23253
|
+
console.warn(`SDK: Call failed. Cause: ${evt?.cause || "Unknown failure"}`);
|
|
23211
23254
|
this.handleCallEnd(evt?.cause || "Call failed");
|
|
23212
23255
|
});
|
|
23213
23256
|
this.session.on("rejected", (evt) => {
|
|
23257
|
+
console.warn(`SDK: Call rejected. Cause: ${evt?.cause || "Rejected"}`);
|
|
23214
23258
|
this.handleCallEnd(evt?.cause || "Call rejected");
|
|
23215
23259
|
});
|
|
23216
23260
|
}
|
|
23261
|
+
startSSRCWatch(pc) {
|
|
23262
|
+
this.stopSSRCWatch();
|
|
23263
|
+
this.lastKnownSSRCs.clear();
|
|
23264
|
+
this.ssrcWatchInterval = window.setInterval(async () => {
|
|
23265
|
+
try {
|
|
23266
|
+
if (!pc) return;
|
|
23267
|
+
const stats = await pc.getStats();
|
|
23268
|
+
stats.forEach((report) => {
|
|
23269
|
+
if (report.type === "inbound-rtp" && report.kind === "audio") {
|
|
23270
|
+
const ssrc = report.ssrc;
|
|
23271
|
+
if (ssrc && !this.lastKnownSSRCs.has(ssrc)) {
|
|
23272
|
+
this.lastKnownSSRCs.add(ssrc);
|
|
23273
|
+
console.log(`SDK: SSRC Switch Detected: ${ssrc}. Re-binding audio...`);
|
|
23274
|
+
setTimeout(() => this.reattachFromReceivers(pc), 200);
|
|
23275
|
+
}
|
|
23276
|
+
}
|
|
23277
|
+
});
|
|
23278
|
+
} catch (e) {
|
|
23279
|
+
}
|
|
23280
|
+
}, 1e3);
|
|
23281
|
+
}
|
|
23282
|
+
stopSSRCWatch() {
|
|
23283
|
+
if (this.ssrcWatchInterval) {
|
|
23284
|
+
clearInterval(this.ssrcWatchInterval);
|
|
23285
|
+
this.ssrcWatchInterval = void 0;
|
|
23286
|
+
}
|
|
23287
|
+
}
|
|
23288
|
+
reattachFromReceivers(pc) {
|
|
23289
|
+
if (!pc) return;
|
|
23290
|
+
const liveAudioTracks = pc.getReceivers().map((r) => r.track).filter((t) => t && t.kind === "audio" && t.readyState === "live");
|
|
23291
|
+
if (liveAudioTracks.length > 0) {
|
|
23292
|
+
console.log(`SDK: Audio Fix: Rebuilding stream from ${liveAudioTracks.length} receivers`);
|
|
23293
|
+
const newStream = new MediaStream(liveAudioTracks);
|
|
23294
|
+
this.remoteStreamValue = newStream;
|
|
23295
|
+
this.events.emit("call:stream", this, newStream);
|
|
23296
|
+
}
|
|
23297
|
+
}
|
|
23217
23298
|
handleCallEnd(reason) {
|
|
23218
23299
|
this.status = "ended";
|
|
23219
23300
|
this.endTime = Date.now();
|
|
23220
23301
|
this.stopDurationTimer();
|
|
23302
|
+
this.stopSSRCWatch();
|
|
23221
23303
|
this.remoteStreamValue = null;
|
|
23222
23304
|
this._isMuted = false;
|
|
23223
23305
|
this.events.emit("call:ended", this, reason);
|
|
@@ -23298,6 +23380,7 @@ var CallManager = class {
|
|
|
23298
23380
|
const extension = options?.extension || this.getDefaultExtension();
|
|
23299
23381
|
const sipDomain = this.extractDomain(this.credentials.sipUri);
|
|
23300
23382
|
const sipTarget = formatPhoneToSIP(targetNumber, sipDomain);
|
|
23383
|
+
console.log(`SDK: Calling ${sipTarget} using extension ${extension}...`);
|
|
23301
23384
|
const session = this.ua.call(sipTarget, {
|
|
23302
23385
|
mediaConstraints: options?.mediaConstraints || { audio: true },
|
|
23303
23386
|
pcConfig: this.callConfig.pcConfig
|
|
@@ -23324,12 +23407,13 @@ var CallManager = class {
|
|
|
23324
23407
|
* Answer the pending incoming call.
|
|
23325
23408
|
* Uses the stored pendingSession from handleIncomingCall.
|
|
23326
23409
|
*/
|
|
23327
|
-
answerCurrentCall() {
|
|
23410
|
+
async answerCurrentCall() {
|
|
23328
23411
|
const session = this.pendingSession;
|
|
23329
23412
|
if (!session) {
|
|
23330
23413
|
throw new CallError("No pending incoming call to answer");
|
|
23331
23414
|
}
|
|
23332
23415
|
try {
|
|
23416
|
+
await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
23333
23417
|
session.answer({
|
|
23334
23418
|
mediaConstraints: { audio: true },
|
|
23335
23419
|
pcConfig: this.callConfig.pcConfig
|
|
@@ -23461,6 +23545,7 @@ var DoraCell = class {
|
|
|
23461
23545
|
this.connectionStatus = "disconnected";
|
|
23462
23546
|
this.retryCount = 0;
|
|
23463
23547
|
this.maxRetries = 3;
|
|
23548
|
+
this.userId = null;
|
|
23464
23549
|
this.config = {
|
|
23465
23550
|
autoSelectExtension: true,
|
|
23466
23551
|
debug: false,
|
|
@@ -23485,17 +23570,32 @@ var DoraCell = class {
|
|
|
23485
23570
|
);
|
|
23486
23571
|
if (this.authProvider instanceof ApiTokenAuthProvider) {
|
|
23487
23572
|
const token = this.authProvider.getSessionToken();
|
|
23488
|
-
if (token)
|
|
23573
|
+
if (token) {
|
|
23574
|
+
console.log("SDK: Session token acquired and set in API client");
|
|
23575
|
+
this.apiClient.setSessionToken(token);
|
|
23576
|
+
} else {
|
|
23577
|
+
console.warn("SDK: No session token found in auth provider");
|
|
23578
|
+
}
|
|
23579
|
+
}
|
|
23580
|
+
await this.getWallet().catch(() => {
|
|
23581
|
+
});
|
|
23582
|
+
if (!this.credentials?.extensions || this.credentials.extensions.length === 0) {
|
|
23583
|
+
console.log("SDK: No extensions in auth response, fetching from API...");
|
|
23584
|
+
await this.fetchExtensions();
|
|
23489
23585
|
}
|
|
23490
23586
|
if (this.config.autoSelectExtension && this.credentials?.extensions && this.credentials.extensions.length > 0) {
|
|
23491
23587
|
const primary = this.credentials.extensions.find((e) => e.isPrimary) || this.credentials.extensions[0];
|
|
23492
|
-
const domain = this.credentials.sipDomain || "
|
|
23588
|
+
const domain = this.credentials.sipDomain || "64.227.10.164";
|
|
23493
23589
|
this.credentials.sipUri = `sip:${primary.extension}@${domain}`;
|
|
23494
23590
|
console.log(`SDK: Auto-selected extension ${primary.extension}`);
|
|
23495
23591
|
}
|
|
23496
|
-
|
|
23497
|
-
|
|
23498
|
-
|
|
23592
|
+
if (this.credentials?.sipUri) {
|
|
23593
|
+
await this.initializeUserAgent();
|
|
23594
|
+
this.initializeCallManager();
|
|
23595
|
+
await this.waitForRegistration();
|
|
23596
|
+
} else {
|
|
23597
|
+
console.warn("SDK: No SIP URI available yet. UA initialization deferred.");
|
|
23598
|
+
}
|
|
23499
23599
|
} catch (error) {
|
|
23500
23600
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
23501
23601
|
this.emitError(new ConnectionError(`Initialization failed: ${errorMessage}`));
|
|
@@ -23529,6 +23629,11 @@ var DoraCell = class {
|
|
|
23529
23629
|
if (!this.credentials) {
|
|
23530
23630
|
throw new ConnectionError("No credentials available");
|
|
23531
23631
|
}
|
|
23632
|
+
if (this.ua) {
|
|
23633
|
+
console.log("SDK: Stopping existing User Agent...");
|
|
23634
|
+
this.ua.stop();
|
|
23635
|
+
this.ua = null;
|
|
23636
|
+
}
|
|
23532
23637
|
try {
|
|
23533
23638
|
const socket = new import_jssip.default.WebSocketInterface(this.credentials.wsUrl);
|
|
23534
23639
|
const pcConfig = {
|
|
@@ -23540,7 +23645,8 @@ var DoraCell = class {
|
|
|
23540
23645
|
sockets: [socket],
|
|
23541
23646
|
register: true,
|
|
23542
23647
|
display_name: this.getDisplayName(),
|
|
23543
|
-
sessionTimers:
|
|
23648
|
+
sessionTimers: true,
|
|
23649
|
+
session_timers_refresh_method: "UPDATE",
|
|
23544
23650
|
trickleIce: false,
|
|
23545
23651
|
pcConfig,
|
|
23546
23652
|
instance_id: this.generateInstanceId()
|
|
@@ -23550,6 +23656,9 @@ var DoraCell = class {
|
|
|
23550
23656
|
this.setupUserAgentHandlers();
|
|
23551
23657
|
console.log("SDK: Starting UA...");
|
|
23552
23658
|
this.ua.start();
|
|
23659
|
+
if (this.callManager) {
|
|
23660
|
+
this.callManager.setUserAgent(this.ua);
|
|
23661
|
+
}
|
|
23553
23662
|
} catch (error) {
|
|
23554
23663
|
throw new ConnectionError(
|
|
23555
23664
|
`Failed to initialize User Agent: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
@@ -23619,7 +23728,7 @@ var DoraCell = class {
|
|
|
23619
23728
|
/**
|
|
23620
23729
|
* Answer an incoming call
|
|
23621
23730
|
*/
|
|
23622
|
-
answerCall() {
|
|
23731
|
+
async answerCall() {
|
|
23623
23732
|
const currentCall = this.callManager?.getCurrentCall();
|
|
23624
23733
|
if (!currentCall) {
|
|
23625
23734
|
throw new CallError("No incoming call to answer");
|
|
@@ -23627,7 +23736,7 @@ var DoraCell = class {
|
|
|
23627
23736
|
if (currentCall.direction !== "inbound") {
|
|
23628
23737
|
throw new CallError("Current call is not an incoming call");
|
|
23629
23738
|
}
|
|
23630
|
-
this.callManager.answerCurrentCall();
|
|
23739
|
+
await this.callManager.answerCurrentCall();
|
|
23631
23740
|
}
|
|
23632
23741
|
/**
|
|
23633
23742
|
* Hangup the current call
|
|
@@ -23663,11 +23772,12 @@ var DoraCell = class {
|
|
|
23663
23772
|
return { balance: 0, currency: "NGN" };
|
|
23664
23773
|
}
|
|
23665
23774
|
const primary = wallets[0];
|
|
23775
|
+
this.userId = primary.user_id || null;
|
|
23666
23776
|
const result = {
|
|
23667
|
-
balance: parseFloat(primary.balance || "0"),
|
|
23777
|
+
balance: parseFloat(primary.balance || primary.amount || "0"),
|
|
23668
23778
|
currency: primary.currency || "NGN"
|
|
23669
23779
|
};
|
|
23670
|
-
console.log("SDK: Wallet balance fetched:",
|
|
23780
|
+
console.log("SDK: Wallet balance fetched successfully, userId:", this.userId);
|
|
23671
23781
|
return result;
|
|
23672
23782
|
} catch (error) {
|
|
23673
23783
|
console.error("SDK: Failed to fetch wallet:", error);
|
|
@@ -23688,7 +23798,9 @@ var DoraCell = class {
|
|
|
23688
23798
|
throw new Error("SDK not authenticated. Call initialize() first.");
|
|
23689
23799
|
}
|
|
23690
23800
|
try {
|
|
23691
|
-
const
|
|
23801
|
+
const path = this.userId ? `/user/${this.userId}/extensions` : "/extensions";
|
|
23802
|
+
console.log(`SDK: Fetching extensions from: ${path}`);
|
|
23803
|
+
const response = await this.apiClient.get(path);
|
|
23692
23804
|
const extensions = response.data || response.extensions || response;
|
|
23693
23805
|
if (this.credentials && Array.isArray(extensions)) {
|
|
23694
23806
|
this.credentials.extensions = extensions;
|
|
@@ -23711,7 +23823,7 @@ var DoraCell = class {
|
|
|
23711
23823
|
async setExtension(extension) {
|
|
23712
23824
|
console.log(`SDK: Switching to extension ${extension}...`);
|
|
23713
23825
|
if (this.credentials) {
|
|
23714
|
-
const domain = this.credentials.sipDomain || "
|
|
23826
|
+
const domain = this.credentials.sipDomain || "64.227.10.164";
|
|
23715
23827
|
this.credentials.sipUri = `sip:${extension}@${domain}`;
|
|
23716
23828
|
await this.initializeUserAgent();
|
|
23717
23829
|
this.emitConnectionStatus(this.connectionStatus);
|
|
@@ -23798,7 +23910,7 @@ var DoraCell = class {
|
|
|
23798
23910
|
case "staging":
|
|
23799
23911
|
case "dev":
|
|
23800
23912
|
return "https://dev.api.cell.usedora.com/api";
|
|
23801
|
-
|
|
23913
|
+
default:
|
|
23802
23914
|
return "https://api.cell.usedora.com/api";
|
|
23803
23915
|
}
|
|
23804
23916
|
}
|