@soham20/smart-offline-sdk 1.0.0 → 1.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soham20/smart-offline-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Smart offline-first JavaScript SDK with intelligent caching for web applications",
5
5
  "main": "./src/index.cjs",
6
6
  "module": "./src/index.js",
@@ -20,6 +20,141 @@ let SDK_CONFIG = {
20
20
  enableDetailedLogs: false
21
21
  };
22
22
 
23
+ /**
24
+ * -------- CONSOLE LOGGING UTILITIES --------
25
+ * Rich, colorful debug logs for caching activity
26
+ */
27
+ const LOG_STYLES = {
28
+ banner: 'background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; font-size: 14px; padding: 8px 16px; border-radius: 4px; font-weight: bold;',
29
+ cached: 'background: #10B981; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;',
30
+ skipped: 'background: #F59E0B; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;',
31
+ served: 'background: #3B82F6; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;',
32
+ error: 'background: #EF4444; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;',
33
+ intercept: 'background: #8B5CF6; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;',
34
+ url: 'color: #6366F1; font-weight: bold;',
35
+ reason: 'color: #059669; font-style: italic;',
36
+ metadata: 'color: #6B7280;',
37
+ priority_high: 'background: #DC2626; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;',
38
+ priority_normal: 'background: #9CA3AF; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;',
39
+ type_api: 'background: #7C3AED; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;',
40
+ type_page: 'background: #2563EB; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;'
41
+ };
42
+
43
+ function formatBytes(bytes) {
44
+ if (bytes === 0 || !bytes) return '0 B';
45
+ const k = 1024;
46
+ const sizes = ['B', 'KB', 'MB', 'GB'];
47
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
48
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
49
+ }
50
+
51
+ function formatTimestamp(ts) {
52
+ return new Date(ts).toLocaleTimeString();
53
+ }
54
+
55
+ /**
56
+ * Print rich cache log to console
57
+ */
58
+ function printCacheLog(action, url, reason, details = {}) {
59
+ if (!SDK_CONFIG.debug) return;
60
+
61
+ const time = formatTimestamp(Date.now());
62
+ const shortUrl = url.length > 60 ? url.substring(0, 57) + '...' : url;
63
+
64
+ switch (action) {
65
+ case 'CACHED':
66
+ console.groupCollapsed(
67
+ `%cšŸ’¾ CACHED %c${shortUrl}`,
68
+ LOG_STYLES.cached,
69
+ LOG_STYLES.url
70
+ );
71
+ console.log(`%cā° Time: %c${time}`, 'font-weight: bold;', 'color: #6B7280;');
72
+ console.log(`%cšŸ“ URL: %c${url}`, 'font-weight: bold;', LOG_STYLES.url);
73
+ console.log(`%cšŸ“ Reason: %c${reason}`, 'font-weight: bold;', LOG_STYLES.reason);
74
+ if (details.type) {
75
+ console.log(`%cšŸ“¦ Type: %c${details.type}`, 'font-weight: bold;', details.type === 'API' ? LOG_STYLES.type_api : LOG_STYLES.type_page);
76
+ }
77
+ if (details.priority) {
78
+ console.log(`%c⚔ Priority: %c${details.priority.toUpperCase()}`, 'font-weight: bold;', details.priority === 'high' ? LOG_STYLES.priority_high : LOG_STYLES.priority_normal);
79
+ }
80
+ if (details.size) {
81
+ console.log(`%cšŸ“Š Size: %c${formatBytes(details.size)}`, 'font-weight: bold;', 'color: #6B7280;');
82
+ }
83
+ if (details.usage) {
84
+ console.log(`%cšŸ“ˆ Usage Stats:`, 'font-weight: bold;', details.usage);
85
+ }
86
+ console.log('%cāœ… Object cached successfully', 'color: #10B981; font-weight: bold;');
87
+ console.groupEnd();
88
+ break;
89
+
90
+ case 'SKIPPED':
91
+ console.groupCollapsed(
92
+ `%cā­ļø SKIPPED %c${shortUrl}`,
93
+ LOG_STYLES.skipped,
94
+ LOG_STYLES.url
95
+ );
96
+ console.log(`%cā° Time: %c${time}`, 'font-weight: bold;', 'color: #6B7280;');
97
+ console.log(`%cšŸ“ URL: %c${url}`, 'font-weight: bold;', LOG_STYLES.url);
98
+ console.log(`%c🚫 Reason: %c${reason}`, 'font-weight: bold;', 'color: #F59E0B; font-weight: bold;');
99
+ if (details.size && details.limit) {
100
+ console.log(`%cšŸ“Š Size: %c${formatBytes(details.size)} (limit: ${formatBytes(details.limit)})`, 'font-weight: bold;', 'color: #EF4444;');
101
+ }
102
+ if (details.networkQuality) {
103
+ console.log(`%c🌐 Network: %c${details.networkQuality}`, 'font-weight: bold;', 'color: #6B7280;');
104
+ }
105
+ if (details.priority) {
106
+ console.log(`%c⚔ Priority: %c${details.priority.toUpperCase()}`, 'font-weight: bold;', details.priority === 'high' ? LOG_STYLES.priority_high : LOG_STYLES.priority_normal);
107
+ }
108
+ console.log('%cāš ļø Object NOT cached', 'color: #F59E0B; font-weight: bold;');
109
+ console.groupEnd();
110
+ break;
111
+
112
+ case 'SERVED':
113
+ console.groupCollapsed(
114
+ `%cšŸ“¤ SERVED FROM CACHE %c${shortUrl}`,
115
+ LOG_STYLES.served,
116
+ LOG_STYLES.url
117
+ );
118
+ console.log(`%cā° Time: %c${time}`, 'font-weight: bold;', 'color: #6B7280;');
119
+ console.log(`%cšŸ“ URL: %c${url}`, 'font-weight: bold;', LOG_STYLES.url);
120
+ console.log(`%cšŸ“ Reason: %c${reason}`, 'font-weight: bold;', LOG_STYLES.reason);
121
+ if (details.priority) {
122
+ console.log(`%c⚔ Priority: %c${details.priority.toUpperCase()}`, 'font-weight: bold;', details.priority === 'high' ? LOG_STYLES.priority_high : LOG_STYLES.priority_normal);
123
+ }
124
+ if (details.usage) {
125
+ console.log(`%cšŸ“ˆ Usage Stats:`, 'font-weight: bold;', details.usage);
126
+ }
127
+ console.log('%cšŸ”„ Served from offline cache', 'color: #3B82F6; font-weight: bold;');
128
+ console.groupEnd();
129
+ break;
130
+
131
+ case 'ERROR':
132
+ console.groupCollapsed(
133
+ `%cāŒ CACHE MISS %c${shortUrl}`,
134
+ LOG_STYLES.error,
135
+ LOG_STYLES.url
136
+ );
137
+ console.log(`%cā° Time: %c${time}`, 'font-weight: bold;', 'color: #6B7280;');
138
+ console.log(`%cšŸ“ URL: %c${url}`, 'font-weight: bold;', LOG_STYLES.url);
139
+ console.log(`%c🚫 Reason: %c${reason}`, 'font-weight: bold;', 'color: #EF4444; font-weight: bold;');
140
+ if (details.priority) {
141
+ console.log(`%c⚔ Priority: %c${details.priority.toUpperCase()}`, 'font-weight: bold;', details.priority === 'high' ? LOG_STYLES.priority_high : LOG_STYLES.priority_normal);
142
+ }
143
+ console.log('%cāŒ Resource not available offline', 'color: #EF4444; font-weight: bold;');
144
+ console.groupEnd();
145
+ break;
146
+
147
+ case 'INTERCEPT':
148
+ console.log(
149
+ `%cšŸ” INTERCEPT %c${shortUrl} %c[${details.isAPI ? 'API' : 'PAGE'}]`,
150
+ LOG_STYLES.intercept,
151
+ LOG_STYLES.url,
152
+ details.isAPI ? LOG_STYLES.type_api : LOG_STYLES.type_page
153
+ );
154
+ break;
155
+ }
156
+ }
157
+
23
158
  /**
24
159
  * Receive config from SDK
25
160
  */
