vg-x07df 0.1.0

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.
Files changed (140) hide show
  1. package/.azure-pipelines/publish-public.yml +37 -0
  2. package/.azure-pipelines/publish.yml +39 -0
  3. package/.changeset/README.md +8 -0
  4. package/.changeset/config.json +11 -0
  5. package/AUTO_JOIN_GUIDE.md +411 -0
  6. package/README.md +215 -0
  7. package/Screenshot 2025-09-24 at 14.34.48.png +0 -0
  8. package/Screenshot 2025-10-04 at 12.58.54.png +0 -0
  9. package/biome.json +48 -0
  10. package/examples/demo/.env.example +19 -0
  11. package/examples/demo/CHANGELOG.md +22 -0
  12. package/examples/demo/README.md +72 -0
  13. package/examples/demo/eslint.config.js +23 -0
  14. package/examples/demo/index.html +13 -0
  15. package/examples/demo/package.json +34 -0
  16. package/examples/demo/pnpm-lock.yaml +2098 -0
  17. package/examples/demo/pnpm-workspace.yaml +1 -0
  18. package/examples/demo/public/vite.svg +1 -0
  19. package/examples/demo/src/App.css +52 -0
  20. package/examples/demo/src/App.tsx +176 -0
  21. package/examples/demo/src/assets/react.svg +1 -0
  22. package/examples/demo/src/components/auth/LoginForm.css +144 -0
  23. package/examples/demo/src/components/auth/LoginForm.tsx +80 -0
  24. package/examples/demo/src/components/calling/AutoJoinSettings.tsx +213 -0
  25. package/examples/demo/src/components/calling/AutoJoinStatus.tsx +72 -0
  26. package/examples/demo/src/components/calling/CallInitiator.css +258 -0
  27. package/examples/demo/src/components/calling/CallInitiator.tsx +142 -0
  28. package/examples/demo/src/components/calling/CallNotifications.css +119 -0
  29. package/examples/demo/src/components/calling/CallNotifications.tsx +108 -0
  30. package/examples/demo/src/components/calling/IncomingCallModal.css +192 -0
  31. package/examples/demo/src/components/calling/IncomingCallModal.tsx +78 -0
  32. package/examples/demo/src/components/calling/MinimizedCall.css +156 -0
  33. package/examples/demo/src/components/calling/MinimizedCall.tsx +78 -0
  34. package/examples/demo/src/components/conference/ConferenceHeader.css +265 -0
  35. package/examples/demo/src/components/conference/ConferenceHeader.tsx +78 -0
  36. package/examples/demo/src/components/conference/EnhancedControlBar.css +356 -0
  37. package/examples/demo/src/components/conference/EnhancedControlBar.tsx +262 -0
  38. package/examples/demo/src/components/conference/PaginationControls.css +67 -0
  39. package/examples/demo/src/components/conference/PaginationControls.tsx +64 -0
  40. package/examples/demo/src/components/conference/ParticipantGrid.css +153 -0
  41. package/examples/demo/src/components/conference/ParticipantGrid.tsx +87 -0
  42. package/examples/demo/src/components/conference/ParticipantTile.css +210 -0
  43. package/examples/demo/src/components/conference/ParticipantTile.tsx +114 -0
  44. package/examples/demo/src/components/conference/VideoConference.css +214 -0
  45. package/examples/demo/src/components/conference/VideoConference.tsx +93 -0
  46. package/examples/demo/src/contexts/AuthContext.tsx +105 -0
  47. package/examples/demo/src/hooks/useAuth.ts +5 -0
  48. package/examples/demo/src/hooks/useCallTimer.ts +42 -0
  49. package/examples/demo/src/index.css +68 -0
  50. package/examples/demo/src/main.tsx +10 -0
  51. package/examples/demo/src/services/auth.service.ts +153 -0
  52. package/examples/demo/src/types/auth.types.ts +31 -0
  53. package/examples/demo/tsconfig.app.json +28 -0
  54. package/examples/demo/tsconfig.json +7 -0
  55. package/examples/demo/tsconfig.node.json +26 -0
  56. package/examples/demo/vite.config.ts +15 -0
  57. package/images/callpad-without-ai.png +0 -0
  58. package/package.json +28 -0
  59. package/packages/sdk/CHANGELOG.md +33 -0
  60. package/packages/sdk/LICENSE +21 -0
  61. package/packages/sdk/README.md +97 -0
  62. package/packages/sdk/documentation.md +1132 -0
  63. package/packages/sdk/openapi-ts.config.ts +7 -0
  64. package/packages/sdk/package.json +88 -0
  65. package/packages/sdk/src/core/auth.manager.ts +52 -0
  66. package/packages/sdk/src/core/events/event-bus.ts +301 -0
  67. package/packages/sdk/src/core/events/index.ts +8 -0
  68. package/packages/sdk/src/core/events/types.ts +165 -0
  69. package/packages/sdk/src/core/index.ts +3 -0
  70. package/packages/sdk/src/core/signal/api.config.ts +49 -0
  71. package/packages/sdk/src/core/signal/index.ts +16 -0
  72. package/packages/sdk/src/core/signal/signal.client.ts +101 -0
  73. package/packages/sdk/src/core/signal/types.ts +110 -0
  74. package/packages/sdk/src/core/socketio/handlers/base.handler.ts +212 -0
  75. package/packages/sdk/src/core/socketio/handlers/call-accepted.handler.ts +34 -0
  76. package/packages/sdk/src/core/socketio/handlers/call-canceled.handler.ts +34 -0
  77. package/packages/sdk/src/core/socketio/handlers/call-declined.handler.ts +29 -0
  78. package/packages/sdk/src/core/socketio/handlers/call-ended.handler.ts +40 -0
  79. package/packages/sdk/src/core/socketio/handlers/call-incoming.handler.ts +72 -0
  80. package/packages/sdk/src/core/socketio/handlers/call-join-info.handler.ts +181 -0
  81. package/packages/sdk/src/core/socketio/handlers/call-participant-joined.handler.ts +42 -0
  82. package/packages/sdk/src/core/socketio/handlers/call-participant-joining.handler.ts +42 -0
  83. package/packages/sdk/src/core/socketio/handlers/call-timeout.handler.ts +31 -0
  84. package/packages/sdk/src/core/socketio/handlers/handler.registry.ts +62 -0
  85. package/packages/sdk/src/core/socketio/handlers/index.ts +21 -0
  86. package/packages/sdk/src/core/socketio/handlers/participant-left.handler.ts +37 -0
  87. package/packages/sdk/src/core/socketio/handlers/schema.ts +130 -0
  88. package/packages/sdk/src/core/socketio/index.ts +5 -0
  89. package/packages/sdk/src/core/socketio/socket.manager.ts +187 -0
  90. package/packages/sdk/src/core/socketio/types.ts +14 -0
  91. package/packages/sdk/src/core/types.ts +23 -0
  92. package/packages/sdk/src/generated/api/core/ApiError.ts +21 -0
  93. package/packages/sdk/src/generated/api/core/ApiRequestOptions.ts +13 -0
  94. package/packages/sdk/src/generated/api/core/ApiResult.ts +7 -0
  95. package/packages/sdk/src/generated/api/core/CancelablePromise.ts +126 -0
  96. package/packages/sdk/src/generated/api/core/OpenAPI.ts +55 -0
  97. package/packages/sdk/src/generated/api/core/request.ts +339 -0
  98. package/packages/sdk/src/generated/api/index.ts +5 -0
  99. package/packages/sdk/src/generated/api/models.ts +219 -0
  100. package/packages/sdk/src/generated/api/services.ts +225 -0
  101. package/packages/sdk/src/hooks/index.ts +21 -0
  102. package/packages/sdk/src/hooks/useAutoJoin.ts +66 -0
  103. package/packages/sdk/src/hooks/useCallActions.ts +28 -0
  104. package/packages/sdk/src/hooks/useCallQuality.ts +416 -0
  105. package/packages/sdk/src/hooks/useCallState.ts +23 -0
  106. package/packages/sdk/src/hooks/useConnection.ts +15 -0
  107. package/packages/sdk/src/hooks/useDevices.ts +296 -0
  108. package/packages/sdk/src/hooks/useErrorRecovery.ts +299 -0
  109. package/packages/sdk/src/hooks/useErrors.ts +84 -0
  110. package/packages/sdk/src/hooks/useEvent.ts +188 -0
  111. package/packages/sdk/src/hooks/useMediaControls.ts +215 -0
  112. package/packages/sdk/src/hooks/useParticipantStatus.ts +318 -0
  113. package/packages/sdk/src/hooks/useParticipants.ts +111 -0
  114. package/packages/sdk/src/index.ts +66 -0
  115. package/packages/sdk/src/livekit/constants.ts +76 -0
  116. package/packages/sdk/src/livekit/device.manager.ts +172 -0
  117. package/packages/sdk/src/livekit/error-classifier.ts +155 -0
  118. package/packages/sdk/src/livekit/events/eventBridge.ts +371 -0
  119. package/packages/sdk/src/livekit/events/trackRegistry.ts +114 -0
  120. package/packages/sdk/src/livekit/index.ts +49 -0
  121. package/packages/sdk/src/livekit/livekit.service.ts +110 -0
  122. package/packages/sdk/src/livekit/media.controls.ts +315 -0
  123. package/packages/sdk/src/livekit/room.manager.ts +79 -0
  124. package/packages/sdk/src/livekit/track.utils.ts +230 -0
  125. package/packages/sdk/src/livekit/types.ts +135 -0
  126. package/packages/sdk/src/provider/RtcProvider.tsx +78 -0
  127. package/packages/sdk/src/services/call-actions.ts +260 -0
  128. package/packages/sdk/src/services/error-recovery.ts +461 -0
  129. package/packages/sdk/src/services/index.ts +2 -0
  130. package/packages/sdk/src/services/sdk-builder.ts +104 -0
  131. package/packages/sdk/src/state/errors.ts +163 -0
  132. package/packages/sdk/src/state/selectors.ts +28 -0
  133. package/packages/sdk/src/state/store.ts +36 -0
  134. package/packages/sdk/src/state/types.ts +151 -0
  135. package/packages/sdk/src/utils/logger.ts +183 -0
  136. package/packages/sdk/tsconfig.json +49 -0
  137. package/packages/sdk/tsup.config.ts +51 -0
  138. package/pnpm-workspace.yaml +4 -0
  139. package/tsconfig.base.json +19 -0
  140. package/turbo.json +34 -0
