iidrak-analytics-react 1.5.1 → 1.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.
- package/README.md +45 -4
- package/documentation.md +39 -0
- package/documentation.pdf +0 -0
- package/metastreamio/metastreamio.interface.js +79 -38
- package/metastreamio/metastreamio.provider.js +88 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# Iidrak Analytics (React Native)
|
|
2
2
|
|
|
3
|
+
**NPM Package**: [iidrak-analytics-react](https://www.npmjs.com/package/iidrak-analytics-react)
|
|
4
|
+
|
|
5
|
+
|
|
3
6
|
A powerful, offline-first analytics SDK for React Native applications that includes **Session Replay**. Track events, manage shopping carts, record user sessions (screens & touches), and persist data reliably even when the network is down.
|
|
4
7
|
|
|
5
8
|
## Features
|
|
6
9
|
|
|
7
10
|
- 📊 **Event Tracking**: Log custom events with arbitrary parameters.
|
|
11
|
+
- ⚡ **Automatic Interaction Tracking**: All screen taps/clicks are automatically captured with zero manual instrumentation.
|
|
12
|
+
- 🚀 **Optimized Bandwidth**: Uses a Minimal Payload architecture for auto-clicks to save battery, data, and database costs, only sending full telemetry on session starts and custom events.
|
|
8
13
|
- 🎥 **Session Replay**: Record screen interactions and touch events for playback analysis.
|
|
14
|
+
- 🎯 **Advanced Touch Context**: Automatically maps React Fiber tree to extract clicked text, component paths (e.g. `TouchableOpacity > Text`), and testing IDs.
|
|
9
15
|
- 🛒 **Cart Management**: Built-in shopping cart state management.
|
|
10
16
|
- 📴 **Offline Support**: Automatically queues events when offline and flushes them when connection is restored.
|
|
11
17
|
- 🆔 **User & Session Management**: Auto-generates session IDs and supports user identification.
|
|
12
|
-
- ⚡ **
|
|
18
|
+
- ⚡ **Zero Performance Impact**: Minimal overhead due to payload optimization and background processing.
|
|
13
19
|
|
|
14
20
|
## Installation
|
|
15
21
|
|
|
@@ -152,7 +158,20 @@ tracker.trackEvent({
|
|
|
152
158
|
```
|
|
153
159
|
*Note: `eventParameters` creates a flexible dictionary of data associated with the event.*
|
|
154
160
|
|
|
155
|
-
### 3.
|
|
161
|
+
### 3. Automatic Event Tracking (Zero Config)
|
|
162
|
+
|
|
163
|
+
The SDK automatically tracks all user interactions (clicks/taps) without any manual coding. When a user interacts with the app, the SDK captures:
|
|
164
|
+
|
|
165
|
+
- **Component Path**: The hierarchy of the clicked element (e.g., `TouchableOpacity > View > Text`).
|
|
166
|
+
- **Text Content**: Any text visible within the clicked element.
|
|
167
|
+
- **Test ID**: The `testID` prop, if defined (ideal for automated testing analysis).
|
|
168
|
+
- **Coordinates**: The exact `x, y` position of the tap.
|
|
169
|
+
- **Metadata**: Component type, accessibility labels, and timestamps.
|
|
170
|
+
|
|
171
|
+
**Performance Optimization (Minimal Payload):**
|
|
172
|
+
To ensure high performance and low data usage, `auto_click` events use a "Minimal Payload" architecture. They omit redundant device and application metadata, which is instead captured once during the `session_start` event.
|
|
173
|
+
|
|
174
|
+
### 4. Screen Tracking
|
|
156
175
|
|
|
157
176
|
Though `MetaStreamProvider` captures the video, you can manually log screen views for analytics:
|
|
158
177
|
|
|
@@ -160,7 +179,7 @@ Though `MetaStreamProvider` captures the video, you can manually log screen view
|
|
|
160
179
|
tracker.screen("HomeScreen", {});
|
|
161
180
|
```
|
|
162
181
|
|
|
163
|
-
###
|
|
182
|
+
### 5. Privacy & Redaction
|
|
164
183
|
|
|
165
184
|
To protect sensitive user data (PII) like passwords or credit card numbers during session replay, wrap your sensitive components with the `<Redact>` component.
|
|
166
185
|
|
|
@@ -178,7 +197,29 @@ import { Redact } from 'iidrak-analytics-react';
|
|
|
178
197
|
|
|
179
198
|
This ensures the wrapped area is covered by a privacy mask in the recorded video, while remaining fully functional for the user.
|
|
180
199
|
|
|
181
|
-
###
|
|
200
|
+
### 6. Suppressing Auto-Capture
|
|
201
|
+
|
|
202
|
+
By default, the SDK captures every tap on the application as an `auto_click` event. To prevent this for a specific component (e.g., when you are already manually tracking a custom event on that button), add the `autoTrack={false}` prop.
|
|
203
|
+
|
|
204
|
+
**Example:**
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
/*
|
|
208
|
+
This button will trigger 'login_success' but will NOT trigger
|
|
209
|
+
a redundant 'auto_click' event.
|
|
210
|
+
*/
|
|
211
|
+
<TouchableOpacity
|
|
212
|
+
onPress={handleLogin}
|
|
213
|
+
autoTrack={false}
|
|
214
|
+
>
|
|
215
|
+
<Text>Sign In</Text>
|
|
216
|
+
</TouchableOpacity>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
This ensures your server doesn't receive duplicate data for the same interaction.
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
### 6. Shopping Cart
|
|
182
223
|
|
|
183
224
|
Manage a persistent shopping cart state:
|
|
184
225
|
|
package/documentation.md
ADDED
|
@@ -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
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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.
|
|
500
|
-
|
|
501
|
-
.
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
.
|
|
506
|
-
|
|
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,94 @@ 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
|
+
let suppressAutoClick = false;
|
|
39
|
+
try {
|
|
40
|
+
let fiber = e._targetInst;
|
|
41
|
+
while (fiber) {
|
|
42
|
+
const name = typeof fiber.elementType === 'string' ? fiber.elementType : (fiber.elementType?.name || fiber.elementType?.displayName || fiber.type?.name || 'Unknown');
|
|
43
|
+
|
|
44
|
+
const p1 = fiber.memoizedProps || {};
|
|
45
|
+
const p2 = fiber.pendingProps || {};
|
|
46
|
+
|
|
47
|
+
const suppressValue =
|
|
48
|
+
p1['autoTrack'] ?? p1['auto-track'] ?? p1['data-auto-track'] ??
|
|
49
|
+
p2['autoTrack'] ?? p2['auto-track'] ?? p2['data-auto-track'];
|
|
50
|
+
|
|
51
|
+
if (suppressValue !== undefined) {
|
|
52
|
+
if (String(suppressValue) === 'false' || suppressValue === false) {
|
|
53
|
+
suppressAutoClick = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (fiber.memoizedProps) {
|
|
58
|
+
const tid = fiber.memoizedProps.testID || fiber.memoizedProps.accessibilityLabel;
|
|
59
|
+
if (testID === 'none' && tid) testID = tid;
|
|
60
|
+
|
|
61
|
+
// FAIL-SAFE: If we see 'no-track' in the testID or Label, suppress it!
|
|
62
|
+
if (typeof tid === 'string' && tid.toLowerCase().includes('no-track')) {
|
|
63
|
+
suppressAutoClick = true;
|
|
64
|
+
}
|
|
65
|
+
if (accessibilityLabel === 'none' && fiber.memoizedProps.accessibilityLabel) accessibilityLabel = fiber.memoizedProps.accessibilityLabel;
|
|
66
|
+
|
|
67
|
+
// Extract text content if available
|
|
68
|
+
let children = fiber.memoizedProps.children;
|
|
69
|
+
if (!textContent && typeof children === 'string') {
|
|
70
|
+
textContent = children;
|
|
71
|
+
} else if (!textContent && Array.isArray(children)) {
|
|
72
|
+
const stringChildren = children.filter(c => typeof c === 'string').join('');
|
|
73
|
+
if (stringChildren) textContent = stringChildren;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Try to get the component's name (e.g., 'Text', 'RCTImageView', 'Button')
|
|
78
|
+
if (fiber.elementType) {
|
|
79
|
+
if (name) {
|
|
80
|
+
if (name !== 'View' && name !== 'RCTView' && componentType === 'unknown') {
|
|
81
|
+
componentType = name;
|
|
82
|
+
}
|
|
83
|
+
// Build a path of component hierarchy (limit to nearest 5 interesting elements to avoid giant payload)
|
|
84
|
+
if (name !== 'View' && name !== 'RCTView' && componentPath.length < 5) {
|
|
85
|
+
componentPath.push(name);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
fiber = fiber.return; // Traverse up the tree
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
// Ignore any errors if structure changes in future RN versions
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (suppressAutoClick) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.tracker.trackEvent({
|
|
100
|
+
eventName: 'auto_click',
|
|
101
|
+
eventParameters: [
|
|
102
|
+
{ key: 'x', value: Math.round(pageX || locationX || 0) },
|
|
103
|
+
{ key: 'y', value: Math.round(pageY || locationY || 0) },
|
|
104
|
+
{ key: 'target_node', value: target ? String(target) : 'unknown' },
|
|
105
|
+
{ key: 'component_type', value: String(componentType) },
|
|
106
|
+
{ key: 'component_path', value: componentPath.join(' > ') || 'unknown' },
|
|
107
|
+
{ key: 'text_content', value: textContent ? String(textContent) : 'none' },
|
|
108
|
+
{ key: 'test_id', value: String(testID) },
|
|
109
|
+
{ key: 'accessibility_label', value: String(accessibilityLabel) },
|
|
110
|
+
{ key: 'timestamp', value: timestamp || Date.now() }
|
|
111
|
+
]
|
|
112
|
+
});
|
|
113
|
+
}
|
|
26
114
|
}
|
|
27
115
|
|
|
28
116
|
render() {
|