fluxy-bot 0.9.4 → 0.9.6

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,8 +1,8 @@
1
1
  {
2
2
  "name": "fluxy-bot",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "releaseNotes": [
5
- "Fixing auto update",
5
+ "Another test for self update",
6
6
  "2. ",
7
7
  "3. ",
8
8
  "4. "
@@ -47,15 +47,86 @@ const MIME_TYPES: Record<string, string> = {
47
47
  };
48
48
 
49
49
  // Service worker content — embedded here so it ships with supervisor/ (always updated)
50
- const SW_JS = `// Service worker PWA installability + push notifications
51
- self.addEventListener('install', () => self.skipWaiting());
52
- self.addEventListener('activate', (e) => e.waitUntil(self.clients.claim()));
53
- self.addEventListener('fetch', () => {});
54
-
55
- // Push notification
56
- self.addEventListener('push', (event) => {
57
- let data = { title: 'Fluxy', body: 'New message' };
58
- try { data = event.data.json(); } catch {}
50
+ // Keep in sync with workspace/client/public/sw.js (prod builds)
51
+ const SW_JS = `// Service worker — app-shell caching + push notifications
52
+ // Caching strategy:
53
+ // Hashed assets (/assets/*-AbCd12.js) cache-first (immutable)
54
+ // Navigation (HTML) → network-first, cache fallback
55
+ // Static assets (img/video/fonts) → stale-while-revalidate
56
+ // JS/CSS modules → stale-while-revalidate
57
+ // API, WebSocket, Vite internals → network-only (no cache)
58
+
59
+ var CACHE = 'fluxy-v2';
60
+ var HASHED_RE = new RegExp('/assets/.+-[a-zA-Z0-9]{6,}[.](js|css)$');
61
+
62
+ self.addEventListener('install', function() { self.skipWaiting(); });
63
+
64
+ self.addEventListener('activate', function(e) {
65
+ e.waitUntil(
66
+ caches.keys()
67
+ .then(function(keys) { return Promise.all(keys.filter(function(k) { return k !== CACHE; }).map(function(k) { return caches.delete(k); })); })
68
+ .then(function() { return self.clients.claim(); })
69
+ );
70
+ });
71
+
72
+ self.addEventListener('message', function(e) {
73
+ if (e.data && e.data.type === 'SKIP_WAITING') self.skipWaiting();
74
+ });
75
+
76
+ self.addEventListener('fetch', function(event) {
77
+ var request = event.request;
78
+ var url = new URL(request.url);
79
+
80
+ // Network-only: never cache these
81
+ if (
82
+ request.method !== 'GET' ||
83
+ url.pathname.indexOf('/api/') === 0 ||
84
+ url.pathname.indexOf('/app/api/') === 0 ||
85
+ url.pathname.slice(-3) === '/ws' ||
86
+ url.pathname === '/sw.js' ||
87
+ url.pathname === '/fluxy/sw.js' ||
88
+ url.pathname.indexOf('/@') === 0 ||
89
+ url.pathname.indexOf('/__') === 0
90
+ ) return;
91
+
92
+ // Hashed assets (immutable, content-addressed) → cache-first
93
+ if (HASHED_RE.test(url.pathname)) {
94
+ event.respondWith(caches.open(CACHE).then(function(c) {
95
+ return c.match(request).then(function(hit) {
96
+ return hit || fetch(request).then(function(r) { if (r.ok) c.put(request, r.clone()); return r; });
97
+ });
98
+ }));
99
+ return;
100
+ }
101
+
102
+ // Navigation (HTML pages) → network-first, cached shell fallback
103
+ if (request.mode === 'navigate') {
104
+ event.respondWith(
105
+ fetch(request)
106
+ .then(function(r) {
107
+ if (r.ok) caches.open(CACHE).then(function(c) { c.put(request, r.clone()); });
108
+ return r;
109
+ })
110
+ .catch(function() { return caches.match(request).then(function(r) { return r || caches.match('/'); }); })
111
+ );
112
+ return;
113
+ }
114
+
115
+ // Everything else → stale-while-revalidate
116
+ event.respondWith(caches.open(CACHE).then(function(c) {
117
+ return c.match(request).then(function(hit) {
118
+ var net = fetch(request)
119
+ .then(function(r) { if (r.ok) c.put(request, r.clone()); return r; })
120
+ .catch(function() { return hit; });
121
+ return hit || net;
122
+ });
123
+ }));
124
+ });
125
+
126
+ // Push notifications
127
+ self.addEventListener('push', function(event) {
128
+ var data = { title: 'Fluxy', body: 'New message' };
129
+ try { data = event.data.json(); } catch(e) {}
59
130
  event.waitUntil(
60
131
  self.registration.showNotification(data.title || 'Fluxy', {
61
132
  body: data.body || '',
@@ -69,13 +140,13 @@ self.addEventListener('push', (event) => {
69
140
  });
70
141
 
71
142
  // Notification click — focus or open app
72
- self.addEventListener('notificationclick', (event) => {
143
+ self.addEventListener('notificationclick', function(event) {
73
144
  event.notification.close();
74
- const url = event.notification.data?.url || '/';
145
+ var url = (event.notification.data && event.notification.data.url) || '/';
75
146
  event.waitUntil(
76
- self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clients) => {
77
- for (const client of clients) {
78
- if (client.url.includes('/fluxy') && 'focus' in client) return client.focus();
147
+ self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function(clients) {
148
+ for (var i = 0; i < clients.length; i++) {
149
+ if (clients[i].url.indexOf('/fluxy') !== -1 && 'focus' in clients[i]) return clients[i].focus();
79
150
  }
80
151
  return self.clients.openWindow(url);
81
152
  })
@@ -87,7 +158,7 @@ const RECOVERING_HTML = `<!DOCTYPE html><html><head><meta charset="UTF-8"><meta
87
158
  <style>body{background:#0a0a0f;color:#94a3b8;font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
88
159
  div{text-align:center}h1{font-size:18px;margin-bottom:8px;color:#e2e8f0}p{font-size:14px}a{color:#60a5fa}</style></head>
89
160
  <body><div><h1>Dashboard is restarting...</h1><p>Refreshing automatically.</p></div>
90
- <script>setTimeout(()=>location.reload(),3000)</script>
161
+ <script>console.error('[refresh-diag] RECOVERING_HTML loaded — will auto-reload in 3s');setTimeout(()=>{console.error('[refresh-diag] RECOVERING_HTML 3s timer fired — reloading');location.reload()},3000)</script>
91
162
  <script src="/fluxy/widget.js"></script></body></html>`;
92
163
 
93
164
  export async function startSupervisor() {
@@ -33,12 +33,21 @@
33
33
  <script>
34
34
  if('serviceWorker' in navigator){
35
35
  navigator.serviceWorker.register('/sw.js').then(function(r){
36
+ console.warn('[refresh-diag] SW registered, state:', r.active?.state, 'waiting:', !!r.waiting);
36
37
  r.update();
37
- if(r.waiting){r.waiting.postMessage({type:'SKIP_WAITING'})}
38
+ if(r.waiting){
39
+ console.warn('[refresh-diag] SW: found waiting worker — sending SKIP_WAITING');
40
+ r.waiting.postMessage({type:'SKIP_WAITING'});
41
+ }
38
42
  r.addEventListener('updatefound',function(){
43
+ console.warn('[refresh-diag] SW: updatefound — new version installing');
39
44
  var w=r.installing;
40
45
  if(w)w.addEventListener('statechange',function(){
41
- if(w.state==='installed'&&navigator.serviceWorker.controller)w.postMessage({type:'SKIP_WAITING'});
46
+ console.warn('[refresh-diag] SW installing statechange:', w.state);
47
+ if(w.state==='installed'&&navigator.serviceWorker.controller){
48
+ console.warn('[refresh-diag] SW: new version installed while controller exists — sending SKIP_WAITING');
49
+ w.postMessage({type:'SKIP_WAITING'});
50
+ }
42
51
  });
43
52
  });
44
53
  });
@@ -1,14 +1,82 @@
1
- // Service worker — PWA installability + push notifications
1
+ // Service worker — app-shell caching + push notifications
2
+ // Caching strategy:
3
+ // Hashed assets (/assets/*-AbCd12.js) → cache-first (immutable)
4
+ // Navigation (HTML) → network-first, cache fallback
5
+ // Static assets (img/video/fonts) → stale-while-revalidate
6
+ // JS/CSS modules → stale-while-revalidate
7
+ // API, WebSocket, Vite internals → network-only (no cache)
8
+
9
+ const CACHE = 'fluxy-v2';
10
+
2
11
  self.addEventListener('install', () => self.skipWaiting());
3
- self.addEventListener('activate', (e) => e.waitUntil(self.clients.claim()));
4
- self.addEventListener('fetch', () => {});
5
12
 
6
- // Push notification
13
+ self.addEventListener('activate', (e) => e.waitUntil(
14
+ caches.keys()
15
+ .then(keys => Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k))))
16
+ .then(() => self.clients.claim())
17
+ ));
18
+
19
+ self.addEventListener('message', (e) => {
20
+ if (e.data?.type === 'SKIP_WAITING') self.skipWaiting();
21
+ });
22
+
23
+ self.addEventListener('fetch', (event) => {
24
+ const { request } = event;
25
+ const url = new URL(request.url);
26
+
27
+ // ── Network-only: never cache these ──────────────────────────────
28
+ if (
29
+ request.method !== 'GET' ||
30
+ url.pathname.startsWith('/api/') ||
31
+ url.pathname.startsWith('/app/api/') ||
32
+ url.pathname.endsWith('/ws') ||
33
+ url.pathname === '/sw.js' ||
34
+ url.pathname === '/fluxy/sw.js' ||
35
+ url.pathname.startsWith('/@') ||
36
+ url.pathname.startsWith('/__')
37
+ ) return;
38
+
39
+ // ── Hashed assets (immutable, content-addressed) → cache-first ──
40
+ if (/\/assets\/.+-[a-zA-Z0-9]{6,}\.(js|css)$/.test(url.pathname)) {
41
+ event.respondWith(caches.open(CACHE).then(c =>
42
+ c.match(request).then(hit =>
43
+ hit || fetch(request).then(r => { if (r.ok) c.put(request, r.clone()); return r; })
44
+ )
45
+ ));
46
+ return;
47
+ }
48
+
49
+ // ── Navigation (HTML pages) → network-first, cached shell fallback ──
50
+ // On restore after OS kill: if network is slow, show cached shell instantly
51
+ if (request.mode === 'navigate') {
52
+ event.respondWith(
53
+ fetch(request)
54
+ .then(r => {
55
+ if (r.ok) caches.open(CACHE).then(c => c.put(request, r.clone()));
56
+ return r;
57
+ })
58
+ .catch(() => caches.match(request).then(r => r || caches.match('/')))
59
+ );
60
+ return;
61
+ }
62
+
63
+ // ── Everything else → stale-while-revalidate ────────────────────
64
+ // Serves cached version instantly, refreshes cache in background.
65
+ // Covers: JS/CSS modules, images, video, fonts, manifest, etc.
66
+ event.respondWith(caches.open(CACHE).then(c =>
67
+ c.match(request).then(hit => {
68
+ const net = fetch(request)
69
+ .then(r => { if (r.ok) c.put(request, r.clone()); return r; })
70
+ .catch(() => hit);
71
+ return hit || net;
72
+ })
73
+ ));
74
+ });
75
+
76
+ // ── Push notifications ─────────────────────────────────────────────
7
77
  self.addEventListener('push', (event) => {
8
78
  let data = { title: 'Fluxy', body: 'New message' };
9
- try {
10
- data = event.data.json();
11
- } catch {}
79
+ try { data = event.data.json(); } catch {}
12
80
 
13
81
  event.waitUntil(
14
82
  self.registration.showNotification(data.title || 'Fluxy', {
@@ -30,9 +98,7 @@ self.addEventListener('notificationclick', (event) => {
30
98
  event.waitUntil(
31
99
  self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clients) => {
32
100
  for (const client of clients) {
33
- if (client.url.includes('/fluxy') && 'focus' in client) {
34
- return client.focus();
35
- }
101
+ if (client.url.includes('/fluxy') && 'focus' in client) return client.focus();
36
102
  }
37
103
  return self.clients.openWindow(url);
38
104
  })
@@ -27,6 +27,79 @@ export default function App() {
27
27
  const [rebuildState, setRebuildState] = useState<'idle' | 'rebuilding' | 'error'>('idle');
28
28
  const [buildError, setBuildError] = useState('');
29
29
 
30
+ // ── Refresh diagnostics ──────────────────────────────────────────
31
+ // Logs every possible reload trigger so we can trace phantom refreshes.
32
+ // Safe to remove once the mystery is solved.
33
+ useEffect(() => {
34
+ const t0 = Date.now();
35
+ const tag = `[refresh-diag ${new Date().toISOString()}]`;
36
+
37
+ // 1. Log page load reason
38
+ const navEntries = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[];
39
+ const navType = navEntries[0]?.type ?? 'unknown'; // 'navigate' | 'reload' | 'back_forward' | 'prerender'
40
+ console.warn(`${tag} Page loaded — navType="${navType}", wasDiscarded=${document.wasDiscarded ?? 'N/A'}, visState="${document.visibilityState}"`);
41
+
42
+ // 2. Intercept location.reload to capture stack trace
43
+ const origReload = location.reload.bind(location);
44
+ location.reload = () => {
45
+ console.error(`${tag} ⚠ location.reload() called! Stack trace:`);
46
+ console.trace();
47
+ origReload();
48
+ };
49
+
50
+ // 3. Service Worker controller changes
51
+ if (navigator.serviceWorker) {
52
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
53
+ console.warn(`${tag} SW controllerchange — new SW took control (can cause reload in some browsers)`);
54
+ });
55
+ }
56
+
57
+ // 4. Vite HMR events (Vite injects import.meta.hot on the client)
58
+ if (import.meta.hot) {
59
+ // Vite fires 'vite:beforeFullReload' before a full page reload
60
+ import.meta.hot.on('vite:beforeFullReload', (payload: unknown) => {
61
+ console.error(`${tag} ⚠ Vite HMR: full-reload triggered!`, payload);
62
+ console.trace();
63
+ });
64
+ import.meta.hot.on('vite:beforeUpdate', (payload: unknown) => {
65
+ console.warn(`${tag} Vite HMR: beforeUpdate`, payload);
66
+ });
67
+ import.meta.hot.on('vite:error', (payload: unknown) => {
68
+ console.error(`${tag} Vite HMR: error`, payload);
69
+ });
70
+ import.meta.hot.on('vite:ws:disconnect', () => {
71
+ console.warn(`${tag} Vite HMR: WebSocket disconnected`);
72
+ });
73
+ import.meta.hot.on('vite:ws:connect', () => {
74
+ console.warn(`${tag} Vite HMR: WebSocket reconnected (may trigger full-reload if stale)`);
75
+ });
76
+ }
77
+
78
+ // 5. Visibility + focus changes (iOS kills PWA processes in background)
79
+ const onVisChange = () => console.warn(`${tag} visibilitychange → "${document.visibilityState}" (uptime: ${((Date.now() - t0) / 1000).toFixed(1)}s)`);
80
+ const onPageShow = (e: PageTransitionEvent) => console.warn(`${tag} pageshow — persisted=${e.persisted} (bfcache restore)`);
81
+ const onFreeze = () => console.warn(`${tag} freeze — page is being frozen by OS`);
82
+ const onResume = () => console.warn(`${tag} resume — page resumed from frozen state`);
83
+ document.addEventListener('visibilitychange', onVisChange);
84
+ window.addEventListener('pageshow', onPageShow);
85
+ document.addEventListener('freeze', onFreeze);
86
+ document.addEventListener('resume', onResume);
87
+
88
+ // 6. beforeunload — last chance to log before the page goes away
89
+ const onBeforeUnload = () => {
90
+ console.warn(`${tag} beforeunload — page is about to unload (uptime: ${((Date.now() - t0) / 1000).toFixed(1)}s)`);
91
+ };
92
+ window.addEventListener('beforeunload', onBeforeUnload);
93
+
94
+ return () => {
95
+ document.removeEventListener('visibilitychange', onVisChange);
96
+ window.removeEventListener('pageshow', onPageShow);
97
+ document.removeEventListener('freeze', onFreeze);
98
+ document.removeEventListener('resume', onResume);
99
+ window.removeEventListener('beforeunload', onBeforeUnload);
100
+ };
101
+ }, []);
102
+
30
103
  useEffect(() => {
31
104
  fetch('/api/settings')
32
105
  .then((r) => r.json())
@@ -41,16 +114,22 @@ export default function App() {
41
114
  let safetyTimer: ReturnType<typeof setTimeout>;
42
115
  const handler = (e: MessageEvent) => {
43
116
  if (e.data?.type === 'fluxy:rebuilding') {
117
+ console.warn('[refresh-diag] fluxy:rebuilding received — starting 60s safety timer');
44
118
  setRebuildState('rebuilding');
45
119
  setBuildError('');
46
120
  // Safety: auto-reload after 60s in case app:rebuilt message is lost
47
121
  clearTimeout(safetyTimer);
48
- safetyTimer = setTimeout(() => location.reload(), 60_000);
122
+ safetyTimer = setTimeout(() => {
123
+ console.error('[refresh-diag] ⚠ 60s safety timer fired — forcing reload');
124
+ location.reload();
125
+ }, 60_000);
49
126
  } else if (e.data?.type === 'fluxy:rebuilt') {
127
+ console.warn('[refresh-diag] fluxy:rebuilt received — reloading now');
50
128
  clearTimeout(safetyTimer);
51
129
  setRebuildState('idle');
52
130
  location.reload();
53
131
  } else if (e.data?.type === 'fluxy:build-error') {
132
+ console.warn('[refresh-diag] fluxy:build-error received:', e.data.error);
54
133
  clearTimeout(safetyTimer);
55
134
  setRebuildState('error');
56
135
  setBuildError(e.data.error || 'Build failed');