-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathservice-worker.js
More file actions
348 lines (302 loc) · 9.46 KB
/
Copy pathservice-worker.js
File metadata and controls
348 lines (302 loc) · 9.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
// service-worker.js
const CACHE_NAME = "taskpilot-v2.0";
const STATIC_CACHE = "taskpilot-static-v2.0";
const DYNAMIC_CACHE = "taskpilot-dynamic-v2.0";
// Assets to cache during installation
const STATIC_ASSETS = [
'/',
'/index.html',
'/manifest.json',
'/icons/icon-192.png',
'/icons/apple-touch-icon.png',
'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css',
'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap'
];
// Install event - cache static assets
self.addEventListener('install', (event) => {
console.log('Service Worker installing...');
event.waitUntil(
caches.open(STATIC_CACHE)
.then((cache) => {
console.log('Caching static assets');
return cache.addAll(STATIC_ASSETS);
})
.then(() => {
console.log('Service Worker installed successfully');
return self.skipWaiting();
})
.catch((error) => {
console.error('Cache installation failed:', error);
})
);
});
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
console.log('Service Worker activating...');
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE && cacheName !== CACHE_NAME) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
}).then(() => {
console.log('Service Worker activated successfully');
return self.clients.claim();
})
);
});
// Fetch event - network first strategy with fallback to cache
self.addEventListener('fetch', (event) => {
// Skip non-GET requests
if (event.request.method !== 'GET') return;
// Skip chrome-extension requests
if (event.request.url.startsWith('chrome-extension://')) return;
event.respondWith(
// Try network first
fetch(event.request)
.then((networkResponse) => {
// If successful, cache the response
if (networkResponse.status === 200) {
const responseClone = networkResponse.clone();
caches.open(DYNAMIC_CACHE)
.then((cache) => {
cache.put(event.request, responseClone);
});
}
return networkResponse;
})
.catch(() => {
// Network failed, try cache
return caches.match(event.request)
.then((cachedResponse) => {
if (cachedResponse) {
return cachedResponse;
}
// For navigation requests, return offline page
if (event.request.mode === 'navigate') {
return caches.match('/index.html');
}
// For other requests, you might want to return a placeholder
return new Response('Network error occurred', {
status: 408,
headers: { 'Content-Type': 'text/plain' }
});
});
})
);
});
// Background Sync for offline task synchronization
self.addEventListener('sync', (event) => {
console.log('Background sync triggered:', event.tag);
if (event.tag === 'sync-tasks') {
event.waitUntil(syncPendingTasks());
}
if (event.tag === 'sync-notes') {
event.waitUntil(syncPendingNotes());
}
});
// Sync pending tasks when back online
async function syncPendingTasks() {
try {
// Get pending tasks from IndexedDB
const pendingTasks = await getPendingTasks();
for (const task of pendingTasks) {
try {
// Simulate API call - replace with your actual API endpoint
const response = await fetch('/api/tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(task),
});
if (response.ok) {
console.log('Task synced successfully:', task);
await removePendingTask(task.id);
}
} catch (error) {
console.error('Failed to sync task:', task, error);
}
}
} catch (error) {
console.error('Background sync failed:', error);
}
}
// Sync pending notes when back online
async function syncPendingNotes() {
try {
const pendingNotes = await getPendingNotes();
for (const note of pendingNotes) {
try {
const response = await fetch('/api/notes', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(note),
});
if (response.ok) {
console.log('Note synced successfully:', note);
await removePendingNote(note.id);
}
} catch (error) {
console.error('Failed to sync note:', note, error);
}
}
} catch (error) {
console.error('Background note sync failed:', error);
}
}
// Push notifications for task reminders
self.addEventListener('push', (event) => {
console.log('Push notification received');
let data = {
title: 'TaskPilot',
body: 'Stay on track with your tasks!',
icon: '/icons/icon-192.png',
badge: '/icons/icon-96.png'
};
if (event.data) {
try {
data = { ...data, ...event.data.json() };
} catch (error) {
console.error('Error parsing push data:', error);
}
}
const options = {
body: data.body,
icon: data.icon,
badge: data.badge,
vibrate: [100, 50, 100],
data: data.url || '/',
actions: [
{
action: 'open',
title: 'Open App'
},
{
action: 'dismiss',
title: 'Dismiss'
}
]
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
// Handle notification clicks
self.addEventListener('notificationclick', (event) => {
console.log('Notification clicked:', event);
event.notification.close();
if (event.action === 'dismiss') {
return;
}
event.waitUntil(
clients.matchAll({ type: 'window' }).then((clientList) => {
// Check if app is already open
for (const client of clientList) {
if (client.url.includes(self.location.origin) && 'focus' in client) {
return client.focus();
}
}
// Open new window if app isn't open
if (clients.openWindow) {
return clients.openWindow('/');
}
})
);
});
// Periodic background sync (for weekly data refresh)
self.addEventListener('periodicsync', (event) => {
if (event.tag === 'weekly-refresh') {
event.waitUntil(refreshWeeklyData());
}
});
async function refreshWeeklyData() {
try {
// Refresh weekly tasks and stats
const responses = await Promise.all([
fetch('/api/weekly-tasks'),
fetch('/api/stats')
]);
const [weeklyTasks, stats] = await Promise.all(
responses.map(res => res.json())
);
// Store updated data in cache
const cache = await caches.open(DYNAMIC_CACHE);
await cache.put('/api/weekly-tasks', new Response(JSON.stringify(weeklyTasks)));
await cache.put('/api/stats', new Response(JSON.stringify(stats)));
console.log('Weekly data refreshed successfully');
} catch (error) {
console.error('Weekly refresh failed:', error);
}
}
// Helper functions for IndexedDB operations
async function getPendingTasks() {
return new Promise((resolve) => {
// This would interact with your IndexedDB
// For now, return empty array as placeholder
resolve([]);
});
}
async function removePendingTask(taskId) {
return new Promise((resolve) => {
// Remove task from IndexedDB
resolve();
});
}
async function getPendingNotes() {
return new Promise((resolve) => {
resolve([]);
});
}
async function removePendingNote(noteId) {
return new Promise((resolve) => {
resolve();
});
}
// Message event handler for communication with the app
self.addEventListener('message', (event) => {
console.log('Service Worker received message:', event.data);
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
if (event.data && event.data.type === 'CACHE_TASKS') {
cacheTasks(event.data.tasks);
}
if (event.data && event.data.type === 'REGISTER_SYNC') {
registerBackgroundSync();
}
});
// Cache tasks for offline access
async function cacheTasks(tasks) {
try {
const cache = await caches.open(DYNAMIC_CACHE);
const response = new Response(JSON.stringify(tasks));
await cache.put('/api/tasks', response);
console.log('Tasks cached for offline access');
} catch (error) {
console.error('Failed to cache tasks:', error);
}
}
// Register for background sync
async function registerBackgroundSync() {
if ('sync' in self.registration) {
try {
await self.registration.sync.register('sync-tasks');
console.log('Background sync registered');
} catch (error) {
console.error('Background sync registration failed:', error);
}
}
}
// Health check endpoint for service worker
self.addEventListener('fetch', (event) => {
if (event.request.url.endsWith('/sw-health')) {
event.respondWith(new Response('OK', { status: 200 }));
}
});