@novo-learning/novo-sdk
Version:
SDK for the Novolanguage Speech Analysis API
164 lines (148 loc) • 5.59 kB
text/typescript
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);
}
}
}