@vapi-ai/web 2.5.0 → 2.5.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/dist/vapi.js CHANGED
@@ -7,6 +7,57 @@ const daily_js_1 = __importDefault(require("@daily-co/daily-js"));
7
7
  const events_1 = __importDefault(require("events"));
8
8
  const client_1 = require("./client");
9
9
  const daily_guards_1 = require("./daily-guards");
10
+ /**
11
+ * Extracts error details into a plain object that serializes properly to JSON.
12
+ * Error objects don't serialize well because their properties (message, stack, name)
13
+ * are not enumerable and get lost when using JSON.stringify().
14
+ */
15
+ function serializeError(error) {
16
+ if (error === null || error === undefined) {
17
+ return { message: 'Unknown error (null or undefined)' };
18
+ }
19
+ if (error instanceof Error) {
20
+ const serialized = {
21
+ message: error.message,
22
+ name: error.name,
23
+ stack: error.stack,
24
+ };
25
+ // Include any additional properties that might be on the error
26
+ const errorAsAny = error;
27
+ if (errorAsAny.code !== undefined) {
28
+ serialized.code = errorAsAny.code;
29
+ }
30
+ if (errorAsAny.cause !== undefined) {
31
+ serialized.cause = String(errorAsAny.cause);
32
+ }
33
+ if (errorAsAny.reason !== undefined) {
34
+ serialized.reason = errorAsAny.reason;
35
+ }
36
+ if (errorAsAny.details !== undefined) {
37
+ serialized.details = errorAsAny.details;
38
+ }
39
+ // Daily.co specific error properties
40
+ if (errorAsAny.errorMsg !== undefined) {
41
+ serialized.errorMsg = errorAsAny.errorMsg;
42
+ }
43
+ if (errorAsAny.error !== undefined && typeof errorAsAny.error === 'string') {
44
+ serialized.errorDetail = errorAsAny.error;
45
+ }
46
+ return serialized;
47
+ }
48
+ if (typeof error === 'string') {
49
+ return { message: error };
50
+ }
51
+ if (typeof error === 'object') {
52
+ // It's already a plain object, but let's ensure message exists
53
+ const errorObj = error;
54
+ return {
55
+ message: errorObj.message || errorObj.error || JSON.stringify(error),
56
+ ...errorObj
57
+ };
58
+ }
59
+ return { message: String(error) };
60
+ }
10
61
  async function startAudioPlayer(player, track) {
11
62
  player.muted = false;
12
63
  player.autoplay = true;
@@ -98,7 +149,7 @@ class Vapi extends VapiEventEmitter {
98
149
  this.emit('error', {
99
150
  type: 'validation-error',
100
151
  stage: 'input-validation',
101
- message: error.message,
152
+ error: serializeError(error),
102
153
  timestamp: new Date().toISOString()
103
154
  });
104
155
  throw error;
@@ -194,17 +245,18 @@ class Vapi extends VapiEventEmitter {
194
245
  }
195
246
  catch (error) {
196
247
  const dailyCallDuration = Date.now() - dailyCallStartTime;
248
+ const serializedError = serializeError(error);
197
249
  this.emit('call-start-progress', {
198
250
  stage: 'daily-call-object-creation',
199
251
  status: 'failed',
200
252
  duration: dailyCallDuration,
201
253
  timestamp: new Date().toISOString(),
202
- metadata: { error: error?.toString() }
254
+ metadata: { error: serializedError.message }
203
255
  });
204
256
  this.emit('error', {
205
257
  type: 'daily-call-object-creation-error',
206
258
  stage: 'daily-call-object-creation',
207
- error,
259
+ error: serializedError,
208
260
  timestamp: new Date().toISOString()
209
261
  });
210
262
  throw error;
@@ -226,13 +278,21 @@ class Vapi extends VapiEventEmitter {
226
278
  this.cleanup().catch(console.error);
227
279
  });
228
280
  this.call.on('error', (error) => {
229
- this.emit('error', error);
281
+ this.emit('error', {
282
+ type: 'daily-error',
283
+ error: serializeError(error),
284
+ timestamp: new Date().toISOString()
285
+ });
230
286
  if (isVideoRecordingEnabled) {
231
287
  this.call?.stopRecording();
232
288
  }
233
289
  });
234
290
  this.call.on('camera-error', (error) => {
235
- this.emit('camera-error', error);
291
+ this.emit('camera-error', {
292
+ type: 'camera-error',
293
+ error: serializeError(error),
294
+ timestamp: new Date().toISOString()
295
+ });
236
296
  });
237
297
  this.call.on('network-quality-change', (event) => {
238
298
  this.emit('network-quality-change', event);
@@ -326,17 +386,18 @@ class Vapi extends VapiEventEmitter {
326
386
  }
327
387
  catch (error) {
328
388
  const joinDuration = Date.now() - joinStartTime;
389
+ const serializedError = serializeError(error);
329
390
  this.emit('call-start-progress', {
330
391
  stage: 'daily-call-join',
331
392
  status: 'failed',
332
393
  duration: joinDuration,
333
394
  timestamp: new Date().toISOString(),
334
- metadata: { error: error?.toString() }
395
+ metadata: { error: serializedError.message }
335
396
  });
336
397
  this.emit('error', {
337
398
  type: 'daily-call-join-error',
338
399
  stage: 'daily-call-join',
339
- error,
400
+ error: serializedError,
340
401
  duration: joinDuration,
341
402
  timestamp: new Date().toISOString()
342
403
  });
@@ -384,17 +445,18 @@ class Vapi extends VapiEventEmitter {
384
445
  }
385
446
  catch (error) {
386
447
  const recordingSetupDuration = Date.now() - recordingStartTime;
448
+ const serializedError = serializeError(error);
387
449
  this.emit('call-start-progress', {
388
450
  stage: 'video-recording-setup',
389
451
  status: 'failed',
390
452
  duration: recordingSetupDuration,
391
453
  timestamp: new Date().toISOString(),
392
- metadata: { error: error?.toString() }
454
+ metadata: { error: serializedError.message }
393
455
  });
394
456
  this.emit('error', {
395
457
  type: 'video-recording-setup-error',
396
458
  stage: 'video-recording-setup',
397
- error,
459
+ error: serializedError,
398
460
  timestamp: new Date().toISOString()
399
461
  });
400
462
  // Don't throw here, video recording is optional
@@ -427,17 +489,18 @@ class Vapi extends VapiEventEmitter {
427
489
  }
428
490
  catch (error) {
429
491
  const audioObserverDuration = Date.now() - audioObserverStartTime;
492
+ const serializedError = serializeError(error);
430
493
  this.emit('call-start-progress', {
431
494
  stage: 'audio-observer-setup',
432
495
  status: 'failed',
433
496
  duration: audioObserverDuration,
434
497
  timestamp: new Date().toISOString(),
435
- metadata: { error: error?.toString() }
498
+ metadata: { error: serializedError.message }
436
499
  });
437
500
  this.emit('error', {
438
501
  type: 'audio-observer-setup-error',
439
502
  stage: 'audio-observer-setup',
440
- error,
503
+ error: serializedError,
441
504
  timestamp: new Date().toISOString()
442
505
  });
443
506
  // Don't throw here, this is non-critical
@@ -488,17 +551,18 @@ class Vapi extends VapiEventEmitter {
488
551
  }
489
552
  catch (error) {
490
553
  const audioProcessingDuration = Date.now() - audioProcessingStartTime;
554
+ const serializedError = serializeError(error);
491
555
  this.emit('call-start-progress', {
492
556
  stage: 'audio-processing-setup',
493
557
  status: 'failed',
494
558
  duration: audioProcessingDuration,
495
559
  timestamp: new Date().toISOString(),
496
- metadata: { error: error?.toString() }
560
+ metadata: { error: serializedError.message }
497
561
  });
498
562
  this.emit('error', {
499
563
  type: 'audio-processing-setup-error',
500
564
  stage: 'audio-processing-setup',
501
- error,
565
+ error: serializedError,
502
566
  timestamp: new Date().toISOString()
503
567
  });
504
568
  // Don't throw here, this is non-critical
@@ -513,11 +577,12 @@ class Vapi extends VapiEventEmitter {
513
577
  }
514
578
  catch (e) {
515
579
  const totalDuration = Date.now() - startTime;
580
+ const serializedError = serializeError(e);
516
581
  this.emit('call-start-failed', {
517
582
  stage: 'unknown',
518
583
  totalDuration,
519
- error: e?.toString() || 'Unknown error occurred',
520
- errorStack: e instanceof Error ? e.stack : 'No stack trace available',
584
+ error: serializedError.message,
585
+ errorStack: serializedError.stack || 'No stack trace available',
521
586
  timestamp: new Date().toISOString(),
522
587
  context: {
523
588
  hasAssistant: !!assistant,
@@ -530,7 +595,7 @@ class Vapi extends VapiEventEmitter {
530
595
  this.emit('error', {
531
596
  type: 'start-method-error',
532
597
  stage: 'unknown',
533
- error: e,
598
+ error: serializedError,
534
599
  totalDuration,
535
600
  timestamp: new Date().toISOString(),
536
601
  context: {
@@ -762,13 +827,21 @@ class Vapi extends VapiEventEmitter {
762
827
  this.cleanup().catch(console.error);
763
828
  });
764
829
  this.call.on('error', (error) => {
765
- this.emit('error', error);
830
+ this.emit('error', {
831
+ type: 'daily-error',
832
+ error: serializeError(error),
833
+ timestamp: new Date().toISOString()
834
+ });
766
835
  if (isVideoRecordingEnabled) {
767
836
  this.call?.stopRecording();
768
837
  }
769
838
  });
770
839
  this.call.on('camera-error', (error) => {
771
- this.emit('camera-error', error);
840
+ this.emit('camera-error', {
841
+ type: 'camera-error',
842
+ error: serializeError(error),
843
+ timestamp: new Date().toISOString()
844
+ });
772
845
  });
773
846
  this.call.on('network-quality-change', (event) => {
774
847
  this.emit('network-quality-change', event);
@@ -920,12 +993,13 @@ class Vapi extends VapiEventEmitter {
920
993
  }
921
994
  catch (error) {
922
995
  const recordingSetupDuration = Date.now() - recordingStartTime;
996
+ const serializedError = serializeError(error);
923
997
  this.emit('call-start-progress', {
924
998
  stage: 'video-recording-setup',
925
999
  status: 'failed',
926
1000
  duration: recordingSetupDuration,
927
1001
  timestamp: new Date().toISOString(),
928
- metadata: { error: error?.toString() }
1002
+ metadata: { error: serializedError.message }
929
1003
  });
930
1004
  // Don't throw here, video recording is optional
931
1005
  }
@@ -957,12 +1031,13 @@ class Vapi extends VapiEventEmitter {
957
1031
  }
958
1032
  catch (error) {
959
1033
  const audioObserverDuration = Date.now() - audioObserverStartTime;
1034
+ const serializedError = serializeError(error);
960
1035
  this.emit('call-start-progress', {
961
1036
  stage: 'audio-observer-setup',
962
1037
  status: 'failed',
963
1038
  duration: audioObserverDuration,
964
1039
  timestamp: new Date().toISOString(),
965
- metadata: { error: error?.toString() }
1040
+ metadata: { error: serializedError.message }
966
1041
  });
967
1042
  // Don't throw here, this is non-critical
968
1043
  }
@@ -991,12 +1066,13 @@ class Vapi extends VapiEventEmitter {
991
1066
  }
992
1067
  catch (error) {
993
1068
  const audioProcessingDuration = Date.now() - audioProcessingStartTime;
1069
+ const serializedError = serializeError(error);
994
1070
  this.emit('call-start-progress', {
995
1071
  stage: 'audio-processing-setup',
996
1072
  status: 'failed',
997
1073
  duration: audioProcessingDuration,
998
1074
  timestamp: new Date().toISOString(),
999
- metadata: { error: error?.toString() }
1075
+ metadata: { error: serializedError.message }
1000
1076
  });
1001
1077
  // Don't throw here, this is non-critical
1002
1078
  }
@@ -1010,11 +1086,12 @@ class Vapi extends VapiEventEmitter {
1010
1086
  }
1011
1087
  catch (e) {
1012
1088
  const totalDuration = Date.now() - startTime;
1089
+ const serializedError = serializeError(e);
1013
1090
  this.emit('call-start-failed', {
1014
1091
  stage: 'reconnect',
1015
1092
  totalDuration,
1016
- error: e?.toString() || 'Unknown error occurred',
1017
- errorStack: e instanceof Error ? e.stack : 'No stack trace available',
1093
+ error: serializedError.message,
1094
+ errorStack: serializedError.stack || 'No stack trace available',
1018
1095
  timestamp: new Date().toISOString(),
1019
1096
  context: {
1020
1097
  isReconnect: true,
@@ -1027,7 +1104,7 @@ class Vapi extends VapiEventEmitter {
1027
1104
  // Also emit the generic error event for backward compatibility
1028
1105
  this.emit('error', {
1029
1106
  type: 'reconnect-error',
1030
- error: e,
1107
+ error: serializedError,
1031
1108
  totalDuration,
1032
1109
  timestamp: new Date().toISOString(),
1033
1110
  context: {
@@ -1042,5 +1119,125 @@ class Vapi extends VapiEventEmitter {
1042
1119
  throw e;
1043
1120
  }
1044
1121
  }
1122
+ /**
1123
+ * Runs all network connectivity tests for pre-call diagnostics.
1124
+ * Creates a temporary Daily call object for testing purposes.
1125
+ *
1126
+ * Tests performed:
1127
+ * 1. Network connectivity (TURN server) - Tests if traffic can be relayed through TURN servers
1128
+ * 2. Websocket connectivity - Tests if websocket connections can be established
1129
+ * 3. Call quality - Tests overall call quality metrics (if available in SDK version)
1130
+ *
1131
+ * @returns {Promise<Record<string, any>>} Test results object with status for each test
1132
+ *
1133
+ * @example
1134
+ * // Run pre-call network diagnostics
1135
+ * const results = await Vapi.runNetworkTestsStandalone();
1136
+ * if (results.networkConnectivity?.result === 'failed') {
1137
+ * console.warn('Network issues detected - calls may not work properly');
1138
+ * }
1139
+ *
1140
+ * @static
1141
+ */
1142
+ static async runNetworkTestsStandalone() {
1143
+ console.log('Starting standalone network connectivity tests...');
1144
+ const results = {};
1145
+ let tempCall = null;
1146
+ try {
1147
+ // Create a temporary call object for testing
1148
+ console.log('Creating temporary call object for testing...');
1149
+ tempCall = daily_js_1.default.createCallObject({
1150
+ audioSource: true,
1151
+ videoSource: true,
1152
+ });
1153
+ // Test 1: Network Connectivity (TURN server test)
1154
+ console.log('\n1. Testing network connectivity (TURN server)...');
1155
+ let videoTrack = null;
1156
+ try {
1157
+ // Create a dummy video track for the test
1158
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
1159
+ videoTrack = stream.getVideoTracks()[0];
1160
+ const networkTest = await tempCall.testNetworkConnectivity(videoTrack);
1161
+ results.networkConnectivity = networkTest;
1162
+ console.log('Network connectivity test result:', networkTest);
1163
+ }
1164
+ catch (error) {
1165
+ results.networkConnectivity = { result: 'error', error: error?.toString() };
1166
+ console.error('Network connectivity test error:', error);
1167
+ }
1168
+ finally {
1169
+ // Clean up the video track
1170
+ if (videoTrack) {
1171
+ videoTrack.stop();
1172
+ }
1173
+ }
1174
+ // Test 2: Websocket Connectivity
1175
+ console.log('\n2. Testing websocket connectivity...');
1176
+ try {
1177
+ const websocketTest = await tempCall.testWebsocketConnectivity();
1178
+ results.websocketConnectivity = websocketTest;
1179
+ console.log('Websocket connectivity test result:', websocketTest);
1180
+ }
1181
+ catch (error) {
1182
+ results.websocketConnectivity = { result: 'error', error: error?.toString() };
1183
+ console.error('Websocket connectivity test error:', error);
1184
+ }
1185
+ // Test 3: Call Quality (if available)
1186
+ console.log('\n3. Testing call quality...');
1187
+ try {
1188
+ // Check if the method exists
1189
+ if (typeof tempCall.testCallQuality === 'function') {
1190
+ // Call quality test requires startCamera to initialize call state
1191
+ try {
1192
+ // Use startCamera to initialize the call state without needing a room
1193
+ console.log('Initializing call state with startCamera...');
1194
+ await tempCall.startCamera();
1195
+ // Now run the call quality test
1196
+ const callQualityTest = await tempCall.testCallQuality();
1197
+ results.callQuality = callQualityTest;
1198
+ console.log('Call quality test result:', callQualityTest);
1199
+ // Camera will be cleaned up when we destroy the call object
1200
+ }
1201
+ catch (startCameraError) {
1202
+ // If startCamera fails, it might be due to permissions or other issues
1203
+ console.error('Failed to start camera for call quality test:', startCameraError);
1204
+ results.callQuality = {
1205
+ result: 'error',
1206
+ error: startCameraError?.toString(),
1207
+ message: 'Failed to initialize camera for call quality test. Check camera permissions.'
1208
+ };
1209
+ }
1210
+ }
1211
+ else {
1212
+ results.callQuality = { result: 'not-available', message: 'testCallQuality method not available' };
1213
+ console.log('Call quality test not available in current Daily.co version');
1214
+ }
1215
+ }
1216
+ catch (error) {
1217
+ results.callQuality = { result: 'error', error: error?.toString() };
1218
+ console.error('Call quality test error:', error);
1219
+ }
1220
+ }
1221
+ catch (error) {
1222
+ console.error('Failed to create temporary call object:', error);
1223
+ results.error = error?.toString();
1224
+ }
1225
+ finally {
1226
+ // Clean up the temporary call object
1227
+ if (tempCall) {
1228
+ try {
1229
+ console.log('Cleaning up temporary call object...');
1230
+ await tempCall.destroy();
1231
+ }
1232
+ catch (error) {
1233
+ console.error('Error destroying temporary call object:', error);
1234
+ }
1235
+ }
1236
+ }
1237
+ // Summary
1238
+ console.log('\n=== Network Test Summary ===');
1239
+ console.log('Results:', JSON.stringify(results, null, 2));
1240
+ return results;
1241
+ }
1045
1242
  }
1046
1243
  exports.default = Vapi;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vapi-ai/web",
3
- "version": "2.5.0",
3
+ "version": "2.5.2",
4
4
  "description": "",
5
5
  "main": "dist/vapi.js",
6
6
  "types": "dist/vapi.d.ts",
@@ -31,7 +31,7 @@
31
31
  "node": ">=18.0.0"
32
32
  },
33
33
  "dependencies": {
34
- "@daily-co/daily-js": "^0.84.0",
34
+ "@daily-co/daily-js": "^0.85.0",
35
35
  "events": "^3.3.0"
36
36
  },
37
37
  "devDependencies": {