@dora-cell/sdk 1.0.0 → 1.0.2

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/README.md CHANGED
@@ -8,9 +8,10 @@ VoIP calling SDK for Dora Cell - Make calls from any JavaScript application with
8
8
  ✅ **TypeScript support** - Full type definitions included
9
9
  ✅ **WebRTC-based** - High-quality voice calls using JsSIP
10
10
  ✅ **Event-driven API** - Listen to call state changes
11
- ✅ **Multiple Auth Modes** - Support for API Token and Direct Extension login
12
- ✅ **Auto-extension selection** - Automatically selects first available extension
13
- ✅ **React bindings** - Optional React hooks for easy integration
11
+ ✅ **API Token Auth** - Securely authenticate with Public & Secret keys
12
+ ✅ **Dynamic Extensions** - Fetch and switch between DID numbers
13
+ ✅ **Wallet Integration** - Check credit balance directly from the SDK
14
+ ✅ **React bindings** - Premium UI components and hooks included
14
15
 
15
16
  ## Installation
16
17
 
@@ -48,14 +49,14 @@ const sdk = new DoraCell({
48
49
 
49
50
  #### Option B: API Token Authentication (Production)
50
51
 
51
- Securely fetch credentials from your backend.
52
+ Best for production apps. The SDK will verify keys and fetch SIP credentials automatically.
52
53
 
53
54
  ```javascript
54
55
  const sdk = new DoraCell({
55
56
  auth: {
56
57
  type: "api-token",
57
- apiToken: "your-api-token-here",
58
- // apiBaseUrl: "https://api.yourdomain.com", // Optional override
58
+ publicKey: "pk_live_...",
59
+ secretKey: "sk_live_...",
59
60
  },
60
61
  });
61
62
  ```
@@ -112,17 +113,20 @@ try {
112
113
  #### `new DoraCell(config)`
113
114
 
114
115
  - `config.auth` (Required)
115
- - `type: 'extension' | 'api-token'`
116
- - `extension: string` (for extension type)
117
- - `apiToken: string` (for api-token type)
116
+ - `type: 'api-token' | 'extension' | 'direct'`
117
+ - `publicKey/secretKey` (for api-token)
118
+ - `extension` (for extension type)
119
+ - `config.environment?: 'dev' | 'staging' | 'production'` - Shortcut for API URLs (default: 'production')
120
+ - `config.autoSelectExtension?: boolean` - Register first available DID (default: true)
118
121
  - `config.debug?: boolean` - Enable JsSIP debug logs (default: false)
119
122
 
120
123
  #### Methods
121
124
 
122
- - `initialize(): Promise<void>` - Connect to the SIP server and register the extension.
123
- - `call(number, options?): Promise<Call>` - Initiate an outbound call.
124
- - `options.extension`: Specify a specific extension to call from.
125
- - `options.mediaConstraints`: Custom WebRTC media constraints.
125
+ - `initialize(): Promise<void>` - Authenticate and register with the SIP server.
126
+ - `call(number, extension?): Promise<Call>` - Initiate an outbound call.
127
+ - `getWallet(): Promise<{ balance: number, currency: string }>` - Get current credit balance.
128
+ - `fetchExtensions(): Promise<Extension[]>` - Fetch available DID numbers/extensions.
129
+ - `setExtension(ext): Promise<void>` - Switch active extension and re-register SIP.
126
130
  - `answerCall(): void` - Answer a pending incoming call.
127
131
  - `hangup(): void` - End the current active call.
128
132
  - `on(event, handler): void` - Listen for events (see below).
@@ -145,7 +149,7 @@ Returned by `call()` and emitted in events.
145
149
 
146
150
  | Event | Description | Data |
147
151
  | ------------------- | ------------------------------ | ---------------------------------- |
148
- | `connection:status` | SIP registration status change | `{ status: string, error?: string }` |
152
+ | `connection:status` | SIP registration status change | `{ status: string, extension?: string, error?: string }` |
149
153
  | `call:incoming` | New incoming call detected | `Call` object |
150
154
  | `call:outgoing` | New outbound call started | `Call` object |
151
155
  | `call:ringing` | Remote party is ringing | `Call` object |
package/dist/index.d.mts CHANGED
@@ -5,8 +5,8 @@ import EventEmitter from 'eventemitter3';
5
5
  */
6
6
  interface ApiTokenAuth {
7
7
  type: 'api-token';
8
- apiToken: string;
9
- apiBaseUrl?: string;
8
+ publicKey: string;
9
+ secretKey: string;
10
10
  }
11
11
  interface DirectCredentialsAuth {
12
12
  type: 'direct';
@@ -21,8 +21,11 @@ interface ExtensionAuth {
21
21
  sipDomain?: string;
22
22
  }
23
23
  type AuthConfig = ApiTokenAuth | DirectCredentialsAuth | ExtensionAuth;
24
+ type DoraCellEnv = 'dev' | 'staging' | 'production' | 'local';
24
25
  interface DoraCellConfig {
25
26
  auth: AuthConfig;
27
+ apiBaseUrl?: string;
28
+ environment?: DoraCellEnv;
26
29
  sipServer?: string;
27
30
  turnServers?: RTCIceServer[];
28
31
  debug?: boolean;
@@ -106,6 +109,8 @@ declare class DoraCell {
106
109
  private credentials;
107
110
  private ua;
108
111
  private callManager;
112
+ private apiClient;
113
+ private apiBaseUrl;
109
114
  private connectionStatus;
110
115
  private retryCount;
111
116
  private maxRetries;
@@ -134,14 +139,29 @@ declare class DoraCell {
134
139
  * Get current call
135
140
  */
136
141
  getCurrentCall(): Call | null;
142
+ /**
143
+ * Get user's wallet/credit balance
144
+ */
145
+ getWallet(): Promise<{
146
+ balance: number;
147
+ currency: string;
148
+ }>;
137
149
  /**
138
150
  * Get connection status
139
151
  */
140
152
  getStatus(): ConnectionStatus;
141
153
  /**
142
- * Get available extensions
154
+ * Fetch available extensions (DID numbers) from the API
155
+ */
156
+ fetchExtensions(): Promise<any[]>;
157
+ /**
158
+ * Get available extensions (DID numbers)
159
+ */
160
+ getExtensions(): any[];
161
+ /**
162
+ * Update active extension and re-initialize SIP connection
143
163
  */
144
- getExtensions(): string[];
164
+ setExtension(extension: string): Promise<void>;
145
165
  /**
146
166
  * Event listener management
147
167
  */
@@ -157,6 +177,7 @@ declare class DoraCell {
157
177
  private getDefaultTurnServers;
158
178
  private getDisplayName;
159
179
  private generateInstanceId;
180
+ private resolveApiBaseUrl;
160
181
  }
161
182
 
162
183
  /**
@@ -215,4 +236,4 @@ declare function extractNumberFromSipUri(sipUri: string): string;
215
236
  */
216
237
  declare function isValidPhoneNumber(phone: string, minLength?: number): boolean;
217
238
 
218
- export { type ApiTokenAuth, type AuthConfig, AuthenticationError, type Call, type CallDirection, CallError, type CallOptions, CallSession, type CallStatus, ConnectionError, type ConnectionState, type ConnectionStatus, type DirectCredentialsAuth, DoraCell, type DoraCellConfig, DoraCellError, type DoraCellEvent, type DoraCellEventMap, type Extension, type SipCredentials, DoraCell as default, extractNumberFromSipUri, formatPhoneToSIP, isValidPhoneNumber, normalizePhoneNumber };
239
+ export { type ApiTokenAuth, type AuthConfig, AuthenticationError, type Call, type CallDirection, CallError, type CallOptions, CallSession, type CallStatus, ConnectionError, type ConnectionState, type ConnectionStatus, type DirectCredentialsAuth, DoraCell, type DoraCellConfig, DoraCellError, type DoraCellEvent, type DoraCellEventMap, type Extension, type ExtensionAuth, type SipCredentials, DoraCell as default, extractNumberFromSipUri, formatPhoneToSIP, isValidPhoneNumber, normalizePhoneNumber };
package/dist/index.d.ts CHANGED
@@ -5,8 +5,8 @@ import EventEmitter from 'eventemitter3';
5
5
  */
6
6
  interface ApiTokenAuth {
7
7
  type: 'api-token';
8
- apiToken: string;
9
- apiBaseUrl?: string;
8
+ publicKey: string;
9
+ secretKey: string;
10
10
  }
11
11
  interface DirectCredentialsAuth {
12
12
  type: 'direct';
@@ -21,8 +21,11 @@ interface ExtensionAuth {
21
21
  sipDomain?: string;
22
22
  }
23
23
  type AuthConfig = ApiTokenAuth | DirectCredentialsAuth | ExtensionAuth;
24
+ type DoraCellEnv = 'dev' | 'staging' | 'production' | 'local';
24
25
  interface DoraCellConfig {
25
26
  auth: AuthConfig;
27
+ apiBaseUrl?: string;
28
+ environment?: DoraCellEnv;
26
29
  sipServer?: string;
27
30
  turnServers?: RTCIceServer[];
28
31
  debug?: boolean;
@@ -106,6 +109,8 @@ declare class DoraCell {
106
109
  private credentials;
107
110
  private ua;
108
111
  private callManager;
112
+ private apiClient;
113
+ private apiBaseUrl;
109
114
  private connectionStatus;
110
115
  private retryCount;
111
116
  private maxRetries;
@@ -134,14 +139,29 @@ declare class DoraCell {
134
139
  * Get current call
135
140
  */
136
141
  getCurrentCall(): Call | null;
142
+ /**
143
+ * Get user's wallet/credit balance
144
+ */
145
+ getWallet(): Promise<{
146
+ balance: number;
147
+ currency: string;
148
+ }>;
137
149
  /**
138
150
  * Get connection status
139
151
  */
140
152
  getStatus(): ConnectionStatus;
141
153
  /**
142
- * Get available extensions
154
+ * Fetch available extensions (DID numbers) from the API
155
+ */
156
+ fetchExtensions(): Promise<any[]>;
157
+ /**
158
+ * Get available extensions (DID numbers)
159
+ */
160
+ getExtensions(): any[];
161
+ /**
162
+ * Update active extension and re-initialize SIP connection
143
163
  */
144
- getExtensions(): string[];
164
+ setExtension(extension: string): Promise<void>;
145
165
  /**
146
166
  * Event listener management
147
167
  */
@@ -157,6 +177,7 @@ declare class DoraCell {
157
177
  private getDefaultTurnServers;
158
178
  private getDisplayName;
159
179
  private generateInstanceId;
180
+ private resolveApiBaseUrl;
160
181
  }
161
182
 
162
183
  /**
@@ -215,4 +236,4 @@ declare function extractNumberFromSipUri(sipUri: string): string;
215
236
  */
216
237
  declare function isValidPhoneNumber(phone: string, minLength?: number): boolean;
217
238
 
218
- export { type ApiTokenAuth, type AuthConfig, AuthenticationError, type Call, type CallDirection, CallError, type CallOptions, CallSession, type CallStatus, ConnectionError, type ConnectionState, type ConnectionStatus, type DirectCredentialsAuth, DoraCell, type DoraCellConfig, DoraCellError, type DoraCellEvent, type DoraCellEventMap, type Extension, type SipCredentials, DoraCell as default, extractNumberFromSipUri, formatPhoneToSIP, isValidPhoneNumber, normalizePhoneNumber };
239
+ export { type ApiTokenAuth, type AuthConfig, AuthenticationError, type Call, type CallDirection, CallError, type CallOptions, CallSession, type CallStatus, ConnectionError, type ConnectionState, type ConnectionStatus, type DirectCredentialsAuth, DoraCell, type DoraCellConfig, DoraCellError, type DoraCellEvent, type DoraCellEventMap, type Extension, type ExtensionAuth, type SipCredentials, DoraCell as default, extractNumberFromSipUri, formatPhoneToSIP, isValidPhoneNumber, normalizePhoneNumber };
package/dist/index.js CHANGED
@@ -23003,43 +23003,41 @@ var ConnectionError = class extends DoraCellError {
23003
23003
 
23004
23004
  // src/core/AuthProvider.ts
23005
23005
  var ApiTokenAuthProvider = class {
23006
- constructor(apiToken, apiBaseUrl) {
23006
+ constructor(publicKey, secretKey) {
23007
23007
  this.credentials = null;
23008
- this.apiToken = apiToken;
23009
- let defaultBaseUrl = "";
23010
- if (typeof process !== "undefined" && process.env) {
23011
- defaultBaseUrl = process.env.NEXT_PUBLIC_BASE_API_URL || "";
23012
- }
23013
- this.apiBaseUrl = apiBaseUrl || defaultBaseUrl;
23008
+ this.sessionToken = null;
23009
+ this.publicKey = publicKey;
23010
+ this.secretKey = secretKey;
23014
23011
  }
23015
- async authenticate(config) {
23012
+ async authenticate(config, apiBaseUrl) {
23016
23013
  if (config.type !== "api-token") {
23017
23014
  throw new AuthenticationError("Invalid auth config type for ApiTokenAuthProvider");
23018
23015
  }
23016
+ const baseUrl = apiBaseUrl?.replace(/\/$/, "") || "https://api.cell.usedora.com/api";
23019
23017
  try {
23020
- const response = await fetch(`${this.apiBaseUrl}/api/sip-credentials`, {
23021
- method: "GET",
23018
+ console.log("SDK: Verifying keys at:", `${baseUrl}/sdk/v1/auth/session`);
23019
+ const authResponse = await fetch(`${baseUrl}/sdk/v1/auth/session`, {
23020
+ method: "POST",
23022
23021
  headers: {
23023
- "Authorization": `Bearer ${this.apiToken}`,
23024
23022
  "Content-Type": "application/json",
23025
23023
  "Accept": "application/json"
23026
23024
  },
23027
- credentials: "include"
23025
+ body: JSON.stringify({
23026
+ secret_key: this.secretKey
23027
+ })
23028
23028
  });
23029
- if (!response.ok) {
23030
- if (response.status === 401 || response.status === 403) {
23031
- throw new AuthenticationError(
23032
- "Invalid API token or insufficient permissions",
23033
- { status: response.status }
23034
- );
23035
- }
23029
+ if (!authResponse.ok) {
23036
23030
  throw new AuthenticationError(
23037
- `Failed to fetch SIP credentials: ${response.status}`,
23038
- { status: response.status }
23031
+ `Key verification failed: ${authResponse.status}`,
23032
+ { status: authResponse.status }
23039
23033
  );
23040
23034
  }
23041
- const data = await response.json();
23042
- this.credentials = this.parseCredentials(data);
23035
+ const authData = await authResponse.json();
23036
+ this.sessionToken = authData.session_token || authData.token || authData.data?.session_token;
23037
+ if (!this.sessionToken) {
23038
+ throw new AuthenticationError("No session token returned after key verification");
23039
+ }
23040
+ this.credentials = this.parseCredentials(authData);
23043
23041
  return this.credentials;
23044
23042
  } catch (error) {
23045
23043
  if (error instanceof AuthenticationError) {
@@ -23051,28 +23049,38 @@ var ApiTokenAuthProvider = class {
23051
23049
  );
23052
23050
  }
23053
23051
  }
23054
- async refreshCredentials() {
23055
- return this.authenticate({ type: "api-token", apiToken: this.apiToken });
23052
+ async refreshCredentials(apiBaseUrl) {
23053
+ return this.authenticate({
23054
+ type: "api-token",
23055
+ publicKey: this.publicKey,
23056
+ secretKey: this.secretKey
23057
+ }, apiBaseUrl);
23056
23058
  }
23057
23059
  isAuthenticated() {
23058
- return this.credentials !== null;
23060
+ return this.credentials !== null && this.sessionToken !== null;
23061
+ }
23062
+ getSessionToken() {
23063
+ return this.sessionToken;
23059
23064
  }
23060
23065
  parseCredentials(data) {
23061
- const wsUrl = data.ws_url || data.wsUrl;
23062
- const sipUri = data.sip_uri || data.sipUri;
23063
- const password = data.password;
23064
- const sipDomain = data.sip_domain || data.sipDomain;
23065
- const extensions = data.extensions;
23066
+ const DEFAULT_WSS_URL = "wss://cell.usedora.com:8089/ws";
23067
+ const DEFAULT_SIP_IP = "64.227.10.164";
23068
+ const DEFAULT_PASSWORD = "1234";
23069
+ const wsUrl = data.ws_url || data.wsUrl || DEFAULT_WSS_URL;
23070
+ const sipDomain = data.sip_domain || data.sipDomain || DEFAULT_SIP_IP;
23071
+ const password = data.password || DEFAULT_PASSWORD;
23072
+ const extensions = data.extensions || [];
23066
23073
  const expiresIn = data.expires_in || data.expiresIn;
23067
- if (!wsUrl || !sipUri) {
23068
- throw new AuthenticationError(
23069
- "Invalid credentials response: missing ws_url or sip_uri"
23070
- );
23074
+ let sipUri = data.sip_uri || data.sipUri;
23075
+ if (!sipUri && extensions.length > 0) {
23076
+ const ext = extensions[0].extension;
23077
+ sipUri = `sip:${ext}@${sipDomain}`;
23078
+ console.log(`SDK: Constructed SIP URI from extension: ${sipUri}`);
23071
23079
  }
23072
23080
  return {
23073
23081
  wsUrl,
23074
- sipUri,
23075
- password: password || "",
23082
+ sipUri: sipUri || "",
23083
+ password,
23076
23084
  sipDomain,
23077
23085
  extensions,
23078
23086
  expiresIn
@@ -23080,64 +23088,42 @@ var ApiTokenAuthProvider = class {
23080
23088
  }
23081
23089
  };
23082
23090
  var DirectCredentialsAuthProvider = class {
23083
- constructor() {
23084
- this.credentials = null;
23091
+ constructor(sipUri, password, wsUrl) {
23092
+ this.credentials = { sipUri, password, wsUrl, extensions: [] };
23085
23093
  }
23086
- async authenticate(config) {
23087
- if (config.type !== "direct") {
23088
- throw new AuthenticationError("Invalid auth config type for DirectCredentialsAuthProvider");
23089
- }
23090
- this.credentials = {
23091
- wsUrl: config.wsUrl,
23092
- sipUri: config.sipUri,
23093
- password: config.password
23094
- };
23094
+ async authenticate() {
23095
23095
  return this.credentials;
23096
23096
  }
23097
23097
  isAuthenticated() {
23098
- return this.credentials !== null;
23098
+ return true;
23099
23099
  }
23100
23100
  };
23101
- var DEFAULT_WSS_URL = "wss://cell.usedora.com:8089/ws";
23102
- var DEFAULT_SIP_IP = "64.227.10.164";
23103
- var DEFAULT_PASSWORD = "1234";
23104
23101
  var ExtensionAuthProvider = class {
23105
- constructor() {
23106
- this.credentials = null;
23107
- }
23108
- async authenticate(config) {
23109
- if (config.type !== "extension") {
23110
- throw new AuthenticationError("Invalid auth config type for ExtensionAuthProvider");
23111
- }
23112
- const extension = config.extension;
23113
- const password = config.password || DEFAULT_PASSWORD;
23114
- const wsUrl = DEFAULT_WSS_URL;
23115
- const sipDomain = config.sipDomain || DEFAULT_SIP_IP;
23116
- const sipUri = `sip:${extension}@${sipDomain}`;
23102
+ constructor(extension, password = "1234", sipDomain = "64.227.10.164") {
23117
23103
  this.credentials = {
23118
- wsUrl,
23119
- sipUri,
23104
+ sipUri: `sip:${extension}@${sipDomain}`,
23120
23105
  password,
23121
- sipDomain,
23122
- extensions: [{ extension, displayName: `Ext ${extension}`, isPrimary: true }]
23106
+ wsUrl: "wss://cell.usedora.com:8089/ws",
23107
+ extensions: [{ extension, isPrimary: true }],
23108
+ sipDomain
23123
23109
  };
23110
+ }
23111
+ async authenticate() {
23124
23112
  return this.credentials;
23125
23113
  }
23126
23114
  isAuthenticated() {
23127
- return this.credentials !== null;
23115
+ return true;
23128
23116
  }
23129
23117
  };
23130
23118
  function createAuthProvider(config) {
23131
- switch (config.type) {
23132
- case "api-token":
23133
- return new ApiTokenAuthProvider(config.apiToken, config.apiBaseUrl);
23134
- case "direct":
23135
- return new DirectCredentialsAuthProvider();
23136
- case "extension":
23137
- return new ExtensionAuthProvider();
23138
- default:
23139
- throw new AuthenticationError("Unknown authentication type");
23119
+ if (config.type === "api-token") {
23120
+ return new ApiTokenAuthProvider(config.publicKey, config.secretKey);
23121
+ } else if (config.type === "direct") {
23122
+ return new DirectCredentialsAuthProvider(config.sipUri, config.password, config.wsUrl);
23123
+ } else if (config.type === "extension") {
23124
+ return new ExtensionAuthProvider(config.extension, config.password, config.sipDomain);
23140
23125
  }
23126
+ throw new AuthenticationError("Unknown authentication type");
23141
23127
  }
23142
23128
 
23143
23129
  // src/utils/phoneFormatter.ts
@@ -23411,6 +23397,64 @@ var CallManager = class {
23411
23397
  }
23412
23398
  };
23413
23399
 
23400
+ // src/core/ApiClient.ts
23401
+ var ApiClient = class {
23402
+ constructor(baseUrl) {
23403
+ this.sessionToken = null;
23404
+ this.baseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
23405
+ }
23406
+ setSessionToken(token) {
23407
+ this.sessionToken = token;
23408
+ }
23409
+ getSessionToken() {
23410
+ return this.sessionToken;
23411
+ }
23412
+ async post(path, data, headers = {}) {
23413
+ return this.request(path, {
23414
+ method: "POST",
23415
+ headers: {
23416
+ "Content-Type": "application/json",
23417
+ ...headers
23418
+ },
23419
+ body: JSON.stringify(data)
23420
+ });
23421
+ }
23422
+ async get(path, headers = {}) {
23423
+ return this.request(path, {
23424
+ method: "GET",
23425
+ headers: {
23426
+ ...headers
23427
+ }
23428
+ });
23429
+ }
23430
+ async request(path, options) {
23431
+ const url = path.startsWith("http") ? path : `${this.baseUrl}${path.startsWith("/") ? "" : "/"}${path}`;
23432
+ const headers = {
23433
+ ...options.headers
23434
+ };
23435
+ if (this.sessionToken && !headers["Authorization"]) {
23436
+ headers["Authorization"] = `Bearer ${this.sessionToken}`;
23437
+ }
23438
+ const response = await fetch(url, {
23439
+ ...options,
23440
+ headers
23441
+ });
23442
+ if (!response.ok) {
23443
+ let errorData;
23444
+ try {
23445
+ errorData = await response.json();
23446
+ } catch (e) {
23447
+ errorData = { message: response.statusText };
23448
+ }
23449
+ const error = new Error(errorData.message || `API Request failed: ${response.status}`);
23450
+ error.status = response.status;
23451
+ error.data = errorData;
23452
+ throw error;
23453
+ }
23454
+ return response.json();
23455
+ }
23456
+ };
23457
+
23414
23458
  // src/core/DoraCell.ts
23415
23459
  var DoraCell = class {
23416
23460
  constructor(config) {
@@ -23428,6 +23472,8 @@ var DoraCell = class {
23428
23472
  };
23429
23473
  this.events = new eventemitter3_default();
23430
23474
  this.authProvider = createAuthProvider(config.auth);
23475
+ this.apiBaseUrl = this.resolveApiBaseUrl(config);
23476
+ this.apiClient = new ApiClient(this.apiBaseUrl);
23431
23477
  if (this.config.debug) {
23432
23478
  import_jssip.default.debug.enable("JsSIP:*");
23433
23479
  }
@@ -23437,7 +23483,20 @@ var DoraCell = class {
23437
23483
  */
23438
23484
  async initialize() {
23439
23485
  try {
23440
- this.credentials = await this.authProvider.authenticate(this.config.auth);
23486
+ this.credentials = await this.authProvider.authenticate(
23487
+ this.config.auth,
23488
+ this.apiBaseUrl
23489
+ );
23490
+ if (this.authProvider instanceof ApiTokenAuthProvider) {
23491
+ const token = this.authProvider.getSessionToken();
23492
+ if (token) this.apiClient.setSessionToken(token);
23493
+ }
23494
+ if (this.config.autoSelectExtension && this.credentials?.extensions && this.credentials.extensions.length > 0) {
23495
+ const primary = this.credentials.extensions.find((e) => e.isPrimary) || this.credentials.extensions[0];
23496
+ const domain = this.credentials.sipDomain || "cell.usedora.com";
23497
+ this.credentials.sipUri = `sip:${primary.extension}@${domain}`;
23498
+ console.log(`SDK: Auto-selected extension ${primary.extension}`);
23499
+ }
23441
23500
  await this.initializeUserAgent();
23442
23501
  this.initializeCallManager();
23443
23502
  await this.waitForRegistration();
@@ -23590,6 +23649,35 @@ var DoraCell = class {
23590
23649
  getCurrentCall() {
23591
23650
  return this.callManager?.getCurrentCall() || null;
23592
23651
  }
23652
+ /**
23653
+ * Get user's wallet/credit balance
23654
+ */
23655
+ async getWallet() {
23656
+ const token = this.apiClient.getSessionToken();
23657
+ if (!token) {
23658
+ console.warn("SDK: Cannot fetch wallet, no session token available.");
23659
+ return { balance: 0, currency: "NGN" };
23660
+ }
23661
+ try {
23662
+ console.log("SDK: Fetching wallet balance...");
23663
+ const response = await this.apiClient.get("/wallets");
23664
+ const wallets = Array.isArray(response) ? response : response.data || [];
23665
+ if (wallets.length === 0) {
23666
+ console.log("SDK: No wallets found for user.");
23667
+ return { balance: 0, currency: "NGN" };
23668
+ }
23669
+ const primary = wallets[0];
23670
+ const result = {
23671
+ balance: parseFloat(primary.balance || "0"),
23672
+ currency: primary.currency || "NGN"
23673
+ };
23674
+ console.log("SDK: Wallet balance fetched:", result);
23675
+ return result;
23676
+ } catch (error) {
23677
+ console.error("SDK: Failed to fetch wallet:", error);
23678
+ throw error;
23679
+ }
23680
+ }
23593
23681
  /**
23594
23682
  * Get connection status
23595
23683
  */
@@ -23597,13 +23685,41 @@ var DoraCell = class {
23597
23685
  return this.connectionStatus;
23598
23686
  }
23599
23687
  /**
23600
- * Get available extensions
23688
+ * Fetch available extensions (DID numbers) from the API
23689
+ */
23690
+ async fetchExtensions() {
23691
+ if (!this.authProvider.isAuthenticated()) {
23692
+ throw new Error("SDK not authenticated. Call initialize() first.");
23693
+ }
23694
+ try {
23695
+ const response = await this.apiClient.get("/extensions");
23696
+ const extensions = response.data || response.extensions || response;
23697
+ if (this.credentials && Array.isArray(extensions)) {
23698
+ this.credentials.extensions = extensions;
23699
+ }
23700
+ return Array.isArray(extensions) ? extensions : [];
23701
+ } catch (error) {
23702
+ console.error("SDK: Failed to fetch extensions:", error);
23703
+ return this.credentials?.extensions || [];
23704
+ }
23705
+ }
23706
+ /**
23707
+ * Get available extensions (DID numbers)
23601
23708
  */
23602
23709
  getExtensions() {
23603
- if (!this.credentials?.extensions) {
23604
- return [];
23710
+ return this.credentials?.extensions || [];
23711
+ }
23712
+ /**
23713
+ * Update active extension and re-initialize SIP connection
23714
+ */
23715
+ async setExtension(extension) {
23716
+ console.log(`SDK: Switching to extension ${extension}...`);
23717
+ if (this.credentials) {
23718
+ const domain = this.credentials.sipDomain || "cell.usedora.com";
23719
+ this.credentials.sipUri = `sip:${extension}@${domain}`;
23720
+ await this.initializeUserAgent();
23721
+ this.emitConnectionStatus(this.connectionStatus);
23605
23722
  }
23606
- return this.credentials.extensions.map((ext) => ext.extension);
23607
23723
  }
23608
23724
  /**
23609
23725
  * Event listener management
@@ -23635,9 +23751,10 @@ var DoraCell = class {
23635
23751
  }
23636
23752
  // Helper methods
23637
23753
  emitConnectionStatus(error) {
23754
+ const extension = this.credentials?.extensions?.[0]?.extension || (this.credentials?.sipUri ? extractNumberFromSipUri(this.credentials.sipUri) : void 0);
23638
23755
  const state = {
23639
23756
  status: this.connectionStatus,
23640
- extension: this.credentials?.extensions?.[0]?.extension,
23757
+ extension,
23641
23758
  error
23642
23759
  };
23643
23760
  this.events.emit("connection:status", state);
@@ -23678,6 +23795,22 @@ var DoraCell = class {
23678
23795
  return v.toString(16);
23679
23796
  });
23680
23797
  }
23798
+ resolveApiBaseUrl(config) {
23799
+ if (config.apiBaseUrl) return config.apiBaseUrl;
23800
+ if (config.environment) {
23801
+ switch (config.environment) {
23802
+ case "staging":
23803
+ case "dev":
23804
+ return "https://dev.api.cell.usedora.com/api";
23805
+ case "production":
23806
+ return "https://api.cell.usedora.com/api";
23807
+ }
23808
+ }
23809
+ if (typeof process !== "undefined" && process.env?.NEXT_PUBLIC_BASE_API_URL) {
23810
+ return process.env.NEXT_PUBLIC_BASE_API_URL;
23811
+ }
23812
+ return "https://api.cell.usedora.com/api";
23813
+ }
23681
23814
  };
23682
23815
 
23683
23816
  exports.AuthenticationError = AuthenticationError;