@axyle/expo-sdk 1.0.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/CONFIG.md +117 -0
- package/README.md +534 -0
- package/dist/client.d.ts +93 -0
- package/dist/client.js +416 -0
- package/dist/config.example.d.ts +8 -0
- package/dist/config.example.js +13 -0
- package/dist/configLoader.d.ts +10 -0
- package/dist/configLoader.js +22 -0
- package/dist/constants.d.ts +38 -0
- package/dist/constants.js +42 -0
- package/dist/context.d.ts +36 -0
- package/dist/context.js +216 -0
- package/dist/core.d.ts +9 -0
- package/dist/core.js +45 -0
- package/dist/hooks/useFeatureTracking.d.ts +34 -0
- package/dist/hooks/useFeatureTracking.js +60 -0
- package/dist/hooks/useOnboardingTracking.d.ts +85 -0
- package/dist/hooks/useOnboardingTracking.js +172 -0
- package/dist/hooks/useScreenTracking.d.ts +23 -0
- package/dist/hooks/useScreenTracking.js +52 -0
- package/dist/hooks/useScrollTracking.d.ts +39 -0
- package/dist/hooks/useScrollTracking.js +146 -0
- package/dist/index.d.ts +140 -0
- package/dist/index.js +171 -0
- package/dist/integrations/expoRouter.d.ts +42 -0
- package/dist/integrations/expoRouter.js +66 -0
- package/dist/integrations/reactNavigation.d.ts +27 -0
- package/dist/integrations/reactNavigation.js +101 -0
- package/dist/queue.d.ts +52 -0
- package/dist/queue.js +143 -0
- package/dist/session.d.ts +44 -0
- package/dist/session.js +139 -0
- package/dist/sessionAnalytics.d.ts +43 -0
- package/dist/sessionAnalytics.js +74 -0
- package/dist/storage.d.ts +67 -0
- package/dist/storage.js +210 -0
- package/dist/transport.d.ts +41 -0
- package/dist/transport.js +158 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.js +5 -0
- package/dist/utils.d.ts +39 -0
- package/dist/utils.js +116 -0
- package/package.json +44 -0
package/dist/context.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Device and app context collection using Expo APIs
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.ContextCollector = void 0;
|
|
43
|
+
const Application = __importStar(require("expo-application"));
|
|
44
|
+
const expo_constants_1 = __importDefault(require("expo-constants"));
|
|
45
|
+
const Device = __importStar(require("expo-device"));
|
|
46
|
+
const react_native_1 = require("react-native");
|
|
47
|
+
class ContextCollector {
|
|
48
|
+
/**
|
|
49
|
+
* Get device and app context
|
|
50
|
+
* Caches result for performance
|
|
51
|
+
*/
|
|
52
|
+
static async getContext(environment) {
|
|
53
|
+
// Return cached context if available
|
|
54
|
+
if (this.cachedContext) {
|
|
55
|
+
return this.cachedContext;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const { width, height } = react_native_1.Dimensions.get("window");
|
|
59
|
+
const scale = react_native_1.Dimensions.get("window").scale;
|
|
60
|
+
const context = {
|
|
61
|
+
app: {
|
|
62
|
+
name: Application.applicationName || "Unknown",
|
|
63
|
+
version: Application.nativeApplicationVersion || "0.0.0",
|
|
64
|
+
build: Application.nativeBuildVersion || "0",
|
|
65
|
+
namespace: Application.applicationId || "unknown",
|
|
66
|
+
},
|
|
67
|
+
device: {
|
|
68
|
+
id: await this.getDeviceId(),
|
|
69
|
+
manufacturer: Device.manufacturer || null,
|
|
70
|
+
model: Device.modelName || null,
|
|
71
|
+
name: Device.deviceName || null,
|
|
72
|
+
type: this.getDeviceType(),
|
|
73
|
+
brand: Device.brand || null,
|
|
74
|
+
},
|
|
75
|
+
os: {
|
|
76
|
+
name: react_native_1.Platform.OS,
|
|
77
|
+
version: react_native_1.Platform.Version.toString(),
|
|
78
|
+
},
|
|
79
|
+
screen: {
|
|
80
|
+
width,
|
|
81
|
+
height,
|
|
82
|
+
density: scale,
|
|
83
|
+
},
|
|
84
|
+
locale: this.getLocale(),
|
|
85
|
+
timezone: this.getTimezone(),
|
|
86
|
+
environment,
|
|
87
|
+
};
|
|
88
|
+
// Cache the context
|
|
89
|
+
this.cachedContext = context;
|
|
90
|
+
return context;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error("[Axyle] Failed to collect context:", error);
|
|
94
|
+
// Return minimal context on error
|
|
95
|
+
return this.getMinimalContext(environment);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get device ID (may be null on some platforms)
|
|
100
|
+
*/
|
|
101
|
+
static async getDeviceId() {
|
|
102
|
+
try {
|
|
103
|
+
// Use Android ID on Android, IDFV on iOS
|
|
104
|
+
if (react_native_1.Platform.OS === "android") {
|
|
105
|
+
return expo_constants_1.default.sessionId || null;
|
|
106
|
+
}
|
|
107
|
+
else if (react_native_1.Platform.OS === "ios") {
|
|
108
|
+
return expo_constants_1.default.sessionId || null;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Determine device type
|
|
118
|
+
*/
|
|
119
|
+
static getDeviceType() {
|
|
120
|
+
try {
|
|
121
|
+
if (Device.deviceType === Device.DeviceType.PHONE) {
|
|
122
|
+
return "PHONE";
|
|
123
|
+
}
|
|
124
|
+
else if (Device.deviceType === Device.DeviceType.TABLET) {
|
|
125
|
+
return "TABLET";
|
|
126
|
+
}
|
|
127
|
+
else if (Device.deviceType === Device.DeviceType.DESKTOP) {
|
|
128
|
+
return "DESKTOP";
|
|
129
|
+
}
|
|
130
|
+
else if (Device.deviceType === Device.DeviceType.TV) {
|
|
131
|
+
return "TV";
|
|
132
|
+
}
|
|
133
|
+
return "UNKNOWN";
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
return "UNKNOWN";
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get device locale
|
|
141
|
+
*/
|
|
142
|
+
static getLocale() {
|
|
143
|
+
try {
|
|
144
|
+
// Try to get locale from Intl API (works on most platforms)
|
|
145
|
+
if (typeof Intl !== "undefined" && Intl.DateTimeFormat) {
|
|
146
|
+
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
147
|
+
if (locale)
|
|
148
|
+
return locale;
|
|
149
|
+
}
|
|
150
|
+
return "en-US";
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return "en-US";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get device timezone
|
|
158
|
+
*/
|
|
159
|
+
static getTimezone() {
|
|
160
|
+
try {
|
|
161
|
+
// Get timezone offset in minutes
|
|
162
|
+
const offset = new Date().getTimezoneOffset();
|
|
163
|
+
const hours = Math.abs(Math.floor(offset / 60));
|
|
164
|
+
const minutes = Math.abs(offset % 60);
|
|
165
|
+
const sign = offset <= 0 ? "+" : "-";
|
|
166
|
+
return `UTC${sign}${hours.toString().padStart(2, "0")}:${minutes
|
|
167
|
+
.toString()
|
|
168
|
+
.padStart(2, "0")}`;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
return "UTC";
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get minimal context as fallback
|
|
176
|
+
*/
|
|
177
|
+
static getMinimalContext(environment) {
|
|
178
|
+
const { width, height } = react_native_1.Dimensions.get("window");
|
|
179
|
+
return {
|
|
180
|
+
app: {
|
|
181
|
+
name: "Unknown",
|
|
182
|
+
version: "0.0.0",
|
|
183
|
+
build: "0",
|
|
184
|
+
namespace: "unknown",
|
|
185
|
+
},
|
|
186
|
+
device: {
|
|
187
|
+
id: null,
|
|
188
|
+
manufacturer: null,
|
|
189
|
+
model: null,
|
|
190
|
+
name: null,
|
|
191
|
+
type: "UNKNOWN",
|
|
192
|
+
brand: null,
|
|
193
|
+
},
|
|
194
|
+
os: {
|
|
195
|
+
name: react_native_1.Platform.OS,
|
|
196
|
+
version: react_native_1.Platform.Version.toString(),
|
|
197
|
+
},
|
|
198
|
+
screen: {
|
|
199
|
+
width,
|
|
200
|
+
height,
|
|
201
|
+
density: 1,
|
|
202
|
+
},
|
|
203
|
+
locale: "en-US",
|
|
204
|
+
timezone: "UTC",
|
|
205
|
+
environment,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Clear cached context (useful for testing or when app info changes)
|
|
210
|
+
*/
|
|
211
|
+
static clearCache() {
|
|
212
|
+
this.cachedContext = null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.ContextCollector = ContextCollector;
|
|
216
|
+
ContextCollector.cachedContext = null;
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core SDK module - exports the singleton client instance
|
|
3
|
+
* This module is used to avoid circular dependencies between index.ts and integrations
|
|
4
|
+
*/
|
|
5
|
+
import { AxyleClient } from "./client";
|
|
6
|
+
import type { EventProperties } from "./types";
|
|
7
|
+
export declare const client: AxyleClient;
|
|
8
|
+
export declare function track(eventName: string, properties?: EventProperties): void;
|
|
9
|
+
export declare function isInitialized(): boolean;
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Core SDK module - exports the singleton client instance
|
|
4
|
+
* This module is used to avoid circular dependencies between index.ts and integrations
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.client = void 0;
|
|
8
|
+
exports.track = track;
|
|
9
|
+
exports.isInitialized = isInitialized;
|
|
10
|
+
const client_1 = require("./client");
|
|
11
|
+
// Create and export singleton instance
|
|
12
|
+
exports.client = new client_1.AxyleClient();
|
|
13
|
+
// Maximum retries when SDK is not initialized
|
|
14
|
+
const MAX_INIT_RETRIES = 50; // 5 seconds max (50 * 100ms)
|
|
15
|
+
// Export a track function that can be used by integrations
|
|
16
|
+
// Note: client.track is async, but we call it without awaiting to avoid blocking
|
|
17
|
+
function track(eventName, properties) {
|
|
18
|
+
// Check if initialized before tracking
|
|
19
|
+
if (!exports.client.isInitialized) {
|
|
20
|
+
// Queue the event to be tracked once initialized
|
|
21
|
+
let retryCount = 0;
|
|
22
|
+
const checkAndTrack = () => {
|
|
23
|
+
if (exports.client.isInitialized) {
|
|
24
|
+
exports.client.track(eventName, properties).catch(() => {
|
|
25
|
+
// Silently handle errors - track already handles errors internally
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else if (retryCount < MAX_INIT_RETRIES) {
|
|
29
|
+
retryCount++;
|
|
30
|
+
setTimeout(checkAndTrack, 100);
|
|
31
|
+
}
|
|
32
|
+
// After max retries, silently drop the event
|
|
33
|
+
};
|
|
34
|
+
checkAndTrack();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Fire and forget - track is async but we don't need to wait for it
|
|
38
|
+
exports.client.track(eventName, properties).catch(() => {
|
|
39
|
+
// Silently handle errors - track already handles errors internally
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// Export isInitialized getter for integrations to check
|
|
43
|
+
function isInitialized() {
|
|
44
|
+
return exports.client.isInitialized;
|
|
45
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React hook and utilities for feature usage tracking
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Track when a feature is used
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* function ProfileScreen() {
|
|
10
|
+
* const handleEditProfile = () => {
|
|
11
|
+
* trackFeatureUsage('edit_profile', { screen: 'Profile' });
|
|
12
|
+
* // ... edit profile logic
|
|
13
|
+
* };
|
|
14
|
+
*
|
|
15
|
+
* return <Button onPress={handleEditProfile}>Edit Profile</Button>;
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function trackFeatureUsage(featureName: string, properties?: Record<string, any>): void;
|
|
20
|
+
/**
|
|
21
|
+
* Track feature usage with additional context
|
|
22
|
+
*/
|
|
23
|
+
export declare function trackFeatureUsageWithContext(featureName: string, context: {
|
|
24
|
+
screen?: string;
|
|
25
|
+
category?: string;
|
|
26
|
+
action?: string;
|
|
27
|
+
value?: number | string;
|
|
28
|
+
[key: string]: any;
|
|
29
|
+
}): void;
|
|
30
|
+
/**
|
|
31
|
+
* Hook to automatically track feature usage on component mount
|
|
32
|
+
* Useful for tracking feature discovery/views
|
|
33
|
+
*/
|
|
34
|
+
export declare function useFeatureViewTracking(featureName: string, properties?: Record<string, any>): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* React hook and utilities for feature usage tracking
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.trackFeatureUsage = trackFeatureUsage;
|
|
7
|
+
exports.trackFeatureUsageWithContext = trackFeatureUsageWithContext;
|
|
8
|
+
exports.useFeatureViewTracking = useFeatureViewTracking;
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const core_1 = require("../core");
|
|
11
|
+
/**
|
|
12
|
+
* Track when a feature is used
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* function ProfileScreen() {
|
|
17
|
+
* const handleEditProfile = () => {
|
|
18
|
+
* trackFeatureUsage('edit_profile', { screen: 'Profile' });
|
|
19
|
+
* // ... edit profile logic
|
|
20
|
+
* };
|
|
21
|
+
*
|
|
22
|
+
* return <Button onPress={handleEditProfile}>Edit Profile</Button>;
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function trackFeatureUsage(featureName, properties) {
|
|
27
|
+
(0, core_1.track)("Feature Used", {
|
|
28
|
+
feature: featureName,
|
|
29
|
+
...properties,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Track feature usage with additional context
|
|
34
|
+
*/
|
|
35
|
+
function trackFeatureUsageWithContext(featureName, context) {
|
|
36
|
+
(0, core_1.track)("Feature Used", {
|
|
37
|
+
feature: featureName,
|
|
38
|
+
feature_category: context.category,
|
|
39
|
+
feature_action: context.action,
|
|
40
|
+
feature_value: context.value,
|
|
41
|
+
screen: context.screen,
|
|
42
|
+
...context,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Hook to automatically track feature usage on component mount
|
|
47
|
+
* Useful for tracking feature discovery/views
|
|
48
|
+
*/
|
|
49
|
+
function useFeatureViewTracking(featureName, properties) {
|
|
50
|
+
const hasTracked = (0, react_1.useRef)(false);
|
|
51
|
+
(0, react_1.useEffect)(() => {
|
|
52
|
+
if (!hasTracked.current) {
|
|
53
|
+
(0, core_1.track)("Feature Viewed", {
|
|
54
|
+
feature: featureName,
|
|
55
|
+
...properties,
|
|
56
|
+
});
|
|
57
|
+
hasTracked.current = true;
|
|
58
|
+
}
|
|
59
|
+
}, [featureName, properties]);
|
|
60
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React hook for user flow tracking
|
|
3
|
+
* Generic flow tracking that can be used for onboarding, checkout, tutorials, etc.
|
|
4
|
+
*/
|
|
5
|
+
export interface FlowTrackingOptions {
|
|
6
|
+
/** Flow identifier (e.g., 'onboarding', 'checkout', 'tutorial') */
|
|
7
|
+
flowId: string;
|
|
8
|
+
/** Flow type/category (e.g., 'onboarding', 'checkout', 'tutorial', 'feature-discovery') */
|
|
9
|
+
flowType?: string;
|
|
10
|
+
/** Current step/screen in the flow */
|
|
11
|
+
step: number;
|
|
12
|
+
/** Total number of steps in the flow */
|
|
13
|
+
totalSteps: number;
|
|
14
|
+
/** Step/screen name */
|
|
15
|
+
stepName: string;
|
|
16
|
+
/** Additional properties to track */
|
|
17
|
+
properties?: Record<string, any>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Hook to track user flow progress and drop-offs
|
|
21
|
+
* Works for any multi-step flow: onboarding, checkout, tutorials, etc.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* // Onboarding example
|
|
26
|
+
* function OnboardingStep1() {
|
|
27
|
+
* useFlowTracking({
|
|
28
|
+
* flowId: 'welcome-onboarding',
|
|
29
|
+
* flowType: 'onboarding',
|
|
30
|
+
* step: 1,
|
|
31
|
+
* totalSteps: 5,
|
|
32
|
+
* stepName: 'Welcome'
|
|
33
|
+
* });
|
|
34
|
+
* return <View>...</View>;
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* // Checkout example
|
|
38
|
+
* function CheckoutStep2() {
|
|
39
|
+
* useFlowTracking({
|
|
40
|
+
* flowId: 'checkout-process',
|
|
41
|
+
* flowType: 'checkout',
|
|
42
|
+
* step: 2,
|
|
43
|
+
* totalSteps: 4,
|
|
44
|
+
* stepName: 'Shipping Info'
|
|
45
|
+
* });
|
|
46
|
+
* return <View>...</View>;
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function useFlowTracking(options: FlowTrackingOptions): void;
|
|
51
|
+
/**
|
|
52
|
+
* Helper function to track flow completion
|
|
53
|
+
* Call this when user completes the entire flow
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* trackFlowCompleted('welcome-onboarding', 5, { flowType: 'onboarding' });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function trackFlowCompleted(flowId: string, totalSteps: number, properties?: Record<string, any>): void;
|
|
61
|
+
/**
|
|
62
|
+
* Helper function to track flow abandonment
|
|
63
|
+
* Call this when user explicitly exits a flow
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* trackFlowAbandoned('checkout-process', 2, 4, {
|
|
68
|
+
* flowType: 'checkout',
|
|
69
|
+
* stepName: 'Shipping Info'
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function trackFlowAbandoned(flowId: string, currentStep: number, totalSteps: number, properties?: Record<string, any>): void;
|
|
74
|
+
/**
|
|
75
|
+
* @deprecated Use useFlowTracking instead
|
|
76
|
+
*/
|
|
77
|
+
export declare const useOnboardingTracking: typeof useFlowTracking;
|
|
78
|
+
/**
|
|
79
|
+
* @deprecated Use trackFlowCompleted instead
|
|
80
|
+
*/
|
|
81
|
+
export declare const trackOnboardingCompleted: (flowId: string, totalSteps: number, properties?: Record<string, any>) => void;
|
|
82
|
+
/**
|
|
83
|
+
* @deprecated Use trackFlowAbandoned instead
|
|
84
|
+
*/
|
|
85
|
+
export declare const trackOnboardingAbandoned: (flowId: string, currentStep: number, totalSteps: number, properties?: Record<string, any>) => void;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* React hook for user flow tracking
|
|
4
|
+
* Generic flow tracking that can be used for onboarding, checkout, tutorials, etc.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.trackOnboardingAbandoned = exports.trackOnboardingCompleted = exports.useOnboardingTracking = void 0;
|
|
8
|
+
exports.useFlowTracking = useFlowTracking;
|
|
9
|
+
exports.trackFlowCompleted = trackFlowCompleted;
|
|
10
|
+
exports.trackFlowAbandoned = trackFlowAbandoned;
|
|
11
|
+
const react_1 = require("react");
|
|
12
|
+
const core_1 = require("../core");
|
|
13
|
+
/**
|
|
14
|
+
* Hook to track user flow progress and drop-offs
|
|
15
|
+
* Works for any multi-step flow: onboarding, checkout, tutorials, etc.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* // Onboarding example
|
|
20
|
+
* function OnboardingStep1() {
|
|
21
|
+
* useFlowTracking({
|
|
22
|
+
* flowId: 'welcome-onboarding',
|
|
23
|
+
* flowType: 'onboarding',
|
|
24
|
+
* step: 1,
|
|
25
|
+
* totalSteps: 5,
|
|
26
|
+
* stepName: 'Welcome'
|
|
27
|
+
* });
|
|
28
|
+
* return <View>...</View>;
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* // Checkout example
|
|
32
|
+
* function CheckoutStep2() {
|
|
33
|
+
* useFlowTracking({
|
|
34
|
+
* flowId: 'checkout-process',
|
|
35
|
+
* flowType: 'checkout',
|
|
36
|
+
* step: 2,
|
|
37
|
+
* totalSteps: 4,
|
|
38
|
+
* stepName: 'Shipping Info'
|
|
39
|
+
* });
|
|
40
|
+
* return <View>...</View>;
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
function useFlowTracking(options) {
|
|
45
|
+
const { flowId, flowType, step, totalSteps, stepName, properties = {}, } = options;
|
|
46
|
+
const hasTracked = (0, react_1.useRef)(false);
|
|
47
|
+
const stepStartTime = (0, react_1.useRef)(null);
|
|
48
|
+
const flowStartTime = (0, react_1.useRef)(null);
|
|
49
|
+
(0, react_1.useEffect)(() => {
|
|
50
|
+
const startTime = Date.now();
|
|
51
|
+
stepStartTime.current = startTime;
|
|
52
|
+
// Track flow start on first step
|
|
53
|
+
if (step === 1 && !hasTracked.current) {
|
|
54
|
+
flowStartTime.current = startTime;
|
|
55
|
+
(0, core_1.track)("Flow Started", {
|
|
56
|
+
flow_id: flowId,
|
|
57
|
+
flow_type: flowType || "custom",
|
|
58
|
+
flow_start_time: startTime,
|
|
59
|
+
total_steps: totalSteps,
|
|
60
|
+
...properties,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// Track step view
|
|
64
|
+
if (!hasTracked.current) {
|
|
65
|
+
(0, core_1.track)("Flow Step Viewed", {
|
|
66
|
+
flow_id: flowId,
|
|
67
|
+
flow_type: flowType || "custom",
|
|
68
|
+
step: step,
|
|
69
|
+
step_name: stepName,
|
|
70
|
+
total_steps: totalSteps,
|
|
71
|
+
step_start_time: startTime,
|
|
72
|
+
progress_percentage: Math.round((step / totalSteps) * 100),
|
|
73
|
+
...properties,
|
|
74
|
+
});
|
|
75
|
+
hasTracked.current = true;
|
|
76
|
+
}
|
|
77
|
+
// Track step completion and potential drop-off when component unmounts
|
|
78
|
+
return () => {
|
|
79
|
+
if (stepStartTime.current !== null) {
|
|
80
|
+
const endTime = Date.now();
|
|
81
|
+
const stepDuration = endTime - stepStartTime.current;
|
|
82
|
+
// Track step completion
|
|
83
|
+
(0, core_1.track)("Flow Step Completed", {
|
|
84
|
+
flow_id: flowId,
|
|
85
|
+
flow_type: flowType || "custom",
|
|
86
|
+
step: step,
|
|
87
|
+
step_name: stepName,
|
|
88
|
+
step_duration_ms: stepDuration,
|
|
89
|
+
step_duration_seconds: Math.round(stepDuration / 1000),
|
|
90
|
+
progress_percentage: Math.round((step / totalSteps) * 100),
|
|
91
|
+
...properties,
|
|
92
|
+
});
|
|
93
|
+
// If not the last step, this could be a drop-off
|
|
94
|
+
if (step < totalSteps) {
|
|
95
|
+
(0, core_1.track)("Flow Step Exited", {
|
|
96
|
+
flow_id: flowId,
|
|
97
|
+
flow_type: flowType || "custom",
|
|
98
|
+
step: step,
|
|
99
|
+
step_name: stepName,
|
|
100
|
+
next_step: step + 1,
|
|
101
|
+
is_drop_off: true,
|
|
102
|
+
step_duration_ms: stepDuration,
|
|
103
|
+
...properties,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}, [flowId, flowType, step, totalSteps, stepName, properties]);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Helper function to track flow completion
|
|
112
|
+
* Call this when user completes the entire flow
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```tsx
|
|
116
|
+
* trackFlowCompleted('welcome-onboarding', 5, { flowType: 'onboarding' });
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
function trackFlowCompleted(flowId, totalSteps, properties) {
|
|
120
|
+
(0, core_1.track)("Flow Completed", {
|
|
121
|
+
flow_id: flowId,
|
|
122
|
+
flow_type: properties?.flowType || "custom",
|
|
123
|
+
total_steps: totalSteps,
|
|
124
|
+
completed_at: Date.now(),
|
|
125
|
+
...properties,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Helper function to track flow abandonment
|
|
130
|
+
* Call this when user explicitly exits a flow
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```tsx
|
|
134
|
+
* trackFlowAbandoned('checkout-process', 2, 4, {
|
|
135
|
+
* flowType: 'checkout',
|
|
136
|
+
* stepName: 'Shipping Info'
|
|
137
|
+
* });
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
function trackFlowAbandoned(flowId, currentStep, totalSteps, properties) {
|
|
141
|
+
(0, core_1.track)("Flow Abandoned", {
|
|
142
|
+
flow_id: flowId,
|
|
143
|
+
flow_type: properties?.flowType || "custom",
|
|
144
|
+
abandoned_at_step: currentStep,
|
|
145
|
+
abandoned_step_name: properties?.stepName || `Step ${currentStep}`,
|
|
146
|
+
total_steps: totalSteps,
|
|
147
|
+
progress_percentage: Math.round((currentStep / totalSteps) * 100),
|
|
148
|
+
abandoned_at: Date.now(),
|
|
149
|
+
...properties,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// Legacy exports for backward compatibility (deprecated)
|
|
153
|
+
/**
|
|
154
|
+
* @deprecated Use useFlowTracking instead
|
|
155
|
+
*/
|
|
156
|
+
exports.useOnboardingTracking = useFlowTracking;
|
|
157
|
+
/**
|
|
158
|
+
* @deprecated Use trackFlowCompleted instead
|
|
159
|
+
*/
|
|
160
|
+
const trackOnboardingCompleted = (flowId, totalSteps, properties) => trackFlowCompleted(flowId, totalSteps, {
|
|
161
|
+
...properties,
|
|
162
|
+
flowType: "onboarding",
|
|
163
|
+
});
|
|
164
|
+
exports.trackOnboardingCompleted = trackOnboardingCompleted;
|
|
165
|
+
/**
|
|
166
|
+
* @deprecated Use trackFlowAbandoned instead
|
|
167
|
+
*/
|
|
168
|
+
const trackOnboardingAbandoned = (flowId, currentStep, totalSteps, properties) => trackFlowAbandoned(flowId, currentStep, totalSteps, {
|
|
169
|
+
...properties,
|
|
170
|
+
flowType: "onboarding",
|
|
171
|
+
});
|
|
172
|
+
exports.trackOnboardingAbandoned = trackOnboardingAbandoned;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React hook for automatic screen tracking with time tracking
|
|
3
|
+
*/
|
|
4
|
+
export interface ScreenTrackingOptions {
|
|
5
|
+
/** Screen name to track */
|
|
6
|
+
screenName: string;
|
|
7
|
+
/** Additional properties to track with screen view */
|
|
8
|
+
properties?: Record<string, any>;
|
|
9
|
+
/** Whether to track time spent on screen (default: true) */
|
|
10
|
+
trackTime?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Hook to automatically track screen views with time tracking
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* function HomeScreen() {
|
|
18
|
+
* useScreenTracking({ screenName: 'Home' });
|
|
19
|
+
* return <View>...</View>;
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function useScreenTracking(options: ScreenTrackingOptions): void;
|