whio-api-sdk 1.0.197-beta-staging → 1.0.199-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 +252 -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 +2 -0
- package/dist/src/sdk/types/common.types.js +0 -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/websocket.types.d.ts +50 -0
- package/dist/src/sdk/types/websocket.types.js +2 -0
- package/examples/websocket-audio-streaming.ts +203 -0
- package/package.json +2 -1
- package/src/sdk/modules/websocket.module.ts +324 -0
- package/src/sdk/sdk.ts +36 -0
- package/src/sdk/types/common.types.ts +2 -0
- package/src/sdk/types/index.ts +1 -0
- package/src/sdk/types/websocket.types.ts +48 -0
- package/test-websocket.mjs +96 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# WebSocket Audio Streaming
|
|
2
|
+
|
|
3
|
+
The SDK now supports real-time audio streaming via WebSocket connections, providing an efficient alternative to HTTP-based audio upload for live transcription scenarios.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔄 **Auto-reconnection** with exponential backoff
|
|
8
|
+
- 📡 **Real-time audio streaming** with base64 audio support
|
|
9
|
+
- 🎯 **Event-driven architecture** for connection and audio events
|
|
10
|
+
- 🛡️ **Automatic authentication** using existing SDK tokens
|
|
11
|
+
- 📊 **Connection statistics** and monitoring
|
|
12
|
+
- 🔧 **Configurable buffering** and reconnection behavior
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { ApiSDK } from 'whio-api-sdk';
|
|
18
|
+
|
|
19
|
+
// Initialize SDK with WebSocket support
|
|
20
|
+
const sdk = new ApiSDK({
|
|
21
|
+
baseUrl: 'https://your-api.com/api',
|
|
22
|
+
storage: localStorage,
|
|
23
|
+
websocket: {
|
|
24
|
+
autoConnect: true, // Connect immediately on SDK initialization
|
|
25
|
+
reconnectAttempts: -1, // Infinite reconnection attempts
|
|
26
|
+
reconnectDelay: 1000, // Initial delay between reconnects (ms)
|
|
27
|
+
maxReconnectDelay: 30000, // Maximum delay between reconnects (ms)
|
|
28
|
+
namespace: 'connections' // WebSocket namespace
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Authenticate first
|
|
33
|
+
await sdk.login('user@example.com', 'password');
|
|
34
|
+
|
|
35
|
+
// Create a session for audio streaming
|
|
36
|
+
const session = await sdk.createSession({
|
|
37
|
+
sessionName: 'Live Audio Session',
|
|
38
|
+
transcript: ''
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Event Handling
|
|
43
|
+
|
|
44
|
+
Set up event listeners to respond to WebSocket events:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// Connection events
|
|
48
|
+
sdk.onWebSocketEvent('connected', (data) => {
|
|
49
|
+
console.log('WebSocket connected:', data);
|
|
50
|
+
// data: { message: string, userId: string, timestamp: string }
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
sdk.onWebSocketEvent('disconnected', (reason) => {
|
|
54
|
+
console.log('WebSocket disconnected:', reason);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
sdk.onWebSocketEvent('reconnecting', (attemptNumber) => {
|
|
58
|
+
console.log(`Reconnecting... attempt ${attemptNumber}`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Audio streaming events
|
|
62
|
+
sdk.onWebSocketEvent('audio-chunk-received', (data) => {
|
|
63
|
+
console.log('Audio chunk processed:', data);
|
|
64
|
+
// data: { sessionId: string, chunkCount: number, timestamp: string }
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
sdk.onWebSocketEvent('transcription-queued', (data) => {
|
|
68
|
+
console.log('Transcription started:', data);
|
|
69
|
+
// data: { sessionId: string, message: string, timestamp: string }
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Error handling
|
|
73
|
+
sdk.onWebSocketEvent('audio-error', (error) => {
|
|
74
|
+
console.error('Audio streaming error:', error);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
sdk.onWebSocketEvent('connection-error', (error) => {
|
|
78
|
+
console.error('Connection error:', error);
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Audio Streaming
|
|
83
|
+
|
|
84
|
+
### Single Chunk Streaming
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// Stream individual audio chunks
|
|
88
|
+
const audioChunk = "SGVsbG8gV29ybGQ="; // Base64 encoded audio data
|
|
89
|
+
sdk.streamAudioChunk(session.id, audioChunk, false);
|
|
90
|
+
|
|
91
|
+
// Stream final chunk to trigger transcription
|
|
92
|
+
sdk.streamAudioChunk(session.id, finalChunk, true);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Batch Streaming
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// Stream multiple chunks with automatic pacing
|
|
99
|
+
const audioChunks = [
|
|
100
|
+
"SGVsbG8gV29ybGQ=", // Base64 chunk 1
|
|
101
|
+
"QmFzZTY0IGNodW5r", // Base64 chunk 2
|
|
102
|
+
"QXVkaW8gZGF0YQ==" // Base64 chunk 3
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
await sdk.streamAudioChunks(session.id, audioChunks, {
|
|
106
|
+
bufferSize: 100 // Delay between chunks in ms
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Microphone Integration
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Real-time microphone streaming (browser)
|
|
114
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
115
|
+
const audioContext = new AudioContext();
|
|
116
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
117
|
+
const processor = audioContext.createScriptProcessor(4096, 1, 1);
|
|
118
|
+
|
|
119
|
+
processor.onaudioprocess = (event) => {
|
|
120
|
+
const inputBuffer = event.inputBuffer;
|
|
121
|
+
const inputData = inputBuffer.getChannelData(0);
|
|
122
|
+
|
|
123
|
+
// Convert float data to base64 (you'll need your own conversion logic)
|
|
124
|
+
const base64Chunk = convertFloatArrayToBase64(inputData);
|
|
125
|
+
sdk.streamAudioChunk(session.id, base64Chunk, false);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
source.connect(processor);
|
|
129
|
+
processor.connect(audioContext.destination);
|
|
130
|
+
|
|
131
|
+
// Stop recording and trigger transcription
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
sdk.streamAudioChunk(session.id, "", true); // Empty string for end
|
|
134
|
+
// Cleanup...
|
|
135
|
+
}, 10000);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Connection Management
|
|
139
|
+
|
|
140
|
+
### Manual Connection Control
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// Disable auto-connect and manage manually
|
|
144
|
+
const sdk = new ApiSDK({
|
|
145
|
+
websocket: { autoConnect: false }
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Connect when ready
|
|
149
|
+
sdk.connectWebSocket();
|
|
150
|
+
|
|
151
|
+
// Check connection status
|
|
152
|
+
if (sdk.isWebSocketConnected()) {
|
|
153
|
+
console.log('Ready to stream audio');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Disconnect when done
|
|
157
|
+
sdk.disconnectWebSocket();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Connection Statistics
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const stats = sdk.getWebSocketStats();
|
|
164
|
+
console.log({
|
|
165
|
+
isConnected: stats.isConnected,
|
|
166
|
+
reconnectAttempts: stats.reconnectAttempts,
|
|
167
|
+
lastConnectedAt: stats.lastConnectedAt,
|
|
168
|
+
lastDisconnectedAt: stats.lastDisconnectedAt
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Configuration Options
|
|
173
|
+
|
|
174
|
+
| Option | Type | Default | Description |
|
|
175
|
+
|--------|------|---------|-------------|
|
|
176
|
+
| `autoConnect` | boolean | `true` | Automatically connect on SDK initialization |
|
|
177
|
+
| `reconnectAttempts` | number | `-1` | Number of reconnection attempts (-1 = infinite) |
|
|
178
|
+
| `reconnectDelay` | number | `1000` | Initial delay between reconnects (ms) |
|
|
179
|
+
| `maxReconnectDelay` | number | `30000` | Maximum delay between reconnects (ms) |
|
|
180
|
+
| `namespace` | string | `'connections'` | WebSocket namespace |
|
|
181
|
+
|
|
182
|
+
## Audio Buffer Management
|
|
183
|
+
|
|
184
|
+
The server automatically buffers audio chunks and flushes them when:
|
|
185
|
+
- Buffer reaches 10 chunks
|
|
186
|
+
- Client sends a chunk with `flag: 'end'`
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// The server handles buffering automatically
|
|
190
|
+
sdk.streamAudioChunk(sessionId, base64Chunk1, false); // Buffered
|
|
191
|
+
sdk.streamAudioChunk(sessionId, base64Chunk2, false); // Buffered
|
|
192
|
+
// ... (8 more chunks buffered)
|
|
193
|
+
sdk.streamAudioChunk(sessionId, base64Chunk10, false); // Auto-flush triggered
|
|
194
|
+
sdk.streamAudioChunk(sessionId, base64Chunk11, true); // End flag triggers transcription
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Error Handling
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// Handle various error scenarios
|
|
201
|
+
sdk.onWebSocketEvent('connection-error', (error) => {
|
|
202
|
+
if (error.message.includes('No access token')) {
|
|
203
|
+
// Re-authenticate
|
|
204
|
+
await sdk.login(email, password);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
sdk.onWebSocketEvent('audio-error', (error) => {
|
|
209
|
+
if (error.error === 'sessionId and audioChunk are required') {
|
|
210
|
+
// Fix payload format
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
sdk.onWebSocketEvent('reconnect-failed', () => {
|
|
215
|
+
// Handle permanent connection failure
|
|
216
|
+
console.error('Unable to reconnect to WebSocket');
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Best Practices
|
|
221
|
+
|
|
222
|
+
1. **Authentication First**: Always authenticate before connecting WebSocket
|
|
223
|
+
2. **Event Listeners**: Set up event listeners before connecting
|
|
224
|
+
3. **Error Handling**: Always handle connection and audio errors
|
|
225
|
+
4. **Cleanup**: Disconnect WebSocket when done to free resources
|
|
226
|
+
5. **Chunk Size**: Keep audio chunks reasonably sized (1024-4096 samples)
|
|
227
|
+
6. **End Flag**: Always send final chunk with `flag: 'end'` to trigger transcription
|
|
228
|
+
|
|
229
|
+
## Browser vs Node.js
|
|
230
|
+
|
|
231
|
+
The WebSocket functionality works in both browser and Node.js environments:
|
|
232
|
+
|
|
233
|
+
- **Browser**: Perfect for real-time microphone streaming
|
|
234
|
+
- **Node.js**: Great for processing audio files or streams
|
|
235
|
+
|
|
236
|
+
## Migration from HTTP
|
|
237
|
+
|
|
238
|
+
Replace HTTP-based audio upload:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
// Old HTTP approach
|
|
242
|
+
await sdk.addBase64Chunk(sessionId, base64Chunks);
|
|
243
|
+
await sdk.queueSessionBase64AudioForTranscription(sessionId);
|
|
244
|
+
|
|
245
|
+
// New WebSocket approach
|
|
246
|
+
for (const chunk of audioChunks) {
|
|
247
|
+
const isLast = chunk === audioChunks[audioChunks.length - 1];
|
|
248
|
+
sdk.streamAudioChunk(sessionId, chunk, isLast);
|
|
249
|
+
}
|
|
250
|
+
// Transcription automatically triggered on end flag
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Troubleshooting
|
|
254
|
+
|
|
255
|
+
- **Connection Issues**: Check network connectivity and authentication
|
|
256
|
+
- **Audio Not Processing**: Ensure chunks are valid base64 strings
|
|
257
|
+
- **Transcription Not Starting**: Verify end flag is sent
|
|
258
|
+
- **Reconnection Problems**: Check server availability and token validity
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { BaseClient } from './base-client';
|
|
2
|
+
import { WebSocketConnectionStats, WebSocketEvents, AudioStreamingOptions } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* WebSocket connection manager with auto-reconnect and audio streaming capabilities
|
|
5
|
+
*/
|
|
6
|
+
export declare class WebSocketModule extends BaseClient {
|
|
7
|
+
private socket;
|
|
8
|
+
private config;
|
|
9
|
+
private reconnectAttempts;
|
|
10
|
+
private maxReconnectAttempts;
|
|
11
|
+
private reconnectDelay;
|
|
12
|
+
private maxReconnectDelay;
|
|
13
|
+
private reconnectTimeout;
|
|
14
|
+
private connectionStats;
|
|
15
|
+
private eventHandlers;
|
|
16
|
+
constructor(baseConfig: any);
|
|
17
|
+
/**
|
|
18
|
+
* Connect to WebSocket server
|
|
19
|
+
*/
|
|
20
|
+
connect(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Disconnect from WebSocket server
|
|
23
|
+
*/
|
|
24
|
+
disconnect(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Check if WebSocket is connected
|
|
27
|
+
*/
|
|
28
|
+
isConnected(): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Get connection statistics
|
|
31
|
+
*/
|
|
32
|
+
getConnectionStats(): WebSocketConnectionStats;
|
|
33
|
+
/**
|
|
34
|
+
* Stream audio chunk to server
|
|
35
|
+
*/
|
|
36
|
+
streamAudioChunk(sessionId: string, audioChunk: string, isEnd?: boolean): void;
|
|
37
|
+
/**
|
|
38
|
+
* Stream multiple audio chunks with automatic end detection
|
|
39
|
+
*/
|
|
40
|
+
streamAudioChunks(sessionId: string, audioChunks: string[], options?: AudioStreamingOptions): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Add event listener
|
|
43
|
+
*/
|
|
44
|
+
on<K extends keyof WebSocketEvents>(event: K, handler: WebSocketEvents[K]): void;
|
|
45
|
+
/**
|
|
46
|
+
* Remove event listener
|
|
47
|
+
*/
|
|
48
|
+
off<K extends keyof WebSocketEvents>(event: K, handler: WebSocketEvents[K]): void;
|
|
49
|
+
/**
|
|
50
|
+
* Emit event to registered handlers
|
|
51
|
+
*/
|
|
52
|
+
private emit;
|
|
53
|
+
/**
|
|
54
|
+
* Setup WebSocket event handlers
|
|
55
|
+
*/
|
|
56
|
+
private setupEventHandlers;
|
|
57
|
+
/**
|
|
58
|
+
* Determine if should attempt reconnection
|
|
59
|
+
*/
|
|
60
|
+
private shouldReconnect;
|
|
61
|
+
/**
|
|
62
|
+
* Schedule reconnection attempt
|
|
63
|
+
*/
|
|
64
|
+
private scheduleReconnect;
|
|
65
|
+
/**
|
|
66
|
+
* Get WebSocket URL
|
|
67
|
+
*/
|
|
68
|
+
private getWebSocketUrl;
|
|
69
|
+
/**
|
|
70
|
+
* Clean up resources
|
|
71
|
+
*/
|
|
72
|
+
destroy(): void;
|
|
73
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { io } from 'socket.io-client';
|
|
11
|
+
import { BaseClient } from './base-client';
|
|
12
|
+
/**
|
|
13
|
+
* WebSocket connection manager with auto-reconnect and audio streaming capabilities
|
|
14
|
+
*/
|
|
15
|
+
export class WebSocketModule extends BaseClient {
|
|
16
|
+
constructor(baseConfig) {
|
|
17
|
+
super(baseConfig);
|
|
18
|
+
this.socket = null;
|
|
19
|
+
this.reconnectAttempts = 0;
|
|
20
|
+
this.reconnectTimeout = null;
|
|
21
|
+
this.eventHandlers = new Map();
|
|
22
|
+
const wsConfig = baseConfig.websocket || {};
|
|
23
|
+
this.config = Object.assign({ autoConnect: true, reconnectAttempts: -1, reconnectDelay: 1000, maxReconnectDelay: 30000, namespace: 'connections' }, wsConfig);
|
|
24
|
+
this.maxReconnectAttempts = this.config.reconnectAttempts;
|
|
25
|
+
this.reconnectDelay = this.config.reconnectDelay;
|
|
26
|
+
this.maxReconnectDelay = this.config.maxReconnectDelay;
|
|
27
|
+
this.connectionStats = {
|
|
28
|
+
isConnected: false,
|
|
29
|
+
reconnectAttempts: 0
|
|
30
|
+
};
|
|
31
|
+
if (this.config.autoConnect) {
|
|
32
|
+
this.connect();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Connect to WebSocket server
|
|
37
|
+
*/
|
|
38
|
+
connect() {
|
|
39
|
+
var _a;
|
|
40
|
+
if ((_a = this.socket) === null || _a === void 0 ? void 0 : _a.connected) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const wsUrl = this.getWebSocketUrl();
|
|
44
|
+
const token = this.getAccessToken();
|
|
45
|
+
if (!token) {
|
|
46
|
+
this.emit('connection-error', new Error('No access token available for WebSocket connection'));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.socket = io(wsUrl, {
|
|
50
|
+
auth: {
|
|
51
|
+
token: token
|
|
52
|
+
},
|
|
53
|
+
forceNew: true,
|
|
54
|
+
timeout: 10000
|
|
55
|
+
});
|
|
56
|
+
this.setupEventHandlers();
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Disconnect from WebSocket server
|
|
60
|
+
*/
|
|
61
|
+
disconnect() {
|
|
62
|
+
if (this.reconnectTimeout) {
|
|
63
|
+
clearTimeout(this.reconnectTimeout);
|
|
64
|
+
this.reconnectTimeout = null;
|
|
65
|
+
}
|
|
66
|
+
if (this.socket) {
|
|
67
|
+
this.socket.disconnect();
|
|
68
|
+
this.socket = null;
|
|
69
|
+
}
|
|
70
|
+
this.connectionStats.isConnected = false;
|
|
71
|
+
this.connectionStats.lastDisconnectedAt = new Date();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if WebSocket is connected
|
|
75
|
+
*/
|
|
76
|
+
isConnected() {
|
|
77
|
+
var _a;
|
|
78
|
+
return ((_a = this.socket) === null || _a === void 0 ? void 0 : _a.connected) || false;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get connection statistics
|
|
82
|
+
*/
|
|
83
|
+
getConnectionStats() {
|
|
84
|
+
return Object.assign({}, this.connectionStats);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Stream audio chunk to server
|
|
88
|
+
*/
|
|
89
|
+
streamAudioChunk(sessionId, audioChunk, isEnd = false) {
|
|
90
|
+
var _a;
|
|
91
|
+
if (!((_a = this.socket) === null || _a === void 0 ? void 0 : _a.connected)) {
|
|
92
|
+
throw new Error('WebSocket not connected. Cannot stream audio chunk.');
|
|
93
|
+
}
|
|
94
|
+
const payload = {
|
|
95
|
+
sessionId,
|
|
96
|
+
audioChunk,
|
|
97
|
+
flag: isEnd ? 'end' : null
|
|
98
|
+
};
|
|
99
|
+
this.socket.emit('audio-chunk', payload);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Stream multiple audio chunks with automatic end detection
|
|
103
|
+
*/
|
|
104
|
+
streamAudioChunks(sessionId, audioChunks, options = {}) {
|
|
105
|
+
var _a;
|
|
106
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
107
|
+
if (!((_a = this.socket) === null || _a === void 0 ? void 0 : _a.connected)) {
|
|
108
|
+
throw new Error('WebSocket not connected. Cannot stream audio chunks.');
|
|
109
|
+
}
|
|
110
|
+
const delay = options.bufferSize || 100; // ms between chunks
|
|
111
|
+
for (let i = 0; i < audioChunks.length; i++) {
|
|
112
|
+
const isLastChunk = i === audioChunks.length - 1;
|
|
113
|
+
this.streamAudioChunk(sessionId, audioChunks[i], isLastChunk);
|
|
114
|
+
// Small delay between chunks to prevent overwhelming the server
|
|
115
|
+
if (!isLastChunk) {
|
|
116
|
+
yield new Promise(resolve => setTimeout(resolve, delay));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Add event listener
|
|
123
|
+
*/
|
|
124
|
+
on(event, handler) {
|
|
125
|
+
if (!this.eventHandlers.has(event)) {
|
|
126
|
+
this.eventHandlers.set(event, []);
|
|
127
|
+
}
|
|
128
|
+
this.eventHandlers.get(event).push(handler);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Remove event listener
|
|
132
|
+
*/
|
|
133
|
+
off(event, handler) {
|
|
134
|
+
const handlers = this.eventHandlers.get(event);
|
|
135
|
+
if (handlers) {
|
|
136
|
+
const index = handlers.indexOf(handler);
|
|
137
|
+
if (index > -1) {
|
|
138
|
+
handlers.splice(index, 1);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Emit event to registered handlers
|
|
144
|
+
*/
|
|
145
|
+
emit(event, ...args) {
|
|
146
|
+
const handlers = this.eventHandlers.get(event);
|
|
147
|
+
if (handlers) {
|
|
148
|
+
handlers.forEach(handler => {
|
|
149
|
+
try {
|
|
150
|
+
handler(...args);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error(`Error in WebSocket event handler for ${event}:`, error);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Setup WebSocket event handlers
|
|
160
|
+
*/
|
|
161
|
+
setupEventHandlers() {
|
|
162
|
+
if (!this.socket)
|
|
163
|
+
return;
|
|
164
|
+
// Connection events
|
|
165
|
+
this.socket.on('connect', () => {
|
|
166
|
+
this.connectionStats.isConnected = true;
|
|
167
|
+
this.connectionStats.lastConnectedAt = new Date();
|
|
168
|
+
this.reconnectAttempts = 0;
|
|
169
|
+
if (this.reconnectTimeout) {
|
|
170
|
+
clearTimeout(this.reconnectTimeout);
|
|
171
|
+
this.reconnectTimeout = null;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
this.socket.on('disconnect', (reason) => {
|
|
175
|
+
this.connectionStats.isConnected = false;
|
|
176
|
+
this.connectionStats.lastDisconnectedAt = new Date();
|
|
177
|
+
this.emit('disconnected', reason);
|
|
178
|
+
// Auto-reconnect logic
|
|
179
|
+
if (this.shouldReconnect(reason)) {
|
|
180
|
+
this.scheduleReconnect();
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
this.socket.on('connect_error', (error) => {
|
|
184
|
+
this.emit('connection-error', error);
|
|
185
|
+
if (this.shouldReconnect()) {
|
|
186
|
+
this.scheduleReconnect();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
// Application-specific events
|
|
190
|
+
this.socket.on('connected', (data) => {
|
|
191
|
+
this.emit('connected', data);
|
|
192
|
+
});
|
|
193
|
+
this.socket.on('audio-chunk-received', (data) => {
|
|
194
|
+
this.emit('audio-chunk-received', data);
|
|
195
|
+
});
|
|
196
|
+
this.socket.on('transcription-queued', (data) => {
|
|
197
|
+
this.emit('transcription-queued', data);
|
|
198
|
+
});
|
|
199
|
+
this.socket.on('audio-error', (error) => {
|
|
200
|
+
this.emit('audio-error', error);
|
|
201
|
+
});
|
|
202
|
+
this.socket.on('transcription-error', (error) => {
|
|
203
|
+
this.emit('transcription-error', error);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Determine if should attempt reconnection
|
|
208
|
+
*/
|
|
209
|
+
shouldReconnect(reason) {
|
|
210
|
+
// Don't reconnect if manually disconnected
|
|
211
|
+
if (reason === 'io client disconnect') {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
// Check if we've exceeded max attempts
|
|
215
|
+
if (this.maxReconnectAttempts > 0 && this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
216
|
+
this.emit('reconnect-failed');
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Schedule reconnection attempt
|
|
223
|
+
*/
|
|
224
|
+
scheduleReconnect() {
|
|
225
|
+
if (this.reconnectTimeout) {
|
|
226
|
+
clearTimeout(this.reconnectTimeout);
|
|
227
|
+
}
|
|
228
|
+
this.reconnectAttempts++;
|
|
229
|
+
this.connectionStats.reconnectAttempts = this.reconnectAttempts;
|
|
230
|
+
// Calculate exponential backoff delay
|
|
231
|
+
const delay = Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1), this.maxReconnectDelay);
|
|
232
|
+
this.emit('reconnecting', this.reconnectAttempts);
|
|
233
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
234
|
+
this.connect();
|
|
235
|
+
}, delay);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get WebSocket URL
|
|
239
|
+
*/
|
|
240
|
+
getWebSocketUrl() {
|
|
241
|
+
const wsProtocol = this.baseUrl.startsWith('https') ? 'wss' : 'ws';
|
|
242
|
+
const baseWsUrl = this.baseUrl.replace(/^https?/, wsProtocol);
|
|
243
|
+
return `${baseWsUrl}/${this.config.namespace}`;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Clean up resources
|
|
247
|
+
*/
|
|
248
|
+
destroy() {
|
|
249
|
+
this.disconnect();
|
|
250
|
+
this.eventHandlers.clear();
|
|
251
|
+
}
|
|
252
|
+
}
|
package/dist/src/sdk/sdk.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { WorkflowModule } from './modules/workflow.module';
|
|
|
12
12
|
import { LogModule } from './modules/log.module';
|
|
13
13
|
import { DebugModule } from './modules/debug.module';
|
|
14
14
|
import { ExternalIntegrationModule } from './modules/external-integration.module';
|
|
15
|
+
import { WebSocketModule } from './modules/websocket.module';
|
|
15
16
|
/**
|
|
16
17
|
* Main SDK class that provides access to all domain-specific modules
|
|
17
18
|
*/
|
|
@@ -28,6 +29,7 @@ export declare class ApiSDK extends BaseClient {
|
|
|
28
29
|
readonly logs: LogModule;
|
|
29
30
|
readonly debug: DebugModule;
|
|
30
31
|
readonly externalIntegrations: ExternalIntegrationModule;
|
|
32
|
+
readonly websocket: WebSocketModule;
|
|
31
33
|
constructor(config?: SDKConfig);
|
|
32
34
|
login(...args: Parameters<AuthModule['login']>): Promise<import("./types").LoginResponse>;
|
|
33
35
|
logout(...args: Parameters<AuthModule['logout']>): Promise<void>;
|
|
@@ -181,4 +183,12 @@ export declare class ApiSDK extends BaseClient {
|
|
|
181
183
|
getExternalIntegration(...args: Parameters<ExternalIntegrationModule['getExternalIntegration']>): Promise<import("./types").ExternalIntegration>;
|
|
182
184
|
updateExternalIntegration(...args: Parameters<ExternalIntegrationModule['updateExternalIntegration']>): Promise<import("./types").ExternalIntegration>;
|
|
183
185
|
deleteExternalIntegration(...args: Parameters<ExternalIntegrationModule['deleteExternalIntegration']>): Promise<void>;
|
|
186
|
+
connectWebSocket(): void;
|
|
187
|
+
disconnectWebSocket(): void;
|
|
188
|
+
isWebSocketConnected(): boolean;
|
|
189
|
+
streamAudioChunk(...args: Parameters<WebSocketModule['streamAudioChunk']>): void;
|
|
190
|
+
streamAudioChunks(...args: Parameters<WebSocketModule['streamAudioChunks']>): Promise<void>;
|
|
191
|
+
onWebSocketEvent(...args: Parameters<WebSocketModule['on']>): void;
|
|
192
|
+
offWebSocketEvent(...args: Parameters<WebSocketModule['off']>): void;
|
|
193
|
+
getWebSocketStats(): import("./types").WebSocketConnectionStats;
|
|
184
194
|
}
|
package/dist/src/sdk/sdk.js
CHANGED
|
@@ -20,6 +20,7 @@ import { WorkflowModule } from './modules/workflow.module';
|
|
|
20
20
|
import { LogModule } from './modules/log.module';
|
|
21
21
|
import { DebugModule } from './modules/debug.module';
|
|
22
22
|
import { ExternalIntegrationModule } from './modules/external-integration.module';
|
|
23
|
+
import { WebSocketModule } from './modules/websocket.module';
|
|
23
24
|
/**
|
|
24
25
|
* Main SDK class that provides access to all domain-specific modules
|
|
25
26
|
*/
|
|
@@ -39,6 +40,7 @@ export class ApiSDK extends BaseClient {
|
|
|
39
40
|
this.logs = new LogModule(config);
|
|
40
41
|
this.debug = new DebugModule(config);
|
|
41
42
|
this.externalIntegrations = new ExternalIntegrationModule(config);
|
|
43
|
+
this.websocket = new WebSocketModule(config);
|
|
42
44
|
}
|
|
43
45
|
// ======================
|
|
44
46
|
// BACKWARD COMPATIBILITY LAYER
|
|
@@ -761,4 +763,29 @@ export class ApiSDK extends BaseClient {
|
|
|
761
763
|
return this.externalIntegrations.deleteExternalIntegration(...args);
|
|
762
764
|
});
|
|
763
765
|
}
|
|
766
|
+
// WebSocket methods
|
|
767
|
+
connectWebSocket() {
|
|
768
|
+
return this.websocket.connect();
|
|
769
|
+
}
|
|
770
|
+
disconnectWebSocket() {
|
|
771
|
+
return this.websocket.disconnect();
|
|
772
|
+
}
|
|
773
|
+
isWebSocketConnected() {
|
|
774
|
+
return this.websocket.isConnected();
|
|
775
|
+
}
|
|
776
|
+
streamAudioChunk(...args) {
|
|
777
|
+
return this.websocket.streamAudioChunk(...args);
|
|
778
|
+
}
|
|
779
|
+
streamAudioChunks(...args) {
|
|
780
|
+
return this.websocket.streamAudioChunks(...args);
|
|
781
|
+
}
|
|
782
|
+
onWebSocketEvent(...args) {
|
|
783
|
+
return this.websocket.on(...args);
|
|
784
|
+
}
|
|
785
|
+
offWebSocketEvent(...args) {
|
|
786
|
+
return this.websocket.off(...args);
|
|
787
|
+
}
|
|
788
|
+
getWebSocketStats() {
|
|
789
|
+
return this.websocket.getConnectionStats();
|
|
790
|
+
}
|
|
764
791
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { WebSocketConfig } from './websocket.types';
|
|
1
2
|
export interface SDKConfig {
|
|
2
3
|
baseUrl?: string;
|
|
3
4
|
storage?: {
|
|
@@ -5,6 +6,7 @@ export interface SDKConfig {
|
|
|
5
6
|
setItem: (key: string, value: string) => void | Promise<void>;
|
|
6
7
|
removeItem: (key: string) => void | Promise<void>;
|
|
7
8
|
};
|
|
9
|
+
websocket?: WebSocketConfig;
|
|
8
10
|
}
|
|
9
11
|
export interface Role {
|
|
10
12
|
id: string;
|