rn-linkrunner 1.1.1 → 2.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.
@@ -0,0 +1,17 @@
1
+ const path = require('path');
2
+
3
+ module.exports = {
4
+ dependencies: {
5
+ 'rn-linkrunner': {
6
+ platforms: {
7
+ android: {
8
+ sourceDir: 'android',
9
+ packageImportPath: 'io.linkrunner.LinkrunnerPackage',
10
+ },
11
+ ios: {
12
+ podspecPath: path.join(__dirname, 'LinkrunnerSDK.podspec'),
13
+ },
14
+ },
15
+ },
16
+ },
17
+ };
package/src/index.ts CHANGED
@@ -1,64 +1,22 @@
1
- import { Linking } from 'react-native';
2
- import DeviceInfo from 'react-native-device-info';
3
- import {
4
- device_data,
5
- getDeeplinkURL,
6
- getLinkRunnerInstallInstanceId,
7
- setDeeplinkURL,
8
- } from './helper';
9
- import type { CampaignData, LRIPLocationData, UserData } from './types';
1
+ import { NativeModules, Platform } from 'react-native';
2
+ import type { AttributionData, IntegrationData, UserData } from './types';
10
3
  import packageJson from '../package.json';
11
- import { Platform } from 'react-native';
12
- import { PlayInstallReferrer } from 'react-native-play-install-referrer';
13
-
14
- const package_version = packageJson.version;
15
- const app_version: string = DeviceInfo.getVersion();
16
-
17
- const baseUrl = 'https://api.linkrunner.io';
18
-
19
- const initApiCall = async (
20
- token: string,
21
- source: 'GENERAL' | 'ADS',
22
- link?: string
23
- ) => {
24
- try {
25
- const fetch_result = await fetch(baseUrl + '/api/client/init', {
26
- method: 'POST',
27
- headers: {
28
- 'Accept': 'application/json',
29
- 'Content-Type': 'application/json',
30
- },
31
- body: JSON.stringify({
32
- token,
33
- package_version,
34
- app_version,
35
- device_data: await device_data(),
36
- platform: 'REACT_NATIVE',
37
- source,
38
- link,
39
- install_instance_id: await getLinkRunnerInstallInstanceId(),
40
- }),
41
- });
42
-
43
- const result = await fetch_result.json();
44
-
45
- if (result?.status !== 200 && result?.status !== 201) {
46
- throw new Error(result?.msg);
47
- }
4
+ import DeviceInfo from 'react-native-device-info';
48
5
 
49
- if (__DEV__) {
50
- console.log('Linkrunner initialised successfully 🔥');
6
+ const LINKING_ERROR =
7
+ `The package 'rn-linkrunner' doesn't seem to be linked. Make sure: \n\n` +
8
+ Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
9
+ '- You rebuilt the app after installing the package\n' +
10
+ '- You are not using Expo Go\n';
51
11
 
52
- console.log('init response > ', result);
53
- }
12
+ const LinkrunnerSDKModule = NativeModules.LinkrunnerSDK;
54
13
 
55
- if (!!result?.data?.deeplink) setDeeplinkURL(result?.data?.deeplink);
14
+ if (!LinkrunnerSDKModule) {
15
+ throw new Error(LINKING_ERROR);
16
+ }
56
17
 
57
- return result?.data;
58
- } catch (error) {
59
- console.error('Error initializing linkrunner', error);
60
- }
61
- };
18
+ const package_version = packageJson.version;
19
+ const app_version: string = DeviceInfo.getVersion();
62
20
 
