@li-nk.me/react-native-sdk 0.1.1 → 0.2.1
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 +20 -86
- package/lib/index.d.ts +1 -0
- package/lib/index.js +62 -7
- package/package.json +5 -1
- package/src/index.ts +58 -7
package/README.md
CHANGED
|
@@ -1,103 +1,37 @@
|
|
|
1
1
|
# LinkMe React Native SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React Native SDK for LinkMe — deep linking and attribution.
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
- Example app: See `example-expo/` directory
|
|
9
|
-
- Android troubleshooting: See [Android Troubleshooting](https://li-nk.me/resources/developer/setup/android#troubleshooting) section in Android setup docs
|
|
5
|
+
- **Main Site**: [li-nk.me](https://li-nk.me)
|
|
6
|
+
- **Documentation**: [React Native Setup](https://li-nk.me/docs/developer/setup/react-native)
|
|
7
|
+
- **Package**: [npm](https://www.npmjs.com/package/@linkme/react-native-sdk)
|
|
10
8
|
|
|
11
9
|
## Installation
|
|
12
10
|
|
|
13
11
|
```bash
|
|
14
|
-
npm install @
|
|
12
|
+
npm install @linkme/react-native-sdk
|
|
15
13
|
```
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
## Basic Usage
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
```ts
|
|
18
|
+
import { configure } from '@linkme/react-native-sdk';
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
### Expo Configuration
|
|
28
|
-
|
|
29
|
-
Add the config plugin to your `app.json` or `app.config.js`:
|
|
30
|
-
|
|
31
|
-
```json
|
|
32
|
-
{
|
|
33
|
-
"expo": {
|
|
34
|
-
"plugins": [
|
|
35
|
-
[
|
|
36
|
-
"@li-nk.me/react-native-sdk/plugin/app.plugin.js",
|
|
37
|
-
{
|
|
38
|
-
"hosts": ["link.example.com"],
|
|
39
|
-
"associatedDomains": ["applinks:link.example.com"],
|
|
40
|
-
"schemes": ["com.example.app"]
|
|
41
|
-
}
|
|
42
|
-
]
|
|
43
|
-
]
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Usage
|
|
49
|
-
|
|
50
|
-
Initialize the SDK in your root layout (e.g., `app/_layout.tsx`):
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
import { configure, onLink, getInitialLink, claimDeferredIfAvailable, track } from '@li-nk.me/react-native-sdk';
|
|
54
|
-
|
|
55
|
-
// ... inside your component
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
configure({
|
|
58
|
-
appId: 'your-app-id',
|
|
59
|
-
appKey: 'your-app-key',
|
|
60
|
-
// ... other options
|
|
61
|
-
}).then(() => {
|
|
62
|
-
// 1. Listen for deep links (foreground)
|
|
63
|
-
onLink((payload) => handlePayload(payload));
|
|
64
|
-
|
|
65
|
-
// 2. Check for initial link (cold start)
|
|
66
|
-
getInitialLink().then((payload) => {
|
|
67
|
-
if (payload) handlePayload(payload);
|
|
68
|
-
else {
|
|
69
|
-
// 3. Check for deferred link (first install)
|
|
70
|
-
claimDeferredIfAvailable().then((payload) => {
|
|
71
|
-
if (payload) handlePayload(payload);
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// 4. Track open
|
|
77
|
-
track('open');
|
|
78
|
-
});
|
|
79
|
-
}, []);
|
|
20
|
+
await configure({
|
|
21
|
+
appId: 'your_app_id',
|
|
22
|
+
appKey: 'your_app_key',
|
|
23
|
+
debug: __DEV__, // Optional: surface verbose logs for deferred/pasteboard flows
|
|
24
|
+
});
|
|
80
25
|
```
|
|
81
26
|
|
|
82
|
-
|
|
27
|
+
For full documentation, guides, and API reference, please visit our [Help Center](https://li-nk.me/docs/help).
|
|
83
28
|
|
|
84
|
-
|
|
29
|
+
## Debugging Deferred Links
|
|
85
30
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// Example: Map to Firebase Analytics
|
|
90
|
-
// analytics().logEvent('campaign_details', {
|
|
91
|
-
// source: payload.utm.source,
|
|
92
|
-
// medium: payload.utm.medium,
|
|
93
|
-
// campaign: payload.utm.campaign,
|
|
94
|
-
// term: payload.utm.term,
|
|
95
|
-
// content: payload.utm.content,
|
|
96
|
-
// });
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
```
|
|
31
|
+
- Pass `debug: true` (or `__DEV__`) to `configure` to emit `[LinkMe SDK]` logs for pasteboard and fingerprint claims.
|
|
32
|
+
- Check that Expo Clipboard is installed if you expect pasteboard-based iOS claims.
|
|
33
|
+
- Review server logs for `/api/deferred/claim` to verify fingerprint matches.
|
|
100
34
|
|
|
101
|
-
|
|
35
|
+
## License
|
|
102
36
|
|
|
103
|
-
|
|
37
|
+
Apache-2.0
|
package/lib/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type LinkMeConfig = {
|
|
|
10
10
|
baseUrl?: string;
|
|
11
11
|
appId?: string;
|
|
12
12
|
appKey?: string;
|
|
13
|
+
debug?: boolean;
|
|
13
14
|
/**
|
|
14
15
|
* @deprecated Pasteboard is now controlled from the Portal (App Settings → iOS).
|
|
15
16
|
* The SDK automatically checks pasteboard on iOS if expo-clipboard is installed.
|
package/lib/index.js
CHANGED
|
@@ -13,6 +13,7 @@ class LinkMeController {
|
|
|
13
13
|
var _a, _b;
|
|
14
14
|
this.ready = false;
|
|
15
15
|
this.advertisingConsent = false;
|
|
16
|
+
this.debug = false;
|
|
16
17
|
this.lastPayload = null;
|
|
17
18
|
this.listeners = new Set();
|
|
18
19
|
this.pendingUrls = [];
|
|
@@ -26,9 +27,13 @@ class LinkMeController {
|
|
|
26
27
|
this.linking = (_b = deps === null || deps === void 0 ? void 0 : deps.linking) !== null && _b !== void 0 ? _b : Linking;
|
|
27
28
|
}
|
|
28
29
|
async configure(config) {
|
|
30
|
+
var _a;
|
|
29
31
|
const normalized = normalizeConfig(config);
|
|
30
32
|
this.config = normalized;
|
|
31
33
|
this.advertisingConsent = !!config.includeAdvertisingId;
|
|
34
|
+
const fallbackDev = Boolean(globalThis === null || globalThis === void 0 ? void 0 : globalThis.__DEV__);
|
|
35
|
+
this.debug = (_a = normalized.debug) !== null && _a !== void 0 ? _a : fallbackDev;
|
|
36
|
+
this.logDebug('configure', { baseUrl: normalized.baseUrl, debug: this.debug });
|
|
32
37
|
this.subscribeToLinking();
|
|
33
38
|
this.ready = true;
|
|
34
39
|
await this.drainPending();
|
|
@@ -67,14 +72,19 @@ class LinkMeController {
|
|
|
67
72
|
async claimDeferredIfAvailable() {
|
|
68
73
|
const cfg = this.config;
|
|
69
74
|
if (!cfg) {
|
|
75
|
+
this.logDebug('deferred.skip_no_config');
|
|
70
76
|
return null;
|
|
71
77
|
}
|
|
78
|
+
this.logDebug('deferred.claim.start', { platform: Platform.OS });
|
|
72
79
|
// 1. On iOS, try to read CID from pasteboard first (if expo-clipboard is available)
|
|
73
80
|
if (Platform.OS === 'ios' && (Clipboard === null || Clipboard === void 0 ? void 0 : Clipboard.getStringAsync)) {
|
|
81
|
+
this.logDebug('deferred.pasteboard.check');
|
|
74
82
|
const pasteboardPayload = await this.tryClaimFromPasteboard(cfg);
|
|
75
83
|
if (pasteboardPayload) {
|
|
84
|
+
this.logDebug('deferred.pasteboard.payload');
|
|
76
85
|
return pasteboardPayload;
|
|
77
86
|
}
|
|
87
|
+
this.logDebug('deferred.pasteboard.no_match');
|
|
78
88
|
}
|
|
79
89
|
// 2. Fallback to probabilistic fingerprint matching
|
|
80
90
|
try {
|
|
@@ -85,48 +95,65 @@ class LinkMeController {
|
|
|
85
95
|
if (device) {
|
|
86
96
|
body.device = device;
|
|
87
97
|
}
|
|
98
|
+
this.logDebug('deferred.fingerprint.request');
|
|
88
99
|
const res = await this.fetchImpl(`${cfg.apiBaseUrl}/deferred/claim`, {
|
|
89
100
|
method: 'POST',
|
|
90
101
|
headers: this.buildHeaders(true),
|
|
91
102
|
body: JSON.stringify(body),
|
|
92
103
|
});
|
|
93
104
|
if (!res.ok) {
|
|
105
|
+
this.logDebug('deferred.fingerprint.http_error', { status: res.status });
|
|
94
106
|
return null;
|
|
95
107
|
}
|
|
96
108
|
const payload = await this.parsePayload(res);
|
|
97
109
|
if (payload) {
|
|
110
|
+
this.logDebug('deferred.fingerprint.payload', { linkId: payload.linkId, duplicate: payload === null || payload === void 0 ? void 0 : payload.duplicate });
|
|
98
111
|
this.emit(payload);
|
|
99
112
|
}
|
|
113
|
+
else {
|
|
114
|
+
this.logDebug('deferred.fingerprint.no_match');
|
|
115
|
+
}
|
|
100
116
|
return payload;
|
|
101
117
|
}
|
|
102
|
-
catch {
|
|
118
|
+
catch (err) {
|
|
119
|
+
this.logDebug('deferred.fingerprint.error', { message: err instanceof Error ? err.message : String(err) });
|
|
103
120
|
return null;
|
|
104
121
|
}
|
|
105
122
|
}
|
|
106
123
|
async tryClaimFromPasteboard(cfg) {
|
|
107
124
|
try {
|
|
108
125
|
if (!(Clipboard === null || Clipboard === void 0 ? void 0 : Clipboard.getStringAsync)) {
|
|
126
|
+
this.logDebug('pasteboard.skip_module');
|
|
109
127
|
return null;
|
|
110
128
|
}
|
|
129
|
+
this.logDebug('pasteboard.read');
|
|
111
130
|
const pasteStr = await Clipboard.getStringAsync();
|
|
112
131
|
if (!pasteStr) {
|
|
132
|
+
this.logDebug('pasteboard.empty');
|
|
113
133
|
return null;
|
|
114
134
|
}
|
|
115
135
|
// Check if the clipboard contains a li-nk.me URL with a cid parameter
|
|
116
136
|
const cid = this.extractCidFromUrl(pasteStr, cfg.baseUrl);
|
|
117
137
|
if (!cid) {
|
|
138
|
+
this.logDebug('pasteboard.no_cid', { hasClipboard: true });
|
|
118
139
|
return null;
|
|
119
140
|
}
|
|
141
|
+
this.logDebug('pasteboard.cid_found');
|
|
120
142
|
// Resolve the CID to get the payload
|
|
121
143
|
const payload = await this.resolveCidWithConfig(cfg, cid);
|
|
122
144
|
if (payload) {
|
|
123
145
|
this.emit(payload);
|
|
124
146
|
// Track pasteboard claim
|
|
125
|
-
this.
|
|
147
|
+
this.logDebug('pasteboard.payload', { linkId: payload.linkId });
|
|
148
|
+
void this.track('claim', { claim_type: 'pasteboard' });
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
this.logDebug('pasteboard.resolve_empty');
|
|
126
152
|
}
|
|
127
153
|
return payload;
|
|
128
154
|
}
|
|
129
|
-
catch {
|
|
155
|
+
catch (err) {
|
|
156
|
+
this.logDebug('pasteboard.error', { message: err instanceof Error ? err.message : String(err) });
|
|
130
157
|
return null;
|
|
131
158
|
}
|
|
132
159
|
}
|
|
@@ -136,13 +163,21 @@ class LinkMeController {
|
|
|
136
163
|
// Check if the URL is from our domain
|
|
137
164
|
const baseHost = new URL(baseUrl).host;
|
|
138
165
|
if (!url.host.endsWith(baseHost) && url.host !== baseHost.replace(/^www\./, '')) {
|
|
166
|
+
this.logDebug('pasteboard.url_mismatch', { host: url.host });
|
|
139
167
|
return null;
|
|
140
168
|
}
|
|
141
169
|
// Extract the cid parameter
|
|
142
170
|
const cid = url.searchParams.get('cid');
|
|
171
|
+
if (cid) {
|
|
172
|
+
this.logDebug('pasteboard.url_cid_present');
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
this.logDebug('pasteboard.url_no_cid');
|
|
176
|
+
}
|
|
143
177
|
return cid || null;
|
|
144
178
|
}
|
|
145
|
-
catch {
|
|
179
|
+
catch (err) {
|
|
180
|
+
this.logDebug('pasteboard.url_parse_error', { message: err instanceof Error ? err.message : String(err) });
|
|
146
181
|
return null;
|
|
147
182
|
}
|
|
148
183
|
}
|
|
@@ -194,11 +229,14 @@ class LinkMeController {
|
|
|
194
229
|
body: JSON.stringify(body),
|
|
195
230
|
});
|
|
196
231
|
if (!res.ok) {
|
|
197
|
-
await res.text().catch(() => undefined);
|
|
232
|
+
const text = await res.text().catch(() => undefined);
|
|
233
|
+
this.logDebug('app_events.http_error', { status: res.status, body: text });
|
|
234
|
+
console.warn('[LinkMe SDK] app_events.http_error', { status: res.status, body: text });
|
|
198
235
|
}
|
|
199
236
|
}
|
|
200
237
|
catch {
|
|
201
|
-
|
|
238
|
+
this.logDebug('app_events.network_error');
|
|
239
|
+
console.warn('[LinkMe SDK] app_events.network_error');
|
|
202
240
|
}
|
|
203
241
|
}
|
|
204
242
|
onLink(listener) {
|
|
@@ -385,7 +423,8 @@ class LinkMeController {
|
|
|
385
423
|
const json = (await res.json());
|
|
386
424
|
return json !== null && json !== void 0 ? json : null;
|
|
387
425
|
}
|
|
388
|
-
catch {
|
|
426
|
+
catch (err) {
|
|
427
|
+
this.logDebug('payload.parse_error', { message: err instanceof Error ? err.message : String(err) });
|
|
389
428
|
return null;
|
|
390
429
|
}
|
|
391
430
|
}
|
|
@@ -400,6 +439,22 @@ class LinkMeController {
|
|
|
400
439
|
}
|
|
401
440
|
}
|
|
402
441
|
}
|
|
442
|
+
logDebug(event, details) {
|
|
443
|
+
if (!this.debug) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
if (details) {
|
|
448
|
+
console.log(`[LinkMe SDK] ${event}`, details);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
console.log(`[LinkMe SDK] ${event}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
/* noop */
|
|
456
|
+
}
|
|
457
|
+
}
|
|
403
458
|
}
|
|
404
459
|
function normalizeConfig(config) {
|
|
405
460
|
const baseUrl = (config === null || config === void 0 ? void 0 : config.baseUrl) || 'https://li-nk.me';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@li-nk.me/react-native-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Pure TypeScript React Native SDK for LinkMe deep and deferred links.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"sdk"
|
|
25
25
|
],
|
|
26
26
|
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/r-dev-limited/li-nk.me-react-native-sdk"
|
|
30
|
+
},
|
|
27
31
|
"peerDependencies": {
|
|
28
32
|
"react": ">=18",
|
|
29
33
|
"react-native": ">=0.72.0",
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type LinkMeConfig = {
|
|
|
21
21
|
baseUrl?: string;
|
|
22
22
|
appId?: string;
|
|
23
23
|
appKey?: string;
|
|
24
|
+
debug?: boolean;
|
|
24
25
|
/**
|
|
25
26
|
* @deprecated Pasteboard is now controlled from the Portal (App Settings → iOS).
|
|
26
27
|
* The SDK automatically checks pasteboard on iOS if expo-clipboard is installed.
|
|
@@ -52,6 +53,7 @@ class LinkMeController {
|
|
|
52
53
|
private config: NormalizedConfig | undefined;
|
|
53
54
|
private ready = false;
|
|
54
55
|
private advertisingConsent = false;
|
|
56
|
+
private debug = false;
|
|
55
57
|
private userId: string | undefined;
|
|
56
58
|
private lastPayload: LinkMePayload | null = null;
|
|
57
59
|
private readonly listeners = new Set<Listener>();
|
|
@@ -74,6 +76,9 @@ class LinkMeController {
|
|
|
74
76
|
const normalized = normalizeConfig(config);
|
|
75
77
|
this.config = normalized;
|
|
76
78
|
this.advertisingConsent = !!config.includeAdvertisingId;
|
|
79
|
+
const fallbackDev = Boolean((globalThis as any)?.__DEV__);
|
|
80
|
+
this.debug = normalized.debug ?? fallbackDev;
|
|
81
|
+
this.logDebug('configure', { baseUrl: normalized.baseUrl, debug: this.debug });
|
|
77
82
|
this.subscribeToLinking();
|
|
78
83
|
this.ready = true;
|
|
79
84
|
await this.drainPending();
|
|
@@ -113,15 +118,20 @@ class LinkMeController {
|
|
|
113
118
|
async claimDeferredIfAvailable(): Promise<LinkMePayload | null> {
|
|
114
119
|
const cfg = this.config;
|
|
115
120
|
if (!cfg) {
|
|
121
|
+
this.logDebug('deferred.skip_no_config');
|
|
116
122
|
return null;
|
|
117
123
|
}
|
|
124
|
+
this.logDebug('deferred.claim.start', { platform: Platform.OS });
|
|
118
125
|
|
|
119
126
|
// 1. On iOS, try to read CID from pasteboard first (if expo-clipboard is available)
|
|
120
127
|
if (Platform.OS === 'ios' && Clipboard?.getStringAsync) {
|
|
128
|
+
this.logDebug('deferred.pasteboard.check');
|
|
121
129
|
const pasteboardPayload = await this.tryClaimFromPasteboard(cfg);
|
|
122
130
|
if (pasteboardPayload) {
|
|
131
|
+
this.logDebug('deferred.pasteboard.payload');
|
|
123
132
|
return pasteboardPayload;
|
|
124
133
|
}
|
|
134
|
+
this.logDebug('deferred.pasteboard.no_match');
|
|
125
135
|
}
|
|
126
136
|
|
|
127
137
|
// 2. Fallback to probabilistic fingerprint matching
|
|
@@ -133,20 +143,26 @@ class LinkMeController {
|
|
|
133
143
|
if (device) {
|
|
134
144
|
body.device = device;
|
|
135
145
|
}
|
|
146
|
+
this.logDebug('deferred.fingerprint.request');
|
|
136
147
|
const res = await this.fetchImpl(`${cfg.apiBaseUrl}/deferred/claim`, {
|
|
137
148
|
method: 'POST',
|
|
138
149
|
headers: this.buildHeaders(true),
|
|
139
150
|
body: JSON.stringify(body),
|
|
140
151
|
});
|
|
141
152
|
if (!res.ok) {
|
|
153
|
+
this.logDebug('deferred.fingerprint.http_error', { status: res.status });
|
|
142
154
|
return null;
|
|
143
155
|
}
|
|
144
156
|
const payload = await this.parsePayload(res);
|
|
145
157
|
if (payload) {
|
|
158
|
+
this.logDebug('deferred.fingerprint.payload', { linkId: payload.linkId, duplicate: (payload as any)?.duplicate });
|
|
146
159
|
this.emit(payload);
|
|
160
|
+
} else {
|
|
161
|
+
this.logDebug('deferred.fingerprint.no_match');
|
|
147
162
|
}
|
|
148
163
|
return payload;
|
|
149
|
-
} catch {
|
|
164
|
+
} catch (err) {
|
|
165
|
+
this.logDebug('deferred.fingerprint.error', { message: err instanceof Error ? err.message : String(err) });
|
|
150
166
|
return null;
|
|
151
167
|
}
|
|
152
168
|
}
|
|
@@ -154,26 +170,35 @@ class LinkMeController {
|
|
|
154
170
|
private async tryClaimFromPasteboard(cfg: NormalizedConfig): Promise<LinkMePayload | null> {
|
|
155
171
|
try {
|
|
156
172
|
if (!Clipboard?.getStringAsync) {
|
|
173
|
+
this.logDebug('pasteboard.skip_module');
|
|
157
174
|
return null;
|
|
158
175
|
}
|
|
176
|
+
this.logDebug('pasteboard.read');
|
|
159
177
|
const pasteStr = await Clipboard.getStringAsync();
|
|
160
178
|
if (!pasteStr) {
|
|
179
|
+
this.logDebug('pasteboard.empty');
|
|
161
180
|
return null;
|
|
162
181
|
}
|
|
163
182
|
// Check if the clipboard contains a li-nk.me URL with a cid parameter
|
|
164
183
|
const cid = this.extractCidFromUrl(pasteStr, cfg.baseUrl);
|
|
165
184
|
if (!cid) {
|
|
185
|
+
this.logDebug('pasteboard.no_cid', { hasClipboard: true });
|
|
166
186
|
return null;
|
|
167
187
|
}
|
|
188
|
+
this.logDebug('pasteboard.cid_found');
|
|
168
189
|
// Resolve the CID to get the payload
|
|
169
190
|
const payload = await this.resolveCidWithConfig(cfg, cid);
|
|
170
191
|
if (payload) {
|
|
171
192
|
this.emit(payload);
|
|
172
193
|
// Track pasteboard claim
|
|
173
|
-
this.
|
|
194
|
+
this.logDebug('pasteboard.payload', { linkId: payload.linkId });
|
|
195
|
+
void this.track('claim', { claim_type: 'pasteboard' });
|
|
196
|
+
} else {
|
|
197
|
+
this.logDebug('pasteboard.resolve_empty');
|
|
174
198
|
}
|
|
175
199
|
return payload;
|
|
176
|
-
} catch {
|
|
200
|
+
} catch (err) {
|
|
201
|
+
this.logDebug('pasteboard.error', { message: err instanceof Error ? err.message : String(err) });
|
|
177
202
|
return null;
|
|
178
203
|
}
|
|
179
204
|
}
|
|
@@ -184,12 +209,19 @@ class LinkMeController {
|
|
|
184
209
|
// Check if the URL is from our domain
|
|
185
210
|
const baseHost = new URL(baseUrl).host;
|
|
186
211
|
if (!url.host.endsWith(baseHost) && url.host !== baseHost.replace(/^www\./, '')) {
|
|
212
|
+
this.logDebug('pasteboard.url_mismatch', { host: url.host });
|
|
187
213
|
return null;
|
|
188
214
|
}
|
|
189
215
|
// Extract the cid parameter
|
|
190
216
|
const cid = url.searchParams.get('cid');
|
|
217
|
+
if (cid) {
|
|
218
|
+
this.logDebug('pasteboard.url_cid_present');
|
|
219
|
+
} else {
|
|
220
|
+
this.logDebug('pasteboard.url_no_cid');
|
|
221
|
+
}
|
|
191
222
|
return cid || null;
|
|
192
|
-
} catch {
|
|
223
|
+
} catch (err) {
|
|
224
|
+
this.logDebug('pasteboard.url_parse_error', { message: err instanceof Error ? err.message : String(err) });
|
|
193
225
|
return null;
|
|
194
226
|
}
|
|
195
227
|
}
|
|
@@ -245,10 +277,13 @@ class LinkMeController {
|
|
|
245
277
|
body: JSON.stringify(body),
|
|
246
278
|
});
|
|
247
279
|
if (!res.ok) {
|
|
248
|
-
await res.text().catch(() => undefined);
|
|
280
|
+
const text = await res.text().catch(() => undefined);
|
|
281
|
+
this.logDebug('app_events.http_error', { status: res.status, body: text });
|
|
282
|
+
console.warn('[LinkMe SDK] app_events.http_error', { status: res.status, body: text });
|
|
249
283
|
}
|
|
250
284
|
} catch {
|
|
251
|
-
|
|
285
|
+
this.logDebug('app_events.network_error');
|
|
286
|
+
console.warn('[LinkMe SDK] app_events.network_error');
|
|
252
287
|
}
|
|
253
288
|
}
|
|
254
289
|
|
|
@@ -436,7 +471,8 @@ class LinkMeController {
|
|
|
436
471
|
try {
|
|
437
472
|
const json = (await res.json()) as LinkMePayload;
|
|
438
473
|
return json ?? null;
|
|
439
|
-
} catch {
|
|
474
|
+
} catch (err) {
|
|
475
|
+
this.logDebug('payload.parse_error', { message: err instanceof Error ? err.message : String(err) });
|
|
440
476
|
return null;
|
|
441
477
|
}
|
|
442
478
|
}
|
|
@@ -451,6 +487,21 @@ class LinkMeController {
|
|
|
451
487
|
}
|
|
452
488
|
}
|
|
453
489
|
}
|
|
490
|
+
|
|
491
|
+
private logDebug(event: string, details?: Record<string, any>): void {
|
|
492
|
+
if (!this.debug) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
if (details) {
|
|
497
|
+
console.log(`[LinkMe SDK] ${event}`, details);
|
|
498
|
+
} else {
|
|
499
|
+
console.log(`[LinkMe SDK] ${event}`);
|
|
500
|
+
}
|
|
501
|
+
} catch {
|
|
502
|
+
/* noop */
|
|
503
|
+
}
|
|
504
|
+
}
|
|
454
505
|
}
|
|
455
506
|
|
|
456
507
|
function normalizeConfig(config: LinkMeConfig): NormalizedConfig {
|