@croacroa/react-native-template 2.0.1 → 3.2.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 (172) hide show
  1. package/.env.example +5 -0
  2. package/.eslintrc.js +8 -0
  3. package/.github/workflows/ci.yml +187 -187
  4. package/.github/workflows/eas-build.yml +55 -55
  5. package/.github/workflows/eas-update.yml +50 -50
  6. package/.github/workflows/npm-publish.yml +57 -0
  7. package/CHANGELOG.md +195 -106
  8. package/CONTRIBUTING.md +377 -377
  9. package/LICENSE +21 -0
  10. package/README.md +446 -399
  11. package/__tests__/accessibility/components.test.tsx +285 -0
  12. package/__tests__/components/Button.test.tsx +2 -4
  13. package/__tests__/components/__snapshots__/snapshots.test.tsx.snap +512 -0
  14. package/__tests__/components/snapshots.test.tsx +131 -131
  15. package/__tests__/helpers/a11y.ts +54 -0
  16. package/__tests__/hooks/useAnalytics.test.ts +100 -0
  17. package/__tests__/hooks/useAnimations.test.ts +70 -0
  18. package/__tests__/hooks/useAuth.test.tsx +71 -28
  19. package/__tests__/hooks/useMedia.test.ts +318 -0
  20. package/__tests__/hooks/usePayments.test.tsx +307 -0
  21. package/__tests__/hooks/usePermission.test.ts +230 -0
  22. package/__tests__/hooks/useWebSocket.test.ts +329 -0
  23. package/__tests__/integration/auth-api.test.tsx +224 -227
  24. package/__tests__/performance/VirtualizedList.perf.test.tsx +385 -362
  25. package/__tests__/services/api.test.ts +24 -6
  26. package/app/(auth)/home.tsx +11 -9
  27. package/app/(auth)/profile.tsx +8 -6
  28. package/app/(auth)/settings.tsx +11 -9
  29. package/app/(public)/forgot-password.tsx +25 -15
  30. package/app/(public)/login.tsx +48 -12
  31. package/app/(public)/onboarding.tsx +5 -5
  32. package/app/(public)/register.tsx +24 -15
  33. package/app/_layout.tsx +6 -3
  34. package/app.config.ts +27 -2
  35. package/assets/images/.gitkeep +7 -7
  36. package/assets/images/adaptive-icon.png +0 -0
  37. package/assets/images/favicon.png +0 -0
  38. package/assets/images/icon.png +0 -0
  39. package/assets/images/notification-icon.png +0 -0
  40. package/assets/images/splash.png +0 -0
  41. package/components/ErrorBoundary.tsx +73 -28
  42. package/components/auth/SocialLoginButtons.tsx +168 -0
  43. package/components/forms/FormInput.tsx +5 -3
  44. package/components/onboarding/OnboardingScreen.tsx +370 -370
  45. package/components/onboarding/index.ts +2 -2
  46. package/components/providers/AnalyticsProvider.tsx +67 -0
  47. package/components/providers/SuspenseBoundary.tsx +359 -357
  48. package/components/providers/index.ts +24 -21
  49. package/components/ui/AnimatedButton.tsx +1 -9
  50. package/components/ui/AnimatedList.tsx +98 -0
  51. package/components/ui/AnimatedScreen.tsx +89 -0
  52. package/components/ui/Avatar.tsx +319 -316
  53. package/components/ui/Badge.tsx +416 -416
  54. package/components/ui/BottomSheet.tsx +307 -307
  55. package/components/ui/Button.tsx +11 -3
  56. package/components/ui/Checkbox.tsx +261 -261
  57. package/components/ui/FeatureGate.tsx +57 -0
  58. package/components/ui/ForceUpdateScreen.tsx +108 -0
  59. package/components/ui/ImagePickerButton.tsx +180 -0
  60. package/components/ui/Input.stories.tsx +2 -10
  61. package/components/ui/Input.tsx +2 -10
  62. package/components/ui/OptimizedImage.tsx +369 -369
  63. package/components/ui/Paywall.tsx +253 -0
  64. package/components/ui/PermissionGate.tsx +155 -0
  65. package/components/ui/PurchaseButton.tsx +84 -0
  66. package/components/ui/Select.tsx +240 -240
  67. package/components/ui/Skeleton.tsx +3 -1
  68. package/components/ui/Toast.tsx +427 -0
  69. package/components/ui/UploadProgress.tsx +189 -0
  70. package/components/ui/VirtualizedList.tsx +288 -285
  71. package/components/ui/index.ts +28 -23
  72. package/constants/config.ts +135 -97
  73. package/docs/adr/001-state-management.md +79 -79
  74. package/docs/adr/002-styling-approach.md +130 -130
  75. package/docs/adr/003-data-fetching.md +155 -155
  76. package/docs/adr/004-auth-adapter-pattern.md +144 -144
  77. package/docs/adr/README.md +78 -78
  78. package/docs/guides/analytics-posthog.md +121 -0
  79. package/docs/guides/auth-supabase.md +162 -0
  80. package/docs/guides/feature-flags-launchdarkly.md +150 -0
  81. package/docs/guides/payments-revenuecat.md +169 -0
  82. package/docs/plans/2026-02-22-phase6-implementation.md +3222 -0
  83. package/docs/plans/2026-02-22-phase6-template-completion-design.md +196 -0
  84. package/docs/plans/2026-02-23-npm-publish-design.md +31 -0
  85. package/docs/plans/2026-02-23-phase7-polish-documentation-design.md +79 -0
  86. package/docs/plans/2026-02-23-phase8-additional-features-design.md +136 -0
  87. package/eas.json +2 -1
  88. package/hooks/index.ts +70 -27
  89. package/hooks/useAnimatedEntry.ts +204 -0
  90. package/hooks/useApi.ts +64 -4
  91. package/hooks/useAuth.tsx +7 -3
  92. package/hooks/useBiometrics.ts +295 -295
  93. package/hooks/useChannel.ts +111 -0
  94. package/hooks/useDeepLinking.ts +256 -256
  95. package/hooks/useExperiment.ts +36 -0
  96. package/hooks/useFeatureFlag.ts +59 -0
  97. package/hooks/useForceUpdate.ts +91 -0
  98. package/hooks/useImagePicker.ts +281 -0
  99. package/hooks/useInAppReview.ts +64 -0
  100. package/hooks/useMFA.ts +509 -499
  101. package/hooks/useParallax.ts +142 -0
  102. package/hooks/usePerformance.ts +434 -434
  103. package/hooks/usePermission.ts +190 -0
  104. package/hooks/usePresence.ts +129 -0
  105. package/hooks/useProducts.ts +36 -0
  106. package/hooks/usePurchase.ts +103 -0
  107. package/hooks/useRateLimit.ts +70 -0
  108. package/hooks/useSubscription.ts +49 -0
  109. package/hooks/useTrackEvent.ts +52 -0
  110. package/hooks/useTrackScreen.ts +40 -0
  111. package/hooks/useUpdates.ts +358 -358
  112. package/hooks/useUpload.ts +165 -0
  113. package/hooks/useWebSocket.ts +111 -0
  114. package/i18n/index.ts +197 -194
  115. package/i18n/locales/ar.json +170 -101
  116. package/i18n/locales/de.json +170 -101
  117. package/i18n/locales/en.json +170 -101
  118. package/i18n/locales/es.json +170 -101
  119. package/i18n/locales/fr.json +170 -101
  120. package/jest.config.js +1 -1
  121. package/maestro/README.md +113 -113
  122. package/maestro/config.yaml +35 -35
  123. package/maestro/flows/login.yaml +62 -62
  124. package/maestro/flows/mfa-login.yaml +92 -92
  125. package/maestro/flows/mfa-setup.yaml +86 -86
  126. package/maestro/flows/navigation.yaml +68 -68
  127. package/maestro/flows/offline-conflict.yaml +101 -101
  128. package/maestro/flows/offline-sync.yaml +128 -128
  129. package/maestro/flows/offline.yaml +60 -60
  130. package/maestro/flows/register.yaml +94 -94
  131. package/package.json +188 -175
  132. package/scripts/generate-placeholders.js +38 -0
  133. package/services/analytics/adapters/console.ts +50 -0
  134. package/services/analytics/analytics-adapter.ts +94 -0
  135. package/services/analytics/types.ts +73 -0
  136. package/services/analytics.ts +428 -428
  137. package/services/api.ts +419 -340
  138. package/services/auth/social/apple.ts +110 -0
  139. package/services/auth/social/google.ts +159 -0
  140. package/services/auth/social/social-auth.ts +100 -0
  141. package/services/auth/social/types.ts +80 -0
  142. package/services/authAdapter.ts +333 -333
  143. package/services/backgroundSync.ts +652 -626
  144. package/services/feature-flags/adapters/mock.ts +108 -0
  145. package/services/feature-flags/feature-flag-adapter.ts +174 -0
  146. package/services/feature-flags/types.ts +79 -0
  147. package/services/force-update.ts +140 -0
  148. package/services/index.ts +116 -54
  149. package/services/media/compression.ts +91 -0
  150. package/services/media/media-picker.ts +151 -0
  151. package/services/media/media-upload.ts +160 -0
  152. package/services/payments/adapters/mock.ts +159 -0
  153. package/services/payments/payment-adapter.ts +118 -0
  154. package/services/payments/types.ts +131 -0
  155. package/services/permissions/permission-manager.ts +284 -0
  156. package/services/permissions/types.ts +104 -0
  157. package/services/realtime/types.ts +100 -0
  158. package/services/realtime/websocket-manager.ts +441 -0
  159. package/services/security.ts +289 -286
  160. package/services/sentry.ts +4 -4
  161. package/stores/appStore.ts +9 -0
  162. package/stores/notificationStore.ts +3 -1
  163. package/tailwind.config.js +47 -47
  164. package/tsconfig.json +37 -13
  165. package/types/user.ts +1 -1
  166. package/utils/accessibility.ts +446 -446
  167. package/utils/animations/presets.ts +182 -0
  168. package/utils/animations/transitions.ts +62 -0
  169. package/utils/index.ts +63 -52
  170. package/utils/toast.ts +9 -2
  171. package/utils/validation.ts +4 -1
  172. package/utils/withAccessibility.tsx +272 -272