63
21
  class Linkrunner {
64
22
  private token: string | null;
@@ -67,7 +25,15 @@ class Linkrunner {
67
25
  this.token = null;
68
26
  }
69
27
 
70
- async init(token: string): Promise<void | LRInitResponse> {
28
+ getAppVersion() {
29
+ return app_version;
30
+ }
31
+
32
+ getPackageVersion() {
33
+ return package_version;
34
+ }
35
+
36
+ async init(token: string, secretKey?: string, keyId?: string) {
71
37
  if (!token) {
72
38
  console.error('Linkrunner needs your project token to initialize!');
73
39
  return;
@@ -75,7 +41,27 @@ class Linkrunner {
75
41
 
76
42
  this.token = token;
77
43
 
78
- return await initApiCall(token, 'GENERAL');
44
+ try {
45
+
46
+ let result;
47
+ if (Platform.OS === 'android') {
48
+ result = await LinkrunnerSDKModule.init(token, {secretKey, keyId});
49
+ } else {
50
+ // iOS init maintains backwards compatibility
51
+ result = await LinkrunnerSDKModule.initializeSDK({token: token, secretKey, keyId});
52
+ }
53
+
54
+ if (__DEV__) {
55
+ console.log("Init successful");
56
+ console.log('Linkrunner initialised successfully');
57
+ console.log('init response > ', result);
58
+ }
59
+
60
+ return;
61
+ } catch (error) {
62
+ console.error('Error initializing linkrunner via native module', error);
63
+ throw error;
64
+ }
79
65
  }
80
66
 
81
67
  async signup({
@@ -84,85 +70,32 @@ class Linkrunner {
84
70
  }: {
85
71
  data?: { [key: string]: any };
86
72
  user_data: UserData;
87
- }): Promise<void | LRTriggerResponse> {
73
+ }) {
88
74
  if (!this.token) {
89
75
  console.error('Linkrunner: Signup failed, token not initialized');
90
76
  return;
91
77
  }
92
78
 
93
- try {
94
- const response = await fetch(baseUrl + '/api/client/trigger', {
95
- method: 'POST',
96
- headers: {
97
- 'Accept': 'application/json',
98
- 'Content-Type': 'application/json',
99
- },
100
- body: JSON.stringify({
101
- token: this.token,
102
- user_data,
103
- platform: 'REACT_NATIVE',
104
- data: {
105
- ...data,
106
- device_data: await device_data(),
107
- },
108
- install_instance_id: await getLinkRunnerInstallInstanceId(),
109
- }),
110
- });
111
- const result = await response.json();
112
-
113
- if (result?.status !== 200 && result?.status !== 201) {
114
- console.error('Linkrunner: Signup failed');
115
- console.error('Linkrunner: ', result?.msg);
116
- return;
117
- }
79
+ // Validate user_data has required id field
80
+ if (!user_data || !user_data.id) {
81
+ console.error('Linkrunner: User data with id is required');
82
+ return;
83
+ }
118
84
 
85
+ try {
86
+ // Pass null as data if it's undefined to avoid potential issues
87
+ const result = await LinkrunnerSDKModule.signup(user_data, data || {});
88
+
119
89
  if (__DEV__) {
120
- console.log('Linkrunner: Signup called 🔥');
90
+ console.log('Linkrunner signup successful');
91
+ console.log('signup response > ', result);
121
92
  }
122
93
 
123
- return result.data;
124
- } catch (err: any) {
125
- console.error('Linkrunner: Signup failed');
126
- console.error('Linkrunner: ', err.message);
127
- }
128
- }
129
-
130
- async triggerDeeplink() {
131
- const deeplink_url = await getDeeplinkURL();
132
-
133
- if (!deeplink_url) {
134
- console.error('Linkrunner: Deeplink URL not found');
135
94
  return;
95
+ } catch (error) {
96
+ console.error('Error during signup via native module', error);
97
+ throw error;
136
98
  }
137
-
138
- Linking.openURL(deeplink_url).then(() => {
139
- fetch(baseUrl + '/api/client/deeplink-triggered', {
140
- method: 'POST',
141
- headers: {
142
- 'Accept': 'application/json',
143
- 'Content-Type': 'application/json',
144
- },
145
- body: JSON.stringify({
146
- token: this.token,
147
- }),
148
- })
149
- .then(() => {
150
- if (__DEV__) {
151
- console.log(
152
- 'Linkrunner: Deeplink triggered successfully',
153
- deeplink_url
154
- );
155
- }
156
- })
157
- .catch(() => {
158
- if (__DEV__) {
159
- console.error(
160
- 'Linkrunner: Deeplink triggering failed',
161
- deeplink_url
162
- );
163
- }
164
- });
165
- });
166
99
  }
167
100
 
168
101
  async setUserData(user_data: UserData) {
@@ -172,32 +105,17 @@ class Linkrunner {
172
105
  }
173
106
 
174
107
  try {
175
- const response = await fetch(baseUrl + '/api/client/set-user-data', {
176
- method: 'POST',
177
- headers: {
178
- 'Accept': 'application/json',
179
- 'Content-Type': 'application/json',
180
- },
181
- body: JSON.stringify({
182
- token: this.token,
183
- user_data,
184
- device_data: await device_data(),
185
- install_instance_id: await getLinkRunnerInstallInstanceId(),
186
- }),
187
- });
188
-
189
- const result = await response.json();
190
-
191
- if (result?.status !== 200 && result?.status !== 201) {
192
- console.error('Linkrunner: Set user data failed');
193
- console.error('Linkrunner: ', result?.msg);
194
- return;
108
+ const result = await LinkrunnerSDKModule.setUserData(user_data);
109
+
110
+ if (__DEV__) {
111
+ console.log('Linkrunner user data set successfully');
112
+ console.log('set user data response > ', result);
195
113
  }
196
114
 
197
- return result.data;
198
- } catch (err: any) {
199
- console.error('Linkrunner: Set user data failed');
200
- console.error('Linkrunner: ', err?.message);
115
+ return result;
116
+ } catch (error) {
117
+ console.error('Error setting user data via native module', error);
118
+ throw error;
201
119
  }
202
120
  }
203
121
 
@@ -227,54 +145,29 @@ class Linkrunner {
227
145
  | 'PAYMENT_CANCELLED';
228
146
  }) {
229
147
  if (!this.token) {
230
- console.error(
231
- 'Linkrunner: Capture payment failed, token not initialized'
232
- );
148
+ console.error('Linkrunner: Payment capture failed, token not initialized');
233
149
  return;
234
150
  }
235
151
 
236
152
  try {
237
- const response = await fetch(baseUrl + '/api/client/capture-payment', {
238
- method: 'POST',
239
- headers: {
240
- 'Accept': 'application/json',
241
- 'Content-Type': 'application/json',
242
- },
243
- body: JSON.stringify({
244
- token: this.token,
245
- user_id: userId,
246
- platform: 'REACT_NATIVE',
247
- data: {
248
- device_data: await device_data(),
249
- },
250
- amount,
251
- payment_id: paymentId,
252
- type,
253
- status,
254
- install_instance_id: await getLinkRunnerInstallInstanceId(),
255
- }),
256
- });
257
-
258
- const result = await response.json();
259
-
260
- if (result?.status !== 200 && result?.status !== 201) {
261
- console.error('Linkrunner: Capture payment failed');
262
- console.error('Linkrunner: ', result?.msg);
263
- return;
264
- }
265
-
153
+ const paymentData = {
154
+ paymentId: paymentId || '',
155
+ userId,
156
+ amount,
157
+ type: type || 'DEFAULT',
158
+ status: status || 'PAYMENT_COMPLETED',
159
+ };
160
+
161
+ const result = await LinkrunnerSDKModule.capturePayment(paymentData);
162
+
266
163
  if (__DEV__) {
267
- console.log('Linkrunner: Payment captured successfully 💸', {
268
- amount,
269
- paymentId,
270
- userId,
271
- type,
272
- status,
273
- });
164
+ console.log('Linkrunner payment captured successfully');
165
+ console.log('capture payment response > ', result);
274
166
  }
167
+
275
168
  } catch (error) {
276
- console.error('Linkrunner: Payment capturing failed!');
277
- return;
169
+ console.error('Error capturing payment via native module', error);
170
+ throw error;
278
171
  }
279
172
  }
280
173
 
@@ -286,55 +179,25 @@ class Linkrunner {
286
179
  userId: string;
287
180
  }) {
288
181
  if (!this.token) {
289
- console.error('Linkrunner: Remove payment failed, token not initialized');
182
+ console.error('Linkrunner: Payment removal failed, token not initialized');
290
183
  return;
291
184
  }
292
185
 
293
- if (!paymentId && !userId) {
294
- return console.error(
295
- 'Linkrunner: Either paymentId or userId must be provided!'
296
- );
297
- }
298
-
299
186
  try {
300
- const response = await fetch(
301
- baseUrl + '/api/client/remove-captured-payment',
302
- {
303
- method: 'POST',
304
- headers: {
305
- 'Accept': 'application/json',
306
- 'Content-Type': 'application/json',
307
- },
308
- body: JSON.stringify({
309
- token: this.token,
310
- user_id: userId,
311
- platform: 'REACT_NATIVE',
312
- data: {
313
- device_data: await device_data(),
314
- },
315
- payment_id: paymentId,
316
- install_instance_id: await getLinkRunnerInstallInstanceId(),
317
- }),
318
- }
319
- );
320
-
321
- const result = await response.json();
322
-
323
- if (result?.status !== 200 && result?.status !== 201) {
324
- console.error('Linkrunner: Capture payment failed');
325
- console.error('Linkrunner: ', result?.msg);
326
- return;
327
- }
187
+ const paymentData = {
188
+ paymentId: paymentId || '',
189
+ userId,
190
+ };
328
191
 
192
+ const result = await LinkrunnerSDKModule.removePayment(paymentData);
193
+
329
194
  if (__DEV__) {
330
- console.log('Linkrunner: Payment entry removed successfully!', {
331
- paymentId,
332
- userId,
333
- });
195
+ console.log('Linkrunner payment removed successfully');
196
+ console.log('remove payment response > ', result);
334
197
  }
335
198
  } catch (error) {
336
- console.error('Linkrunner: Payment capturing failed!');
337
- return;
199
+ console.error('Error removing payment via native module', error);
200
+ throw error;
338
201
  }
339
202
  }
340
203
 
@@ -349,135 +212,86 @@ class Linkrunner {
349
212
  }
350
213
 
351
214
  try {
352
- const response = await fetch(baseUrl + '/api/client/capture-event', {
353
- method: 'POST',
354
- headers: {
355
- 'Accept': 'application/json',
356
- 'Content-Type': 'application/json',
357
- },
358
- body: JSON.stringify({
359
- token: this.token,
360
- event_name: eventName,
361
- event_data: eventData,
362
- device_data: await device_data(),
363
- install_instance_id: await getLinkRunnerInstallInstanceId(),
364
- }),
365
- });
366
-
367
- const result = await response.json();
368
-
369
- if (result?.status !== 200 && result?.status !== 201) {
370
- console.error('Linkrunner: Track event failed');
371
- console.error('Linkrunner: ', result?.msg);
372
- return;
215
+ const result = await LinkrunnerSDKModule.trackEvent(eventName, eventData || {});
216
+
217
+ if (__DEV__) {
218
+ console.log('Linkrunner event tracked successfully:', eventName);
219
+ console.log('track event response > ', result);
373
220
  }
374
221
 
222
+ return result?.data;
223
+ } catch (error) {
224
+ console.error('Error tracking event via native module', error);
225
+ throw error;
226
+ }
227
+ }
228
+
229
+
230
+ async setAdditionalData(integrationData: IntegrationData): Promise<void | any> {
231
+ if (!this.token) {
232
+ console.error('Linkrunner: Setting integration data failed, token not initialized');
233
+ return;
234
+ }
235
+
236
+ if (!integrationData || Object.keys(integrationData).length === 0) {
237
+ console.error('Linkrunner: Integration data is required');
238
+ return;
239
+ }
240
+
241
+ try {
242
+ // Call the native module implementation
243
+ const result = await LinkrunnerSDKModule.setAdditionalData(integrationData);
244
+
375
245
  if (__DEV__) {
376
- console.log('Linkrunner: Tracking event', eventName, eventData);
246
+ console.log('Linkrunner: Integration data set successfully', integrationData);
247
+ console.log('set additional data response > ', result);
377
248
  }
378
249
 
379
250
  return result?.data;
380
251
  } catch (error) {
381
- console.error('Linkrunner: Track event failed');
252
+ console.error('Linkrunner: Setting integration data failed');
382
253
  console.error('Linkrunner: ', error);
254
+ throw error;
383
255
  }
384
256
  }
385
-
386
- /**
387
- * Processes Google Analytics with GCLID from install referrer
388
- * @param analytics - Instance of Firebase Analytics
389
- */
390
- async processGoogleAnalytics(analytics: any): Promise<void> {
391
- if (Platform.OS !== 'android') {
257
+
258
+ async getAttributionData(): Promise<AttributionData | void> {
259
+ if (!this.token) {
260
+ console.error('Linkrunner: Getting attribution data failed, token not initialized');
392
261
  return;
393
262
  }
394
263
 
395
264
  try {
396
- const gclid = await this.extractGCLID();
397
-
398
- if (!gclid) {
399
- return;
265
+ const result = await LinkrunnerSDKModule.getAttributionData();
266
+
267
+ if (__DEV__) {
268
+ console.log('Linkrunner: Attribution data retrieved successfully');
269
+ console.log('get attribution data response > ', result);
400
270
  }
401
271
 
402
- // Log event with GCLID
403
- await analytics().logEvent('install_with_gclid', {
404
- gclid: gclid,
405
- });
406
-
407
- // Set user property with GCLID
408
- await analytics().setUserProperty('gclid', gclid);
272
+ return result as AttributionData;
409
273
  } catch (error) {
410
- console.error('Linkrunner: Error processing Google Analytics:', error);
274
+ console.error('Linkrunner: Getting attribution data failed');
275
+ console.error('Linkrunner: ', error);
276
+ throw error;
411
277
  }
412
278
  }
413
279
 
414
- /**
415
- * Extracts GCLID from install referrer
416
- * @returns Promise with GCLID string or null if not found
417
- */
418
- private extractGCLID(): Promise<string | null> {
419
- return new Promise((resolve) => {
420
- // Set a timeout to ensure the promise resolves even if there's an issue
421
- const timeoutId = setTimeout(() => {
422
- resolve(null);
423
- }, 5000);
424
-
425
- try {
426
- PlayInstallReferrer.getInstallReferrerInfo(
427
- (installReferrerInfo, error) => {
428
- // Clear the timeout since callback fired
429
- clearTimeout(timeoutId);
430
-
431
- if (error) {
432
- resolve(null);
433
- return;
434
- }
435
-
436
- if (!installReferrerInfo || !installReferrerInfo.installReferrer) {
437
- resolve(null);
438
- return;
439
- }
440
-
441
- // Parse the referrer URL to extract GCLID
442
- try {
443
- const referrer = installReferrerInfo.installReferrer;
444
- const urlParams = new URLSearchParams(referrer);
445
- let gclid = urlParams.get('gclid');
446
-
447
- if (!gclid) {
448
- const match = referrer.match(/gclid=([^&]*)/);
449
- gclid = !!match?.[1] ? match[1] : null;
450
- }
451
-
452
- resolve(gclid);
453
- } catch (parseError) {
454
- console.error(
455
- 'Linkrunner: Error parsing referrer URL:',
456
- parseError
457
- );
458
- resolve(null);
459
- }
460
- }
461
- );
462
- } catch (e) {
463
- // Clear the timeout since we caught an exception
464
- clearTimeout(timeoutId);
465
- console.error('Linkrunner: Exception in extractGCLID:', e);
466
- resolve(null);
280
+ enablePIIHashing(enabled: boolean = true): void {
281
+ try {
282
+ LinkrunnerSDKModule.enablePIIHashing(enabled);
283
+
284
+ if (__DEV__) {
285
+ console.log(`Linkrunner: PII hashing ${enabled ? 'enabled' : 'disabled'}`);
467
286
  }
468
- });
287
+ } catch (error) {
288
+ console.error(`Linkrunner: Failed to ${enabled ? 'enable' : 'disable'} PII hashing`);
289
+ console.error('Linkrunner: ', error);
290
+ }
469
291
  }
292
+
470
293
  }
471
294
 
472
295
  const linkrunner = new Linkrunner();
473
296
 
474
- export type LRInitResponse = {
475
- ip_location_data: LRIPLocationData;
476
- deeplink: string;
477
- root_domain: boolean;
478
- campaign_data: CampaignData;
479
- };
480
-
481
- export type LRTriggerResponse = Response;
482
-
483
297
  export default linkrunner;
package/src/types.ts CHANGED
@@ -15,26 +15,29 @@ export interface UserData {
15
15
  name?: string;
16
16
  phone?: string;
17
17
  email?: string;
18
+ mixpanel_distinct_id?: string;
19
+ amplitude_device_id?: string;
20
+ posthog_distinct_id?: string;
18
21
  }
19
22
 
20
- export interface TriggerConfig {
21
- trigger_deeplink?: boolean;
23
+ export interface IntegrationData {
24
+ clevertapId?: string;
22
25
  }
23
26
 
24
27
  export interface CampaignData {
25
28
  id: string;
26
29
  name: string;
27
- type: 'ORGANIC' | 'INORGANIC';
28
- ad_network: 'META' | 'GOOGLE' | null;
29
- group_name: string | null;
30
- asset_group_name: string | null;
31
- asset_name: string | null;
30
+ type: string;
31
+ adNetwork?: string | null;
32
+ installedAt: string;
33
+ storeClickAt?: string | null;
34
+ groupName?: string;
35
+ assetName?: string;
36
+ assetGroupName?: string;
32
37
  }
33
38
 
34
- export type Response = {
35
- ip_location_data: LRIPLocationData;
36
- deeplink: string;
37
- root_domain: boolean;
38
- trigger?: boolean;
39
- campaign_data: CampaignData;
40
- };
39
+ export interface AttributionData {
40
+ // Direct fields
41
+ deeplink?: string;
42
+ campaignData?: CampaignData;
43
+ }