fluxy-bot 0.10.12 → 0.10.13
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 +1 -1
- package/supervisor/index.ts +38 -14
- package/workspace/client/index.html +26 -3
- package/workspace/client/public/sw.js +29 -10
- package/workspace/client/src/main.tsx +6 -1
package/package.json
CHANGED
package/supervisor/index.ts
CHANGED
|
@@ -53,21 +53,37 @@ const MIME_TYPES: Record<string, string> = {
|
|
|
53
53
|
const SW_JS = `// Service worker — app-shell caching + push notifications
|
|
54
54
|
// Caching strategy:
|
|
55
55
|
// Hashed assets (/assets/*-AbCd12.js) → cache-first (immutable)
|
|
56
|
-
// Navigation (HTML) →
|
|
56
|
+
// Navigation (HTML) → stale-while-revalidate (precached on install)
|
|
57
57
|
// Static assets (img/video/fonts) → stale-while-revalidate
|
|
58
58
|
// JS/CSS modules → stale-while-revalidate
|
|
59
59
|
// API, WebSocket, Vite internals → network-only (no cache)
|
|
60
60
|
|
|
61
|
-
var CACHE = 'fluxy-
|
|
61
|
+
var CACHE = 'fluxy-v4';
|
|
62
62
|
var HASHED_RE = new RegExp('/assets/.+-[a-zA-Z0-9]{6,}[.](js|css)$');
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
// Precache the HTML shell on install so the cache is never empty.
|
|
65
|
+
// Without this, the first navigation isn't intercepted (SW wasn't
|
|
66
|
+
// controlling yet), so refresh would find an empty cache → white screen.
|
|
67
|
+
self.addEventListener('install', function(e) {
|
|
68
|
+
console.log('[SW] installing, cache:', CACHE);
|
|
69
|
+
e.waitUntil(
|
|
70
|
+
caches.open(CACHE)
|
|
71
|
+
.then(function(c) { return c.add('/'); })
|
|
72
|
+
.then(function() { console.log('[SW] precached / — calling skipWaiting'); return self.skipWaiting(); })
|
|
73
|
+
.catch(function(err) { console.error('[SW] install failed:', err); throw err; })
|
|
74
|
+
);
|
|
75
|
+
});
|
|
65
76
|
|
|
66
77
|
self.addEventListener('activate', function(e) {
|
|
78
|
+
console.log('[SW] activating, cache:', CACHE);
|
|
67
79
|
e.waitUntil(
|
|
68
80
|
caches.keys()
|
|
69
|
-
.then(function(keys) {
|
|
70
|
-
|
|
81
|
+
.then(function(keys) {
|
|
82
|
+
var old = keys.filter(function(k) { return k !== CACHE; });
|
|
83
|
+
if (old.length) console.log('[SW] deleting old caches:', old);
|
|
84
|
+
return Promise.all(old.map(function(k) { return caches.delete(k); }));
|
|
85
|
+
})
|
|
86
|
+
.then(function() { console.log('[SW] claiming clients'); return self.clients.claim(); })
|
|
71
87
|
);
|
|
72
88
|
});
|
|
73
89
|
|
|
@@ -101,16 +117,24 @@ self.addEventListener('fetch', function(event) {
|
|
|
101
117
|
return;
|
|
102
118
|
}
|
|
103
119
|
|
|
104
|
-
// Navigation (HTML pages) →
|
|
120
|
+
// Navigation (HTML pages) → stale-while-revalidate
|
|
121
|
+
// Serves cached shell INSTANTLY (no white flash on refresh), then
|
|
122
|
+
// refreshes cache from network in the background.
|
|
105
123
|
if (request.mode === 'navigate') {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
console.log('[SW] navigate →', url.pathname);
|
|
125
|
+
event.respondWith(caches.open(CACHE).then(function(c) {
|
|
126
|
+
return c.match('/').then(function(hit) {
|
|
127
|
+
console.log('[SW] cache hit for /:', !!hit);
|
|
128
|
+
var net = fetch(request)
|
|
129
|
+
.then(function(r) {
|
|
130
|
+
console.log('[SW] network response for /:', r.status);
|
|
131
|
+
if (r.ok) c.put('/', r.clone());
|
|
132
|
+
return r;
|
|
133
|
+
})
|
|
134
|
+
.catch(function(err) { console.warn('[SW] network failed, using cache:', err.message); return hit; });
|
|
135
|
+
return hit || net;
|
|
136
|
+
});
|
|
137
|
+
}));
|
|
114
138
|
return;
|
|
115
139
|
}
|
|
116
140
|
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
// The last painted frame before teardown will be the dark splash
|
|
47
47
|
// instead of a white gap.
|
|
48
48
|
window.addEventListener('beforeunload', function () {
|
|
49
|
+
console.log('[splash] beforeunload — showing splash');
|
|
49
50
|
var s = document.getElementById('splash');
|
|
50
51
|
if (s) { s.style.transition = 'none'; s.style.display = 'flex'; s.style.opacity = '1'; }
|
|
51
52
|
});
|
|
@@ -53,16 +54,38 @@
|
|
|
53
54
|
<script type="module" src="/src/main.tsx"></script>
|
|
54
55
|
<script>
|
|
55
56
|
if('serviceWorker' in navigator){
|
|
57
|
+
console.log('[sw-reg] current controller:', navigator.serviceWorker.controller ? 'yes' : 'none');
|
|
58
|
+
// When a new SW takes control (update), show splash and reload
|
|
59
|
+
// so the new caching strategy kicks in immediately.
|
|
60
|
+
var swRefreshing=false;
|
|
61
|
+
navigator.serviceWorker.addEventListener('controllerchange',function(){
|
|
62
|
+
console.log('[sw-reg] controllerchange fired, refreshing:', swRefreshing);
|
|
63
|
+
if(swRefreshing)return;
|
|
64
|
+
swRefreshing=true;
|
|
65
|
+
var s=document.getElementById('splash');
|
|
66
|
+
if(s){s.style.transition='none';s.style.display='flex';s.style.opacity='1'}
|
|
67
|
+
console.log('[sw-reg] reloading after new SW took control');
|
|
68
|
+
location.reload();
|
|
69
|
+
});
|
|
56
70
|
navigator.serviceWorker.register('/sw.js').then(function(r){
|
|
71
|
+
console.log('[sw-reg] registered, active:', r.active?.state, 'waiting:', !!r.waiting, 'installing:', !!r.installing);
|
|
57
72
|
r.update();
|
|
58
|
-
if(r.waiting){
|
|
73
|
+
if(r.waiting){
|
|
74
|
+
console.log('[sw-reg] found waiting SW — sending SKIP_WAITING');
|
|
75
|
+
r.waiting.postMessage({type:'SKIP_WAITING'});
|
|
76
|
+
}
|
|
59
77
|
r.addEventListener('updatefound',function(){
|
|
78
|
+
console.log('[sw-reg] updatefound — new SW installing');
|
|
60
79
|
var w=r.installing;
|
|
61
80
|
if(w)w.addEventListener('statechange',function(){
|
|
62
|
-
|
|
81
|
+
console.log('[sw-reg] installing SW state:', w.state);
|
|
82
|
+
if(w.state==='installed'&&navigator.serviceWorker.controller){
|
|
83
|
+
console.log('[sw-reg] new SW installed — sending SKIP_WAITING');
|
|
84
|
+
w.postMessage({type:'SKIP_WAITING'});
|
|
85
|
+
}
|
|
63
86
|
});
|
|
64
87
|
});
|
|
65
|
-
});
|
|
88
|
+
}).catch(function(err){ console.error('[sw-reg] registration failed:', err); });
|
|
66
89
|
}
|
|
67
90
|
</script>
|
|
68
91
|
<script src="/fluxy/widget.js"></script>
|
|
@@ -11,15 +11,28 @@ const CACHE = 'fluxy-v4';
|
|
|
11
11
|
// Precache the HTML shell on install so the cache is never empty.
|
|
12
12
|
// Without this, the first navigation isn't intercepted (SW wasn't
|
|
13
13
|
// controlling yet), so refresh would find an empty cache → white screen.
|
|
14
|
-
self.addEventListener('install', (e) =>
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
self.addEventListener('install', (e) => {
|
|
15
|
+
console.log('[SW] installing, cache:', CACHE);
|
|
16
|
+
e.waitUntil(
|
|
17
|
+
caches.open(CACHE)
|
|
18
|
+
.then(c => c.add('/'))
|
|
19
|
+
.then(() => { console.log('[SW] precached / — calling skipWaiting'); return self.skipWaiting(); })
|
|
20
|
+
.catch(err => { console.error('[SW] install failed:', err); throw err; })
|
|
21
|
+
);
|
|
22
|
+
});
|
|
17
23
|
|
|
18
|
-
self.addEventListener('activate', (e) =>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
.
|
|
22
|
-
|
|
24
|
+
self.addEventListener('activate', (e) => {
|
|
25
|
+
console.log('[SW] activating, cache:', CACHE);
|
|
26
|
+
e.waitUntil(
|
|
27
|
+
caches.keys()
|
|
28
|
+
.then(keys => {
|
|
29
|
+
const old = keys.filter(k => k !== CACHE);
|
|
30
|
+
if (old.length) console.log('[SW] deleting old caches:', old);
|
|
31
|
+
return Promise.all(old.map(k => caches.delete(k)));
|
|
32
|
+
})
|
|
33
|
+
.then(() => { console.log('[SW] claiming clients'); return self.clients.claim(); })
|
|
34
|
+
);
|
|
35
|
+
});
|
|
23
36
|
|
|
24
37
|
self.addEventListener('message', (e) => {
|
|
25
38
|
if (e.data?.type === 'SKIP_WAITING') self.skipWaiting();
|
|
@@ -56,11 +69,17 @@ self.addEventListener('fetch', (event) => {
|
|
|
56
69
|
// refreshes cache from network in the background.
|
|
57
70
|
// First visit has no cache → falls through to network.
|
|
58
71
|
if (request.mode === 'navigate') {
|
|
72
|
+
console.log('[SW] navigate →', url.pathname);
|
|
59
73
|
event.respondWith(caches.open(CACHE).then(c =>
|
|
60
74
|
c.match('/').then(hit => {
|
|
75
|
+
console.log('[SW] cache hit for /:', !!hit);
|
|
61
76
|
const net = fetch(request)
|
|
62
|
-
.then(r => {
|
|
63
|
-
|
|
77
|
+
.then(r => {
|
|
78
|
+
console.log('[SW] network response for /:', r.status);
|
|
79
|
+
if (r.ok) c.put('/', r.clone());
|
|
80
|
+
return r;
|
|
81
|
+
})
|
|
82
|
+
.catch(err => { console.warn('[SW] network failed, using cache:', err.message); return hit; });
|
|
64
83
|
return hit || net;
|
|
65
84
|
})
|
|
66
85
|
));
|
|
@@ -12,10 +12,15 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
12
12
|
// Fade out the HTML splash screen once React has actually painted.
|
|
13
13
|
// React 18's createRoot().render() is async — wait for the next frame
|
|
14
14
|
// to ensure the app is visible before removing the splash.
|
|
15
|
+
console.log('[main] React render called, waiting for paint to hide splash');
|
|
15
16
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
17
|
+
console.log('[main] double-rAF fired — hiding splash');
|
|
16
18
|
const splash = document.getElementById('splash');
|
|
17
19
|
if (splash) {
|
|
18
20
|
splash.style.opacity = '0';
|
|
19
|
-
splash.addEventListener('transitionend', () => {
|
|
21
|
+
splash.addEventListener('transitionend', () => {
|
|
22
|
+
splash.style.display = 'none';
|
|
23
|
+
console.log('[main] splash hidden');
|
|
24
|
+
}, { once: true });
|
|
20
25
|
}
|
|
21
26
|
}));
|