@linkforty/mobile-sdk-react-native 1.0.1 → 1.1.1

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 CHANGED
@@ -1,137 +1,37 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to the LinkForty React Native SDK will be documented in this file.
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.0.0/),
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.0.0] - 2024-11-22
8
+ ## [Unreleased]
9
9
 
10
+ ## [1.1.1] - 2026-02-12
10
11
  ### Added
11
- - Initial release of LinkForty React Native SDK
12
- - **Deferred Deep Linking** - Route new users to specific content after install
13
- - **Direct Deep Linking** - Handle Universal Links (iOS) and App Links (Android)
14
- - **Probabilistic Attribution** - Match installs to clicks with 70%+ confidence
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)
12
+ - `createLink()` method for creating short links programmatically from mobile apps via the LinkForty API (`POST /api/links`)
13
+ - `CreateLinkOptions` type accepts `templateId`, `templateSlug`, optional `deepLinkParameters`, `title`, `description`, `customCode`, and `utmParameters`
14
+ - `CreateLinkResult` type returns `url` (full shareable URL), `shortCode`, and `linkId`
15
+ - Exported `CreateLinkOptions` and `CreateLinkResult` types from package entry point
80
16
 
81
- ### Security
17
+ ## [1.1.0] - 2026-02-11
82
18
 
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]
19
+ ### Added
20
+ - 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
21
+ - Device fingerprint collection sent with resolve requests for click attribution and deferred deep linking analytics
22
+ - `deepLinkPath` and `appScheme` optional fields on `DeepLinkData` type
23
+ - `ResolveFunction` type export for advanced SDK consumers
101
24
 
102
25
  ### Changed
103
- - Removed unused dependencies `@onamfc/developer-log` and `@onamfc/pkg-inspect` to reduce bundle size
104
- - Broadened peer dependency support to React >=17.0.0 and React Native >=0.64.0 for wider compatibility
105
-
106
- ### Planned Features
107
- - iOS SDK (Native Swift) - Q2 2025
108
- - Android SDK (Native Kotlin) - Q2 2025
109
- - React Native Expo compatibility mode
110
- - Offline event queue for poor network conditions
111
- - Advanced debugging tools
112
- - Custom domain support
113
- - A/B testing integration
114
- - User segmentation
115
-
116
- ---
117
-
118
- ## Version History
119
-
120
- - **1.0.0** - Initial release (2024-11-22)
121
-
122
- ## Migration Guides
123
-
124
- ### Upgrading to 1.0.0
125
-
126
- This is the initial release, so no migration is necessary.
127
-
128
- ## Support
26
+ - `DeepLinkHandler.handleDeepLink` is now async to support server-side URL resolution
27
+ - `DeepLinkHandler.initialize` accepts an optional `resolveFn` parameter for URL resolution
28
+ - `DeepLinkHandler.parseURL` now correctly extracts the short code from template URLs (e.g., `/templateSlug/shortCode`)
29
+ - `LinkFortySDK.onDeepLink` automatically provides a resolver that calls the backend resolve endpoint
30
+ - Deferred deep link responses now normalize `deepLinkParameters` from the backend into `customParameters` for a consistent interface across direct and deferred deep link paths
129
31
 
130
- For issues, questions, or feature requests:
131
- - GitHub Issues: https://github.com/linkforty/react-native-sdk/issues
132
- - Documentation: https://docs.linkforty.com
133
- - Discord Community: https://discord.gg/linkforty
32
+ ### Fixed
33
+ - 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
134
34
 
135
- ## License
35
+ ## [1.0.1] - 2025-12-11
136
36
 
