@vapi-ai/web 2.1.9 → 2.2.1

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.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import type { ChatCompletionMessageParam } from 'openai/resources';
3
- import { DailyCall, DailyAdvancedConfig, DailyFactoryOptions } from '@daily-co/daily-js';
3
+ import { DailyCall, DailyAdvancedConfig, DailyFactoryOptions, DailyParticipant, DailyVideoSendSettings } from '@daily-co/daily-js';
4
4
  import EventEmitter from 'events';
5
5
  import { Call, CreateSquadDTO, CreateAssistantDTO, AssistantOverrides } from './api';
6
6
  export interface AddMessageMessage {
@@ -18,7 +18,7 @@ export interface SayMessage {
18
18
  endCallAfterSpoken?: boolean;
19
19
  }
20
20
  type VapiClientToServerMessage = AddMessageMessage | ControlMessages | SayMessage;
21
- type VapiEventNames = 'call-end' | 'call-start' | 'volume-level' | 'speech-start' | 'speech-end' | 'message' | 'video' | 'error';
21
+ type VapiEventNames = 'call-end' | 'call-start' | 'volume-level' | 'speech-start' | 'speech-end' | 'message' | 'video' | 'error' | 'daily-participant-updated';
22
22
  type VapiEventListeners = {
23
23
  'call-end': () => void;
24
24
  'call-start': () => void;
@@ -28,6 +28,7 @@ type VapiEventListeners = {
28
28
  video: (track: MediaStreamTrack) => void;
29
29
  message: (message: any) => void;
30
30
  error: (error: any) => void;
31
+ 'daily-participant-updated': (participant: DailyParticipant) => void;
31
32
  };
32
33
  declare class VapiEventEmitter extends EventEmitter {
33
34
  on<E extends VapiEventNames>(event: E, listener: VapiEventListeners[E]): this;
@@ -42,6 +43,7 @@ export default class Vapi extends VapiEventEmitter {
42
43
  private speakingTimeout;
43
44
  private dailyCallConfig;
44
45
  private dailyCallObject;
46
+ private hasEmittedCallEndedStatus;
45
47
  constructor(apiToken: string, apiBaseUrl?: string, dailyCallConfig?: Pick<DailyAdvancedConfig, 'avoidEval' | 'alwaysIncludeMicInPermissionPrompt'>, dailyCallObject?: Pick<DailyFactoryOptions, 'audioSource'>);
46
48
  private cleanup;
47
49
  private isMobileDevice;
@@ -57,5 +59,7 @@ export default class Vapi extends VapiEventEmitter {
57
59
  setInputDevicesAsync(options: Parameters<DailyCall['setInputDevicesAsync']>[0]): void;
58
60
  setOutputDeviceAsync(options: Parameters<DailyCall['setOutputDeviceAsync']>[0]): void;
59
61
  getDailyCallObject(): DailyCall | null;
62
+ startScreenSharing(displayMediaOptions?: DisplayMediaStreamOptions, screenVideoSendSettings?: DailyVideoSendSettings): void;
63
+ stopScreenSharing(): void;
60
64
  }
61
65
  export {};
package/dist/vapi.js CHANGED
@@ -62,6 +62,7 @@ class Vapi extends VapiEventEmitter {
62
62
  speakingTimeout = null;
63
63
  dailyCallConfig = {};
64
64
  dailyCallObject = {};
65
+ hasEmittedCallEndedStatus = false;
65
66
  constructor(apiToken, apiBaseUrl, dailyCallConfig, dailyCallObject) {
66
67
  super();
67
68
  client_1.client.baseUrl = apiBaseUrl ?? 'https://api.vapi.ai';
@@ -114,16 +115,19 @@ class Vapi extends VapiEventEmitter {
114
115
  this.call.iframe()?.style.setProperty('display', 'none');
115
116
  this.call.on('left-meeting', () => {
116
117
  this.emit('call-end');
118
+ if (!this.hasEmittedCallEndedStatus) {
119
+ this.emit('message', {
120
+ type: 'status-update',
121
+ status: 'ended',
122
+ 'endedReason': 'customer-ended-call',
123
+ });
124
+ this.hasEmittedCallEndedStatus = true;
125
+ }
117
126
  if (isVideoRecordingEnabled) {
118
127
  this.call?.stopRecording();
119
128
  }
120
129
  this.cleanup();
121
130
  });
122
- this.call.on('participant-left', (e) => {
123
- if (!e)
124
- return;
125
- destroyAudioPlayer(e.participant.session_id);
126
- });
127
131
  this.call.on('error', (error) => {
128
132
  this.emit('error', error);
129
133
  if (isVideoRecordingEnabled) {
@@ -134,12 +138,15 @@ class Vapi extends VapiEventEmitter {
134
138
  this.emit('error', error);
135
139
  });
136
140
  this.call.on('track-started', async (e) => {
137
- if (!e || !e.participant)
141
+ if (!e || !e.participant) {
138
142
  return;
139
- if (e.participant?.local)
143
+ }
144
+ if (e.participant?.local) {
140
145
  return;
141
- if (e.participant?.user_name !== 'Vapi Speaker')
146
+ }
147
+ if (e.participant?.user_name !== 'Vapi Speaker') {
142
148
  return;
149
+ }
143
150
  if (e.track.kind === 'video') {
144
151
  this.emit('video', e.track);
145
152
  }
@@ -153,6 +160,18 @@ class Vapi extends VapiEventEmitter {
153
160
  return;
154
161
  subscribeToTracks(e, this.call, isVideoRecordingEnabled, isVideoEnabled);
155
162
  });
163
+ this.call.on('participant-updated', (e) => {
164
+ if (!e) {
165
+ return;
166
+ }
167
+ this.emit('daily-participant-updated', e.participant);
168
+ });
169
+ this.call.on('participant-left', (e) => {
170
+ if (!e) {
171
+ return;
172
+ }
173
+ destroyAudioPlayer(e.participant.session_id);
174
+ });
156
175
  // Allow mobile devices to finish processing the microphone permissions
157
176
  // request before joining the call and playing the assistant's audio
158
177
  if (this.isMobileDevice()) {
@@ -220,8 +239,9 @@ class Vapi extends VapiEventEmitter {
220
239
  }
221
240
  }
222
241
  onAppMessage(e) {
223
- if (!e)
242
+ if (!e) {
224
243
  return;
244
+ }
225
245
  try {
226
246
  if (e.data === 'listening') {
227
247
  return this.emit('call-start');
@@ -230,6 +250,9 @@ class Vapi extends VapiEventEmitter {
230
250
  try {
231
251
  const parsedMessage = JSON.parse(e.data);
232
252
  this.emit('message', parsedMessage);
253
+ if (parsedMessage.type === 'status-update' && parsedMessage.status === 'ended') {
254
+ this.hasEmittedCallEndedStatus = true;
255
+ }
233
256
  }
234
257
  catch (parseError) {
235
258
  console.log('Error parsing message data: ', parseError);
@@ -244,8 +267,9 @@ class Vapi extends VapiEventEmitter {
244
267
  const speechLevel = Object.values(e.participantsAudioLevel).reduce((a, b) => a + b, 0);
245
268
  this.emit('volume-level', Math.min(1, speechLevel / 0.15));
246
269
  const isSpeaking = speechLevel > 0.01;
247
- if (!isSpeaking)
270
+ if (!isSpeaking) {
248
271
  return;
272
+ }
249
273
  if (this.speakingTimeout) {
250
274
  clearTimeout(this.speakingTimeout);
251
275
  this.speakingTimeout = null;
@@ -294,5 +318,14 @@ class Vapi extends VapiEventEmitter {
294
318
  getDailyCallObject() {
295
319
  return this.call;
296
320
  }
321
+ startScreenSharing(displayMediaOptions, screenVideoSendSettings) {
322
+ this.call?.startScreenShare({
323
+ displayMediaOptions,
324
+ screenVideoSendSettings,
325
+ });
326
+ }
327
+ stopScreenSharing() {
328
+ this.call?.stopScreenShare();
329
+ }
297
330
  }
298
331
  exports.default = Vapi;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vapi-ai/web",
3
- "version": "2.1.9",
3
+ "version": "2.2.1",
4
4
  "description": "",
5
5
  "main": "dist/vapi.js",
6
6
  "types": "dist/vapi.d.ts",
package/vapi.ts CHANGED
@@ -7,6 +7,8 @@ import DailyIframe, {
7
7
  DailyEventObjectAppMessage,
8
8
  DailyEventObjectParticipant,
9
9
  DailyEventObjectRemoteParticipantsAudioLevel,
10
+ DailyParticipant,
11
+ DailyVideoSendSettings,
10
12
  } from '@daily-co/daily-js';
11
13
  import EventEmitter from 'events';
12
14
 
@@ -48,7 +50,8 @@ type VapiEventNames =
48
50
  | 'speech-end'
49
51
  | 'message'
50
52
  | 'video'
51
- | 'error';
53
+ | 'error'
54
+ | 'daily-participant-updated';
52
55
 
53
56
  type VapiEventListeners = {
54
57
  'call-end': () => void;
@@ -59,6 +62,7 @@ type VapiEventListeners = {
59
62
  video: (track: MediaStreamTrack) => void;
60
63
  message: (message: any) => void;
61
64
  error: (error: any) => void;
65
+ 'daily-participant-updated': (participant: DailyParticipant) => void;
62
66
  };
63
67
 
64
68
  async function startAudioPlayer(
@@ -148,6 +152,8 @@ export default class Vapi extends VapiEventEmitter {
148
152
  private dailyCallConfig: DailyAdvancedConfig = {};
149
153
  private dailyCallObject: DailyFactoryOptions = {};
150
154
 
155
+ private hasEmittedCallEndedStatus: boolean = false;
156
+
151
157
  constructor(
152
158
  apiToken: string,
153
159
  apiBaseUrl?: string,
@@ -228,17 +234,20 @@ export default class Vapi extends VapiEventEmitter {
228
234
 
229
235
  this.call.on('left-meeting', () => {
230
236
  this.emit('call-end');
237
+ if (!this.hasEmittedCallEndedStatus) {
238
+ this.emit('message', {
239
+ type: 'status-update',
240
+ status: 'ended',
241
+ 'endedReason': 'customer-ended-call',
242
+ });
243
+ this.hasEmittedCallEndedStatus = true;
244
+ }
231
245
  if (isVideoRecordingEnabled) {
232
246
  this.call?.stopRecording();
233
247
  }
234
248
  this.cleanup();
235
249
  });
236
250
 
237
- this.call.on('participant-left', (e) => {
238
- if (!e) return;
239
- destroyAudioPlayer(e.participant.session_id);
240
- });
241
-
242
251
  this.call.on('error', (error: any) => {
243
252
  this.emit('error', error);
244
253
  if (isVideoRecordingEnabled) {
@@ -251,18 +260,21 @@ export default class Vapi extends VapiEventEmitter {
251
260
  });
252
261
 
253
262
  this.call.on('track-started', async (e) => {
254
- if (!e || !e.participant) return;
255
- if (e.participant?.local) return;
256
- if (e.participant?.user_name !== 'Vapi Speaker') return;
257
-
263
+ if (!e || !e.participant) {
264
+ return;
265
+ }
266
+ if (e.participant?.local) {
267
+ return;
268
+ }
269
+ if (e.participant?.user_name !== 'Vapi Speaker') {
270
+ return;
271
+ }
258
272
  if (e.track.kind === 'video') {
259
273
  this.emit('video', e.track);
260
274
  }
261
-
262
275
  if (e.track.kind === 'audio') {
263
276
  await buildAudioPlayer(e.track, e.participant.session_id);
264
277
  }
265
-
266
278
  this.call?.sendAppMessage('playable');
267
279
  });
268
280
 
@@ -276,6 +288,20 @@ export default class Vapi extends VapiEventEmitter {
276
288
  );
277
289
  });
278
290
 
291
+ this.call.on('participant-updated', (e) => {
292
+ if (!e) {
293
+ return;
294
+ }
295
+ this.emit('daily-participant-updated', e.participant);
296
+ });
297
+
298
+ this.call.on('participant-left', (e) => {
299
+ if (!e) {
300
+ return;
301
+ }
302
+ destroyAudioPlayer(e.participant.session_id);
303
+ });
304
+
279
305
  // Allow mobile devices to finish processing the microphone permissions
280
306
  // request before joining the call and playing the assistant's audio
281
307
  if (this.isMobileDevice()) {
@@ -353,7 +379,9 @@ export default class Vapi extends VapiEventEmitter {
353
379
  }
354
380
 
355
381
  private onAppMessage(e?: DailyEventObjectAppMessage) {
356
- if (!e) return;
382
+ if (!e) {
383
+ return;
384
+ }
357
385
  try {
358
386
  if (e.data === 'listening') {
359
387
  return this.emit('call-start');
@@ -361,6 +389,9 @@ export default class Vapi extends VapiEventEmitter {
361
389
  try {
362
390
  const parsedMessage = JSON.parse(e.data);
363
391
  this.emit('message', parsedMessage);
392
+ if (parsedMessage.type === 'status-update' && parsedMessage.status === 'ended') {
393
+ this.hasEmittedCallEndedStatus = true;
394
+ }
364
395
  } catch (parseError) {
365
396
  console.log('Error parsing message data: ', parseError);
366
397
  }
@@ -382,7 +413,9 @@ export default class Vapi extends VapiEventEmitter {
382
413
 
383
414
  const isSpeaking = speechLevel > 0.01;
384
415
 
385
- if (!isSpeaking) return;
416
+ if (!isSpeaking) {
417
+ return;
418
+ }
386
419
 
387
420
  if (this.speakingTimeout) {
388
421
  clearTimeout(this.speakingTimeout);
@@ -444,4 +477,15 @@ export default class Vapi extends VapiEventEmitter {
444
477
  public getDailyCallObject(): DailyCall | null {
445
478
  return this.call;
446
479
  }
480
+
481
+ public startScreenSharing(displayMediaOptions?: DisplayMediaStreamOptions, screenVideoSendSettings?: DailyVideoSendSettings) {
482
+ this.call?.startScreenShare({
483
+ displayMediaOptions,
484
+ screenVideoSendSettings,
485
+ });
486
+ }
487
+
488
+ public stopScreenSharing() {
489
+ this.call?.stopScreenShare();
490
+ }
447
491
  }