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.
@@ -0,0 +1,50 @@
1
+ export interface AudioChunkPayload {
2
+ sessionId: string;
3
+ audioChunk: string;
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,2 @@
1
+ // WebSocket related types
2
+ export {};
@@ -0,0 +1,203 @@
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 base64 audio data (in a real app, this would come from your audio processing)
76
+ const simulateBase64AudioChunks = (): string[] => {
77
+ const chunks: string[] = [];
78
+ for (let i = 0; i < 15; i++) {
79
+ // Simulate different sized base64 chunks (this would be your actual audio data)
80
+ const mockData = `mock-audio-chunk-${i}-${Date.now()}`;
81
+ const base64Chunk = btoa(mockData);
82
+ chunks.push(base64Chunk);
83
+ }
84
+ return chunks;
85
+ };
86
+
87
+ const audioChunks = simulateBase64AudioChunks();
88
+
89
+ // Stream audio chunks one by one
90
+ for (let i = 0; i < audioChunks.length; i++) {
91
+ const isLastChunk = i === audioChunks.length - 1;
92
+
93
+ console.log(`Streaming chunk ${i + 1}/${audioChunks.length}`);
94
+ sdk.streamAudioChunk(session.id, audioChunks[i], isLastChunk);
95
+
96
+ // Small delay between chunks
97
+ await new Promise(resolve => setTimeout(resolve, 200));
98
+ }
99
+
100
+ console.log('Finished streaming audio chunks');
101
+
102
+ // Alternative: Stream all chunks at once
103
+ // await sdk.streamAudioChunks(session.id, audioChunks);
104
+
105
+ // Keep the connection alive for a bit to see the responses
106
+ await new Promise(resolve => setTimeout(resolve, 5000));
107
+
108
+ // Clean up
109
+ sdk.disconnectWebSocket();
110
+ console.log('WebSocket disconnected');
111
+ }
112
+
113
+ // Example of handling microphone input (browser environment)
114
+ async function exampleMicrophoneStreaming() {
115
+ const sdk = new ApiSDK({
116
+ baseUrl: 'http://localhost:3000/api',
117
+ storage: {
118
+ getItem: (key: string) => localStorage.getItem(key),
119
+ setItem: (key: string, value: string) => localStorage.setItem(key, value),
120
+ removeItem: (key: string) => localStorage.removeItem(key)
121
+ }
122
+ });
123
+
124
+ // Authenticate and create session
125
+ await sdk.login('user@example.com', 'password');
126
+ const session = await sdk.createSession({
127
+ sessionName: 'Microphone Streaming',
128
+ transcript: ''
129
+ });
130
+
131
+ // Set up WebSocket events
132
+ sdk.onWebSocketEvent('connected', (data) => {
133
+ console.log('Ready for microphone streaming');
134
+ });
135
+
136
+ sdk.onWebSocketEvent('audio-chunk-received', (data) => {
137
+ console.log(`Buffer count: ${data.chunkCount}`);
138
+ });
139
+
140
+ sdk.onWebSocketEvent('transcription-queued', (data) => {
141
+ console.log('Transcription started for session:', data.sessionId);
142
+ });
143
+
144
+ // Wait for connection
145
+ while (!sdk.isWebSocketConnected()) {
146
+ await new Promise(resolve => setTimeout(resolve, 100));
147
+ }
148
+
149
+ // Get microphone access
150
+ try {
151
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
152
+ const audioContext = new AudioContext();
153
+ const source = audioContext.createMediaStreamSource(stream);
154
+ const processor = audioContext.createScriptProcessor(4096, 1, 1);
155
+
156
+ processor.onaudioprocess = (event) => {
157
+ const inputBuffer = event.inputBuffer;
158
+ const inputData = inputBuffer.getChannelData(0);
159
+
160
+ // Convert Float32Array to base64 (you'll need your own conversion logic)
161
+ // This is just a placeholder - replace with your actual conversion
162
+ const mockBase64 = btoa(String.fromCharCode(...Array.from(inputData.slice(0, 100))));
163
+
164
+ // Stream the base64 audio chunk
165
+ sdk.streamAudioChunk(session.id, mockBase64, false);
166
+ };
167
+
168
+ source.connect(processor);
169
+ processor.connect(audioContext.destination);
170
+
171
+ console.log('Microphone streaming started. Recording for 10 seconds...');
172
+
173
+ // Stop after 10 seconds and trigger transcription
174
+ setTimeout(() => {
175
+ // Send empty base64 string with end flag to trigger transcription
176
+ sdk.streamAudioChunk(session.id, "", true);
177
+
178
+ // Clean up
179
+ processor.disconnect();
180
+ source.disconnect();
181
+ stream.getTracks().forEach(track => track.stop());
182
+
183
+ console.log('Recording stopped, transcription queued');
184
+ }, 10000);
185
+
186
+ } catch (error) {
187
+ console.error('Error accessing microphone:', error);
188
+ }
189
+ }
190
+
191
+ // Run the example
192
+ if (typeof window !== 'undefined') {
193
+ // Browser environment
194
+ console.log('Browser environment detected');
195
+ // Uncomment to run microphone example
196
+ // exampleMicrophoneStreaming();
197
+ } else {
198
+ // Node.js environment
199
+ console.log('Node.js environment detected');
200
+ exampleWebSocketAudioStreaming();
201
+ }
202
+
203
+ export { exampleWebSocketAudioStreaming, exampleMicrophoneStreaming };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whio-api-sdk",
3
- "version": "1.0.197-beta-staging",
3
+ "version": "1.0.199-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,324 @@
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: string,
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: string[],
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 delay = options.bufferSize || 100; // ms between chunks
145
+
146
+ for (let i = 0; i < audioChunks.length; i++) {
147
+ const isLastChunk = i === audioChunks.length - 1;
148
+
149
+ this.streamAudioChunk(sessionId, audioChunks[i], isLastChunk);
150
+
151
+ // Small delay between chunks to prevent overwhelming the server
152
+ if (!isLastChunk) {
153
+ await new Promise(resolve => setTimeout(resolve, delay));
154
+ }
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Add event listener
160
+ */
161
+ public on<K extends keyof WebSocketEvents>(
162
+ event: K,
163
+ handler: WebSocketEvents[K]
164
+ ): void {
165
+ if (!this.eventHandlers.has(event)) {
166
+ this.eventHandlers.set(event, []);
167
+ }
168
+ this.eventHandlers.get(event)!.push(handler);
169
+ }
170
+
171
+ /**
172
+ * Remove event listener
173
+ */
174
+ public off<K extends keyof WebSocketEvents>(
175
+ event: K,
176
+ handler: WebSocketEvents[K]
177
+ ): void {
178
+ const handlers = this.eventHandlers.get(event);
179
+ if (handlers) {
180
+ const index = handlers.indexOf(handler);
181
+ if (index > -1) {
182
+ handlers.splice(index, 1);
183
+ }
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Emit event to registered handlers
189
+ */
190
+ private emit<K extends keyof WebSocketEvents>(
191
+ event: K,
192
+ ...args: Parameters<WebSocketEvents[K]>
193
+ ): void {
194
+ const handlers = this.eventHandlers.get(event);
195
+ if (handlers) {
196
+ handlers.forEach(handler => {
197
+ try {
198
+ (handler as Function)(...args);
199
+ } catch (error) {
200
+ console.error(`Error in WebSocket event handler for ${event}:`, error);
201
+ }
202
+ });
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Setup WebSocket event handlers
208
+ */
209
+ private setupEventHandlers(): void {
210
+ if (!this.socket) return;
211
+
212
+ // Connection events
213
+ this.socket.on('connect', () => {
214
+ this.connectionStats.isConnected = true;
215
+ this.connectionStats.lastConnectedAt = new Date();
216
+ this.reconnectAttempts = 0;
217
+
218
+ if (this.reconnectTimeout) {
219
+ clearTimeout(this.reconnectTimeout);
220
+ this.reconnectTimeout = null;
221
+ }
222
+ });
223
+
224
+ this.socket.on('disconnect', (reason: string) => {
225
+ this.connectionStats.isConnected = false;
226
+ this.connectionStats.lastDisconnectedAt = new Date();
227
+
228
+ this.emit('disconnected', reason);
229
+
230
+ // Auto-reconnect logic
231
+ if (this.shouldReconnect(reason)) {
232
+ this.scheduleReconnect();
233
+ }
234
+ });
235
+
236
+ this.socket.on('connect_error', (error: Error) => {
237
+ this.emit('connection-error', error);
238
+
239
+ if (this.shouldReconnect()) {
240
+ this.scheduleReconnect();
241
+ }
242
+ });
243
+
244
+ // Application-specific events
245
+ this.socket.on('connected', (data: any) => {
246
+ this.emit('connected', data);
247
+ });
248
+
249
+ this.socket.on('audio-chunk-received', (data: any) => {
250
+ this.emit('audio-chunk-received', data);
251
+ });
252
+
253
+ this.socket.on('transcription-queued', (data: any) => {
254
+ this.emit('transcription-queued', data);
255
+ });
256
+
257
+ this.socket.on('audio-error', (error: any) => {
258
+ this.emit('audio-error', error);
259
+ });
260
+
261
+ this.socket.on('transcription-error', (error: any) => {
262
+ this.emit('transcription-error', error);
263
+ });
264
+ }
265
+
266
+ /**
267
+ * Determine if should attempt reconnection
268
+ */
269
+ private shouldReconnect(reason?: string): boolean {
270
+ // Don't reconnect if manually disconnected
271
+ if (reason === 'io client disconnect') {
272
+ return false;
273
+ }
274
+
275
+ // Check if we've exceeded max attempts
276
+ if (this.maxReconnectAttempts > 0 && this.reconnectAttempts >= this.maxReconnectAttempts) {
277
+ this.emit('reconnect-failed');
278
+ return false;
279
+ }
280
+
281
+ return true;
282
+ }
283
+
284
+ /**
285
+ * Schedule reconnection attempt
286
+ */
287
+ private scheduleReconnect(): void {
288
+ if (this.reconnectTimeout) {
289
+ clearTimeout(this.reconnectTimeout);
290
+ }
291
+
292
+ this.reconnectAttempts++;
293
+ this.connectionStats.reconnectAttempts = this.reconnectAttempts;
294
+
295
+ // Calculate exponential backoff delay
296
+ const delay = Math.min(
297
+ this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
298
+ this.maxReconnectDelay
299
+ );
300
+
301
+ this.emit('reconnecting', this.reconnectAttempts);
302
+
303
+ this.reconnectTimeout = setTimeout(() => {
304
+ this.connect();
305
+ }, delay);
306
+ }
307
+
308
+ /**
309
+ * Get WebSocket URL
310
+ */
311
+ private getWebSocketUrl(): string {
312
+ const wsProtocol = this.baseUrl.startsWith('https') ? 'wss' : 'ws';
313
+ const baseWsUrl = this.baseUrl.replace(/^https?/, wsProtocol);
314
+ return `${baseWsUrl}/${this.config.namespace}`;
315
+ }
316
+
317
+ /**
318
+ * Clean up resources
319
+ */
320
+ public destroy(): void {
321
+ this.disconnect();
322
+ this.eventHandlers.clear();
323
+ }
324
+ }
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
  }
@@ -1,4 +1,5 @@
1
1
  // Common types used across the SDK
2
+ import { WebSocketConfig } from './websocket.types';
2
3
 
3
4
  export interface SDKConfig {
4
5
  baseUrl?: string;
@@ -7,6 +8,7 @@ export interface SDKConfig {
7
8
  setItem: (key: string, value: string) => void | Promise<void>;
8
9
  removeItem: (key: string) => void | Promise<void>;
9
10
  };
11
+ websocket?: WebSocketConfig;
10
12
  }
11
13
 
12
14
  // Role type
@@ -11,3 +11,4 @@ export * from './audio.types';
11
11
  export * from './workflow.types';
12
12
  export * from './log.types';
13
13
  export * from './external-integration.types';
14
+ export * from './websocket.types';