137
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
37
+ - Initial release
package/README.md CHANGED
@@ -15,9 +15,9 @@ Official React Native SDK for [LinkForty](https://github.com/linkforty/core) - O
15
15
  ## Installation
16
16
 
17
17
  ```bash
18
- npm install @linkforty/react-native-sdk
18
+ npm i @linkforty/mobile-sdk-react-native
19
19
  # or
20
- yarn add @linkforty/react-native-sdk
20
+ yarn add @linkforty/mobile-sdk-react-native
21
21
  ```
22
22
 
23
23
  ### Requirements
@@ -104,7 +104,7 @@ cd ios && pod install
104
104
  ## Quick Start
105
105
 
106
106
  ```typescript
107
- import LinkForty from '@linkforty/react-native-sdk';
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
- * Parse deep link URL and extract data
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;;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"}
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"}
@@ -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
- const deepLinkData = this.parseURL(url);
38
- this.callback(url, deepLinkData);
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
- * Parse deep link URL and extract data
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 shortCode = parsedUrl.pathname.replace('/', '');
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
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * LinkFortySDK - Main SDK class for LinkForty deep linking and attribution
3
3
  */
4
- import type { LinkFortyConfig, DeepLinkData, DeferredDeepLinkCallback, DeepLinkCallback } from './types';
4
+ import type { LinkFortyConfig, DeepLinkData, DeferredDeepLinkCallback, DeepLinkCallback, CreateLinkOptions, CreateLinkResult } from './types';
5
5
  export declare class LinkFortySDK {
6
6
  private config;
7
7
  private deepLinkHandler;
@@ -21,13 +21,32 @@ 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
  /**
28
30
  * Track in-app event
29
31
  */
30
32
  trackEvent(name: string, properties?: Record<string, any>): Promise<void>;
33
+ /**
34
+ * Create a new short link via the LinkForty API.
35
+ *
36
+ * Requires an API key to be configured in the SDK init options.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * const result = await LinkFortySDK.createLink({
41
+ * templateId: 'uuid-of-template',
42
+ * templateSlug: 'ToQs',
43
+ * deepLinkParameters: { route: 'VIDEO_VIEWER', id: 'video-uuid' },
44
+ * title: 'My Video',
45
+ * });
46
+ * // result.url → 'https://go.example.com/ToQs/abc123'
47
+ * ```
48
+ */
49
+ createLink(options: CreateLinkOptions): Promise<CreateLinkResult>;
31
50
  /**
32
51
  * Get install ID
33
52
  */
@@ -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;;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"}
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,EAChB,iBAAiB,EACjB,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,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAmDvE;;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"}
@@ -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
- this.deepLinkHandler.initialize(this.config.baseUrl, callback);
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
@@ -109,6 +123,62 @@ export class LinkFortySDK {
109
123
  console.error('[LinkForty] Failed to track event:', error);
110
124
  }
111
125
  }
126
+ /**
127
+ * Create a new short link via the LinkForty API.
128
+ *
129
+ * Requires an API key to be configured in the SDK init options.
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * const result = await LinkFortySDK.createLink({
134
+ * templateId: 'uuid-of-template',
135
+ * templateSlug: 'ToQs',
136
+ * deepLinkParameters: { route: 'VIDEO_VIEWER', id: 'video-uuid' },
137
+ * title: 'My Video',
138
+ * });
139
+ * // result.url → 'https://go.example.com/ToQs/abc123'
140
+ * ```
141
+ */
142
+ async createLink(options) {
143
+ if (!this.config) {
144
+ throw new Error('SDK not initialized. Call init() first.');
145
+ }
146
+ if (!this.config.apiKey) {
147
+ throw new Error('API key required to create links. Pass apiKey in init().');
148
+ }
149
+ const body = {
150
+ templateId: options.templateId,
151
+ };
152
+ if (options.deepLinkParameters) {
153
+ body.deepLinkParameters = options.deepLinkParameters;
154
+ }
155
+ if (options.title) {
156
+ body.title = options.title;
157
+ }
158
+ if (options.description) {
159
+ body.description = options.description;
160
+ }
161
+ if (options.customCode) {
162
+ body.customCode = options.customCode;
163
+ }
164
+ if (options.utmParameters) {
165
+ body.utmParameters = options.utmParameters;
166
+ }
167
+ const response = await this.apiRequest('/api/links', {
168
+ method: 'POST',
169
+ body: JSON.stringify(body),
170
+ });
171
+ const shortCode = response.short_code;
172
+ const url = `${this.config.baseUrl}/${options.templateSlug}/${shortCode}`;
173
+ if (this.config.debug) {
174
+ console.log('[LinkForty] Created link:', url);
175
+ }
176
+ return {
177
+ url,
178
+ shortCode,
179
+ linkId: response.id,
180
+ };
181
+ }
112
182
  /**
113
183
  * Get install ID
114
184
  */
@@ -191,10 +261,18 @@ export class LinkFortySDK {
191
261
  }
192
262
  // If attributed, store deep link data
