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.
- package/CHANGELOG.md +70 -0
- package/LICENSE +22 -0
- package/README.md +138 -0
- package/dist/attribution/attribution-context.d.ts +34 -0
- package/dist/attribution/attribution-context.d.ts.map +1 -0
- package/dist/attribution/attribution-context.js +98 -0
- package/dist/attribution/attribution-manager.d.ts +18 -0
- package/dist/attribution/attribution-manager.d.ts.map +1 -0
- package/dist/attribution/attribution-manager.js +94 -0
- package/dist/deeplink/deep-link-handler.d.ts +43 -0
- package/dist/deeplink/deep-link-handler.d.ts.map +1 -0
- package/dist/deeplink/deep-link-handler.js +150 -0
- package/dist/deeplink/url-parser.d.ts +13 -0
- package/dist/deeplink/url-parser.d.ts.map +1 -0
- package/dist/deeplink/url-parser.js +92 -0
- package/dist/errors/linkforty-error.d.ts +26 -0
- package/dist/errors/linkforty-error.d.ts.map +1 -0
- package/dist/errors/linkforty-error.js +50 -0
- package/dist/events/event-queue.d.ts +14 -0
- package/dist/events/event-queue.d.ts.map +1 -0
- package/dist/events/event-queue.js +68 -0
- package/dist/events/event-tracker.d.ts +18 -0
- package/dist/events/event-tracker.d.ts.map +1 -0
- package/dist/events/event-tracker.js +92 -0
- package/dist/fingerprint/fingerprint-collector.d.ts +8 -0
- package/dist/fingerprint/fingerprint-collector.d.ts.map +1 -0
- package/dist/fingerprint/fingerprint-collector.js +27 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/linkforty-sdk.d.ts +53 -0
- package/dist/linkforty-sdk.d.ts.map +1 -0
- package/dist/linkforty-sdk.js +255 -0
- package/dist/logger.d.ts +7 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +18 -0
- package/dist/models/attribution.d.ts +28 -0
- package/dist/models/attribution.d.ts.map +1 -0
- package/dist/models/attribution.js +9 -0
- package/dist/models/config.d.ts +72 -0
- package/dist/models/config.d.ts.map +1 -0
- package/dist/models/config.js +26 -0
- package/dist/models/create-link-options.d.ts +15 -0
- package/dist/models/create-link-options.d.ts.map +1 -0
- package/dist/models/create-link-options.js +1 -0
- package/dist/models/create-link-result.d.ts +8 -0
- package/dist/models/create-link-result.d.ts.map +1 -0
- package/dist/models/create-link-result.js +1 -0
- package/dist/models/deep-link-data.d.ts +14 -0
- package/dist/models/deep-link-data.d.ts.map +1 -0
- package/dist/models/deep-link-data.js +1 -0
- package/dist/models/device-fingerprint.d.ts +13 -0
- package/dist/models/device-fingerprint.d.ts.map +1 -0
- package/dist/models/device-fingerprint.js +1 -0
- package/dist/models/event-request.d.ts +13 -0
- package/dist/models/event-request.d.ts.map +1 -0
- package/dist/models/event-request.js +1 -0
- package/dist/models/install-response.d.ts +9 -0
- package/dist/models/install-response.d.ts.map +1 -0
- package/dist/models/install-response.js +1 -0
- package/dist/models/utm-parameters.d.ts +8 -0
- package/dist/models/utm-parameters.d.ts.map +1 -0
- package/dist/models/utm-parameters.js +1 -0
- package/dist/navigation/navigation-tracker.d.ts +52 -0
- package/dist/navigation/navigation-tracker.d.ts.map +1 -0
- package/dist/navigation/navigation-tracker.js +153 -0
- package/dist/network/network-manager.d.ts +13 -0
- package/dist/network/network-manager.d.ts.map +1 -0
- package/dist/network/network-manager.js +90 -0
- package/dist/storage/storage-keys.d.ts +8 -0
- package/dist/storage/storage-keys.d.ts.map +1 -0
- package/dist/storage/storage-keys.js +7 -0
- package/dist/storage/storage-manager.d.ts +20 -0
- package/dist/storage/storage-manager.d.ts.map +1 -0
- package/dist/storage/storage-manager.js +46 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/llms.txt +392 -0
- package/package.json +83 -0
- package/src/attribution/attribution-context.ts +106 -0
- package/src/attribution/attribution-manager.ts +126 -0
- package/src/deeplink/deep-link-handler.ts +171 -0
- package/src/deeplink/url-parser.ts +96 -0
- package/src/errors/linkforty-error.ts +89 -0
- package/src/events/event-queue.ts +75 -0
- package/src/events/event-tracker.ts +118 -0
- package/src/fingerprint/fingerprint-collector.ts +35 -0
- package/src/index.ts +29 -0
- package/src/linkforty-sdk.ts +342 -0
- package/src/logger.ts +23 -0
- package/src/models/attribution.ts +29 -0
- package/src/models/config.ts +109 -0
- package/src/models/create-link-options.ts +15 -0
- package/src/models/create-link-result.ts +7 -0
- package/src/models/deep-link-data.ts +14 -0
- package/src/models/device-fingerprint.ts +12 -0
- package/src/models/event-request.ts +15 -0
- package/src/models/install-response.ts +9 -0
- package/src/models/utm-parameters.ts +7 -0
- package/src/navigation/navigation-tracker.ts +195 -0
- package/src/network/network-manager.ts +111 -0
- package/src/storage/storage-keys.ts +7 -0
- package/src/storage/storage-manager.ts +61 -0
- package/src/version.ts +3 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NavigationTracker — auto-emits `screen_view` events from React Navigation.
|
|
3
|
+
*
|
|
4
|
+
* When `autoTrackNavigation` is enabled with a `navigationRef`, the SDK
|
|
5
|
+
* subscribes to React Navigation's `'state'` event and emits a `screen_view`
|
|
6
|
+
* for the active route on each change — debounced (rapid transitions collapse to
|
|
7
|
+
* the final screen) and deduped (re-renders that don't change the screen are
|
|
8
|
+
* ignored). The event flows through the normal event pipeline, so it carries the
|
|
9
|
+
* active deep-link attribution context + session.
|
|
10
|
+
*
|
|
11
|
+
* Fully guarded: a missing/malformed `navigationRef` (or an app that doesn't use
|
|
12
|
+
* react-navigation) is a no-op, never a crash. The ref is typed structurally so
|
|
13
|
+
* there is no compile-time dependency on `@react-navigation/native`.
|
|
14
|
+
*/
|
|
15
|
+
import type { NavigationContainerRefLike } from '../models/config';
|
|
16
|
+
/** Emit function the tracker calls — bound to the SDK's `trackEvent`. */
|
|
17
|
+
export type ScreenEventEmitter = (name: string, properties: Record<string, unknown>) => void;
|
|
18
|
+
export interface NavigationTrackerOptions {
|
|
19
|
+
/** Collapse rapid transitions; emit only the settled screen. Default 350ms. */
|
|
20
|
+
debounceMs?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Explicit allow-list of route param keys whose primitive values may be
|
|
23
|
+
* captured. Omitted/empty = capture NO params (screen name only) — the
|
|
24
|
+
* privacy-safe default.
|
|
25
|
+
*/
|
|
26
|
+
captureParams?: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Reduce route params to a safe, flat set limited to an explicit allow-list of
|
|
30
|
+
* keys: only allow-listed keys are considered, and only primitive values
|
|
31
|
+
* survive (strings capped, nested objects/arrays/functions dropped). Returns
|
|
32
|
+
* undefined when nothing survives (including an empty/omitted allow-list).
|
|
33
|
+
*/
|
|
34
|
+
export declare function sanitizeScreenParams(params: Record<string, unknown> | undefined, allowList: string[] | undefined): Record<string, string | number | boolean> | undefined;
|
|
35
|
+
export declare class NavigationTracker {
|
|
36
|
+
private readonly navigationRef;
|
|
37
|
+
private readonly emit;
|
|
38
|
+
private readonly debounceMs;
|
|
39
|
+
private readonly captureParams;
|
|
40
|
+
private unsubscribe;
|
|
41
|
+
private debounceTimer;
|
|
42
|
+
private lastScreen;
|
|
43
|
+
private started;
|
|
44
|
+
constructor(navigationRef: NavigationContainerRefLike, emit: ScreenEventEmitter, options?: NavigationTrackerOptions);
|
|
45
|
+
/** Subscribe to navigation changes. No-op if the ref is malformed. Idempotent. */
|
|
46
|
+
start(): void;
|
|
47
|
+
/** Unsubscribe and clear any pending debounce. Idempotent. */
|
|
48
|
+
stop(): void;
|
|
49
|
+
private scheduleCapture;
|
|
50
|
+
private captureCurrentScreen;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=navigation-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation-tracker.d.ts","sourceRoot":"","sources":["../../src/navigation/navigation-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,0BAA0B,EAAuB,MAAM,kBAAkB,CAAC;AAGxF,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,GAAG,CAC/B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAChC,IAAI,CAAC;AAEV,MAAM,WAAW,wBAAwB;IACvC,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAMD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC3C,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,GAC9B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,SAAS,CAkBvD;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAuB;IAErD,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,OAAO,CAAS;gBAGtB,aAAa,EAAE,0BAA0B,EACzC,IAAI,EAAE,kBAAkB,EACxB,OAAO,GAAE,wBAA6B;IAQxC,kFAAkF;IAClF,KAAK,IAAI,IAAI;IAyCb,8DAA8D;IAC9D,IAAI,IAAI,IAAI;IAiBZ,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,oBAAoB;CAkC7B"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NavigationTracker — auto-emits `screen_view` events from React Navigation.
|
|
3
|
+
*
|
|
4
|
+
* When `autoTrackNavigation` is enabled with a `navigationRef`, the SDK
|
|
5
|
+
* subscribes to React Navigation's `'state'` event and emits a `screen_view`
|
|
6
|
+
* for the active route on each change — debounced (rapid transitions collapse to
|
|
7
|
+
* the final screen) and deduped (re-renders that don't change the screen are
|
|
8
|
+
* ignored). The event flows through the normal event pipeline, so it carries the
|
|
9
|
+
* active deep-link attribution context + session.
|
|
10
|
+
*
|
|
11
|
+
* Fully guarded: a missing/malformed `navigationRef` (or an app that doesn't use
|
|
12
|
+
* react-navigation) is a no-op, never a crash. The ref is typed structurally so
|
|
13
|
+
* there is no compile-time dependency on `@react-navigation/native`.
|
|
14
|
+
*/
|
|
15
|
+
import { logger } from '../logger';
|
|
16
|
+
const DEFAULT_DEBOUNCE_MS = 350;
|
|
17
|
+
const SCREEN_VIEW_EVENT = 'screen_view';
|
|
18
|
+
const MAX_PARAM_STRING_LENGTH = 256;
|
|
19
|
+
/**
|
|
20
|
+
* Reduce route params to a safe, flat set limited to an explicit allow-list of
|
|
21
|
+
* keys: only allow-listed keys are considered, and only primitive values
|
|
22
|
+
* survive (strings capped, nested objects/arrays/functions dropped). Returns
|
|
23
|
+
* undefined when nothing survives (including an empty/omitted allow-list).
|
|
24
|
+
*/
|
|
25
|
+
export function sanitizeScreenParams(params, allowList) {
|
|
26
|
+
if (!params || typeof params !== 'object' || !allowList || allowList.length === 0) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const allowed = new Set(allowList);
|
|
30
|
+
const out = {};
|
|
31
|
+
for (const [key, value] of Object.entries(params)) {
|
|
32
|
+
if (!allowed.has(key))
|
|
33
|
+
continue;
|
|
34
|
+
if (typeof value === 'string') {
|
|
35
|
+
out[key] =
|
|
36
|
+
value.length > MAX_PARAM_STRING_LENGTH ? value.slice(0, MAX_PARAM_STRING_LENGTH) : value;
|
|
37
|
+
}
|
|
38
|
+
else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
39
|
+
out[key] = value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
43
|
+
}
|
|
44
|
+
export class NavigationTracker {
|
|
45
|
+
navigationRef;
|
|
46
|
+
emit;
|
|
47
|
+
debounceMs;
|
|
48
|
+
captureParams;
|
|
49
|
+
unsubscribe = null;
|
|
50
|
+
debounceTimer = null;
|
|
51
|
+
lastScreen = null;
|
|
52
|
+
started = false;
|
|
53
|
+
constructor(navigationRef, emit, options = {}) {
|
|
54
|
+
this.navigationRef = navigationRef;
|
|
55
|
+
this.emit = emit;
|
|
56
|
+
this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
57
|
+
this.captureParams = options.captureParams;
|
|
58
|
+
}
|
|
59
|
+
/** Subscribe to navigation changes. No-op if the ref is malformed. Idempotent. */
|
|
60
|
+
start() {
|
|
61
|
+
if (this.started)
|
|
62
|
+
return;
|
|
63
|
+
if (!this.navigationRef ||
|
|
64
|
+
typeof this.navigationRef.addListener !== 'function' ||
|
|
65
|
+
typeof this.navigationRef.getCurrentRoute !== 'function') {
|
|
66
|
+
logger.warn('autoTrackNavigation enabled but navigationRef is missing or not a React Navigation container ref — screen tracking is disabled.');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const listener = this.navigationRef.addListener('state', () => {
|
|
71
|
+
this.scheduleCapture();
|
|
72
|
+
});
|
|
73
|
+
if (typeof listener === 'function') {
|
|
74
|
+
this.unsubscribe = listener;
|
|
75
|
+
}
|
|
76
|
+
else if (listener && typeof listener.remove === 'function') {
|
|
77
|
+
this.unsubscribe = () => listener.remove?.();
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
this.unsubscribe = null;
|
|
81
|
+
}
|
|
82
|
+
this.started = true;
|
|
83
|
+
const ready = typeof this.navigationRef.isReady === 'function' ? this.navigationRef.isReady() : true;
|
|
84
|
+
if (ready) {
|
|
85
|
+
this.captureCurrentScreen();
|
|
86
|
+
}
|
|
87
|
+
logger.log('Navigation tracking started');
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
logger.warn('Failed to start navigation tracking:', e);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Unsubscribe and clear any pending debounce. Idempotent. */
|
|
94
|
+
stop() {
|
|
95
|
+
if (this.debounceTimer) {
|
|
96
|
+
clearTimeout(this.debounceTimer);
|
|
97
|
+
this.debounceTimer = null;
|
|
98
|
+
}
|
|
99
|
+
if (this.unsubscribe) {
|
|
100
|
+
try {
|
|
101
|
+
this.unsubscribe();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// ignore teardown errors
|
|
105
|
+
}
|
|
106
|
+
this.unsubscribe = null;
|
|
107
|
+
}
|
|
108
|
+
this.started = false;
|
|
109
|
+
this.lastScreen = null;
|
|
110
|
+
}
|
|
111
|
+
scheduleCapture() {
|
|
112
|
+
if (this.debounceTimer) {
|
|
113
|
+
clearTimeout(this.debounceTimer);
|
|
114
|
+
}
|
|
115
|
+
this.debounceTimer = setTimeout(() => {
|
|
116
|
+
this.debounceTimer = null;
|
|
117
|
+
this.captureCurrentScreen();
|
|
118
|
+
}, this.debounceMs);
|
|
119
|
+
}
|
|
120
|
+
captureCurrentScreen() {
|
|
121
|
+
let route;
|
|
122
|
+
try {
|
|
123
|
+
route = this.navigationRef.getCurrentRoute();
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
logger.warn('getCurrentRoute failed:', e);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!route || !route.name)
|
|
130
|
+
return;
|
|
131
|
+
// Dedupe: ignore state changes that don't move to a different screen.
|
|
132
|
+
if (route.name === this.lastScreen)
|
|
133
|
+
return;
|
|
134
|
+
const previousScreen = this.lastScreen;
|
|
135
|
+
this.lastScreen = route.name;
|
|
136
|
+
const properties = { screen: route.name };
|
|
137
|
+
if (previousScreen) {
|
|
138
|
+
properties.previousScreen = previousScreen;
|
|
139
|
+
}
|
|
140
|
+
if (this.captureParams && this.captureParams.length > 0) {
|
|
141
|
+
const params = sanitizeScreenParams(route.params, this.captureParams);
|
|
142
|
+
if (params) {
|
|
143
|
+
properties.params = params;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
this.emit(SCREEN_VIEW_EVENT, properties);
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
logger.warn('Failed to emit screen_view:', e);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface NetworkManagerProtocol {
|
|
2
|
+
request<T>(endpoint: string, options?: RequestInit): Promise<T>;
|
|
3
|
+
}
|
|
4
|
+
export declare class NetworkManager implements NetworkManagerProtocol {
|
|
5
|
+
private readonly baseUrl;
|
|
6
|
+
private readonly apiKey;
|
|
7
|
+
private readonly maxRetries;
|
|
8
|
+
constructor(baseUrl: string, apiKey?: string);
|
|
9
|
+
request<T>(endpoint: string, options?: RequestInit): Promise<T>;
|
|
10
|
+
private performRequest;
|
|
11
|
+
private delay;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=network-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-manager.d.ts","sourceRoot":"","sources":["../../src/network/network-manager.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACjE;AAED,qBAAa,cAAe,YAAW,sBAAsB;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAK;gBAEpB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;IAKtC,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;YA+C3D,cAAc;IA0C5B,OAAO,CAAC,KAAK;CAGd"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { LinkFortyError } from '../errors/linkforty-error';
|
|
2
|
+
import { logger } from '../logger';
|
|
3
|
+
import { SDK_NAME, SDK_VERSION } from '../version';
|
|
4
|
+
export class NetworkManager {
|
|
5
|
+
baseUrl;
|
|
6
|
+
apiKey;
|
|
7
|
+
maxRetries = 3;
|
|
8
|
+
constructor(baseUrl, apiKey) {
|
|
9
|
+
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
|
10
|
+
this.apiKey = apiKey;
|
|
11
|
+
}
|
|
12
|
+
async request(endpoint, options = {}) {
|
|
13
|
+
let lastError;
|
|
14
|
+
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
|
15
|
+
try {
|
|
16
|
+
return await this.performRequest(endpoint, options);
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
lastError = e;
|
|
20
|
+
// Don't retry on client errors (4xx) or non-retryable errors
|
|
21
|
+
if (e instanceof LinkFortyError) {
|
|
22
|
+
if (e.code === 'INVALID_RESPONSE' &&
|
|
23
|
+
e.message.match(/status: (\d+)/)) {
|
|
24
|
+
const statusMatch = e.message.match(/status: (\d+)/);
|
|
25
|
+
if (statusMatch) {
|
|
26
|
+
const status = parseInt(statusMatch[1], 10);
|
|
27
|
+
if (status >= 400 && status < 500)
|
|
28
|
+
throw e;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (e.code === 'INVALID_CONFIGURATION' ||
|
|
32
|
+
e.code === 'DECODING_ERROR') {
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Exponential backoff: 1s, 2s, 4s
|
|
37
|
+
if (attempt < this.maxRetries) {
|
|
38
|
+
const delayMs = Math.pow(2, attempt - 1) * 1000;
|
|
39
|
+
logger.log(`Request failed (attempt ${attempt}/${this.maxRetries}), retrying in ${delayMs / 1000}s...`);
|
|
40
|
+
await this.delay(delayMs);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
throw lastError instanceof LinkFortyError
|
|
45
|
+
? lastError
|
|
46
|
+
: LinkFortyError.networkError(lastError ?? new Error(`Request failed after ${this.maxRetries} attempts`));
|
|
47
|
+
}
|
|
48
|
+
async performRequest(endpoint, options) {
|
|
49
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
50
|
+
const headers = {
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
// SDK identity header (SIT-235) — name/version on every SDK request.
|
|
53
|
+
'X-LinkForty-SDK': `${SDK_NAME}/${SDK_VERSION}`,
|
|
54
|
+
...options.headers,
|
|
55
|
+
};
|
|
56
|
+
if (this.apiKey) {
|
|
57
|
+
headers['Authorization'] = `Bearer ${this.apiKey}`;
|
|
58
|
+
}
|
|
59
|
+
let response;
|
|
60
|
+
try {
|
|
61
|
+
response = await fetch(url, {
|
|
62
|
+
...options,
|
|
63
|
+
headers,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
throw LinkFortyError.networkError(e);
|
|
68
|
+
}
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
let message;
|
|
71
|
+
try {
|
|
72
|
+
const body = await response.json();
|
|
73
|
+
message = body.message || body.error;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// ignore parse failure
|
|
77
|
+
}
|
|
78
|
+
throw LinkFortyError.invalidResponse(response.status, message);
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
return (await response.json());
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
throw LinkFortyError.decodingError(e);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
delay(ms) {
|
|
88
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const STORAGE_KEYS: {
|
|
2
|
+
readonly INSTALL_ID: "@linkforty:install_id";
|
|
3
|
+
readonly INSTALL_DATA: "@linkforty:install_data";
|
|
4
|
+
readonly FIRST_LAUNCH: "@linkforty:first_launch";
|
|
5
|
+
readonly EVENT_QUEUE: "@linkforty:event_queue";
|
|
6
|
+
readonly ATTRIBUTION: "@linkforty:attribution";
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=storage-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-keys.d.ts","sourceRoot":"","sources":["../../src/storage/storage-keys.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY;;;;;;CAMf,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DeepLinkData } from '../models/deep-link-data';
|
|
2
|
+
export interface StorageManagerProtocol {
|
|
3
|
+
saveInstallId(installId: string): Promise<void>;
|
|
4
|
+
getInstallId(): Promise<string | null>;
|
|
5
|
+
saveInstallData(data: DeepLinkData): Promise<void>;
|
|
6
|
+
getInstallData(): Promise<DeepLinkData | null>;
|
|
7
|
+
isFirstLaunch(): Promise<boolean>;
|
|
8
|
+
setHasLaunched(): Promise<void>;
|
|
9
|
+
clearAll(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare class StorageManager implements StorageManagerProtocol {
|
|
12
|
+
saveInstallId(installId: string): Promise<void>;
|
|
13
|
+
getInstallId(): Promise<string | null>;
|
|
14
|
+
saveInstallData(data: DeepLinkData): Promise<void>;
|
|
15
|
+
getInstallData(): Promise<DeepLinkData | null>;
|
|
16
|
+
isFirstLaunch(): Promise<boolean>;
|
|
17
|
+
setHasLaunched(): Promise<void>;
|
|
18
|
+
clearAll(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=storage-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-manager.d.ts","sourceRoot":"","sources":["../../src/storage/storage-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D,MAAM,WAAW,sBAAsB;IACrC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,cAAc,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC/C,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAClC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,qBAAa,cAAe,YAAW,sBAAsB;IACrD,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAItC,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAQlD,cAAc,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAW9C,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IAKjC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAQhC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
|
+
import { logger } from '../logger';
|
|
3
|
+
import { STORAGE_KEYS } from './storage-keys';
|
|
4
|
+
export class StorageManager {
|
|
5
|
+
async saveInstallId(installId) {
|
|
6
|
+
await AsyncStorage.setItem(STORAGE_KEYS.INSTALL_ID, installId);
|
|
7
|
+
}
|
|
8
|
+
async getInstallId() {
|
|
9
|
+
return AsyncStorage.getItem(STORAGE_KEYS.INSTALL_ID);
|
|
10
|
+
}
|
|
11
|
+
async saveInstallData(data) {
|
|
12
|
+
try {
|
|
13
|
+
await AsyncStorage.setItem(STORAGE_KEYS.INSTALL_DATA, JSON.stringify(data));
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
logger.error('Failed to encode install data:', e);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async getInstallData() {
|
|
20
|
+
try {
|
|
21
|
+
const json = await AsyncStorage.getItem(STORAGE_KEYS.INSTALL_DATA);
|
|
22
|
+
if (!json)
|
|
23
|
+
return null;
|
|
24
|
+
return JSON.parse(json);
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
logger.error('Failed to decode install data:', e);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async isFirstLaunch() {
|
|
32
|
+
const flag = await AsyncStorage.getItem(STORAGE_KEYS.FIRST_LAUNCH);
|
|
33
|
+
return flag === null;
|
|
34
|
+
}
|
|
35
|
+
async setHasLaunched() {
|
|
36
|
+
await AsyncStorage.setItem(STORAGE_KEYS.FIRST_LAUNCH, 'true');
|
|
37
|
+
}
|
|
38
|
+
async clearAll() {
|
|
39
|
+
await AsyncStorage.multiRemove([
|
|
40
|
+
STORAGE_KEYS.INSTALL_ID,
|
|
41
|
+
STORAGE_KEYS.INSTALL_DATA,
|
|
42
|
+
STORAGE_KEYS.FIRST_LAUNCH,
|
|
43
|
+
STORAGE_KEYS.EVENT_QUEUE,
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,QAAQ,SAAS,CAAC;AAC/B,eAAO,MAAM,WAAW,UAAU,CAAC"}
|
package/dist/version.js
ADDED