@@ -44,9 +179,27 @@ self.addEventListener("message", (event) => {
44
179
  });
45
180
 
46
181
  /**
47
- * Log cache events to IndexedDB
182
+ * Log cache events to IndexedDB and send to main thread
48
183
  */
49
184
  function logEvent(type, url, reason, metadata = {}) {
185
+ const eventData = {
186
+ type: `CACHE_${type.toUpperCase()}`,
187
+ url,
188
+ reason,
189
+ metadata,
190
+ timestamp: Date.now()
191
+ };
192
+
193
+ // Always send to main thread when debug or enableDetailedLogs is on
194
+ if (SDK_CONFIG.debug || SDK_CONFIG.enableDetailedLogs) {
195
+ self.clients.matchAll().then(clients => {
196
+ clients.forEach(client => {
197
+ client.postMessage(eventData);
198
+ });
199
+ });
200
+ }
201
+
202
+ // Store in IndexedDB only if enableDetailedLogs is on
50
203
  if (!SDK_CONFIG.enableDetailedLogs) return;
51
204
 
52
205
  const request = indexedDB.open("smart-offline-logs", 1);
@@ -72,19 +225,6 @@ function logEvent(type, url, reason, metadata = {}) {
72
225
  date: new Date().toISOString()
73
226
  });