193
263
  if (response.attributed && response.deepLinkData) {
194
- await AsyncStorage.setItem(STORAGE_KEYS.INSTALL_DATA, JSON.stringify(response.deepLinkData));
264
+ // Normalize backend response to match DeepLinkData type
265
+ // Populate customParameters from deepLinkParameters so app developers
266
+ // get a consistent field regardless of direct vs deferred deep link path
267
+ const deepLinkData = {
268
+ ...response.deepLinkData,
269
+ customParameters: response.deepLinkData.deepLinkParameters
270
+ || response.deepLinkData.customParameters,
271
+ };
272
+ await AsyncStorage.setItem(STORAGE_KEYS.INSTALL_DATA, JSON.stringify(deepLinkData));
195
273
  // Call deferred deep link callback if registered
196
274
  if (this.deferredDeepLinkCallback) {
197
- this.deferredDeepLinkCallback(response.deepLinkData);
275
+ this.deferredDeepLinkCallback(deepLinkData);
198
276
  }
199
277
  if (this.config.debug) {
200
278
  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, CreateLinkOptions, CreateLinkResult, } from './types';
13
13
  //# sourceMappingURL=index.d.ts.map
@@ -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,GACjB,MAAM,SAAS,CAAC"}
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,EACf,iBAAiB,EACjB,gBAAgB,GACjB,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 parameters */
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,46 @@ 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>;
116
+ /**
117
+ * Options for creating a new short link via the LinkForty API
118
+ */
119
+ export interface CreateLinkOptions {
120
+ /** Template ID (UUID) — required by the API */
121
+ templateId: string;
122
+ /** Template slug — used to construct the full shareable URL */
123
+ templateSlug: string;
124
+ /** Custom parameters embedded in the link (e.g., { route: 'VIDEO_VIEWER', id: '...' }) */
125
+ deepLinkParameters?: Record<string, string>;
126
+ /** Link title (for internal reference) */
127
+ title?: string;
128
+ /** Link description */
129
+ description?: string;
130
+ /** Custom short code (auto-generated if omitted) */
131
+ customCode?: string;
132
+ /** UTM parameters */
133
+ utmParameters?: {
134
+ source?: string;
135
+ medium?: string;
136
+ campaign?: string;
137
+ term?: string;
138
+ content?: string;
139
+ };
140
+ }
141
+ /**
142
+ * Result of creating a short link
143
+ */
144
+ export interface CreateLinkResult {
145
+ /** Full shareable URL (e.g., 'https://go.example.com/tmpl/abc123') */
146
+ url: string;
147
+ /** The generated short code */
148
+ shortCode: string;
149
+ /** Link UUID */
150
+ linkId: string;
151
+ }
106
152
  //# sourceMappingURL=types.d.ts.map
@@ -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,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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linkforty/mobile-sdk-react-native",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
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",
@@ -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 type { DeepLinkData, DeepLinkCallback } from './types';
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
- const deepLinkData = this.parseURL(url);
47
- this.callback(url, deepLinkData);
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
- * Parse deep link URL and extract data
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 shortCode = parsedUrl.pathname.replace('/', '');
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;
@@ -11,6 +11,8 @@ import type {
11
11
  DeepLinkData,
12
12
  DeferredDeepLinkCallback,
13
13
  DeepLinkCallback,
14
+ CreateLinkOptions,
15
+ CreateLinkResult,
14
16
  } from './types';
15
17
 
16
18
  const STORAGE_KEYS = {
@@ -88,7 +90,9 @@ export class LinkFortySDK {
88
90
  }
89
91
 
90
92
  /**
91
- * Register callback for direct deep links (existing users)
93
+ * Register callback for direct deep links (existing users).
94
+ * When a LinkForty URL is detected, the SDK will resolve it via the server
95
+ * to retrieve the full deep link data (custom parameters, UTM params, etc.).
92
96
  */
93
97
  onDeepLink(callback: DeepLinkCallback): void {
94
98
  if (!this.config) {
@@ -99,7 +103,19 @@ export class LinkFortySDK {
99
103
  this.deepLinkHandler = new DeepLinkHandler();
100
104
  }
101
105
 
102
- this.deepLinkHandler.initialize(this.config.baseUrl, callback);
106
+ // Create a resolver that wraps the private apiRequest method
107
+ const resolveFn = async (path: string): Promise<DeepLinkData | null> => {
108
+ try {
109
+ return await this.apiRequest<DeepLinkData>(path);
110
+ } catch (error) {
111
+ if (this.config?.debug) {
112
+ console.log('[LinkForty] URL resolve failed:', error);
113
+ }
114
+ return null;
115
+ }
116
+ };
117
+
118
+ this.deepLinkHandler.initialize(this.config.baseUrl, callback, resolveFn);
103
119
  }
104
120
 
105
121
  /**
@@ -137,6 +153,73 @@ export class LinkFortySDK {
137
153
  }
138
154
  }
139
155
 
156
+ /**
157
+ * Create a new short link via the LinkForty API.
158
+ *
159
+ * Requires an API key to be configured in the SDK init options.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * const result = await LinkFortySDK.createLink({
164
+ * templateId: 'uuid-of-template',
165
+ * templateSlug: 'ToQs',
166
+ * deepLinkParameters: { route: 'VIDEO_VIEWER', id: 'video-uuid' },
167
+ * title: 'My Video',
168
+ * });
169
+ * // result.url → 'https://go.example.com/ToQs/abc123'
170
+ * ```
171
+ */
172
+ async createLink(options: CreateLinkOptions): Promise<CreateLinkResult> {
173
+ if (!this.config) {
174
+ throw new Error('SDK not initialized. Call init() first.');
175
+ }
176
+
177
+ if (!this.config.apiKey) {
178
+ throw new Error('API key required to create links. Pass apiKey in init().');
179
+ }
180
+
181
+ const body: Record<string, unknown> = {
182
+ templateId: options.templateId,
183
+ };
184
+
185
+ if (options.deepLinkParameters) {
186
+ body.deepLinkParameters = options.deepLinkParameters;
187
+ }
188
+ if (options.title) {
189
+ body.title = options.title;
190
+ }
191
+ if (options.description) {
192
+ body.description = options.description;
193
+ }
194
+ if (options.customCode) {
195
+ body.customCode = options.customCode;
196
+ }
197
+ if (options.utmParameters) {
198
+ body.utmParameters = options.utmParameters;
199
+ }
200
+
201
+ const response = await this.apiRequest<{ id: string; short_code: string }>(
202
+ '/api/links',
203
+ {
204
+ method: 'POST',
205
+ body: JSON.stringify(body),
206
+ },
207
+ );
208
+
209
+ const shortCode = response.short_code;
210
+ const url = `${this.config.baseUrl}/${options.templateSlug}/${shortCode}`;
211
+
212
+ if (this.config.debug) {
213
+ console.log('[LinkForty] Created link:', url);
214
+ }
215
+
216
+ return {
217
+ url,
218
+ shortCode,
219
+ linkId: response.id,
220
+ };
221
+ }
222
+
140
223
  /**
141
224
  * Get install ID
142
225
  */
@@ -239,14 +322,23 @@ export class LinkFortySDK {
239
322
 
240
323
  // If attributed, store deep link data
241
324
  if (response.attributed && response.deepLinkData) {
325
+ // Normalize backend response to match DeepLinkData type
326
+ // Populate customParameters from deepLinkParameters so app developers
327
+ // get a consistent field regardless of direct vs deferred deep link path
328
+ const deepLinkData: DeepLinkData = {
329
+ ...response.deepLinkData as DeepLinkData,
330
+ customParameters: (response.deepLinkData as any).deepLinkParameters
331
+ || (response.deepLinkData as DeepLinkData).customParameters,
332
+ };
333
+
242
334
  await AsyncStorage.setItem(
243
335
  STORAGE_KEYS.INSTALL_DATA,
244
- JSON.stringify(response.deepLinkData)
336
+ JSON.stringify(deepLinkData)
245
337
  );
246
338
 
247
339
  // Call deferred deep link callback if registered
248
340
  if (this.deferredDeepLinkCallback) {
249
- this.deferredDeepLinkCallback(response.deepLinkData as DeepLinkData);
341
+ this.deferredDeepLinkCallback(deepLinkData);
250
342
  }
251
343
 
252
344
  if (this.config.debug) {
package/src/index.ts CHANGED
@@ -23,4 +23,7 @@ export type {
23
23
  EventData,
24
24
  DeferredDeepLinkCallback,
25
25
  DeepLinkCallback,
26
+ ResolveFunction,
27
+ CreateLinkOptions,
28
+ CreateLinkResult,
26
29
  } from './types';
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 parameters */
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,48 @@ 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>;
124
+
125
+ /**
126
+ * Options for creating a new short link via the LinkForty API
127
+ */
128
+ export interface CreateLinkOptions {
129
+ /** Template ID (UUID) — required by the API */
130
+ templateId: string;
131
+ /** Template slug — used to construct the full shareable URL */
132
+ templateSlug: string;
133
+ /** Custom parameters embedded in the link (e.g., { route: 'VIDEO_VIEWER', id: '...' }) */
134
+ deepLinkParameters?: Record<string, string>;
135
+ /** Link title (for internal reference) */
136
+ title?: string;
137
+ /** Link description */
138
+ description?: string;
139
+ /** Custom short code (auto-generated if omitted) */
140
+ customCode?: string;
141
+ /** UTM parameters */
142
+ utmParameters?: {
143
+ source?: string;
144
+ medium?: string;
145
+ campaign?: string;
146
+ term?: string;
147
+ content?: string;
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Result of creating a short link
153
+ */
154
+ export interface CreateLinkResult {
155
+ /** Full shareable URL (e.g., 'https://go.example.com/tmpl/abc123') */
156
+ url: string;
157
+ /** The generated short code */
158
+ shortCode: string;
159
+ /** Link UUID */
160
+ linkId: string;
161
+ }