@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.
- package/CHANGELOG.md +136 -0
- package/CONTRIBUTING.md +340 -0
- package/LICENSE +21 -0
- package/README.md +410 -0
- package/dist/DeepLinkHandler.d.ts +25 -0
- package/dist/DeepLinkHandler.d.ts.map +1 -0
- package/dist/DeepLinkHandler.js +81 -0
- package/dist/FingerprintCollector.d.ts +23 -0
- package/dist/FingerprintCollector.d.ts.map +1 -0
- package/dist/FingerprintCollector.js +79 -0
- package/dist/LinkFortySDK.d.ts +58 -0
- package/dist/LinkFortySDK.d.ts.map +1 -0
- package/dist/LinkFortySDK.js +258 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/examples/AdvancedHooks.tsx +230 -0
- package/examples/BasicSetup.tsx +100 -0
- package/examples/EcommerceTracking.tsx +142 -0
- package/examples/README.md +318 -0
- package/examples/SelfHostedSetup.tsx +126 -0
- package/package.json +68 -0
- package/src/DeepLinkHandler.ts +96 -0
- package/src/FingerprintCollector.ts +90 -0
- package/src/LinkFortySDK.ts +320 -0
- package/src/index.ts +26 -0
- package/src/types.ts +112 -0
package/README.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# LinkForty React Native SDK
|
|
2
|
+
|
|
3
|
+
Official React Native SDK for [LinkForty](https://github.com/linkforty/core) - Open-source deep linking and mobile attribution platform.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Deferred Deep Linking** - Route new users to specific content after install
|
|
8
|
+
- ✅ **Direct Deep Linking** - Handle Universal Links (iOS) and App Links (Android)
|
|
9
|
+
- ✅ **Probabilistic Attribution** - Match installs to clicks with 70%+ confidence
|
|
10
|
+
- ✅ **Event Tracking** - Track in-app events with attribution
|
|
11
|
+
- ✅ **Privacy-Focused** - No persistent device IDs required
|
|
12
|
+
- ✅ **TypeScript Support** - Full type definitions included
|
|
13
|
+
- ✅ **Works with Core & Cloud** - Compatible with self-hosted and Cloud instances
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @linkforty/react-native-sdk
|
|
19
|
+
# or
|
|
20
|
+
yarn add @linkforty/react-native-sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Requirements
|
|
24
|
+
|
|
25
|
+
- React Native >= 0.76.0
|
|
26
|
+
- React >= 18.3.0
|
|
27
|
+
- Node.js >= 20.0.0
|
|
28
|
+
|
|
29
|
+
### Additional Dependencies
|
|
30
|
+
|
|
31
|
+
This SDK requires the following peer dependencies:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @react-native-async-storage/async-storage react-native-device-info
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### iOS Setup
|
|
38
|
+
|
|
39
|
+
1. Install CocoaPods dependencies:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd ios && pod install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. Configure Universal Links in Xcode:
|
|
46
|
+
- Open your project in Xcode
|
|
47
|
+
- Select your app target → Signing & Capabilities
|
|
48
|
+
- Add "Associated Domains" capability
|
|
49
|
+
- Add domain: `applinks:go.yourdomain.com` (replace with your LinkForty or custom domain)
|
|
50
|
+
|
|
51
|
+
3. Create AASA file on your server at:
|
|
52
|
+
```
|
|
53
|
+
https://go.yourdomain.com/.well-known/apple-app-site-association
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Example AASA file:
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"applinks": {
|
|
60
|
+
"apps": [],
|
|
61
|
+
"details": [
|
|
62
|
+
{
|
|
63
|
+
"appID": "TEAM_ID.com.yourapp.bundle",
|
|
64
|
+
"paths": ["*"]
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Android Setup
|
|
72
|
+
|
|
73
|
+
1. Configure App Links in `android/app/src/main/AndroidManifest.xml`:
|
|
74
|
+
|
|
75
|
+
```xml
|
|
76
|
+
<activity android:name=".MainActivity">
|
|
77
|
+
<intent-filter android:autoVerify="true">
|
|
78
|
+
<action android:name="android.intent.action.VIEW" />
|
|
79
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
80
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
81
|
+
<data android:scheme="https" />
|
|
82
|
+
<data android:host="go.yourdomain.com" />
|
|
83
|
+
</intent-filter>
|
|
84
|
+
</activity>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
2. Create Digital Asset Links file on your server at:
|
|
88
|
+
```
|
|
89
|
+
https://go.yourdomain.com/.well-known/assetlinks.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Example assetlinks.json:
|
|
93
|
+
```json
|
|
94
|
+
[{
|
|
95
|
+
"relation": ["delegate_permission/common.handle_all_urls"],
|
|
96
|
+
"target": {
|
|
97
|
+
"namespace": "android_app",
|
|
98
|
+
"package_name": "com.yourapp",
|
|
99
|
+
"sha256_cert_fingerprints": ["YOUR_SHA256_FINGERPRINT"]
|
|
100
|
+
}
|
|
101
|
+
}]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Quick Start
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import LinkForty from '@linkforty/react-native-sdk';
|
|
108
|
+
|
|
109
|
+
// Initialize SDK (call in App.tsx or index.js)
|
|
110
|
+
await LinkForty.init({
|
|
111
|
+
baseUrl: 'https://go.yourdomain.com',
|
|
112
|
+
apiKey: 'optional-for-cloud-users', // Optional
|
|
113
|
+
debug: __DEV__, // Enable debug logging in development
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Handle deferred deep links (new installs)
|
|
117
|
+
LinkForty.onDeferredDeepLink((deepLinkData) => {
|
|
118
|
+
if (deepLinkData) {
|
|
119
|
+
// User clicked a link before installing
|
|
120
|
+
console.log('Deferred deep link:', deepLinkData);
|
|
121
|
+
|
|
122
|
+
// Navigate to specific content
|
|
123
|
+
navigation.navigate('Product', {
|
|
124
|
+
id: deepLinkData.utmParameters?.content
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
// Organic install (no link clicked)
|
|
128
|
+
console.log('Organic install');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Handle direct deep links (existing users)
|
|
133
|
+
LinkForty.onDeepLink((url, deepLinkData) => {
|
|
134
|
+
console.log('Deep link opened:', url, deepLinkData);
|
|
135
|
+
|
|
136
|
+
if (deepLinkData) {
|
|
137
|
+
// Navigate to specific content
|
|
138
|
+
navigation.navigate('Product', {
|
|
139
|
+
id: deepLinkData.utmParameters?.content
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Track in-app events
|
|
145
|
+
await LinkForty.trackEvent('purchase', {
|
|
146
|
+
amount: 99.99,
|
|
147
|
+
currency: 'USD',
|
|
148
|
+
productId: 'premium_plan'
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## API Reference
|
|
153
|
+
|
|
154
|
+
### `init(config: LinkFortyConfig): Promise<void>`
|
|
155
|
+
|
|
156
|
+
Initialize the SDK. Must be called before using any other methods.
|
|
157
|
+
|
|
158
|
+
**Parameters:**
|
|
159
|
+
|
|
160
|
+
- `config.baseUrl` (string, required) - Base URL of your LinkForty instance
|
|
161
|
+
- `config.apiKey` (string, optional) - API key for Cloud authentication
|
|
162
|
+
- `config.debug` (boolean, optional) - Enable debug logging
|
|
163
|
+
- `config.attributionWindow` (number, optional) - Attribution window in days (default: 7)
|
|
164
|
+
|
|
165
|
+
**Example:**
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
await LinkForty.init({
|
|
169
|
+
baseUrl: 'https://go.yourdomain.com',
|
|
170
|
+
debug: true,
|
|
171
|
+
attributionWindow: 7,
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `onDeferredDeepLink(callback: (deepLinkData: DeepLinkData | null) => void): void`
|
|
176
|
+
|
|
177
|
+
Register a callback for deferred deep links. Called when the app is launched for the first time after installation.
|
|
178
|
+
|
|
179
|
+
**Parameters:**
|
|
180
|
+
|
|
181
|
+
- `callback` - Function called with deep link data or `null` for organic installs
|
|
182
|
+
|
|
183
|
+
**Example:**
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
LinkForty.onDeferredDeepLink((deepLinkData) => {
|
|
187
|
+
if (deepLinkData) {
|
|
188
|
+
// Attributed install
|
|
189
|
+
console.log('User came from:', deepLinkData.utmParameters?.source);
|
|
190
|
+
} else {
|
|
191
|
+
// Organic install
|
|
192
|
+
console.log('Organic install');
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### `onDeepLink(callback: (url: string, deepLinkData: DeepLinkData | null) => void): void`
|
|
198
|
+
|
|
199
|
+
Register a callback for direct deep links. Called when the app is opened via a Universal Link (iOS) or App Link (Android).
|
|
200
|
+
|
|
201
|
+
**Parameters:**
|
|
202
|
+
|
|
203
|
+
- `callback` - Function called with the full URL and parsed deep link data
|
|
204
|
+
|
|
205
|
+
**Example:**
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
LinkForty.onDeepLink((url, deepLinkData) => {
|
|
209
|
+
if (deepLinkData?.shortCode) {
|
|
210
|
+
// Navigate based on link
|
|
211
|
+
navigation.navigate('Details', { id: deepLinkData.shortCode });
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### `trackEvent(name: string, properties?: Record<string, any>): Promise<void>`
|
|
217
|
+
|
|
218
|
+
Track an in-app event with optional properties.
|
|
219
|
+
|
|
220
|
+
**Parameters:**
|
|
221
|
+
|
|
222
|
+
- `name` - Event name (e.g., 'purchase', 'signup', 'add_to_cart')
|
|
223
|
+
- `properties` - Optional event properties
|
|
224
|
+
|
|
225
|
+
**Example:**
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
await LinkForty.trackEvent('purchase', {
|
|
229
|
+
amount: 99.99,
|
|
230
|
+
currency: 'USD',
|
|
231
|
+
productId: 'premium_plan',
|
|
232
|
+
category: 'subscription'
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### `getInstallData(): Promise<DeepLinkData | null>`
|
|
237
|
+
|
|
238
|
+
Get cached install attribution data.
|
|
239
|
+
|
|
240
|
+
**Returns:** Deep link data from install or `null` if not attributed
|
|
241
|
+
|
|
242
|
+
**Example:**
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
const installData = await LinkForty.getInstallData();
|
|
246
|
+
if (installData) {
|
|
247
|
+
console.log('Install source:', installData.utmParameters?.source);
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### `getInstallId(): Promise<string | null>`
|
|
252
|
+
|
|
253
|
+
Get the unique install ID for this app installation.
|
|
254
|
+
|
|
255
|
+
**Returns:** Install ID or `null` if not available
|
|
256
|
+
|
|
257
|
+
**Example:**
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
const installId = await LinkForty.getInstallId();
|
|
261
|
+
console.log('Install ID:', installId);
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### `clearData(): Promise<void>`
|
|
265
|
+
|
|
266
|
+
Clear all cached SDK data. Useful for testing.
|
|
267
|
+
|
|
268
|
+
**Example:**
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
await LinkForty.clearData();
|
|
272
|
+
// App will behave as if it's a fresh install
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## TypeScript Types
|
|
276
|
+
|
|
277
|
+
### `DeepLinkData`
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
interface DeepLinkData {
|
|
281
|
+
shortCode: string;
|
|
282
|
+
iosUrl?: string;
|
|
283
|
+
androidUrl?: string;
|
|
284
|
+
webUrl?: string;
|
|
285
|
+
utmParameters?: {
|
|
286
|
+
source?: string;
|
|
287
|
+
medium?: string;
|
|
288
|
+
campaign?: string;
|
|
289
|
+
term?: string;
|
|
290
|
+
content?: string;
|
|
291
|
+
};
|
|
292
|
+
customParameters?: Record<string, string>;
|
|
293
|
+
clickedAt?: string;
|
|
294
|
+
linkId?: string;
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### `LinkFortyConfig`
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
interface LinkFortyConfig {
|
|
302
|
+
baseUrl: string;
|
|
303
|
+
apiKey?: string;
|
|
304
|
+
debug?: boolean;
|
|
305
|
+
attributionWindow?: number;
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Advanced Usage
|
|
310
|
+
|
|
311
|
+
### Testing Deferred Deep Linking
|
|
312
|
+
|
|
313
|
+
1. **Uninstall the app** or clear all data:
|
|
314
|
+
```typescript
|
|
315
|
+
await LinkForty.clearData();
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
2. **Click a LinkForty link** on your device (in Safari/Chrome, not in the app)
|
|
319
|
+
|
|
320
|
+
3. **Install/Open the app** from App Store/Play Store
|
|
321
|
+
|
|
322
|
+
4. **Check logs** - you should see attribution data in the `onDeferredDeepLink` callback
|
|
323
|
+
|
|
324
|
+
### Using with Self-Hosted LinkForty Core
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
// Point to your self-hosted instance
|
|
328
|
+
await LinkForty.init({
|
|
329
|
+
baseUrl: 'http://localhost:3000', // or your domain
|
|
330
|
+
debug: true,
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Using with LinkForty Cloud
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
// Add API key for Cloud authentication
|
|
338
|
+
await LinkForty.init({
|
|
339
|
+
baseUrl: 'https://go.yourdomain.com',
|
|
340
|
+
apiKey: 'your-api-key-here',
|
|
341
|
+
debug: false,
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Custom Attribution Window
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
// Change attribution window to 14 days
|
|
349
|
+
await LinkForty.init({
|
|
350
|
+
baseUrl: 'https://go.yourdomain.com',
|
|
351
|
+
attributionWindow: 14, // days
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Troubleshooting
|
|
356
|
+
|
|
357
|
+
### Deep links not working on iOS
|
|
358
|
+
|
|
359
|
+
1. Verify your AASA file is accessible at `https://go.yourdomain.com/.well-known/apple-app-site-association`
|
|
360
|
+
2. Check that your Team ID and Bundle ID are correct
|
|
361
|
+
3. Make sure "Associated Domains" capability is added in Xcode
|
|
362
|
+
4. Test with a real device (Universal Links don't work in simulator)
|
|
363
|
+
|
|
364
|
+
### Deep links not working on Android
|
|
365
|
+
|
|
366
|
+
1. Verify your assetlinks.json file is accessible at `https://go.yourdomain.com/.well-known/assetlinks.json`
|
|
367
|
+
2. Check that your package name and SHA256 fingerprint are correct
|
|
368
|
+
3. Run `adb shell pm get-app-links com.yourapp` to verify link verification status
|
|
369
|
+
4. Make sure `android:autoVerify="true"` is set in your intent filter
|
|
370
|
+
|
|
371
|
+
### Deferred deep links not attributing
|
|
372
|
+
|
|
373
|
+
1. Make sure you're testing on first install (or call `clearData()`)
|
|
374
|
+
2. Check debug logs for fingerprint data
|
|
375
|
+
3. Verify your LinkForty backend is receiving the install event
|
|
376
|
+
4. Ensure the click and install happen within the attribution window (default: 7 days)
|
|
377
|
+
5. Try clicking the link and installing from the same network
|
|
378
|
+
|
|
379
|
+
### TypeScript errors
|
|
380
|
+
|
|
381
|
+
Make sure you have the latest type definitions installed:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
npm install --save-dev @types/react @types/react-native
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Contributing
|
|
388
|
+
|
|
389
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
390
|
+
|
|
391
|
+
## License
|
|
392
|
+
|
|
393
|
+
MIT License - see [LICENSE](LICENSE) file for details
|
|
394
|
+
|
|
395
|
+
## Support
|
|
396
|
+
|
|
397
|
+
- **Documentation:** Coming soon (self-hosting guide available in Core repository)
|
|
398
|
+
- **Issues:** [Report on GitHub](https://github.com/linkforty/react-native-sdk/issues)
|
|
399
|
+
- **Questions:** Open a GitHub Discussion or Issue
|
|
400
|
+
|
|
401
|
+
## Related Projects
|
|
402
|
+
|
|
403
|
+
- [LinkForty Cloud](https://linkforty.com) - Cloud platform with dashboard and API
|
|
404
|
+
- [LinkForty Core](https://github.com/linkforty/core) - Self-hosted open-source backend
|
|
405
|
+
- **iOS SDK** - Native Swift SDK (planned for future release)
|
|
406
|
+
- **Android SDK** - Native Kotlin SDK (planned for future release)
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
Made with ❤️ by the LinkForty team
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepLinkHandler - Handles Universal Links (iOS) and App Links (Android)
|
|
3
|
+
*/
|
|
4
|
+
import type { DeepLinkData, DeepLinkCallback } from './types';
|
|
5
|
+
export declare class DeepLinkHandler {
|
|
6
|
+
private callback;
|
|
7
|
+
private baseUrl;
|
|
8
|
+
/**
|
|
9
|
+
* Initialize deep link listener
|
|
10
|
+
*/
|
|
11
|
+
initialize(baseUrl: string, callback: DeepLinkCallback): void;
|
|
12
|
+
/**
|
|
13
|
+
* Clean up listeners
|
|
14
|
+
*/
|
|
15
|
+
cleanup(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Handle incoming deep link
|
|
18
|
+
*/
|
|
19
|
+
private handleDeepLink;
|
|
20
|
+
/**
|
|
21
|
+
* Parse deep link URL and extract data
|
|
22
|
+
*/
|
|
23
|
+
parseURL(url: string): DeepLinkData | null;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=DeepLinkHandler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeepLinkHandler.d.ts","sourceRoot":"","sources":["../src/DeepLinkHandler.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE9D,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,OAAO,CAAuB;IAEtC;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAe7D;;OAEG;IACH,OAAO,IAAI,IAAI;IAKf;;OAEG;IACH,OAAO,CAAC,cAAc,CAOpB;IAEF;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;CA2C3C"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepLinkHandler - Handles Universal Links (iOS) and App Links (Android)
|
|
3
|
+
*/
|
|
4
|
+
import { Linking } from 'react-native';
|
|
5
|
+
export class DeepLinkHandler {
|
|
6
|
+
callback = null;
|
|
7
|
+
baseUrl = null;
|
|
8
|
+
/**
|
|
9
|
+
* Initialize deep link listener
|
|
10
|
+
*/
|
|
11
|
+
initialize(baseUrl, callback) {
|
|
12
|
+
this.baseUrl = baseUrl;
|
|
13
|
+
this.callback = callback;
|
|
14
|
+
// Listen for deep links when app is already open
|
|
15
|
+
Linking.addEventListener('url', this.handleDeepLink);
|
|
16
|
+
// Check if app was opened via deep link
|
|
17
|
+
Linking.getInitialURL().then((url) => {
|
|
18
|
+
if (url) {
|
|
19
|
+
this.handleDeepLink({ url });
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Clean up listeners
|
|
25
|
+
*/
|
|
26
|
+
cleanup() {
|
|
27
|
+
Linking.removeAllListeners('url');
|
|
28
|
+
this.callback = null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Handle incoming deep link
|
|
32
|
+
*/
|
|
33
|
+
handleDeepLink = ({ url }) => {
|
|
34
|
+
if (!this.callback || !url) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const deepLinkData = this.parseURL(url);
|
|
38
|
+
this.callback(url, deepLinkData);
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Parse deep link URL and extract data
|
|
42
|
+
*/
|
|
43
|
+
parseURL(url) {
|
|
44
|
+
try {
|
|
45
|
+
const parsedUrl = new URL(url);
|
|
46
|
+
// Check if this is a LinkForty URL
|
|
47
|
+
if (this.baseUrl && !url.startsWith(this.baseUrl)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// Extract short code from path
|
|
51
|
+
const shortCode = parsedUrl.pathname.replace('/', '');
|
|
52
|
+
if (!shortCode) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
// Extract UTM parameters
|
|
56
|
+
const utmParameters = {
|
|
57
|
+
source: parsedUrl.searchParams.get('utm_source') || undefined,
|
|
58
|
+
medium: parsedUrl.searchParams.get('utm_medium') || undefined,
|
|
59
|
+
campaign: parsedUrl.searchParams.get('utm_campaign') || undefined,
|
|
60
|
+
term: parsedUrl.searchParams.get('utm_term') || undefined,
|
|
61
|
+
content: parsedUrl.searchParams.get('utm_content') || undefined,
|
|
62
|
+
};
|
|
63
|
+
// Extract all other query parameters as custom parameters
|
|
64
|
+
const customParameters = {};
|
|
65
|
+
parsedUrl.searchParams.forEach((value, key) => {
|
|
66
|
+
if (!key.startsWith('utm_')) {
|
|
67
|
+
customParameters[key] = value;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
shortCode,
|
|
72
|
+
utmParameters,
|
|
73
|
+
customParameters: Object.keys(customParameters).length > 0 ? customParameters : undefined,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error('Failed to parse deep link URL:', error);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FingerprintCollector - Collects device fingerprint data for attribution matching
|
|
3
|
+
*/
|
|
4
|
+
import type { DeviceFingerprint } from './types';
|
|
5
|
+
export declare class FingerprintCollector {
|
|
6
|
+
/**
|
|
7
|
+
* Collect device fingerprint data
|
|
8
|
+
*/
|
|
9
|
+
static collect(): Promise<DeviceFingerprint>;
|
|
10
|
+
/**
|
|
11
|
+
* Generate query parameters from fingerprint for URL
|
|
12
|
+
*/
|
|
13
|
+
static generateQueryParams(): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Get device timezone
|
|
16
|
+
*/
|
|
17
|
+
private static getTimezone;
|
|
18
|
+
/**
|
|
19
|
+
* Get device language
|
|
20
|
+
*/
|
|
21
|
+
private static getLanguage;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=FingerprintCollector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FingerprintCollector.d.ts","sourceRoot":"","sources":["../src/FingerprintCollector.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,qBAAa,oBAAoB;IAC/B;;OAEG;WACU,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAmBlD;;OAEG;WACU,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;IAiBnD;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAS1B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;CAuB3B"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FingerprintCollector - Collects device fingerprint data for attribution matching
|
|
3
|
+
*/
|
|
4
|
+
import { Platform, Dimensions, NativeModules } from 'react-native';
|
|
5
|
+
import DeviceInfo from 'react-native-device-info';
|
|
6
|
+
export class FingerprintCollector {
|
|
7
|
+
/**
|
|
8
|
+
* Collect device fingerprint data
|
|
9
|
+
*/
|
|
10
|
+
static async collect() {
|
|
11
|
+
const { width, height } = Dimensions.get('window');
|
|
12
|
+
const timezone = this.getTimezone();
|
|
13
|
+
const language = this.getLanguage();
|
|
14
|
+
const fingerprint = {
|
|
15
|
+
userAgent: await DeviceInfo.getUserAgent(),
|
|
16
|
+
timezone,
|
|
17
|
+
language,
|
|
18
|
+
screenResolution: `${width}x${height}`,
|
|
19
|
+
platform: Platform.OS,
|
|
20
|
+
deviceModel: await DeviceInfo.getModel(),
|
|
21
|
+
osVersion: await DeviceInfo.getSystemVersion(),
|
|
22
|
+
appVersion: await DeviceInfo.getVersion(),
|
|
23
|
+
};
|
|
24
|
+
return fingerprint;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generate query parameters from fingerprint for URL
|
|
28
|
+
*/
|
|
29
|
+
static async generateQueryParams() {
|
|
30
|
+
const fingerprint = await this.collect();
|
|
31
|
+
const params = new URLSearchParams({
|
|
32
|
+
ua: fingerprint.userAgent,
|
|
33
|
+
tz: fingerprint.timezone,
|
|
34
|
+
lang: fingerprint.language,
|
|
35
|
+
screen: fingerprint.screenResolution,
|
|
36
|
+
platform: fingerprint.platform,
|
|
37
|
+
...(fingerprint.deviceModel && { model: fingerprint.deviceModel }),
|
|
38
|
+
...(fingerprint.osVersion && { os: fingerprint.osVersion }),
|
|
39
|
+
...(fingerprint.appVersion && { app_version: fingerprint.appVersion }),
|
|
40
|
+
});
|
|
41
|
+
return params.toString();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get device timezone
|
|
45
|
+
*/
|
|
46
|
+
static getTimezone() {
|
|
47
|
+
try {
|
|
48
|
+
// Get timezone using Intl API
|
|
49
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return 'UTC';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get device language
|
|
57
|
+
*/
|
|
58
|
+
static getLanguage() {
|
|
59
|
+
try {
|
|
60
|
+
// Try to get locale from NativeModules
|
|
61
|
+
const locale = Platform.select({
|
|
62
|
+
ios: NativeModules.SettingsManager?.settings?.AppleLocale ||
|
|
63
|
+
NativeModules.SettingsManager?.settings?.AppleLanguages?.[0],
|
|
64
|
+
android: NativeModules.I18nManager?.localeIdentifier,
|
|
65
|
+
});
|
|
66
|
+
if (locale) {
|
|
67
|
+
return locale;
|
|
68
|
+
}
|
|
69
|
+
// Fallback to navigator if available
|
|
70
|
+
if (typeof navigator !== 'undefined' && navigator.language) {
|
|
71
|
+
return navigator.language;
|
|
72
|
+
}
|
|
73
|
+
return 'en-US';
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
return 'en-US';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LinkFortySDK - Main SDK class for LinkForty deep linking and attribution
|
|
3
|
+
*/
|
|
4
|
+
import type { LinkFortyConfig, DeepLinkData, DeferredDeepLinkCallback, DeepLinkCallback } from './types';
|
|
5
|
+
export declare class LinkFortySDK {
|
|
6
|
+
private config;
|
|
7
|
+
private deepLinkHandler;
|
|
8
|
+
private deferredDeepLinkCallback;
|
|
9
|
+
private installId;
|
|
10
|
+
private initialized;
|
|
11
|
+
/**
|
|
12
|
+
* Initialize the SDK
|
|
13
|
+
*/
|
|
14
|
+
init(config: LinkFortyConfig): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Get install attribution data (deferred deep link)
|
|
17
|
+
*/
|
|
18
|
+
getInstallData(): Promise<DeepLinkData | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Register callback for deferred deep links (new installs)
|
|
21
|
+
*/
|
|
22
|
+
onDeferredDeepLink(callback: DeferredDeepLinkCallback): void;
|
|
23
|
+
/**
|
|
24
|
+
* Register callback for direct deep links (existing users)
|
|
25
|
+
*/
|
|
26
|
+
onDeepLink(callback: DeepLinkCallback): void;
|
|
27
|
+
/**
|
|
28
|
+
* Track in-app event
|
|
29
|
+
*/
|
|
30
|
+
trackEvent(name: string, properties?: Record<string, any>): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Get install ID
|
|
33
|
+
*/
|
|
34
|
+
getInstallId(): Promise<string | null>;
|
|
35
|
+
/**
|
|
36
|
+
* Clear all cached data (for testing)
|
|
37
|
+
*/
|
|
38
|
+
clearData(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Check if this is the first app launch
|
|
41
|
+
*/
|
|
42
|
+
private isFirstLaunch;
|
|
43
|
+
/**
|
|
44
|
+
* Report app installation to LinkForty backend
|
|
45
|
+
*/
|
|
46
|
+
private reportInstall;
|
|
47
|
+
/**
|
|
48
|
+
* Load cached install data
|
|
49
|
+
*/
|
|
50
|
+
private loadInstallData;
|
|
51
|
+
/**
|
|
52
|
+
* Make API request to LinkForty backend
|
|
53
|
+
*/
|
|
54
|
+
private apiRequest;
|
|
55
|
+
}
|
|
56
|
+
declare const _default: LinkFortySDK;
|
|
57
|
+
export default _default;
|
|
58
|
+
//# sourceMappingURL=LinkFortySDK.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LinkFortySDK.d.ts","sourceRoot":"","sources":["../src/LinkFortySDK.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EACV,eAAe,EAEf,YAAY,EACZ,wBAAwB,EACxB,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAQjB,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,wBAAwB,CAAyC;IACzE,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,WAAW,CAAkB;IAErC;;OAEG;IACG,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAiClD;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAQpD;;OAEG;IACH,kBAAkB,CAAC,QAAQ,EAAE,wBAAwB,GAAG,IAAI;IAW5D;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAY5C;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAgC/E;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAa5C;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAchC;;OAEG;YACW,aAAa;IAW3B;;OAEG;YACW,aAAa;IAsF3B;;OAEG;YACW,eAAe;IAI7B;;OAEG;YACW,UAAU;CA+BzB;;AAGD,wBAAkC"}
|