UNPKG

@novo-learning/novo-sdk

Version:

SDK for the Novolanguage Speech Analysis API

164 lines (148 loc) 5.59 kB
import { ExerciseResult } from '../../../entities'; import { Exercise } from '../../../entities/exercise'; import { SetExerciseIdDto } from '../dto/exercise-id.dto'; import { AccessTokenDto, PromptDto } from '../dto/index'; import { SetChoiceDto } from '../dto/set-choice.dto'; import { SetConfigDto } from '../dto/set-config.dto'; import { EventBus } from '../event-bus'; import { ConnectionStateChangedEvent } from '../events/connection-state-change.event'; import { Deferred } from '../utils/deferred.promise'; import { MessageQueue } from './message-queue'; import { ConnectionMonitor } from './monitor'; export class SpeechApiConnection { private websocket?: WebSocket; readonly eventBus = new EventBus(); private connectionMonitor?: ConnectionMonitor; private messageQueue?: MessageQueue; private lastUsedToken?: string; constructor(private readonly url: string) { if (window) { window.addEventListener('offline', () => { console.log('Browser went offline'); this.disconnect(); }); window.addEventListener('online', () => { console.log('Browser went online'); if (this.lastUsedToken) { this.connect(this.lastUsedToken); } }); } } connect(token: string): Promise<void> { if (this.connectionMonitor !== undefined) { console.warn(`Already connected`); return Promise.resolve(); } this.lastUsedToken = token; this.websocket = new WebSocket(`${this.url}?access_token=${token}`); this.websocket.binaryType = 'arraybuffer'; this.messageQueue = new MessageQueue(this.eventBus, this.websocket); this.connectionMonitor = new ConnectionMonitor(this.eventBus, this.messageQueue, this.websocket); const connecting = new Deferred<void>(); const waitForConnection = (event: ConnectionStateChangedEvent): void => { // Wait until open state is reached if (event.state === 'initial' || event.state === 'connecting') { return; } if (event.state === 'open') { connecting.resolve(); } else if (event.state === 'closed') { connecting.reject(); } // Remove event listener, so promise is only resolved or rejected once this.eventBus.removeEventListener(ConnectionStateChangedEvent.type, waitForConnection); }; this.eventBus.addEventListener<ConnectionStateChangedEvent>(ConnectionStateChangedEvent.type, waitForConnection); return connecting.promise; } disconnect(): void { if (this.connectionMonitor) { this.connectionMonitor.close(); this.connectionMonitor = undefined; } if (this.messageQueue) { this.messageQueue.clearMessageQueue(); this.messageQueue = undefined; } } async sendAudio(data: Blob | ArrayBuffer): Promise<void> { if (this.connectionMonitor?.state === 'open') { this.websocket?.send(data); } } async getResult(): Promise<ExerciseResult | undefined> { if (this.messageQueue) { return this.messageQueue.request<void, ExerciseResult>('get_result'); } return undefined; } async setExercise(exercise: Exercise): Promise<{ result: { success: boolean; error?: string } }> { if (this.messageQueue) { const result = await this.messageQueue.request<Exercise, { result: { success: boolean; error?: string } }>( 'set_exercise', exercise, ); if (result !== undefined) { return result; } } return { result: { success: false, error: 'No connection' } }; } async setPrompt( prompt: string, confusion?: string | string[][], metadata?: { [key: string]: string | number | boolean }, ): Promise<{ result: { success: boolean; error?: string } }> { if (this.messageQueue) { const result = await this.messageQueue.request<PromptDto, { result: { success: boolean; error?: string } }>( 'set_prompt', { prompt, confusion, metadata }, ); if (result !== undefined) { return result; } } return { result: { success: false, error: 'No connection' } }; } async setChoice( correctOptions: string[], incorrectOptions: string[], metadata?: { [key: string]: string | number | boolean }, multiple?: boolean, ): Promise<{ result: { success: boolean; error?: string } }> { if (this.messageQueue) { const result = await this.messageQueue.request<SetChoiceDto, { result: { success: boolean; error?: string } }>( 'set_choice', { correctOptions, incorrectOptions, metadata, multiple }, ); if (result !== undefined) { return result; } } return { result: { success: false, error: 'No connection' } }; } async setExerciseId(config: SetExerciseIdDto): Promise<{ result: { success: boolean; error?: string } }> { if (this.messageQueue) { const result = await this.messageQueue.request< SetExerciseIdDto, { result: { success: boolean; error?: string } } >('set_exercise_id', config); if (result !== undefined) { return result; } } return { result: { success: false, error: 'No connection' } }; } async updateAccessToken(accessToken: string): Promise<void> { this.lastUsedToken = accessToken; if (this.messageQueue) { return this.messageQueue.request<AccessTokenDto, void>('set_access_token', { accessToken }); } } async setConfig(config: SetConfigDto): Promise<void> { if (this.messageQueue) { return this.messageQueue.request<SetConfigDto, void>('set_config', config); } } }