@soham20/smart-offline-sdk 0.1.3 → 0.2.0

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,24 +1,38 @@
1
1
  {
2
2
  "name": "@soham20/smart-offline-sdk",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Smart offline-first JavaScript SDK with intelligent caching for web applications",
5
- "main": "src/index.cjs.js",
6
- "module": "src/index.js",
5
+ "main": "./src/index.cjs.js",
6
+ "module": "./src/index.js",
7
+ "browser": "./src/index.js",
8
+ "types": "./src/index.d.ts",
7
9
  "exports": {
8
10
  ".": {
9
11
  "import": "./src/index.js",
10
- "require": "./src/index.cjs.js"
12
+ "require": "./src/index.cjs.js",
13
+ "browser": "./src/index.js",
14
+ "default": "./src/index.js"
11
15
  },
12
16
  "./client": {
13
17
  "require": "./src/sdk/index.cjs"
14
18
  }
15
19
  },
20
+ "files": [
21
+ "src",
22
+ "smart-offline-sw.js"
23
+ ],
16
24
  "scripts": {
17
25
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
18
26
  "build": "echo \"no build step\"",
19
27
  "lint": "echo \"no lint configured\""
20
28
  },
21
- "keywords": ["offline", "pwa", "service-worker", "cache", "offline-first"],
29
+ "keywords": [
30
+ "offline",
31
+ "pwa",
32
+ "service-worker",
33
+ "cache",
34
+ "offline-first"
35
+ ],
22
36
  "author": "Atif Sayed",
23
37
  "license": "MIT",
24
38
  "type": "module",
@@ -15,6 +15,9 @@ let SDK_CONFIG = {
15
15
  maxResourceSize: Infinity,
16
16
  networkQuality: "auto", // 'auto' | 'fast' | 'slow'
17
17
  significance: {}, // { urlPattern: 'high' | 'normal' | 'low' }
18
+ weights: { frequency: 1, recency: 1, size: 1 },
19
+ customPriorityFn: null,
20
+ enableDetailedLogs: false
18
21
  };
19
22
 
20
23
  /**
@@ -24,12 +27,66 @@ self.addEventListener("message", (event) => {
24
27
  if (event.data && event.data.type === "INIT_CONFIG") {
25
28
  SDK_CONFIG = event.data.payload;
26
29
 
30
+ // Deserialize custom priority function if provided
31
+ if (SDK_CONFIG.customPriorityFn && typeof SDK_CONFIG.customPriorityFn === 'string') {
32
+ try {
33
+ SDK_CONFIG.customPriorityFn = eval(`(${SDK_CONFIG.customPriorityFn})`);
34
+ } catch (e) {
35
+ console.error('[SmartOffline] Failed to parse customPriorityFn:', e);
36
+ SDK_CONFIG.customPriorityFn = null;
37
+ }
38
+ }
39
+
27
40
  if (SDK_CONFIG.debug) {
28
41
  console.log("[SmartOffline] Config received:", SDK_CONFIG);
29
42
  }
30
43
  }
31
44
  });
32
45
 
46
+ /**
47
+ * Log cache events to IndexedDB
48
+ */
49
+ function logEvent(type, url, reason, metadata = {}) {
50
+ if (!SDK_CONFIG.enableDetailedLogs) return;
51
+
52
+ const request = indexedDB.open("smart-offline-logs", 1);
53
+
54
+ request.onupgradeneeded = () => {
55
+ const db = request.result;
56
+ if (!db.objectStoreNames.contains("logs")) {
57
+ db.createObjectStore("logs", { autoIncrement: true });
58
+ }
59
+ };
60
+
61
+ request.onsuccess = () => {
62
+ const db = request.result;
63
+ const tx = db.transaction("logs", "readwrite");
64
+ const store = tx.objectStore("logs");
65
+
66
+ store.add({
67
+ type,
68
+ url,
69
+ reason,
70
+ metadata,
71
+ timestamp: Date.now(),
72
+ date: new Date().toISOString()
73
+ });
74
+ };
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
+ }
89
+
33
90
  /**
34
91
  * -------- Usage Tracking (IndexedDB) --------
35
92
  * Tracks frequency + recency per URL
@@ -86,9 +143,19 @@ function getUsage(url) {
86
143
  }
87
144
 
88
145
  /**
89
- * Decide priority based on real usage and developer-tuned config
146
+ * Decide priority based on real usage and developer-tuned config with weights
90
147
  */
91
148
  function isHighPriority(usage, url) {
149
+ // Custom priority function override
150
+ if (SDK_CONFIG.customPriorityFn && typeof SDK_CONFIG.customPriorityFn === 'function') {
151
+ try {
152
+ const score = SDK_CONFIG.customPriorityFn(usage, url, SDK_CONFIG);
153
+ return score > 50; // Scores above 50 are high priority
154
+ } catch (e) {
155
+ console.error('[SmartOffline] Custom priority function error:', e);
156
+ }
157
+ }
158
+
92
159
  // Manual significance override
93
160
  for (const pattern in SDK_CONFIG.significance) {
94
161
  if (url.includes(pattern)) {
@@ -101,10 +168,25 @@ function isHighPriority(usage, url) {
101
168
 
102
169
  if (!usage) return false;
103
170
 
104
- const frequent = usage.count >= SDK_CONFIG.frequencyThreshold;
105
- const recent = Date.now() - usage.lastAccessed <= SDK_CONFIG.recencyThreshold;
106
-
107
- return frequent || recent;
171
+ // Weighted priority calculation
172
+ const weights = SDK_CONFIG.weights || { frequency: 1, recency: 1, size: 1 };
173
+
174
+ // Frequency score (0-100)
175
+ const frequencyScore = Math.min(100, (usage.count / SDK_CONFIG.frequencyThreshold) * 100);
176
+
177
+ // Recency score (0-100)
178
+ const timeSinceAccess = Date.now() - usage.lastAccessed;
179
+ const recencyScore = Math.max(0, 100 - (timeSinceAccess / SDK_CONFIG.recencyThreshold) * 100);
180
+
181
+ // Weighted total
182
+ const totalWeight = weights.frequency + weights.recency;
183
+ const weightedScore = (
184
+ (frequencyScore * weights.frequency) +
185
+ (recencyScore * weights.recency)
186
+ ) / totalWeight;
187
+
188
+ // High priority if weighted score > 50
189
+ return weightedScore > 50;
108
190
  }
109
191
 
110
192
  /**
@@ -182,6 +264,11 @@ self.addEventListener("fetch", (event) => {
182
264
  const contentLength = response.headers.get("content-length");
183
265
  const size = contentLength ? parseInt(contentLength, 10) : 0;
184
266
  if (size > SDK_CONFIG.maxResourceSize) {
267
+ logEvent('skip', request.url, 'size_limit_exceeded', {
268
+ size,
269
+ limit: SDK_CONFIG.maxResourceSize
270
+ });
271
+
185
272
  if (SDK_CONFIG.debug) {
186
273
  console.log(
187
274
  `[SmartOffline] Skipped caching (size ${size} > ${SDK_CONFIG.maxResourceSize}):`,
@@ -193,8 +280,15 @@ self.addEventListener("fetch", (event) => {
193
280
 
194
281
  // Network quality aware caching
195
282
  const netQuality = getEffectiveNetworkQuality();
196
- if (netQuality === "slow" && !isHighPriority(null, request.url)) {
197
- // On slow network, skip caching low priority resources proactively
283
+ const usage = await getUsage(request.url);
284
+ const highPriority = isHighPriority(usage, request.url);
285
+
286
+ if (netQuality === "slow" && !highPriority) {
287
+ logEvent('skip', request.url, 'slow_network_low_priority', {
288
+ networkQuality: netQuality,
289
+ priority: 'low'
290
+ });
291
+
198
292
  if (SDK_CONFIG.debug) {
199
293
  console.log(
200
294
  `[SmartOffline] Skipped caching (slow network, not high priority):`,
@@ -207,6 +301,11 @@ self.addEventListener("fetch", (event) => {
207
301
  const clone = response.clone();
208
302
  caches.open(CACHE_NAME).then((cache) => {
209
303
  cache.put(request.url, clone);
304
+ logEvent('cache', request.url, 'network_fetch_success', {
305
+ type: isAPI ? 'API' : 'PAGE',
306
+ size,
307
+ priority: highPriority ? 'high' : 'normal'
308
+ });
210
309
  });
211
310
 
212
311
  if (SDK_CONFIG.debug) {
@@ -218,25 +317,33 @@ self.addEventListener("fetch", (event) => {
218
317
 
219
318
  return response;
220
319
  })
221
- .catch(() => {
320
+ .catch(async () => {
222
321
  // Offline / network failure
223
322
  trackUsage(request.url);
224
323
 
225
- return getUsage(request.url).then((usage) => {
226
- const highPriority = isHighPriority(usage, request.url);
324
+ const usage = await getUsage(request.url);
325
+ const highPriority = isHighPriority(usage, request.url);
326
+ const cached = await caches.match(request.url);
327
+
328
+ if (cached) {
329
+ logEvent('serve', request.url, 'offline_cache_hit', {
330
+ priority: highPriority ? 'high' : 'normal',
331
+ usage: usage ? { count: usage.count, lastAccessed: usage.lastAccessed } : null
332
+ });
227
333
 
228
334
  if (SDK_CONFIG.debug) {
229
335
  console.log(
230
- `[SmartOffline] ${
231
- highPriority ? "HIGH" : "NORMAL"
232
- } priority:`,
336
+ `[SmartOffline] Served from cache (${highPriority ? "HIGH" : "NORMAL"} priority):`,
233
337
  request.url
234
338
  );
235
339
  }
340
+ } else {
341
+ logEvent('error', request.url, 'cache_miss_offline', {
342
+ priority: highPriority ? 'high' : 'normal'
343
+ });
344
+ }
236
345
 
237
- // v1 behavior: both return cache, but priority is decided & logged
238
- return caches.match(request.url);
239
- });
346
+ return cached;
240
347
  })
241
348
  );
242
349
  });
package/src/index.cjs.js CHANGED
@@ -1,8 +1,16 @@
1
1
  /**
2
2
  * SmartOffline SDK (CommonJS version)
3
3
  */
4
+ const eventListeners = {
5
+ cache: [],
6
+ skip: [],
7
+ serve: [],
8
+ clear: [],
9
+ error: []
10
+ };
11
+
4
12
  function init(config = {}) {
5
- if (typeof navigator === 'undefined' || !("serviceWorker" in navigator)) {
13
+ if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
6
14
  console.warn("Service Workers not supported");
7
15
  return;
8
16
  }
@@ -16,11 +24,34 @@ function init(config = {}) {
16
24
  maxResourceSize: config.maxResourceSize ?? Infinity,
17
25
  networkQuality: config.networkQuality ?? "auto",
18
26
  significance: config.significance ?? {},
27
+ weights: {
28
+ frequency: config.weights?.frequency ?? 1,
29
+ recency: config.weights?.recency ?? 1,
30
+ size: config.weights?.size ?? 1
31
+ },
32
+ customPriorityFn: config.customPriorityFn ? config.customPriorityFn.toString() : null,
33
+ enableDetailedLogs: config.enableDetailedLogs ?? false
19
34
  };
20
35
 
36
+ if (config.onCacheEvent) {
37
+ eventListeners.cache.push(config.onCacheEvent);
38
+ eventListeners.skip.push(config.onCacheEvent);
39
+ eventListeners.serve.push(config.onCacheEvent);
40
+ eventListeners.clear.push(config.onCacheEvent);
41
+ eventListeners.error.push(config.onCacheEvent);
42
+ }
43
+
21
44
  navigator.serviceWorker.register("/smart-offline-sw.js").then(() => {
22
45
  console.log("Smart Offline Service Worker registered");
23
46
 
47
+ navigator.serviceWorker.addEventListener('message', (event) => {
48
+ if (event.data && event.data.type) {
49
+ const eventType = event.data.type.replace('CACHE_', '').toLowerCase();
50
+ const listeners = eventListeners[eventType] || [];
51
+ listeners.forEach(fn => fn(event.data));
52
+ }
53
+ });
54
+
24
55
  navigator.serviceWorker.ready.then(() => {
25
56
  if (navigator.serviceWorker.controller) {
26
57
  navigator.serviceWorker.controller.postMessage({
@@ -42,14 +73,64 @@ function init(config = {}) {
42
73
  });
43
74
 
44
75
  if (sdkConfig.debug) {
45
- console.log("[SmartOffline] Config sent after controllerchange:", sdkConfig);
76
+ console.log(
77
+ "[SmartOffline] Config sent after controllerchange:",
78
+ sdkConfig,
79
+ );
46
80
  }
47
81
  }
48
82
  });
49
83
  });
50
84
  }
51
85
 
52
- const SmartOffline = { init };
86
+ function on(eventType, callback) {
87
+ if (eventListeners[eventType]) {
88
+ eventListeners[eventType].push(callback);
89
+ }
90
+ }
91
+
92
+ function off(eventType, callback) {
93
+ if (eventListeners[eventType]) {
94
+ const index = eventListeners[eventType].indexOf(callback);
95
+ if (index > -1) {
96
+ eventListeners[eventType].splice(index, 1);
97
+ }
98
+ }
99
+ }
100
+
101
+ async function getCacheLogs() {
102
+ const db = await openDB('smart-offline-logs', 1);
103
+ const tx = db.transaction('logs', 'readonly');
104
+ const store = tx.objectStore('logs');
105
+ return new Promise((resolve) => {
106
+ const request = store.getAll();
107
+ request.onsuccess = () => resolve(request.result || []);
108
+ request.onerror = () => resolve([]);
109
+ });
110
+ }
111
+
112
+ async function clearCacheLogs() {
113
+ const db = await openDB('smart-offline-logs', 1);
114
+ const tx = db.transaction('logs', 'readwrite');
115
+ const store = tx.objectStore('logs');
116
+ store.clear();
117
+ }
118
+
119
+ function openDB(name, version) {
120
+ return new Promise((resolve, reject) => {
121
+ const request = indexedDB.open(name, version);
122
+ request.onupgradeneeded = () => {
123
+ const db = request.result;
124
+ if (!db.objectStoreNames.contains('logs')) {
125
+ db.createObjectStore('logs', { autoIncrement: true });
126
+ }
127
+ };
128
+ request.onsuccess = () => resolve(request.result);
129
+ request.onerror = () => reject(request.error);
130
+ });
131
+ }
132
+
133
+ const SmartOffline = { init, on, off, getCacheLogs, clearCacheLogs };
53
134
 
54
135
  module.exports = { SmartOffline };
55
136
  module.exports.default = SmartOffline;
package/src/index.d.ts ADDED
@@ -0,0 +1,76 @@
1
+ export interface UsageData {
2
+ url: string;
3
+ count: number;
4
+ lastAccessed: number;
5
+ }
6
+
7
+ export interface CacheWeights {
8
+ /** Weight for frequency score (default: 1) */
9
+ frequency?: number;
10
+ /** Weight for recency score (default: 1) */
11
+ recency?: number;
12
+ /** Weight for size consideration (default: 1) */
13
+ size?: number;
14
+ }
15
+
16
+ export interface CacheEvent {
17
+ type: 'CACHE_CACHE' | 'CACHE_SKIP' | 'CACHE_SERVE' | 'CACHE_CLEAR' | 'CACHE_ERROR';
18
+ url: string;
19
+ reason: string;
20
+ metadata?: any;
21
+ timestamp: number;
22
+ }
23
+
24
+ export type CustomPriorityFunction = (
25
+ usage: UsageData | null,
26
+ url: string,
27
+ config: SmartOfflineConfig
28
+ ) => number; // Return 0-100, >50 is high priority
29
+
30
+ export interface SmartOfflineConfig {
31
+ /** Array of page URL patterns to cache */
32
+ pages?: string[];
33
+ /** Array of API URL patterns to cache */
34
+ apis?: string[];
35
+ /** Enable debug logging */
36
+ debug?: boolean;
37
+ /** Number of accesses before resource is considered "frequent" (default: 3) */
38
+ frequencyThreshold?: number;
39
+ /** Milliseconds within which resource is considered "recent" (default: 24h) */
40
+ recencyThreshold?: number;
41
+ /** Max bytes to cache per resource; larger resources skipped (default: Infinity) */
42
+ maxResourceSize?: number;
43
+ /** Network quality setting: 'auto' | 'fast' | 'slow' (default: 'auto') */
44
+ networkQuality?: "auto" | "fast" | "slow";
45
+ /** Manual priority overrides by URL pattern */
46
+ significance?: Record<string, "high" | "normal" | "low">;
47
+ /** Weights for priority calculation (default: all 1) */
48
+ weights?: CacheWeights;
49
+ /** Custom priority function for complete control */
50
+ customPriorityFn?: CustomPriorityFunction;
51
+ /** Enable detailed event logging to IndexedDB */
52
+ enableDetailedLogs?: boolean;
53
+ /** Callback for all cache events */
54
+ onCacheEvent?: (event: CacheEvent) => void;
55
+ }
56
+
57
+ export interface CacheLog {
58
+ type: string;
59
+ url: string;
60
+ reason: string;
61
+ metadata?: any;
62
+ timestamp: number;
63
+ date: string;
64
+ }
65
+
66
+ export interface SmartOfflineSDK {
67
+ init(config?: SmartOfflineConfig): void;
68
+ on(eventType: 'cache' | 'skip' | 'serve' | 'clear' | 'error', callback: (event: CacheEvent) => void): void;
69
+ off(eventType: 'cache' | 'skip' | 'serve' | 'clear' | 'error', callback: (event: CacheEvent) => void): void;
70
+ getCacheLogs(): Promise<CacheLog[]>;
71
+ clearCacheLogs(): Promise<void>;
72
+ }
73
+
74
+ export declare const SmartOffline: SmartOfflineSDK;
75
+ export default SmartOffline;
76
+
package/src/index.js CHANGED
@@ -7,7 +7,20 @@
7
7
  * - maxResourceSize: max bytes to cache per resource; larger resources skipped (default Infinity)
8
8
  * - networkQuality: 'auto' | 'fast' | 'slow' — affects caching aggressiveness (default 'auto')
9
9
  * - significance: { [urlPattern]: 'high' | 'normal' | 'low' } — manual priority overrides
10
+ * - weights: { frequency: number, recency: number, size: number } — priority weights (default all 1)
11
+ * - customPriorityFn: (usage, url, config) => number — custom priority function (0-100)
12
+ * - onCacheEvent: (event) => void — callback for cache events (cache, skip, serve, clear)
10
13
  */
14
+
15
+ // Event listener registry
16
+ const eventListeners = {
17
+ cache: [],
18
+ skip: [],
19
+ serve: [],
20
+ clear: [],
21
+ error: []
22
+ };
23
+
11
24
  function init(config = {}) {
12
25
  if (!("serviceWorker" in navigator)) {
13
26
  console.warn("Service Workers not supported");
@@ -23,11 +36,38 @@ function init(config = {}) {
23
36
  maxResourceSize: config.maxResourceSize ?? Infinity,
24
37
  networkQuality: config.networkQuality ?? "auto",
25
38
  significance: config.significance ?? {},
39
+
40
+ // New features
41
+ weights: {
42
+ frequency: config.weights?.frequency ?? 1,
43
+ recency: config.weights?.recency ?? 1,
44
+ size: config.weights?.size ?? 1
45
+ },
46
+ customPriorityFn: config.customPriorityFn ? config.customPriorityFn.toString() : null,
47
+ enableDetailedLogs: config.enableDetailedLogs ?? false
26
48
  };
27
49
 
28
- navigator.serviceWorker.register("/smart-offline-sw.js").then(() => {
50
+ // Register event listener if provided
51
+ if (config.onCacheEvent) {
52
+ eventListeners.cache.push(config.onCacheEvent);
53
+ eventListeners.skip.push(config.onCacheEvent);
54
+ eventListeners.serve.push(config.onCacheEvent);
55
+ eventListeners.clear.push(config.onCacheEvent);
56
+ eventListeners.error.push(config.onCacheEvent);
57
+ }
58
+
59
+ navigator.serviceWorker.register("/smart-offline-sw.js").then((registration) => {
29
60
  console.log("Smart Offline Service Worker registered");
30
61
 
62
+ // Listen for messages from SW
63
+ navigator.serviceWorker.addEventListener('message', (event) => {
64
+ if (event.data && event.data.type) {
65
+ const eventType = event.data.type.replace('CACHE_', '').toLowerCase();
66
+ const listeners = eventListeners[eventType] || [];
67
+ listeners.forEach(fn => fn(event.data));
68
+ }
69
+ });
70
+
31
71
  navigator.serviceWorker.ready.then(() => {
32
72
  if (navigator.serviceWorker.controller) {
33
73
  navigator.serviceWorker.controller.postMessage({
@@ -51,7 +91,7 @@ function init(config = {}) {
51
91
  if (sdkConfig.debug) {
52
92
  console.log(
53
93
  "[SmartOffline] Config sent after controllerchange:",
54
- sdkConfig
94
+ sdkConfig,
55
95
  );
56
96
  }
57
97
  }
@@ -59,7 +99,63 @@ function init(config = {}) {
59
99
  });
60
100
  }
61
101
 
62
- const SmartOffline = { init };
102
+ // API for managing event listeners
103
+ function on(eventType, callback) {
104
+ if (eventListeners[eventType]) {
105
+ eventListeners[eventType].push(callback);
106
+ }
107
+ }
108
+
109
+ function off(eventType, callback) {
110
+ if (eventListeners[eventType]) {
111
+ const index = eventListeners[eventType].indexOf(callback);
112
+ if (index > -1) {
113
+ eventListeners[eventType].splice(index, 1);
114
+ }
115
+ }
116
+ }
117
+
118
+ // API to get cache logs
119
+ async function getCacheLogs() {
120
+ const db = await openDB('smart-offline-logs', 1);
121
+ const tx = db.transaction('logs', 'readonly');
122
+ const store = tx.objectStore('logs');
123
+ return new Promise((resolve) => {
124
+ const request = store.getAll();
125
+ request.onsuccess = () => resolve(request.result || []);
126
+ request.onerror = () => resolve([]);
127
+ });
128
+ }
129
+
130
+ // API to clear cache logs
131
+ async function clearCacheLogs() {
132
+ const db = await openDB('smart-offline-logs', 1);
133
+ const tx = db.transaction('logs', 'readwrite');
134
+ const store = tx.objectStore('logs');
135
+ store.clear();
136
+ }
137
+
138
+ function openDB(name, version) {
139
+ return new Promise((resolve, reject) => {
140
+ const request = indexedDB.open(name, version);
141
+ request.onupgradeneeded = () => {
142
+ const db = request.result;
143
+ if (!db.objectStoreNames.contains('logs')) {
144
+ db.createObjectStore('logs', { autoIncrement: true });
145
+ }
146
+ };
147
+ request.onsuccess = () => resolve(request.result);
148
+ request.onerror = () => reject(request.error);
149
+ });
150
+ }
151
+
152
+ const SmartOffline = {
153
+ init,
154
+ on,
155
+ off,
156
+ getCacheLogs,
157
+ clearCacheLogs
158
+ };
63
159
 
64
- export { SmartOffline }; // ✅ named export
65
- export default SmartOffline; // ✅ default export
160
+ export { SmartOffline }; // ✅ named export
161
+ export default SmartOffline; // ✅ default export
package/CONTRIBUTING.md DELETED
@@ -1,5 +0,0 @@
1
- # Contributing
2
-
3
- - Fork the repo and open a PR.
4
- - Run tests with `npm test`.
5
- - Follow the code style in existing files.
@@ -1,94 +0,0 @@
1
- # SmartOffline SDK — Project Analysis
2
-
3
- Date: 2026-01-22
4
-
5
- ---
6
-
7
- ## Project Summary
8
-
9
- SmartOffline is a JavaScript SDK that enables offline-first behavior by registering a Service Worker which caches configured pages and API responses. The repository includes a demo, basic tests, and a Node-style test SDK.
10
-
11
- Key locations:
12
-
13
- - `smart-offline-sw.js` — Service Worker implementing caching, usage tracking, and priority logic.
14
- - `src/index.js` — Browser SDK entry (`SmartOffline.init`) that registers the SW and sends configuration.
15
- - `src/usageTracker.js` — IndexedDB-based usage tracker (client-side helper).
16
- - `src/sdk/index.js` — Small Node test client (`HackvisionClient`) used by tests/examples.
17
- - `demo/` — Demo page and sample API (`demo/index.html`, `demo/api/profile.json`).
18
- - `tests/` — `tests/basic.test.js` (Jest) covering the test client.
19
-
20
- ---
21
-
22
- ## Features That Work (Implemented)
23
-
24
- - Service Worker registration via `SmartOffline.init()` (browser).
25
- - SW intercepts `GET` requests for configured pages and APIs and caches successful network responses.
26
- - Offline fallback: SW serves cached responses when fetch fails.
27
- - Usage tracking in IndexedDB (stores `count` and `lastAccessed` per URL) implemented both in `smart-offline-sw.js` and `src/usageTracker.js`.
28
- - Priority determination (high/normal) based on frequency and recency thresholds.
29
- - Demo implementation showing SDK usage and an API example.
30
- - Node test client (`HackvisionClient.echo`) and unit test (`jest`) pass.
31
- - CI workflow exists to run tests (.github/workflows/ci.yml).
32
- - Package published as `@soham20/smart-offline-sdk` (v0.1.1).
33
-
34
- ---
35
-
36
- ## Issues & Gaps (Needs Attention)
37
-
38
- - README contains unresolved merge markers. Clean and add clear quickstarts for browser and Node.
39
- - `package.json` declares `"type": "commonjs"` but `src/index.js` uses ESM-style `export` (module mismatch). This may confuse Node consumers and bundlers.
40
- - No build/bundling step: source files are published directly. This reduces compatibility across consumers (CJS/ESM/browser UMD). No `dist/` artifacts.
41
- - Service Worker behavior is not covered by automated integration tests (typical but recommended to add Playwright/Puppeteer tests for offline behavior).
42
- - Inconsistent public API surface: demo uses `SmartOffline.init()` while package `main` points to `src/sdk/index.js` (Node test client). Clarify and add a top-level entry that documents both browser and Node exports.
43
- - Privacy/consent: usage tracking stores per-URL usage in IndexedDB — document privacy implications and provide opt-out.
44
- - No cache eviction / quota management — cache can grow without limits.
45
-
46
- ---
47
-
48
- ## Short-Term Recommendations (Actionable)
49
-
50
- 1. Fix `README.md` (remove merge markers) and add clear Quickstart sections for browser and Node usage.
51
- 2. Standardize module format:
52
- - Option A: Build ESM + CJS bundles (Rollup) and set `main`/`module`/`exports` in `package.json`.
53
- - Option B: Convert `src/index.js` to CommonJS `module.exports` if targeting Node-only.
54
- 3. Add a build step that outputs `dist/` bundles (UMD for browsers, ESM and CJS for Node). Update `package.json` scripts (`build`, `prepublishOnly`).
55
- 4. Add integration tests (Playwright) that:
56
- - Serve demo, register SW, fetch a resource online, go offline, and verify cached response is served.
57
- 5. Add cache size management (LRU or simple max-entries) using usage metrics in IndexedDB.
58
- 6. Document the usage tracking behavior and add opt-out configuration (e.g., `trackUsage: false`).
59
-
60
- ---
61
-
62
- ## Mid/Large-Term Enhancements (Future)
63
-
64
- - Prefetching & background sync for high-priority resources.
65
- - Configurable stale-while-revalidate and TTL per resource.
66
- - TypeScript conversion + publishing type definitions.
67
- - Expose a small diagnostics API so apps can query cache status and usage stats.
68
- - Publish a UMD browser bundle and host on a CDN for direct `<script>` usage.
69
- - Telemetry (opt-in) and analytics for adoption + cache effectiveness metrics.
70
-
71
- ---
72
-
73
- ## Suggested Next Steps (I can implement)
74
-
75
- - Patch `README.md` to a clean quickstart (browser + Node).
76
- - Add a small build setup (Rollup) to produce `dist/` artifacts and update `package.json`.
77
- - Add a Playwright integration test for offline cache validation.
78
-
79
- If you want, I can: (pick one or more)
80
-
81
- - Fix the `README.md` now.
82
- - Add a minimal Rollup build and update `package.json`.
83
- - Add a Playwright test and a small local server script to run it.
84
-
85
- ---
86
-
87
- ## Notes & Observations
88
-
89
- - The published package name changed from `hackvision2026-sdk` (spam-detected) to scoped `@soham20/smart-offline-sdk`, which is preferable.
90
- - Tests pass (`npm test`). The test target is small (echo), so expand tests to include SDK behavior where feasible.
91
-
92
- ---
93
-
94
- _Generated by repository scan on 2026-01-22._
package/jest.config.js DELETED
@@ -1,9 +0,0 @@
1
- /** @type {import('jest').Config} */
2
- const config = {
3
- testEnvironment: 'node',
4
- transform: {},
5
- moduleFileExtensions: ['js', 'cjs', 'mjs', 'json'],
6
- testMatch: ['**/tests/**/*.test.cjs'],
7
- };
8
-
9
- export default config;