@magicpixel/rn-mp-client-sdk 1.13.0 → 1.13.21
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/README.md +163 -14
- package/lib/commonjs/common/app-types.js.map +1 -1
- package/lib/commonjs/common/constants.js +11 -2
- package/lib/commonjs/common/constants.js.map +1 -1
- package/lib/commonjs/common/data-store.js +13 -30
- package/lib/commonjs/common/data-store.js.map +1 -1
- package/lib/commonjs/common/deeplink-helper.js +174 -0
- package/lib/commonjs/common/deeplink-helper.js.map +1 -0
- package/lib/commonjs/common/device-info-helper.js +168 -0
- package/lib/commonjs/common/device-info-helper.js.map +1 -0
- package/lib/commonjs/common/event-bus.js +39 -0
- package/lib/commonjs/common/event-bus.js.map +1 -1
- package/lib/commonjs/common/network-service.js +119 -15
- package/lib/commonjs/common/network-service.js.map +1 -1
- package/lib/commonjs/common/reporter.js +75 -14
- package/lib/commonjs/common/reporter.js.map +1 -1
- package/lib/commonjs/common/storage-helper.js +227 -0
- package/lib/commonjs/common/storage-helper.js.map +1 -0
- package/lib/commonjs/common/utils.js +62 -2
- package/lib/commonjs/common/utils.js.map +1 -1
- package/lib/commonjs/eedl/eedl.js +198 -44
- package/lib/commonjs/eedl/eedl.js.map +1 -1
- package/lib/commonjs/index.js +301 -54
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/models/mp-client-sdk.js +17 -10
- package/lib/commonjs/models/mp-client-sdk.js.map +1 -1
- package/lib/commonjs/processors/data-element.processor.js +51 -7
- package/lib/commonjs/processors/data-element.processor.js.map +1 -1
- package/lib/commonjs/processors/visit-id.processor.js +78 -15
- package/lib/commonjs/processors/visit-id.processor.js.map +1 -1
- package/lib/module/common/app-types.js.map +1 -1
- package/lib/module/common/constants.js +11 -2
- package/lib/module/common/constants.js.map +1 -1
- package/lib/module/common/data-store.js +13 -30
- package/lib/module/common/data-store.js.map +1 -1
- package/lib/module/common/deeplink-helper.js +168 -0
- package/lib/module/common/deeplink-helper.js.map +1 -0
- package/lib/module/common/device-info-helper.js +161 -0
- package/lib/module/common/device-info-helper.js.map +1 -0
- package/lib/module/common/event-bus.js +39 -0
- package/lib/module/common/event-bus.js.map +1 -1
- package/lib/module/common/network-service.js +119 -15
- package/lib/module/common/network-service.js.map +1 -1
- package/lib/module/common/reporter.js +76 -14
- package/lib/module/common/reporter.js.map +1 -1
- package/lib/module/common/storage-helper.js +221 -0
- package/lib/module/common/storage-helper.js.map +1 -0
- package/lib/module/common/utils.js +63 -2
- package/lib/module/common/utils.js.map +1 -1
- package/lib/module/eedl/eedl.js +198 -44
- package/lib/module/eedl/eedl.js.map +1 -1
- package/lib/module/index.js +290 -53
- package/lib/module/index.js.map +1 -1
- package/lib/module/models/mp-client-sdk.js +16 -9
- package/lib/module/models/mp-client-sdk.js.map +1 -1
- package/lib/module/processors/data-element.processor.js +51 -7
- package/lib/module/processors/data-element.processor.js.map +1 -1
- package/lib/module/processors/visit-id.processor.js +78 -15
- package/lib/module/processors/visit-id.processor.js.map +1 -1
- package/lib/typescript/{common → src/common}/app-types.d.ts +30 -9
- package/lib/typescript/{common → src/common}/constants.d.ts +0 -1
- package/lib/typescript/{common → src/common}/data-store.d.ts +3 -8
- package/lib/typescript/src/common/deeplink-helper.d.ts +60 -0
- package/lib/typescript/src/common/device-info-helper.d.ts +54 -0
- package/lib/typescript/src/common/event-bus.d.ts +21 -0
- package/lib/typescript/src/common/network-service.d.ts +32 -0
- package/lib/typescript/{common → src/common}/reporter.d.ts +2 -1
- package/lib/typescript/src/common/storage-helper.d.ts +47 -0
- package/lib/typescript/{common → src/common}/utils.d.ts +25 -0
- package/lib/typescript/{eedl → src/eedl}/eedl.d.ts +43 -1
- package/lib/typescript/{index.d.ts → src/index.d.ts} +39 -5
- package/lib/typescript/{models → src/models}/mp-client-sdk.d.ts +7 -0
- package/lib/typescript/src/processors/visit-id.processor.d.ts +23 -0
- package/package.json +25 -36
- package/src/common/app-types.ts +33 -10
- package/src/common/constants.ts +0 -6
- package/src/common/data-store.ts +8 -30
- package/src/common/deeplink-helper.ts +181 -0
- package/src/common/device-info-helper.ts +190 -0
- package/src/common/event-bus.ts +39 -0
- package/src/common/network-service.ts +154 -21
- package/src/common/reporter.ts +97 -16
- package/src/common/storage-helper.ts +260 -0
- package/src/common/utils.ts +63 -2
- package/src/eedl/eedl.ts +225 -51
- package/src/index.tsx +346 -73
- package/src/models/mp-client-sdk.ts +8 -0
- package/src/processors/data-element.processor.ts +85 -7
- package/src/processors/visit-id.processor.ts +92 -22
- package/lib/commonjs/processors/trans-function.processor.js +0 -73
- package/lib/commonjs/processors/trans-function.processor.js.map +0 -1
- package/lib/module/processors/trans-function.processor.js +0 -66
- package/lib/module/processors/trans-function.processor.js.map +0 -1
- package/lib/typescript/common/event-bus.d.ts +0 -6
- package/lib/typescript/common/network-service.d.ts +0 -8
- package/lib/typescript/processors/trans-function.processor.d.ts +0 -12
- package/lib/typescript/processors/visit-id.processor.d.ts +0 -9
- package/src/processors/trans-function.processor.ts +0 -85
- /package/lib/typescript/{common → src/common}/logger.d.ts +0 -0
- /package/lib/typescript/{models → src/models}/geo-api-response.d.ts +0 -0
- /package/lib/typescript/{processors → src/processors}/data-element.processor.d.ts +0 -0
- /package/lib/typescript/{processors → src/processors}/geo-location.processor.d.ts +0 -0
- /package/lib/typescript/{processors → src/processors}/qc.processor.d.ts +0 -0
- /package/lib/typescript/{processors → src/processors}/tag.processor.d.ts +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magicpixel/rn-mp-client-sdk",
|
|
3
|
-
"version": "1.13.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.13.21",
|
|
4
|
+
"description": "React Native SDK for MagicPixel analytics and tag management",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
7
7
|
"types": "lib/typescript/index.d.ts",
|
|
@@ -10,40 +10,36 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"src",
|
|
12
12
|
"lib",
|
|
13
|
-
"android",
|
|
14
|
-
"ios",
|
|
15
|
-
"cpp",
|
|
16
|
-
"rn-mp-client-sdk.podspec",
|
|
17
13
|
"!lib/typescript/example",
|
|
18
|
-
"!android/build",
|
|
19
|
-
"!ios/build",
|
|
20
14
|
"!**/__tests__",
|
|
21
15
|
"!**/__fixtures__",
|
|
22
16
|
"!**/__mocks__"
|
|
23
17
|
],
|
|
24
18
|
"scripts": {
|
|
25
|
-
"test": "
|
|
19
|
+
"test": "echo 'No tests configured'",
|
|
26
20
|
"typescript": "tsc --noEmit",
|
|
27
21
|
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
28
22
|
"prepare": "bob build",
|
|
29
23
|
"release": "release-it",
|
|
30
24
|
"example": "yarn --cwd example",
|
|
31
25
|
"bootstrap": "yarn example && yarn && yarn example pods",
|
|
32
|
-
"
|
|
33
|
-
"localpublish": "npm publish --@magicpixel:registry=http://localhost:4873 && npm run postpublish"
|
|
26
|
+
"localpublish": "npm publish --@magicpixel:registry=http://localhost:4873"
|
|
34
27
|
},
|
|
35
28
|
"keywords": [
|
|
36
29
|
"react-native",
|
|
37
30
|
"ios",
|
|
38
31
|
"android"
|
|
39
32
|
],
|
|
40
|
-
"repository":
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git@github.com:digital-madtech/mp-rn-client-sdk.git"
|
|
36
|
+
},
|
|
41
37
|
"author": "MP Engineering <engineering@magicpixel.io> (https://magicpixel.io)",
|
|
42
38
|
"license": "MIT",
|
|
43
39
|
"bugs": {
|
|
44
|
-
"url": "https://github.com/mp/issues"
|
|
40
|
+
"url": "https://github.com/digital-madtech/mp-rn-client-sdk/issues"
|
|
45
41
|
},
|
|
46
|
-
"homepage": "https://github.com/mp#readme",
|
|
42
|
+
"homepage": "https://github.com/digital-madtech/mp-rn-client-sdk#readme",
|
|
47
43
|
"publishConfig": {
|
|
48
44
|
"registry": "https://registry.npmjs.org/",
|
|
49
45
|
"access": "public"
|
|
@@ -53,38 +49,34 @@
|
|
|
53
49
|
"@babel/eslint-parser": "7.19.1",
|
|
54
50
|
"@babel/runtime": "7.26.10",
|
|
55
51
|
"@commitlint/config-conventional": "17.1.0",
|
|
52
|
+
"@octokit/plugin-paginate-rest": "9.2.2",
|
|
53
|
+
"@octokit/request": "8.4.1",
|
|
54
|
+
"@octokit/request-error": "5.1.1",
|
|
56
55
|
"@react-native-community/eslint-config": "3.1.0",
|
|
57
56
|
"@release-it/conventional-changelog": "8.0.1",
|
|
58
57
|
"@types/flat": "5.0.2",
|
|
59
|
-
"@types/
|
|
60
|
-
"@types/react": "~17.0.21",
|
|
58
|
+
"@types/react": "17.0.50",
|
|
61
59
|
"@types/react-native": "0.72.8",
|
|
62
60
|
"@types/uuid": "9.0.0",
|
|
61
|
+
"braces": "3.0.3",
|
|
63
62
|
"commitlint": "17.1.2",
|
|
63
|
+
"cross-spawn": "7.0.5",
|
|
64
64
|
"eslint": "8.23.1",
|
|
65
65
|
"eslint-config-prettier": "8.5.0",
|
|
66
66
|
"eslint-plugin-prettier": "4.2.1",
|
|
67
|
-
"
|
|
67
|
+
"micromatch": "4.0.8",
|
|
68
68
|
"pod-install": "0.1.38",
|
|
69
69
|
"prettier": "2.7.1",
|
|
70
70
|
"react": "18.2.0",
|
|
71
71
|
"react-native": "0.72.17",
|
|
72
72
|
"react-native-builder-bob": "0.18.3",
|
|
73
73
|
"release-it": "17.1.1",
|
|
74
|
-
"ts-jest": "29.0.2",
|
|
75
74
|
"tsconfig-paths": "4.1.0",
|
|
76
75
|
"typescript": "4.8.3",
|
|
77
|
-
"ts-node": "10.9.1",
|
|
78
|
-
"@octokit/plugin-paginate-rest": "9.2.2",
|
|
79
|
-
"@octokit/request": "8.4.1",
|
|
80
|
-
"@octokit/request-error": "5.1.1",
|
|
81
|
-
"braces": "3.0.3",
|
|
82
|
-
"cross-spawn": "7.0.5",
|
|
83
|
-
"micromatch": "4.0.8",
|
|
84
76
|
"ws": "7.5.10"
|
|
85
77
|
},
|
|
86
78
|
"resolutions": {
|
|
87
|
-
"@types/react": "17.0.
|
|
79
|
+
"@types/react": "17.0.50",
|
|
88
80
|
"ip": "2.0.2",
|
|
89
81
|
"pac-resolver": "7.0.1",
|
|
90
82
|
"socks": "2.7.3",
|
|
@@ -93,20 +85,14 @@
|
|
|
93
85
|
"socks-proxy-agent": "8.0.2",
|
|
94
86
|
"agent-base": "7.1.0"
|
|
95
87
|
},
|
|
88
|
+
"overrides": {
|
|
89
|
+
"diff": "8.0.3",
|
|
90
|
+
"tmp": "0.2.4"
|
|
91
|
+
},
|
|
96
92
|
"peerDependencies": {
|
|
97
93
|
"react": "*",
|
|
98
94
|
"react-native": "*"
|
|
99
95
|
},
|
|
100
|
-
"jest": {
|
|
101
|
-
"preset": "react-native",
|
|
102
|
-
"transform": {
|
|
103
|
-
".+\\.(t|j)s$": "ts-jest"
|
|
104
|
-
},
|
|
105
|
-
"modulePathIgnorePatterns": [
|
|
106
|
-
"<rootDir>/example/node_modules",
|
|
107
|
-
"<rootDir>/lib/"
|
|
108
|
-
]
|
|
109
|
-
},
|
|
110
96
|
"commitlint": {
|
|
111
97
|
"extends": [
|
|
112
98
|
"@commitlint/config-conventional"
|
|
@@ -174,7 +160,10 @@
|
|
|
174
160
|
"flat": "5.0.2",
|
|
175
161
|
"nanoid": "5.1.5",
|
|
176
162
|
"performant-array-to-tree": "1.11.0",
|
|
163
|
+
"react-native-device-info": "15.0.1",
|
|
164
|
+
"react-native-get-random-values": "1.11.0",
|
|
177
165
|
"react-native-url-polyfill": "1.3.0",
|
|
166
|
+
"ulid": "3.0.1",
|
|
178
167
|
"uuid": "9.0.0"
|
|
179
168
|
}
|
|
180
169
|
}
|
package/src/common/app-types.ts
CHANGED
|
@@ -4,14 +4,6 @@ export interface MapLike<T = any> {
|
|
|
4
4
|
[key: string]: T;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export interface RunQueueModel {
|
|
8
|
-
sseOnly: boolean;
|
|
9
|
-
name: string;
|
|
10
|
-
dcrName: string;
|
|
11
|
-
eventId: string;
|
|
12
|
-
dcrPayload?: Record<string, any>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
7
|
export type TypedAny = Record<
|
|
16
8
|
string,
|
|
17
9
|
| string
|
|
@@ -38,6 +30,7 @@ export interface AppCustomerInfo {
|
|
|
38
30
|
|
|
39
31
|
export interface AppPageLoad {
|
|
40
32
|
page_name: string;
|
|
33
|
+
page_url?: string;
|
|
41
34
|
is_entry?: number;
|
|
42
35
|
deep_link_url?: string;
|
|
43
36
|
app_version?: string;
|
|
@@ -94,6 +87,7 @@ export interface Report {
|
|
|
94
87
|
tagCt: number; // expected tag count on this page
|
|
95
88
|
dws: 'j' | 'n'; // down stream configuration. java or node
|
|
96
89
|
v: string; // client sdk version for this call
|
|
90
|
+
devPayload?: string; // base64 encoded developer payload
|
|
97
91
|
}
|
|
98
92
|
|
|
99
93
|
export interface UrlInfo {
|
|
@@ -130,18 +124,47 @@ export interface SdkInitOptions {
|
|
|
130
124
|
baseUrl: string;
|
|
131
125
|
logLevel: string;
|
|
132
126
|
|
|
127
|
+
/**
|
|
128
|
+
* @deprecated device_type is now auto-detected and cannot be overridden
|
|
129
|
+
* This field is ignored. The SDK uses Expo/react-native-device-info for reliable detection.
|
|
130
|
+
*/
|
|
133
131
|
device_type?: string;
|
|
134
132
|
|
|
133
|
+
/**
|
|
134
|
+
* @deprecated app_version is now auto-detected and cannot be overridden
|
|
135
|
+
* This field is ignored. The SDK will always use the actual app version from the device.
|
|
136
|
+
*/
|
|
135
137
|
app_version?: string;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @deprecated os_version is now auto-detected and cannot be overridden
|
|
141
|
+
* This field is ignored. The SDK will always use the actual OS version from the device.
|
|
142
|
+
*/
|
|
136
143
|
os_version?: string;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Event deduplication window in milliseconds
|
|
147
|
+
* If the same event (name + payload) fires within this window, it will be skipped
|
|
148
|
+
* Purchase events are deduplicated by transaction_id regardless of other fields
|
|
149
|
+
* Default: 5000ms (5 seconds)
|
|
150
|
+
* Set to 0 to disable deduplication
|
|
151
|
+
*/
|
|
152
|
+
eventDeduplicationWindowMs?: number;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Callback invoked when SDK initialization fails after all retries
|
|
156
|
+
* Use this to handle initialization failures gracefully in your app
|
|
157
|
+
* @param error The error that caused the failure
|
|
158
|
+
*/
|
|
159
|
+
onInitFailure?: (error: Error) => void;
|
|
137
160
|
}
|
|
138
161
|
|
|
139
162
|
export interface MpDeviceInfo {
|
|
140
163
|
package_name?: string; // app package name like com.company.starter
|
|
141
164
|
short_version?: string;
|
|
142
165
|
long_version?: string;
|
|
143
|
-
os_version
|
|
144
|
-
device_model_name
|
|
166
|
+
os_version?: string; // Will be auto-detected if not provided
|
|
167
|
+
device_model_name?: string; // Will be auto-detected if not provided
|
|
145
168
|
locale?: string;
|
|
146
169
|
timezone_abbreviation?: string;
|
|
147
170
|
carrier?: string;
|
package/src/common/constants.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import { customAlphabet } from 'nanoid/non-secure';
|
|
2
1
|
import type { BaseResourceParamType } from '../models/mp-client-sdk';
|
|
3
2
|
|
|
4
3
|
export class Constants {
|
|
5
4
|
static PLACEHOLDER_REGEX = new RegExp(/({{)([A-Z_0-9 -]+)(}})/gi);
|
|
6
5
|
|
|
7
|
-
static LARGE_RANDOM_POOL = customAlphabet(
|
|
8
|
-
'1234567090abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
9
|
-
21
|
|
10
|
-
);
|
|
11
|
-
|
|
12
6
|
static MP_DL_EVT = 'mp_dl_event';
|
|
13
7
|
static CUST_EVT = 'custom_event';
|
|
14
8
|
|
package/src/common/data-store.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type {
|
|
|
4
4
|
MapLike,
|
|
5
5
|
MpDeviceInfo,
|
|
6
6
|
QcInfoItem,
|
|
7
|
-
RunQueueModel,
|
|
8
7
|
SdkInitOptions,
|
|
9
8
|
UrlInfo,
|
|
10
9
|
VisitInfo,
|
|
@@ -21,8 +20,7 @@ import type {
|
|
|
21
20
|
} from '../models/mp-client-sdk';
|
|
22
21
|
import { Platform } from 'react-native';
|
|
23
22
|
import { Utils } from './utils';
|
|
24
|
-
|
|
25
|
-
const CORE_VERSION = '0.8.6';
|
|
23
|
+
import { version as SDK_VERSION } from '../../package.json';
|
|
26
24
|
|
|
27
25
|
export class DataStore {
|
|
28
26
|
private static isDataStoreInitialized = false;
|
|
@@ -34,14 +32,12 @@ export class DataStore {
|
|
|
34
32
|
private static clientSdk: MpClientSdk;
|
|
35
33
|
private static urls: UrlInfo;
|
|
36
34
|
private static debugId: string;
|
|
37
|
-
private static visitorInfo: VisitorInfo;
|
|
35
|
+
private static visitorInfo: VisitorInfo | null;
|
|
38
36
|
private static visitInfo: VisitInfo;
|
|
39
37
|
private static pageLang = 'en';
|
|
40
38
|
private static mpEnvShort: 'stg' | 'prd';
|
|
41
39
|
private static devicePlatform = 'unknown';
|
|
42
40
|
private static deviceType = 'mobile';
|
|
43
|
-
private static runQueue: RunQueueModel[] = [];
|
|
44
|
-
private static isProcessing = false;
|
|
45
41
|
private static sdkInitOptions: SdkInitOptions;
|
|
46
42
|
private static deviceInfo: MpDeviceInfo;
|
|
47
43
|
private static geoInfo: GeoResponse;
|
|
@@ -117,16 +113,6 @@ export class DataStore {
|
|
|
117
113
|
return true;
|
|
118
114
|
}
|
|
119
115
|
|
|
120
|
-
static enQueueTMFire(
|
|
121
|
-
sseOnly: boolean,
|
|
122
|
-
name: string,
|
|
123
|
-
dcrName: string,
|
|
124
|
-
eventId: string,
|
|
125
|
-
dcrPayload?: Record<string, any>
|
|
126
|
-
): void {
|
|
127
|
-
this.runQueue.push({ sseOnly, name, dcrName, eventId, dcrPayload });
|
|
128
|
-
}
|
|
129
|
-
|
|
130
116
|
static isDataStoreReady(): boolean {
|
|
131
117
|
return this.isDataStoreInitialized;
|
|
132
118
|
}
|
|
@@ -163,18 +149,6 @@ export class DataStore {
|
|
|
163
149
|
this.sdkInitOptions = options;
|
|
164
150
|
}
|
|
165
151
|
|
|
166
|
-
static deQueueTMFire(): RunQueueModel {
|
|
167
|
-
return this.runQueue.length > 0 ? this.runQueue.pop() : null;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
static isTagManagerProcessing(): boolean {
|
|
171
|
-
return this.isProcessing;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
static setTagManagerProcessing(status: boolean): void {
|
|
175
|
-
this.isProcessing = status;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
152
|
static getSdkDataElements(): MapLike<ClientSdkDeItem> {
|
|
179
153
|
return this.clientSdk?.d || {};
|
|
180
154
|
}
|
|
@@ -217,10 +191,14 @@ export class DataStore {
|
|
|
217
191
|
: undefined;
|
|
218
192
|
}
|
|
219
193
|
|
|
220
|
-
static setVisitorInfo(visitorInfo: VisitorInfo): void {
|
|
194
|
+
static setVisitorInfo(visitorInfo: VisitorInfo | null): void {
|
|
221
195
|
this.visitorInfo = visitorInfo;
|
|
222
196
|
}
|
|
223
197
|
|
|
198
|
+
static getVisitorInfo(): VisitorInfo | null {
|
|
199
|
+
return this.visitorInfo;
|
|
200
|
+
}
|
|
201
|
+
|
|
224
202
|
static getDebugId(): string {
|
|
225
203
|
return this.debugId;
|
|
226
204
|
}
|
|
@@ -380,7 +358,7 @@ export class DataStore {
|
|
|
380
358
|
}
|
|
381
359
|
|
|
382
360
|
static getCoreVersion(): string {
|
|
383
|
-
return
|
|
361
|
+
return SDK_VERSION;
|
|
384
362
|
}
|
|
385
363
|
|
|
386
364
|
static getMpEnv(): 'stg' | 'prd' {
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { Linking } from 'react-native';
|
|
2
|
+
import { Logger } from './logger';
|
|
3
|
+
|
|
4
|
+
export enum DeepLinkType {
|
|
5
|
+
CUSTOM_SCHEME = 'custom_scheme', // myapp://
|
|
6
|
+
UNIVERSAL_LINK = 'universal_link', // https:// (iOS Universal Links)
|
|
7
|
+
APP_LINK = 'app_link', // https:// (Android App Links)
|
|
8
|
+
HTTP_LINK = 'http_link', // http://
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Helper class to automatically detect and handle deeplinks
|
|
13
|
+
*
|
|
14
|
+
* Supports multiple link types:
|
|
15
|
+
* 1. Custom URL Schemes: myapp://product/123
|
|
16
|
+
* 2. Universal Links (iOS): https://my-site.com/product/123
|
|
17
|
+
* 3. App Links (Android): https://my-site.com/product/123
|
|
18
|
+
* 4. HTTP Links: http://my-site.com/product/123
|
|
19
|
+
*
|
|
20
|
+
* Common use cases:
|
|
21
|
+
* - Ad clicks (Facebook, Google, TikTok ads with http/https URLs)
|
|
22
|
+
* - Push notification deep links
|
|
23
|
+
* - Email marketing links
|
|
24
|
+
* - SMS campaign links
|
|
25
|
+
* - QR codes
|
|
26
|
+
*/
|
|
27
|
+
export class DeepLinkHelper {
|
|
28
|
+
private static listener: any = null;
|
|
29
|
+
private static onDeepLinkCallback:
|
|
30
|
+
| ((url: string, linkType: DeepLinkType) => void)
|
|
31
|
+
| null = null;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Determine the type of deeplink
|
|
35
|
+
* @param url The URL to analyze
|
|
36
|
+
* @returns The type of deeplink
|
|
37
|
+
*/
|
|
38
|
+
static getLinkType(url: string): DeepLinkType {
|
|
39
|
+
try {
|
|
40
|
+
if (url.startsWith('https://')) {
|
|
41
|
+
// Universal Links (iOS) and App Links (Android) both use HTTPS
|
|
42
|
+
return DeepLinkType.UNIVERSAL_LINK;
|
|
43
|
+
} else if (url.startsWith('http://')) {
|
|
44
|
+
return DeepLinkType.HTTP_LINK;
|
|
45
|
+
} else {
|
|
46
|
+
// Custom URL scheme (e.g., myapp://)
|
|
47
|
+
return DeepLinkType.CUSTOM_SCHEME;
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
Logger.logError('Error determining link type:', error);
|
|
51
|
+
return DeepLinkType.CUSTOM_SCHEME;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Initialize deeplink detection
|
|
57
|
+
* - Checks if app was opened with initial URL (custom scheme, universal link, or HTTP link)
|
|
58
|
+
* - Sets up listener for deeplinks while app is running
|
|
59
|
+
* @param callback Optional callback to be notified when deeplink is detected
|
|
60
|
+
* @returns The initial URL if app was opened with deeplink, null otherwise
|
|
61
|
+
*/
|
|
62
|
+
static async initialize(
|
|
63
|
+
callback?: (url: string, linkType: DeepLinkType) => void
|
|
64
|
+
): Promise<string | null> {
|
|
65
|
+
try {
|
|
66
|
+
// Store callback if provided
|
|
67
|
+
if (callback) {
|
|
68
|
+
this.onDeepLinkCallback = callback;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check if app was opened with a deeplink
|
|
72
|
+
// This works for:
|
|
73
|
+
// - Custom schemes: myapp://product/123
|
|
74
|
+
// - Universal Links (iOS): https://my-site.com/product/123
|
|
75
|
+
// - App Links (Android): https://my-site.com/product/123
|
|
76
|
+
// - HTTP links: http://my-site.com/product/123
|
|
77
|
+
const initialUrl = await Linking.getInitialURL();
|
|
78
|
+
|
|
79
|
+
if (initialUrl) {
|
|
80
|
+
const linkType = this.getLinkType(initialUrl);
|
|
81
|
+
Logger.logDbg(`App opened with initial ${linkType}:`, initialUrl);
|
|
82
|
+
this.handleDeepLink(initialUrl);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ALWAYS set up listener for deeplinks while app is running
|
|
86
|
+
// This ensures we can detect deeplinks even if app was opened with initial deeplink
|
|
87
|
+
this.setupListener();
|
|
88
|
+
|
|
89
|
+
return initialUrl;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
Logger.logError('Error initializing deeplink detection:', error);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Set up listener for deeplinks received while app is running
|
|
98
|
+
* Works for all link types: custom schemes, universal links, app links, HTTP links
|
|
99
|
+
*/
|
|
100
|
+
private static setupListener(): void {
|
|
101
|
+
// Remove existing listener if any
|
|
102
|
+
if (this.listener) {
|
|
103
|
+
this.listener.remove();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Listen for deeplinks while app is running (e.g., user clicks link while app is open)
|
|
107
|
+
this.listener = Linking.addEventListener('url', (event) => {
|
|
108
|
+
const linkType = this.getLinkType(event.url);
|
|
109
|
+
Logger.logDbg(`Deeplink received (${linkType}):`, event.url);
|
|
110
|
+
this.handleDeepLink(event.url);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
Logger.logDbg(
|
|
114
|
+
'Deeplink listener set up (supports custom schemes, universal/app links, HTTP links)'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Handle deeplink URL - notify callback if registered
|
|
120
|
+
* @param url The deeplink URL
|
|
121
|
+
*/
|
|
122
|
+
private static handleDeepLink(url: string): void {
|
|
123
|
+
if (this.onDeepLinkCallback) {
|
|
124
|
+
const linkType = this.getLinkType(url);
|
|
125
|
+
this.onDeepLinkCallback(url, linkType);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Remove deeplink listener and clear callback
|
|
131
|
+
*/
|
|
132
|
+
static cleanup(): void {
|
|
133
|
+
if (this.listener) {
|
|
134
|
+
this.listener.remove();
|
|
135
|
+
this.listener = null;
|
|
136
|
+
}
|
|
137
|
+
this.onDeepLinkCallback = null;
|
|
138
|
+
Logger.logDbg('Deeplink listener and callback cleared');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Parse UTM parameters and other campaign data from URL
|
|
143
|
+
* @param url The deeplink URL
|
|
144
|
+
* @returns Object containing campaign parameters
|
|
145
|
+
*/
|
|
146
|
+
static parseCampaignParams(url: string): Record<string, string> {
|
|
147
|
+
try {
|
|
148
|
+
const urlObj = new URL(url);
|
|
149
|
+
const params: Record<string, string> = {};
|
|
150
|
+
|
|
151
|
+
// Common campaign parameters
|
|
152
|
+
const campaignParams = [
|
|
153
|
+
'utm_source',
|
|
154
|
+
'utm_medium',
|
|
155
|
+
'utm_campaign',
|
|
156
|
+
'utm_term',
|
|
157
|
+
'utm_content',
|
|
158
|
+
'utm_id',
|
|
159
|
+
'gclid', // Google Click ID
|
|
160
|
+
'fbclid', // Facebook Click ID
|
|
161
|
+
'ttclid', // TikTok Click ID
|
|
162
|
+
'campaign_id',
|
|
163
|
+
'ad_id',
|
|
164
|
+
'adset_id',
|
|
165
|
+
'creative_id',
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
campaignParams.forEach((param) => {
|
|
169
|
+
const value = urlObj.searchParams.get(param);
|
|
170
|
+
if (value) {
|
|
171
|
+
params[param] = value;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return params;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
Logger.logError('Error parsing campaign params from URL:', error);
|
|
178
|
+
return {};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { Logger } from './logger';
|
|
3
|
+
|
|
4
|
+
interface DeviceAppInfo {
|
|
5
|
+
app_version: string;
|
|
6
|
+
build_number: string;
|
|
7
|
+
package_name: string;
|
|
8
|
+
device_model_name: string;
|
|
9
|
+
device_manufacturer: string;
|
|
10
|
+
device_id: string;
|
|
11
|
+
os_name: string;
|
|
12
|
+
os_version: string;
|
|
13
|
+
is_tablet: boolean;
|
|
14
|
+
is_emulator: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Helper class to automatically detect device and app information
|
|
19
|
+
* Supports both Expo and bare React Native:
|
|
20
|
+
* 1. Tries Expo modules first (works in Expo Go)
|
|
21
|
+
* 2. Falls back to react-native-device-info (for bare RN)
|
|
22
|
+
* 3. Returns safe defaults if neither is available
|
|
23
|
+
*/
|
|
24
|
+
export class DeviceInfoHelper {
|
|
25
|
+
private static expoApplication: any = null;
|
|
26
|
+
private static expoDevice: any = null;
|
|
27
|
+
private static expoConstants: any = null;
|
|
28
|
+
private static deviceInfo: any = null;
|
|
29
|
+
private static initialized = false;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Try to load Expo modules and react-native-device-info
|
|
33
|
+
* This uses dynamic imports to avoid errors when modules aren't available
|
|
34
|
+
*/
|
|
35
|
+
private static async initialize(): Promise<void> {
|
|
36
|
+
if (this.initialized) return;
|
|
37
|
+
|
|
38
|
+
// Try to load Expo modules first (works in Expo Go)
|
|
39
|
+
try {
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
41
|
+
this.expoApplication = require('expo-application');
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
43
|
+
this.expoDevice = require('expo-device');
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
45
|
+
this.expoConstants = require('expo-constants').default;
|
|
46
|
+
Logger.logDbg('Using Expo modules for device info');
|
|
47
|
+
} catch (error) {
|
|
48
|
+
Logger.logDbg(
|
|
49
|
+
'Expo modules not available, will try react-native-device-info'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Fall back to react-native-device-info (for bare RN)
|
|
54
|
+
if (!this.expoApplication) {
|
|
55
|
+
try {
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
57
|
+
this.deviceInfo = require('react-native-device-info').default;
|
|
58
|
+
Logger.logDbg('Using react-native-device-info for device info');
|
|
59
|
+
} catch (error) {
|
|
60
|
+
Logger.logError(
|
|
61
|
+
'Neither Expo modules nor react-native-device-info available:',
|
|
62
|
+
error
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.initialized = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get comprehensive app and device information
|
|
72
|
+
* All methods are async-safe and handle errors gracefully
|
|
73
|
+
*/
|
|
74
|
+
static async getAppInfo(): Promise<DeviceAppInfo> {
|
|
75
|
+
await this.initialize();
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// Try Expo modules first
|
|
79
|
+
if (this.expoApplication && this.expoDevice) {
|
|
80
|
+
return await this.getAppInfoFromExpo();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Fall back to react-native-device-info
|
|
84
|
+
if (this.deviceInfo) {
|
|
85
|
+
return await this.getAppInfoFromDeviceInfo();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Return safe fallback values
|
|
89
|
+
return this.getSafeDefaults();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
Logger.logError('Error getting device info:', error);
|
|
92
|
+
return this.getSafeDefaults();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get device info using Expo modules (works in Expo Go)
|
|
98
|
+
*/
|
|
99
|
+
private static async getAppInfoFromExpo(): Promise<DeviceAppInfo> {
|
|
100
|
+
const version = this.expoApplication.nativeApplicationVersion || 'unknown';
|
|
101
|
+
const buildNumber = this.expoApplication.nativeBuildVersion || 'unknown';
|
|
102
|
+
const packageName = this.expoApplication.applicationId || 'unknown';
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
// App information
|
|
106
|
+
app_version: version,
|
|
107
|
+
build_number: buildNumber,
|
|
108
|
+
package_name: packageName,
|
|
109
|
+
|
|
110
|
+
// Device information
|
|
111
|
+
device_model_name: this.expoDevice.modelName || 'unknown',
|
|
112
|
+
device_manufacturer: this.expoDevice.manufacturer || 'unknown',
|
|
113
|
+
device_id: this.expoConstants.sessionId || 'unknown',
|
|
114
|
+
|
|
115
|
+
// OS information
|
|
116
|
+
os_name: Platform.OS,
|
|
117
|
+
os_version:
|
|
118
|
+
this.expoDevice.osVersion || Platform.Version?.toString() || 'unknown',
|
|
119
|
+
|
|
120
|
+
// Additional useful info
|
|
121
|
+
// DeviceType enum: UNKNOWN=0, PHONE=1, TABLET=2, DESKTOP=3, TV=4
|
|
122
|
+
is_tablet: this.expoDevice.deviceType === 2, // DeviceType.TABLET = 2
|
|
123
|
+
is_emulator: !this.expoDevice.isDevice,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get device info using react-native-device-info (for bare RN)
|
|
129
|
+
*/
|
|
130
|
+
private static async getAppInfoFromDeviceInfo(): Promise<DeviceAppInfo> {
|
|
131
|
+
const [version, buildNumber, uniqueId, bundleId] = await Promise.all([
|
|
132
|
+
this.deviceInfo.getVersion(),
|
|
133
|
+
this.deviceInfo.getBuildNumber(),
|
|
134
|
+
this.deviceInfo.getUniqueId(),
|
|
135
|
+
this.deviceInfo.getBundleId(),
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
// App information
|
|
140
|
+
app_version: version,
|
|
141
|
+
build_number: buildNumber,
|
|
142
|
+
package_name: bundleId,
|
|
143
|
+
|
|
144
|
+
// Device information
|
|
145
|
+
device_model_name: this.deviceInfo.getModel(),
|
|
146
|
+
device_manufacturer: this.deviceInfo.getManufacturerSync(),
|
|
147
|
+
device_id: uniqueId,
|
|
148
|
+
|
|
149
|
+
// OS information
|
|
150
|
+
os_name: Platform.OS,
|
|
151
|
+
os_version: this.deviceInfo.getSystemVersion(),
|
|
152
|
+
|
|
153
|
+
// Additional useful info
|
|
154
|
+
is_tablet: this.deviceInfo.isTablet(),
|
|
155
|
+
is_emulator: await this.deviceInfo.isEmulator(),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Safe default values when no device info library is available
|
|
161
|
+
*/
|
|
162
|
+
private static getSafeDefaults(): DeviceAppInfo {
|
|
163
|
+
return {
|
|
164
|
+
app_version: 'unknown',
|
|
165
|
+
build_number: 'unknown',
|
|
166
|
+
package_name: 'unknown',
|
|
167
|
+
device_model_name: 'unknown',
|
|
168
|
+
device_manufacturer: 'unknown',
|
|
169
|
+
device_id: 'unknown',
|
|
170
|
+
os_name: Platform.OS,
|
|
171
|
+
os_version: Platform.Version?.toString() || 'unknown',
|
|
172
|
+
is_tablet: false,
|
|
173
|
+
is_emulator: false,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get app version in format: "1.0.0 (123)"
|
|
179
|
+
* Combines version and build number for better tracking
|
|
180
|
+
*/
|
|
181
|
+
static async getFormattedAppVersion(): Promise<string> {
|
|
182
|
+
try {
|
|
183
|
+
const info = await this.getAppInfo();
|
|
184
|
+
return `${info.app_version} (${info.build_number})`;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
Logger.logError('Error getting formatted app version:', error);
|
|
187
|
+
return 'unknown';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|