@lavarage/telemetry 1.2.0 → 1.2.2
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/dist/index.js +132 -25
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -43,6 +43,19 @@ class LavarageTelemetry {
|
|
|
43
43
|
}
|
|
44
44
|
try {
|
|
45
45
|
const win = window;
|
|
46
|
+
// Check for Solana wallets FIRST (since they're more specific and users might have both)
|
|
47
|
+
if (win.solana) {
|
|
48
|
+
// Phantom
|
|
49
|
+
if (win.solana.isPhantom) {
|
|
50
|
+
return 'Phantom';
|
|
51
|
+
}
|
|
52
|
+
// Solflare
|
|
53
|
+
if (win.solana.isSolflare) {
|
|
54
|
+
return 'Solflare';
|
|
55
|
+
}
|
|
56
|
+
// Generic Solana
|
|
57
|
+
return 'Solana';
|
|
58
|
+
}
|
|
46
59
|
// Check for Ethereum wallets
|
|
47
60
|
if (win.ethereum) {
|
|
48
61
|
// MetaMask
|
|
@@ -80,19 +93,6 @@ class LavarageTelemetry {
|
|
|
80
93
|
// Default to generic Ethereum
|
|
81
94
|
return 'Ethereum';
|
|
82
95
|
}
|
|
83
|
-
// Check for Solana wallets
|
|
84
|
-
if (win.solana) {
|
|
85
|
-
// Phantom
|
|
86
|
-
if (win.solana.isPhantom) {
|
|
87
|
-
return 'Phantom';
|
|
88
|
-
}
|
|
89
|
-
// Solflare
|
|
90
|
-
if (win.solana.isSolflare) {
|
|
91
|
-
return 'Solflare';
|
|
92
|
-
}
|
|
93
|
-
// Generic Solana
|
|
94
|
-
return 'Solana';
|
|
95
|
-
}
|
|
96
96
|
// Check for other wallet providers
|
|
97
97
|
if (win.web3) {
|
|
98
98
|
return 'Web3';
|
|
@@ -124,16 +124,57 @@ class LavarageTelemetry {
|
|
|
124
124
|
}
|
|
125
125
|
shouldCaptureHost(url) {
|
|
126
126
|
try {
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
// Skip empty or invalid URLs
|
|
128
|
+
if (!url || url.trim() === '') {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
// Resolve relative URLs to absolute URLs
|
|
132
|
+
let absoluteUrl;
|
|
133
|
+
try {
|
|
134
|
+
// Try to parse as absolute URL first
|
|
135
|
+
new URL(url);
|
|
136
|
+
absoluteUrl = url;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// If it fails, it's likely a relative URL - resolve it using current origin
|
|
140
|
+
if (typeof window !== 'undefined' && window.location) {
|
|
141
|
+
try {
|
|
142
|
+
absoluteUrl = new URL(url, window.location.origin).href;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Still can't resolve, don't capture
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// Not in browser environment, can't resolve relative URLs
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const urlObj = new URL(absoluteUrl);
|
|
155
|
+
let hostname = urlObj.hostname;
|
|
156
|
+
// Handle special URL types that don't have a hostname (data:, blob:, etc.)
|
|
157
|
+
if (!hostname || hostname === '') {
|
|
158
|
+
// For include mode, don't capture URLs without hostnames
|
|
159
|
+
// For exclude mode, these would be captured (but they're unlikely to be in exclude list)
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
// Remove port number if present (hostname property should already exclude it, but be defensive)
|
|
163
|
+
// Also normalize: lowercase and trim
|
|
164
|
+
hostname = hostname.split(':')[0].toLowerCase().trim();
|
|
129
165
|
const { mode, hosts = [], patterns = [] } = this.hostFilter;
|
|
130
166
|
if (mode === 'all')
|
|
131
167
|
return true;
|
|
132
168
|
if (mode === 'none')
|
|
133
169
|
return false;
|
|
134
|
-
//
|
|
170
|
+
// In include mode, if no hosts or patterns are specified, don't capture anything
|
|
171
|
+
if (mode === 'include' && hosts.length === 0 && patterns.length === 0) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
// Check host patterns (normalize them too)
|
|
135
175
|
for (const host of hosts) {
|
|
136
|
-
|
|
176
|
+
const normalizedHost = host.toLowerCase().trim();
|
|
177
|
+
if (this.matchesHost(hostname, normalizedHost)) {
|
|
137
178
|
return mode === 'include';
|
|
138
179
|
}
|
|
139
180
|
}
|
|
@@ -159,16 +200,26 @@ class LavarageTelemetry {
|
|
|
159
200
|
}
|
|
160
201
|
}
|
|
161
202
|
matchesHost(hostname, pattern) {
|
|
203
|
+
// Normalize inputs (should already be normalized, but be defensive)
|
|
204
|
+
hostname = hostname.toLowerCase().trim();
|
|
205
|
+
pattern = pattern.toLowerCase().trim();
|
|
162
206
|
// Exact match
|
|
163
207
|
if (hostname === pattern)
|
|
164
208
|
return true;
|
|
165
209
|
// Wildcard subdomain: *.example.com
|
|
166
210
|
if (pattern.startsWith('*.')) {
|
|
167
|
-
const domain = pattern.substring(2);
|
|
211
|
+
const domain = pattern.substring(2).toLowerCase().trim();
|
|
212
|
+
// Match exact domain or any subdomain
|
|
168
213
|
return hostname === domain || hostname.endsWith('.' + domain);
|
|
169
214
|
}
|
|
170
215
|
// Domain match (matches domain and all subdomains)
|
|
171
|
-
|
|
216
|
+
// e.g., 'lavarave.wtf' matches 'lavarave.wtf' and 'api.lavarave.wtf'
|
|
217
|
+
if (hostname === pattern) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
// Check if hostname is a subdomain of pattern
|
|
221
|
+
// e.g., 'api.lavarave.wtf' ends with '.lavarave.wtf'
|
|
222
|
+
if (hostname.endsWith('.' + pattern)) {
|
|
172
223
|
return true;
|
|
173
224
|
}
|
|
174
225
|
return false;
|
|
@@ -220,10 +271,27 @@ class LavarageTelemetry {
|
|
|
220
271
|
}
|
|
221
272
|
if (typeof data === 'object') {
|
|
222
273
|
const sanitized = {};
|
|
223
|
-
|
|
274
|
+
// Exact matches for sensitive keys
|
|
275
|
+
const exactSensitiveKeys = ['privateKey', 'mnemonic', 'password', 'secret', 'apiKey', 'authorization'];
|
|
276
|
+
// Patterns that indicate sensitive tokens (but not identifiers like baseToken, quoteToken)
|
|
277
|
+
const sensitiveTokenPatterns = [
|
|
278
|
+
/^access[_-]?token$/i,
|
|
279
|
+
/^auth[_-]?token$/i,
|
|
280
|
+
/^api[_-]?token$/i,
|
|
281
|
+
/^bearer[_-]?token$/i,
|
|
282
|
+
/^refresh[_-]?token$/i,
|
|
283
|
+
/^session[_-]?token$/i,
|
|
284
|
+
/^token$/i, // Only exact match for "token"
|
|
285
|
+
];
|
|
224
286
|
for (const [key, value] of Object.entries(data)) {
|
|
225
287
|
const lowerKey = key.toLowerCase();
|
|
226
|
-
|
|
288
|
+
// Check for exact matches first
|
|
289
|
+
const isExactMatch = exactSensitiveKeys.some(sk => lowerKey === sk.toLowerCase());
|
|
290
|
+
// Check for sensitive token patterns (but exclude common non-sensitive patterns)
|
|
291
|
+
const isSensitiveToken = sensitiveTokenPatterns.some(pattern => pattern.test(key));
|
|
292
|
+
// Exclude common non-sensitive patterns that contain "token"
|
|
293
|
+
const isNonSensitiveToken = /^(base|quote|tokenId|tokenAddress|tokenSymbol|tokenName|tokenUri|tokenType|tokenContract|tokenDecimals)/i.test(key);
|
|
294
|
+
const isSensitive = isExactMatch || (isSensitiveToken && !isNonSensitiveToken);
|
|
227
295
|
if (isSensitive) {
|
|
228
296
|
sanitized[key] = '[REDACTED]';
|
|
229
297
|
}
|
|
@@ -502,16 +570,53 @@ class LavarageTelemetry {
|
|
|
502
570
|
}
|
|
503
571
|
// Request interceptor
|
|
504
572
|
axiosInstance.interceptors.request.use((config) => {
|
|
505
|
-
|
|
573
|
+
// Construct full URL from axios config
|
|
574
|
+
let url;
|
|
575
|
+
try {
|
|
576
|
+
if (config.url) {
|
|
577
|
+
// If url is absolute (starts with http:// or https://), use it directly
|
|
578
|
+
if (config.url.startsWith('http://') || config.url.startsWith('https://')) {
|
|
579
|
+
url = config.url;
|
|
580
|
+
}
|
|
581
|
+
else if (config.baseURL) {
|
|
582
|
+
// Relative URL with baseURL - use URL constructor to properly combine them
|
|
583
|
+
try {
|
|
584
|
+
url = new URL(config.url, config.baseURL).href;
|
|
585
|
+
}
|
|
586
|
+
catch {
|
|
587
|
+
// Fallback to manual concatenation if URL constructor fails
|
|
588
|
+
const base = config.baseURL.endsWith('/') ? config.baseURL.slice(0, -1) : config.baseURL;
|
|
589
|
+
const path = config.url.startsWith('/') ? config.url : '/' + config.url;
|
|
590
|
+
url = base + path;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
// Relative URL without baseURL - will be resolved in shouldCaptureHost
|
|
595
|
+
url = config.url;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
else if (config.baseURL) {
|
|
599
|
+
url = config.baseURL;
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
// No URL and no baseURL - skip capture
|
|
603
|
+
return config;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
catch {
|
|
607
|
+
// If URL construction fails, skip capture
|
|
608
|
+
return config;
|
|
609
|
+
}
|
|
506
610
|
const method = config.method?.toUpperCase() || 'GET';
|
|
507
611
|
if (!this.shouldCaptureHost(url)) {
|
|
508
612
|
return config;
|
|
509
613
|
}
|
|
510
614
|
const requestId = this.generateRequestId();
|
|
511
615
|
const startTime = Date.now();
|
|
512
|
-
// Store request metadata
|
|
616
|
+
// Store request metadata (including full URL for response interceptor)
|
|
513
617
|
config._telemetryRequestId = requestId;
|
|
514
618
|
config._telemetryStartTime = startTime;
|
|
619
|
+
config._telemetryUrl = url; // Store full URL for response interceptor
|
|
515
620
|
// Track request start
|
|
516
621
|
this.enqueue({
|
|
517
622
|
type: 'request',
|
|
@@ -535,7 +640,8 @@ class LavarageTelemetry {
|
|
|
535
640
|
const startTime = config._telemetryStartTime;
|
|
536
641
|
if (requestId && startTime) {
|
|
537
642
|
const duration = Date.now() - startTime;
|
|
538
|
-
|
|
643
|
+
// Use stored URL from request interceptor, fallback to response URL
|
|
644
|
+
const url = config._telemetryUrl || response.config?.url || response.request?.responseURL || '';
|
|
539
645
|
this.enqueue({
|
|
540
646
|
type: 'request',
|
|
541
647
|
wallet: this.wallet,
|
|
@@ -557,7 +663,8 @@ class LavarageTelemetry {
|
|
|
557
663
|
const startTime = config._telemetryStartTime;
|
|
558
664
|
if (requestId && startTime) {
|
|
559
665
|
const duration = Date.now() - startTime;
|
|
560
|
-
|
|
666
|
+
// Use stored URL from request interceptor, fallback to error config URL
|
|
667
|
+
const url = config._telemetryUrl || config.url || error.request?.responseURL || '';
|
|
561
668
|
const errorMessage = error.message || 'Request failed';
|
|
562
669
|
this.enqueue({
|
|
563
670
|
type: 'request',
|