@linkforty/mobile-sdk-react-native 1.0.0 → 1.1.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 +16 -124
- package/README.md +5 -5
- package/dist/DeepLinkHandler.d.ts +23 -4
- package/dist/DeepLinkHandler.d.ts.map +1 -1
- package/dist/DeepLinkHandler.js +82 -8
- package/dist/LinkFortySDK.d.ts +3 -1
- package/dist/LinkFortySDK.d.ts.map +1 -1
- package/dist/LinkFortySDK.js +26 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +11 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/DeepLinkHandler.ts +89 -9
- package/src/LinkFortySDK.ts +27 -4
- package/src/index.ts +1 -0
- package/src/types.ts +12 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,136 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
All notable changes to
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [1.
|
|
8
|
+
## [1.1.0] - 2026-02-11
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- **Event Tracking** - Track in-app events with attribution
|
|
16
|
-
- **Privacy-Focused** - No persistent device IDs required
|
|
17
|
-
- **TypeScript Support** - Full type definitions included
|
|
18
|
-
- **Works with Core & Cloud** - Compatible with self-hosted and Cloud instances
|
|
19
|
-
|
|
20
|
-
### Core Features
|
|
21
|
-
|
|
22
|
-
#### SDK Initialization
|
|
23
|
-
- `init(config)` - Initialize SDK with base URL and optional API key
|
|
24
|
-
- Support for debug logging during development
|
|
25
|
-
- Customizable attribution window (default: 7 days)
|
|
26
|
-
|
|
27
|
-
#### Deep Linking
|
|
28
|
-
- `onDeferredDeepLink(callback)` - Handle deferred deep links for new installs
|
|
29
|
-
- `onDeepLink(callback)` - Handle direct deep links for existing users
|
|
30
|
-
- Automatic Universal Links (iOS) and App Links (Android) configuration
|
|
31
|
-
- Deep link data includes UTM parameters and custom parameters
|
|
32
|
-
|
|
33
|
-
#### Event Tracking
|
|
34
|
-
- `trackEvent(name, properties)` - Track in-app events with optional properties
|
|
35
|
-
- Automatic attribution to install source
|
|
36
|
-
- Webhook triggers for conversion events
|
|
37
|
-
- Support for revenue tracking and custom event properties
|
|
38
|
-
|
|
39
|
-
#### Data Management
|
|
40
|
-
- `getInstallData()` - Retrieve cached install attribution data
|
|
41
|
-
- `getInstallId()` - Get unique install ID for this app installation
|
|
42
|
-
- `clearData()` - Clear all cached SDK data (useful for testing)
|
|
43
|
-
|
|
44
|
-
### Device Fingerprinting
|
|
45
|
-
|
|
46
|
-
Collects the following non-invasive signals for probabilistic attribution:
|
|
47
|
-
- User agent string
|
|
48
|
-
- Device timezone
|
|
49
|
-
- Device language
|
|
50
|
-
- Screen resolution
|
|
51
|
-
- Platform (iOS/Android)
|
|
52
|
-
- Device model and OS version
|
|
53
|
-
- IP address (server-side only)
|
|
54
|
-
|
|
55
|
-
### API Endpoints
|
|
56
|
-
|
|
57
|
-
Uses the following LinkForty Core API endpoints:
|
|
58
|
-
- `POST /api/sdk/v1/install` - Report app installation
|
|
59
|
-
- `POST /api/sdk/v1/event` - Track in-app events
|
|
60
|
-
- `GET /api/sdk/v1/attribution/:fingerprint` - Debug attribution data
|
|
61
|
-
- `GET /api/sdk/v1/health` - Health check
|
|
62
|
-
|
|
63
|
-
### Dependencies
|
|
64
|
-
|
|
65
|
-
**Peer Dependencies:**
|
|
66
|
-
- `react` >= 16.8.0
|
|
67
|
-
- `react-native` >= 0.60.0
|
|
68
|
-
|
|
69
|
-
**Runtime Dependencies:**
|
|
70
|
-
- `@react-native-async-storage/async-storage` ^1.21.0
|
|
71
|
-
- `react-native-device-info` ^10.12.0
|
|
72
|
-
|
|
73
|
-
### Platform Support
|
|
74
|
-
|
|
75
|
-
- ✅ iOS 12+
|
|
76
|
-
- ✅ Android 5.0+ (API level 21+)
|
|
77
|
-
- ✅ React Native 0.60+
|
|
78
|
-
- ✅ TypeScript 5.3+
|
|
79
|
-
- ✅ Expo (with custom native modules)
|
|
80
|
-
|
|
81
|
-
### Security
|
|
82
|
-
|
|
83
|
-
- No persistent device identifiers stored
|
|
84
|
-
- Privacy-focused fingerprinting
|
|
85
|
-
- Configurable attribution window
|
|
86
|
-
- Secure HTTPS-only communication
|
|
87
|
-
- No third-party tracking SDKs
|
|
88
|
-
|
|
89
|
-
### Documentation
|
|
90
|
-
|
|
91
|
-
- Comprehensive README with setup instructions
|
|
92
|
-
- TypeScript type definitions included
|
|
93
|
-
- iOS Universal Links setup guide
|
|
94
|
-
- Android App Links setup guide
|
|
95
|
-
- Troubleshooting guide
|
|
96
|
-
- API reference with examples
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## [Unreleased]
|
|
11
|
+
- Server-side URL resolution for deep links intercepted by App Links (Android) and Universal Links (iOS) — when the OS opens the app directly, the SDK now calls the resolve endpoint to retrieve custom parameters, UTM data, and link metadata
|
|
12
|
+
- Device fingerprint collection sent with resolve requests for click attribution and deferred deep linking analytics
|
|
13
|
+
- `deepLinkPath` and `appScheme` optional fields on `DeepLinkData` type
|
|
14
|
+
- `ResolveFunction` type export for advanced SDK consumers
|
|
101
15
|
|
|
102
16
|
### Changed
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
-
- React Native Expo compatibility mode
|
|
109
|
-
- Offline event queue for poor network conditions
|
|
110
|
-
- Advanced debugging tools
|
|
111
|
-
- Custom domain support
|
|
112
|
-
- A/B testing integration
|
|
113
|
-
- User segmentation
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## Version History
|
|
118
|
-
|
|
119
|
-
- **1.0.0** - Initial release (2024-11-22)
|
|
120
|
-
|
|
121
|
-
## Migration Guides
|
|
122
|
-
|
|
123
|
-
### Upgrading to 1.0.0
|
|
124
|
-
|
|
125
|
-
This is the initial release, so no migration is necessary.
|
|
126
|
-
|
|
127
|
-
## Support
|
|
17
|
+
- `DeepLinkHandler.handleDeepLink` is now async to support server-side URL resolution
|
|
18
|
+
- `DeepLinkHandler.initialize` accepts an optional `resolveFn` parameter for URL resolution
|
|
19
|
+
- `DeepLinkHandler.parseURL` now correctly extracts the short code from template URLs (e.g., `/templateSlug/shortCode`)
|
|
20
|
+
- `LinkFortySDK.onDeepLink` automatically provides a resolver that calls the backend resolve endpoint
|
|
21
|
+
- Deferred deep link responses now normalize `deepLinkParameters` from the backend into `customParameters` for a consistent interface across direct and deferred deep link paths
|
|
128
22
|
|
|
129
|
-
|
|
130
|
-
-
|
|
131
|
-
- Documentation: https://docs.linkforty.com
|
|
132
|
-
- Discord Community: https://discord.gg/linkforty
|
|
23
|
+
### Fixed
|
|
24
|
+
- Fixed deep links returning empty `customParameters` when the app was opened via App Links or Universal Links, where the OS intercepted the URL before the LinkForty server could append parameters via redirect
|
|
133
25
|
|
|
134
|
-
##
|
|
26
|
+
## [1.0.1] - 2025-12-11
|
|
135
27
|
|
|
136
|
-
|
|
28
|
+
- Initial release
|
package/README.md
CHANGED
|
@@ -15,15 +15,15 @@ Official React Native SDK for [LinkForty](https://github.com/linkforty/core) - O
|
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm
|
|
18
|
+
npm i @linkforty/mobile-sdk-react-native
|
|
19
19
|
# or
|
|
20
|
-
yarn add @linkforty/react-native
|
|
20
|
+
yarn add @linkforty/mobile-sdk-react-native
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
### Requirements
|
|
24
24
|
|
|
25
|
-
- React Native >= 0.
|
|
26
|
-
- React >=
|
|
25
|
+
- React Native >= 0.64.0
|
|
26
|
+
- React >= 17.0.0
|
|
27
27
|
- Node.js >= 20.0.0
|
|
28
28
|
|
|
29
29
|
### Additional Dependencies
|
|
@@ -104,7 +104,7 @@ cd ios && pod install
|
|
|
104
104
|
## Quick Start
|
|
105
105
|
|
|
106
106
|
```typescript
|
|
107
|
-
import LinkForty from '@linkforty/react-native
|
|
107
|
+
import LinkForty from '@linkforty/mobile-sdk-react-native';
|
|
108
108
|
|
|
109
109
|
// Initialize SDK (call in App.tsx or index.js)
|
|
110
110
|
await LinkForty.init({
|
|
@@ -1,24 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DeepLinkHandler - Handles Universal Links (iOS) and App Links (Android)
|
|
3
|
+
*
|
|
4
|
+
* When the OS intercepts a LinkForty URL via App Links or Universal Links,
|
|
5
|
+
* the redirect server is bypassed. This handler detects LinkForty URLs and
|
|
6
|
+
* resolves them via the server API to retrieve the full deep link data
|
|
7
|
+
* (custom parameters, UTM params, etc.) that would normally be appended
|
|
8
|
+
* during the redirect.
|
|
3
9
|
*/
|
|
4
|
-
import type { DeepLinkData, DeepLinkCallback } from './types';
|
|
10
|
+
import type { DeepLinkData, DeepLinkCallback, ResolveFunction } from './types';
|
|
5
11
|
export declare class DeepLinkHandler {
|
|
6
12
|
private callback;
|
|
7
13
|
private baseUrl;
|
|
14
|
+
private resolveFn;
|
|
8
15
|
/**
|
|
9
16
|
* Initialize deep link listener
|
|
17
|
+
* @param baseUrl - LinkForty instance base URL for detecting LinkForty URLs
|
|
18
|
+
* @param callback - Callback invoked with parsed/resolved deep link data
|
|
19
|
+
* @param resolveFn - Optional function to resolve LinkForty URLs via the server API
|
|
10
20
|
*/
|
|
11
|
-
initialize(baseUrl: string, callback: DeepLinkCallback): void;
|
|
21
|
+
initialize(baseUrl: string, callback: DeepLinkCallback, resolveFn?: ResolveFunction): void;
|
|
12
22
|
/**
|
|
13
23
|
* Clean up listeners
|
|
14
24
|
*/
|
|
15
25
|
cleanup(): void;
|
|
16
26
|
/**
|
|
17
|
-
* Handle incoming deep link
|
|
27
|
+
* Handle incoming deep link.
|
|
28
|
+
* If the URL is a LinkForty URL and a resolver is available, resolves via the server.
|
|
29
|
+
* Falls back to local URL parsing on failure.
|
|
18
30
|
*/
|
|
19
31
|
private handleDeepLink;
|
|
20
32
|
/**
|
|
21
|
-
*
|
|
33
|
+
* Resolve a LinkForty URL via the server API.
|
|
34
|
+
* Extracts the path from the URL, collects fingerprint data for click attribution,
|
|
35
|
+
* and calls the resolve endpoint.
|
|
36
|
+
*/
|
|
37
|
+
private resolveURL;
|
|
38
|
+
/**
|
|
39
|
+
* Parse deep link URL and extract data locally (without server call).
|
|
40
|
+
* Used as fallback when server resolution fails or for non-LinkForty URLs.
|
|
22
41
|
*/
|
|
23
42
|
parseURL(url: string): DeepLinkData | null;
|
|
24
43
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeepLinkHandler.d.ts","sourceRoot":"","sources":["../src/DeepLinkHandler.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"DeepLinkHandler.d.ts","sourceRoot":"","sources":["../src/DeepLinkHandler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/E,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,SAAS,CAAgC;IAEjD;;;;;OAKG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,SAAS,CAAC,EAAE,eAAe,GAAG,IAAI;IAgB1F;;OAEG;IACH,OAAO,IAAI,IAAI;IAKf;;;;OAIG;IACH,OAAO,CAAC,cAAc,CAuBpB;IAEF;;;;OAIG;YACW,UAAU;IA2CxB;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;CA4C3C"}
|
package/dist/DeepLinkHandler.js
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DeepLinkHandler - Handles Universal Links (iOS) and App Links (Android)
|
|
3
|
+
*
|
|
4
|
+
* When the OS intercepts a LinkForty URL via App Links or Universal Links,
|
|
5
|
+
* the redirect server is bypassed. This handler detects LinkForty URLs and
|
|
6
|
+
* resolves them via the server API to retrieve the full deep link data
|
|
7
|
+
* (custom parameters, UTM params, etc.) that would normally be appended
|
|
8
|
+
* during the redirect.
|
|
3
9
|
*/
|
|
4
10
|
import { Linking } from 'react-native';
|
|
11
|
+
import { FingerprintCollector } from './FingerprintCollector';
|
|
5
12
|
export class DeepLinkHandler {
|
|
6
13
|
callback = null;
|
|
7
14
|
baseUrl = null;
|
|
15
|
+
resolveFn = null;
|
|
8
16
|
/**
|
|
9
17
|
* Initialize deep link listener
|
|
18
|
+
* @param baseUrl - LinkForty instance base URL for detecting LinkForty URLs
|
|
19
|
+
* @param callback - Callback invoked with parsed/resolved deep link data
|
|
20
|
+
* @param resolveFn - Optional function to resolve LinkForty URLs via the server API
|
|
10
21
|
*/
|
|
11
|
-
initialize(baseUrl, callback) {
|
|
22
|
+
initialize(baseUrl, callback, resolveFn) {
|
|
12
23
|
this.baseUrl = baseUrl;
|
|
13
24
|
this.callback = callback;
|
|
25
|
+
this.resolveFn = resolveFn || null;
|
|
14
26
|
// Listen for deep links when app is already open
|
|
15
27
|
Linking.addEventListener('url', this.handleDeepLink);
|
|
16
28
|
// Check if app was opened via deep link
|
|
@@ -28,17 +40,78 @@ export class DeepLinkHandler {
|
|
|
28
40
|
this.callback = null;
|
|
29
41
|
}
|
|
30
42
|
/**
|
|
31
|
-
* Handle incoming deep link
|
|
43
|
+
* Handle incoming deep link.
|
|
44
|
+
* If the URL is a LinkForty URL and a resolver is available, resolves via the server.
|
|
45
|
+
* Falls back to local URL parsing on failure.
|
|
32
46
|
*/
|
|
33
|
-
handleDeepLink = ({ url }) => {
|
|
47
|
+
handleDeepLink = async ({ url }) => {
|
|
34
48
|
if (!this.callback || !url) {
|
|
35
49
|
return;
|
|
36
50
|
}
|
|
37
|
-
|
|
38
|
-
this.
|
|
51
|
+
// Parse locally first (for fallback and to detect LinkForty URLs)
|
|
52
|
+
const localData = this.parseURL(url);
|
|
53
|
+
// If this is a LinkForty URL and we have a resolver, try the server
|
|
54
|
+
if (localData && this.resolveFn && this.baseUrl && url.startsWith(this.baseUrl)) {
|
|
55
|
+
try {
|
|
56
|
+
const resolvedData = await this.resolveURL(url);
|
|
57
|
+
if (resolvedData) {
|
|
58
|
+
this.callback(url, resolvedData);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.warn('[LinkForty] Failed to resolve URL from server, falling back to local parse:', error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Fallback to locally-parsed data
|
|
67
|
+
this.callback(url, localData);
|
|
39
68
|
};
|
|
40
69
|
/**
|
|
41
|
-
*
|
|
70
|
+
* Resolve a LinkForty URL via the server API.
|
|
71
|
+
* Extracts the path from the URL, collects fingerprint data for click attribution,
|
|
72
|
+
* and calls the resolve endpoint.
|
|
73
|
+
*/
|
|
74
|
+
async resolveURL(url) {
|
|
75
|
+
if (!this.resolveFn) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const parsedUrl = new URL(url);
|
|
79
|
+
const pathSegments = parsedUrl.pathname.split('/').filter(Boolean);
|
|
80
|
+
if (pathSegments.length === 0) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
// Build resolve endpoint path (handles both /shortCode and /templateSlug/shortCode)
|
|
84
|
+
let resolvePath;
|
|
85
|
+
if (pathSegments.length >= 2) {
|
|
86
|
+
resolvePath = `/api/sdk/v1/resolve/${pathSegments[0]}/${pathSegments[1]}`;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
resolvePath = `/api/sdk/v1/resolve/${pathSegments[0]}`;
|
|
90
|
+
}
|
|
91
|
+
// Collect fingerprint data for click attribution
|
|
92
|
+
try {
|
|
93
|
+
const fingerprint = await FingerprintCollector.collect();
|
|
94
|
+
const [sw, sh] = fingerprint.screenResolution.split('x');
|
|
95
|
+
const queryParams = new URLSearchParams();
|
|
96
|
+
queryParams.set('fp_tz', fingerprint.timezone);
|
|
97
|
+
queryParams.set('fp_lang', fingerprint.language);
|
|
98
|
+
queryParams.set('fp_sw', sw);
|
|
99
|
+
queryParams.set('fp_sh', sh);
|
|
100
|
+
queryParams.set('fp_platform', fingerprint.platform);
|
|
101
|
+
if (fingerprint.osVersion) {
|
|
102
|
+
queryParams.set('fp_pv', fingerprint.osVersion);
|
|
103
|
+
}
|
|
104
|
+
resolvePath += `?${queryParams.toString()}`;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
// If fingerprint collection fails, still resolve without it
|
|
108
|
+
console.warn('[LinkForty] Fingerprint collection failed, resolving without fingerprint:', error);
|
|
109
|
+
}
|
|
110
|
+
return this.resolveFn(resolvePath);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Parse deep link URL and extract data locally (without server call).
|
|
114
|
+
* Used as fallback when server resolution fails or for non-LinkForty URLs.
|
|
42
115
|
*/
|
|
43
116
|
parseURL(url) {
|
|
44
117
|
try {
|
|
@@ -47,8 +120,9 @@ export class DeepLinkHandler {
|
|
|
47
120
|
if (this.baseUrl && !url.startsWith(this.baseUrl)) {
|
|
48
121
|
return null;
|
|
49
122
|
}
|
|
50
|
-
// Extract short code from path
|
|
51
|
-
const
|
|
123
|
+
// Extract short code from path (last segment handles both /shortCode and /templateSlug/shortCode)
|
|
124
|
+
const pathSegments = parsedUrl.pathname.split('/').filter(Boolean);
|
|
125
|
+
const shortCode = pathSegments[pathSegments.length - 1];
|
|
52
126
|
if (!shortCode) {
|
|
53
127
|
return null;
|
|
54
128
|
}
|
package/dist/LinkFortySDK.d.ts
CHANGED
|
@@ -21,7 +21,9 @@ export declare class LinkFortySDK {
|
|
|
21
21
|
*/
|
|
22
22
|
onDeferredDeepLink(callback: DeferredDeepLinkCallback): void;
|
|
23
23
|
/**
|
|
24
|
-
* Register callback for direct deep links (existing users)
|
|
24
|
+
* Register callback for direct deep links (existing users).
|
|
25
|
+
* When a LinkForty URL is detected, the SDK will resolve it via the server
|
|
26
|
+
* to retrieve the full deep link data (custom parameters, UTM params, etc.).
|
|
25
27
|
*/
|
|
26
28
|
onDeepLink(callback: DeepLinkCallback): void;
|
|
27
29
|
/**
|
|
@@ -1 +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
|
|
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;;;;OAIG;IACH,UAAU,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAwB5C;;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;IA+F3B;;OAEG;YACW,eAAe;IAI7B;;OAEG;YACW,UAAU;CA+BzB;;AAGD,wBAAkC"}
|
package/dist/LinkFortySDK.js
CHANGED
|
@@ -67,7 +67,9 @@ export class LinkFortySDK {
|
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
|
-
* Register callback for direct deep links (existing users)
|
|
70
|
+
* Register callback for direct deep links (existing users).
|
|
71
|
+
* When a LinkForty URL is detected, the SDK will resolve it via the server
|
|
72
|
+
* to retrieve the full deep link data (custom parameters, UTM params, etc.).
|
|
71
73
|
*/
|
|
72
74
|
onDeepLink(callback) {
|
|
73
75
|
if (!this.config) {
|
|
@@ -76,7 +78,19 @@ export class LinkFortySDK {
|
|
|
76
78
|
if (!this.deepLinkHandler) {
|
|
77
79
|
this.deepLinkHandler = new DeepLinkHandler();
|
|
78
80
|
}
|
|
79
|
-
|
|
81
|
+
// Create a resolver that wraps the private apiRequest method
|
|
82
|
+
const resolveFn = async (path) => {
|
|
83
|
+
try {
|
|
84
|
+
return await this.apiRequest(path);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (this.config?.debug) {
|
|
88
|
+
console.log('[LinkForty] URL resolve failed:', error);
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
this.deepLinkHandler.initialize(this.config.baseUrl, callback, resolveFn);
|
|
80
94
|
}
|
|
81
95
|
/**
|
|
82
96
|
* Track in-app event
|
|
@@ -191,10 +205,18 @@ export class LinkFortySDK {
|
|
|
191
205
|
}
|
|
192
206
|
// If attributed, store deep link data
|
|
193
207
|
if (response.attributed && response.deepLinkData) {
|
|
194
|
-
|
|
208
|
+
// Normalize backend response to match DeepLinkData type
|
|
209
|
+
// Populate customParameters from deepLinkParameters so app developers
|
|
210
|
+
// get a consistent field regardless of direct vs deferred deep link path
|
|
211
|
+
const deepLinkData = {
|
|
212
|
+
...response.deepLinkData,
|
|
213
|
+
customParameters: response.deepLinkData.deepLinkParameters
|
|
214
|
+
|| response.deepLinkData.customParameters,
|
|
215
|
+
};
|
|
216
|
+
await AsyncStorage.setItem(STORAGE_KEYS.INSTALL_DATA, JSON.stringify(deepLinkData));
|
|
195
217
|
// Call deferred deep link callback if registered
|
|
196
218
|
if (this.deferredDeepLinkCallback) {
|
|
197
|
-
this.deferredDeepLinkCallback(
|
|
219
|
+
this.deferredDeepLinkCallback(deepLinkData);
|
|
198
220
|
}
|
|
199
221
|
if (this.config.debug) {
|
|
200
222
|
console.log('[LinkForty] Install attributed with confidence:', response.confidenceScore);
|
package/dist/index.d.ts
CHANGED
|
@@ -9,5 +9,5 @@ export { default } from './LinkFortySDK';
|
|
|
9
9
|
export { LinkFortySDK } from './LinkFortySDK';
|
|
10
10
|
export { FingerprintCollector } from './FingerprintCollector';
|
|
11
11
|
export { DeepLinkHandler } from './DeepLinkHandler';
|
|
12
|
-
export type { LinkFortyConfig, DeviceFingerprint, DeepLinkData, InstallAttributionResponse, EventData, DeferredDeepLinkCallback, DeepLinkCallback, } from './types';
|
|
12
|
+
export type { LinkFortyConfig, DeviceFingerprint, DeepLinkData, InstallAttributionResponse, EventData, DeferredDeepLinkCallback, DeepLinkCallback, ResolveFunction, } from './types';
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +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,
|
|
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,EAChB,eAAe,GAChB,MAAM,SAAS,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -57,8 +57,12 @@ export interface DeepLinkData {
|
|
|
57
57
|
term?: string;
|
|
58
58
|
content?: string;
|
|
59
59
|
};
|
|
60
|
-
/** Custom query
|
|
60
|
+
/** Custom parameters (from URL query params on direct links, or from link configuration on deferred links) */
|
|
61
61
|
customParameters?: Record<string, string>;
|
|
62
|
+
/** In-app destination path (e.g., '/video/123') */
|
|
63
|
+
deepLinkPath?: string;
|
|
64
|
+
/** Custom URI scheme (e.g., 'myapp') */
|
|
65
|
+
appScheme?: string;
|
|
62
66
|
/** Click timestamp */
|
|
63
67
|
clickedAt?: string;
|
|
64
68
|
/** Link ID */
|
|
@@ -103,4 +107,10 @@ export type DeferredDeepLinkCallback = (deepLinkData: DeepLinkData | null) => vo
|
|
|
103
107
|
* Callback function for direct deep links
|
|
104
108
|
*/
|
|
105
109
|
export type DeepLinkCallback = (url: string, deepLinkData: DeepLinkData | null) => void;
|
|
110
|
+
/**
|
|
111
|
+
* Function that resolves a LinkForty URL path to deep link data via the server.
|
|
112
|
+
* Used internally by DeepLinkHandler when the OS intercepts a LinkForty URL
|
|
113
|
+
* before the server can process the redirect.
|
|
114
|
+
*/
|
|
115
|
+
export type ResolveFunction = (path: string) => Promise<DeepLinkData | null>;
|
|
106
116
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +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,
|
|
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,8GAA8G;IAC9G,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,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;AAExF;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linkforty/mobile-sdk-react-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "React Native SDK for LinkForty - Open-source deep linking and mobile attribution platform",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"npm": ">=10.0.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"react": ">=
|
|
45
|
-
"react-native": ">=0.
|
|
44
|
+
"react": ">=17.0.0",
|
|
45
|
+
"react-native": ">=0.64.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
package/src/DeepLinkHandler.ts
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DeepLinkHandler - Handles Universal Links (iOS) and App Links (Android)
|
|
3
|
+
*
|
|
4
|
+
* When the OS intercepts a LinkForty URL via App Links or Universal Links,
|
|
5
|
+
* the redirect server is bypassed. This handler detects LinkForty URLs and
|
|
6
|
+
* resolves them via the server API to retrieve the full deep link data
|
|
7
|
+
* (custom parameters, UTM params, etc.) that would normally be appended
|
|
8
|
+
* during the redirect.
|
|
3
9
|
*/
|
|
4
10
|
|
|
5
11
|
import { Linking } from 'react-native';
|
|
6
|
-
import
|
|
12
|
+
import { FingerprintCollector } from './FingerprintCollector';
|
|
13
|
+
import type { DeepLinkData, DeepLinkCallback, ResolveFunction } from './types';
|
|
7
14
|
|
|
8
15
|
export class DeepLinkHandler {
|
|
9
16
|
private callback: DeepLinkCallback | null = null;
|
|
10
17
|
private baseUrl: string | null = null;
|
|
18
|
+
private resolveFn: ResolveFunction | null = null;
|
|
11
19
|
|
|
12
20
|
/**
|
|
13
21
|
* Initialize deep link listener
|
|
22
|
+
* @param baseUrl - LinkForty instance base URL for detecting LinkForty URLs
|
|
23
|
+
* @param callback - Callback invoked with parsed/resolved deep link data
|
|
24
|
+
* @param resolveFn - Optional function to resolve LinkForty URLs via the server API
|
|
14
25
|
*/
|
|
15
|
-
initialize(baseUrl: string, callback: DeepLinkCallback): void {
|
|
26
|
+
initialize(baseUrl: string, callback: DeepLinkCallback, resolveFn?: ResolveFunction): void {
|
|
16
27
|
this.baseUrl = baseUrl;
|
|
17
28
|
this.callback = callback;
|
|
29
|
+
this.resolveFn = resolveFn || null;
|
|
18
30
|
|
|
19
31
|
// Listen for deep links when app is already open
|
|
20
32
|
Linking.addEventListener('url', this.handleDeepLink);
|
|
@@ -36,19 +48,86 @@ export class DeepLinkHandler {
|
|
|
36
48
|
}
|
|
37
49
|
|
|
38
50
|
/**
|
|
39
|
-
* Handle incoming deep link
|
|
51
|
+
* Handle incoming deep link.
|
|
52
|
+
* If the URL is a LinkForty URL and a resolver is available, resolves via the server.
|
|
53
|
+
* Falls back to local URL parsing on failure.
|
|
40
54
|
*/
|
|
41
|
-
private handleDeepLink = ({ url }: { url: string }): void => {
|
|
55
|
+
private handleDeepLink = async ({ url }: { url: string }): Promise<void> => {
|
|
42
56
|
if (!this.callback || !url) {
|
|
43
57
|
return;
|
|
44
58
|
}
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
this.
|
|
60
|
+
// Parse locally first (for fallback and to detect LinkForty URLs)
|
|
61
|
+
const localData = this.parseURL(url);
|
|
62
|
+
|
|
63
|
+
// If this is a LinkForty URL and we have a resolver, try the server
|
|
64
|
+
if (localData && this.resolveFn && this.baseUrl && url.startsWith(this.baseUrl)) {
|
|
65
|
+
try {
|
|
66
|
+
const resolvedData = await this.resolveURL(url);
|
|
67
|
+
if (resolvedData) {
|
|
68
|
+
this.callback(url, resolvedData);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.warn('[LinkForty] Failed to resolve URL from server, falling back to local parse:', error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Fallback to locally-parsed data
|
|
77
|
+
this.callback(url, localData);
|
|
48
78
|
};
|
|
49
79
|
|
|
50
80
|
/**
|
|
51
|
-
*
|
|
81
|
+
* Resolve a LinkForty URL via the server API.
|
|
82
|
+
* Extracts the path from the URL, collects fingerprint data for click attribution,
|
|
83
|
+
* and calls the resolve endpoint.
|
|
84
|
+
*/
|
|
85
|
+
private async resolveURL(url: string): Promise<DeepLinkData | null> {
|
|
86
|
+
if (!this.resolveFn) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const parsedUrl = new URL(url);
|
|
91
|
+
const pathSegments = parsedUrl.pathname.split('/').filter(Boolean);
|
|
92
|
+
|
|
93
|
+
if (pathSegments.length === 0) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Build resolve endpoint path (handles both /shortCode and /templateSlug/shortCode)
|
|
98
|
+
let resolvePath: string;
|
|
99
|
+
if (pathSegments.length >= 2) {
|
|
100
|
+
resolvePath = `/api/sdk/v1/resolve/${pathSegments[0]}/${pathSegments[1]}`;
|
|
101
|
+
} else {
|
|
102
|
+
resolvePath = `/api/sdk/v1/resolve/${pathSegments[0]}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Collect fingerprint data for click attribution
|
|
106
|
+
try {
|
|
107
|
+
const fingerprint = await FingerprintCollector.collect();
|
|
108
|
+
const [sw, sh] = fingerprint.screenResolution.split('x');
|
|
109
|
+
const queryParams = new URLSearchParams();
|
|
110
|
+
queryParams.set('fp_tz', fingerprint.timezone);
|
|
111
|
+
queryParams.set('fp_lang', fingerprint.language);
|
|
112
|
+
queryParams.set('fp_sw', sw);
|
|
113
|
+
queryParams.set('fp_sh', sh);
|
|
114
|
+
queryParams.set('fp_platform', fingerprint.platform);
|
|
115
|
+
if (fingerprint.osVersion) {
|
|
116
|
+
queryParams.set('fp_pv', fingerprint.osVersion);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
resolvePath += `?${queryParams.toString()}`;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
// If fingerprint collection fails, still resolve without it
|
|
122
|
+
console.warn('[LinkForty] Fingerprint collection failed, resolving without fingerprint:', error);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return this.resolveFn(resolvePath);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Parse deep link URL and extract data locally (without server call).
|
|
130
|
+
* Used as fallback when server resolution fails or for non-LinkForty URLs.
|
|
52
131
|
*/
|
|
53
132
|
parseURL(url: string): DeepLinkData | null {
|
|
54
133
|
try {
|
|
@@ -59,8 +138,9 @@ export class DeepLinkHandler {
|
|
|
59
138
|
return null;
|
|
60
139
|
}
|
|
61
140
|
|
|
62
|
-
// Extract short code from path
|
|
63
|
-
const
|
|
141
|
+
// Extract short code from path (last segment handles both /shortCode and /templateSlug/shortCode)
|
|
142
|
+
const pathSegments = parsedUrl.pathname.split('/').filter(Boolean);
|
|
143
|
+
const shortCode = pathSegments[pathSegments.length - 1];
|
|
64
144
|
|
|
65
145
|
if (!shortCode) {
|
|
66
146
|
return null;
|
package/src/LinkFortySDK.ts
CHANGED
|
@@ -88,7 +88,9 @@ export class LinkFortySDK {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
|
-
* Register callback for direct deep links (existing users)
|
|
91
|
+
* Register callback for direct deep links (existing users).
|
|
92
|
+
* When a LinkForty URL is detected, the SDK will resolve it via the server
|
|
93
|
+
* to retrieve the full deep link data (custom parameters, UTM params, etc.).
|
|
92
94
|
*/
|
|
93
95
|
onDeepLink(callback: DeepLinkCallback): void {
|
|
94
96
|
if (!this.config) {
|
|
@@ -99,7 +101,19 @@ export class LinkFortySDK {
|
|
|
99
101
|
this.deepLinkHandler = new DeepLinkHandler();
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
// Create a resolver that wraps the private apiRequest method
|
|
105
|
+
const resolveFn = async (path: string): Promise<DeepLinkData | null> => {
|
|
106
|
+
try {
|
|
107
|
+
return await this.apiRequest<DeepLinkData>(path);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (this.config?.debug) {
|
|
110
|
+
console.log('[LinkForty] URL resolve failed:', error);
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
this.deepLinkHandler.initialize(this.config.baseUrl, callback, resolveFn);
|
|
103
117
|
}
|
|
104
118
|
|
|
105
119
|
/**
|
|
@@ -239,14 +253,23 @@ export class LinkFortySDK {
|
|
|
239
253
|
|
|
240
254
|
// If attributed, store deep link data
|
|
241
255
|
if (response.attributed && response.deepLinkData) {
|
|
256
|
+
// Normalize backend response to match DeepLinkData type
|
|
257
|
+
// Populate customParameters from deepLinkParameters so app developers
|
|
258
|
+
// get a consistent field regardless of direct vs deferred deep link path
|
|
259
|
+
const deepLinkData: DeepLinkData = {
|
|
260
|
+
...response.deepLinkData as DeepLinkData,
|
|
261
|
+
customParameters: (response.deepLinkData as any).deepLinkParameters
|
|
262
|
+
|| (response.deepLinkData as DeepLinkData).customParameters,
|
|
263
|
+
};
|
|
264
|
+
|
|
242
265
|
await AsyncStorage.setItem(
|
|
243
266
|
STORAGE_KEYS.INSTALL_DATA,
|
|
244
|
-
JSON.stringify(
|
|
267
|
+
JSON.stringify(deepLinkData)
|
|
245
268
|
);
|
|
246
269
|
|
|
247
270
|
// Call deferred deep link callback if registered
|
|
248
271
|
if (this.deferredDeepLinkCallback) {
|
|
249
|
-
this.deferredDeepLinkCallback(
|
|
272
|
+
this.deferredDeepLinkCallback(deepLinkData);
|
|
250
273
|
}
|
|
251
274
|
|
|
252
275
|
if (this.config.debug) {
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -60,8 +60,12 @@ export interface DeepLinkData {
|
|
|
60
60
|
term?: string;
|
|
61
61
|
content?: string;
|
|
62
62
|
};
|
|
63
|
-
/** Custom query
|
|
63
|
+
/** Custom parameters (from URL query params on direct links, or from link configuration on deferred links) */
|
|
64
64
|
customParameters?: Record<string, string>;
|
|
65
|
+
/** In-app destination path (e.g., '/video/123') */
|
|
66
|
+
deepLinkPath?: string;
|
|
67
|
+
/** Custom URI scheme (e.g., 'myapp') */
|
|
68
|
+
appScheme?: string;
|
|
65
69
|
/** Click timestamp */
|
|
66
70
|
clickedAt?: string;
|
|
67
71
|
/** Link ID */
|
|
@@ -110,3 +114,10 @@ export type DeferredDeepLinkCallback = (deepLinkData: DeepLinkData | null) => vo
|
|
|
110
114
|
* Callback function for direct deep links
|
|
111
115
|
*/
|
|
112
116
|
export type DeepLinkCallback = (url: string, deepLinkData: DeepLinkData | null) => void;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Function that resolves a LinkForty URL path to deep link data via the server.
|
|
120
|
+
* Used internally by DeepLinkHandler when the OS intercepts a LinkForty URL
|
|
121
|
+
* before the server can process the redirect.
|
|
122
|
+
*/
|
|
123
|
+
export type ResolveFunction = (path: string) => Promise<DeepLinkData | null>;
|