whio-api-sdk 1.0.196-beta-staging → 1.0.198-beta-staging
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/WEBSOCKET_AUDIO_STREAMING.md +258 -0
- package/dist/src/sdk/modules/websocket.module.d.ts +73 -0
- package/dist/src/sdk/modules/websocket.module.js +253 -0
- package/dist/src/sdk/sdk.d.ts +10 -0
- package/dist/src/sdk/sdk.js +27 -0
- package/dist/src/sdk/types/common.types.d.ts +9 -0
- package/dist/src/sdk/types/common.types.js +9 -1
- package/dist/src/sdk/types/index.d.ts +1 -0
- package/dist/src/sdk/types/index.js +1 -0
- package/dist/src/sdk/types/session.types.d.ts +3 -0
- package/dist/src/sdk/types/websocket.types.d.ts +50 -0
- package/dist/src/sdk/types/websocket.types.js +2 -0
- package/examples/websocket-audio-streaming.ts +204 -0
- package/package.json +2 -1
- package/src/sdk/modules/websocket.module.ts +325 -0
- package/src/sdk/sdk.ts +36 -0
- package/src/sdk/types/common.types.ts +11 -0
- package/src/sdk/types/index.ts +1 -0
- package/src/sdk/types/session.types.ts +3 -0
- package/src/sdk/types/websocket.types.ts +48 -0
- package/test-websocket.mjs +96 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { User } from './user.types';
|
|
2
2
|
import { Template, TranscriptionSummary } from './template.types';
|
|
3
3
|
import { AudioFile } from './audio.types';
|
|
4
|
+
import { SummaryStatus } from './common.types';
|
|
4
5
|
export interface Session {
|
|
5
6
|
id: string;
|
|
6
7
|
transcript: string;
|
|
@@ -12,6 +13,7 @@ export interface Session {
|
|
|
12
13
|
templateName?: string;
|
|
13
14
|
summary?: string;
|
|
14
15
|
sessionName?: string;
|
|
16
|
+
summaryStatus: SummaryStatus;
|
|
15
17
|
createdAt: string;
|
|
16
18
|
updatedAt: string;
|
|
17
19
|
templateId: string;
|
|
@@ -50,5 +52,6 @@ export interface UpdateSessionDto {
|
|
|
50
52
|
templateName?: string;
|
|
51
53
|
summary?: string;
|
|
52
54
|
sessionName?: string;
|
|
55
|
+
summaryStatus?: SummaryStatus;
|
|
53
56
|
primaryTranscriptionSummaryId?: string;
|
|
54
57
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface AudioChunkPayload {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
audioChunk: number[] | number[][];
|
|
4
|
+
flag: 'end' | null;
|
|
5
|
+
}
|
|
6
|
+
export interface WebSocketConfig {
|
|
7
|
+
autoConnect?: boolean;
|
|
8
|
+
reconnectAttempts?: number;
|
|
9
|
+
reconnectDelay?: number;
|
|
10
|
+
maxReconnectDelay?: number;
|
|
11
|
+
namespace?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface WebSocketConnectionStats {
|
|
14
|
+
isConnected: boolean;
|
|
15
|
+
reconnectAttempts: number;
|
|
16
|
+
lastConnectedAt?: Date;
|
|
17
|
+
lastDisconnectedAt?: Date;
|
|
18
|
+
}
|
|
19
|
+
export interface AudioStreamingOptions {
|
|
20
|
+
bufferSize?: number;
|
|
21
|
+
autoTranscribe?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface WebSocketEvents {
|
|
24
|
+
connected: (data: {
|
|
25
|
+
message: string;
|
|
26
|
+
userId: string;
|
|
27
|
+
timestamp: string;
|
|
28
|
+
}) => void;
|
|
29
|
+
disconnected: (reason: string) => void;
|
|
30
|
+
'audio-chunk-received': (data: {
|
|
31
|
+
sessionId: string;
|
|
32
|
+
chunkCount: number;
|
|
33
|
+
timestamp: string;
|
|
34
|
+
}) => void;
|
|
35
|
+
'transcription-queued': (data: {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
message: string;
|
|
38
|
+
timestamp: string;
|
|
39
|
+
}) => void;
|
|
40
|
+
'audio-error': (error: {
|
|
41
|
+
error: string;
|
|
42
|
+
}) => void;
|
|
43
|
+
'transcription-error': (error: {
|
|
44
|
+
sessionId: string;
|
|
45
|
+
error: string;
|
|
46
|
+
}) => void;
|
|
47
|
+
'connection-error': (error: Error) => void;
|
|
48
|
+
'reconnecting': (attemptNumber: number) => void;
|
|
49
|
+
'reconnect-failed': () => void;
|
|
50
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { ApiSDK } from '../src/sdk/sdk';
|
|
2
|
+
|
|
3
|
+
// Example demonstrating WebSocket audio streaming
|
|
4
|
+
async function exampleWebSocketAudioStreaming() {
|
|
5
|
+
// Initialize SDK with WebSocket configuration
|
|
6
|
+
const sdk = new ApiSDK({
|
|
7
|
+
baseUrl: 'http://localhost:3000/api',
|
|
8
|
+
storage: {
|
|
9
|
+
getItem: (key: string) => localStorage.getItem(key),
|
|
10
|
+
setItem: (key: string, value: string) => localStorage.setItem(key, value),
|
|
11
|
+
removeItem: (key: string) => localStorage.removeItem(key)
|
|
12
|
+
},
|
|
13
|
+
websocket: {
|
|
14
|
+
autoConnect: true,
|
|
15
|
+
reconnectAttempts: -1, // Infinite reconnection attempts
|
|
16
|
+
reconnectDelay: 1000,
|
|
17
|
+
maxReconnectDelay: 30000,
|
|
18
|
+
namespace: 'connections'
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// First, authenticate
|
|
23
|
+
try {
|
|
24
|
+
await sdk.login('user@example.com', 'password');
|
|
25
|
+
console.log('Authenticated successfully');
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Authentication failed:', error);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Create a session for audio streaming
|
|
32
|
+
const session = await sdk.createSession({
|
|
33
|
+
sessionName: 'WebSocket Audio Test',
|
|
34
|
+
transcript: ''
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
console.log('Created session:', session.id);
|
|
38
|
+
|
|
39
|
+
// Set up WebSocket event listeners
|
|
40
|
+
sdk.onWebSocketEvent('connected', (data) => {
|
|
41
|
+
console.log('WebSocket connected:', data);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
sdk.onWebSocketEvent('disconnected', (reason) => {
|
|
45
|
+
console.log('WebSocket disconnected:', reason);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
sdk.onWebSocketEvent('audio-chunk-received', (data) => {
|
|
49
|
+
console.log('Audio chunk received:', data);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
sdk.onWebSocketEvent('transcription-queued', (data) => {
|
|
53
|
+
console.log('Transcription queued:', data);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
sdk.onWebSocketEvent('audio-error', (error) => {
|
|
57
|
+
console.error('Audio error:', error);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
sdk.onWebSocketEvent('connection-error', (error) => {
|
|
61
|
+
console.error('Connection error:', error);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
sdk.onWebSocketEvent('reconnecting', (attemptNumber) => {
|
|
65
|
+
console.log(`Reconnecting... attempt ${attemptNumber}`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Wait for WebSocket connection
|
|
69
|
+
while (!sdk.isWebSocketConnected()) {
|
|
70
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log('WebSocket connected, starting audio streaming...');
|
|
74
|
+
|
|
75
|
+
// Simulate audio data (in a real app, this would come from microphone)
|
|
76
|
+
const simulateAudioChunks = (): number[][] => {
|
|
77
|
+
const chunks: number[][] = [];
|
|
78
|
+
for (let i = 0; i < 15; i++) {
|
|
79
|
+
const chunk: number[] = [];
|
|
80
|
+
for (let j = 0; j < 1024; j++) {
|
|
81
|
+
// Generate random audio samples between -1 and 1
|
|
82
|
+
chunk.push((Math.random() - 0.5) * 2);
|
|
83
|
+
}
|
|
84
|
+
chunks.push(chunk);
|
|
85
|
+
}
|
|
86
|
+
return chunks;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const audioChunks = simulateAudioChunks();
|
|
90
|
+
|
|
91
|
+
// Stream audio chunks one by one
|
|
92
|
+
for (let i = 0; i < audioChunks.length; i++) {
|
|
93
|
+
const isLastChunk = i === audioChunks.length - 1;
|
|
94
|
+
|
|
95
|
+
console.log(`Streaming chunk ${i + 1}/${audioChunks.length}`);
|
|
96
|
+
sdk.streamAudioChunk(session.id, audioChunks[i], isLastChunk);
|
|
97
|
+
|
|
98
|
+
// Small delay between chunks
|
|
99
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log('Finished streaming audio chunks');
|
|
103
|
+
|
|
104
|
+
// Alternative: Stream all chunks at once
|
|
105
|
+
// await sdk.streamAudioChunks(session.id, audioChunks);
|
|
106
|
+
|
|
107
|
+
// Keep the connection alive for a bit to see the responses
|
|
108
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
109
|
+
|
|
110
|
+
// Clean up
|
|
111
|
+
sdk.disconnectWebSocket();
|
|
112
|
+
console.log('WebSocket disconnected');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Example of handling microphone input (browser environment)
|
|
116
|
+
async function exampleMicrophoneStreaming() {
|
|
117
|
+
const sdk = new ApiSDK({
|
|
118
|
+
baseUrl: 'http://localhost:3000/api',
|
|
119
|
+
storage: {
|
|
120
|
+
getItem: (key: string) => localStorage.getItem(key),
|
|
121
|
+
setItem: (key: string, value: string) => localStorage.setItem(key, value),
|
|
122
|
+
removeItem: (key: string) => localStorage.removeItem(key)
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Authenticate and create session
|
|
127
|
+
await sdk.login('user@example.com', 'password');
|
|
128
|
+
const session = await sdk.createSession({
|
|
129
|
+
sessionName: 'Microphone Streaming',
|
|
130
|
+
transcript: ''
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Set up WebSocket events
|
|
134
|
+
sdk.onWebSocketEvent('connected', (data) => {
|
|
135
|
+
console.log('Ready for microphone streaming');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
sdk.onWebSocketEvent('audio-chunk-received', (data) => {
|
|
139
|
+
console.log(`Buffer count: ${data.chunkCount}`);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
sdk.onWebSocketEvent('transcription-queued', (data) => {
|
|
143
|
+
console.log('Transcription started for session:', data.sessionId);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Wait for connection
|
|
147
|
+
while (!sdk.isWebSocketConnected()) {
|
|
148
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Get microphone access
|
|
152
|
+
try {
|
|
153
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
154
|
+
const audioContext = new AudioContext();
|
|
155
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
156
|
+
const processor = audioContext.createScriptProcessor(4096, 1, 1);
|
|
157
|
+
|
|
158
|
+
processor.onaudioprocess = (event) => {
|
|
159
|
+
const inputBuffer = event.inputBuffer;
|
|
160
|
+
const inputData = inputBuffer.getChannelData(0);
|
|
161
|
+
|
|
162
|
+
// Convert Float32Array to regular array
|
|
163
|
+
const audioChunk = Array.from(inputData);
|
|
164
|
+
|
|
165
|
+
// Stream the audio chunk
|
|
166
|
+
sdk.streamAudioChunk(session.id, audioChunk, false);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
source.connect(processor);
|
|
170
|
+
processor.connect(audioContext.destination);
|
|
171
|
+
|
|
172
|
+
console.log('Microphone streaming started. Recording for 10 seconds...');
|
|
173
|
+
|
|
174
|
+
// Stop after 10 seconds and trigger transcription
|
|
175
|
+
setTimeout(() => {
|
|
176
|
+
// Send empty chunk with end flag to trigger transcription
|
|
177
|
+
sdk.streamAudioChunk(session.id, [], true);
|
|
178
|
+
|
|
179
|
+
// Clean up
|
|
180
|
+
processor.disconnect();
|
|
181
|
+
source.disconnect();
|
|
182
|
+
stream.getTracks().forEach(track => track.stop());
|
|
183
|
+
|
|
184
|
+
console.log('Recording stopped, transcription queued');
|
|
185
|
+
}, 10000);
|
|
186
|
+
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('Error accessing microphone:', error);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Run the example
|
|
193
|
+
if (typeof window !== 'undefined') {
|
|
194
|
+
// Browser environment
|
|
195
|
+
console.log('Browser environment detected');
|
|
196
|
+
// Uncomment to run microphone example
|
|
197
|
+
// exampleMicrophoneStreaming();
|
|
198
|
+
} else {
|
|
199
|
+
// Node.js environment
|
|
200
|
+
console.log('Node.js environment detected');
|
|
201
|
+
exampleWebSocketAudioStreaming();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export { exampleWebSocketAudioStreaming, exampleMicrophoneStreaming };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whio-api-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.198-beta-staging",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"jwt-decode": "^4.0.0",
|
|
14
|
+
"socket.io-client": "^4.8.1",
|
|
14
15
|
"typescript": "^4.0.0"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { io, Socket } from 'socket.io-client';
|
|
2
|
+
import { BaseClient } from './base-client';
|
|
3
|
+
import {
|
|
4
|
+
WebSocketConfig,
|
|
5
|
+
WebSocketConnectionStats,
|
|
6
|
+
WebSocketEvents,
|
|
7
|
+
AudioChunkPayload,
|
|
8
|
+
AudioStreamingOptions
|
|
9
|
+
} from '../types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* WebSocket connection manager with auto-reconnect and audio streaming capabilities
|
|
13
|
+
*/
|
|
14
|
+
export class WebSocketModule extends BaseClient {
|
|
15
|
+
private socket: Socket | null = null;
|
|
16
|
+
private config: WebSocketConfig;
|
|
17
|
+
private reconnectAttempts = 0;
|
|
18
|
+
private maxReconnectAttempts: number;
|
|
19
|
+
private reconnectDelay: number;
|
|
20
|
+
private maxReconnectDelay: number;
|
|
21
|
+
private reconnectTimeout: NodeJS.Timeout | null = null;
|
|
22
|
+
private connectionStats: WebSocketConnectionStats;
|
|
23
|
+
private eventHandlers: Map<keyof WebSocketEvents, Function[]> = new Map();
|
|
24
|
+
|
|
25
|
+
constructor(baseConfig: any) {
|
|
26
|
+
super(baseConfig);
|
|
27
|
+
|
|
28
|
+
const wsConfig = baseConfig.websocket || {};
|
|
29
|
+
this.config = {
|
|
30
|
+
autoConnect: true,
|
|
31
|
+
reconnectAttempts: -1, // Infinite attempts
|
|
32
|
+
reconnectDelay: 1000,
|
|
33
|
+
maxReconnectDelay: 30000,
|
|
34
|
+
namespace: 'connections',
|
|
35
|
+
...wsConfig
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this.maxReconnectAttempts = this.config.reconnectAttempts!;
|
|
39
|
+
this.reconnectDelay = this.config.reconnectDelay!;
|
|
40
|
+
this.maxReconnectDelay = this.config.maxReconnectDelay!;
|
|
41
|
+
|
|
42
|
+
this.connectionStats = {
|
|
43
|
+
isConnected: false,
|
|
44
|
+
reconnectAttempts: 0
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (this.config.autoConnect) {
|
|
48
|
+
this.connect();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Connect to WebSocket server
|
|
54
|
+
*/
|
|
55
|
+
public connect(): void {
|
|
56
|
+
if (this.socket?.connected) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const wsUrl = this.getWebSocketUrl();
|
|
61
|
+
const token = this.getAccessToken();
|
|
62
|
+
|
|
63
|
+
if (!token) {
|
|
64
|
+
this.emit('connection-error', new Error('No access token available for WebSocket connection'));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.socket = io(wsUrl, {
|
|
69
|
+
auth: {
|
|
70
|
+
token: token
|
|
71
|
+
},
|
|
72
|
+
forceNew: true,
|
|
73
|
+
timeout: 10000
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.setupEventHandlers();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Disconnect from WebSocket server
|
|
81
|
+
*/
|
|
82
|
+
public disconnect(): void {
|
|
83
|
+
if (this.reconnectTimeout) {
|
|
84
|
+
clearTimeout(this.reconnectTimeout);
|
|
85
|
+
this.reconnectTimeout = null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (this.socket) {
|
|
89
|
+
this.socket.disconnect();
|
|
90
|
+
this.socket = null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.connectionStats.isConnected = false;
|
|
94
|
+
this.connectionStats.lastDisconnectedAt = new Date();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if WebSocket is connected
|
|
99
|
+
*/
|
|
100
|
+
public isConnected(): boolean {
|
|
101
|
+
return this.socket?.connected || false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get connection statistics
|
|
106
|
+
*/
|
|
107
|
+
public getConnectionStats(): WebSocketConnectionStats {
|
|
108
|
+
return { ...this.connectionStats };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Stream audio chunk to server
|
|
113
|
+
*/
|
|
114
|
+
public streamAudioChunk(
|
|
115
|
+
sessionId: string,
|
|
116
|
+
audioChunk: number[] | number[][],
|
|
117
|
+
isEnd: boolean = false
|
|
118
|
+
): void {
|
|
119
|
+
if (!this.socket?.connected) {
|
|
120
|
+
throw new Error('WebSocket not connected. Cannot stream audio chunk.');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const payload: AudioChunkPayload = {
|
|
124
|
+
sessionId,
|
|
125
|
+
audioChunk,
|
|
126
|
+
flag: isEnd ? 'end' : null
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
this.socket.emit('audio-chunk', payload);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Stream multiple audio chunks with automatic end detection
|
|
134
|
+
*/
|
|
135
|
+
public async streamAudioChunks(
|
|
136
|
+
sessionId: string,
|
|
137
|
+
audioChunks: number[],
|
|
138
|
+
options: AudioStreamingOptions = {}
|
|
139
|
+
): Promise<void> {
|
|
140
|
+
if (!this.socket?.connected) {
|
|
141
|
+
throw new Error('WebSocket not connected. Cannot stream audio chunks.');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const chunks = audioChunks;
|
|
145
|
+
const delay = options.bufferSize || 100; // ms between chunks
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
148
|
+
const isLastChunk = i === chunks.length - 1;
|
|
149
|
+
|
|
150
|
+
this.streamAudioChunk(sessionId, chunks, isLastChunk);
|
|
151
|
+
|
|
152
|
+
// Small delay between chunks to prevent overwhelming the server
|
|
153
|
+
if (!isLastChunk) {
|
|
154
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Add event listener
|
|
161
|
+
*/
|
|
162
|
+
public on<K extends keyof WebSocketEvents>(
|
|
163
|
+
event: K,
|
|
164
|
+
handler: WebSocketEvents[K]
|
|
165
|
+
): void {
|
|
166
|
+
if (!this.eventHandlers.has(event)) {
|
|
167
|
+
this.eventHandlers.set(event, []);
|
|
168
|
+
}
|
|
169
|
+
this.eventHandlers.get(event)!.push(handler);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Remove event listener
|
|
174
|
+
*/
|
|
175
|
+
public off<K extends keyof WebSocketEvents>(
|
|
176
|
+
event: K,
|
|
177
|
+
handler: WebSocketEvents[K]
|
|
178
|
+
): void {
|
|
179
|
+
const handlers = this.eventHandlers.get(event);
|
|
180
|
+
if (handlers) {
|
|
181
|
+
const index = handlers.indexOf(handler);
|
|
182
|
+
if (index > -1) {
|
|
183
|
+
handlers.splice(index, 1);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Emit event to registered handlers
|
|
190
|
+
*/
|
|
191
|
+
private emit<K extends keyof WebSocketEvents>(
|
|
192
|
+
event: K,
|
|
193
|
+
...args: Parameters<WebSocketEvents[K]>
|
|
194
|
+
): void {
|
|
195
|
+
const handlers = this.eventHandlers.get(event);
|
|
196
|
+
if (handlers) {
|
|
197
|
+
handlers.forEach(handler => {
|
|
198
|
+
try {
|
|
199
|
+
(handler as Function)(...args);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error(`Error in WebSocket event handler for ${event}:`, error);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Setup WebSocket event handlers
|
|
209
|
+
*/
|
|
210
|
+
private setupEventHandlers(): void {
|
|
211
|
+
if (!this.socket) return;
|
|
212
|
+
|
|
213
|
+
// Connection events
|
|
214
|
+
this.socket.on('connect', () => {
|
|
215
|
+
this.connectionStats.isConnected = true;
|
|
216
|
+
this.connectionStats.lastConnectedAt = new Date();
|
|
217
|
+
this.reconnectAttempts = 0;
|
|
218
|
+
|
|
219
|
+
if (this.reconnectTimeout) {
|
|
220
|
+
clearTimeout(this.reconnectTimeout);
|
|
221
|
+
this.reconnectTimeout = null;
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
this.socket.on('disconnect', (reason: string) => {
|
|
226
|
+
this.connectionStats.isConnected = false;
|
|
227
|
+
this.connectionStats.lastDisconnectedAt = new Date();
|
|
228
|
+
|
|
229
|
+
this.emit('disconnected', reason);
|
|
230
|
+
|
|
231
|
+
// Auto-reconnect logic
|
|
232
|
+
if (this.shouldReconnect(reason)) {
|
|
233
|
+
this.scheduleReconnect();
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
this.socket.on('connect_error', (error: Error) => {
|
|
238
|
+
this.emit('connection-error', error);
|
|
239
|
+
|
|
240
|
+
if (this.shouldReconnect()) {
|
|
241
|
+
this.scheduleReconnect();
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Application-specific events
|
|
246
|
+
this.socket.on('connected', (data: any) => {
|
|
247
|
+
this.emit('connected', data);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
this.socket.on('audio-chunk-received', (data: any) => {
|
|
251
|
+
this.emit('audio-chunk-received', data);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
this.socket.on('transcription-queued', (data: any) => {
|
|
255
|
+
this.emit('transcription-queued', data);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
this.socket.on('audio-error', (error: any) => {
|
|
259
|
+
this.emit('audio-error', error);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
this.socket.on('transcription-error', (error: any) => {
|
|
263
|
+
this.emit('transcription-error', error);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Determine if should attempt reconnection
|
|
269
|
+
*/
|
|
270
|
+
private shouldReconnect(reason?: string): boolean {
|
|
271
|
+
// Don't reconnect if manually disconnected
|
|
272
|
+
if (reason === 'io client disconnect') {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Check if we've exceeded max attempts
|
|
277
|
+
if (this.maxReconnectAttempts > 0 && this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
278
|
+
this.emit('reconnect-failed');
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Schedule reconnection attempt
|
|
287
|
+
*/
|
|
288
|
+
private scheduleReconnect(): void {
|
|
289
|
+
if (this.reconnectTimeout) {
|
|
290
|
+
clearTimeout(this.reconnectTimeout);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
this.reconnectAttempts++;
|
|
294
|
+
this.connectionStats.reconnectAttempts = this.reconnectAttempts;
|
|
295
|
+
|
|
296
|
+
// Calculate exponential backoff delay
|
|
297
|
+
const delay = Math.min(
|
|
298
|
+
this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
|
|
299
|
+
this.maxReconnectDelay
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
this.emit('reconnecting', this.reconnectAttempts);
|
|
303
|
+
|
|
304
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
305
|
+
this.connect();
|
|
306
|
+
}, delay);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get WebSocket URL
|
|
311
|
+
*/
|
|
312
|
+
private getWebSocketUrl(): string {
|
|
313
|
+
const wsProtocol = this.baseUrl.startsWith('https') ? 'wss' : 'ws';
|
|
314
|
+
const baseWsUrl = this.baseUrl.replace(/^https?/, wsProtocol);
|
|
315
|
+
return `${baseWsUrl}/${this.config.namespace}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Clean up resources
|
|
320
|
+
*/
|
|
321
|
+
public destroy(): void {
|
|
322
|
+
this.disconnect();
|
|
323
|
+
this.eventHandlers.clear();
|
|
324
|
+
}
|
|
325
|
+
}
|
package/src/sdk/sdk.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { WorkflowModule } from './modules/workflow.module';
|
|
|
13
13
|
import { LogModule } from './modules/log.module';
|
|
14
14
|
import { DebugModule } from './modules/debug.module';
|
|
15
15
|
import { ExternalIntegrationModule } from './modules/external-integration.module';
|
|
16
|
+
import { WebSocketModule } from './modules/websocket.module';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Main SDK class that provides access to all domain-specific modules
|
|
@@ -30,6 +31,7 @@ export class ApiSDK extends BaseClient {
|
|
|
30
31
|
public readonly logs: LogModule;
|
|
31
32
|
public readonly debug: DebugModule;
|
|
32
33
|
public readonly externalIntegrations: ExternalIntegrationModule;
|
|
34
|
+
public readonly websocket: WebSocketModule;
|
|
33
35
|
|
|
34
36
|
constructor(config: SDKConfig = {}) {
|
|
35
37
|
super(config);
|
|
@@ -47,6 +49,7 @@ export class ApiSDK extends BaseClient {
|
|
|
47
49
|
this.logs = new LogModule(config);
|
|
48
50
|
this.debug = new DebugModule(config);
|
|
49
51
|
this.externalIntegrations = new ExternalIntegrationModule(config);
|
|
52
|
+
this.websocket = new WebSocketModule(config);
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
// ======================
|
|
@@ -629,4 +632,37 @@ export class ApiSDK extends BaseClient {
|
|
|
629
632
|
public async deleteExternalIntegration(...args: Parameters<ExternalIntegrationModule['deleteExternalIntegration']>) {
|
|
630
633
|
return this.externalIntegrations.deleteExternalIntegration(...args);
|
|
631
634
|
}
|
|
635
|
+
|
|
636
|
+
// WebSocket methods
|
|
637
|
+
public connectWebSocket() {
|
|
638
|
+
return this.websocket.connect();
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
public disconnectWebSocket() {
|
|
642
|
+
return this.websocket.disconnect();
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
public isWebSocketConnected() {
|
|
646
|
+
return this.websocket.isConnected();
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
public streamAudioChunk(...args: Parameters<WebSocketModule['streamAudioChunk']>) {
|
|
650
|
+
return this.websocket.streamAudioChunk(...args);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
public streamAudioChunks(...args: Parameters<WebSocketModule['streamAudioChunks']>) {
|
|
654
|
+
return this.websocket.streamAudioChunks(...args);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
public onWebSocketEvent(...args: Parameters<WebSocketModule['on']>) {
|
|
658
|
+
return this.websocket.on(...args);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
public offWebSocketEvent(...args: Parameters<WebSocketModule['off']>) {
|
|
662
|
+
return this.websocket.off(...args);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
public getWebSocketStats() {
|
|
666
|
+
return this.websocket.getConnectionStats();
|
|
667
|
+
}
|
|
632
668
|
}
|