deep-link-expo 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.
Files changed (105) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +22 -0
  3. package/README.md +138 -0
  4. package/dist/attribution/attribution-context.d.ts +34 -0
  5. package/dist/attribution/attribution-context.d.ts.map +1 -0
  6. package/dist/attribution/attribution-context.js +98 -0
  7. package/dist/attribution/attribution-manager.d.ts +18 -0
  8. package/dist/attribution/attribution-manager.d.ts.map +1 -0
  9. package/dist/attribution/attribution-manager.js +94 -0
  10. package/dist/deeplink/deep-link-handler.d.ts +43 -0
  11. package/dist/deeplink/deep-link-handler.d.ts.map +1 -0
  12. package/dist/deeplink/deep-link-handler.js +150 -0
  13. package/dist/deeplink/url-parser.d.ts +13 -0
  14. package/dist/deeplink/url-parser.d.ts.map +1 -0
  15. package/dist/deeplink/url-parser.js +92 -0
  16. package/dist/errors/linkforty-error.d.ts +26 -0
  17. package/dist/errors/linkforty-error.d.ts.map +1 -0
  18. package/dist/errors/linkforty-error.js +50 -0
  19. package/dist/events/event-queue.d.ts +14 -0
  20. package/dist/events/event-queue.d.ts.map +1 -0
  21. package/dist/events/event-queue.js +68 -0
  22. package/dist/events/event-tracker.d.ts +18 -0
  23. package/dist/events/event-tracker.d.ts.map +1 -0
  24. package/dist/events/event-tracker.js +92 -0
  25. package/dist/fingerprint/fingerprint-collector.d.ts +8 -0
  26. package/dist/fingerprint/fingerprint-collector.d.ts.map +1 -0
  27. package/dist/fingerprint/fingerprint-collector.js +27 -0
  28. package/dist/index.d.ts +18 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +6 -0
  31. package/dist/linkforty-sdk.d.ts +53 -0
  32. package/dist/linkforty-sdk.d.ts.map +1 -0
  33. package/dist/linkforty-sdk.js +255 -0
  34. package/dist/logger.d.ts +7 -0
  35. package/dist/logger.d.ts.map +1 -0
  36. package/dist/logger.js +18 -0
  37. package/dist/models/attribution.d.ts +28 -0
  38. package/dist/models/attribution.d.ts.map +1 -0
  39. package/dist/models/attribution.js +9 -0
  40. package/dist/models/config.d.ts +72 -0
  41. package/dist/models/config.d.ts.map +1 -0
  42. package/dist/models/config.js +26 -0
  43. package/dist/models/create-link-options.d.ts +15 -0
  44. package/dist/models/create-link-options.d.ts.map +1 -0
  45. package/dist/models/create-link-options.js +1 -0
  46. package/dist/models/create-link-result.d.ts +8 -0
  47. package/dist/models/create-link-result.d.ts.map +1 -0
  48. package/dist/models/create-link-result.js +1 -0
  49. package/dist/models/deep-link-data.d.ts +14 -0
  50. package/dist/models/deep-link-data.d.ts.map +1 -0
  51. package/dist/models/deep-link-data.js +1 -0
  52. package/dist/models/device-fingerprint.d.ts +13 -0
  53. package/dist/models/device-fingerprint.d.ts.map +1 -0
  54. package/dist/models/device-fingerprint.js +1 -0
  55. package/dist/models/event-request.d.ts +13 -0
  56. package/dist/models/event-request.d.ts.map +1 -0
  57. package/dist/models/event-request.js +1 -0
  58. package/dist/models/install-response.d.ts +9 -0
  59. package/dist/models/install-response.d.ts.map +1 -0
  60. package/dist/models/install-response.js +1 -0
  61. package/dist/models/utm-parameters.d.ts +8 -0
  62. package/dist/models/utm-parameters.d.ts.map +1 -0
  63. package/dist/models/utm-parameters.js +1 -0
  64. package/dist/navigation/navigation-tracker.d.ts +52 -0
  65. package/dist/navigation/navigation-tracker.d.ts.map +1 -0
  66. package/dist/navigation/navigation-tracker.js +153 -0
  67. package/dist/network/network-manager.d.ts +13 -0
  68. package/dist/network/network-manager.d.ts.map +1 -0
  69. package/dist/network/network-manager.js +90 -0
  70. package/dist/storage/storage-keys.d.ts +8 -0
  71. package/dist/storage/storage-keys.d.ts.map +1 -0
  72. package/dist/storage/storage-keys.js +7 -0
  73. package/dist/storage/storage-manager.d.ts +20 -0
  74. package/dist/storage/storage-manager.d.ts.map +1 -0
  75. package/dist/storage/storage-manager.js +46 -0
  76. package/dist/version.d.ts +3 -0
  77. package/dist/version.d.ts.map +1 -0
  78. package/dist/version.js +3 -0
  79. package/llms.txt +392 -0
  80. package/package.json +83 -0
  81. package/src/attribution/attribution-context.ts +106 -0
  82. package/src/attribution/attribution-manager.ts +126 -0
  83. package/src/deeplink/deep-link-handler.ts +171 -0
  84. package/src/deeplink/url-parser.ts +96 -0
  85. package/src/errors/linkforty-error.ts +89 -0
  86. package/src/events/event-queue.ts +75 -0
  87. package/src/events/event-tracker.ts +118 -0
  88. package/src/fingerprint/fingerprint-collector.ts +35 -0
  89. package/src/index.ts +29 -0
  90. package/src/linkforty-sdk.ts +342 -0
  91. package/src/logger.ts +23 -0
  92. package/src/models/attribution.ts +29 -0
  93. package/src/models/config.ts +109 -0
  94. package/src/models/create-link-options.ts +15 -0
  95. package/src/models/create-link-result.ts +7 -0
  96. package/src/models/deep-link-data.ts +14 -0
  97. package/src/models/device-fingerprint.ts +12 -0
  98. package/src/models/event-request.ts +15 -0
  99. package/src/models/install-response.ts +9 -0
  100. package/src/models/utm-parameters.ts +7 -0
  101. package/src/navigation/navigation-tracker.ts +195 -0
  102. package/src/network/network-manager.ts +111 -0
  103. package/src/storage/storage-keys.ts +7 -0
  104. package/src/storage/storage-manager.ts +61 -0
  105. package/src/version.ts +3 -0
