diff --git a/client/src/api/tauri-adapter/__tests__/misc-mapping.test.ts b/client/src/api/tauri-adapter/__tests__/misc-mapping.test.ts index 6141985..1185479 100644 --- a/client/src/api/tauri-adapter/__tests__/misc-mapping.test.ts +++ b/client/src/api/tauri-adapter/__tests__/misc-mapping.test.ts @@ -124,7 +124,7 @@ describe('tauri-adapter mapping (misc)', () => { }); }); - it('covers additional adapter commands with payload assertions', async () => { + it('covers additional adapter commands', async () => { const { invoke, listen } = createInvokeListenMocks(); const annotation = { @@ -321,28 +321,5 @@ describe('tauri-adapter mapping (misc)', () => { await api.getDiarizationJobStatus('job'); await api.renameSpeaker('m1', 'old', 'new'); await api.cancelDiarization('job'); - - expect(invoke).toHaveBeenCalledWith('export_transcript', { - meeting_id: 'm1', - format: 1, - }); - expect(invoke).toHaveBeenCalledWith('set_trigger_enabled', { enabled: true }); - expect(invoke).toHaveBeenCalledWith('extract_entities', { - meeting_id: 'm1', - force_refresh: true, - }); - expect(invoke).toHaveBeenCalledWith('register_webhook', { - request: { - workspace_id: 'w1', - name: 'Webhook', - url: 'https://example.com', - events: ['meeting.completed'], - }, - }); - expect(invoke).toHaveBeenCalledWith('list_calendar_events', { - hours_ahead: 2, - limit: 5, - provider: 'google', - }); }); }); diff --git a/client/src/api/tauri-adapter/__tests__/transcription-mapping.test.ts b/client/src/api/tauri-adapter/__tests__/transcription-mapping.test.ts index d65187d..d2b49e8 100644 --- a/client/src/api/tauri-adapter/__tests__/transcription-mapping.test.ts +++ b/client/src/api/tauri-adapter/__tests__/transcription-mapping.test.ts @@ -30,7 +30,7 @@ describe('tauri-adapter mapping (transcription)', () => { expect(invoke).toHaveBeenCalledWith('start_recording', { meeting_id: 'm1' }); expect(invoke).toHaveBeenCalledWith('send_audio_chunk', { meeting_id: 'm1', - audio_data: chunk.audio_data, + audio_data: [0.25, -0.25], timestamp: 12.34, sample_rate: 48000, channels: 2, @@ -60,8 +60,7 @@ describe('tauri-adapter mapping (transcription)', () => { meeting_id: 'm2', timestamp: 1.23, }); - const audioData = args.audio_data as Float32Array | undefined; - expect(audioData).toBeInstanceOf(Float32Array); + const audioData = args.audio_data as number[] | undefined; expect(audioData).toHaveLength(1); expect(audioData?.[0]).toBeCloseTo(0.1, 5); }); @@ -158,42 +157,12 @@ describe('tauri-adapter mapping (transcription)', () => { assertTranscriptionStream(stream); await stream.onUpdate(() => {}); - await stream.close(); + stream.close(); expect(unlisten).toHaveBeenCalled(); expect(invoke).toHaveBeenCalledWith('stop_recording', { meeting_id: 'm1' }); }); - it('propagates errors when closing a transcription stream fails', async () => { - const { invoke, listen } = createInvokeListenMocks(); - const unlisten = vi.fn(); - listen.mockResolvedValueOnce(unlisten); - invoke.mockResolvedValueOnce(undefined); - - const stopError = new Error('STOP_RECORDING failed'); - invoke.mockRejectedValueOnce(stopError); - - const api: NoteFlowAPI = createTauriAPI(invoke as TauriInvoke, listen as TauriListen); - const stream: unknown = await api.startTranscription('m1'); - assertTranscriptionStream(stream); - - const onError = vi.fn(); - stream.onError(onError); - - await stream.onUpdate(() => {}); - - await expect(stream.close()).rejects.toBe(stopError); - - expect(onError).toHaveBeenCalledTimes(1); - expect(onError).toHaveBeenCalledWith( - expect.objectContaining({ - code: 'stream_close_failed', - message: expect.stringContaining('Failed to stop recording'), - }) - ); - expect(unlisten).toHaveBeenCalledTimes(1); - }); - it('cleans up pending transcript listener when closed before listen resolves', async () => { let capturedHandler: ((event: { payload: TranscriptUpdate }) => void) | null = null; let resolveListen: ((fn: () => void) => void) | null = null; diff --git a/client/src/api/tauri-adapter/environment.ts b/client/src/api/tauri-adapter/environment.ts index 41f97fb..f980346 100644 --- a/client/src/api/tauri-adapter/environment.ts +++ b/client/src/api/tauri-adapter/environment.ts @@ -1,7 +1,6 @@ import type { NoteFlowAPI } from '../interface'; import { extractErrorMessage } from '../helpers'; import { createTauriAPI } from './api'; -import { addClientLog } from '@/lib/client-logs'; /** Check if running in a Tauri environment (synchronous hint). */ export function isTauriEnvironment(): boolean { @@ -15,28 +14,16 @@ export function isTauriEnvironment(): boolean { /** Dynamically import Tauri APIs and create the adapter. */ export async function initializeTauriAPI(): Promise { - const [core, event] = await Promise.all([ - import('@tauri-apps/api/core'), - import('@tauri-apps/api/event'), - ]).catch((error) => { - addClientLog({ - level: 'debug', - source: 'api', - message: 'Tauri adapter initialization: import failed', - details: extractErrorMessage(error, 'unknown error'), - }); - throw new Error('Not running in Tauri environment.'); - }); - + // Try to import Tauri APIs - this will fail in browser but succeed in Tauri try { - return createTauriAPI(core.invoke, event.listen); + const { invoke } = await import('@tauri-apps/api/core'); + const { listen } = await import('@tauri-apps/api/event'); + // Test if invoke actually works by calling a simple command + await invoke('is_connected'); + return createTauriAPI(invoke, listen); } catch (error) { - addClientLog({ - level: 'error', - source: 'api', - message: 'Tauri adapter initialization failed', - details: extractErrorMessage(error, 'unknown error'), - }); - throw new Error('Failed to initialize Tauri API.'); + throw new Error( + `Not running in Tauri environment: ${extractErrorMessage(error, 'unknown error')}` + ); } } diff --git a/client/src/api/tauri-adapter/sections/preferences.ts b/client/src/api/tauri-adapter/sections/preferences.ts index ec64282..cd4e3ae 100644 --- a/client/src/api/tauri-adapter/sections/preferences.ts +++ b/client/src/api/tauri-adapter/sections/preferences.ts @@ -21,8 +21,8 @@ export function createPreferencesApi(invoke: TauriInvoke): Pick< source: 'api', message: 'TauriAdapter.getPreferences: received', metadata: { - input: prefs.audio_devices?.input_device_id ? 'SET' : 'UNSET', - output: prefs.audio_devices?.output_device_id ? 'SET' : 'UNSET', + input: prefs.audio_devices?.input_device_id ?? 'UNDEFINED', + output: prefs.audio_devices?.output_device_id ?? 'UNDEFINED', }, }); return prefs; diff --git a/client/src/api/tauri-adapter/sections/webhooks.ts b/client/src/api/tauri-adapter/sections/webhooks.ts index 7143fb7..6ae41b9 100644 --- a/client/src/api/tauri-adapter/sections/webhooks.ts +++ b/client/src/api/tauri-adapter/sections/webhooks.ts @@ -11,38 +11,14 @@ import { TauriCommands } from '../../tauri-constants'; import { clientLog } from '@/lib/client-log-events'; import type { TauriInvoke } from '../types'; -function sanitizeWebhookRequest(request: T): T { - const url = request.url?.trim(); - if (request.url !== undefined && !url) { - throw new Error('Webhook URL is required.'); - } - if (request.events !== undefined && request.events.length === 0) { - throw new Error('Webhook events are required.'); - } - const name = request.name?.trim(); - const secret = request.secret?.trim(); - return { - ...request, - ...(url ? { url } : {}), - ...(name ? { name } : {}), - ...(secret ? { secret } : {}), - }; -} - export function createWebhookApi(invoke: TauriInvoke): Pick< NoteFlowAPI, 'registerWebhook' | 'listWebhooks' | 'updateWebhook' | 'deleteWebhook' | 'getWebhookDeliveries' > { return { async registerWebhook(r: RegisterWebhookRequest): Promise { - const request = sanitizeWebhookRequest(r); const webhook = await invoke(TauriCommands.REGISTER_WEBHOOK, { - request, + request: r, }); clientLog.webhookRegistered(webhook.id, webhook.name); return webhook; @@ -53,8 +29,7 @@ export function createWebhookApi(invoke: TauriInvoke): Pick< }); }, async updateWebhook(r: UpdateWebhookRequest): Promise { - const request = sanitizeWebhookRequest(r); - return invoke(TauriCommands.UPDATE_WEBHOOK, { request }); + return invoke(TauriCommands.UPDATE_WEBHOOK, { request: r }); }, async deleteWebhook(webhookId: string): Promise { const response = await invoke(TauriCommands.DELETE_WEBHOOK, { diff --git a/client/src/api/tauri-adapter/stream.ts b/client/src/api/tauri-adapter/stream.ts index 9770f6d..7cbef6e 100644 --- a/client/src/api/tauri-adapter/stream.ts +++ b/client/src/api/tauri-adapter/stream.ts @@ -38,7 +38,6 @@ export class TauriTranscriptionStream { /** Queue for ordered, backpressure-aware chunk transmission. */ private readonly sendQueue: StreamingQueue; - private readonly drainTimeoutMs = 5000; constructor( private meetingId: string, @@ -89,7 +88,7 @@ export class TauriTranscriptionStream { const args: Record = { meeting_id: chunk.meeting_id, - audio_data: chunk.audio_data, + audio_data: Array.from(chunk.audio_data), timestamp: chunk.timestamp, }; if (typeof chunk.sample_rate === 'number') { @@ -224,16 +223,9 @@ export class TauriTranscriptionStream { // Drain the send queue to ensure all pending chunks are transmitted try { - const drainPromise = this.sendQueue.drain(); - const timeoutPromise = new Promise((_, reject) => { - const timeoutId = setTimeout(() => { - reject(new Error('Queue drain timeout')); - }, this.drainTimeoutMs); - drainPromise.finally(() => clearTimeout(timeoutId)); - }); - await Promise.race([drainPromise, timeoutPromise]); + await this.sendQueue.drain(); } catch { - // Queue drain failed or timed out - continue with cleanup anyway + // Queue drain failed - continue with cleanup anyway } if (this.unlistenFn) { diff --git a/client/src/api/tauri-adapter/utils.ts b/client/src/api/tauri-adapter/utils.ts index 804214c..2e353c1 100644 --- a/client/src/api/tauri-adapter/utils.ts +++ b/client/src/api/tauri-adapter/utils.ts @@ -23,18 +23,12 @@ export function recordingBlockedDetails(error: unknown): { ruleLabel?: string; appName?: string; } | null { - let message: string; - if (error instanceof Error) { - message = error.message; - } else if (typeof error === 'string') { - message = error; - } else { - try { - message = JSON.stringify(error); - } catch { - message = String(error); - } - } + const message = + error instanceof Error + ? error.message + : typeof error === 'string' + ? error + : JSON.stringify(error); if (!message.includes(RECORDING_BLOCKED_PREFIX)) { return null;