@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.
Files changed (2) hide show
  1. package/dist/index.js +132 -25
  2. 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
- const urlObj = new URL(url);
128
- const hostname = urlObj.hostname;
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
- // Check host patterns
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
- if (this.matchesHost(hostname, host)) {
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
- if (hostname === pattern || hostname.endsWith('.' + pattern)) {
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
- const sensitiveKeys = ['privateKey', 'mnemonic', 'password', 'secret', 'token', 'apiKey', 'authorization'];
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
- const isSensitive = sensitiveKeys.some(sk => lowerKey.includes(sk.toLowerCase()));
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
- const url = config.url || (config.baseURL ? `${config.baseURL}${config.url || ''}` : '');
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
- const url = response.config?.url || response.request?.responseURL || '';
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
- const url = config.url || error.request?.responseURL || '';
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',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lavarage/telemetry",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Production telemetry SDK for Lavarage and partner applications",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",