Repository navigation
Expand file tree
/
Copy pathsw.js
More file actions
119 lines (107 loc) · 5.39 KB
/
Copy pathsw.js
File metadata and controls
119 lines (107 loc) · 5.39 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
'use strict';
// Kept in sync with APP_VERSION (src/utils.js) by hand -- every version bump
// must touch this string too, or old cached assets never get evicted.
// Checked by tests/features/test_pwa.py::test_sw_cache_name_matches_app_version.
const CACHE_NAME = 'myfinances-v6.67.0';
const CDN_URL = 'https://cdn.jsdelivr.net/npm/chart.js';
// The whole app shell -- enumerated explicitly since there's no build step
// to glob these. tests/features/test_pwa.py::test_sw_precache_list_includes_every_src_js_file
// fails CI if a new src/*.js file is added here without updating this list.
const PRECACHE_URLS = [
'/', '/index.html', '/styles.css', '/styles-csp-classes.css',
'/guide.html', '/guide.css', '/manifest.json',
'/icons/icon-192.png', '/icons/icon-512.png', '/icons/icon-maskable-512.png', '/icons/favicon-32.png',
'/src/accounts.js', '/src/analytics.js', '/src/app.js', '/src/bills.js', '/src/bonusAdvisor.js',
'/src/breakEven.js', '/src/categoryBudgets.js', '/src/charts.js', '/src/commandPalette.js', '/src/dataExport.js',
'/src/dataTransferModal.js', '/src/debtBreakEven.js', '/src/debtCalculator.js', '/src/debtOwnership.js', '/src/debts.js',
'/src/forecast.js', '/src/guideNav.js', '/src/guideTheme.js', '/src/health.js',
'/src/homePurchase.js',
'/src/i18n.js', '/src/income.js', '/src/ledger.js', '/src/ledgerOverrides.js',
'/src/ledgerCleared.js', '/src/keyboardShortcuts.js',
'/src/ledgerTransactions.js', '/src/lazyModules.js', '/src/reconciliation.js', '/src/recurring.js', '/src/recurringPaid.js', '/src/reports.js',
'/src/reportsCalendar.js', '/src/reportsCashFlow.js', '/src/reportsMoneyFlowSankey.js', '/src/reportsNetWorth.js', '/src/reportsSummary.js',
'/src/reportsVariance.js', '/src/retirement.js', '/src/retirementCalculator.js',
'/src/sanitizers.js', '/src/savings.js', '/src/serviceWorker.js',
'/src/settings.js', '/src/settingsBackup.js', '/src/setupWizard.js', '/src/spending.js', '/src/storage.js',
'/src/calendarFeed.js',
'/src/loginGate.js', '/src/people.js', '/src/pgMigrationModal.js', '/src/postgresSync.js', '/src/postgresImport.js',
'/src/storageAdapters.js', '/src/strategy.js', '/src/strategyCalendar.js', '/src/strategyComparison.js',
'/src/strategyPlanCalculation.js', '/src/strategyScheduleTable.js', '/src/strategySummaryTable.js', '/src/ui.js',
'/src/utils.js', '/src/balanceHistoryCore.js', '/src/balanceHistory.js', '/src/balanceHistoryModal.js',
'/src/transfers.js', '/src/passwordPolicy.js', '/src/passwordUi.js', '/src/securitySettings.js',
'/src/locales/en.js', '/src/locales/es.js', '/src/locales/pl.js',
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(PRECACHE_URLS))
.catch((err) => console.error('[sw] precache failed', err))
);
});
self.addEventListener('activate', (event) => {
// Deliberately no skipWaiting()/clients.claim() here -- a new version
// installs and waits; src/serviceWorker.js prompts the user to reload
// instead of silently swapping assets under an open session.
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(
keys
.filter((key) => key.startsWith('myfinances-v') && key !== CACHE_NAME)
.map((key) => caches.delete(key))
)
)
);
});
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
async function networkFirst(request) {
let response;
try {
response = await fetch(request);
} catch (err) {
try {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(request);
if (cached) return cached;
} catch (cacheErr) {
// Storage can be unavailable in private browsing; preserve the
// original network error rather than making cache access required.
}
throw err;
}
try {
// Honour Cache-Control: no-store from the server — don't persist
// sensitive responses (e.g. /calendar.ics) in the SW cache.
if (!response.headers.get('cache-control')?.includes('no-store')) {
const cache = await caches.open(CACHE_NAME);
await cache.put(request, response.clone());
}
} catch (err) {
// A failed cache write must not prevent an online response from loading.
}
return response;
}
async function staleWhileRevalidate(request) {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(request);
const fetchPromise = fetch(request).then((response) => {
cache.put(request, response.clone()).catch(() => {});
return response;
}).catch(() => cached);
return cached || fetchPromise;
}
self.addEventListener('fetch', (event) => {
const { request } = event;
if (request.method !== 'GET') return;
if (request.url.startsWith(CDN_URL)) {
event.respondWith(staleWhileRevalidate(request));
} else if (new URL(request.url).origin === self.location.origin) {
// App-shell filenames are stable between releases. Prefer the network
// while online so an active older worker cannot keep serving a stale
// release; fall back to the precache only when offline.
event.respondWith(networkFirst(request));
}
});