74
227
  };
75
-
76
- // Also send to main thread
77
- self.clients.matchAll().then(clients => {
78
- clients.forEach(client => {
79
- client.postMessage({
80
- type: `CACHE_${type.toUpperCase()}`,
81
- url,
82
- reason,
83
- metadata,
84
- timestamp: Date.now()
85
- });
86
- });
87
- });
88
228
  }
89
229
 
90
230
  /**
@@ -250,9 +390,8 @@ self.addEventListener("fetch", (event) => {
250
390
 
251
391
  if (!isPage && !isAPI) return;
252
392
 
253
- if (SDK_CONFIG.debug) {
254
- console.log("[SW] Intercepted:", request.url);
255
- }
393
+ // Log interception event
394
+ printCacheLog('INTERCEPT', request.url, 'URL pattern matched', { isPage, isAPI });
256
395
 
257
396
  event.respondWith(
258
397
  fetch(request)
@@ -269,12 +408,11 @@ self.addEventListener("fetch", (event) => {
269
408
  limit: SDK_CONFIG.maxResourceSize
270
409
  });
271
410
 
272
- if (SDK_CONFIG.debug) {
273
- console.log(
274
- `[SmartOffline] Skipped caching (size ${size} > ${SDK_CONFIG.maxResourceSize}):`,
275
- request.url
276
- );
277
- }
411
+ printCacheLog('SKIPPED', request.url, 'Resource too large (exceeds maxResourceSize)', {
412
+ size,
413
+ limit: SDK_CONFIG.maxResourceSize
414
+ });
415
+
278
416
  return response;
279
417
  }
280
418
 
@@ -289,12 +427,11 @@ self.addEventListener("fetch", (event) => {
289
427
  priority: 'low'
290
428
  });
291
429
 
292
- if (SDK_CONFIG.debug) {
293
- console.log(
294
- `[SmartOffline] Skipped caching (slow network, not high priority):`,
295
- request.url
296
- );
297
- }
430
+ printCacheLog('SKIPPED', request.url, 'Slow network detected - skipping low priority resource', {
431
+ networkQuality: netQuality,
432
+ priority: 'low'
433
+ });
434
+
298
435
  return response;
299
436
  }
300
437
 
@@ -308,12 +445,13 @@ self.addEventListener("fetch", (event) => {
308
445
  });
309
446
  });
310
447
 
311
- if (SDK_CONFIG.debug) {
312
- console.log(
313
- `[SmartOffline] Cached ${isAPI ? "API" : "PAGE"}:`,
314
- request.url
315
- );
316
- }
448
+ // Rich console log for successful cache
449
+ printCacheLog('CACHED', request.url, 'Successfully fetched from network and cached', {
450
+ type: isAPI ? 'API' : 'PAGE',
451
+ size,
452
+ priority: highPriority ? 'high' : 'normal',
453
+ usage: usage ? { accessCount: usage.count, lastAccessed: new Date(usage.lastAccessed).toLocaleString() } : { accessCount: 1, lastAccessed: 'Now (first access)' }
454
+ });
317
455
 
318
456
  return response;
319
457
  })
@@ -331,16 +469,18 @@ self.addEventListener("fetch", (event) => {
331
469
  usage: usage ? { count: usage.count, lastAccessed: usage.lastAccessed } : null
332
470
  });
333
471
 
334
- if (SDK_CONFIG.debug) {
335
- console.log(
336
- `[SmartOffline] Served from cache (${highPriority ? "HIGH" : "NORMAL"} priority):`,
337
- request.url
338
- );
339
- }
472
+ printCacheLog('SERVED', request.url, 'Network unavailable - serving from cache', {
473
+ priority: highPriority ? 'high' : 'normal',
474
+ usage: usage ? { accessCount: usage.count, lastAccessed: new Date(usage.lastAccessed).toLocaleString() } : null
475
+ });
340
476
  } else {
341
477
  logEvent('error', request.url, 'cache_miss_offline', {
342
478
  priority: highPriority ? 'high' : 'normal'
343
479
  });
480
+
481
+ printCacheLog('ERROR', request.url, 'Network unavailable and resource not in cache', {
482
+ priority: highPriority ? 'high' : 'normal'
483
+ });
344
484
  }
345
485
 
346
486
  return cached;
package/src/index.js CHANGED
@@ -92,7 +92,8 @@ export async function setupSmartOffline(config = {}) {
92
92
  try {
93
93
  // Register service worker
94
94
  if (currentConfig.debug) {
95
- console.log("[SmartOffline] Registering service worker...");
95
+ printStartupBanner();
96
+ console.log("%cšŸ”§ Registering service worker...", "color: #94a3b8;");
96
97
  }
97
98
 
98
99
  serviceWorkerRegistration = await navigator.serviceWorker.register(
@@ -102,8 +103,8 @@ export async function setupSmartOffline(config = {}) {
102
103
 
103
104
  if (currentConfig.debug) {
104
105
  console.log(
105
- "[SmartOffline] Service worker registered:",
106
- serviceWorkerRegistration.scope,
106
+ "%cāœ“ Service worker registered", "color: #22c55e;",
107
+ `(scope: ${serviceWorkerRegistration.scope})`
107
108
  );
108
109
  }
109
110
 
@@ -111,7 +112,7 @@ export async function setupSmartOffline(config = {}) {
111
112
  await navigator.serviceWorker.ready;
112
113
 
113
114
  if (currentConfig.debug) {
114
- console.log("[SmartOffline] Service worker ready");
115
+ console.log("%cāœ“ Service worker ready", "color: #22c55e;");
115
116
  }
116
117
 
117
118
  // Send configuration to service worker
@@ -123,12 +124,7 @@ export async function setupSmartOffline(config = {}) {
123
124
  isInitialized = true;
124
125
 
125
126
  if (currentConfig.debug) {
126
- console.log("[SmartOffline] Setup complete! Configuration:", {
127
- pages: currentConfig.pages,
128
- apis: currentConfig.apis,
129
- frequencyThreshold: currentConfig.frequencyThreshold,
130
- recencyThreshold: `${currentConfig.recencyThreshold / (60 * 60 * 1000)}h`,
131
- });
127
+ printConfigSummary();
132
128
  }
133
129
 
134
130
  return { success: true, registration: serviceWorkerRegistration };
@@ -145,6 +141,108 @@ export async function setupSmartOffline(config = {}) {
145
141
  // HELPER FUNCTIONS
146
142
  // ============================================================================
147
143
 
144
+ /**
145
+ * Print startup banner
146
+ */
147
+ function printStartupBanner() {
148
+ console.log(
149
+ `%c
150
+ ╔═══════════════════════════════════════════════════════════╗
151
+ ā•‘ ā•‘
152
+ ā•‘ šŸš€ SmartOffline SDK v1.0.0 ā•‘
153
+ ā•‘ Intelligent offline-first caching ā•‘
154
+ ā•‘ ā•‘
155
+ ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•
156
+ `,
157
+ "color: #3b82f6; font-weight: bold;"
158
+ );
159
+ }
160
+
161
+ /**
162
+ * Print configuration summary
163
+ */
164
+ function printConfigSummary() {
165
+ console.log(
166
+ `%c━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
167
+ "color: #475569;"
168
+ );
169
+ console.log(
170
+ `%cšŸ“‹ Configuration Summary`,
171
+ "color: #22c55e; font-weight: bold; font-size: 14px;"
172
+ );
173
+ console.log(
174
+ `%c━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
175
+ "color: #475569;"
176
+ );
177
+
178
+ // Pages
179
+ console.log("%cšŸ“„ Pages to cache:", "color: #60a5fa; font-weight: bold;");
180
+ if (currentConfig.pages.length > 0) {
181
+ currentConfig.pages.forEach((p) =>
182
+ console.log(` %c• ${p}`, "color: #94a3b8;")
183
+ );
184
+ } else {
185
+ console.log(" %c(none)", "color: #64748b; font-style: italic;");
186
+ }
187
+
188
+ // APIs
189
+ console.log("%cšŸ”Œ APIs to cache:", "color: #60a5fa; font-weight: bold;");
190
+ if (currentConfig.apis.length > 0) {
191
+ currentConfig.apis.forEach((a) =>
192
+ console.log(` %c• ${a}`, "color: #94a3b8;")
193
+ );
194
+ } else {
195
+ console.log(" %c(none)", "color: #64748b; font-style: italic;");
196
+ }
197
+
198
+ // Priority settings
199
+ console.log("%c⚔ Priority settings:", "color: #60a5fa; font-weight: bold;");
200
+ console.log(
201
+ ` %cFrequency threshold: ${currentConfig.frequencyThreshold} accesses`,
202
+ "color: #94a3b8;"
203
+ );
204
+ console.log(
205
+ ` %cRecency threshold: ${currentConfig.recencyThreshold / (60 * 60 * 1000)}h`,
206
+ "color: #94a3b8;"
207
+ );
208
+ console.log(
209
+ ` %cMax resource size: ${formatBytes(currentConfig.maxResourceSize)}`,
210
+ "color: #94a3b8;"
211
+ );
212
+ console.log(
213
+ ` %cNetwork quality: ${currentConfig.networkQuality}`,
214
+ "color: #94a3b8;"
215
+ );
216
+
217
+ // Significance overrides
218
+ const sigKeys = Object.keys(currentConfig.significance || {});
219
+ if (sigKeys.length > 0) {
220
+ console.log(
221
+ "%cšŸŽÆ Significance overrides:",
222
+ "color: #60a5fa; font-weight: bold;"
223
+ );
224
+ sigKeys.forEach((key) => {
225
+ const val = currentConfig.significance[key];
226
+ const icon = val === "high" ? "šŸ”“" : val === "low" ? "šŸ”µ" : "⚪";
227
+ console.log(` %c${icon} ${key} → ${val}`, "color: #94a3b8;");
228
+ });
229
+ }
230
+
231
+ console.log(
232
+ `%c━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
233
+ "color: #475569;"
234
+ );
235
+ console.log(
236
+ "%cāœ… SmartOffline is now active! Cache events will appear below.",
237
+ "color: #22c55e; font-weight: bold;"
238
+ );
239
+ console.log(
240
+ `%c━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
241
+ "color: #475569;"
242
+ );
243
+ console.log(""); // Empty line for spacing
244
+ }
245
+
148
246
  async function sendConfigToServiceWorker() {
149
247
  const controller = navigator.serviceWorker.controller;
150
248
 
@@ -202,29 +300,134 @@ function setupEventListenersInternal() {
202
300
  const listeners = eventListeners[eventType] || [];
203
301
  listeners.forEach((fn) => fn(cacheEvent));
204
302
 
205
- // Debug logging
303
+ // Rich debug logging with colors
206
304
  if (currentConfig.debug) {
207
- const icon = {
208
- CACHE_CACHE: "šŸ’¾",
209
- CACHE_SKIP: "ā­ļø",
210
- CACHE_SERVE: "šŸ“¤",
211
- CACHE_ERROR: "āŒ",
212
- };
213
-
214
- console.log(
215
- `[SmartOffline] ${icon[cacheEvent.type] || "šŸ“"} ${cacheEvent.type.replace("CACHE_", "")}:`,
216
- cacheEvent.url.replace(
217
- typeof window !== "undefined" ? window.location.origin : "",
218
- "",
219
- ),
220
- `(${cacheEvent.reason})`,
221
- );
305
+ logCacheEventToConsole(cacheEvent);
222
306
  }
223
307
  }
224
308
  }
