@linkforty/mobile-sdk-react-native 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.
@@ -0,0 +1,258 @@
1
+ /**
2
+ * LinkFortySDK - Main SDK class for LinkForty deep linking and attribution
3
+ */
4
+ import AsyncStorage from '@react-native-async-storage/async-storage';
5
+ import { FingerprintCollector } from './FingerprintCollector';
6
+ import { DeepLinkHandler } from './DeepLinkHandler';
7
+ const STORAGE_KEYS = {
8
+ INSTALL_ID: '@linkforty:install_id',
9
+ INSTALL_DATA: '@linkforty:install_data',
10
+ FIRST_LAUNCH: '@linkforty:first_launch',
11
+ };
12
+ export class LinkFortySDK {
13
+ config = null;
14
+ deepLinkHandler = null;
15
+ deferredDeepLinkCallback = null;
16
+ installId = null;
17
+ initialized = false;
18
+ /**
19
+ * Initialize the SDK
20
+ */
21
+ async init(config) {
22
+ if (this.initialized) {
23
+ console.warn('LinkForty SDK already initialized');
24
+ return;
25
+ }
26
+ this.config = config;
27
+ if (config.debug) {
28
+ console.log('[LinkForty] Initializing SDK with config:', config);
29
+ }
30
+ // Check if this is first launch
31
+ const isFirstLaunch = await this.isFirstLaunch();
32
+ if (isFirstLaunch) {
33
+ // Report install and get deferred deep link
34
+ await this.reportInstall();
35
+ }
36
+ else {
37
+ // Load cached install data
38
+ await this.loadInstallData();
39
+ }
40
+ // Initialize deep link handler for direct deep links
41
+ this.deepLinkHandler = new DeepLinkHandler();
42
+ this.initialized = true;
43
+ if (config.debug) {
44
+ console.log('[LinkForty] SDK initialized successfully');
45
+ }
46
+ }
47
+ /**
48
+ * Get install attribution data (deferred deep link)
49
+ */
50
+ async getInstallData() {
51
+ const cached = await AsyncStorage.getItem(STORAGE_KEYS.INSTALL_DATA);
52
+ if (cached) {
53
+ return JSON.parse(cached);
54
+ }
55
+ return null;
56
+ }
57
+ /**
58
+ * Register callback for deferred deep links (new installs)
59
+ */
60
+ onDeferredDeepLink(callback) {
61
+ this.deferredDeepLinkCallback = callback;
62
+ // If we already have install data, call the callback immediately
63
+ this.getInstallData().then((data) => {
64
+ if (data && this.deferredDeepLinkCallback) {
65
+ this.deferredDeepLinkCallback(data);
66
+ }
67
+ });
68
+ }
69
+ /**
70
+ * Register callback for direct deep links (existing users)
71
+ */
72
+ onDeepLink(callback) {
73
+ if (!this.config) {
74
+ throw new Error('SDK not initialized. Call init() first.');
75
+ }
76
+ if (!this.deepLinkHandler) {
77
+ this.deepLinkHandler = new DeepLinkHandler();
78
+ }
79
+ this.deepLinkHandler.initialize(this.config.baseUrl, callback);
80
+ }
81
+ /**
82
+ * Track in-app event
83
+ */
84
+ async trackEvent(name, properties) {
85
+ if (!this.config) {
86
+ throw new Error('SDK not initialized. Call init() first.');
87
+ }
88
+ if (!this.installId) {
89
+ console.warn('[LinkForty] Cannot track event: No install ID available');
90
+ return;
91
+ }
92
+ // Backend expects: { installId, eventName, eventData, timestamp }
93
+ const requestBody = {
94
+ installId: this.installId,
95
+ eventName: name,
96
+ eventData: properties || {},
97
+ timestamp: new Date().toISOString(),
98
+ };
99
+ try {
100
+ const response = await this.apiRequest('/api/sdk/v1/event', {
101
+ method: 'POST',
102
+ body: JSON.stringify(requestBody),
103
+ });
104
+ if (this.config.debug) {
105
+ console.log('[LinkForty] Event tracked:', name, response);
106
+ }
107
+ }
108
+ catch (error) {
109
+ console.error('[LinkForty] Failed to track event:', error);
110
+ }
111
+ }
112
+ /**
113
+ * Get install ID
114
+ */
115
+ async getInstallId() {
116
+ if (this.installId) {
117
+ return this.installId;
118
+ }
119
+ const cached = await AsyncStorage.getItem(STORAGE_KEYS.INSTALL_ID);
120
+ if (cached) {
121
+ this.installId = cached;
122
+ }
123
+ return this.installId;
124
+ }
125
+ /**
126
+ * Clear all cached data (for testing)
127
+ */
128
+ async clearData() {
129
+ await AsyncStorage.multiRemove([
130
+ STORAGE_KEYS.INSTALL_ID,
131
+ STORAGE_KEYS.INSTALL_DATA,
132
+ STORAGE_KEYS.FIRST_LAUNCH,
133
+ ]);
134
+ this.installId = null;
135
+ if (this.config?.debug) {
136
+ console.log('[LinkForty] All data cleared');
137
+ }
138
+ }
139
+ /**
140
+ * Check if this is the first app launch
141
+ */
142
+ async isFirstLaunch() {
143
+ const firstLaunchFlag = await AsyncStorage.getItem(STORAGE_KEYS.FIRST_LAUNCH);
144
+ if (!firstLaunchFlag) {
145
+ await AsyncStorage.setItem(STORAGE_KEYS.FIRST_LAUNCH, 'true');
146
+ return true;
147
+ }
148
+ return false;
149
+ }
150
+ /**
151
+ * Report app installation to LinkForty backend
152
+ */
153
+ async reportInstall() {
154
+ if (!this.config) {
155
+ return;
156
+ }
157
+ try {
158
+ // Collect device fingerprint
159
+ const fingerprint = await FingerprintCollector.collect();
160
+ if (this.config.debug) {
161
+ console.log('[LinkForty] Reporting install with fingerprint:', fingerprint);
162
+ }
163
+ // Parse screen resolution (e.g., "1080x1920" -> [1080, 1920])
164
+ const [screenWidth, screenHeight] = fingerprint.screenResolution
165
+ .split('x')
166
+ .map(Number);
167
+ // Convert attribution window from days to hours
168
+ const attributionWindowHours = (this.config.attributionWindow || 7) * 24;
169
+ // Call install endpoint with flattened structure matching backend contract
170
+ const response = await this.apiRequest('/api/sdk/v1/install', {
171
+ method: 'POST',
172
+ body: JSON.stringify({
173
+ userAgent: fingerprint.userAgent,
174
+ timezone: fingerprint.timezone,
175
+ language: fingerprint.language,
176
+ screenWidth,
177
+ screenHeight,
178
+ platform: fingerprint.platform,
179
+ platformVersion: fingerprint.osVersion,
180
+ deviceId: undefined, // Optional: Can add IDFA/GAID if available
181
+ attributionWindowHours,
182
+ }),
183
+ });
184
+ if (this.config.debug) {
185
+ console.log('[LinkForty] Install response:', response);
186
+ }
187
+ // Store install ID
188
+ if (response.installId) {
189
+ this.installId = response.installId;
190
+ await AsyncStorage.setItem(STORAGE_KEYS.INSTALL_ID, response.installId);
191
+ }
192
+ // If attributed, store deep link data
193
+ if (response.attributed && response.deepLinkData) {
194
+ await AsyncStorage.setItem(STORAGE_KEYS.INSTALL_DATA, JSON.stringify(response.deepLinkData));
195
+ // Call deferred deep link callback if registered
196
+ if (this.deferredDeepLinkCallback) {
197
+ this.deferredDeepLinkCallback(response.deepLinkData);
198
+ }
199
+ if (this.config.debug) {
200
+ console.log('[LinkForty] Install attributed with confidence:', response.confidenceScore);
201
+ console.log('[LinkForty] Matched factors:', response.matchedFactors);
202
+ }
203
+ }
204
+ else {
205
+ if (this.config.debug) {
206
+ console.log('[LinkForty] Install not attributed (organic install)');
207
+ }
208
+ // Call callback with null to indicate organic install
209
+ if (this.deferredDeepLinkCallback) {
210
+ this.deferredDeepLinkCallback(null);
211
+ }
212
+ }
213
+ }
214
+ catch (error) {
215
+ console.error('[LinkForty] Failed to report install:', error);
216
+ // Call callback with null on error
217
+ if (this.deferredDeepLinkCallback) {
218
+ this.deferredDeepLinkCallback(null);
219
+ }
220
+ }
221
+ }
222
+ /**
223
+ * Load cached install data
224
+ */
225
+ async loadInstallData() {
226
+ this.installId = await AsyncStorage.getItem(STORAGE_KEYS.INSTALL_ID);
227
+ }
228
+ /**
229
+ * Make API request to LinkForty backend
230
+ */
231
+ async apiRequest(endpoint, options = {}) {
232
+ if (!this.config) {
233
+ throw new Error('SDK not initialized');
234
+ }
235
+ const url = `${this.config.baseUrl}${endpoint}`;
236
+ const headers = {
237
+ 'Content-Type': 'application/json',
238
+ };
239
+ // Add API key if provided
240
+ if (this.config.apiKey) {
241
+ headers['Authorization'] = `Bearer ${this.config.apiKey}`;
242
+ }
243
+ const response = await fetch(url, {
244
+ ...options,
245
+ headers: {
246
+ ...headers,
247
+ ...options.headers,
248
+ },
249
+ });
250
+ if (!response.ok) {
251
+ const error = await response.json().catch(() => ({ message: 'Network error' }));
252
+ throw new Error(error.message || `HTTP ${response.status}`);
253
+ }
254
+ return response.json();
255
+ }
256
+ }
257
+ // Export singleton instance
258
+ export default new LinkFortySDK();
@@ -0,0 +1,13 @@
1
+ /**
2
+ * LinkForty React Native SDK
3
+ *
4
+ * Deep linking and mobile attribution for React Native apps
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ export { default } from './LinkFortySDK';
9
+ export { LinkFortySDK } from './LinkFortySDK';
10
+ export { FingerprintCollector } from './FingerprintCollector';
11
+ export { DeepLinkHandler } from './DeepLinkHandler';
12
+ export type { LinkFortyConfig, DeviceFingerprint, DeepLinkData, InstallAttributionResponse, EventData, DeferredDeepLinkCallback, DeepLinkCallback, } from './types';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAGzC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,0BAA0B,EAC1B,SAAS,EACT,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * LinkForty React Native SDK
3
+ *
4
+ * Deep linking and mobile attribution for React Native apps
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ // Export singleton SDK instance as default
9
+ export { default } from './LinkFortySDK';
10
+ // Export classes
11
+ export { LinkFortySDK } from './LinkFortySDK';
12
+ export { FingerprintCollector } from './FingerprintCollector';
13
+ export { DeepLinkHandler } from './DeepLinkHandler';
@@ -0,0 +1,106 @@
1
+ /**
2
+ * LinkForty React Native SDK Type Definitions
3
+ */
4
+ /**
5
+ * Configuration options for initializing the LinkForty SDK
6
+ */
7
+ export interface LinkFortyConfig {
8
+ /** Base URL of your LinkForty instance (e.g., 'https://go.yourdomain.com') */
9
+ baseUrl: string;
10
+ /** Optional API key for Cloud authentication */
11
+ apiKey?: string;
12
+ /** Enable debug logging */
13
+ debug?: boolean;
14
+ /** Custom attribution window in days (default: 7) */
15
+ attributionWindow?: number;
16
+ }
17
+ /**
18
+ * Device fingerprint data collected for attribution matching
19
+ */
20
+ export interface DeviceFingerprint {
21
+ /** IP address (server-side) */
22
+ ip?: string;
23
+ /** User agent string */
24
+ userAgent: string;
25
+ /** Device timezone */
26
+ timezone: string;
27
+ /** Device language */
28
+ language: string;
29
+ /** Screen resolution (width x height) */
30
+ screenResolution: string;
31
+ /** Device platform (ios/android) */
32
+ platform: string;
33
+ /** Device model */
34
+ deviceModel?: string;
35
+ /** OS version */
36
+ osVersion?: string;
37
+ /** App version */
38
+ appVersion?: string;
39
+ }
40
+ /**
41
+ * Deep link data containing URLs and parameters
42
+ */
43
+ export interface DeepLinkData {
44
+ /** Short code that was clicked */
45
+ shortCode: string;
46
+ /** iOS app URL */
47
+ iosUrl?: string;
48
+ /** Android app URL */
49
+ androidUrl?: string;
50
+ /** Web fallback URL */
51
+ webUrl?: string;
52
+ /** UTM parameters */
53
+ utmParameters?: {
54
+ source?: string;
55
+ medium?: string;
56
+ campaign?: string;
57
+ term?: string;
58
+ content?: string;
59
+ };
60
+ /** Custom query parameters */
61
+ customParameters?: Record<string, string>;
62
+ /** Click timestamp */
63
+ clickedAt?: string;
64
+ /** Link ID */
65
+ linkId?: string;
66
+ }
67
+ /**
68
+ * Response from install attribution endpoint
69
+ * Matches backend contract from /api/sdk/v1/install
70
+ */
71
+ export interface InstallAttributionResponse {
72
+ /** Install event UUID */
73
+ installId: string;
74
+ /** Whether attribution was successful */
75
+ attributed: boolean;
76
+ /** Attribution confidence score (0-100) */
77
+ confidenceScore: number;
78
+ /** Array of matched fingerprint factors (e.g., ['ip', 'user_agent', 'timezone', 'language', 'screen']) */
79
+ matchedFactors: string[];
80
+ /** Deep link data if attributed (contains shortCode, URLs, UTM params, confidence, etc.) */
81
+ deepLinkData: DeepLinkData | Record<string, never>;
82
+ }
83
+ /**
84
+ * Event data for tracking in-app events
85
+ */
86
+ export interface EventData {
87
+ /** Event name (e.g., 'purchase', 'signup', 'add_to_cart') */
88
+ name: string;
89
+ /** Event properties */
90
+ properties?: Record<string, any>;
91
+ /** Revenue amount (for conversion events) */
92
+ revenue?: number;
93
+ /** Currency code (e.g., 'USD') */
94
+ currency?: string;
95
+ /** Install ID for attribution */
96
+ installId?: string;
97
+ }
98
+ /**
99
+ * Callback function for deferred deep links
100
+ */
101
+ export type DeferredDeepLinkCallback = (deepLinkData: DeepLinkData | null) => void;
102
+ /**
103
+ * Callback function for direct deep links
104
+ */
105
+ export type DeepLinkCallback = (url: string, deepLinkData: DeepLinkData | null) => void;
106
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,+BAA+B;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,wBAAwB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,sBAAsB;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,gBAAgB,EAAE,MAAM,CAAC;IACzB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,kCAAkC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sBAAsB;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qBAAqB;IACrB,aAAa,CAAC,EAAE;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,8BAA8B;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,UAAU,EAAE,OAAO,CAAC;IACpB,2CAA2C;IAC3C,eAAe,EAAE,MAAM,CAAC;IACxB,0GAA0G;IAC1G,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,4FAA4F;IAC5F,YAAY,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CACpD;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;AAEnF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * LinkForty React Native SDK Type Definitions
3
+ */
4
+ export {};
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Advanced Hooks Example
3
+ *
4
+ * This example shows how to create custom React hooks for
5
+ * LinkForty SDK integration with TypeScript.
6
+ */
7
+
8
+ import { useEffect, useState, useCallback } from 'react';
9
+ import LinkForty from '@linkforty/react-native-sdk';
10
+ import type { DeepLinkData } from '@linkforty/react-native-sdk';
11
+
12
+ /**
13
+ * Custom hook for managing LinkForty SDK initialization
14
+ */
15
+ export function useLinkForty(config: {
16
+ baseUrl: string;
17
+ apiKey?: string;
18
+ debug?: boolean;
19
+ }) {
20
+ const [isInitialized, setIsInitialized] = useState(false);
21
+ const [error, setError] = useState<Error | null>(null);
22
+
23
+ useEffect(() => {
24
+ const init = async () => {
25
+ try {
26
+ await LinkForty.init(config);
27
+ setIsInitialized(true);
28
+ } catch (err) {
29
+ setError(err as Error);
30
+ }
31
+ };
32
+
33
+ init();
34
+ }, [config.baseUrl, config.apiKey]);
35
+
36
+ return { isInitialized, error };
37
+ }
38
+
39
+ /**
40
+ * Custom hook for handling install attribution (deferred deep links)
41
+ */
42
+ export function useInstallAttribution(
43
+ onAttribution?: (data: DeepLinkData) => void
44
+ ) {
45
+ const [installData, setInstallData] = useState<DeepLinkData | null>(null);
46
+ const [isOrganic, setIsOrganic] = useState<boolean | null>(null);
47
+
48
+ useEffect(() => {
49
+ LinkForty.onDeferredDeepLink((data) => {
50
+ if (data) {
51
+ setInstallData(data);
52
+ setIsOrganic(false);
53
+ onAttribution?.(data);
54
+ } else {
55
+ setIsOrganic(true);
56
+ }
57
+ });
58
+ }, [onAttribution]);
59
+
60
+ return { installData, isOrganic };
61
+ }
62
+
63
+ /**
64
+ * Custom hook for handling deep links
65
+ */
66
+ export function useDeepLink(
67
+ onDeepLink?: (url: string, data: DeepLinkData | null) => void
68
+ ) {
69
+ const [currentDeepLink, setCurrentDeepLink] = useState<{
70
+ url: string;
71
+ data: DeepLinkData | null;
72
+ } | null>(null);
73
+
74
+ useEffect(() => {
75
+ LinkForty.onDeepLink((url, data) => {
76
+ setCurrentDeepLink({ url, data });
77
+ onDeepLink?.(url, data);
78
+ });
79
+ }, [onDeepLink]);
80
+
81
+ return currentDeepLink;
82
+ }
83
+
84
+ /**
85
+ * Custom hook for tracking events with error handling
86
+ */
87
+ export function useEventTracking() {
88
+ const [isTracking, setIsTracking] = useState(false);
89
+ const [lastError, setLastError] = useState<Error | null>(null);
90
+
91
+ const trackEvent = useCallback(
92
+ async (name: string, properties?: Record<string, any>) => {
93
+ setIsTracking(true);
94
+ setLastError(null);
95
+
96
+ try {
97
+ await LinkForty.trackEvent(name, properties);
98
+ } catch (error) {
99
+ setLastError(error as Error);
100
+ throw error;
101
+ } finally {
102
+ setIsTracking(false);
103
+ }
104
+ },
105
+ []
106
+ );
107
+
108
+ return { trackEvent, isTracking, lastError };
109
+ }
110
+
111
+ /**
112
+ * Example: Using the hooks in a component
113
+ */
114
+ export default function ExampleComponent() {
115
+ // Initialize SDK
116
+ const { isInitialized, error } = useLinkForty({
117
+ baseUrl: 'https://go.yourdomain.com',
118
+ debug: __DEV__,
119
+ });
120
+
121
+ // Handle install attribution
122
+ const { installData, isOrganic } = useInstallAttribution((data) => {
123
+ console.log('User came from:', data.utmParameters?.source);
124
+ });
125
+
126
+ // Handle deep links
127
+ const deepLink = useDeepLink((url, data) => {
128
+ console.log('Deep link opened:', url);
129
+ // Navigate to specific screen
130
+ });
131
+
132
+ // Event tracking
133
+ const { trackEvent, isTracking } = useEventTracking();
134
+
135
+ // Track screen view
136
+ useEffect(() => {
137
+ if (isInitialized) {
138
+ trackEvent('screen_view', {
139
+ screen_name: 'Home',
140
+ timestamp: new Date().toISOString(),
141
+ });
142
+ }
143
+ }, [isInitialized]);
144
+
145
+ const handlePurchase = async () => {
146
+ try {
147
+ await trackEvent('purchase', {
148
+ amount: 99.99,
149
+ currency: 'USD',
150
+ product_id: 'premium_plan',
151
+ });
152
+ alert('Purchase tracked!');
153
+ } catch (error) {
154
+ console.error('Failed to track purchase:', error);
155
+ }
156
+ };
157
+
158
+ if (error) {
159
+ return <Text>SDK initialization failed: {error.message}</Text>;
160
+ }
161
+
162
+ if (!isInitialized) {
163
+ return <Text>Initializing LinkForty SDK...</Text>;
164
+ }
165
+
166
+ return (
167
+ <View>
168
+ <Text>SDK Initialized!</Text>
169
+
170
+ {isOrganic !== null && (
171
+ <Text>
172
+ Install Type: {isOrganic ? 'Organic' : 'Attributed'}
173
+ </Text>
174
+ )}
175
+
176
+ {installData && (
177
+ <View>
178
+ <Text>Source: {installData.utmParameters?.source}</Text>
179
+ <Text>Campaign: {installData.utmParameters?.campaign}</Text>
180
+ </View>
181
+ )}
182
+
183
+ {deepLink && (
184
+ <Text>Current Deep Link: {deepLink.url}</Text>
185
+ )}
186
+
187
+ <Button
188
+ title={isTracking ? 'Tracking...' : 'Track Purchase'}
189
+ onPress={handlePurchase}
190
+ disabled={isTracking}
191
+ />
192
+ </View>
193
+ );
194
+ }
195
+
196
+ /**
197
+ * TypeScript Utility Types
198
+ */
199
+
200
+ // Extract UTM parameters with defaults
201
+ export function getUTMParameters(data: DeepLinkData | null) {
202
+ return {
203
+ source: data?.utmParameters?.source || 'unknown',
204
+ medium: data?.utmParameters?.medium || 'unknown',
205
+ campaign: data?.utmParameters?.campaign || 'none',
206
+ term: data?.utmParameters?.term,
207
+ content: data?.utmParameters?.content,
208
+ };
209
+ }
210
+
211
+ // Check if install is attributed to a specific campaign
212
+ export function isFromCampaign(
213
+ data: DeepLinkData | null,
214
+ campaignName: string
215
+ ): boolean {
216
+ return data?.utmParameters?.campaign === campaignName;
217
+ }
218
+
219
+ // Get install age in days
220
+ export async function getInstallAge(): Promise<number | null> {
221
+ const installData = await LinkForty.getInstallData();
222
+ if (!installData?.clickedAt) return null;
223
+
224
+ const clickDate = new Date(installData.clickedAt);
225
+ const now = new Date();
226
+ const diffTime = Math.abs(now.getTime() - clickDate.getTime());
227
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
228
+
229
+ return diffDays;
230
+ }