whio-api-sdk 1.0.197-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.
@@ -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,2 @@
1
+ // WebSocket related types
2
+ export {};
@@ -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.197-beta-staging",
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
  }
@@ -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';