@tolinku/react-native-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sitepact LLC (Tolinku)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,233 @@
1
+ # @tolinku/react-native-sdk
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@tolinku/react-native-sdk.svg)](https://www.npmjs.com/package/@tolinku/react-native-sdk)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+
6
+ The official [Tolinku](https://tolinku.com) SDK for **React Native**. Add deep linking, analytics, referral tracking, deferred deep links, and in-app messages to your React Native app. Works with both React Native CLI and Expo projects.
7
+
8
+ ## What is Tolinku?
9
+
10
+ [Tolinku](https://tolinku.com) is a deep linking platform for mobile and web apps. It handles Universal Links (iOS), App Links (Android), deferred deep linking, referral programs, analytics, and smart banners. Tolinku provides a complete toolkit for user acquisition, attribution, and engagement across platforms.
11
+
12
+ Get your API key at [tolinku.com](https://tolinku.com) and check out the [documentation](https://tolinku.com/docs) to get started.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @tolinku/react-native-sdk @react-native-async-storage/async-storage
18
+ ```
19
+
20
+ Or with yarn:
21
+
22
+ ```bash
23
+ yarn add @tolinku/react-native-sdk @react-native-async-storage/async-storage
24
+ ```
25
+
26
+ `@react-native-async-storage/async-storage` is a required peer dependency used for message impression tracking and dismissal state.
27
+
28
+ ### Expo
29
+
30
+ This SDK is compatible with [Expo](https://expo.dev). No custom native modules are required.
31
+
32
+ ```bash
33
+ npx expo install @tolinku/react-native-sdk @react-native-async-storage/async-storage
34
+ ```
35
+
36
+ Most features (analytics, referrals, in-app messages) work in Expo Go. However, deep linking (Universal Links and App Links) requires a [development build](https://docs.expo.dev/develop/development-builds/introduction/) or production build, since Expo Go cannot register custom domain associations. Use `npx expo run:ios` or `eas build --profile development` to create a dev build for testing deep links.
37
+
38
+ ### React Native CLI
39
+
40
+ For bare React Native projects, install the packages above and run pod install for iOS:
41
+
42
+ ```bash
43
+ cd ios && pod install
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ ```typescript
49
+ import { Tolinku } from '@tolinku/react-native-sdk';
50
+
51
+ // Initialize the SDK (typically at app startup)
52
+ Tolinku.init({ apiKey: 'tolk_pub_your_api_key' });
53
+
54
+ // Identify a user
55
+ Tolinku.setUserId('user_123');
56
+
57
+ // Track a custom event
58
+ await Tolinku.track('purchase', { plan: 'growth' });
59
+ ```
60
+
61
+ ## Features
62
+
63
+ ### Analytics
64
+
65
+ Track custom events with automatic batching. Events are queued and sent in batches of 10, or every 5 seconds.
66
+
67
+ ```typescript
68
+ await Tolinku.track('signup_completed', { source: 'landing_page' });
69
+
70
+ // Flush queued events immediately
71
+ await Tolinku.flush();
72
+ ```
73
+
74
+ ### Referrals
75
+
76
+ Create and manage referral programs with leaderboards and reward tracking.
77
+
78
+ ```typescript
79
+ // Create a referral
80
+ const { referral_code, referral_url } = await Tolinku.referrals.create({
81
+ userId: 'user_123',
82
+ userName: 'Alice',
83
+ });
84
+
85
+ // Look up a referral
86
+ const info = await Tolinku.referrals.get(referral_code);
87
+
88
+ // Complete a referral
89
+ await Tolinku.referrals.complete({
90
+ code: referral_code,
91
+ referredUserId: 'user_456',
92
+ referredUserName: 'Bob',
93
+ });
94
+
95
+ // Update milestone
96
+ await Tolinku.referrals.milestone({
97
+ code: referral_code,
98
+ milestone: 'first_purchase',
99
+ });
100
+
101
+ // Claim reward
102
+ await Tolinku.referrals.claimReward(referral_code);
103
+
104
+ // Fetch leaderboard
105
+ const { leaderboard } = await Tolinku.referrals.leaderboard(10);
106
+ ```
107
+
108
+ ### Deferred Deep Links
109
+
110
+ Recover deep link context for users who installed your app after clicking a link. Deferred deep linking lets you route users to specific content even when the app was not installed at the time of the click.
111
+
112
+ ```typescript
113
+ // Claim by referrer token (from Play Store referrer or clipboard)
114
+ const link = await Tolinku.deferred.claimByToken('abc123');
115
+ if (link) {
116
+ console.log(link.deep_link_path); // e.g. "/merchant/xyz"
117
+ }
118
+
119
+ // Claim by device signal matching (auto-detects timezone, language, screen size)
120
+ const link = await Tolinku.deferred.claimBySignals({
121
+ appspaceId: 'your_appspace_id',
122
+ });
123
+ ```
124
+
125
+ ### In-App Messages (React Native Component)
126
+
127
+ Display server-configured in-app messages using the `<TolinkuMessages>` component. Drop it anywhere in your React Native component tree to automatically fetch and render messages as a modal overlay. Messages are created and managed from the Tolinku dashboard without shipping app updates.
128
+
129
+ ```tsx
130
+ import { TolinkuMessages } from '@tolinku/react-native-sdk';
131
+
132
+ function App() {
133
+ return (
134
+ <>
135
+ <MainContent />
136
+ <TolinkuMessages
137
+ trigger="milestone"
138
+ triggerValue="first_purchase"
139
+ onButtonPress={(action, messageId) => {
140
+ console.log(`Action: ${action}, Message: ${messageId}`);
141
+ }}
142
+ onDismiss={(messageId) => {
143
+ console.log(`Dismissed: ${messageId}`);
144
+ }}
145
+ />
146
+ </>
147
+ );
148
+ }
149
+ ```
150
+
151
+ The component automatically filters dismissed and suppressed messages, and shows the highest-priority match. It respects `dismiss_days`, `max_impressions`, and `min_interval_hours` settings from the server.
152
+
153
+ **Props:**
154
+
155
+ | Prop | Type | Description |
156
+ |------|------|-------------|
157
+ | `trigger` | `string?` | Filter messages by trigger type |
158
+ | `triggerValue` | `string?` | Match a specific trigger value |
159
+ | `onButtonPress` | `(action, messageId) => void` | Called when a message button is pressed |
160
+ | `onDismiss` | `(messageId) => void` | Called when a message is dismissed |
161
+
162
+ ## Configuration Options
163
+
164
+ ```typescript
165
+ Tolinku.init({
166
+ apiKey: 'tolk_pub_your_api_key', // Required. Your Tolinku publishable API key.
167
+ baseUrl: 'https://api.tolinku.com', // Optional. API base URL.
168
+ debug: false, // Optional. Enable debug logging.
169
+ timeout: 30000, // Optional. Request timeout in ms.
170
+ });
171
+
172
+ // Set user identity at any time
173
+ Tolinku.setUserId('user_123');
174
+
175
+ // Shut down the SDK
176
+ await Tolinku.destroy();
177
+ ```
178
+
179
+ ## API Reference
180
+
181
+ ### `Tolinku`
182
+
183
+ | Method | Description |
184
+ |--------|-------------|
185
+ | `init(config)` | Initialize the SDK (static) |
186
+ | `isConfigured()` | Check if the SDK is initialized (static) |
187
+ | `setUserId(userId)` | Set or clear the current user ID (static) |
188
+ | `getUserId()` | Get the current user ID (static) |
189
+ | `track(eventType, properties?)` | Track a custom event (static) |
190
+ | `flush()` | Flush queued analytics events (static) |
191
+ | `destroy()` | Shut down the SDK and release resources (static) |
192
+
193
+ ### `Tolinku.referrals`
194
+
195
+ | Method | Description |
196
+ |--------|-------------|
197
+ | `create(options)` | Create a new referral |
198
+ | `get(code)` | Get referral details by code |
199
+ | `complete(options)` | Mark a referral as converted |
200
+ | `milestone(options)` | Update a referral milestone |
201
+ | `claimReward(code)` | Claim a referral reward |
202
+ | `leaderboard(limit?)` | Fetch the referral leaderboard |
203
+
204
+ ### `Tolinku.deferred`
205
+
206
+ | Method | Description |
207
+ |--------|-------------|
208
+ | `claimByToken(token)` | Claim a deferred link by token |
209
+ | `claimBySignals(options)` | Claim a deferred link by device signals |
210
+
211
+ ### `<TolinkuMessages>`
212
+
213
+ | Prop | Description |
214
+ |------|-------------|
215
+ | `trigger` | Filter messages by trigger type |
216
+ | `triggerValue` | Match a specific trigger value |
217
+ | `onButtonPress` | Callback for button presses |
218
+ | `onDismiss` | Callback for message dismissal |
219
+
220
+ ## Documentation
221
+
222
+ Full documentation is available at [tolinku.com/docs](https://tolinku.com/docs).
223
+
224
+ ## Community
225
+
226
+ - [GitHub](https://github.com/tolinku)
227
+ - [X (Twitter)](https://x.com/trytolinku)
228
+ - [Facebook](https://facebook.com/trytolinku)
229
+ - [Instagram](https://www.instagram.com/trytolinku/)
230
+
231
+ ## License
232
+
233
+ MIT
@@ -0,0 +1,306 @@
1
+ import React from 'react';
2
+
3
+ /** Configuration options for the Tolinku SDK */
4
+ interface TolinkuConfig {
5
+ /** Your Tolinku publishable API key (starts with tolk_pub_) */
6
+ apiKey: string;
7
+ /** Base URL of your Tolinku domain. Defaults to https://api.tolinku.com */
8
+ baseUrl?: string;
9
+ /** Enable debug logging to the console. Defaults to false. */
10
+ debug?: boolean;
11
+ /** Request timeout in milliseconds. Defaults to 30000 (30 seconds). */
12
+ timeout?: number;
13
+ }
14
+ /** Resolved configuration with all defaults applied */
15
+ interface ResolvedTolinkuConfig {
16
+ apiKey: string;
17
+ baseUrl: string;
18
+ debug: boolean;
19
+ timeout: number;
20
+ }
21
+ /** Properties for custom event tracking */
22
+ interface TrackProperties {
23
+ campaign?: string;
24
+ source?: string;
25
+ medium?: string;
26
+ platform?: string;
27
+ [key: string]: string | undefined;
28
+ }
29
+ /** Options for creating a referral */
30
+ interface CreateReferralOptions {
31
+ userId: string;
32
+ metadata?: Record<string, string>;
33
+ userName?: string;
34
+ }
35
+ /** Response from creating a referral */
36
+ interface CreateReferralResult {
37
+ referral_code: string;
38
+ referral_url: string | null;
39
+ referral_id: string;
40
+ }
41
+ /** Options for completing a referral */
42
+ interface CompleteReferralOptions {
43
+ code: string;
44
+ referredUserId: string;
45
+ milestone?: string;
46
+ referredUserName?: string;
47
+ }
48
+ /** Response from completing a referral */
49
+ interface CompleteReferralResult {
50
+ referral: {
51
+ id: string;
52
+ referrer_id: string;
53
+ referred_user_id: string;
54
+ status: string;
55
+ milestone: string;
56
+ completed_at: string;
57
+ reward_type: string | null;
58
+ reward_value: string | null;
59
+ };
60
+ }
61
+ /** Options for updating a referral milestone */
62
+ interface MilestoneOptions {
63
+ code: string;
64
+ milestone: string;
65
+ }
66
+ /** Response from updating a milestone */
67
+ interface MilestoneResult {
68
+ referral: {
69
+ id: string;
70
+ referral_code: string;
71
+ milestone: string;
72
+ status: string;
73
+ reward_type: string | null;
74
+ reward_value: string | null;
75
+ };
76
+ }
77
+ /** Referral info returned by GET /api/referral/:code */
78
+ interface ReferralInfo {
79
+ referrer_id: string;
80
+ status: string;
81
+ milestone: string;
82
+ milestone_history: Array<{
83
+ milestone: string;
84
+ timestamp: string;
85
+ }>;
86
+ reward_type: string | null;
87
+ reward_value: string | null;
88
+ reward_claimed: boolean;
89
+ created_at: string;
90
+ }
91
+ /** Leaderboard entry */
92
+ interface LeaderboardEntry {
93
+ referrer_id: string;
94
+ referrer_name: string | null;
95
+ total: number;
96
+ completed: number;
97
+ pending: number;
98
+ total_reward_value: string | null;
99
+ }
100
+ /** Deferred deep link result */
101
+ interface DeferredLink {
102
+ deep_link_path: string;
103
+ appspace_id: string;
104
+ referrer_id?: string;
105
+ referral_code?: string;
106
+ }
107
+ /** Options for claiming deferred link by signals */
108
+ interface ClaimBySignalsOptions {
109
+ appspaceId: string;
110
+ timezone?: string;
111
+ language?: string;
112
+ screenWidth?: number;
113
+ screenHeight?: number;
114
+ }
115
+ /** In-app message from the API */
116
+ interface Message {
117
+ id: string;
118
+ name: string;
119
+ title: string;
120
+ body: string | null;
121
+ trigger: string;
122
+ trigger_value: string | null;
123
+ content: MessageContent | null;
124
+ background_color: string;
125
+ priority: number;
126
+ dismiss_days: number | null;
127
+ max_impressions: number | null;
128
+ min_interval_hours: number | null;
129
+ }
130
+ /** Puck component content tree */
131
+ interface MessageContent {
132
+ root: {
133
+ props: Record<string, unknown>;
134
+ };
135
+ content: MessageComponent[];
136
+ }
137
+ /** A single Puck component */
138
+ interface MessageComponent {
139
+ type: string;
140
+ props: Record<string, unknown>;
141
+ }
142
+ /** Options for showing an in-app message */
143
+ interface ShowMessageOptions {
144
+ trigger?: string;
145
+ triggerValue?: string;
146
+ onDismiss?: (messageId: string) => void;
147
+ onButtonPress?: (action: string, messageId: string) => void;
148
+ }
149
+
150
+ declare class HttpClient {
151
+ private baseUrl;
152
+ private apiKey;
153
+ private timeout;
154
+ /** Set of AbortControllers for in-flight requests. Used by destroy() to cancel all. */
155
+ private pendingControllers;
156
+ private destroyed;
157
+ constructor(config: ResolvedTolinkuConfig);
158
+ get<T>(path: string, params?: Record<string, string>): Promise<T>;
159
+ post<T>(path: string, body?: Record<string, unknown>): Promise<T>;
160
+ /** GET without API key auth (for public endpoints like deferred claim) */
161
+ getPublic<T>(path: string, params?: Record<string, string>): Promise<T>;
162
+ /** POST without API key auth (for public endpoints like deferred claim) */
163
+ postPublic<T>(path: string, body?: Record<string, unknown>): Promise<T>;
164
+ /**
165
+ * Abort all in-flight requests and mark the client as destroyed.
166
+ * After calling this, all future requests will throw immediately.
167
+ */
168
+ abort(): void;
169
+ private authenticatedHeaders;
170
+ private publicHeaders;
171
+ /**
172
+ * Execute a fetch request with retry logic. Retries on:
173
+ * - Network errors (fetch throws)
174
+ * - HTTP 429 (Too Many Requests), respecting the Retry-After header
175
+ * - HTTP 5xx (server errors)
176
+ *
177
+ * Does NOT retry on 4xx errors (except 429).
178
+ * Uses exponential backoff: BASE_DELAY_MS * 2^attempt + random jitter (0..MAX_JITTER_MS).
179
+ */
180
+ private executeWithRetry;
181
+ }
182
+ declare class TolinkuError extends Error {
183
+ status: number;
184
+ code: string | undefined;
185
+ constructor(message: string, status: number, code?: string);
186
+ }
187
+
188
+ declare class Referrals {
189
+ private client;
190
+ constructor(client: HttpClient);
191
+ /** Create a new referral for a user */
192
+ create(options: CreateReferralOptions): Promise<CreateReferralResult>;
193
+ /** Get referral info by code */
194
+ get(code: string): Promise<ReferralInfo>;
195
+ /** Complete a referral (mark as converted) */
196
+ complete(options: CompleteReferralOptions): Promise<CompleteReferralResult>;
197
+ /** Update a referral milestone */
198
+ milestone(options: MilestoneOptions): Promise<MilestoneResult>;
199
+ /** Claim a referral reward */
200
+ claimReward(code: string): Promise<{
201
+ success: boolean;
202
+ referral_code: string;
203
+ reward_claimed: boolean;
204
+ }>;
205
+ /** Get the referral leaderboard */
206
+ leaderboard(limit?: number): Promise<{
207
+ leaderboard: LeaderboardEntry[];
208
+ }>;
209
+ }
210
+
211
+ declare class Deferred {
212
+ private client;
213
+ constructor(client: HttpClient);
214
+ /** Claim a deferred deep link by referrer token (from Play Store referrer or clipboard) */
215
+ claimByToken(token: string): Promise<DeferredLink | null>;
216
+ /** Claim a deferred deep link by device signal matching */
217
+ claimBySignals(options: ClaimBySignalsOptions): Promise<DeferredLink | null>;
218
+ }
219
+
220
+ /**
221
+ * Main Tolinku SDK singleton.
222
+ *
223
+ * Initialize once with Tolinku.init(), then use static methods for all operations.
224
+ *
225
+ * Example:
226
+ * Tolinku.init({ apiKey: 'tolk_pub_...' });
227
+ * await Tolinku.track('signup', { source: 'onboarding' });
228
+ */
229
+ declare class Tolinku {
230
+ static readonly VERSION = "0.1.0";
231
+ private static client;
232
+ private static analyticsInstance;
233
+ private static referralsInstance;
234
+ private static deferredInstance;
235
+ private static _initialized;
236
+ private static _userId;
237
+ /**
238
+ * Initialize the SDK. Must be called before any other method.
239
+ *
240
+ * If init() is called a second time without calling destroy() first,
241
+ * a warning is logged and the existing instance is returned.
242
+ */
243
+ static init(config: TolinkuConfig): void;
244
+ /** Check whether the SDK has been initialized. */
245
+ static isConfigured(): boolean;
246
+ /**
247
+ * Set the user ID for segment targeting and analytics attribution.
248
+ * Pass null to clear the user ID.
249
+ */
250
+ static setUserId(userId: string | null): void;
251
+ /** Get the current user ID, or null if not set. */
252
+ static getUserId(): string | null;
253
+ /** Get the underlying HTTP client (used internally by MessageProvider) */
254
+ static getClient(): HttpClient;
255
+ /**
256
+ * Track a custom event (shorthand for analytics.track).
257
+ * Event type is auto-prefixed with "custom." if not already.
258
+ * Events are batched and flushed automatically.
259
+ */
260
+ static track(eventType: string, properties?: TrackProperties): Promise<void>;
261
+ /**
262
+ * Immediately flush all queued analytics events to the server.
263
+ */
264
+ static flush(): Promise<void>;
265
+ /** Referrals: create, complete, milestone, leaderboard, claimReward */
266
+ static get referrals(): Referrals;
267
+ /** Deferred deep links: claimByToken, claimBySignals */
268
+ static get deferred(): Deferred;
269
+ /**
270
+ * Shut down the SDK and release resources.
271
+ * Flushes remaining analytics events, cancels timers, removes listeners,
272
+ * and aborts in-flight requests. After calling this, you must call init()
273
+ * again before using the SDK.
274
+ */
275
+ static destroy(): Promise<void>;
276
+ }
277
+
278
+ interface TolinkuMessagesProps {
279
+ /** The trigger type to filter messages by (e.g. "milestone", "event") */
280
+ trigger?: string;
281
+ /** The trigger value to match (e.g. "installed", "first_purchase") */
282
+ triggerValue?: string;
283
+ /** Called when a message is dismissed */
284
+ onDismiss?: (messageId: string) => void;
285
+ /** Called when a button in the message is pressed */
286
+ onButtonPress?: (action: string, messageId: string) => void;
287
+ }
288
+ /**
289
+ * React component that fetches and displays in-app messages as a modal overlay.
290
+ *
291
+ * Place this component anywhere in your component tree. It will automatically
292
+ * fetch messages matching the given trigger and display the highest-priority
293
+ * non-dismissed message.
294
+ *
295
+ * Example:
296
+ * <TolinkuMessages trigger="milestone" triggerValue="installed" />
297
+ */
298
+ declare function TolinkuMessages({ trigger, triggerValue, onDismiss, onButtonPress, }: TolinkuMessagesProps): React.ReactElement;
299
+
300
+ /**
301
+ * Check if a URL is safe to open or render. Only allows http: and https: protocols.
302
+ * Blocks javascript:, data:, file:, and other potentially dangerous protocols.
303
+ */
304
+ declare function isSafeUrl(url: string): boolean;
305
+
306
+ export { type ClaimBySignalsOptions, type CompleteReferralOptions, type CompleteReferralResult, type CreateReferralOptions, type CreateReferralResult, type DeferredLink, type LeaderboardEntry, type Message, type MessageComponent, type MessageContent, type MilestoneOptions, type MilestoneResult, type ReferralInfo, type ResolvedTolinkuConfig, type ShowMessageOptions, Tolinku, type TolinkuConfig, TolinkuError, TolinkuMessages, type TrackProperties, isSafeUrl };