@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.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
- this.credentials = this.parseCredentials(authData);
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.ontrack = (event) => {
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) this.apiClient.setSessionToken(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 || "cell.usedora.com";
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
- await this.initializeUserAgent();
23497
- this.initializeCallManager();
23498
- await this.waitForRegistration();
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: false,
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:", result);
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 response = await this.apiClient.get("/extensions");
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 || "cell.usedora.com";
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
- case "production":
23913
+ default:
23802
23914
  return "https://api.cell.usedora.com/api";
23803
23915
  }
23804
23916
  }