iidrak-analytics-react 1.5.1 → 1.6.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.
package/README.md CHANGED
@@ -5,7 +5,9 @@ A powerful, offline-first analytics SDK for React Native applications that inclu
5
5
  ## Features
6
6
 
7
7
  - 📊 **Event Tracking**: Log custom events with arbitrary parameters.
8
+ - 🚀 **Optimized Bandwidth**: Uses a Minimal Payload architecture for auto-clicks to save battery, data, and database costs, only sending full telemetry on session starts.
8
9
  - 🎥 **Session Replay**: Record screen interactions and touch events for playback analysis.
10
+ - 🎯 **Advanced Touch Context**: Automatically maps React Fiber tree to extract clicked text, component paths (e.g. `TouchableOpacity > Text`), and testing IDs without manual instrumentation.
9
11
  - 🛒 **Cart Management**: Built-in shopping cart state management.
10
12
  - 📴 **Offline Support**: Automatically queues events when offline and flushes them when connection is restored.
11
13
  - 🆔 **User & Session Management**: Auto-generates session IDs and supports user identification.
@@ -0,0 +1,39 @@
1
+ # MetaStreamIO (iidrak-analytics-react) Documentation
2
+
3
+ ## Overview
4
+ `iidrak-analytics-react` is a robust, offline-capable React Native analytics and session recording SDK. It is designed to capture user behavior smoothly without bloated payloads or manual developer instrumentation.
5
+
6
+ ## Key Architecture Concepts
7
+
8
+ ### 1. Minimal Payload Optimization
9
+ Traditional analytics tools drain user battery and network bandwidth by repeatedly sending static parameters (like the phone model, app version, and network status) on every single button tap.
10
+
11
+ Our SDK is heavily optimized. It sends a **Full Payload** only when a user initiates a session (`session_start`). For all subsequent thousands of interactions (like screen taps, swiping, or custom events), the SDK dynamically strips out these static properties and transmits a **Minimal Payload**.
12
+
13
+ This allows you to link all data backend via the unique `Session ID` while processing millions of events per second with virtually zero bandwidth overhead.
14
+
15
+ ### 2. Auto Component Mapping
16
+ By wrapping your application in the `MetaStreamProvider`, the SDK hooks directly into the React Native rendering engine (the Fiber Tree). When a user taps the screen, the SDK crawls up the UI tree in milliseconds to discover context.
17
+
18
+ This means a raw X/Y tap instantly becomes:
19
+ - **`component_type`**: E.g., `Text`, `TextInput`, `Image`.
20
+ - **`text_content`**: The literal word the user tapped on (e.g. `Add to Cart`).
21
+ - **`component_path`**: The ancestry tree of where this tap occurred (`Text > TouchableOpacity > ScrollViewContainer`).
22
+
23
+ ### 3. Queue Strategy
24
+ The SDK contains a powerful offline-first buffering engine.
25
+ - All interactions are serialized into models and placed into a robust internal device queue.
26
+ - If the device drops offline, the queue enters sleep mode.
27
+ - The moment the device connection is restored, the queue securely processes and flushes the backlog to your collector endpoints.
28
+
29
+ ## Example Backend Ingestion Strategy
30
+
31
+ Since payloads arrive in `MINIMAL` formats, it is up to your analytics backend to enrich the clickstream.
32
+
33
+ **Example ingestion:**
34
+ 1. A `session_start` event arrives: INSERT to `Sessions` table. (Contains device height, memory, version).
35
+ 2. A `login_success` minimal event arrives: INSERT to `Events` table, linking via `session_id`.
36
+ 3. An `auto_click` minimal event arrives: INSERT to `Interactions` table. Your dashboard runs a `JOIN` on the `Sessions` table using the `session_id` to build heatmaps segmented by the user's specific screen size.
37
+
38
+ ---
39
+ *Generated automatically by MetaStream SDK documentation builder.*
Binary file
@@ -197,6 +197,14 @@ class MetaStreamIO {
197
197
  });
198
198
  // Update recorder session ID
199
199
  if (this.recorder) this.recorder.setSessionId(this.session.session.id);
200
+
201
+ // Push the full payload event to register the session
202
+ setTimeout(() => {
203
+ this.trackEvent({
204
+ eventName: "session_start",
205
+ eventParameters: [],
206
+ });
207
+ }, 1500); // Small delay to allow enriching device info before it queues
200
208
  }