@@ -0,0 +1,461 @@
1
+ /**
2
+ * Enhanced Error Recovery Service
3
+ *
4
+ * Implements automatic error recovery and retry mechanisms for the SDK.
5
+ * Handles network reconnection, failed participant invitations, and media failures.
6
+ */
7
+
8
+ import { SdkEventType, eventBus } from "../core/events";
9
+ import { rtcStore } from "../state/store";
10
+ import type { RtcError } from "../state/types";
11
+ import { createLogger } from "../utils/logger";
12
+
13
+ const logger = createLogger("services:error-recovery");
14
+
15
+ export interface ErrorRecoveryConfig {
16
+ maxRetries: number;
17
+ retryDelay: number;
18
+ exponentialBackoff: boolean;
19
+ recoverableErrors: string[];
20
+ }
21
+
22
+ export interface RetryContext {
23
+ attempts: number;
24
+ lastAttempt: number;
25
+ error: RtcError;
26
+ config: ErrorRecoveryConfig;
27
+ }
28
+
29
+ /**
30
+ * Default error recovery configuration
31
+ */
32
+ export const DEFAULT_ERROR_RECOVERY_CONFIG: ErrorRecoveryConfig = {
33
+ maxRetries: 3,
34
+ retryDelay: 1000, // 1 second
35
+ exponentialBackoff: true,
36
+ recoverableErrors: [
37
+ "NETWORK_ERROR",
38
+ "CONNECTION_LOST",
39
+ "SOCKET_DISCONNECTED",
40
+ "LIVEKIT_CONNECTION_FAILED",
41
+ "MEDIA_PERMISSION_DENIED",
42
+ "DEVICE_SWITCH_FAILED",
43
+ "PARTICIPANT_INVITATION_FAILED",
44
+ ],
45
+ };
46
+
47
+ /**
48
+ * Error Recovery Service
49
+ */
50
+ export class ErrorRecoveryService {
51
+ private config: ErrorRecoveryConfig;
52
+ private activeRetries = new Map<string, RetryContext>();
53
+ private reconnectionTimer: number | null = null;
54
+ private isRecovering = false;
55
+
56
+ constructor(config: ErrorRecoveryConfig = DEFAULT_ERROR_RECOVERY_CONFIG) {
57
+ this.config = config;
58
+ this.setupEventListeners();
59
+ }
60
+
61
+ private setupEventListeners(): void {
62
+ // Listen for errors that might need recovery
63
+ eventBus.on(SdkEventType.ERROR_OCCURRED, (event) => {
64
+ this.handleError(event.payload);
65
+ });
66
+
67
+ // Listen for connection quality changes
68
+ eventBus.on(SdkEventType.CONNECTION_QUALITY_CHANGED, (event) => {
69
+ if (event.payload.quality === "lost") {
70
+ this.handleConnectionLoss(event.payload.participantId);
71
+ }
72
+ });
73
+
74
+ // Listen for participant left events (might indicate network issues)
75
+ eventBus.on(SdkEventType.PARTICIPANT_LEFT, (event) => {
76
+ if (event.payload.reason === "error") {
77
+ this.handleParticipantError(event.payload.participantId);
78
+ }
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Handle error and determine if recovery should be attempted
84
+ */
85
+ private async handleError(error: RtcError): Promise<void> {
86
+ logger.debug("Handling error for recovery", { error });
87
+
88
+ // Check if error is recoverable
89
+ if (!this.isRecoverableError(error)) {
90
+ logger.debug("Error is not recoverable", { code: error.code });
91
+ return;
92
+ }
93
+
94
+ // Get or create retry context
95
+ const retryKey = this.getRetryKey(error);
96
+ let context = this.activeRetries.get(retryKey);
97
+
98
+ if (!context) {
99
+ context = {
100
+ attempts: 0,
101
+ lastAttempt: 0,
102
+ error,
103
+ config: this.config,
104
+ };
105
+ this.activeRetries.set(retryKey, context);
106
+ }
107
+
108
+ // Check if we've exceeded max retries
109
+ if (context.attempts >= this.config.maxRetries) {
110
+ logger.warn("Max retries exceeded for error", {
111
+ code: error.code,
112
+ attempts: context.attempts,
113
+ });
114
+ this.activeRetries.delete(retryKey);
115
+ this.emitRecoveryFailed(error, context.attempts);
116
+ return;
117
+ }
118
+
119
+ // Calculate delay with exponential backoff
120
+ const delay = this.calculateRetryDelay(context.attempts);
121
+ const now = Date.now();
122
+
123
+ // Ensure minimum delay between attempts
124
+ if (now - context.lastAttempt < delay) {
125
+ setTimeout(
126
+ () => this.attemptRecovery(retryKey),
127
+ delay - (now - context.lastAttempt)
128
+ );
129
+ return;
130
+ }
131
+
132
+ await this.attemptRecovery(retryKey);
133
+ }
134
+
135
+ /**
136
+ * Attempt to recover from the error
137
+ */
138
+ private async attemptRecovery(retryKey: string): Promise<void> {
139
+ const context = this.activeRetries.get(retryKey);
140
+ if (!context) return;
141
+
142
+ context.attempts++;
143
+ context.lastAttempt = Date.now();
144
+
145
+ logger.info("Attempting error recovery", {
146
+ code: context.error.code,
147
+ attempt: context.attempts,
148
+ maxRetries: this.config.maxRetries,
149
+ });
150
+
151
+ this.emitRecoveryAttempt(context.error, context.attempts);
152
+
153
+ try {
154
+ const success = await this.executeRecoveryStrategy(context.error);
155
+
156
+ if (success) {
157
+ logger.info("Error recovery successful", {
158
+ code: context.error.code,
159
+ attempts: context.attempts,
160
+ });
161
+ this.activeRetries.delete(retryKey);
162
+ this.emitRecoverySuccess(context.error, context.attempts);
163
+ } else {
164
+ // Recovery failed, will retry later
165
+ logger.debug("Recovery attempt failed", {
166
+ code: context.error.code,
167
+ attempts: context.attempts,
168
+ });
169
+ }
170
+ } catch (recoveryError) {
171
+ logger.error("Recovery attempt threw error", {
172
+ originalError: context.error.code,
173
+ recoveryError,
174
+ attempts: context.attempts,
175
+ });
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Execute the appropriate recovery strategy based on error type
181
+ */
182
+ private async executeRecoveryStrategy(error: RtcError): Promise<boolean> {
183
+ switch (error.code) {
184
+ case "NETWORK_ERROR":
185
+ case "CONNECTION_LOST":
186
+ case "SOCKET_DISCONNECTED":
187
+ return this.recoverNetworkConnection();
188
+
189
+ case "LIVEKIT_CONNECTION_FAILED":
190
+ return this.recoverLivekitConnection();
191
+
192
+ case "MEDIA_PERMISSION_DENIED":
193
+ return this.recoverMediaPermission(error);
194
+
195
+ case "DEVICE_SWITCH_FAILED":
196
+ return this.recoverDeviceSwitch(error);
197
+
198
+ case "PARTICIPANT_INVITATION_FAILED":
199
+ return this.recoverParticipantInvitation(error);
200
+
201
+ default:
202
+ logger.warn("No recovery strategy for error", { code: error.code });
203
+ return false;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Recovery strategies
209
+ */
210
+ private async recoverNetworkConnection(): Promise<boolean> {
211
+ try {
212
+ // Check if we're already recovering to avoid duplicate attempts
213
+ if (this.isRecovering) {
214
+ return false;
215
+ }
216
+
217
+ this.isRecovering = true;
218
+
219
+ // Get SDK instance from store
220
+ const store = rtcStore.getState();
221
+
222
+ // Try to reconnect socket
223
+ if (store.connection && !store.connection.connected) {
224
+ logger.debug("Attempting socket reconnection");
225
+
226
+ // The socket manager should handle reconnection automatically
227
+ // We just wait a bit and check if it succeeded
228
+ await this.delay(2000);
229
+
230
+ const newState = rtcStore.getState();
231
+ const success = newState.connection.connected;
232
+
233
+ if (success) {
234
+ logger.info("Network connection recovered");
235
+ return true;
236
+ }
237
+ }
238
+
239
+ return false;
240
+ } catch (error) {
241
+ logger.error("Network recovery failed", { error });
242
+ return false;
243
+ } finally {
244
+ this.isRecovering = false;
245
+ }
246
+ }
247
+
248
+ private async recoverLivekitConnection(): Promise<boolean> {
249
+ try {
250
+ // For LiveKit connection recovery, we'd need access to the SDK instance
251
+ // This would typically involve re-establishing the LiveKit room connection
252
+ logger.debug("Attempting LiveKit connection recovery");
253
+
254
+ // This is a placeholder - actual implementation would need SDK access
255
+ // to call something like sdk.livekit.reconnect()
256
+
257
+ return false; // Placeholder
258
+ } catch (error) {
259
+ logger.error("LiveKit recovery failed", { error });
260
+ return false;
261
+ }
262
+ }
263
+
264
+ private async recoverMediaPermission(error: RtcError): Promise<boolean> {
265
+ try {
266
+ logger.debug("Attempting media permission recovery");
267
+
268
+ // For media permission recovery, we could try requesting permissions again
269
+ // or gracefully degrade to audio-only mode
270
+
271
+ const context = error.context;
272
+ if (context?.device === "camera") {
273
+ // Try to gracefully degrade to audio-only
274
+ rtcStore.getState().patch((state) => {
275
+ state.local.videoEnabled = false;
276
+ });
277
+
278
+ // Emit event about graceful degradation
279
+ eventBus.emit(
280
+ SdkEventType.MEDIA_DISABLED,
281
+ {
282
+ participantId: "local",
283
+ mediaType: "video",
284
+ timestamp: Date.now(),
285
+ },
286
+ "user"
287
+ );
288
+
289
+ logger.info(
290
+ "Gracefully degraded to audio-only due to camera permission"
291
+ );
292
+ return true;
293
+ }
294
+
295
+ return false;
296
+ } catch (error) {
297
+ logger.error("Media permission recovery failed", { error });
298
+ return false;
299
+ }
300
+ }
301
+
302
+ private async recoverDeviceSwitch(error: RtcError): Promise<boolean> {
303
+ try {
304
+ logger.debug("Attempting device switch recovery");
305
+
306
+ // For device switch recovery, we could try falling back to default device
307
+ // This would need access to the device manager
308
+
309
+ return false; // Placeholder
310
+ } catch (error) {
311
+ logger.error("Device switch recovery failed", { error });
312
+ return false;
313
+ }
314
+ }
315
+
316
+ private async recoverParticipantInvitation(
317
+ error: RtcError
318
+ ): Promise<boolean> {
319
+ try {
320
+ logger.debug("Attempting participant invitation recovery");
321
+
322
+ // For participant invitation recovery, we could retry the invitation
323
+ // This would need access to the call actions service
324
+
325
+ return false; // Placeholder
326
+ } catch (error) {
327
+ logger.error("Participant invitation recovery failed", { error });
328
+ return false;
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Handle connection loss for a specific participant
334
+ */
335
+ private handleConnectionLoss(participantId: string): void {
336
+ logger.debug("Handling connection loss", { participantId });
337
+
338
+ // For local participant connection loss, trigger network recovery
339
+ if (participantId === "local") {
340
+ const networkError: RtcError = {
341
+ code: "CONNECTION_LOST",
342
+ message: "Local participant connection lost",
343
+ timestamp: Date.now(),
344
+ context: { participantId },
345
+ };
346
+
347
+ this.handleError(networkError);
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Handle participant error
353
+ */
354
+ private handleParticipantError(participantId: string): void {
355
+ logger.debug("Handling participant error", { participantId });
356
+
357
+ // Could implement participant re-invitation logic here
358
+ }
359
+
360
+ /**
361
+ * Utility methods
362
+ */
363
+ private isRecoverableError(error: RtcError): boolean {
364
+ return this.config.recoverableErrors.includes(error.code);
365
+ }
366
+
367
+ private getRetryKey(error: RtcError): string {
368
+ // Create a unique key for this error type and context
369
+ return `${error.code}-${JSON.stringify(error.context || {})}`;
370
+ }
371
+
372
+ private calculateRetryDelay(attempts: number): number {
373
+ if (!this.config.exponentialBackoff) {
374
+ return this.config.retryDelay;
375
+ }
376
+
377
+ // Exponential backoff: delay * (2 ^ attempts)
378
+ return this.config.retryDelay * 2 ** attempts;
379
+ }
380
+
381
+ private delay(ms: number): Promise<void> {
382
+ return new Promise((resolve) => setTimeout(resolve, ms));
383
+ }
384
+
385
+ /**
386
+ * Event emission methods
387
+ */
388
+ private emitRecoveryAttempt(error: RtcError, attempts: number): void {
389
+ eventBus.emit(
390
+ "recovery:attempt",
391
+ {
392
+ error,
393
+ attempts,
394
+ timestamp: Date.now(),
395
+ },
396
+ "user"
397
+ );
398
+ }
399
+
400
+ private emitRecoverySuccess(error: RtcError, attempts: number): void {
401
+ eventBus.emit(
402
+ "recovery:success",
403
+ {
404
+ error,
405
+ attempts,
406
+ timestamp: Date.now(),
407
+ },
408
+ "user"
409
+ );
410
+ }
411
+
412
+ private emitRecoveryFailed(error: RtcError, attempts: number): void {
413
+ eventBus.emit(
414
+ "recovery:failed",
415
+ {
416
+ error,
417
+ attempts,
418
+ timestamp: Date.now(),
419
+ },
420
+ "user"
421
+ );
422
+ }
423
+
424
+ /**
425
+ * Public methods for managing recovery
426
+ */
427
+ public updateConfig(newConfig: Partial<ErrorRecoveryConfig>): void {
428
+ this.config = { ...this.config, ...newConfig };
429
+ logger.debug("Updated error recovery config", { config: this.config });
430
+ }
431
+
432
+ public getActiveRetries(): Map<string, RetryContext> {
433
+ return new Map(this.activeRetries);
434
+ }
435
+
436
+ public cancelRetry(retryKey: string): boolean {
437
+ const cancelled = this.activeRetries.delete(retryKey);
438
+ if (cancelled) {
439
+ logger.debug("Cancelled retry", { retryKey });
440
+ }
441
+ return cancelled;
442
+ }
443
+
444
+ public cancelAllRetries(): void {
445
+ const count = this.activeRetries.size;
446
+ this.activeRetries.clear();
447
+ logger.debug("Cancelled all retries", { count });
448
+ }
449
+
450
+ public destroy(): void {
451
+ this.cancelAllRetries();
452
+ if (this.reconnectionTimer) {
453
+ clearTimeout(this.reconnectionTimer);
454
+ this.reconnectionTimer = null;
455
+ }
456
+ logger.debug("Error recovery service destroyed");
457
+ }
458
+ }
459
+
460
+ // Global error recovery service instance
461
+ export const errorRecoveryService = new ErrorRecoveryService();
@@ -0,0 +1,2 @@
1
+ export * from "./sdk-builder";
2
+ export * from "./call-actions";
@@ -0,0 +1,104 @@
1
+ import { AuthManager, SocketManager } from "../core";
2
+ import { type ApiConfig, SignalClient, apiConfig } from "../core/signal";
3
+ import { type AutoJoinConfig } from "../core/types";
4
+ import { LiveKitService } from "../livekit";
5
+ import { rtcStore } from "../state/store";
6
+ import { type LogLevel, setGlobalLoggerOptions } from "../utils/logger";
7
+ import { type CallActions, createCallActions } from "./call-actions";
8
+
9
+ export interface SdkBuildOptions {
10
+ appId: string;
11
+ signalHost: string;
12
+ authProvider: () => string | null;
13
+
14
+ // Logging configuration
15
+ logLevel?: LogLevel;
16
+ enableDebug?: boolean;
17
+
18
+ // Custom log callback
19
+ log?: (level: LogLevel, message: string, meta?: any) => void;
20
+
21
+ // Auto-join configuration
22
+ autoJoin?: Partial<AutoJoinConfig>;
23
+ }
24
+
25
+ export interface RtcSdk extends CallActions {
26
+ store: typeof rtcStore;
27
+ auth: AuthManager;
28
+ socket: SocketManager;
29
+ signal: SignalClient;
30
+ livekit: LiveKitService;
31
+ autoJoinConfig: AutoJoinConfig;
32
+ cleanup: () => void;
33
+
34
+ configureApi: (config: ApiConfig) => void;
35
+ }
36
+
37
+ // Default auto-join configuration
38
+ const DEFAULT_AUTO_JOIN_CONFIG: AutoJoinConfig = {
39
+ enabled: true, // Everyone auto-joins by default
40
+ retryOnFailure: true,
41
+ maxRetries: 2,
42
+ };
43
+
44
+ export function buildSdk(opts: SdkBuildOptions): RtcSdk {
45
+ // Configure global logging system
46
+ const loggerOptions: any = {};
47
+ if (opts.logLevel !== undefined) {
48
+ loggerOptions.level = opts.logLevel;
49
+ }
50
+ if (opts.enableDebug !== undefined) {
51
+ loggerOptions.enableDebug = opts.enableDebug;
52
+ }
53
+ if (opts.log !== undefined) {
54
+ loggerOptions.customLogger = opts.log;
55
+ }
56
+ setGlobalLoggerOptions(loggerOptions);
57
+
58
+ // Merge auto-join configuration with defaults
59
+ const autoJoinConfig: AutoJoinConfig = {
60
+ ...DEFAULT_AUTO_JOIN_CONFIG,
61
+ ...opts.autoJoin,
62
+ };
63
+
64
+ // Initialize core managers
65
+ const auth = new AuthManager(opts.authProvider);
66
+ const socket = SocketManager.getInstance();
67
+ const signal = new SignalClient({
68
+ baseUrl: opts.signalHost,
69
+ appId: opts.appId,
70
+ authManager: auth,
71
+ });
72
+
73
+ const livekit = new LiveKitService({
74
+ log: opts.log,
75
+ });
76
+
77
+ const callActions = createCallActions(signal, auth, livekit);
78
+
79
+ // Socket now handles events directly - no event bridge needed
80
+
81
+ const cleanup = () => {
82
+ socket.destroy();
83
+ livekit.destroy();
84
+ rtcStore.getState().reset();
85
+ };
86
+
87
+
88
+ return {
89
+ store: rtcStore,
90
+ auth,
91
+ socket,
92
+ signal,
93
+ livekit,
94
+ autoJoinConfig,
95
+ ...callActions,
96
+ cleanup,
97
+
98
+ // API configuration
99
+ configureApi: (config: ApiConfig) => {
100
+ apiConfig.configure(config);
101
+ },
102
+
103
+ };
104
+ }