225
309
  });
226
310
  }
227
311
 
312
+ /**
313
+ * Rich console logging for cache events with colors and formatting
314
+ */
315
+ function logCacheEventToConsole(cacheEvent) {
316
+ const origin = typeof window !== "undefined" ? window.location.origin : "";
317
+ const shortUrl = cacheEvent.url.replace(origin, "") || cacheEvent.url;
318
+ const time = new Date(cacheEvent.timestamp).toLocaleTimeString();
319
+
320
+ // Style configurations for each event type
321
+ const styles = {
322
+ CACHE_INTERCEPT: {
323
+ icon: "šŸ”",
324
+ label: "INTERCEPT",
325
+ bgColor: "#6366f1",
326
+ textColor: "#fff",
327
+ },
328
+ CACHE_CACHE: {
329
+ icon: "šŸ’¾",
330
+ label: "CACHED",
331
+ bgColor: "#22c55e",
332
+ textColor: "#fff",
333
+ },
334
+ CACHE_SKIP: {
335
+ icon: "ā­ļø",
336
+ label: "SKIPPED",
337
+ bgColor: "#f59e0b",
338
+ textColor: "#000",
339
+ },
340
+ CACHE_SERVE: {
341
+ icon: "šŸ“¤",
342
+ label: "SERVED",
343
+ bgColor: "#3b82f6",
344
+ textColor: "#fff",
345
+ },
346
+ CACHE_ERROR: {
347
+ icon: "āŒ",
348
+ label: "MISS",
349
+ bgColor: "#ef4444",
350
+ textColor: "#fff",
351
+ },
352
+ };
353
+
354
+ const style = styles[cacheEvent.type] || {
355
+ icon: "šŸ“",
356
+ label: "EVENT",
357
+ bgColor: "#64748b",
358
+ textColor: "#fff",
359
+ };
360
+
361
+ // Build metadata string
362
+ let metaInfo = "";
363
+ if (cacheEvent.metadata) {
364
+ const meta = cacheEvent.metadata;
365
+ const parts = [];
366
+ if (meta.type) parts.push(`type: ${meta.type}`);
367
+ if (meta.priority) parts.push(`priority: ${meta.priority}`);
368
+ if (meta.size) parts.push(`size: ${formatBytes(meta.size)}`);
369
+ if (meta.networkQuality) parts.push(`network: ${meta.networkQuality}`);
370
+ if (meta.isPage !== undefined) parts.push(meta.isPage ? "PAGE" : "API");
371
+ if (parts.length > 0) metaInfo = ` [${parts.join(", ")}]`;
372
+ }
373
+
374
+ // Reason badge
375
+ const reasonText = cacheEvent.reason ? ` → ${formatReason(cacheEvent.reason)}` : "";
376
+
377
+ // Log with styling
378
+ console.log(
379
+ `%c ${style.icon} SmartOffline %c ${style.label} %c ${time} %c ${shortUrl}${reasonText}${metaInfo}`,
380
+ `background: #1e293b; color: #fff; padding: 2px 6px; border-radius: 3px 0 0 3px; font-weight: bold;`,
381
+ `background: ${style.bgColor}; color: ${style.textColor}; padding: 2px 8px; font-weight: bold;`,
382
+ `background: #334155; color: #94a3b8; padding: 2px 6px;`,
383
+ `background: transparent; color: #e2e8f0; padding: 2px 6px;`
384
+ );
385
+
386
+ // For cached items, show additional details in a group
387
+ if (cacheEvent.type === "CACHE_CACHE" && cacheEvent.metadata) {
388
+ console.groupCollapsed(` ↳ Details for ${shortUrl}`);
389
+ console.table({
390
+ URL: shortUrl,
391
+ Type: cacheEvent.metadata.type || "unknown",
392
+ Priority: cacheEvent.metadata.priority || "normal",
393
+ Size: cacheEvent.metadata.size ? formatBytes(cacheEvent.metadata.size) : "unknown",
394
+ Timestamp: new Date(cacheEvent.timestamp).toISOString(),
395
+ });
396
+ console.groupEnd();
397
+ }
398
+
399
+ // For serve events, show usage data if available
400
+ if (cacheEvent.type === "CACHE_SERVE" && cacheEvent.metadata?.usage) {
401
+ console.groupCollapsed(` ↳ Usage data for ${shortUrl}`);
402
+ console.table({
403
+ "Access Count": cacheEvent.metadata.usage.count,
404
+ "Last Accessed": new Date(cacheEvent.metadata.usage.lastAccessed).toISOString(),
405
+ Priority: cacheEvent.metadata.priority || "normal",
406
+ });
407
+ console.groupEnd();
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Format bytes to human readable string
413
+ */
414
+ function formatBytes(bytes) {
415
+ if (bytes === 0) return "0 B";
416
+ const k = 1024;
417
+ const sizes = ["B", "KB", "MB", "GB"];
418
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
419
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
420
+ }
421
+
422
+ /**
423
+ * Format reason string to be more readable
424
+ */
425
+ function formatReason(reason) {
426
+ return reason
427
+ .replace(/_/g, " ")
428
+ .replace(/\b\w/g, (l) => l.toUpperCase());
429
+ }
430
+
228
431
  function openDB(name, version) {
229
432
  return new Promise((resolve, reject) => {
230
433
  const request = indexedDB.open(name, version);