react-native-qalink 0.6.0 → 0.7.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 (36) hide show
  1. package/README.md +258 -0
  2. package/dist/core/MiddlewarePipeline.d.ts +37 -0
  3. package/dist/core/MiddlewarePipeline.d.ts.map +1 -0
  4. package/dist/core/MiddlewarePipeline.js +58 -0
  5. package/dist/core/MiddlewarePipeline.js.map +1 -0
  6. package/dist/core/OfflineQueue.d.ts +48 -0
  7. package/dist/core/OfflineQueue.d.ts.map +1 -0
  8. package/dist/core/OfflineQueue.js +145 -0
  9. package/dist/core/OfflineQueue.js.map +1 -0
  10. package/dist/core/session.d.ts +2 -0
  11. package/dist/core/session.d.ts.map +1 -1
  12. package/dist/core/session.js +9 -0
  13. package/dist/core/session.js.map +1 -1
  14. package/dist/index.d.ts +79 -13
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +160 -28
  17. package/dist/index.js.map +1 -1
  18. package/dist/trackers/NavigationLoopTracker.d.ts +31 -0
  19. package/dist/trackers/NavigationLoopTracker.d.ts.map +1 -0
  20. package/dist/trackers/NavigationLoopTracker.js +73 -0
  21. package/dist/trackers/NavigationLoopTracker.js.map +1 -0
  22. package/dist/trackers/RageClickTracker.d.ts +33 -0
  23. package/dist/trackers/RageClickTracker.d.ts.map +1 -0
  24. package/dist/trackers/RageClickTracker.js +56 -0
  25. package/dist/trackers/RageClickTracker.js.map +1 -0
  26. package/dist/trackers/SilentFailureTracker.d.ts +55 -0
  27. package/dist/trackers/SilentFailureTracker.d.ts.map +1 -0
  28. package/dist/trackers/SilentFailureTracker.js +84 -0
  29. package/dist/trackers/SilentFailureTracker.js.map +1 -0
  30. package/dist/transport/websocket.d.ts +18 -9
  31. package/dist/transport/websocket.d.ts.map +1 -1
  32. package/dist/transport/websocket.js +66 -30
  33. package/dist/transport/websocket.js.map +1 -1
  34. package/dist/types/index.d.ts +90 -10
  35. package/dist/types/index.d.ts.map +1 -1
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -351,6 +351,184 @@ QALink.init({
351
351
 
352
352
  ---
353
353
 
354
+ ## 🧠 Advanced QA Features (v0.7.0)
355
+
356
+ ### Rage Click Detection
357
+
358
+ Detects when a user taps the same element multiple times rapidly — a strong frustration signal.
359
+
360
+ ```typescript
361
+ // 1. Enable (once, after init)
362
+ QALink.enableRageClickDetection({ threshold: 3, windowMs: 600 });
363
+
364
+ // 2. Call from any onPress handler
365
+ <Button
366
+ onPress={() => {
367
+ QALink.trackUIInteraction('checkout-submit-btn');
368
+ handleSubmit();
369
+ }}
370
+ />
371
+ ```
372
+
373
+ A `rage_click` event fires in the dashboard when the threshold is hit.
374
+
375
+ ---
376
+
377
+ ### Navigation Loop Detection
378
+
379
+ Detects when the user bounces back and forth between the same two screens (A→B→A→B…). Signals broken back navigation or an unresolved error state.
380
+
381
+ ```typescript
382
+ // Enable (once, after init)
383
+ QALink.enableNavigationLoopDetection(3); // fires after 3 alternations
384
+
385
+ // Automatically triggered by setScreen() and startPackage()
386
+ QALink.setScreen('CheckoutScreen');
387
+ ```
388
+
389
+ A `navigation_loop` event fires in the dashboard with the full navigation pattern.
390
+
391
+ ---
392
+
393
+ ### Silent Failure Detection
394
+
395
+ The hardest QA bug: API returns 200 OK but the data is null/empty. No error, no crash — the app just silently breaks.
396
+
397
+ ```typescript
398
+ const orders = await fetchOrders(userId); // 200 OK
399
+
400
+ QALink.trackDataState({
401
+ context: 'orders_list',
402
+ expected: Array.isArray(orders) && orders.length > 0,
403
+ actual: orders,
404
+ statusCode: 200,
405
+ url: '/api/orders',
406
+ });
407
+ // If orders is [] → fires a silent_failure event in the dashboard
408
+ ```
409
+
410
+ Returns `true` if the check passes, `false` if a failure was detected.
411
+
412
+ ---
413
+
414
+ ### Middleware (Event Processing Pipeline)
415
+
416
+ Intercept, enrich, filter, or transform any event before it's sent.
417
+
418
+ ```typescript
419
+ // Drop noisy console_log events in production
420
+ QALink.use((event, next) => {
421
+ if (event.type === 'console_log') return; // drop
422
+ next(event);
423
+ });
424
+
425
+ // Add a custom tag to every event
426
+ QALink.use((event, next) => {
427
+ next({ ...event, _release: '2.1.0' } as typeof event);
428
+ });
429
+
430
+ // Log every event locally for debugging
431
+ QALink.use((event, next) => {
432
+ console.log('[QALink middleware]', event.type);
433
+ next(event);
434
+ });
435
+ ```
436
+
437
+ Middlewares run in insertion order. Calling `next` is optional — skipping it drops the event.
438
+
439
+ ---
440
+
441
+ ### Offline-First Queue
442
+
443
+ Events are buffered automatically when the connection drops. Auto-flush triggers:
444
+
445
+ ```typescript
446
+ QALink.init({
447
+ apiKey: 'qlk_...',
448
+ appVersion: '1.0.0',
449
+ offlineQueue: {
450
+ maxSize: 500, // max buffered events (drops oldest when full)
451
+ flushOnCount: 20, // auto-flush when 20 events are buffered
452
+ flushIntervalMs: 30_000, // auto-flush every 30s
453
+ },
454
+ });
455
+
456
+ // Manual flush (e.g. before app goes to background)
457
+ QALink.flush();
458
+ ```
459
+
460
+ If `@react-native-async-storage/async-storage` is installed, events also persist across app restarts.
461
+
462
+ ---
463
+
464
+ ### Sequence Numbers
465
+
466
+ Every envelope sent to the dashboard now includes a `sequenceNumber` — a monotonically increasing counter per session. The dashboard can use it to detect out-of-order or missing events.
467
+
468
+ ---
469
+
470
+ ## 📦 Event Packages (v0.6.0)
471
+
472
+ Los **Event Packages** agrupan **todos los eventos** de una pantalla en un solo payload que se envía cuando el usuario sale de ella. Incluyen métricas pre-calculadas y el historial completo de eventos.
473
+
474
+ ### Integración en 1 línea — `useTrackedScreen`
475
+
476
+ ```typescript
477
+ import { useTrackedScreen } from 'react-native-qalink';
478
+
479
+ function CheckoutScreen() {
480
+ useTrackedScreen('CheckoutScreen');
481
+ // ↑ inicia el package al montar, lo cierra y envía al desmontar
482
+ return <View>...</View>;
483
+ }
484
+ ```
485
+
486
+ Eso es todo. Todos los eventos generados mientras el usuario está en esa pantalla (API calls, logs, errores, breadcrumbs) se agrupan y se envían juntos al salir.
487
+
488
+ ### API manual — `startPackage` / `endPackage`
489
+
490
+ ```typescript
491
+ // En React Navigation (App.tsx)
492
+ <NavigationContainer
493
+ onStateChange={() => {
494
+ const screen = navigationRef.current?.getCurrentRoute()?.name ?? '';
495
+
496
+ QALink.endPackage(); // cierra el package de la pantalla anterior
497
+ QALink.startPackage(screen); // inicia uno nuevo para la pantalla actual
498
+ }}
499
+ >
500
+ ```
501
+
502
+ ### Payload que recibe el dashboard
503
+
504
+ ```json
505
+ {
506
+ "type": "event_package",
507
+ "packageId": "...",
508
+ "context": {
509
+ "screenName": "CheckoutScreen",
510
+ "sessionId": "...",
511
+ "startTime": 1710445670000,
512
+ "endTime": 1710445680000,
513
+ "durationMs": 10000
514
+ },
515
+ "events": [ ...todos los eventos capturados en esa pantalla... ],
516
+ "metrics": {
517
+ "totalApiCalls": 3,
518
+ "totalErrors": 1,
519
+ "totalLogs": 5,
520
+ "totalBreadcrumbs": 2,
521
+ "totalEvents": 11
522
+ }
523
+ }
524
+ ```
525
+
526
+ ### Backward compatibility
527
+
528
+ Si **no** llamas `startPackage` ni `useTrackedScreen`, cada evento se sigue enviando inmediatamente como antes. Sin cambios de comportamiento.
529
+
530
+ ---
531
+
354
532
  ## 📊 Tipos de eventos capturados
355
533
 
356
534
  El SDK captura automáticamente:
@@ -358,6 +536,10 @@ El SDK captura automáticamente:
358
536
  | Tipo | Descripción |
359
537
  |------|-------------|
360
538
  | `session_start` | Inicio de sesión |
539
+ | `event_package` | Batch de eventos de una pantalla (v0.6.0) |
540
+ | `rage_click` | Usuario tapeó el mismo elemento N veces (v0.7.0) |
541
+ | `navigation_loop` | Usuario rebotando entre mismas pantallas (v0.7.0) |
542
+ | `silent_failure` | API 200 OK con datos inválidos/vacíos (v0.7.0) |
361
543
  | `http_request` | Request HTTP (fetch/axios) |
362
544
  | `user_log` | Logs con debug/info/warn/error/critical |
363
545
  | `custom_event` | Eventos custom con logEvent() |
@@ -445,6 +627,82 @@ QALink.logEvent('experiment_viewed', {
445
627
  });
446
628
  ```
447
629
 
630
+ ### 4. Event Package con React Navigation
631
+
632
+ ```typescript
633
+ // App.tsx
634
+ import { useTrackedScreen } from 'react-native-qalink';
635
+
636
+ // En cada pantalla — 1 sola línea:
637
+ function PaymentScreen() {
638
+ useTrackedScreen('PaymentScreen');
639
+ // ...
640
+ }
641
+
642
+ function ProfileScreen() {
643
+ useTrackedScreen('ProfileScreen');
644
+ // ...
645
+ }
646
+ ```
647
+
648
+ O con `NavigationContainer` para cobertura automática de todas las pantallas:
649
+
650
+ ```typescript
651
+ // App.tsx
652
+ import { NavigationContainer } from '@react-navigation/native';
653
+ import { QALink } from 'react-native-qalink';
654
+
655
+ <NavigationContainer
656
+ onStateChange={() => {
657
+ const screen = navigationRef.current?.getCurrentRoute()?.name ?? 'unknown';
658
+ QALink.endPackage(); // cierra el package de la pantalla anterior
659
+ QALink.startPackage(screen); // inicia uno nuevo
660
+ }}
661
+ >
662
+ ```
663
+
664
+ ---
665
+
666
+ ## 📋 Referencia completa de la API
667
+
668
+ | Método | Descripción |
669
+ |--------|-------------|
670
+ | **`init(config)`** | Inicializa el SDK. Requerido antes de todo. |
671
+ | **`interceptAxios(instance)`** | Registra una instancia de Axios. |
672
+ | **`use(middleware)`** | Agrega un middleware al pipeline de eventos. |
673
+ | **`setUserContext(ctx)`** | Adjunta datos del usuario a todos los eventos. |
674
+ | **`setCustomContext(ctx)`** | Adjunta datos custom a todos los eventos. |
675
+ | **`clearContext()`** | Limpia usuario y contexto custom. |
676
+ | **`debug(...args)`** | Log nivel debug. |
677
+ | **`info(...args)`** | Log nivel info. |
678
+ | **`warn(...args)`** | Log nivel warn. |
679
+ | **`error(...args)`** | Log nivel error. |
680
+ | **`critical(...args)`** | Log nivel critical. |
681
+ | **`logEvent(name, data?)`** | Evento custom de negocio. |
682
+ | **`addBreadcrumb(action, data?)`** | Breadcrumb manual. |
683
+ | **`logRequest(options)`** | Registra request de red manualmente. |
684
+ | **`setScreen(name)`** | Cambia la pantalla actual + breadcrumb + loop detection. |
685
+ | **`startPackage(screenName)`** | Inicia un Event Package para esa pantalla. |
686
+ | **`endPackage()`** | Cierra el package activo y lo envía. |
687
+ | **`flush()`** | Envía inmediatamente todos los eventos en cola offline. |
688
+ | **`enableRageClickDetection(options?)`** | Activa detección de rage clicks. |
689
+ | **`trackUIInteraction(target)`** | Registra tap para rage click. |
690
+ | **`enableNavigationLoopDetection(threshold?)`** | Activa detección de loops de navegación. |
691
+ | **`trackDataState(check)`** | Detecta silent failures (API 200 con datos inválidos). |
692
+ | **`captureScreen(label?)`** | Captura la pantalla completa. |
693
+ | **`captureRef(ref, label?)`** | Captura un componente específico. |
694
+ | **`configureHTTP(config)`** | Actualiza config HTTP en runtime. |
695
+ | **`getDeviceId()`** | Devuelve el deviceId de la sesión. |
696
+ | **`getStatus()`** | Estado de la conexión WebSocket. |
697
+ | **`getQueueSize()`** | Número de eventos en cola offline. |
698
+ | **`destroy()`** | Limpia interceptores y desconecta. |
699
+
700
+ **Hook:**
701
+
702
+ | Hook | Descripción |
703
+ |------|-------------|
704
+ | **`useTrackedScreen(name)`** | 1 línea: inicia y cierra el package automáticamente al montar/desmontar la pantalla. |
705
+
448
706
  ---
449
707
 
450
708
  ## 🚨 Troubleshooting
@@ -0,0 +1,37 @@
1
+ /**
2
+ * MiddlewarePipeline — chain of event processors applied before sending.
3
+ *
4
+ * Each middleware receives an event and a `next` callback:
5
+ * - Call `next(event)` to pass the (optionally modified) event forward.
6
+ * - Don't call `next` to drop the event entirely.
7
+ * - Mutate or replace the event to enrich/transform it.
8
+ *
9
+ * @example
10
+ * pipeline.use((event, next) => {
11
+ * // Drop noisy console logs in production
12
+ * if (event.type === 'console_log') return;
13
+ * next(event);
14
+ * });
15
+ *
16
+ * @example
17
+ * pipeline.use((event, next) => {
18
+ * // Enrich every event with a custom tag
19
+ * next({ ...event, _tag: 'v2' } as typeof event);
20
+ * });
21
+ */
22
+ import type { QALinkEvent } from '../types';
23
+ export type EventMiddleware = (event: QALinkEvent, next: (event: QALinkEvent) => void) => void;
24
+ export declare class MiddlewarePipeline {
25
+ private readonly middlewares;
26
+ /** Register a middleware. Middlewares run in insertion order. */
27
+ use(middleware: EventMiddleware): this;
28
+ /** Clear all registered middlewares. */
29
+ clear(): void;
30
+ get count(): number;
31
+ /**
32
+ * Run `event` through the full middleware chain.
33
+ * `finalHandler` is called only if every middleware calls `next`.
34
+ */
35
+ process(event: QALinkEvent, finalHandler: (event: QALinkEvent) => void): void;
36
+ }
37
+ //# sourceMappingURL=MiddlewarePipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MiddlewarePipeline.d.ts","sourceRoot":"","sources":["../../src/core/MiddlewarePipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,MAAM,MAAM,eAAe,GAAG,CAC5B,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,KAC/B,IAAI,CAAC;AAEV,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IAErD,iEAAiE;IACjE,GAAG,CAAC,UAAU,EAAE,eAAe,GAAG,IAAI;IAKtC,wCAAwC;IACxC,KAAK,IAAI,IAAI;IAIb,IAAI,KAAK,IAAI,MAAM,CAElB;IAED;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI;CAa9E"}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ /**
3
+ * MiddlewarePipeline — chain of event processors applied before sending.
4
+ *
5
+ * Each middleware receives an event and a `next` callback:
6
+ * - Call `next(event)` to pass the (optionally modified) event forward.
7
+ * - Don't call `next` to drop the event entirely.
8
+ * - Mutate or replace the event to enrich/transform it.
9
+ *
10
+ * @example
11
+ * pipeline.use((event, next) => {
12
+ * // Drop noisy console logs in production
13
+ * if (event.type === 'console_log') return;
14
+ * next(event);
15
+ * });
16
+ *
17
+ * @example
18
+ * pipeline.use((event, next) => {
19
+ * // Enrich every event with a custom tag
20
+ * next({ ...event, _tag: 'v2' } as typeof event);
21
+ * });
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.MiddlewarePipeline = void 0;
25
+ class MiddlewarePipeline {
26
+ constructor() {
27
+ this.middlewares = [];
28
+ }
29
+ /** Register a middleware. Middlewares run in insertion order. */
30
+ use(middleware) {
31
+ this.middlewares.push(middleware);
32
+ return this;
33
+ }
34
+ /** Clear all registered middlewares. */
35
+ clear() {
36
+ this.middlewares.length = 0;
37
+ }
38
+ get count() {
39
+ return this.middlewares.length;
40
+ }
41
+ /**
42
+ * Run `event` through the full middleware chain.
43
+ * `finalHandler` is called only if every middleware calls `next`.
44
+ */
45
+ process(event, finalHandler) {
46
+ const chain = this.middlewares;
47
+ const run = (index, current) => {
48
+ if (index >= chain.length) {
49
+ finalHandler(current);
50
+ return;
51
+ }
52
+ chain[index](current, (next) => run(index + 1, next));
53
+ };
54
+ run(0, event);
55
+ }
56
+ }
57
+ exports.MiddlewarePipeline = MiddlewarePipeline;
58
+ //# sourceMappingURL=MiddlewarePipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MiddlewarePipeline.js","sourceRoot":"","sources":["../../src/core/MiddlewarePipeline.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AASH,MAAa,kBAAkB;IAA/B;QACmB,gBAAW,GAAsB,EAAE,CAAC;IAkCvD,CAAC;IAhCC,iEAAiE;IACjE,GAAG,CAAC,UAA2B;QAC7B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,wCAAwC;IACxC,KAAK;QACH,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;IAC9B,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;IACjC,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,KAAkB,EAAE,YAA0C;QACpE,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC;QAE/B,MAAM,GAAG,GAAG,CAAC,KAAa,EAAE,OAAoB,EAAQ,EAAE;YACxD,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC1B,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,OAAO;YACT,CAAC;YACD,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACxD,CAAC,CAAC;QAEF,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAChB,CAAC;CACF;AAnCD,gDAmCC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * OfflineQueue — FIFO event queue with optional AsyncStorage persistence.
3
+ *
4
+ * - Falls back to pure in-memory when AsyncStorage is not installed.
5
+ * - Respects a max-size limit (drops oldest event when full).
6
+ * - Supports two auto-flush triggers:
7
+ * · count-based: flush when queue reaches `flushOnCount` events.
8
+ * · time-based: flush every `flushIntervalMs` milliseconds.
9
+ * - Persists across app restarts when AsyncStorage is available.
10
+ */
11
+ import type { QALinkEvent } from '../types';
12
+ export interface OfflineQueueConfig {
13
+ maxSize?: number;
14
+ /** Trigger flush when this many events are buffered. @default 20 */
15
+ flushOnCount?: number;
16
+ /** Trigger flush every N milliseconds. @default 30000 */
17
+ flushIntervalMs?: number;
18
+ debug?: boolean;
19
+ }
20
+ type FlushCallback = (events: QALinkEvent[]) => void;
21
+ export declare class OfflineQueue {
22
+ private memory;
23
+ private readonly maxSize;
24
+ private readonly flushOnCount;
25
+ private readonly flushIntervalMs;
26
+ private readonly debug;
27
+ private flushTimer;
28
+ private onFlush;
29
+ private storage;
30
+ constructor(config?: OfflineQueueConfig);
31
+ private tryBindAsyncStorage;
32
+ setFlushCallback(cb: FlushCallback): void;
33
+ startAutoFlush(): void;
34
+ stopAutoFlush(): void;
35
+ push(event: QALinkEvent): Promise<void>;
36
+ /** Remove and return all buffered events. Clears persistence. */
37
+ drain(): QALinkEvent[];
38
+ peek(): readonly QALinkEvent[];
39
+ get size(): number;
40
+ /** Load events persisted from a previous session and merge into current queue. */
41
+ loadPersisted(): Promise<void>;
42
+ private persist;
43
+ private clearPersisted;
44
+ private triggerFlush;
45
+ private log;
46
+ }
47
+ export {};
48
+ //# sourceMappingURL=OfflineQueue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OfflineQueue.d.ts","sourceRoot":"","sources":["../../src/core/OfflineQueue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAO5C,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,KAAK,aAAa,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;AAQrD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAChC,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,OAAO,CAAoC;gBAEvC,MAAM,GAAE,kBAAuB;IAU3C,OAAO,CAAC,mBAAmB;IAa3B,gBAAgB,CAAC,EAAE,EAAE,aAAa,GAAG,IAAI;IAMzC,cAAc,IAAI,IAAI;IAUtB,aAAa,IAAI,IAAI;IASf,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAc7C,iEAAiE;IACjE,KAAK,IAAI,WAAW,EAAE;IAOtB,IAAI,IAAI,SAAS,WAAW,EAAE;IAI9B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAID,kFAAkF;IAC5E,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;YAwBtB,OAAO;IASrB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,GAAG;CAGZ"}
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ /**
3
+ * OfflineQueue — FIFO event queue with optional AsyncStorage persistence.
4
+ *
5
+ * - Falls back to pure in-memory when AsyncStorage is not installed.
6
+ * - Respects a max-size limit (drops oldest event when full).
7
+ * - Supports two auto-flush triggers:
8
+ * · count-based: flush when queue reaches `flushOnCount` events.
9
+ * · time-based: flush every `flushIntervalMs` milliseconds.
10
+ * - Persists across app restarts when AsyncStorage is available.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.OfflineQueue = void 0;
14
+ const STORAGE_KEY = '__qalink_offline_queue__';
15
+ const DEFAULT_MAX_SIZE = 500;
16
+ const DEFAULT_FLUSH_COUNT = 20;
17
+ const DEFAULT_FLUSH_INTERVAL_MS = 30000;
18
+ class OfflineQueue {
19
+ constructor(config = {}) {
20
+ var _a, _b, _c, _d;
21
+ this.memory = [];
22
+ this.flushTimer = null;
23
+ this.onFlush = null;
24
+ this.storage = null;
25
+ this.maxSize = (_a = config.maxSize) !== null && _a !== void 0 ? _a : DEFAULT_MAX_SIZE;
26
+ this.flushOnCount = (_b = config.flushOnCount) !== null && _b !== void 0 ? _b : DEFAULT_FLUSH_COUNT;
27
+ this.flushIntervalMs = (_c = config.flushIntervalMs) !== null && _c !== void 0 ? _c : DEFAULT_FLUSH_INTERVAL_MS;
28
+ this.debug = (_d = config.debug) !== null && _d !== void 0 ? _d : false;
29
+ this.tryBindAsyncStorage();
30
+ }
31
+ // ─── Setup ─────────────────────────────────────────────────────────────────
32
+ tryBindAsyncStorage() {
33
+ try {
34
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
35
+ const AS = require('@react-native-async-storage/async-storage').default;
36
+ if (AS && typeof AS.getItem === 'function') {
37
+ this.storage = AS;
38
+ this.log('AsyncStorage persistence enabled');
39
+ }
40
+ }
41
+ catch (_a) {
42
+ this.log('AsyncStorage unavailable — in-memory queue only');
43
+ }
44
+ }
45
+ setFlushCallback(cb) {
46
+ this.onFlush = cb;
47
+ }
48
+ // ─── Auto-flush lifecycle ──────────────────────────────────────────────────
49
+ startAutoFlush() {
50
+ if (this.flushTimer)
51
+ return;
52
+ this.flushTimer = setInterval(() => {
53
+ if (this.memory.length > 0) {
54
+ this.log(`Auto-flush (time) — ${this.memory.length} events`);
55
+ this.triggerFlush();
56
+ }
57
+ }, this.flushIntervalMs);
58
+ }
59
+ stopAutoFlush() {
60
+ if (this.flushTimer) {
61
+ clearInterval(this.flushTimer);
62
+ this.flushTimer = null;
63
+ }
64
+ }
65
+ // ─── Queue operations ──────────────────────────────────────────────────────
66
+ async push(event) {
67
+ if (this.memory.length >= this.maxSize) {
68
+ this.memory.shift();
69
+ this.log('Queue full — dropped oldest event');
70
+ }
71
+ this.memory.push(event);
72
+ await this.persist();
73
+ if (this.memory.length >= this.flushOnCount) {
74
+ this.log(`Auto-flush (count) — ${this.memory.length} events`);
75
+ this.triggerFlush();
76
+ }
77
+ }
78
+ /** Remove and return all buffered events. Clears persistence. */
79
+ drain() {
80
+ const events = [...this.memory];
81
+ this.memory = [];
82
+ this.clearPersisted();
83
+ return events;
84
+ }
85
+ peek() {
86
+ return this.memory;
87
+ }
88
+ get size() {
89
+ return this.memory.length;
90
+ }
91
+ // ─── Persistence ───────────────────────────────────────────────────────────
92
+ /** Load events persisted from a previous session and merge into current queue. */
93
+ async loadPersisted() {
94
+ if (!this.storage)
95
+ return;
96
+ try {
97
+ const raw = await this.storage.getItem(STORAGE_KEY);
98
+ if (!raw)
99
+ return;
100
+ const parsed = JSON.parse(raw);
101
+ if (!Array.isArray(parsed))
102
+ return;
103
+ const existingIds = new Set(this.memory.map((e) => e.id).filter(Boolean));
104
+ for (const ev of parsed) {
105
+ const id = ev.id;
106
+ if (!id || !existingIds.has(id)) {
107
+ this.memory.push(ev);
108
+ }
109
+ }
110
+ this.log(`Loaded ${parsed.length} persisted events from previous session`);
111
+ }
112
+ catch (_a) {
113
+ this.log('Failed to load persisted queue — starting fresh');
114
+ }
115
+ }
116
+ async persist() {
117
+ if (!this.storage)
118
+ return;
119
+ try {
120
+ await this.storage.setItem(STORAGE_KEY, JSON.stringify(this.memory));
121
+ }
122
+ catch (_a) {
123
+ // Ignore silently — persistence is best-effort
124
+ }
125
+ }
126
+ clearPersisted() {
127
+ var _a;
128
+ (_a = this.storage) === null || _a === void 0 ? void 0 : _a.removeItem(STORAGE_KEY).catch(() => { });
129
+ }
130
+ // ─── Flush trigger ─────────────────────────────────────────────────────────
131
+ triggerFlush() {
132
+ if (!this.onFlush)
133
+ return;
134
+ const events = this.drain();
135
+ if (events.length > 0)
136
+ this.onFlush(events);
137
+ }
138
+ // ─── Helpers ───────────────────────────────────────────────────────────────
139
+ log(...args) {
140
+ if (this.debug)
141
+ console.log('[QALink OfflineQueue]', ...args);
142
+ }
143
+ }
144
+ exports.OfflineQueue = OfflineQueue;
145
+ //# sourceMappingURL=OfflineQueue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OfflineQueue.js","sourceRoot":"","sources":["../../src/core/OfflineQueue.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,8 @@ import { DeviceInfo } from '../types';
2
2
  export declare function generateId(): string;
3
3
  export declare function getSessionId(): string;
4
4
  export declare function resetSession(): string;
5
+ export declare function nextSequenceNumber(): number;
6
+ export declare function resetSequenceCounter(): void;
5
7
  export declare function getDeviceInfo(appVersion: string): Promise<DeviceInfo>;
6
8
  export declare function sanitizeBody(body: unknown, logBodies: boolean): unknown;
7
9
  //# sourceMappingURL=session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/core/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAItC,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAED,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAmB3E;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,CAGvE"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/core/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAKtC,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAmB3E;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,CAGvE"}
@@ -3,9 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateId = generateId;
4
4
  exports.getSessionId = getSessionId;
5
5
  exports.resetSession = resetSession;
6
+ exports.nextSequenceNumber = nextSequenceNumber;
7
+ exports.resetSequenceCounter = resetSequenceCounter;
6
8
  exports.getDeviceInfo = getDeviceInfo;
7
9
  exports.sanitizeBody = sanitizeBody;
8
10
  let currentSessionId = null;
11
+ let sequenceCounter = 0;
9
12
  function generateId() {
10
13
  return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
11
14
  }
@@ -19,6 +22,12 @@ function resetSession() {
19
22
  currentSessionId = generateId();
20
23
  return currentSessionId;
21
24
  }
25
+ function nextSequenceNumber() {
26
+ return ++sequenceCounter;
27
+ }
28
+ function resetSequenceCounter() {
29
+ sequenceCounter = 0;
30
+ }
22
31
  async function getDeviceInfo(appVersion) {
23
32
  var _a, _b;
24
33
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/core/session.ts"],"names":[],"mappings":";;AAIA,gCAEC;AAED,oCAKC;AAED,oCAGC;AAED,sCAmBC;AAED,oCAGC;AA1CD,IAAI,gBAAgB,GAAkB,IAAI,CAAC;AAE3C,SAAgB,UAAU;IACxB,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AACpE,CAAC;AAED,SAAgB,YAAY;IAC1B,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,gBAAgB,GAAG,UAAU,EAAE,CAAC;IAClC,CAAC;IACD,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,SAAgB,YAAY;IAC1B,gBAAgB,GAAG,UAAU,EAAE,CAAC;IAChC,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAEM,KAAK,UAAU,aAAa,CAAC,UAAkB;;IACpD,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;QAC7C,OAAO;YACL,QAAQ,EAAE,QAAQ,CAAC,EAAuB;YAC1C,SAAS,EAAE,MAAA,MAAA,QAAQ,CAAC,OAAO,0CAAE,QAAQ,EAAE,mCAAI,SAAS;YACpD,UAAU;YACV,WAAW,EAAE,SAAS;YACtB,SAAS,EAAE,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;SAC3E,CAAC;IACJ,CAAC;IAAC,WAAM,CAAC;QACP,OAAO;YACL,QAAQ,EAAE,SAAS;YACnB,SAAS,EAAE,SAAS;YACpB,UAAU;YACV,WAAW,EAAE,SAAS;YACtB,SAAS,EAAE,SAAS;SACrB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAgB,YAAY,CAAC,IAAa,EAAE,SAAkB;IAC5D,IAAI,CAAC,SAAS;QAAE,OAAO,0CAA0C,CAAC;IAClE,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/core/session.ts"],"names":[],"mappings":";;AAKA,gCAEC;AAED,oCAKC;AAED,oCAGC;AAED,gDAEC;AAED,oDAEC;AAED,sCAmBC;AAED,oCAGC;AAnDD,IAAI,gBAAgB,GAAkB,IAAI,CAAC;AAC3C,IAAI,eAAe,GAAG,CAAC,CAAC;AAExB,SAAgB,UAAU;IACxB,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AACpE,CAAC;AAED,SAAgB,YAAY;IAC1B,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,gBAAgB,GAAG,UAAU,EAAE,CAAC;IAClC,CAAC;IACD,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,SAAgB,YAAY;IAC1B,gBAAgB,GAAG,UAAU,EAAE,CAAC;IAChC,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,SAAgB,kBAAkB;IAChC,OAAO,EAAE,eAAe,CAAC;AAC3B,CAAC;AAED,SAAgB,oBAAoB;IAClC,eAAe,GAAG,CAAC,CAAC;AACtB,CAAC;AAEM,KAAK,UAAU,aAAa,CAAC,UAAkB;;IACpD,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;QAC7C,OAAO;YACL,QAAQ,EAAE,QAAQ,CAAC,EAAuB;YAC1C,SAAS,EAAE,MAAA,MAAA,QAAQ,CAAC,OAAO,0CAAE,QAAQ,EAAE,mCAAI,SAAS;YACpD,UAAU;YACV,WAAW,EAAE,SAAS;YACtB,SAAS,EAAE,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;SAC3E,CAAC;IACJ,CAAC;IAAC,WAAM,CAAC;QACP,OAAO;YACL,QAAQ,EAAE,SAAS;YACnB,SAAS,EAAE,SAAS;YACpB,UAAU;YACV,WAAW,EAAE,SAAS;YACtB,SAAS,EAAE,SAAS;SACrB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAgB,YAAY,CAAC,IAAa,EAAE,SAAkB;IAC5D,IAAI,CAAC,SAAS;QAAE,OAAO,0CAA0C,CAAC;IAClE,OAAO,IAAI,CAAC;AACd,CAAC"}