@@ -0,0 +1,441 @@
1
+ /**
2
+ * @fileoverview WebSocket connection manager with auto-reconnect
3
+ * Provides a robust WebSocket client with heartbeat, message queuing,
4
+ * channel subscriptions, and exponential backoff reconnection.
5
+ * @module services/realtime/websocket-manager
6
+ */
7
+
8
+ import type {
9
+ ConnectionStatus,
10
+ WebSocketConfig,
11
+ WebSocketMessage,
12
+ MessageHandler,
13
+ StatusHandler,
14
+ } from "./types";
15
+
16
+ /** Maximum reconnect delay cap in milliseconds */
17
+ const MAX_RECONNECT_DELAY = 30_000;
18
+
19
+ /**
20
+ * WebSocket connection manager.
21
+ *
22
+ * Manages a single WebSocket connection with support for:
23
+ * - Automatic reconnection with exponential backoff
24
+ * - Heartbeat keep-alive messages
25
+ * - Message queuing when disconnected
26
+ * - Channel-based subscriptions
27
+ * - Global and per-channel message handlers
28
+ * - Status change listeners
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { WebSocketManager } from '@/services/realtime/websocket-manager';
33
+ *
34
+ * const manager = new WebSocketManager({
35
+ * url: 'wss://api.example.com/ws',
36
+ * getToken: async () => authStore.getState().token,
37
+ * });
38
+ *
39
+ * manager.onStatusChange((status) => console.log('WS status:', status));
40
+ * manager.connect();
41
+ *
42
+ * const unsub = manager.subscribe('chat:room-1', (msg) => {
43
+ * console.log('New message:', msg.payload);
44
+ * });
45
+ *
46
+ * manager.send('chat:message', { text: 'Hello!' }, 'chat:room-1');
47
+ *
48
+ * // Later: cleanup
49
+ * unsub();
50
+ * manager.disconnect();
51
+ * ```
52
+ */
53
+ export class WebSocketManager {
54
+ private ws: WebSocket | null = null;
55
+ private config: Required<WebSocketConfig>;
56
+ private status: ConnectionStatus = "disconnected";
57
+ private reconnectAttempts = 0;
58
+ private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
59
+ private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
60
+ private connectionTimeoutTimer: ReturnType<typeof setTimeout> | null = null;
61
+ private messageQueue: WebSocketMessage[] = [];
62
+ private shouldReconnect = true;
63
+
64
+ /** Per-channel message handlers */
65
+ private messageHandlers: Map<string, Set<MessageHandler>> = new Map();
66
+ /** Global message handlers (receive all messages) */
67
+ private globalHandlers: Set<MessageHandler> = new Set();
68
+ /** Connection status change handlers */
69
+ private statusHandlers: Set<StatusHandler> = new Set();
70
+
71
+ constructor(config: WebSocketConfig) {
72
+ this.config = {
73
+ url: config.url,
74
+ getToken: config.getToken ?? (async () => null),
75
+ autoReconnect: config.autoReconnect ?? true,
76
+ maxReconnectAttempts: config.maxReconnectAttempts ?? 10,
77
+ reconnectBaseDelay: config.reconnectBaseDelay ?? 1000,
78
+ heartbeatInterval: config.heartbeatInterval ?? 30_000,
79
+ connectionTimeout: config.connectionTimeout ?? 10_000,
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Open the WebSocket connection.
85
+ * If a `getToken` function was provided in the config, the token is appended
86
+ * as a `token` query parameter on the connection URL.
87
+ */
88
+ async connect(): Promise<void> {
89
+ if (this.status === "connecting" || this.status === "connected") {
90
+ return;
91
+ }
92
+
93
+ this.shouldReconnect = this.config.autoReconnect;
94
+ this.setStatus("connecting");
95
+
96
+ try {
97
+ let url = this.config.url;
98
+
99
+ // Inject auth token as query param if available
100
+ const token = await this.config.getToken();
101
+ if (token) {
102
+ const separator = url.includes("?") ? "&" : "?";
103
+ url = `${url}${separator}token=${encodeURIComponent(token)}`;
104
+ }
105
+
106
+ this.ws = new WebSocket(url);
107
+
108
+ // Set a connection timeout
109
+ this.connectionTimeoutTimer = setTimeout(() => {
110
+ if (this.status === "connecting") {
111
+ console.warn("[WebSocketManager] Connection timeout");
112
+ this.ws?.close();
113
+ this.attemptReconnect();
114
+ }
115
+ }, this.config.connectionTimeout);
116
+
117
+ this.ws.onopen = () => {
118
+ this.clearConnectionTimeout();
119
+ this.reconnectAttempts = 0;
120
+ this.setStatus("connected");
121
+ this.startHeartbeat();
122
+ this.flushQueue();
123
+ };
124
+
125
+ this.ws.onmessage = (event: MessageEvent) => {
126
+ this.handleMessage(event);
127
+ };
128
+
129
+ this.ws.onclose = () => {
130
+ this.stopHeartbeat();
131
+ this.clearConnectionTimeout();
132
+ if (this.status !== "disconnected") {
133
+ this.setStatus("disconnected");
134
+ this.attemptReconnect();
135
+ }
136
+ };
137
+
138
+ this.ws.onerror = (error: Event) => {
139
+ console.error("[WebSocketManager] WebSocket error:", error);
140
+ // onclose will fire after onerror, so reconnect logic is handled there
141
+ };
142
+ } catch (error) {
143
+ console.error("[WebSocketManager] Failed to connect:", error);
144
+ this.setStatus("disconnected");
145
+ this.attemptReconnect();
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Gracefully close the WebSocket connection.
151
+ * Disables auto-reconnect and clears all timers.
152
+ */
153
+ disconnect(): void {
154
+ this.shouldReconnect = false;
155
+ this.clearReconnectTimer();
156
+ this.stopHeartbeat();
157
+ this.clearConnectionTimeout();
158
+
159
+ if (this.ws) {
160
+ this.ws.onopen = null;
161
+ this.ws.onmessage = null;
162
+ this.ws.onclose = null;
163
+ this.ws.onerror = null;
164
+ this.ws.close();
165
+ this.ws = null;
166
+ }
167
+
168
+ this.setStatus("disconnected");
169
+ }
170
+
171
+ /**
172
+ * Send a typed message over the WebSocket.
173
+ * If the connection is not open, the message is queued and sent
174
+ * once the connection is (re)established.
175
+ *
176
+ * @typeParam T - The shape of the message payload
177
+ * @param type - Message type identifier
178
+ * @param payload - The message payload
179
+ * @param channel - Optional channel to target
180
+ */
181
+ send<T = unknown>(type: string, payload: T, channel?: string): void {
182
+ const message: WebSocketMessage<T> = {
183
+ type,
184
+ channel,
185
+ payload,
186
+ timestamp: new Date().toISOString(),
187
+ };
188
+
189
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
190
+ this.ws.send(JSON.stringify(message));
191
+ } else {
192
+ this.messageQueue.push(message as WebSocketMessage);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Subscribe to messages on a specific channel.
198
+ * Sends a `subscribe` message to the server and registers a local handler.
199
+ *
200
+ * @param channel - The channel name to subscribe to
201
+ * @param handler - Callback invoked for each message on this channel
202
+ * @returns Unsubscribe function that removes the handler and sends an `unsubscribe` message
203
+ */
204
+ subscribe<T = unknown>(
205
+ channel: string,
206
+ handler: MessageHandler<T>
207
+ ): () => void {
208
+ if (!this.messageHandlers.has(channel)) {
209
+ this.messageHandlers.set(channel, new Set());
210
+ }
211
+
212
+ const handlers = this.messageHandlers.get(channel)!;
213
+ handlers.add(handler as MessageHandler);
214
+
215
+ // Notify the server about the subscription
216
+ this.send("subscribe", { channel });
217
+
218
+ return () => {
219
+ handlers.delete(handler as MessageHandler);
220
+ if (handlers.size === 0) {
221
+ this.messageHandlers.delete(channel);
222
+ }
223
+ // Notify the server about the unsubscription
224
+ this.send("unsubscribe", { channel });
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Register a global message handler that receives all messages
230
+ * regardless of channel.
231
+ *
232
+ * @param handler - Callback invoked for every incoming message
233
+ * @returns Unsubscribe function that removes the handler
234
+ */
235
+ onMessage<T = unknown>(handler: MessageHandler<T>): () => void {
236
+ this.globalHandlers.add(handler as MessageHandler);
237
+ return () => {
238
+ this.globalHandlers.delete(handler as MessageHandler);
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Register a handler for connection status changes.
244
+ *
245
+ * @param handler - Callback invoked with the new status
246
+ * @returns Unsubscribe function that removes the handler
247
+ */
248
+ onStatusChange(handler: StatusHandler): () => void {
249
+ this.statusHandlers.add(handler);
250
+ return () => {
251
+ this.statusHandlers.delete(handler);
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Get the current connection status.
257
+ *
258
+ * @returns The current ConnectionStatus
259
+ */
260
+ getStatus(): ConnectionStatus {
261
+ return this.status;
262
+ }
263
+
264
+ // =========================================
265
+ // Private methods
266
+ // =========================================
267
+
268
+ /**
269
+ * Update the connection status and notify all status handlers.
270
+ */
271
+ private setStatus(newStatus: ConnectionStatus): void {
272
+ if (this.status === newStatus) return;
273
+ this.status = newStatus;
274
+ this.statusHandlers.forEach((handler) => {
275
+ try {
276
+ handler(newStatus);
277
+ } catch (error) {
278
+ console.error("[WebSocketManager] Status handler error:", error);
279
+ }
280
+ });
281
+ }
282
+
283
+ /**
284
+ * Validate that parsed data conforms to the WebSocketMessage shape.
285
+ */
286
+ private isValidMessage(data: unknown): data is WebSocketMessage {
287
+ return (
288
+ typeof data === "object" &&
289
+ data !== null &&
290
+ typeof (data as Record<string, unknown>).type === "string" &&
291
+ "payload" in data
292
+ );
293
+ }
294
+
295
+ /**
296
+ * Parse and dispatch an incoming WebSocket message to the appropriate handlers.
297
+ */
298
+ private handleMessage(event: MessageEvent): void {
299
+ try {
300
+ const parsed: unknown = JSON.parse(event.data as string);
301
+
302
+ if (!this.isValidMessage(parsed)) {
303
+ console.warn("[WebSocketManager] Skipping invalid message:", parsed);
304
+ return;
305
+ }
306
+
307
+ const message: WebSocketMessage = parsed;
308
+
309
+ // Ignore heartbeat acknowledgements
310
+ if (message.type === "pong") {
311
+ return;
312
+ }
313
+
314
+ // Dispatch to global handlers
315
+ this.globalHandlers.forEach((handler) => {
316
+ try {
317
+ handler(message);
318
+ } catch (error) {
319
+ console.error(
320
+ "[WebSocketManager] Global message handler error:",
321
+ error
322
+ );
323
+ }
324
+ });
325
+
326
+ // Dispatch to channel-specific handlers
327
+ if (message.channel) {
328
+ const channelHandlers = this.messageHandlers.get(message.channel);
329
+ if (channelHandlers) {
330
+ channelHandlers.forEach((handler) => {
331
+ try {
332
+ handler(message);
333
+ } catch (error) {
334
+ console.error(
335
+ "[WebSocketManager] Channel message handler error:",
336
+ error
337
+ );
338
+ }
339
+ });
340
+ }
341
+ }
342
+ } catch (error) {
343
+ console.error("[WebSocketManager] Failed to parse message:", error);
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Attempt to reconnect using exponential backoff.
349
+ * The delay doubles with each attempt, capped at MAX_RECONNECT_DELAY.
350
+ */
351
+ private attemptReconnect(): void {
352
+ if (!this.shouldReconnect) {
353
+ return;
354
+ }
355
+
356
+ if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
357
+ console.warn(
358
+ `[WebSocketManager] Max reconnect attempts (${this.config.maxReconnectAttempts}) reached`
359
+ );
360
+ this.setStatus("disconnected");
361
+ return;
362
+ }
363
+
364
+ this.setStatus("reconnecting");
365
+
366
+ const delay = Math.min(
367
+ this.config.reconnectBaseDelay * Math.pow(2, this.reconnectAttempts),
368
+ MAX_RECONNECT_DELAY
369
+ );
370
+
371
+ console.log(
372
+ `[WebSocketManager] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1}/${this.config.maxReconnectAttempts})`
373
+ );
374
+
375
+ this.reconnectTimer = setTimeout(() => {
376
+ this.reconnectAttempts++;
377
+ this.connect();
378
+ }, delay);
379
+ }
380
+
381
+ /**
382
+ * Start sending periodic heartbeat (ping) messages to keep the connection alive.
383
+ */
384
+ private startHeartbeat(): void {
385
+ this.stopHeartbeat();
386
+ this.heartbeatTimer = setInterval(() => {
387
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
388
+ this.send("ping", {});
389
+ }
390
+ }, this.config.heartbeatInterval);
391
+ }
392
+
393
+ /**
394
+ * Stop the heartbeat timer.
395
+ */
396
+ private stopHeartbeat(): void {
397
+ if (this.heartbeatTimer) {
398
+ clearInterval(this.heartbeatTimer);
399
+ this.heartbeatTimer = null;
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Send all queued messages that were buffered while disconnected.
405
+ */
406
+ private flushQueue(): void {
407
+ if (this.messageQueue.length === 0) return;
408
+
409
+ const queue = [...this.messageQueue];
410
+ this.messageQueue = [];
411
+
412
+ queue.forEach((message) => {
413
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
414
+ this.ws.send(JSON.stringify(message));
415
+ } else {
416
+ // Re-queue if connection was lost during flush
417
+ this.messageQueue.push(message);
418
+ }
419
+ });
420
+ }
421
+
422
+ /**
423
+ * Clear the reconnect timer.
424
+ */
425
+ private clearReconnectTimer(): void {
426
+ if (this.reconnectTimer) {
427
+ clearTimeout(this.reconnectTimer);
428
+ this.reconnectTimer = null;
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Clear the connection timeout timer.
434
+ */
435
+ private clearConnectionTimeout(): void {
436
+ if (this.connectionTimeoutTimer) {
437
+ clearTimeout(this.connectionTimeoutTimer);
438
+ this.connectionTimeoutTimer = null;
439
+ }
440
+ }
441
+ }