@vapi-ai/web 3.0.0 → 3.1.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/README.md +11 -1
- package/api.ts +8610 -5557
- package/dist/api.d.ts +6568 -4374
- package/dist/api.js +345 -17
- package/dist/jest.config.js +3 -0
- package/dist/vapi.d.ts +46 -3
- package/dist/vapi.js +157 -95
- package/package.json +2 -2
- package/vapi.ts +224 -126
package/dist/vapi.js
CHANGED
|
@@ -58,6 +58,8 @@ function serializeError(error) {
|
|
|
58
58
|
}
|
|
59
59
|
return { message: String(error) };
|
|
60
60
|
}
|
|
61
|
+
// The app message Vapi sends when it couldn't start a server-mode recording.
|
|
62
|
+
const VIDEO_RECORDING_START_FAILED_MESSAGE_TYPE = 'video-recording.start-failed';
|
|
61
63
|
async function startAudioPlayer(player, track) {
|
|
62
64
|
player.muted = false;
|
|
63
65
|
player.autoplay = true;
|
|
@@ -88,13 +90,13 @@ function destroyAudioPlayer(participantId) {
|
|
|
88
90
|
const player = document.querySelector(`audio[data-participant-id="${participantId}"]`);
|
|
89
91
|
player?.remove();
|
|
90
92
|
}
|
|
91
|
-
function subscribeToTracks(e, call, isVideoRecordingEnabled
|
|
93
|
+
function subscribeToTracks(e, call, isVideoRecordingEnabled) {
|
|
92
94
|
if (e.participant.local)
|
|
93
95
|
return;
|
|
94
96
|
call.updateParticipant(e.participant.session_id, {
|
|
95
97
|
setSubscribedTracks: {
|
|
96
98
|
audio: true,
|
|
97
|
-
video: isVideoRecordingEnabled
|
|
99
|
+
video: isVideoRecordingEnabled,
|
|
98
100
|
},
|
|
99
101
|
});
|
|
100
102
|
}
|
|
@@ -134,6 +136,39 @@ class Vapi extends VapiEventEmitter {
|
|
|
134
136
|
speakingTimeout = null;
|
|
135
137
|
dailyCallConfig = {};
|
|
136
138
|
dailyCallObject = {};
|
|
139
|
+
microphoneTrack = null;
|
|
140
|
+
cancelMicrophonePreflight = null;
|
|
141
|
+
async microphoneAcquire() {
|
|
142
|
+
const source = this.dailyCallObject.audioSource;
|
|
143
|
+
if (source && typeof source === 'object') {
|
|
144
|
+
if (source.readyState === 'ended') {
|
|
145
|
+
throw new DOMException('The supplied microphone track has ended.', 'NotReadableError');
|
|
146
|
+
}
|
|
147
|
+
return source;
|
|
148
|
+
}
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
let cancelled = false;
|
|
151
|
+
this.cancelMicrophonePreflight = () => {
|
|
152
|
+
cancelled = true;
|
|
153
|
+
reject(new DOMException('Microphone preflight cancelled.', 'AbortError'));
|
|
154
|
+
};
|
|
155
|
+
navigator.mediaDevices.getUserMedia({
|
|
156
|
+
audio: typeof source === 'string' ? { deviceId: { exact: source } } : true,
|
|
157
|
+
}).then((stream) => {
|
|
158
|
+
const track = stream.getAudioTracks()[0];
|
|
159
|
+
if (cancelled || !track || track.readyState === 'ended') {
|
|
160
|
+
stream.getTracks().forEach((item) => item.stop());
|
|
161
|
+
reject(new DOMException('No live microphone track available.', 'NotFoundError'));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
stream.getTracks().filter((item) => item !== track).forEach((item) => item.stop());
|
|
165
|
+
this.microphoneTrack = track;
|
|
166
|
+
resolve(track);
|
|
167
|
+
}, reject);
|
|
168
|
+
}).finally(() => {
|
|
169
|
+
this.cancelMicrophonePreflight = null;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
137
172
|
hasEmittedCallEndedStatus = false;
|
|
138
173
|
constructor(apiToken, apiBaseUrl, dailyCallConfig, dailyCallObject) {
|
|
139
174
|
super();
|
|
@@ -143,6 +178,8 @@ class Vapi extends VapiEventEmitter {
|
|
|
143
178
|
this.dailyCallObject = (0, daily_guards_1.createSafeDailyFactoryOptions)(dailyCallObject);
|
|
144
179
|
}
|
|
145
180
|
async cleanup() {
|
|
181
|
+
this.microphoneTrack?.stop();
|
|
182
|
+
this.microphoneTrack = null;
|
|
146
183
|
this.started = false;
|
|
147
184
|
this.hasEmittedCallEndedStatus = false;
|
|
148
185
|
if (this.call) {
|
|
@@ -243,7 +280,16 @@ class Vapi extends VapiEventEmitter {
|
|
|
243
280
|
}
|
|
244
281
|
});
|
|
245
282
|
this.started = true;
|
|
283
|
+
let failureStage = 'microphone-preflight';
|
|
246
284
|
try {
|
|
285
|
+
if (this.call) {
|
|
286
|
+
await this.cleanup();
|
|
287
|
+
this.started = true;
|
|
288
|
+
}
|
|
289
|
+
const audioSource = options?.microphonePreflightEnabled === false
|
|
290
|
+
? this.dailyCallObject.audioSource ?? true
|
|
291
|
+
: await this.microphoneAcquire();
|
|
292
|
+
failureStage = 'unknown';
|
|
247
293
|
// Stage 1: Create web call
|
|
248
294
|
this.emit('call-start-progress', {
|
|
249
295
|
stage: 'web-call-creation',
|
|
@@ -251,7 +297,9 @@ class Vapi extends VapiEventEmitter {
|
|
|
251
297
|
timestamp: new Date().toISOString()
|
|
252
298
|
});
|
|
253
299
|
const webCallStartTime = Date.now();
|
|
254
|
-
|
|
300
|
+
// An unset option stays undefined, so JSON.stringify drops the key and
|
|
301
|
+
// older servers never see it.
|
|
302
|
+
const createWebCallBody = {
|
|
255
303
|
assistant: typeof assistant === 'string' ? undefined : assistant,
|
|
256
304
|
assistantId: typeof assistant === 'string' ? assistant : undefined,
|
|
257
305
|
assistantOverrides,
|
|
@@ -261,7 +309,9 @@ class Vapi extends VapiEventEmitter {
|
|
|
261
309
|
workflowId: typeof workflow === 'string' ? workflow : undefined,
|
|
262
310
|
workflowOverrides,
|
|
263
311
|
roomDeleteOnUserLeaveEnabled: options?.roomDeleteOnUserLeaveEnabled,
|
|
264
|
-
|
|
312
|
+
videoRecordingStartMode: options?.videoRecordingStartMode,
|
|
313
|
+
};
|
|
314
|
+
const webCall = (await client_1.client.call.callControllerCreateWebCall(createWebCallBody)).data;
|
|
265
315
|
const webCallDuration = Date.now() - webCallStartTime;
|
|
266
316
|
this.emit('call-start-progress', {
|
|
267
317
|
stage: 'web-call-creation',
|
|
@@ -274,22 +324,17 @@ class Vapi extends VapiEventEmitter {
|
|
|
274
324
|
voiceProvider: webCall?.assistant?.voice?.provider || 'unknown'
|
|
275
325
|
}
|
|
276
326
|
});
|
|
277
|
-
if (this.call) {
|
|
278
|
-
this.emit('call-start-progress', {
|
|
279
|
-
stage: 'daily-call-object-creation',
|
|
280
|
-
status: 'started',
|
|
281
|
-
timestamp: new Date().toISOString(),
|
|
282
|
-
metadata: { action: 'cleanup-existing' }
|
|
283
|
-
});
|
|
284
|
-
await this.cleanup();
|
|
285
|
-
}
|
|
286
327
|
const isVideoRecordingEnabled = webCall?.artifactPlan?.videoRecordingEnabled ?? false;
|
|
287
|
-
const isVideoEnabled = webCall?.assistant?.voice?.provider === 'tavus';
|
|
288
328
|
// When video recording is enabled, the server sends a Daily meeting
|
|
289
|
-
// token
|
|
290
|
-
//
|
|
291
|
-
// servers
|
|
292
|
-
|
|
329
|
+
// token. In client mode it auto-starts the cloud recording on join. In
|
|
330
|
+
// server mode it starts nothing: Vapi starts the recording from its
|
|
331
|
+
// servers once the user has joined, which a weak client network can't
|
|
332
|
+
// stop. Without a token the SDK emits recording-error and starts
|
|
333
|
+
// nothing.
|
|
334
|
+
const transport = webCall?.transport;
|
|
335
|
+
const callToken = transport?.callToken;
|
|
336
|
+
const isRecordingStartedByServer = transport?.videoRecordingStartMode === 'server';
|
|
337
|
+
const recordingTokenMissing = isVideoRecordingEnabled && !callToken;
|
|
293
338
|
// Stage 2: Create Daily call object
|
|
294
339
|
this.emit('call-start-progress', {
|
|
295
340
|
stage: 'daily-call-object-creation',
|
|
@@ -299,13 +344,12 @@ class Vapi extends VapiEventEmitter {
|
|
|
299
344
|
audioSource: this.dailyCallObject.audioSource ?? true,
|
|
300
345
|
videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
|
|
301
346
|
isVideoRecordingEnabled,
|
|
302
|
-
isVideoEnabled
|
|
303
347
|
}
|
|
304
348
|
});
|
|
305
349
|
const dailyCallStartTime = Date.now();
|
|
306
350
|
try {
|
|
307
351
|
this.call = daily_js_1.default.createCallObject({
|
|
308
|
-
audioSource
|
|
352
|
+
audioSource,
|
|
309
353
|
videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
|
|
310
354
|
dailyConfig: this.dailyCallConfig,
|
|
311
355
|
});
|
|
@@ -400,7 +444,7 @@ class Vapi extends VapiEventEmitter {
|
|
|
400
444
|
this.call.on('participant-joined', (e) => {
|
|
401
445
|
if (!e || !this.call)
|
|
402
446
|
return;
|
|
403
|
-
subscribeToTracks(e, this.call, isVideoRecordingEnabled
|
|
447
|
+
subscribeToTracks(e, this.call, isVideoRecordingEnabled);
|
|
404
448
|
});
|
|
405
449
|
this.call.on('participant-updated', (e) => {
|
|
406
450
|
if (!e) {
|
|
@@ -414,6 +458,10 @@ class Vapi extends VapiEventEmitter {
|
|
|
414
458
|
}
|
|
415
459
|
this.detachAudioPlayer(e.participant.session_id);
|
|
416
460
|
});
|
|
461
|
+
// Registered before join(), as in reconnect(): Vapi starts a server-mode
|
|
462
|
+
// recording when it sees the user join, so its start-failed message can
|
|
463
|
+
// arrive while join() is still in flight.
|
|
464
|
+
this.call.on('app-message', (e) => this.onAppMessage(e));
|
|
417
465
|
// Registered before join(): a token-auto-started recording can begin
|
|
418
466
|
// while join() is still in flight, so a listener added after join
|
|
419
467
|
// could miss the event.
|
|
@@ -508,51 +556,29 @@ class Vapi extends VapiEventEmitter {
|
|
|
508
556
|
}
|
|
509
557
|
// Stage 5: Video recording setup (if enabled)
|
|
510
558
|
if (isVideoRecordingEnabled && callToken) {
|
|
511
|
-
// The meeting token auto-started the recording at join,
|
|
512
|
-
// nothing to request from the
|
|
559
|
+
// The meeting token auto-started the recording at join, or Vapi
|
|
560
|
+
// starts it from its servers, so there is nothing to request from the
|
|
561
|
+
// client.
|
|
513
562
|
this.emit('call-start-progress', {
|
|
514
563
|
stage: 'video-recording-setup',
|
|
515
564
|
status: 'completed',
|
|
516
565
|
timestamp: new Date().toISOString(),
|
|
517
|
-
metadata: {
|
|
566
|
+
metadata: {
|
|
567
|
+
action: isRecordingStartedByServer
|
|
568
|
+
? 'started-by-server'
|
|
569
|
+
: 'auto-started-via-meeting-token'
|
|
570
|
+
}
|
|
518
571
|
});
|
|
519
572
|
}
|
|
520
573
|
else if (isVideoRecordingEnabled) {
|
|
574
|
+
// No token: nothing starts the recording. The recording-error is
|
|
575
|
+
// reported once start() has finished.
|
|
521
576
|
this.emit('call-start-progress', {
|
|
522
577
|
stage: 'video-recording-setup',
|
|
523
|
-
status: '
|
|
524
|
-
timestamp: new Date().toISOString()
|
|
578
|
+
status: 'failed',
|
|
579
|
+
timestamp: new Date().toISOString(),
|
|
580
|
+
metadata: { action: 'no-meeting-token' }
|
|
525
581
|
});
|
|
526
|
-
const recordingStartTime = Date.now();
|
|
527
|
-
try {
|
|
528
|
-
recordingRequestedTime = new Date().getTime();
|
|
529
|
-
this.startRecording();
|
|
530
|
-
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
531
|
-
this.emit('call-start-progress', {
|
|
532
|
-
stage: 'video-recording-setup',
|
|
533
|
-
status: 'completed',
|
|
534
|
-
duration: recordingSetupDuration,
|
|
535
|
-
timestamp: new Date().toISOString()
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
catch (error) {
|
|
539
|
-
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
540
|
-
const serializedError = serializeError(error);
|
|
541
|
-
this.emit('call-start-progress', {
|
|
542
|
-
stage: 'video-recording-setup',
|
|
543
|
-
status: 'failed',
|
|
544
|
-
duration: recordingSetupDuration,
|
|
545
|
-
timestamp: new Date().toISOString(),
|
|
546
|
-
metadata: { error: serializedError.message }
|
|
547
|
-
});
|
|
548
|
-
this.emit('error', {
|
|
549
|
-
type: 'video-recording-setup-error',
|
|
550
|
-
stage: 'video-recording-setup',
|
|
551
|
-
error: serializedError,
|
|
552
|
-
timestamp: new Date().toISOString()
|
|
553
|
-
});
|
|
554
|
-
// Don't throw here, video recording is optional
|
|
555
|
-
}
|
|
556
582
|
}
|
|
557
583
|
else {
|
|
558
584
|
this.emit('call-start-progress', {
|
|
@@ -605,7 +631,6 @@ class Vapi extends VapiEventEmitter {
|
|
|
605
631
|
if (e)
|
|
606
632
|
this.handleLocalAudioLevel(e);
|
|
607
633
|
});
|
|
608
|
-
this.call.on('app-message', (e) => this.onAppMessage(e));
|
|
609
634
|
this.call.on('nonfatal-error', (e) => {
|
|
610
635
|
// https://docs.daily.co/reference/daily-js/events/meeting-events#type-audio-processor-error
|
|
611
636
|
if (e?.type === 'local-audio-level-observer-error') {
|
|
@@ -688,13 +713,17 @@ class Vapi extends VapiEventEmitter {
|
|
|
688
713
|
callId: webCall?.id || 'unknown',
|
|
689
714
|
timestamp: new Date().toISOString()
|
|
690
715
|
});
|
|
716
|
+
if (recordingTokenMissing) {
|
|
717
|
+
this.recordingTokenMissingReportSchedule();
|
|
718
|
+
}
|
|
691
719
|
return webCall;
|
|
692
720
|
}
|
|
693
721
|
catch (e) {
|
|
694
722
|
const totalDuration = Date.now() - startTime;
|
|
695
723
|
const serializedError = serializeError(e);
|
|
724
|
+
await this.cleanup();
|
|
696
725
|
this.emit('call-start-failed', {
|
|
697
|
-
stage:
|
|
726
|
+
stage: failureStage,
|
|
698
727
|
totalDuration,
|
|
699
728
|
error: serializedError.message,
|
|
700
729
|
errorStack: serializedError.stack || 'No stack trace available',
|
|
@@ -708,8 +737,10 @@ class Vapi extends VapiEventEmitter {
|
|
|
708
737
|
});
|
|
709
738
|
// Also emit the generic error event for backward compatibility
|
|
710
739
|
this.emit('error', {
|
|
711
|
-
type: '
|
|
712
|
-
|
|
740
|
+
type: failureStage === 'microphone-preflight'
|
|
741
|
+
? `microphone-preflight-${e && typeof e === 'object' && 'name' in e ? e.name : 'error'}`
|
|
742
|
+
: 'start-method-error',
|
|
743
|
+
stage: failureStage,
|
|
713
744
|
error: serializedError,
|
|
714
745
|
totalDuration,
|
|
715
746
|
timestamp: new Date().toISOString(),
|
|
@@ -720,7 +751,6 @@ class Vapi extends VapiEventEmitter {
|
|
|
720
751
|
isMobile: this.isMobileDevice()
|
|
721
752
|
}
|
|
722
753
|
});
|
|
723
|
-
await this.cleanup();
|
|
724
754
|
return null;
|
|
725
755
|
}
|
|
726
756
|
}
|
|
@@ -735,6 +765,12 @@ class Vapi extends VapiEventEmitter {
|
|
|
735
765
|
else {
|
|
736
766
|
try {
|
|
737
767
|
const parsedMessage = JSON.parse(e.data);
|
|
768
|
+
// Not a Vapi client message: re-emitted as recording-error, and kept
|
|
769
|
+
// out of 'message' because its consumers switch on `type`.
|
|
770
|
+
if (parsedMessage?.type === VIDEO_RECORDING_START_FAILED_MESSAGE_TYPE) {
|
|
771
|
+
this.videoRecordingStartFailedReport(parsedMessage);
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
738
774
|
this.emit('message', parsedMessage);
|
|
739
775
|
if (parsedMessage && 'type' in parsedMessage && 'status' in parsedMessage && parsedMessage.type === 'status-update' && parsedMessage.status === 'ended') {
|
|
740
776
|
this.hasEmittedCallEndedStatus = true;
|
|
@@ -778,6 +814,12 @@ class Vapi extends VapiEventEmitter {
|
|
|
778
814
|
* If `roomDeleteOnUserLeaveEnabled` is set to `true`, the Vapi call will also be destroyed, preventing any reconnections.
|
|
779
815
|
*/
|
|
780
816
|
async stop() {
|
|
817
|
+
if (this.cancelMicrophonePreflight) {
|
|
818
|
+
this.cancelMicrophonePreflight();
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
this.microphoneTrack?.stop();
|
|
822
|
+
this.microphoneTrack = null;
|
|
781
823
|
this.started = false;
|
|
782
824
|
if (this.call) {
|
|
783
825
|
await this.call.destroy();
|
|
@@ -896,6 +938,37 @@ class Vapi extends VapiEventEmitter {
|
|
|
896
938
|
console.error(`[vapi] ${event} event listener threw`, error);
|
|
897
939
|
}
|
|
898
940
|
}
|
|
941
|
+
/**
|
|
942
|
+
* Reported in a later task, like Daily's own recording-error: start() or
|
|
943
|
+
* reconnect() has resolved and the caller's await continuation has run, so a
|
|
944
|
+
* listener registered right after it still hears this, and one that calls
|
|
945
|
+
* stop() can't interrupt call setup. Skipped if that call was stopped or
|
|
946
|
+
* replaced.
|
|
947
|
+
*/
|
|
948
|
+
recordingTokenMissingReportSchedule() {
|
|
949
|
+
const call = this.call;
|
|
950
|
+
if (!call)
|
|
951
|
+
return;
|
|
952
|
+
setTimeout(() => {
|
|
953
|
+
if (!this.started || this.call !== call)
|
|
954
|
+
return;
|
|
955
|
+
this.emitToConsumer('recording-error', {
|
|
956
|
+
action: 'recording-error',
|
|
957
|
+
callClientId: call.callClientId,
|
|
958
|
+
errorMsg: 'Vapi returned no recording token, so the recording was not started. Call startRecording() to try starting it from the client.',
|
|
959
|
+
});
|
|
960
|
+
}, 0);
|
|
961
|
+
}
|
|
962
|
+
/** The fields are read defensively: the app message is untrusted JSON. */
|
|
963
|
+
videoRecordingStartFailedReport(message) {
|
|
964
|
+
const outcome = typeof message.outcome === 'string' ? message.outcome : 'unknown';
|
|
965
|
+
const error = typeof message.error === 'string' ? message.error : 'unknown';
|
|
966
|
+
this.emitToConsumer('recording-error', {
|
|
967
|
+
action: 'recording-error',
|
|
968
|
+
callClientId: this.call?.callClientId ?? '',
|
|
969
|
+
errorMsg: `Vapi couldn't start the recording (${outcome}): ${error}`,
|
|
970
|
+
});
|
|
971
|
+
}
|
|
899
972
|
/** Drops the reference before removing, so nothing hands out a detached element. */
|
|
900
973
|
detachAudioPlayer(participantId) {
|
|
901
974
|
if (this.audioPlayer?.dataset.participantId === participantId) {
|
|
@@ -1128,11 +1201,14 @@ class Vapi extends VapiEventEmitter {
|
|
|
1128
1201
|
});
|
|
1129
1202
|
}
|
|
1130
1203
|
const isVideoRecordingEnabled = webCall?.artifactPlan?.videoRecordingEnabled ?? false;
|
|
1131
|
-
|
|
1132
|
-
// Same auto-start token as in start(). Rejoining with it while the
|
|
1204
|
+
// Same token as in start(). In client mode, rejoining with it while the
|
|
1133
1205
|
// recording is still running is safe; if the recording stopped when the
|
|
1134
|
-
// user left, the token starts a new one.
|
|
1206
|
+
// user left, the token starts a new one. In server mode it starts
|
|
1207
|
+
// nothing. Without a token the SDK emits recording-error and starts
|
|
1208
|
+
// nothing.
|
|
1135
1209
|
const callToken = webCall?.transport?.callToken;
|
|
1210
|
+
const isRecordingStartedByServer = webCall?.transport?.videoRecordingStartMode === 'server';
|
|
1211
|
+
const recordingTokenMissing = isVideoRecordingEnabled && !callToken;
|
|
1136
1212
|
// Stage 1: Create Daily call object
|
|
1137
1213
|
this.emit('call-start-progress', {
|
|
1138
1214
|
stage: 'daily-call-object-creation',
|
|
@@ -1142,7 +1218,6 @@ class Vapi extends VapiEventEmitter {
|
|
|
1142
1218
|
audioSource: this.dailyCallObject.audioSource ?? true,
|
|
1143
1219
|
videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
|
|
1144
1220
|
isVideoRecordingEnabled,
|
|
1145
|
-
isVideoEnabled
|
|
1146
1221
|
}
|
|
1147
1222
|
});
|
|
1148
1223
|
const dailyCallStartTime = Date.now();
|
|
@@ -1224,7 +1299,7 @@ class Vapi extends VapiEventEmitter {
|
|
|
1224
1299
|
this.call.on('participant-joined', (e) => {
|
|
1225
1300
|
if (!e || !this.call)
|
|
1226
1301
|
return;
|
|
1227
|
-
subscribeToTracks(e, this.call, isVideoRecordingEnabled
|
|
1302
|
+
subscribeToTracks(e, this.call, isVideoRecordingEnabled);
|
|
1228
1303
|
});
|
|
1229
1304
|
this.call.on('participant-updated', (e) => {
|
|
1230
1305
|
if (!e) {
|
|
@@ -1341,45 +1416,29 @@ class Vapi extends VapiEventEmitter {
|
|
|
1341
1416
|
});
|
|
1342
1417
|
// Stage 4: Video recording setup (if enabled)
|
|
1343
1418
|
if (isVideoRecordingEnabled && callToken) {
|
|
1344
|
-
// The meeting token auto-started the recording at join,
|
|
1345
|
-
// nothing to request from the
|
|
1419
|
+
// The meeting token auto-started the recording at join, or Vapi
|
|
1420
|
+
// starts it from its servers, so there is nothing to request from the
|
|
1421
|
+
// client.
|
|
1346
1422
|
this.emit('call-start-progress', {
|
|
1347
1423
|
stage: 'video-recording-setup',
|
|
1348
1424
|
status: 'completed',
|
|
1349
1425
|
timestamp: new Date().toISOString(),
|
|
1350
|
-
metadata: {
|
|
1426
|
+
metadata: {
|
|
1427
|
+
action: isRecordingStartedByServer
|
|
1428
|
+
? 'started-by-server'
|
|
1429
|
+
: 'auto-started-via-meeting-token'
|
|
1430
|
+
}
|
|
1351
1431
|
});
|
|
1352
1432
|
}
|
|
1353
1433
|
else if (isVideoRecordingEnabled) {
|
|
1434
|
+
// No token: nothing starts the recording. The recording-error is
|
|
1435
|
+
// reported once reconnect() has finished.
|
|
1354
1436
|
this.emit('call-start-progress', {
|
|
1355
1437
|
stage: 'video-recording-setup',
|
|
1356
|
-
status: '
|
|
1357
|
-
timestamp: new Date().toISOString()
|
|
1438
|
+
status: 'failed',
|
|
1439
|
+
timestamp: new Date().toISOString(),
|
|
1440
|
+
metadata: { action: 'no-meeting-token' }
|
|
1358
1441
|
});
|
|
1359
|
-
const recordingStartTime = Date.now();
|
|
1360
|
-
try {
|
|
1361
|
-
recordingRequestedTime = new Date().getTime();
|
|
1362
|
-
this.startRecording();
|
|
1363
|
-
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
1364
|
-
this.emit('call-start-progress', {
|
|
1365
|
-
stage: 'video-recording-setup',
|
|
1366
|
-
status: 'completed',
|
|
1367
|
-
duration: recordingSetupDuration,
|
|
1368
|
-
timestamp: new Date().toISOString()
|
|
1369
|
-
});
|
|
1370
|
-
}
|
|
1371
|
-
catch (error) {
|
|
1372
|
-
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
1373
|
-
const serializedError = serializeError(error);
|
|
1374
|
-
this.emit('call-start-progress', {
|
|
1375
|
-
stage: 'video-recording-setup',
|
|
1376
|
-
status: 'failed',
|
|
1377
|
-
duration: recordingSetupDuration,
|
|
1378
|
-
timestamp: new Date().toISOString(),
|
|
1379
|
-
metadata: { error: serializedError.message }
|
|
1380
|
-
});
|
|
1381
|
-
// Don't throw here, video recording is optional
|
|
1382
|
-
}
|
|
1383
1442
|
}
|
|
1384
1443
|
else {
|
|
1385
1444
|
this.emit('call-start-progress', {
|
|
@@ -1473,6 +1532,9 @@ class Vapi extends VapiEventEmitter {
|
|
|
1473
1532
|
timestamp: new Date().toISOString()
|
|
1474
1533
|
});
|
|
1475
1534
|
this.emit('call-start');
|
|
1535
|
+
if (recordingTokenMissing) {
|
|
1536
|
+
this.recordingTokenMissingReportSchedule();
|
|
1537
|
+
}
|
|
1476
1538
|
}
|
|
1477
1539
|
catch (e) {
|
|
1478
1540
|
const totalDuration = Date.now() - startTime;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vapi-ai/web",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/vapi.js",
|
|
6
6
|
"types": "dist/vapi.d.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"node": ">=22.14.0"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@daily-co/daily-js": "^0.
|
|
34
|
+
"@daily-co/daily-js": "^0.94.0",
|
|
35
35
|
"events": "^3.3.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|