@nuancelabs/js-sdk 0.2.1 → 0.4.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/index.d.ts +2 -8
- package/index.js +219 -22
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -216,8 +216,8 @@ export declare type ConversationEvent =
|
|
|
216
216
|
/**
|
|
217
217
|
* How long the live session has left, when it is capped.
|
|
218
218
|
*
|
|
219
|
-
* Emitted
|
|
220
|
-
* countdown should
|
|
219
|
+
* Emitted when the time left is learned or corrected, so it steps rather
|
|
220
|
+
* than ticks; a caller showing a countdown should count down from the latest.
|
|
221
221
|
*/
|
|
222
222
|
| {
|
|
223
223
|
type: "time-remaining";
|
|
@@ -344,9 +344,6 @@ export declare interface CreateNuanceConversationOptions {
|
|
|
344
344
|
}
|
|
345
345
|
|
|
346
346
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
347
|
/**
|
|
351
348
|
* Raised by {@link Conversation.connect} when the server refuses a new session
|
|
352
349
|
* because the account has not completed onboarding or accepted the current
|
|
@@ -406,7 +403,6 @@ export declare type MediaErrorReason = "permission-denied" | "device-not-found"
|
|
|
406
403
|
export declare type NetworkQuality = "good" | "poor" | "lost";
|
|
407
404
|
|
|
408
405
|
|
|
409
|
-
|
|
410
406
|
/**
|
|
411
407
|
* Raised by {@link Conversation.connect} when the server refuses a new session
|
|
412
408
|
* because the runtime recently ended too many of this account's sessions for
|
|
@@ -428,11 +424,9 @@ export declare type SessionEndReason = "time-limit" | "ended" | "policy-violatio
|
|
|
428
424
|
|
|
429
425
|
|
|
430
426
|
|
|
431
|
-
|
|
432
427
|
/** When the avatar starts speaking: see {@link CreateNuanceConversationOptions.startOn}. */
|
|
433
428
|
export declare type StartOn = "first-frame" | "full-quality";
|
|
434
429
|
|
|
435
|
-
|
|
436
430
|
/**
|
|
437
431
|
* The local user participant in a conversation.
|
|
438
432
|
*/
|
package/index.js
CHANGED
|
@@ -4,7 +4,7 @@ function isRecord(value) {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
// src/core/sdk-version.ts
|
|
7
|
-
var SDK_HEADER = `js/${"0.
|
|
7
|
+
var SDK_HEADER = `js/${"0.4.0"}`;
|
|
8
8
|
|
|
9
9
|
// src/utils.ts
|
|
10
10
|
function normalizeHost(host) {
|
|
@@ -56,6 +56,9 @@ class NuanceApiClient {
|
|
|
56
56
|
async heartbeatConversation(conversationId) {
|
|
57
57
|
return await this.request(`/conversation/${conversationId}/heartbeat`, { method: "POST", token: this.tokens.get(conversationId) });
|
|
58
58
|
}
|
|
59
|
+
async conversationEvents(conversationId, after, signal) {
|
|
60
|
+
return await this.request(`/conversation/${conversationId}/events?after=${after}`, { token: this.tokens.get(conversationId), signal });
|
|
61
|
+
}
|
|
59
62
|
async abandonConversation(conversationId) {
|
|
60
63
|
return await this.request(`/conversation/${conversationId}/abandon`, { method: "POST", token: this.tokens.get(conversationId) });
|
|
61
64
|
}
|
|
@@ -71,7 +74,8 @@ class NuanceApiClient {
|
|
|
71
74
|
const response = await fetch(`${this.host}${path}`, {
|
|
72
75
|
method: options?.method ?? "GET",
|
|
73
76
|
headers,
|
|
74
|
-
body: options?.body !== undefined ? JSON.stringify(options.body) : undefined
|
|
77
|
+
body: options?.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
78
|
+
signal: options?.signal
|
|
75
79
|
});
|
|
76
80
|
if (!response.ok) {
|
|
77
81
|
let detail = response.statusText;
|
|
@@ -1167,6 +1171,15 @@ function microphonePublishOptions(resilient) {
|
|
|
1167
1171
|
return;
|
|
1168
1172
|
return { dtx: false, audioPreset: AudioPresets.speech };
|
|
1169
1173
|
}
|
|
1174
|
+
var SCREEN_CAPTURE = {
|
|
1175
|
+
audio: false,
|
|
1176
|
+
resolution: { width: 1920, height: 1080, frameRate: 2 },
|
|
1177
|
+
contentHint: "detail"
|
|
1178
|
+
};
|
|
1179
|
+
var SCREEN_PUBLISH = {
|
|
1180
|
+
screenShareEncoding: { maxBitrate: 1500000, maxFramerate: 2 },
|
|
1181
|
+
simulcast: false
|
|
1182
|
+
};
|
|
1170
1183
|
|
|
1171
1184
|
class LiveKitUserParticipant {
|
|
1172
1185
|
room;
|
|
@@ -1174,6 +1187,8 @@ class LiveKitUserParticipant {
|
|
|
1174
1187
|
onSpeakingChanged;
|
|
1175
1188
|
onMicrophoneChanged;
|
|
1176
1189
|
onCameraChanged;
|
|
1190
|
+
onScreenShareChanged;
|
|
1191
|
+
screenTrack;
|
|
1177
1192
|
inputs = {
|
|
1178
1193
|
audioinput: { changing: false, unavailable: false },
|
|
1179
1194
|
videoinput: { changing: false, unavailable: false }
|
|
@@ -1188,7 +1203,7 @@ class LiveKitUserParticipant {
|
|
|
1188
1203
|
lastAudioSource = null;
|
|
1189
1204
|
_isSpeaking = false;
|
|
1190
1205
|
speakingHandler = null;
|
|
1191
|
-
constructor(room, onTracksChanged, onSpeakingChanged, onMicrophoneChanged, onCameraChanged = () => {}, initialCameraFacing, resilient = false) {
|
|
1206
|
+
constructor(room, onTracksChanged, onSpeakingChanged, onMicrophoneChanged, onCameraChanged = () => {}, initialCameraFacing, resilient = false, onScreenShareChanged = () => {}) {
|
|
1192
1207
|
this.room = room;
|
|
1193
1208
|
this.onTracksChanged = onTracksChanged;
|
|
1194
1209
|
this.onSpeakingChanged = onSpeakingChanged;
|
|
@@ -1196,7 +1211,9 @@ class LiveKitUserParticipant {
|
|
|
1196
1211
|
this.onCameraChanged = onCameraChanged;
|
|
1197
1212
|
this.initialCameraFacing = initialCameraFacing;
|
|
1198
1213
|
this.microphoneOptions = microphonePublishOptions(resilient);
|
|
1214
|
+
this.onScreenShareChanged = onScreenShareChanged;
|
|
1199
1215
|
this.room.on(RoomEvent2.ActiveDeviceChanged, this.handleDeviceChanged);
|
|
1216
|
+
this.room.localParticipant.on(ParticipantEvent2.LocalTrackUnpublished, this.handleTrackUnpublished);
|
|
1200
1217
|
}
|
|
1201
1218
|
updateTracks() {
|
|
1202
1219
|
let changed = false;
|
|
@@ -1357,9 +1374,14 @@ class LiveKitUserParticipant {
|
|
|
1357
1374
|
this.notifyInput(kind);
|
|
1358
1375
|
}
|
|
1359
1376
|
};
|
|
1377
|
+
handleTrackUnpublished = (publication) => {
|
|
1378
|
+
if (!this.disposed && publication.source === Track5.Source.ScreenShare)
|
|
1379
|
+
this.onScreenShareChanged();
|
|
1380
|
+
};
|
|
1360
1381
|
cleanup() {
|
|
1361
1382
|
this.disposed = true;
|
|
1362
1383
|
this.room.off(RoomEvent2.ActiveDeviceChanged, this.handleDeviceChanged);
|
|
1384
|
+
this.room.localParticipant.off(ParticipantEvent2.LocalTrackUnpublished, this.handleTrackUnpublished);
|
|
1363
1385
|
if (this.inputs.audioinput.changing)
|
|
1364
1386
|
this.inputTrack("audioinput")?.stop();
|
|
1365
1387
|
if (this.inputs.videoinput.changing)
|
|
@@ -1437,10 +1459,44 @@ class LiveKitUserParticipant {
|
|
|
1437
1459
|
get isVideoEnabled() {
|
|
1438
1460
|
return this.room.localParticipant.isCameraEnabled;
|
|
1439
1461
|
}
|
|
1462
|
+
async startScreenShare() {
|
|
1463
|
+
await this.setScreenShare(true);
|
|
1464
|
+
}
|
|
1465
|
+
async stopScreenShare() {
|
|
1466
|
+
const track = this.screenTrack;
|
|
1467
|
+
this.screenTrack = undefined;
|
|
1468
|
+
try {
|
|
1469
|
+
if (!this.disposed)
|
|
1470
|
+
await this.setScreenShare(false);
|
|
1471
|
+
} finally {
|
|
1472
|
+
track?.stop();
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
get isScreenSharing() {
|
|
1476
|
+
return this.room.localParticipant.isScreenShareEnabled;
|
|
1477
|
+
}
|
|
1478
|
+
async setScreenShare(enabled) {
|
|
1479
|
+
if (this.disposed)
|
|
1480
|
+
throw new Error("Participant is disconnected");
|
|
1481
|
+
try {
|
|
1482
|
+
const publication = await this.room.localParticipant.setScreenShareEnabled(enabled, SCREEN_CAPTURE, SCREEN_PUBLISH);
|
|
1483
|
+
if (this.disposed)
|
|
1484
|
+
publication?.track?.stop();
|
|
1485
|
+
else if (enabled)
|
|
1486
|
+
this.screenTrack = publication?.track;
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
throw new MediaAccessError(error);
|
|
1489
|
+
} finally {
|
|
1490
|
+
if (!this.disposed)
|
|
1491
|
+
this.onScreenShareChanged();
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1440
1494
|
releaseDevices() {
|
|
1441
1495
|
for (const pub of this.room.localParticipant.trackPublications.values()) {
|
|
1442
1496
|
pub.track?.stop();
|
|
1443
1497
|
}
|
|
1498
|
+
this.screenTrack?.stop();
|
|
1499
|
+
this.screenTrack = undefined;
|
|
1444
1500
|
}
|
|
1445
1501
|
startSpeakingDetection() {
|
|
1446
1502
|
this.stopSpeakingDetection();
|
|
@@ -1601,7 +1657,7 @@ class LiveKitConversation {
|
|
|
1601
1657
|
type: "speaking-changed",
|
|
1602
1658
|
participant: "user",
|
|
1603
1659
|
isSpeaking
|
|
1604
|
-
}), () => this.emit({ type: "microphone-changed" }), () => this.emit({ type: "camera-changed" }), this.cameraFacing, this.resilient);
|
|
1660
|
+
}), () => this.emit({ type: "microphone-changed" }), () => this.emit({ type: "camera-changed" }), this.cameraFacing, this.resilient, () => this.emit({ type: "screen-share-changed" }));
|
|
1605
1661
|
this._user.startSpeakingDetection();
|
|
1606
1662
|
try {
|
|
1607
1663
|
await this._user.unmuteAudio();
|
|
@@ -1677,7 +1733,7 @@ class LiveKitConversation {
|
|
|
1677
1733
|
}
|
|
1678
1734
|
onChange(handler) {
|
|
1679
1735
|
return this.onEvent((event) => {
|
|
1680
|
-
if (event.type !== "data-event")
|
|
1736
|
+
if (event.type !== "data-event" && event.type !== "screen-share-changed")
|
|
1681
1737
|
handler(event);
|
|
1682
1738
|
});
|
|
1683
1739
|
}
|
|
@@ -2085,7 +2141,8 @@ class LiveKitConversation {
|
|
|
2085
2141
|
var DEFAULT_HOST = "https://api.nuancelabs.ai";
|
|
2086
2142
|
var QUEUE_HEARTBEAT_INTERVAL_MS = 5000;
|
|
2087
2143
|
var HEARTBEAT_INTERVAL_MS = 5000;
|
|
2088
|
-
var
|
|
2144
|
+
var EVENTS_RETRY_MS = 5000;
|
|
2145
|
+
var ADMISSION_FAILED_REQUESTS = 6;
|
|
2089
2146
|
var BUSY_RETRY_LIMIT_MS = 60000;
|
|
2090
2147
|
var BUSY_RETRY_DEFAULT_MS = 3000;
|
|
2091
2148
|
var LOST_WINDOW_MS = 30000;
|
|
@@ -2152,6 +2209,7 @@ class NuanceConversation {
|
|
|
2152
2209
|
delegate = null;
|
|
2153
2210
|
delegateUnsubscribe = null;
|
|
2154
2211
|
stopBeats = null;
|
|
2212
|
+
stopEvents = null;
|
|
2155
2213
|
admitted = null;
|
|
2156
2214
|
failedBeats = 0;
|
|
2157
2215
|
resilient = false;
|
|
@@ -2240,10 +2298,8 @@ class NuanceConversation {
|
|
|
2240
2298
|
this._id = conversation.id;
|
|
2241
2299
|
this.held = conversation.id;
|
|
2242
2300
|
this.emit({ type: "session-created", id: conversation.id });
|
|
2243
|
-
|
|
2244
|
-
this.
|
|
2245
|
-
await this.admitted.promise;
|
|
2246
|
-
this.delegate = await this.createDelegate(conversation, mediaDevices, attempt);
|
|
2301
|
+
const grant = await this.waitForSlot(conversation.id, attempt);
|
|
2302
|
+
this.delegate = await this.createDelegate(conversation, grant, mediaDevices, attempt);
|
|
2247
2303
|
this.delegateUnsubscribe = this.delegate.onEvent((event) => {
|
|
2248
2304
|
if (event.type === "state-changed") {
|
|
2249
2305
|
this.setState(event.state);
|
|
@@ -2266,7 +2322,9 @@ class NuanceConversation {
|
|
|
2266
2322
|
this.emit(event);
|
|
2267
2323
|
});
|
|
2268
2324
|
await this.delegate.connect();
|
|
2269
|
-
|
|
2325
|
+
if (grant === null) {
|
|
2326
|
+
this.startHeartbeat(conversation.id, HEARTBEAT_INTERVAL_MS);
|
|
2327
|
+
}
|
|
2270
2328
|
} catch (error) {
|
|
2271
2329
|
if (attempt !== this.attempt) {
|
|
2272
2330
|
throw error;
|
|
@@ -2280,6 +2338,8 @@ class NuanceConversation {
|
|
|
2280
2338
|
this.attempt += 1;
|
|
2281
2339
|
this.clearLimit();
|
|
2282
2340
|
this.stopHeartbeat();
|
|
2341
|
+
this.stopEvents?.();
|
|
2342
|
+
this.stopEvents = null;
|
|
2283
2343
|
this.admitted?.reject(new Error("Disconnected while waiting for a slot"));
|
|
2284
2344
|
this.admitted = null;
|
|
2285
2345
|
this.delegateUnsubscribe?.();
|
|
@@ -2321,7 +2381,7 @@ class NuanceConversation {
|
|
|
2321
2381
|
}
|
|
2322
2382
|
onChange(handler) {
|
|
2323
2383
|
return this.onEvent((event) => {
|
|
2324
|
-
if (event.type !== "data-event")
|
|
2384
|
+
if (event.type !== "data-event" && event.type !== "screen-share-changed")
|
|
2325
2385
|
handler(event);
|
|
2326
2386
|
});
|
|
2327
2387
|
}
|
|
@@ -2338,19 +2398,146 @@ class NuanceConversation {
|
|
|
2338
2398
|
}
|
|
2339
2399
|
});
|
|
2340
2400
|
}
|
|
2341
|
-
async
|
|
2401
|
+
async waitForSlot(conversationId, attempt) {
|
|
2402
|
+
const admission = await this.followEvents(conversationId, attempt);
|
|
2403
|
+
if (admission !== null) {
|
|
2404
|
+
this.followLive(conversationId, admission.after, attempt);
|
|
2405
|
+
return admission.grant;
|
|
2406
|
+
}
|
|
2407
|
+
this.admitted = Promise.withResolvers();
|
|
2408
|
+
this.startHeartbeat(conversationId, QUEUE_HEARTBEAT_INTERVAL_MS);
|
|
2409
|
+
await this.admitted.promise;
|
|
2410
|
+
return null;
|
|
2411
|
+
}
|
|
2412
|
+
async followEvents(conversationId, attempt) {
|
|
2413
|
+
const abort = new AbortController;
|
|
2414
|
+
const stop = () => abort.abort();
|
|
2415
|
+
this.stopEvents = stop;
|
|
2416
|
+
let after = 0;
|
|
2417
|
+
let answered = false;
|
|
2418
|
+
let failures = 0;
|
|
2419
|
+
try {
|
|
2420
|
+
for (;; ) {
|
|
2421
|
+
let events;
|
|
2422
|
+
try {
|
|
2423
|
+
({ events } = await this.api.conversationEvents(conversationId, after, abort.signal));
|
|
2424
|
+
} catch (error) {
|
|
2425
|
+
if (attempt !== this.attempt)
|
|
2426
|
+
throw new DisconnectedError;
|
|
2427
|
+
if (!answered && error instanceof ApiError && error.status === 404) {
|
|
2428
|
+
return null;
|
|
2429
|
+
}
|
|
2430
|
+
failures += 1;
|
|
2431
|
+
if (failures >= ADMISSION_FAILED_REQUESTS) {
|
|
2432
|
+
throw new Error("Lost contact with the server while waiting", {
|
|
2433
|
+
cause: error
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2436
|
+
await this.pause(EVENTS_RETRY_MS, abort.signal);
|
|
2437
|
+
if (attempt !== this.attempt)
|
|
2438
|
+
throw new DisconnectedError;
|
|
2439
|
+
continue;
|
|
2440
|
+
}
|
|
2441
|
+
if (attempt !== this.attempt)
|
|
2442
|
+
throw new DisconnectedError;
|
|
2443
|
+
answered = true;
|
|
2444
|
+
failures = 0;
|
|
2445
|
+
for (const event of events) {
|
|
2446
|
+
if (event.type === "admitted") {
|
|
2447
|
+
const grant = {
|
|
2448
|
+
server_url: event.livekit_url,
|
|
2449
|
+
participant_token: event.token
|
|
2450
|
+
};
|
|
2451
|
+
return { grant, after: event.id };
|
|
2452
|
+
}
|
|
2453
|
+
after = Math.max(after, event.id);
|
|
2454
|
+
if (event.type === "ended") {
|
|
2455
|
+
throw new TerminalStatusError(event.reason);
|
|
2456
|
+
}
|
|
2457
|
+
if (event.type === "queued" && event.position != null) {
|
|
2458
|
+
this.reportPlace(event.position, event.estimated_wait_seconds ?? null, null);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
} finally {
|
|
2463
|
+
if (this.stopEvents === stop)
|
|
2464
|
+
this.stopEvents = null;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
async followLive(conversationId, after, attempt) {
|
|
2468
|
+
const abort = new AbortController;
|
|
2469
|
+
const stop = () => abort.abort();
|
|
2470
|
+
this.stopEvents = stop;
|
|
2471
|
+
let failures = 0;
|
|
2472
|
+
try {
|
|
2473
|
+
while (attempt === this.attempt && !this.ended) {
|
|
2474
|
+
let events;
|
|
2475
|
+
try {
|
|
2476
|
+
({ events } = await this.api.conversationEvents(conversationId, after, abort.signal));
|
|
2477
|
+
} catch {
|
|
2478
|
+
if (attempt !== this.attempt)
|
|
2479
|
+
return;
|
|
2480
|
+
failures += 1;
|
|
2481
|
+
await this.pause(EVENTS_RETRY_MS, abort.signal);
|
|
2482
|
+
continue;
|
|
2483
|
+
}
|
|
2484
|
+
if (attempt !== this.attempt)
|
|
2485
|
+
return;
|
|
2486
|
+
const receivedAt = Date.now();
|
|
2487
|
+
this.lostContact = failures > 0;
|
|
2488
|
+
failures = 0;
|
|
2489
|
+
for (const event of events) {
|
|
2490
|
+
after = Math.max(after, event.id);
|
|
2491
|
+
if (event.type === "started" && event.seconds_remaining != null) {
|
|
2492
|
+
this.capped = true;
|
|
2493
|
+
this.emit({
|
|
2494
|
+
type: "time-remaining",
|
|
2495
|
+
secondsRemaining: event.seconds_remaining
|
|
2496
|
+
});
|
|
2497
|
+
this.armLimit(event.seconds_remaining, receivedAt);
|
|
2498
|
+
}
|
|
2499
|
+
if (event.type === "ended") {
|
|
2500
|
+
this.endSession(this.endReason(event.reason));
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
} finally {
|
|
2506
|
+
if (this.stopEvents === stop)
|
|
2507
|
+
this.stopEvents = null;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
endReason(reason) {
|
|
2511
|
+
if (reason === "time_limit" || this.atLimit())
|
|
2512
|
+
return "time-limit";
|
|
2513
|
+
if (reason === "policy_violation")
|
|
2514
|
+
return "policy-violation";
|
|
2515
|
+
if (this.cutOff())
|
|
2516
|
+
return "connection-lost";
|
|
2517
|
+
return "ended";
|
|
2518
|
+
}
|
|
2519
|
+
pause(ms, signal) {
|
|
2520
|
+
return new Promise((resolve) => {
|
|
2521
|
+
let stopTicker = () => {};
|
|
2522
|
+
const done = () => {
|
|
2523
|
+
stopTicker();
|
|
2524
|
+
signal.removeEventListener("abort", done);
|
|
2525
|
+
resolve();
|
|
2526
|
+
};
|
|
2527
|
+
stopTicker = this.ticker(done, ms);
|
|
2528
|
+
signal.addEventListener("abort", done);
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
async createDelegate(conversation, grant, mediaDevices, attempt) {
|
|
2342
2532
|
if (conversation.provider !== "livekit") {
|
|
2343
2533
|
throw new Error(`Unsupported provider ${conversation.provider}`);
|
|
2344
2534
|
}
|
|
2345
|
-
const
|
|
2535
|
+
const connection = grant ?? await this.connectRoom(conversation.id);
|
|
2346
2536
|
if (attempt !== this.attempt) {
|
|
2347
2537
|
throw new DisconnectedError;
|
|
2348
2538
|
}
|
|
2349
|
-
if (response.connection.kind !== "livekit") {
|
|
2350
|
-
throw new Error(`Expected LiveKit connection, received ${response.connection.kind}`);
|
|
2351
|
-
}
|
|
2352
2539
|
this.resilient = typeof this.resilientReconnect === "function" ? this.resilientReconnect() : this.resilientReconnect;
|
|
2353
|
-
return this.provider(
|
|
2540
|
+
return this.provider(connection, {
|
|
2354
2541
|
conversationId: conversation.id,
|
|
2355
2542
|
mediaDevices,
|
|
2356
2543
|
cameraEnabled: !this.sessionOverrides.disable_camera && mediaDevices.cameraEnabled !== false,
|
|
@@ -2359,6 +2546,13 @@ class NuanceConversation {
|
|
|
2359
2546
|
resilientReconnect: this.resilient
|
|
2360
2547
|
});
|
|
2361
2548
|
}
|
|
2549
|
+
async connectRoom(conversationId) {
|
|
2550
|
+
const response = await this.api.connectConversation(conversationId);
|
|
2551
|
+
if (response.connection.kind !== "livekit") {
|
|
2552
|
+
throw new Error(`Expected LiveKit connection, received ${response.connection.kind}`);
|
|
2553
|
+
}
|
|
2554
|
+
return response.connection;
|
|
2555
|
+
}
|
|
2362
2556
|
async createWhenNotBusy(request, attempt) {
|
|
2363
2557
|
const giveUpAt = Date.now() + BUSY_RETRY_LIMIT_MS;
|
|
2364
2558
|
for (;; ) {
|
|
@@ -2406,7 +2600,7 @@ class NuanceConversation {
|
|
|
2406
2600
|
return;
|
|
2407
2601
|
if (!(error instanceof TerminalStatusError)) {
|
|
2408
2602
|
this.failedBeats += 1;
|
|
2409
|
-
if (this.admitted && this.failedBeats >=
|
|
2603
|
+
if (this.admitted && this.failedBeats >= ADMISSION_FAILED_REQUESTS) {
|
|
2410
2604
|
this.admitted.reject(new Error("Lost contact with the server while waiting", {
|
|
2411
2605
|
cause: error
|
|
2412
2606
|
}));
|
|
@@ -2445,14 +2639,17 @@ class NuanceConversation {
|
|
|
2445
2639
|
if (state.status !== "queued" || state.position == null) {
|
|
2446
2640
|
return;
|
|
2447
2641
|
}
|
|
2642
|
+
this.reportPlace(state.position, state.estimated_wait_seconds ?? null, state.pool_slots ?? null);
|
|
2643
|
+
}
|
|
2644
|
+
reportPlace(position, estimate, poolSlots) {
|
|
2448
2645
|
if (this._state !== "queued") {
|
|
2449
2646
|
this.setState("queued");
|
|
2450
2647
|
}
|
|
2451
2648
|
this.emit({
|
|
2452
2649
|
type: "queue-changed",
|
|
2453
|
-
position
|
|
2454
|
-
estimatedWaitSeconds: this.smoothEstimate(
|
|
2455
|
-
poolSlots
|
|
2650
|
+
position,
|
|
2651
|
+
estimatedWaitSeconds: this.smoothEstimate(position, estimate),
|
|
2652
|
+
poolSlots
|
|
2456
2653
|
});
|
|
2457
2654
|
}
|
|
2458
2655
|
smoothEstimate(position, estimate) {
|