Skip to content

Commit dc1ea22

Browse files
committed
auto update website without user input
1 parent f478aec commit dc1ea22

File tree

1 file changed

+135
-7
lines changed

1 file changed

+135
-7
lines changed

src/app/app.component.ts

Lines changed: 135 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,22 @@ export class AppComponent implements OnInit, OnDestroy {
1313
loginPromptVisible = false;
1414
isLoggedIn = false;
1515
private sessionInvalidSub: Subscription | null = null;
16+
private swVersionSub: Subscription | null = null;
17+
private swUnrecoverableSub: Subscription | null = null;
18+
private updateCheckIntervalId: any = null;
19+
private updateCheckHandler: (() => void) | null = null;
20+
private visibilityChangeHandler: (() => void) | null = null;
21+
private userActivityHandler: (() => void) | null = null;
22+
private pendingReload = false;
23+
private reloadStartAt: number | null = null;
24+
private reloadTimerId: any = null;
25+
private lastUserActivityAt = Date.now();
26+
private readonly appStartAt = Date.now();
27+
28+
private readonly updateCheckIntervalMs = 5 * 60_000; // 5 minutes
29+
private readonly idleBeforeReloadMs = 30_000; // 30s of inactivity
30+
private readonly maxReloadDelayMs = 5 * 60_000; // force refresh within 5 minutes
31+
private readonly startupImmediateReloadWindowMs = 10_000; // reload immediately if update is found within first 10s after load
1632

1733
constructor(
1834
private swUpdate: SwUpdate,
@@ -44,6 +60,33 @@ export class AppComponent implements OnInit, OnDestroy {
4460
if (this.sessionInvalidSub) {
4561
this.sessionInvalidSub.unsubscribe();
4662
}
63+
if (this.swVersionSub) {
64+
this.swVersionSub.unsubscribe();
65+
}
66+
if (this.swUnrecoverableSub) {
67+
this.swUnrecoverableSub.unsubscribe();
68+
}
69+
if (this.updateCheckIntervalId) {
70+
clearInterval(this.updateCheckIntervalId);
71+
this.updateCheckIntervalId = null;
72+
}
73+
if (this.reloadTimerId) {
74+
clearTimeout(this.reloadTimerId);
75+
this.reloadTimerId = null;
76+
}
77+
if (this.updateCheckHandler) {
78+
window.removeEventListener('focus', this.updateCheckHandler);
79+
window.removeEventListener('online', this.updateCheckHandler);
80+
}
81+
if (this.visibilityChangeHandler) {
82+
document.removeEventListener('visibilitychange', this.visibilityChangeHandler);
83+
}
84+
if (this.userActivityHandler) {
85+
window.removeEventListener('pointerdown', this.userActivityHandler, true);
86+
window.removeEventListener('keydown', this.userActivityHandler, true);
87+
window.removeEventListener('touchstart', this.userActivityHandler, true);
88+
window.removeEventListener('wheel', this.userActivityHandler, true);
89+
}
4790
}
4891

4992
private async validateSessionOnStartup(): Promise<void> {
@@ -60,20 +103,105 @@ export class AppComponent implements OnInit, OnDestroy {
60103

61104
checkForUpdates() {
62105
if (this.swUpdate.isEnabled) {
63-
this.swUpdate.checkForUpdate().then(() => {
64-
console.log("Checked for updates");
65-
});
106+
this.updateCheckHandler = () => {
107+
if (typeof navigator !== 'undefined' && navigator && 'onLine' in navigator && !navigator.onLine) return;
108+
this.swUpdate
109+
.checkForUpdate()
110+
.then(found => {
111+
if (found) {
112+
console.log('New version detected');
113+
this.activateUpdateAndReload();
114+
}
115+
})
116+
.catch(err => {
117+
console.warn('Service worker update check failed', err);
118+
});
119+
};
120+
121+
this.visibilityChangeHandler = () => {
122+
if (document.visibilityState === 'visible') {
123+
this.updateCheckHandler?.();
124+
} else if (this.pendingReload) {
125+
this.tryReloadForUpdate();
126+
}
127+
};
66128

67-
this.swUpdate.versionUpdates.subscribe(event => {
129+
this.userActivityHandler = () => {
130+
this.lastUserActivityAt = Date.now();
131+
};
132+
133+
window.addEventListener('focus', this.updateCheckHandler);
134+
window.addEventListener('online', this.updateCheckHandler);
135+
document.addEventListener('visibilitychange', this.visibilityChangeHandler);
136+
window.addEventListener('pointerdown', this.userActivityHandler, true);
137+
window.addEventListener('keydown', this.userActivityHandler, true);
138+
window.addEventListener('touchstart', this.userActivityHandler, true);
139+
window.addEventListener('wheel', this.userActivityHandler, true);
140+
141+
this.swVersionSub = this.swUpdate.versionUpdates.subscribe(event => {
68142
if (event.type === 'VERSION_READY') {
69-
if (confirm("New version available. Load New Version?")) {
70-
window.location.reload();
71-
}
143+
this.activateUpdateAndReload();
144+
} else if (event.type === 'VERSION_INSTALLATION_FAILED') {
145+
console.warn('Service worker update installation failed', event.error);
72146
}
73147
});
148+
149+
this.swUnrecoverableSub = this.swUpdate.unrecoverable.subscribe(event => {
150+
console.warn('Service worker unrecoverable state, reloading', event.reason);
151+
window.location.reload();
152+
});
153+
154+
this.updateCheckHandler();
155+
this.updateCheckIntervalId = setInterval(this.updateCheckHandler, this.updateCheckIntervalMs);
74156
}
75157
}
76158

159+
private activateUpdateAndReload() {
160+
if (this.pendingReload) return;
161+
this.pendingReload = true;
162+
this.reloadStartAt = Date.now();
163+
164+
this.swUpdate
165+
.activateUpdate()
166+
.then(activated => {
167+
if (activated) console.log('Activated new version');
168+
this.tryReloadForUpdate();
169+
})
170+
.catch(err => {
171+
console.warn('Service worker activateUpdate failed', err);
172+
this.tryReloadForUpdate();
173+
});
174+
}
175+
176+
private tryReloadForUpdate() {
177+
if (!this.pendingReload) return;
178+
179+
const now = Date.now();
180+
const waitedMs = this.reloadStartAt ? now - this.reloadStartAt : 0;
181+
const idleMs = now - this.lastUserActivityAt;
182+
const detectedAt = this.reloadStartAt ?? now;
183+
const detectedSinceStartMs = detectedAt - this.appStartAt;
184+
const shouldReloadImmediatelyOnStartup = detectedSinceStartMs <= this.startupImmediateReloadWindowMs;
185+
186+
const shouldReload =
187+
shouldReloadImmediatelyOnStartup ||
188+
document.visibilityState === 'hidden' ||
189+
!document.hasFocus() ||
190+
idleMs >= this.idleBeforeReloadMs ||
191+
waitedMs >= this.maxReloadDelayMs;
192+
193+
if (shouldReload) {
194+
window.location.reload();
195+
return;
196+
}
197+
198+
if (this.reloadTimerId) return;
199+
this.reloadTimerId = setTimeout(() => {
200+
this.reloadTimerId = null;
201+
this.tryReloadForUpdate();
202+
}, 5_000);
203+
}
204+
77205
onLoginModalVisibleChange(visible: boolean) {
78206
this.loginPromptVisible = visible;
79207
}

0 commit comments

Comments
 (0)