@soham20/smart-offline-sdk 0.1.0 → 0.1.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 +5 -0
- package/demo/index.html +3 -3
- package/package.json +1 -1
- package/smart-offline-sw.js +104 -23
- package/src/usageTracker.js +37 -0
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<<<<<<< HEAD
|
|
2
|
+
# Hackvision2026
|
|
3
|
+
|
|
4
|
+
=======
|
|
1
5
|
# Hackvision2026 SDK (JavaScript)
|
|
2
6
|
|
|
3
7
|
This repository contains the JavaScript SDK for Hackvision2026.
|
|
@@ -12,3 +16,4 @@ client.echo('hello').then(console.log);
|
|
|
12
16
|
```
|
|
13
17
|
|
|
14
18
|
See `examples/` for a runnable example.
|
|
19
|
+
>>>>>>> 061641ebd342b507a49884dd32b30125a6525011
|
package/demo/index.html
CHANGED
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
|
|
13
13
|
SmartOffline.init({
|
|
14
14
|
pages: ["/demo/index.html","/demo/api/profile.json"],
|
|
15
|
-
apis: ["https://jsonplaceholder.typicode.com/posts"],
|
|
15
|
+
apis: ["https://jsonplaceholder.typicode.com/posts/2"],
|
|
16
16
|
debug: true
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
fetch("https://jsonplaceholder.typicode.com/posts")
|
|
19
|
+
fetch("https://jsonplaceholder.typicode.com/posts/2")
|
|
20
20
|
.then(res => res.json())
|
|
21
21
|
.then(data => {
|
|
22
|
-
document.body.innerHTML += `<pre>${data}</pre>`;
|
|
22
|
+
document.body.innerHTML += `<pre>${JSON.stringify(data, null, 2)}</pre>`;
|
|
23
23
|
})
|
|
24
24
|
.catch(err => {
|
|
25
25
|
document.body.innerHTML += "<p>API failed</p>";
|
package/package.json
CHANGED
package/smart-offline-sw.js
CHANGED
|
@@ -22,11 +22,81 @@ self.addEventListener("message", (event) => {
|
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* -------- Usage Tracking (IndexedDB) --------
|
|
27
|
+
* Tracks frequency + recency per URL
|
|
28
|
+
*/
|
|
29
|
+
function trackUsage(url) {
|
|
30
|
+
const request = indexedDB.open("smart-offline-usage", 1);
|
|
31
|
+
|
|
32
|
+
request.onupgradeneeded = () => {
|
|
33
|
+
const db = request.result;
|
|
34
|
+
if (!db.objectStoreNames.contains("usage")) {
|
|
35
|
+
db.createObjectStore("usage", { keyPath: "url" });
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
request.onsuccess = () => {
|
|
40
|
+
const db = request.result;
|
|
41
|
+
const tx = db.transaction("usage", "readwrite");
|
|
42
|
+
const store = tx.objectStore("usage");
|
|
43
|
+
|
|
44
|
+
const getReq = store.get(url);
|
|
45
|
+
getReq.onsuccess = () => {
|
|
46
|
+
const data = getReq.result || {
|
|
47
|
+
url,
|
|
48
|
+
count: 0,
|
|
49
|
+
lastAccessed: 0,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
data.count += 1;
|
|
53
|
+
data.lastAccessed = Date.now();
|
|
54
|
+
store.put(data);
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read usage info
|
|
61
|
+
*/
|
|
62
|
+
function getUsage(url) {
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
const request = indexedDB.open("smart-offline-usage", 1);
|
|
65
|
+
|
|
66
|
+
request.onsuccess = () => {
|
|
67
|
+
const db = request.result;
|
|
68
|
+
const tx = db.transaction("usage", "readonly");
|
|
69
|
+
const store = tx.objectStore("usage");
|
|
70
|
+
|
|
71
|
+
const getReq = store.get(url);
|
|
72
|
+
getReq.onsuccess = () => resolve(getReq.result);
|
|
73
|
+
getReq.onerror = () => resolve(null);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
request.onerror = () => resolve(null);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Decide priority based on real usage
|
|
82
|
+
*/
|
|
83
|
+
function isHighPriority(usage) {
|
|
84
|
+
if (!usage) return false;
|
|
85
|
+
|
|
86
|
+
const FREQUENT_THRESHOLD = 3;
|
|
87
|
+
const RECENT_THRESHOLD = 24 * 60 * 60 * 1000; // 24h
|
|
88
|
+
|
|
89
|
+
const frequent = usage.count >= FREQUENT_THRESHOLD;
|
|
90
|
+
const recent = Date.now() - usage.lastAccessed <= RECENT_THRESHOLD;
|
|
91
|
+
|
|
92
|
+
return frequent || recent;
|
|
93
|
+
}
|
|
94
|
+
|
|
25
95
|
/**
|
|
26
96
|
* Install event
|
|
27
97
|
*/
|
|
28
98
|
self.addEventListener("install", (event) => {
|
|
29
|
-
self.skipWaiting();
|
|
99
|
+
self.skipWaiting();
|
|
30
100
|
event.waitUntil(caches.open(CACHE_NAME));
|
|
31
101
|
});
|
|
32
102
|
|
|
@@ -36,69 +106,80 @@ self.addEventListener("install", (event) => {
|
|
|
36
106
|
self.addEventListener("activate", (event) => {
|
|
37
107
|
event.waitUntil(
|
|
38
108
|
Promise.all([
|
|
39
|
-
self.clients.claim(),
|
|
109
|
+
self.clients.claim(),
|
|
40
110
|
caches.keys().then((cacheNames) =>
|
|
41
111
|
Promise.all(
|
|
42
112
|
cacheNames.map((name) => {
|
|
43
113
|
if (name !== CACHE_NAME) {
|
|
44
114
|
return caches.delete(name);
|
|
45
115
|
}
|
|
46
|
-
})
|
|
47
|
-
)
|
|
116
|
+
})
|
|
117
|
+
)
|
|
48
118
|
),
|
|
49
|
-
])
|
|
119
|
+
])
|
|
50
120
|
);
|
|
51
121
|
});
|
|
52
122
|
|
|
53
|
-
|
|
54
123
|
/**
|
|
55
|
-
* Fetch event
|
|
124
|
+
* Fetch event — SMART + PRIORITY logic
|
|
56
125
|
*/
|
|
57
126
|
self.addEventListener("fetch", (event) => {
|
|
58
127
|
const request = event.request;
|
|
59
128
|
|
|
60
129
|
if (request.method !== "GET") return;
|
|
61
130
|
|
|
62
|
-
console.log("[SW] Intercepted:", request.url);
|
|
63
|
-
|
|
64
131
|
const isPage = SDK_CONFIG.pages.some((p) =>
|
|
65
132
|
request.url.includes(p)
|
|
66
133
|
);
|
|
67
|
-
|
|
68
134
|
const isAPI = SDK_CONFIG.apis.some((a) =>
|
|
69
135
|
request.url.includes(a)
|
|
70
136
|
);
|
|
71
137
|
|
|
72
138
|
if (!isPage && !isAPI) return;
|
|
73
139
|
|
|
140
|
+
if (SDK_CONFIG.debug) {
|
|
141
|
+
console.log("[SW] Intercepted:", request.url);
|
|
142
|
+
}
|
|
143
|
+
|
|
74
144
|
event.respondWith(
|
|
75
145
|
fetch(request)
|
|
76
146
|
.then((response) => {
|
|
77
|
-
|
|
78
|
-
|
|
147
|
+
// Network success
|
|
148
|
+
trackUsage(request.url);
|
|
79
149
|
|
|
150
|
+
const clone = response.clone();
|
|
80
151
|
caches.open(CACHE_NAME).then((cache) => {
|
|
81
|
-
cache.put(
|
|
152
|
+
cache.put(request.url, clone);
|
|
82
153
|
});
|
|
83
154
|
|
|
84
155
|
if (SDK_CONFIG.debug) {
|
|
85
156
|
console.log(
|
|
86
157
|
`[SmartOffline] Cached ${isAPI ? "API" : "PAGE"}:`,
|
|
87
|
-
|
|
158
|
+
request.url
|
|
88
159
|
);
|
|
89
160
|
}
|
|
90
161
|
|
|
91
162
|
return response;
|
|
92
163
|
})
|
|
93
164
|
.catch(() => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
165
|
+
// Offline / network failure
|
|
166
|
+
trackUsage(request.url);
|
|
167
|
+
|
|
168
|
+
return getUsage(request.url).then((usage) => {
|
|
169
|
+
const highPriority = isHighPriority(usage);
|
|
170
|
+
|
|
171
|
+
if (SDK_CONFIG.debug) {
|
|
172
|
+
console.log(
|
|
173
|
+
`[SmartOffline] ${
|
|
174
|
+
highPriority ? "HIGH" : "NORMAL"
|
|
175
|
+
} priority:`,
|
|
176
|
+
request.url
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// v1 behavior: both return cache, but priority is decided & logged
|
|
181
|
+
return caches.match(request.url);
|
|
182
|
+
});
|
|
183
|
+
})
|
|
102
184
|
);
|
|
103
185
|
});
|
|
104
|
-
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const DB_NAME = "smart-offline-usage";
|
|
2
|
+
const STORE_NAME = "usage";
|
|
3
|
+
const DB_VERSION = 1;
|
|
4
|
+
|
|
5
|
+
function openDB() {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
8
|
+
|
|
9
|
+
request.onupgradeneeded = () => {
|
|
10
|
+
const db = request.result;
|
|
11
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
12
|
+
db.createObjectStore(STORE_NAME, { keyPath: "url" });
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
request.onsuccess = () => resolve(request.result);
|
|
17
|
+
request.onerror = () => reject(request.error);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function trackUsage(url) {
|
|
22
|
+
const db = await openDB();
|
|
23
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
24
|
+
const store = tx.objectStore(STORE_NAME);
|
|
25
|
+
|
|
26
|
+
const existing = await new Promise(resolve => {
|
|
27
|
+
const req = store.get(url);
|
|
28
|
+
req.onsuccess = () => resolve(req.result);
|
|
29
|
+
req.onerror = () => resolve(null);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const data = existing || { url, count: 0, lastAccessed: 0 };
|
|
33
|
+
data.count += 1;
|
|
34
|
+
data.lastAccessed = Date.now();
|
|
35
|
+
|
|
36
|
+
store.put(data);
|
|
37
|
+
}
|