@volley/recognition-client-sdk
Version:
Recognition Service TypeScript/Node.js Client SDK
131 lines (113 loc) • 4.13 kB
text/typescript
/**
* Message Handler for Recognition Client
* Routes incoming WebSocket messages to appropriate callbacks
*/
import {
RecognitionResultTypeV1,
ClientControlActionV1,
type TranscriptionResultV1,
type FunctionCallResultV1,
type MetadataResultV1,
type ErrorResultV1,
type ClientControlMessageV1
} from '@recog/shared-types';
export interface MessageHandlerCallbacks {
onTranscript: (result: TranscriptionResultV1) => void;
onFunctionCall: (result: FunctionCallResultV1) => void;
onMetadata: (metadata: MetadataResultV1) => void;
onError: (error: ErrorResultV1) => void;
onControlMessage: (msg: ClientControlMessageV1) => void;
logger?: (level: 'debug' | 'info' | 'warn' | 'error', message: string, data?: any) => void;
}
export class MessageHandler {
private firstTranscriptTime: number | null = null;
private sessionStartTime: number | null = null;
private callbacks: MessageHandlerCallbacks;
constructor(callbacks: MessageHandlerCallbacks) {
this.callbacks = callbacks;
}
/**
* Set session start time for performance tracking
*/
setSessionStartTime(time: number): void {
this.sessionStartTime = time;
}
/**
* Handle incoming WebSocket message
*/
handleMessage(msg: { v: number; type: string; data: any }): void {
// Log ALL incoming messages for debugging
if (this.callbacks.logger) {
this.callbacks.logger('debug', 'Received WebSocket message', {
msgType: msg.type,
msgDataType: msg.data && typeof msg.data === 'object' && 'type' in msg.data ? msg.data.type : 'N/A',
fullMessage: msg
});
}
// Safely check for type in msg.data - guard against primitives
// Log error if we receive primitive data (indicates server issue)
if (msg.data && typeof msg.data !== 'object') {
if (this.callbacks.logger) {
this.callbacks.logger('error', 'Received primitive msg.data from server', {
dataType: typeof msg.data,
data: msg.data,
fullMessage: msg
});
}
}
const msgType = (msg.data && typeof msg.data === 'object' && 'type' in msg.data ? msg.data.type : undefined) || msg.type;
const msgData = msg.data || msg;
switch (msgType) {
case RecognitionResultTypeV1.TRANSCRIPTION:
this.handleTranscription(msgData as TranscriptionResultV1);
break;
case RecognitionResultTypeV1.FUNCTION_CALL:
this.callbacks.onFunctionCall(msgData as FunctionCallResultV1);
break;
case RecognitionResultTypeV1.METADATA:
this.callbacks.onMetadata(msgData as MetadataResultV1);
break;
case RecognitionResultTypeV1.ERROR:
this.callbacks.onError(msgData as ErrorResultV1);
break;
case RecognitionResultTypeV1.CLIENT_CONTROL_MESSAGE:
this.callbacks.onControlMessage(msgData as ClientControlMessageV1);
break;
default:
// Unknown message type - log if logger available
if (this.callbacks.logger) {
this.callbacks.logger('debug', 'Unknown message type', { type: msgType });
}
}
}
/**
* Handle transcript message and track performance metrics
* @param result - The transcription result from the server
*/
private handleTranscription(result: TranscriptionResultV1): void {
// Track time to first transcript
if (!this.firstTranscriptTime && this.sessionStartTime) {
this.firstTranscriptTime = Date.now();
const timeToFirstTranscript = this.firstTranscriptTime - this.sessionStartTime;
if (this.callbacks.logger) {
this.callbacks.logger('debug', 'First transcript received', {
timeToFirstTranscriptMs: timeToFirstTranscript
});
}
}
this.callbacks.onTranscript(result);
}
/**
* Get performance metrics
*/
getMetrics() {
return {
sessionStartTime: this.sessionStartTime,
firstTranscriptTime: this.firstTranscriptTime,
timeToFirstTranscript:
this.firstTranscriptTime && this.sessionStartTime
? this.firstTranscriptTime - this.sessionStartTime
: null
};
}
}