201
209
  } catch (err) {
202
210
  errors.push("session");
@@ -365,35 +373,66 @@ class MetaStreamIO {
365
373
  ) {
366
374
  this.logger.log("event", this.constant.MetaStreamIO_Log_SendOnce);
367
375
  } else {
368
- let e = new EventModel({
369
- account_balances: this.utility.copyObject(
370
- this.account.account.list()
371
- ),
372
- account_type: this.account.account.type,
373
- app: this.app_id,
374
- app_environment: this.app_environment,
375
- app_info: null,
376
- app_performance: null,
377
- cart: cart,
378
- cart_id: temporaryCart ? temporaryCart.id : this.cart.cart.id,
379
- channel: this.channel,
380
- ciam_id: this.user.ciam_id,
381
- client_event_date: client_event_date,
382
- client_event_timestamp: client_event_timestamp,
383
- device: null,
384
- device_token: this.user.device_token,
385
- email_id: this.user.email_id,
386
- event_name: eventName,
387
- event_params: tempEventParameters,
388
- network: null,
389
- session: this.utility.copyObject(
390
- this.session.logSession(this.previous.event_time)
391
- ),
392
- transaction_id: transactionId,
393
- user_id: this.user.id,
394
- user_country: this.user.country,
395
- user_properties: this.utility.copyObject(this.userproperties.list()),
396
- });
376
+ let e;
377
+
378
+ if (eventName === 'auto_click') {
379
+ // MINIMAL PAYLOAD FOR AUTO CLICKS
380
+ e = new EventModel({
381
+ app: this.app_id,
382
+ app_environment: this.app_environment,
383
+ client_event_date: client_event_date,
384
+ client_event_timestamp: client_event_timestamp,
385
+ event_name: eventName,
386
+ event_params: tempEventParameters,
387
+ session: { id: this.session.session.id }, // Just pass session ID
388
+ user_id: this.user.id,
389
+ // Skip everything else to save bandwidth
390
+ device: null,
391
+ app_info: null,
392
+ app_performance: null,
393
+ network: null,
394
+ user_properties: null,
395
+ account_balances: null,
396
+ account_type: null,
397
+ cart: null,
398
+ user_country: null,
399
+ email_id: null,
400
+ device_token: null,
401
+ ciam_id: null,
402
+ channel: null
403
+ });
404
+ } else {
405
+ // FULL PAYLOAD FOR CUSTOM EVENTS & SESSION START
406
+ e = new EventModel({
407
+ account_balances: this.utility.copyObject(
408
+ this.account.account.list()
409
+ ),
410
+ account_type: this.account.account.type,
411
+ app: this.app_id,
412
+ app_environment: this.app_environment,
413
+ app_info: null,
414
+ app_performance: null,
415
+ cart: cart,
416
+ cart_id: temporaryCart ? temporaryCart.id : this.cart.cart.id,
417
+ channel: this.channel,
418
+ ciam_id: this.user.ciam_id,
419
+ client_event_date: client_event_date,
420
+ client_event_timestamp: client_event_timestamp,
421
+ device: null,
422
+ device_token: this.user.device_token,
423
+ email_id: this.user.email_id,
424
+ event_name: eventName,
425
+ event_params: tempEventParameters,
426
+ network: null,
427
+ session: this.utility.copyObject(
428
+ this.session.logSession(this.previous.event_time)
429
+ ),
430
+ transaction_id: transactionId,
431
+ user_id: this.user.id,
432
+ user_country: this.user.country,
433
+ user_properties: this.utility.copyObject(this.userproperties.list()),
434
+ });
435
+ }
397
436
 
398
437
  this.storeEventMeta({
399
438
  eventTimestamp: client_event_timestamp,
@@ -495,15 +534,17 @@ class MetaStreamIO {
495
534
  async runQueue() {
496
535
  return this.queue.run(
497
536
  async function (event) {
498
- // Enrich
499
- event.app_info = await this.environment.logAppInfo().catch(() => null);
500
- event.app_performance = await this.environment
501
- .logAppPerformance()
502
- .catch(() => null);
503
- event.device = await this.environment.logDevice().catch(() => null);
504
- event.network = await this.environment
505
- .logNetworkInfo()
506
- .catch(() => null);
537
+ // Enrich only ONE time per session to save battery/bandwidth
538
+ if (event.event_name === 'session_start') {
539
+ event.app_info = await this.environment.logAppInfo().catch(() => null);
540
+ event.app_performance = await this.environment
541
+ .logAppPerformance()
542
+ .catch(() => null);
543
+ event.device = await this.environment.logDevice().catch(() => null);
544
+ event.network = await this.environment
545
+ .logNetworkInfo()
546
+ .catch(() => null);
547
+ }
507
548
 
508
549
  let response = await this.post_event(event.json())
509
550
  .then((res) => {
@@ -23,6 +23,69 @@ class MetaStreamProvider extends Component {
23
23
  if (this.tracker && this.tracker.recorder) {
24
24
  this.tracker.recorder.recordInteraction(type, e.nativeEvent);
25
25
  }
26
+
27
+ // Auto-capture all screen taps/clicks
28
+ if (type === 'touch_end' && this.tracker && typeof this.tracker.trackEvent === 'function') {
29
+ const { pageX, pageY, locationX, locationY, target, timestamp } = e.nativeEvent;
30
+
31
+ let componentType = 'unknown';
32
+ let testID = 'none';
33
+ let accessibilityLabel = 'none';
34
+ let textContent = '';
35
+ let componentPath = [];
36
+
37
+ // Traverse the React Fiber tree to extract component names, text, and testIDs
38
+ try {
39
+ let fiber = e._targetInst;
40
+ while (fiber) {
41
+ if (fiber.memoizedProps) {
42
+ if (testID === 'none' && fiber.memoizedProps.testID) testID = fiber.memoizedProps.testID;
43
+ if (accessibilityLabel === 'none' && fiber.memoizedProps.accessibilityLabel) accessibilityLabel = fiber.memoizedProps.accessibilityLabel;
44
+
45
+ // Extract text content if available
46
+ let children = fiber.memoizedProps.children;
47
+ if (!textContent && typeof children === 'string') {
48
+ textContent = children;
49
+ } else if (!textContent && Array.isArray(children)) {
50
+ const stringChildren = children.filter(c => typeof c === 'string').join('');
51
+ if (stringChildren) textContent = stringChildren;
52
+ }
53
+ }
54
+
55
+ // Try to get the component's name (e.g., 'Text', 'RCTImageView', 'Button')
56
+ if (fiber.elementType) {
57
+ let name = typeof fiber.elementType === 'string' ? fiber.elementType : (fiber.elementType.name || fiber.elementType.displayName);
58
+ if (name) {
59
+ if (name !== 'View' && name !== 'RCTView' && componentType === 'unknown') {
60
+ componentType = name;
61
+ }
62
+ // Build a path of component hierarchy (limit to nearest 5 interesting elements to avoid giant payload)
63
+ if (name !== 'View' && name !== 'RCTView' && componentPath.length < 5) {
64
+ componentPath.push(name);
65
+ }
66
+ }
67
+ }
68
+ fiber = fiber.return; // Traverse up the tree
69
+ }
70
+ } catch (err) {
71
+ // Ignore any errors if structure changes in future RN versions
72
+ }
73
+
74
+ this.tracker.trackEvent({
75
+ eventName: 'auto_click',
76
+ eventParameters: [
77
+ { key: 'x', value: Math.round(pageX || locationX || 0) },
78
+ { key: 'y', value: Math.round(pageY || locationY || 0) },
79
+ { key: 'target_node', value: target ? String(target) : 'unknown' },
80
+ { key: 'component_type', value: String(componentType) },
81
+ { key: 'component_path', value: componentPath.join(' > ') || 'unknown' },
82
+ { key: 'text_content', value: textContent ? String(textContent) : 'none' },
83
+ { key: 'test_id', value: String(testID) },
84
+ { key: 'accessibility_label', value: String(accessibilityLabel) },
85
+ { key: 'timestamp', value: timestamp || Date.now() }
86
+ ]
87
+ });
88
+ }
26
89
  }
27
90
 
28
91
  render() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iidrak-analytics-react",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "react native client for metastreamio",
5
5
  "peerDependencies": {
6
6
  "react-native-mmkv": ">=4.0.0",