@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.d.mts
CHANGED
|
@@ -79,6 +79,7 @@ type DoraCellEventMap = {
|
|
|
79
79
|
'call:connected': (call: Call) => void;
|
|
80
80
|
'call:ended': (call: Call, reason?: string) => void;
|
|
81
81
|
'call:failed': (call: Call, error: string) => void;
|
|
82
|
+
'call:stream': (call: Call, stream: MediaStream) => void;
|
|
82
83
|
'error': (error: Error) => void;
|
|
83
84
|
};
|
|
84
85
|
type DoraCellEvent = keyof DoraCellEventMap;
|
|
@@ -114,6 +115,7 @@ declare class DoraCell {
|
|
|
114
115
|
private connectionStatus;
|
|
115
116
|
private retryCount;
|
|
116
117
|
private maxRetries;
|
|
118
|
+
private userId;
|
|
117
119
|
constructor(config: DoraCellConfig);
|
|
118
120
|
/**
|
|
119
121
|
* Initialize the SDK - authenticate and connect to SIP server
|
|
@@ -130,7 +132,7 @@ declare class DoraCell {
|
|
|
130
132
|
/**
|
|
131
133
|
* Answer an incoming call
|
|
132
134
|
*/
|
|
133
|
-
answerCall(): void
|
|
135
|
+
answerCall(): Promise<void>;
|
|
134
136
|
/**
|
|
135
137
|
* Hangup the current call
|
|
136
138
|
*/
|
|
@@ -199,9 +201,14 @@ declare class CallSession implements Call {
|
|
|
199
201
|
private remoteStreamValue;
|
|
200
202
|
private durationInterval?;
|
|
201
203
|
private events;
|
|
204
|
+
private ssrcWatchInterval?;
|
|
205
|
+
private lastKnownSSRCs;
|
|
202
206
|
constructor(session: any, direction: CallDirection, remoteNumber: string, localExtension: string, events: EventEmitter);
|
|
203
207
|
private generateCallId;
|
|
204
208
|
private setupSessionHandlers;
|
|
209
|
+
private startSSRCWatch;
|
|
210
|
+
private stopSSRCWatch;
|
|
211
|
+
private reattachFromReceivers;
|
|
205
212
|
private handleCallEnd;
|
|
206
213
|
private startDurationTimer;
|
|
207
214
|
private stopDurationTimer;
|
package/dist/index.d.ts
CHANGED
|
@@ -79,6 +79,7 @@ type DoraCellEventMap = {
|
|
|
79
79
|
'call:connected': (call: Call) => void;
|
|
80
80
|
'call:ended': (call: Call, reason?: string) => void;
|
|
81
81
|
'call:failed': (call: Call, error: string) => void;
|
|
82
|
+
'call:stream': (call: Call, stream: MediaStream) => void;
|
|
82
83
|
'error': (error: Error) => void;
|
|
83
84
|
};
|
|
84
85
|
type DoraCellEvent = keyof DoraCellEventMap;
|
|
@@ -114,6 +115,7 @@ declare class DoraCell {
|
|
|
114
115
|
private connectionStatus;
|
|
115
116
|
private retryCount;
|
|
116
117
|
private maxRetries;
|
|
118
|
+
private userId;
|
|
117
119
|
constructor(config: DoraCellConfig);
|
|
118
120
|
/**
|
|
119
121
|
* Initialize the SDK - authenticate and connect to SIP server
|
|
@@ -130,7 +132,7 @@ declare class DoraCell {
|
|
|
130
132
|
/**
|
|
131
133
|
* Answer an incoming call
|
|
132
134
|
*/
|
|
133
|
-
answerCall(): void
|
|
135
|
+
answerCall(): Promise<void>;
|
|
134
136
|
/**
|
|
135
137
|
* Hangup the current call
|
|
136
138
|
*/
|
|
@@ -199,9 +201,14 @@ declare class CallSession implements Call {
|
|
|
199
201
|
private remoteStreamValue;
|
|
200
202
|
private durationInterval?;
|
|
201
203
|
private events;
|
|
204
|
+
private ssrcWatchInterval?;
|
|
205
|
+
private lastKnownSSRCs;
|
|
202
206
|
constructor(session: any, direction: CallDirection, remoteNumber: string, localExtension: string, events: EventEmitter);
|
|
203
207
|
private generateCallId;
|
|
204
208
|
private setupSessionHandlers;
|
|
209
|
+
private startSSRCWatch;
|
|
210
|
+
private stopSSRCWatch;
|
|
211
|
+
private reattachFromReceivers;
|
|
205
212
|
private handleCallEnd;
|
|
206
213
|
private startDurationTimer;
|
|
207
214
|
private stopDurationTimer;
|
package/dist/index.js
CHANGED
|
@@ -23037,7 +23037,30 @@ var ApiTokenAuthProvider = class {
|
|
|
23037
23037
|
if (!this.sessionToken) {
|
|
23038
23038
|
throw new AuthenticationError("No session token returned after key verification");
|
|
23039
23039
|
}
|
|
23040
|
-
|
|
23040
|
+
console.log("SDK: Performing secondary validation...");
|
|
23041
|
+
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
|
23042
|
+
const validateResponse = await fetch(`${baseUrl}/sdk/v1/auth/validate`, {
|
|
23043
|
+
method: "GET",
|
|
23044
|
+
headers: {
|
|
23045
|
+
"Authorization": `Bearer ${this.sessionToken}`,
|
|
23046
|
+
"x-dora-public-key": this.publicKey,
|
|
23047
|
+
"Origin": origin,
|
|
23048
|
+
"Accept": "application/json"
|
|
23049
|
+
}
|
|
23050
|
+
});
|
|
23051
|
+
if (!validateResponse.ok) {
|
|
23052
|
+
throw new AuthenticationError(
|
|
23053
|
+
`Secondary validation failed: ${validateResponse.status}`,
|
|
23054
|
+
{ status: validateResponse.status }
|
|
23055
|
+
);
|
|
23056
|
+
}
|
|
23057
|
+
const validateData = await validateResponse.json();
|
|
23058
|
+
console.log(`SDK: Secondary validation successful for "${validateData.app_name}"`);
|
|
23059
|
+
if (validateData.features && !validateData.features.includes("voice")) {
|
|
23060
|
+
console.warn('SDK: App token does not have the "voice" feature enabled.');
|
|
23061
|
+
}
|
|
23062
|
+
const actualResponseData = authData.data && typeof authData.data === "object" ? authData.data : authData;
|
|
23063
|
+
this.credentials = this.parseCredentials(actualResponseData);
|
|
23041
23064
|
return this.credentials;
|
|
23042
23065
|
} catch (error) {
|
|
23043
23066
|
if (error instanceof AuthenticationError) {
|
|
@@ -23076,6 +23099,8 @@ var ApiTokenAuthProvider = class {
|
|
|
23076
23099
|
const ext = extensions[0].extension;
|
|
23077
23100
|
sipUri = `sip:${ext}@${sipDomain}`;
|
|
23078
23101
|
console.log(`SDK: Constructed SIP URI from extension: ${sipUri}`);
|
|
23102
|
+
} else if (!sipUri) {
|
|
23103
|
+
sipUri = "";
|
|
23079
23104
|
}
|
|
23080
23105
|
return {
|
|
23081
23106
|
wsUrl,
|
|
@@ -23176,6 +23201,7 @@ var CallSession = class {
|
|
|
23176
23201
|
// JsSIP RTCSession
|
|
23177
23202
|
this._isMuted = false;
|
|
23178
23203
|
this.remoteStreamValue = null;
|
|
23204
|
+
this.lastKnownSSRCs = /* @__PURE__ */ new Set();
|
|
23179
23205
|
this.id = this.generateCallId();
|
|
23180
23206
|
this.session = session;
|
|
23181
23207
|
this.direction = direction;
|
|
@@ -23193,6 +23219,9 @@ var CallSession = class {
|
|
|
23193
23219
|
if (code === 180 || code === 183) {
|
|
23194
23220
|
this.status = "ringing";
|
|
23195
23221
|
this.events.emit("call:ringing", this);
|
|
23222
|
+
if (this.session.connection) {
|
|
23223
|
+
setTimeout(() => this.reattachFromReceivers(this.session.connection), 200);
|
|
23224
|
+
}
|
|
23196
23225
|
}
|
|
23197
23226
|
});
|
|
23198
23227
|
this.session.on("confirmed", () => {
|
|
@@ -23200,28 +23229,81 @@ var CallSession = class {
|
|
|
23200
23229
|
this.startTime = Date.now();
|
|
23201
23230
|
this.startDurationTimer();
|
|
23202
23231
|
this.events.emit("call:connected", this);
|
|
23232
|
+
if (this.session.connection) {
|
|
23233
|
+
this.reattachFromReceivers(this.session.connection);
|
|
23234
|
+
this.startSSRCWatch(this.session.connection);
|
|
23235
|
+
}
|
|
23203
23236
|
});
|
|
23204
23237
|
this.session.on("peerconnection", (evt) => {
|
|
23205
|
-
evt.peerconnection
|
|
23238
|
+
const pc = evt.peerconnection;
|
|
23239
|
+
pc.addEventListener("track", (event) => {
|
|
23240
|
+
setTimeout(() => this.reattachFromReceivers(pc), 150);
|
|
23206
23241
|
if (event.streams && event.streams[0]) {
|
|
23207
23242
|
this.remoteStreamValue = event.streams[0];
|
|
23243
|
+
this.events.emit("call:stream", this, event.streams[0]);
|
|
23244
|
+
}
|
|
23245
|
+
});
|
|
23246
|
+
pc.oniceconnectionstatechange = () => {
|
|
23247
|
+
if (pc.iceConnectionState === "connected" || pc.iceConnectionState === "completed") {
|
|
23248
|
+
this.reattachFromReceivers(pc);
|
|
23208
23249
|
}
|
|
23209
23250
|
};
|
|
23210
23251
|
});
|
|
23211
23252
|
this.session.on("ended", (evt) => {
|
|
23253
|
+
console.log(`SDK: Call ended. Cause: ${evt?.cause || "Normal"}`);
|
|
23212
23254
|
this.handleCallEnd(evt?.cause);
|
|
23213
23255
|
});
|
|
23214
23256
|
this.session.on("failed", (evt) => {
|
|
23257
|
+
console.warn(`SDK: Call failed. Cause: ${evt?.cause || "Unknown failure"}`);
|
|
23215
23258
|
this.handleCallEnd(evt?.cause || "Call failed");
|
|
23216
23259
|
});
|
|
23217
23260
|
this.session.on("rejected", (evt) => {
|
|
23261
|
+
console.warn(`SDK: Call rejected. Cause: ${evt?.cause || "Rejected"}`);
|
|
23218
23262
|
this.handleCallEnd(evt?.cause || "Call rejected");
|
|
23219
23263
|
});
|
|
23220
23264
|
}
|
|
23265
|
+
startSSRCWatch(pc) {
|
|
23266
|
+
this.stopSSRCWatch();
|
|
23267
|
+
this.lastKnownSSRCs.clear();
|
|
23268
|
+
this.ssrcWatchInterval = window.setInterval(async () => {
|
|
23269
|
+
try {
|
|
23270
|
+
if (!pc) return;
|
|
23271
|
+
const stats = await pc.getStats();
|
|
23272
|
+
stats.forEach((report) => {
|
|
23273
|
+
if (report.type === "inbound-rtp" && report.kind === "audio") {
|
|
23274
|
+
const ssrc = report.ssrc;
|
|
23275
|
+
if (ssrc && !this.lastKnownSSRCs.has(ssrc)) {
|
|
23276
|
+
this.lastKnownSSRCs.add(ssrc);
|
|
23277
|
+
console.log(`SDK: SSRC Switch Detected: ${ssrc}. Re-binding audio...`);
|
|
23278
|
+
setTimeout(() => this.reattachFromReceivers(pc), 200);
|
|
23279
|
+
}
|
|
23280
|
+
}
|
|
23281
|
+
});
|
|
23282
|
+
} catch (e) {
|
|
23283
|
+
}
|
|
23284
|
+
}, 1e3);
|
|
23285
|
+
}
|
|
23286
|
+
stopSSRCWatch() {
|
|
23287
|
+
if (this.ssrcWatchInterval) {
|
|
23288
|
+
clearInterval(this.ssrcWatchInterval);
|
|
23289
|
+
this.ssrcWatchInterval = void 0;
|
|
23290
|
+
}
|
|
23291
|
+
}
|
|
23292
|
+
reattachFromReceivers(pc) {
|
|
23293
|
+
if (!pc) return;
|
|
23294
|
+
const liveAudioTracks = pc.getReceivers().map((r) => r.track).filter((t) => t && t.kind === "audio" && t.readyState === "live");
|
|
23295
|
+
if (liveAudioTracks.length > 0) {
|
|
23296
|
+
console.log(`SDK: Audio Fix: Rebuilding stream from ${liveAudioTracks.length} receivers`);
|
|
23297
|
+
const newStream = new MediaStream(liveAudioTracks);
|
|
23298
|
+
this.remoteStreamValue = newStream;
|
|
23299
|
+
this.events.emit("call:stream", this, newStream);
|
|
23300
|
+
}
|
|
23301
|
+
}
|
|
23221
23302
|
handleCallEnd(reason) {
|
|
23222
23303
|
this.status = "ended";
|
|
23223
23304
|
this.endTime = Date.now();
|
|
23224
23305
|
this.stopDurationTimer();
|
|
23306
|
+
this.stopSSRCWatch();
|
|
23225
23307
|
this.remoteStreamValue = null;
|
|
23226
23308
|
this._isMuted = false;
|
|
23227
23309
|
this.events.emit("call:ended", this, reason);
|
|
@@ -23302,6 +23384,7 @@ var CallManager = class {
|
|
|
23302
23384
|
const extension = options?.extension || this.getDefaultExtension();
|
|
23303
23385
|
const sipDomain = this.extractDomain(this.credentials.sipUri);
|
|
23304
23386
|
const sipTarget = formatPhoneToSIP(targetNumber, sipDomain);
|
|
23387
|
+
console.log(`SDK: Calling ${sipTarget} using extension ${extension}...`);
|
|
23305
23388
|
const session = this.ua.call(sipTarget, {
|
|
23306
23389
|
mediaConstraints: options?.mediaConstraints || { audio: true },
|
|
23307
23390
|
pcConfig: this.callConfig.pcConfig
|
|
@@ -23328,12 +23411,13 @@ var CallManager = class {
|
|
|
23328
23411
|
* Answer the pending incoming call.
|
|
23329
23412
|
* Uses the stored pendingSession from handleIncomingCall.
|
|
23330
23413
|
*/
|
|
23331
|
-
answerCurrentCall() {
|
|
23414
|
+
async answerCurrentCall() {
|
|
23332
23415
|
const session = this.pendingSession;
|
|
23333
23416
|
if (!session) {
|
|
23334
23417
|
throw new CallError("No pending incoming call to answer");
|
|
23335
23418
|
}
|
|
23336
23419
|
try {
|
|
23420
|
+
await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
23337
23421
|
session.answer({
|
|
23338
23422
|
mediaConstraints: { audio: true },
|
|
23339
23423
|
pcConfig: this.callConfig.pcConfig
|
|
@@ -23465,6 +23549,7 @@ var DoraCell = class {
|
|
|
23465
23549
|
this.connectionStatus = "disconnected";
|
|
23466
23550
|
this.retryCount = 0;
|
|
23467
23551
|
this.maxRetries = 3;
|
|
23552
|
+
this.userId = null;
|
|
23468
23553
|
this.config = {
|
|
23469
23554
|
autoSelectExtension: true,
|
|
23470
23555
|
debug: false,
|
|
@@ -23489,17 +23574,32 @@ var DoraCell = class {
|
|
|
23489
23574
|
);
|
|
23490
23575
|
if (this.authProvider instanceof ApiTokenAuthProvider) {
|
|
23491
23576
|
const token = this.authProvider.getSessionToken();
|
|
23492
|
-
if (token)
|
|
23577
|
+
if (token) {
|
|
23578
|
+
console.log("SDK: Session token acquired and set in API client");
|
|
23579
|
+
this.apiClient.setSessionToken(token);
|
|
23580
|
+
} else {
|
|
23581
|
+
console.warn("SDK: No session token found in auth provider");
|
|
23582
|
+
}
|
|
23583
|
+
}
|
|
23584
|
+
await this.getWallet().catch(() => {
|
|
23585
|
+
});
|
|
23586
|
+
if (!this.credentials?.extensions || this.credentials.extensions.length === 0) {
|
|
23587
|
+
console.log("SDK: No extensions in auth response, fetching from API...");
|
|
23588
|
+
await this.fetchExtensions();
|
|
23493
23589
|
}
|
|
23494
23590
|
if (this.config.autoSelectExtension && this.credentials?.extensions && this.credentials.extensions.length > 0) {
|
|
23495
23591
|
const primary = this.credentials.extensions.find((e) => e.isPrimary) || this.credentials.extensions[0];
|
|
23496
|
-
const domain = this.credentials.sipDomain || "
|
|
23592
|
+
const domain = this.credentials.sipDomain || "64.227.10.164";
|
|
23497
23593
|
this.credentials.sipUri = `sip:${primary.extension}@${domain}`;
|
|
23498
23594
|
console.log(`SDK: Auto-selected extension ${primary.extension}`);
|
|
23499
23595
|
}
|
|
23500
|
-
|
|
23501
|
-
|
|
23502
|
-
|
|
23596
|
+
if (this.credentials?.sipUri) {
|
|
23597
|
+
await this.initializeUserAgent();
|
|
23598
|
+
this.initializeCallManager();
|
|
23599
|
+
await this.waitForRegistration();
|
|
23600
|
+
} else {
|
|
23601
|
+
console.warn("SDK: No SIP URI available yet. UA initialization deferred.");
|
|
23602
|
+
}
|
|
23503
23603
|
} catch (error) {
|
|
23504
23604
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
23505
23605
|
this.emitError(new ConnectionError(`Initialization failed: ${errorMessage}`));
|
|
@@ -23533,6 +23633,11 @@ var DoraCell = class {
|
|
|
23533
23633
|
if (!this.credentials) {
|
|
23534
23634
|
throw new ConnectionError("No credentials available");
|
|
23535
23635
|
}
|
|
23636
|
+
if (this.ua) {
|
|
23637
|
+
console.log("SDK: Stopping existing User Agent...");
|
|
23638
|
+
this.ua.stop();
|
|
23639
|
+
this.ua = null;
|
|
23640
|
+
}
|
|
23536
23641
|
try {
|
|
23537
23642
|
const socket = new import_jssip.default.WebSocketInterface(this.credentials.wsUrl);
|
|
23538
23643
|
const pcConfig = {
|
|
@@ -23544,7 +23649,8 @@ var DoraCell = class {
|
|
|
23544
23649
|
sockets: [socket],
|
|
23545
23650
|
register: true,
|
|
23546
23651
|
display_name: this.getDisplayName(),
|
|
23547
|
-
sessionTimers:
|
|
23652
|
+
sessionTimers: true,
|
|
23653
|
+
session_timers_refresh_method: "UPDATE",
|
|
23548
23654
|
trickleIce: false,
|
|
23549
23655
|
pcConfig,
|
|
23550
23656
|
instance_id: this.generateInstanceId()
|
|
@@ -23554,6 +23660,9 @@ var DoraCell = class {
|
|
|
23554
23660
|
this.setupUserAgentHandlers();
|
|
23555
23661
|
console.log("SDK: Starting UA...");
|
|
23556
23662
|
this.ua.start();
|
|
23663
|
+
if (this.callManager) {
|
|
23664
|
+
this.callManager.setUserAgent(this.ua);
|
|
23665
|
+
}
|
|
23557
23666
|
} catch (error) {
|
|
23558
23667
|
throw new ConnectionError(
|
|
23559
23668
|
`Failed to initialize User Agent: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
@@ -23623,7 +23732,7 @@ var DoraCell = class {
|
|
|
23623
23732
|
/**
|
|
23624
23733
|
* Answer an incoming call
|
|
23625
23734
|
*/
|
|
23626
|
-
answerCall() {
|
|
23735
|
+
async answerCall() {
|
|
23627
23736
|
const currentCall = this.callManager?.getCurrentCall();
|
|
23628
23737
|
if (!currentCall) {
|
|
23629
23738
|
throw new CallError("No incoming call to answer");
|
|
@@ -23631,7 +23740,7 @@ var DoraCell = class {
|
|
|
23631
23740
|
if (currentCall.direction !== "inbound") {
|
|
23632
23741
|
throw new CallError("Current call is not an incoming call");
|
|
23633
23742
|
}
|
|
23634
|
-
this.callManager.answerCurrentCall();
|
|
23743
|
+
await this.callManager.answerCurrentCall();
|
|
23635
23744
|
}
|
|
23636
23745
|
/**
|
|
23637
23746
|
* Hangup the current call
|
|
@@ -23667,11 +23776,12 @@ var DoraCell = class {
|
|
|
23667
23776
|
return { balance: 0, currency: "NGN" };
|
|
23668
23777
|
}
|
|
23669
23778
|
const primary = wallets[0];
|
|
23779
|
+
this.userId = primary.user_id || null;
|
|
23670
23780
|
const result = {
|
|
23671
|
-
balance: parseFloat(primary.balance || "0"),
|
|
23781
|
+
balance: parseFloat(primary.balance || primary.amount || "0"),
|
|
23672
23782
|
currency: primary.currency || "NGN"
|
|
23673
23783
|
};
|
|
23674
|
-
console.log("SDK: Wallet balance fetched:",
|
|
23784
|
+
console.log("SDK: Wallet balance fetched successfully, userId:", this.userId);
|
|
23675
23785
|
return result;
|
|
23676
23786
|
} catch (error) {
|
|
23677
23787
|
console.error("SDK: Failed to fetch wallet:", error);
|
|
@@ -23692,7 +23802,9 @@ var DoraCell = class {
|
|
|
23692
23802
|
throw new Error("SDK not authenticated. Call initialize() first.");
|
|
23693
23803
|
}
|
|
23694
23804
|
try {
|
|
23695
|
-
const
|
|
23805
|
+
const path = this.userId ? `/user/${this.userId}/extensions` : "/extensions";
|
|
23806
|
+
console.log(`SDK: Fetching extensions from: ${path}`);
|
|
23807
|
+
const response = await this.apiClient.get(path);
|
|
23696
23808
|
const extensions = response.data || response.extensions || response;
|
|
23697
23809
|
if (this.credentials && Array.isArray(extensions)) {
|
|
23698
23810
|
this.credentials.extensions = extensions;
|
|
@@ -23715,7 +23827,7 @@ var DoraCell = class {
|
|
|
23715
23827
|
async setExtension(extension) {
|
|
23716
23828
|
console.log(`SDK: Switching to extension ${extension}...`);
|
|
23717
23829
|
if (this.credentials) {
|
|
23718
|
-
const domain = this.credentials.sipDomain || "
|
|
23830
|
+
const domain = this.credentials.sipDomain || "64.227.10.164";
|
|
23719
23831
|
this.credentials.sipUri = `sip:${extension}@${domain}`;
|
|
23720
23832
|
await this.initializeUserAgent();
|
|
23721
23833
|
this.emitConnectionStatus(this.connectionStatus);
|
|
@@ -23802,7 +23914,7 @@ var DoraCell = class {
|
|
|
23802
23914
|
case "staging":
|
|
23803
23915
|
case "dev":
|
|
23804
23916
|
return "https://dev.api.cell.usedora.com/api";
|
|
23805
|
-
|
|
23917
|
+
default:
|
|
23806
23918
|
return "https://api.cell.usedora.com/api";
|
|
23807
23919
|
}
|
|
23808
23920
|
}
|