@@ -0,0 +1,53 @@
1
+ import type { LinkFortyConfig } from './models/config';
2
+ import type { InstallAttributionResponse } from './models/install-response';
3
+ import type { DeepLinkData } from './models/deep-link-data';
4
+ import type { CreateLinkOptions } from './models/create-link-options';
5
+ import type { CreateLinkResult } from './models/create-link-result';
6
+ import type { DeferredDeepLinkCallback, DeepLinkCallback } from './deeplink/deep-link-handler';
7
+ export declare class LinkFortySDK {
8
+ private config;
9
+ private networkManager;
10
+ private attributionManager;
11
+ private attributionContext;
12
+ private deepLinkHandler;
13
+ private eventTracker;
14
+ private navigationTracker;
15
+ private externalUserId;
16
+ private _isInitialized;
17
+ get isInitialized(): boolean;
18
+ initialize(config: LinkFortyConfig): Promise<InstallAttributionResponse>;
19
+ onDeferredDeepLink(callback: DeferredDeepLinkCallback): void;
20
+ onDeepLink(callback: DeepLinkCallback): void;
21
+ handleDeepLink(url: string): void;
22
+ /**
23
+ * Sets the external user ID for attribution. This ID will be attached to all
24
+ * links created via createLink() unless overridden per-call. Pass null to clear.
25
+ */
26
+ setExternalUserId(id: string | null): void;
27
+ /**
28
+ * Returns the current external user ID, if set.
29
+ */
30
+ getExternalUserId(): string | null;
31
+ trackEvent(name: string, properties?: Record<string, unknown>): Promise<void>;
32
+ trackRevenue(amount: number, currency: string, properties?: Record<string, unknown>): Promise<void>;
33
+ flushEvents(): Promise<void>;
34
+ clearEventQueue(): Promise<void>;
35
+ get queuedEventCount(): number;
36
+ createLink(options: CreateLinkOptions): Promise<CreateLinkResult>;
37
+ getInstallId(): Promise<string | null>;
38
+ getInstallData(): Promise<DeepLinkData | null>;
39
+ isFirstLaunch(): Promise<boolean>;
40
+ clearData(): Promise<void>;
41
+ reset(): void;
42
+ /**
43
+ * Current session id — identifies one app-open journey. Rotates on cold start
44
+ * and on each new deep-link open. Used to group a visit's screen-flow.
45
+ */
46
+ getSessionId(): string | null;
47
+ private requireInitialized;
48
+ private requireDeepLinkHandler;
49
+ private requireEventTracker;
50
+ }
51
+ declare const _default: LinkFortySDK;
52
+ export default _default;
53
+ //# sourceMappingURL=linkforty-sdk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkforty-sdk.d.ts","sourceRoot":"","sources":["../src/linkforty-sdk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAc/F,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,iBAAiB,CAAkC;IAC3D,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,cAAc,CAAS;IAE/B,IAAI,aAAa,IAAI,OAAO,CAE3B;IAIK,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAmH9E,kBAAkB,CAAC,QAAQ,EAAE,wBAAwB,GAAG,IAAI;IAI5D,UAAU,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAI5C,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAMjC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAK1C;;OAEG;IACH,iBAAiB,IAAI,MAAM,GAAG,IAAI;IAM5B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7E,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,OAAO,CAAC,IAAI,CAAC;IAIV,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAItC,IAAI,gBAAgB,IAAI,MAAM,CAE7B;IAIK,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA6DjE,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAItC,cAAc,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAI9C,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IAMjC,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAShC,KAAK,IAAI,IAAI;IAiBb;;;OAGG;IACH,YAAY,IAAI,MAAM,GAAG,IAAI;IAM7B,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,sBAAsB;IAO9B,OAAO,CAAC,mBAAmB;CAM5B;;AAED,wBAAkC"}
@@ -0,0 +1,255 @@
1
+ import { validateConfig } from './models/config';
2
+ import { LinkFortyError } from './errors/linkforty-error';
3
+ import { NetworkManager } from './network/network-manager';
4
+ import { StorageManager } from './storage/storage-manager';
5
+ import { FingerprintCollector } from './fingerprint/fingerprint-collector';
6
+ import { AttributionManager } from './attribution/attribution-manager';
7
+ import { AttributionContext } from './attribution/attribution-context';
8
+ import { DeepLinkHandler } from './deeplink/deep-link-handler';
9
+ import { EventTracker } from './events/event-tracker';
10
+ import { EventQueue } from './events/event-queue';
11
+ import { NavigationTracker } from './navigation/navigation-tracker';
12
+ import { logger } from './logger';
13
+ export class LinkFortySDK {
14
+ config = null;
15
+ networkManager = null;
16
+ attributionManager = null;
17
+ attributionContext = null;
18
+ deepLinkHandler = null;
19
+ eventTracker = null;
20
+ navigationTracker = null;
21
+ externalUserId = null;
22
+ _isInitialized = false;
23
+ get isInitialized() {
24
+ return this._isInitialized;
25
+ }
26
+ // -- Initialization --
27
+ async initialize(config) {
28
+ if (this._isInitialized) {
29
+ throw LinkFortyError.alreadyInitialized();
30
+ }
31
+ validateConfig(config);
32
+ this.config = config;
33
+ logger.setDebug(config.debug ?? false);
34
+ logger.log('Initializing SDK with config:', {
35
+ baseUrl: config.baseUrl,
36
+ debug: config.debug,
37
+ attributionWindowHours: config.attributionWindowHours,
38
+ linkPathPrefix: config.linkPathPrefix,
39
+ apiKey: config.apiKey ? '***' : undefined,
40
+ });
41
+ // Create managers
42
+ const networkManager = new NetworkManager(config.baseUrl, config.apiKey);
43
+ const storageManager = new StorageManager();
44
+ const fingerprintCollector = new FingerprintCollector();
45
+ const eventQueue = new EventQueue();
46
+ await eventQueue.load();
47
+ this.networkManager = networkManager;
48
+ this.attributionManager = new AttributionManager(networkManager, storageManager, fingerprintCollector);
49
+ // Last-click attribution context: restore any persisted active link before
50
+ // we attribute the install or handle deep links.
51
+ const attributionContext = new AttributionContext();
52
+ await attributionContext.load();
53
+ this.attributionContext = attributionContext;
54
+ this.eventTracker = new EventTracker(networkManager, storageManager, eventQueue, attributionContext);
55
+ const deepLinkHandler = new DeepLinkHandler(config.baseUrl, networkManager, fingerprintCollector, config.linkPathPrefix);
56
+ this.deepLinkHandler = deepLinkHandler;
57
+ // Record every direct (re-engagement) deep-link open into the attribution
58
+ // context so subsequent events credit the link the user just tapped — not
59
+ // their original install link. Registered before any developer callbacks.
60
+ deepLinkHandler.onDeepLink((_url, data) => {
61
+ if (data?.linkId) {
62
+ void attributionContext.recordDeepLinkOpen(data.linkId);
63
+ }
64
+ });
65
+ this._isInitialized = true;
66
+ // Report install
67
+ const attributionWindowHours = config.attributionWindowHours ?? 168;
68
+ const response = await this.attributionManager.reportInstall(attributionWindowHours, undefined, // deviceId — IDFA/GAID not collected by Expo SDK
69
+ config.appToken);
70
+ // If attributed, deliver deferred deep link + seed the attribution context so
71
+ // the new user's first-session events credit the install link.
72
+ if (response.attributed && response.deepLinkData) {
73
+ if (response.deepLinkData.linkId) {
74
+ await attributionContext.recordDeepLinkOpen(response.deepLinkData.linkId);
75
+ }
76
+ deepLinkHandler.deliverDeferredDeepLink(response.deepLinkData);
77
+ }
78
+ else {
79
+ deepLinkHandler.deliverDeferredDeepLink(null);
80
+ }
81
+ // Start listening for direct deep links
82
+ deepLinkHandler.startListening();
83
+ // Optional auto screen-view tracking. Screen views flow through trackEvent,
84
+ // so they inherit the attribution stamp. Guarded: a missing/invalid
85
+ // navigationRef (or no react-navigation) is a no-op.
86
+ if (config.autoTrackNavigation) {
87
+ if (config.navigationRef) {
88
+ const navOptions = typeof config.autoTrackNavigation === 'object' ? config.autoTrackNavigation : {};
89
+ this.navigationTracker = new NavigationTracker(config.navigationRef, (name, properties) => {
90
+ void this.trackEvent(name, properties);
91
+ }, { captureParams: navOptions.captureParams, debounceMs: navOptions.debounceMs });
92
+ this.navigationTracker.start();
93
+ }
94
+ else {
95
+ logger.warn('autoTrackNavigation is enabled but no navigationRef was provided — screen tracking is disabled.');
96
+ }
97
+ }
98
+ logger.log('SDK initialized successfully (attributed:', response.attributed, ')');
99
+ return response;
100
+ }
101
+ // -- Deep Linking --
102
+ onDeferredDeepLink(callback) {
103
+ this.requireDeepLinkHandler().onDeferredDeepLink(callback);
104
+ }
105
+ onDeepLink(callback) {
106
+ this.requireDeepLinkHandler().onDeepLink(callback);
107
+ }
108
+ handleDeepLink(url) {
109
+ this.requireDeepLinkHandler().handleDeepLink(url);
110
+ }
111
+ // -- User Identity --
112
+ /**
113
+ * Sets the external user ID for attribution. This ID will be attached to all
114
+ * links created via createLink() unless overridden per-call. Pass null to clear.
115
+ */
116
+ setExternalUserId(id) {
117
+ this.externalUserId = id;
118
+ logger.log('External user ID set:', id);
119
+ }
120
+ /**
121
+ * Returns the current external user ID, if set.
122
+ */
123
+ getExternalUserId() {
124
+ return this.externalUserId;
125
+ }
126
+ // -- Event Tracking --
127
+ async trackEvent(name, properties) {
128
+ return this.requireEventTracker().trackEvent(name, properties);
129
+ }
130
+ async trackRevenue(amount, currency, properties) {
131
+ return this.requireEventTracker().trackRevenue(amount, currency, properties);
132
+ }
133
+ async flushEvents() {
134
+ return this.requireEventTracker().flushQueue();
135
+ }
136
+ async clearEventQueue() {
137
+ return this.requireEventTracker().clearQueue();
138
+ }
139
+ get queuedEventCount() {
140
+ return this.eventTracker?.queuedEventCount ?? 0;
141
+ }
142
+ // -- Link Creation --
143
+ async createLink(options) {
144
+ this.requireInitialized();
145
+ if (!this.config.apiKey) {
146
+ throw LinkFortyError.missingApiKey();
147
+ }
148
+ const body = {};
149
+ if (options.templateId)
150
+ body.templateId = options.templateId;
151
+ if (options.deepLinkParameters)
152
+ body.deepLinkParameters = options.deepLinkParameters;
153
+ if (options.title)
154
+ body.title = options.title;
155
+ if (options.description)
156
+ body.description = options.description;
157
+ if (options.customCode)
158
+ body.customCode = options.customCode;
159
+ if (options.utmParameters)
160
+ body.utmParameters = options.utmParameters;
161
+ if (options.webFallbackUrl)
162
+ body.webFallbackUrl = options.webFallbackUrl;
163
+ // Per-call externalUserId takes precedence, then fall back to SDK-level value
164
+ const resolvedUserId = options.externalUserId ?? this.externalUserId;
165
+ if (resolvedUserId)
166
+ body.externalUserId = resolvedUserId;
167
+ const useSimplifiedEndpoint = !options.templateId;
168
+ const endpoint = useSimplifiedEndpoint ? '/api/sdk/v1/links' : '/api/links';
169
+ const response = await this.networkManager.request(endpoint, {
170
+ method: 'POST',
171
+ body: JSON.stringify(body),
172
+ });
173
+ // SDK endpoint returns { url, shortCode, linkId, deduplicated? } directly
174
+ if (useSimplifiedEndpoint && response.url) {
175
+ return {
176
+ url: response.url,
177
+ shortCode: response.shortCode || response.short_code,
178
+ linkId: response.linkId || response.id,
179
+ deduplicated: response.deduplicated,
180
+ };
181
+ }
182
+ // Dashboard endpoint returns { id, short_code } — build URL from parts
183
+ const shortCode = response.short_code;
184
+ const baseUrl = this.config.baseUrl.replace(/\/+$/, '');
185
+ const prefix = (options.templateSlug || this.config.linkPathPrefix || '').replace(/^\/+|\/+$/g, '');
186
+ const url = prefix
187
+ ? `${baseUrl}/${prefix}/${shortCode}`
188
+ : `${baseUrl}/${shortCode}`;
189
+ return {
190
+ url,
191
+ shortCode,
192
+ linkId: response.id,
193
+ };
194
+ }
195
+ // -- Attribution Data --
196
+ async getInstallId() {
197
+ return this.attributionManager?.getInstallId() ?? null;
198
+ }
199
+ async getInstallData() {
200
+ return this.attributionManager?.getInstallData() ?? null;
201
+ }
202
+ async isFirstLaunch() {
203
+ return this.attributionManager?.isFirstLaunch() ?? true;
204
+ }
205
+ // -- Data Management --
206
+ async clearData() {
207
+ await this.attributionManager?.clearData();
208
+ await this.attributionContext?.clear();
209
+ await this.eventTracker?.clearQueue();
210
+ this.deepLinkHandler?.clearCallbacks();
211
+ this.externalUserId = null;
212
+ logger.log('All SDK data cleared');
213
+ }
214
+ reset() {
215
+ this.deepLinkHandler?.cleanup();
216
+ this.navigationTracker?.stop();
217
+ this.config = null;
218
+ this.networkManager = null;
219
+ this.attributionManager = null;
220
+ this.attributionContext = null;
221
+ this.deepLinkHandler = null;
222
+ this.eventTracker = null;
223
+ this.navigationTracker = null;
224
+ this.externalUserId = null;
225
+ this._isInitialized = false;
226
+ logger.log('SDK reset to uninitialized state');
227
+ }
228
+ // -- Attribution Session --
229
+ /**
230
+ * Current session id — identifies one app-open journey. Rotates on cold start
231
+ * and on each new deep-link open. Used to group a visit's screen-flow.
232
+ */
233
+ getSessionId() {
234
+ return this.attributionContext?.getSessionId() ?? null;
235
+ }
236
+ // -- Guards --
237
+ requireInitialized() {
238
+ if (!this._isInitialized) {
239
+ throw LinkFortyError.notInitialized();
240
+ }
241
+ }
242
+ requireDeepLinkHandler() {
243
+ if (!this._isInitialized || !this.deepLinkHandler) {
244
+ throw LinkFortyError.notInitialized();
245
+ }
246
+ return this.deepLinkHandler;
247
+ }
248
+ requireEventTracker() {
249
+ if (!this._isInitialized || !this.eventTracker) {
250
+ throw LinkFortyError.notInitialized();
251
+ }
252
+ return this.eventTracker;
253
+ }
254
+ }
255
+ export default new LinkFortySDK();
@@ -0,0 +1,7 @@
1
+ export declare const logger: {
2
+ setDebug(enabled: boolean): void;
3
+ log(...args: unknown[]): void;
4
+ warn(...args: unknown[]): void;
5
+ error(...args: unknown[]): void;
6
+ };
7
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,MAAM;sBACC,OAAO,GAAG,IAAI;iBAInB,OAAO,EAAE,GAAG,IAAI;kBAMf,OAAO,EAAE,GAAG,IAAI;mBAIf,OAAO,EAAE,GAAG,IAAI;CAGhC,CAAC"}
package/dist/logger.js ADDED
@@ -0,0 +1,18 @@
1
+ const TAG = '[LinkForty]';
2
+ let debugEnabled = false;
3
+ export const logger = {
4
+ setDebug(enabled) {
5
+ debugEnabled = enabled;
6
+ },
7
+ log(...args) {
8
+ if (debugEnabled) {
9
+ console.log(TAG, ...args);
10
+ }
11
+ },
12
+ warn(...args) {
13
+ console.warn(TAG, ...args);
14
+ },
15
+ error(...args) {
16
+ console.error(TAG, ...args);
17
+ },
18
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Last-click attribution models (SIT-237).
3
+ *
4
+ * In-app activity (screen views + custom events) is attributed to the deep link
5
+ * that drove it. Every deep-link open pins an active context; the newest open
6
+ * supersedes the previous one; events are stamped with the active context so the
7
+ * backend can credit the link under a last-click + window model.
8
+ */
9
+ /** The deep link currently credited for in-app activity (last-click). */
10
+ export interface ActiveAttribution {
11
+ /** The link the deep link resolved to (`DeepLinkData.linkId`) */
12
+ linkId: string;
13
+ /** Optional originating click id (link-level attribution works without it) */
14
+ clickId?: string;
15
+ /** ISO timestamp of when this deep link opened the app */
16
+ openedAt: string;
17
+ }
18
+ /**
19
+ * Attribution fields merged into every event payload. `sessionId` is always
20
+ * present; the link fields are absent for organic activity (no deep link yet).
21
+ */
22
+ export interface AttributionStamp {
23
+ attributedLinkId?: string;
24
+ attributedClickId?: string;
25
+ linkOpenedAt?: string;
26
+ sessionId: string;
27
+ }
28
+ //# sourceMappingURL=attribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../../src/models/attribution.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Last-click attribution models (SIT-237).
3
+ *
4
+ * In-app activity (screen views + custom events) is attributed to the deep link
5
+ * that drove it. Every deep-link open pins an active context; the newest open
6
+ * supersedes the previous one; events are stamped with the active context so the
7
+ * backend can credit the link under a last-click + window model.
8
+ */
9
+ export {};
@@ -0,0 +1,72 @@
1
+ export interface LinkFortyConfig {
2
+ /** Base URL of your LinkForty instance (e.g., 'https://go.yourdomain.com') */
3
+ baseUrl: string;
4
+ /**
5
+ * Per-app short-link path prefix (e.g. '/mQuDYblrf6'). Required when multiple
6
+ * apps share one domain so short codes resolve under this app's path.
7
+ */
8
+ linkPathPrefix?: string;
9
+ /** Optional API key for Cloud authentication */
10
+ apiKey?: string;
11
+ /**
12
+ * Public workspace token (LinkForty Cloud only). Recommended — required
13
+ * for organic installs (App Store discovery, social mentions, etc.) to
14
+ * be attributed to your workspace. Find it in the dashboard under
15
+ * Workspace Settings → App Token. Safe to ship in your app bundle.
16
+ * Format: `at_<32 hex chars>`.
17
+ */
18
+ appToken?: string;
19
+ /** Enable debug logging (default: false) */
20
+ debug?: boolean;
21
+ /** Attribution window in hours (default: 168 = 7 days) */
22
+ attributionWindowHours?: number;
23
+ /**
24
+ * Auto-emit `screen_view` events from React Navigation state (no manual
25
+ * per-screen calls). Requires `navigationRef`. Off by default.
26
+ *
27
+ * Pass `true` for the privacy-safe default (screen name only, no params). To
28
+ * capture specific non-PII params, pass an options object with an explicit
29
+ * allow-list: `{ captureParams: ['productId', 'category'] }`. Screen views flow
30
+ * through the normal event pipeline and carry the active deep-link attribution
31
+ * context. Apps without react-navigation are unaffected.
32
+ */
33
+ autoTrackNavigation?: boolean | AutoTrackNavigationOptions;
34
+ /**
35
+ * The app's React Navigation container ref (`createNavigationContainerRef()`),
36
+ * required when `autoTrackNavigation` is enabled. Typed structurally so this
37
+ * SDK never has a compile-time dependency on `@react-navigation/native`.
38
+ */
39
+ navigationRef?: NavigationContainerRefLike;
40
+ }
41
+ /**
42
+ * Options for `autoTrackNavigation`. No route params are captured by default;
43
+ * opt in per key via `captureParams`.
44
+ */
45
+ export interface AutoTrackNavigationOptions {
46
+ /**
47
+ * Explicit allow-list of route param keys whose primitive values may be
48
+ * captured on `screen_view`. Omitted/empty = capture no params. Never list
49
+ * keys that can hold personal data.
50
+ */
51
+ captureParams?: string[];
52
+ /** Debounce window for rapid transitions, in ms. Default 350. */
53
+ debounceMs?: number;
54
+ }
55
+ /**
56
+ * The slice of a React Navigation route the SDK reads. Structural — matches
57
+ * `getCurrentRoute()` without importing `@react-navigation/native`.
58
+ */
59
+ export interface NavigationRouteLike {
60
+ name: string;
61
+ params?: Record<string, unknown>;
62
+ }
63
+ /** The slice of a React Navigation container ref the SDK uses. Structural. */
64
+ export interface NavigationContainerRefLike {
65
+ addListener: (type: 'state', callback: (event?: unknown) => void) => (() => void) | {
66
+ remove?: () => void;
67
+ } | void;
68
+ getCurrentRoute: () => NavigationRouteLike | undefined;
69
+ isReady?: () => boolean;
70
+ }
71
+ export declare function validateConfig(config: LinkFortyConfig): void;
72
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/models/config.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0DAA0D;IAC1D,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;;;;OASG;IACH,mBAAmB,CAAC,EAAE,OAAO,GAAG,0BAA0B,CAAC;IAC3D;;;;OAIG;IACH,aAAa,CAAC,EAAE,0BAA0B,CAAC;CAC5C;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,8EAA8E;AAC9E,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,CACX,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,KAChC,CAAC,MAAM,IAAI,CAAC,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IACnD,eAAe,EAAE,MAAM,mBAAmB,GAAG,SAAS,CAAC;IACvD,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC;CACzB;AAID,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CA6B5D"}
@@ -0,0 +1,26 @@
1
+ import { LinkFortyError } from '../errors/linkforty-error';
2
+ const LOCALHOST_HOSTS = ['localhost', '127.0.0.1', '0.0.0.0', '10.0.2.2'];
3
+ export function validateConfig(config) {
4
+ if (!config.baseUrl) {
5
+ throw LinkFortyError.invalidConfiguration('baseUrl is required');
6
+ }
7
+ // Parse URL to validate scheme
8
+ let parsed;
9
+ try {
10
+ parsed = new URL(config.baseUrl);
11
+ }
12
+ catch {
13
+ throw LinkFortyError.invalidConfiguration(`Invalid base URL: ${config.baseUrl}`);
14
+ }
15
+ // HTTPS required except for localhost
16
+ if (parsed.protocol !== 'https:' && !LOCALHOST_HOSTS.includes(parsed.hostname)) {
17
+ throw LinkFortyError.invalidConfiguration('Base URL must use HTTPS (HTTP only allowed for localhost)');
18
+ }
19
+ // Validate attribution window bounds
20
+ const windowHours = config.attributionWindowHours;
21
+ if (windowHours !== undefined) {
22
+ if (!Number.isFinite(windowHours) || windowHours < 1 || windowHours > 2160) {
23
+ throw LinkFortyError.invalidConfiguration('Attribution window must be between 1 and 2160 hours');
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,15 @@
1
+ import type { UTMParameters } from './utm-parameters';
2
+ export interface CreateLinkOptions {
3
+ templateId?: string;
4
+ templateSlug?: string;
5
+ deepLinkParameters?: Record<string, string>;
6
+ title?: string;
7
+ description?: string;
8
+ customCode?: string;
9
+ utmParameters?: UTMParameters;
10
+ /** Identifier for the app user creating the link (enables per-user deduplication and share attribution) */
11
+ externalUserId?: string;
12
+ /** Web fallback URL — used when the app is not installed or on desktop browsers */
13
+ webFallbackUrl?: string;
14
+ }
15
+ //# sourceMappingURL=create-link-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-link-options.d.ts","sourceRoot":"","sources":["../../src/models/create-link-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,2GAA2G;IAC3G,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ export interface CreateLinkResult {
2
+ url: string;
3
+ shortCode: string;
4
+ linkId: string;
5
+ /** True if an existing link was returned instead of creating a new one (per-user deduplication) */
6
+ deduplicated?: boolean;
7
+ }
8
+ //# sourceMappingURL=create-link-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-link-result.d.ts","sourceRoot":"","sources":["../../src/models/create-link-result.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,mGAAmG;IACnG,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { UTMParameters } from './utm-parameters';
2
+ export interface DeepLinkData {
3
+ shortCode: string;
4
+ iosUrl?: string;
5
+ androidUrl?: string;
6
+ webUrl?: string;
7
+ utmParameters?: UTMParameters;
8
+ customParameters?: Record<string, string>;
9
+ deepLinkPath?: string;
10
+ appScheme?: string;
11
+ clickedAt?: string;
12
+ linkId?: string;
13
+ }
14
+ //# sourceMappingURL=deep-link-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deep-link-data.d.ts","sourceRoot":"","sources":["../../src/models/deep-link-data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ export interface DeviceFingerprint {
2
+ userAgent: string;
3
+ timezone: string;
4
+ language: string;
5
+ screenWidth: number;
6
+ screenHeight: number;
7
+ platform: string;
8
+ platformVersion: string;
9
+ appVersion: string;
10
+ deviceId?: string;
11
+ attributionWindowHours: number;
12
+ }
13
+ //# sourceMappingURL=device-fingerprint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-fingerprint.d.ts","sourceRoot":"","sources":["../../src/models/device-fingerprint.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,MAAM,CAAC;CAChC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ export interface EventRequest {
2
+ installId: string;
3
+ eventName: string;
4
+ eventData: Record<string, unknown>;
5
+ timestamp: string;
6
+ attributedLinkId?: string;
7
+ attributedClickId?: string;
8
+ linkOpenedAt?: string;
9
+ sessionId?: string;
10
+ sdkName?: string;
11
+ sdkVersion?: string;
12
+ }
13
+ //# sourceMappingURL=event-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-request.d.ts","sourceRoot":"","sources":["../../src/models/event-request.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAGlB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { DeepLinkData } from './deep-link-data';
2
+ export interface InstallAttributionResponse {
3
+ installId: string;
4
+ attributed: boolean;
5
+ confidenceScore: number;
6
+ matchedFactors: string[];
7
+ deepLinkData: DeepLinkData | null;
8
+ }
9
+ //# sourceMappingURL=install-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-response.d.ts","sourceRoot":"","sources":["../../src/models/install-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;CACnC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ export interface UTMParameters {
2
+ source?: string;
3
+ medium?: string;
4
+ campaign?: string;
5
+ term?: string;
6
+ content?: string;
7
+ }
8
+ //# sourceMappingURL=utm-parameters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utm-parameters.d.ts","sourceRoot":"","sources":["../../src/models/utm-parameters.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
@@ -0,0 +1 @@
1
+ export {};