diff --git a/daily-guards.ts b/daily-guards.ts new file mode 100644 index 000000000..b47a32d05 --- /dev/null +++ b/daily-guards.ts @@ -0,0 +1,81 @@ +import type { + DailyCall, + DailyAdvancedConfig, + DailyFactoryOptions, +} from '@daily-co/daily-js'; + +export interface SafeDailyAdvancedConfig extends Omit { + alwaysIncludeMicInPermissionPrompt?: true; // Only allow true +} + +export interface SafeDailyFactoryOptions extends Omit { + audioSource?: string | boolean | MediaStreamTrack; +} + +export function createSafeDailyConfig( + config?: Pick +): SafeDailyAdvancedConfig { + if (!config) return {}; + + const { alwaysIncludeMicInPermissionPrompt, ...rest } = config; + + // Force true or remove the property entirely. This can cause Chrome 140+ issues + if (alwaysIncludeMicInPermissionPrompt === false) { + console.warn( + '[Vapi] alwaysIncludeMicInPermissionPrompt:false detected. ' + + 'This can cause Chrome 140+ issues. Removing the property.' + ); + return rest; + } + + return config as SafeDailyAdvancedConfig; +} + +export function safeSetLocalAudio(call: DailyCall | null, enabled: boolean): void { + if (!call) { + throw new Error('Call object is not available.'); + } + + // Never use forceDiscardTrack. This can cause Chrome 140+ issues + call.setLocalAudio(enabled); +} + +export async function safeSetInputDevicesAsync( + call: DailyCall | null, + options: Parameters[0] +): Promise { + if (!call) { + throw new Error('Call object is not available.'); + } + + // Validate audioSource + if ('audioSource' in options && options.audioSource === false) { + console.warn( + '[Vapi] setInputDevicesAsync with audioSource:false detected. ' + + 'This can cause Chrome 140+ issues. Using default device instead.' + ); + + const { audioSource, ...safeOptions } = options; + await call.setInputDevicesAsync(safeOptions); + return; + } + + await call.setInputDevicesAsync(options); +} + +export function createSafeDailyFactoryOptions( + options?: Pick +): SafeDailyFactoryOptions { + if (!options) return {}; + + // Ensure audioSource is never false + if (options.audioSource === false) { + console.warn( + '[Vapi] audioSource:false detected in factory options. ' + + 'This can cause Chrome 140+ issues. Defaulting to true.' + ); + return { ...options, audioSource: true }; + } + + return options; +} diff --git a/package-lock.json b/package-lock.json index 937677a45..643b564c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.3.9", "license": "MIT", "dependencies": { - "@daily-co/daily-js": "^0.80.0", + "@daily-co/daily-js": "^0.83.1", "events": "^3.3.0" }, "devDependencies": { @@ -589,9 +589,9 @@ } }, "node_modules/@daily-co/daily-js": { - "version": "0.80.0", - "resolved": "https://registry.npmjs.org/@daily-co/daily-js/-/daily-js-0.80.0.tgz", - "integrity": "sha512-zG2NBbKbHfm56P0lg4ddC94vBtn5AQKcgvbYrO5+ohNWPSolMqlJiYxQC9uhOHfFYRhH4ELKQ6NHqGatX9VD7A==", + "version": "0.83.1", + "resolved": "https://registry.npmjs.org/@daily-co/daily-js/-/daily-js-0.83.1.tgz", + "integrity": "sha512-KXA3zrSnNPZONwhip4TI6ayD3huumI1QBD/xPAjFArvHuFFB7t0QTOS2oxH1bXvdOBJ6XnY08vdlJslTVi2/2w==", "license": "BSD-2-Clause", "dependencies": { "@babel/runtime": "^7.12.5", diff --git a/package.json b/package.json index 1c23e9628..f9c88ab08 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "node": ">=18.0.0" }, "dependencies": { - "@daily-co/daily-js": "^0.80.0", + "@daily-co/daily-js": "^0.83.1", "events": "^3.3.0" }, "devDependencies": { diff --git a/vapi.ts b/vapi.ts index 1f69502cd..3809101fd 100644 --- a/vapi.ts +++ b/vapi.ts @@ -21,6 +21,12 @@ import { WorkflowOverrides, } from './api'; import { client } from './client'; +import { + createSafeDailyConfig, + createSafeDailyFactoryOptions, + safeSetLocalAudio, + safeSetInputDevicesAsync, +} from './daily-guards'; export interface AddMessageMessage { type: 'add-message'; @@ -206,8 +212,8 @@ export default class Vapi extends VapiEventEmitter { super(); client.baseUrl = apiBaseUrl ?? 'https://api.vapi.ai'; client.setSecurityData(apiToken); - this.dailyCallConfig = dailyCallConfig ?? {}; - this.dailyCallObject = dailyCallObject ?? {}; + this.dailyCallConfig = createSafeDailyConfig(dailyCallConfig); + this.dailyCallObject = createSafeDailyFactoryOptions(dailyCallObject); } private cleanup() { @@ -649,7 +655,7 @@ export default class Vapi extends VapiEventEmitter { }, }) .then(() => { - this.call?.setLocalAudio(true); + safeSetLocalAudio(this.call, true); }); } }); @@ -805,10 +811,7 @@ export default class Vapi extends VapiEventEmitter { } public setMuted(mute: boolean) { - if (!this.call) { - throw new Error('Call object is not available.'); - } - this.call.setLocalAudio(!mute); + safeSetLocalAudio(this.call, !mute); } public isMuted() { @@ -832,7 +835,7 @@ export default class Vapi extends VapiEventEmitter { public setInputDevicesAsync( options: Parameters[0], ) { - this.call?.setInputDevicesAsync(options); + return safeSetInputDevicesAsync(this.call, options); } public async increaseMicLevel(gain: number) { @@ -855,7 +858,7 @@ export default class Vapi extends VapiEventEmitter { gainNode.connect(destination); const [boostedTrack] = destination.stream.getAudioTracks(); - await this.call.setInputDevicesAsync({ audioSource: boostedTrack }); + await safeSetInputDevicesAsync(this.call, { audioSource: boostedTrack }); } catch (error) { console.error("Error adjusting microphone level:", error); }