Fix PWA redirect loop and installation options after uninstalling PWA

This commit is contained in:
David Frassi
2026-06-18 02:36:52 +02:00
parent 93385097f2
commit b0bb5735c8
20 changed files with 466 additions and 145 deletions
+183 -30
View File
@@ -26,6 +26,9 @@ export class AppComponent implements OnInit {
public showRedirectOverlay = signal<boolean>(false);
public showInstallOverlay = signal<boolean>(false);
public isInstalling = signal<boolean>(false);
public isRedirecting = signal<boolean>(false);
public isPwaInstalled = signal<boolean>(false);
public redirectFailed = signal<boolean>(false);
public protocolLink = '';
@@ -45,13 +48,12 @@ export class AppComponent implements OnInit {
return;
}
// Se l'overlay di redirect o di installazione è mostrato, NON nascondiamo il loader iniziale
// per rimanere nella welcome page mentre l'utente sceglie.
if (this.showRedirectOverlay() || this.showInstallOverlay()) {
// Se stiamo attivamente installando o reindirizzando, NON nascondiamo il loader
if (this.isInstalling() || this.isRedirecting()) {
return;
}
// Altrimenti, nascondiamo il loader per far entrare l'utente nell'app
// Altrimenti, nascondiamo il loader per far entrare l'utente
if ((window as any).PwaLoader) {
(window as any).PwaLoader.hide();
}
@@ -95,6 +97,15 @@ export class AppComponent implements OnInit {
// Set signal indicating startup version check is complete
this.settingsService.isVersionCheckComplete.set(true);
// Handle PWA Launch Queue if supported (focus-existing launch behavior)
if ('launchQueue' in window) {
(window as any).launchQueue.setConsumer((launchParams: any) => {
if (launchParams.targetURL) {
this.handleLaunchUrl(launchParams.targetURL);
}
});
}
this.route.queryParams.subscribe(params => {
const protocolUrl = params['url'];
if (protocolUrl && protocolUrl.startsWith('web+canti:')) {
@@ -120,10 +131,73 @@ export class AppComponent implements OnInit {
}
}
});
window.addEventListener('appinstalled', () => {
console.log('[AppComponent] PWA appinstalled event caught.');
localStorage.setItem('pwa-installed', 'true');
this.isPwaInstalled.set(true);
this.isInstalling.set(false);
this.isRedirecting.set(true);
this.showInstallOverlay.set(false);
this.showRedirectOverlay.set(false);
if ((window as any).PwaLoader) {
(window as any).PwaLoader.show();
(window as any).PwaLoader.update({
title: 'Chiudi il browser',
desc: 'Applicazione installata con successo! Chiudi il browser e continua sulla PWA.',
isRedirect: true
});
}
setTimeout(() => {
window.location.href = this.protocolLink;
}, 1000);
});
this.checkAndRedirectToPwa();
}
handleLaunchUrl(urlStr: string) {
try {
const urlObj = new URL(urlStr);
// 1. Check if it's a protocol link inside query params (e.g. /?url=web+canti://...)
const protocolUrl = urlObj.searchParams.get('url');
if (protocolUrl && protocolUrl.startsWith('web+canti:')) {
const cleanUrl = protocolUrl.replace('web+canti://', 'http://localhost/');
const innerUrlObj = new URL(cleanUrl);
let targetPath = innerUrlObj.pathname;
if (targetPath === '/open' || targetPath === '//open') {
targetPath = '/';
} else if (targetPath.startsWith('/open/')) {
targetPath = targetPath.substring(5);
}
const queryParams: any = {};
innerUrlObj.searchParams.forEach((value, key) => {
queryParams[key] = value;
});
console.log('[AppComponent] Launch queue routing (protocol) to:', targetPath, queryParams);
this.router.navigate([targetPath], { queryParams, replaceUrl: true });
return;
}
// 2. Otherwise, route directly to the pathname and query params of the URL
let targetPath = urlObj.pathname;
const queryParams: any = {};
urlObj.searchParams.forEach((value, key) => {
queryParams[key] = value;
});
console.log('[AppComponent] Launch queue routing (direct) to:', targetPath, queryParams);
this.router.navigate([targetPath], { queryParams, replaceUrl: true });
} catch (e) {
console.error('Failed to parse launch url:', urlStr, e);
}
}
async checkVersionSync(): Promise<boolean> {
// Controlla SEMPRE version.json per primo — è il modo più affidabile per
// rilevare un disallineamento di versione, indipendentemente dallo stato del SW.
@@ -304,30 +378,49 @@ export class AppComponent implements OnInit {
}
}
if (isInstalled) {
const skipRedirect = sessionStorage.getItem('skip-pwa-redirect') === 'true';
if (!skipRedirect) {
this.showRedirectOverlay.set(true);
this.redirectFailed.set(false);
if ((window as any).PwaLoader) {
(window as any).PwaLoader.update({ isRedirect: true });
// Se non è rilevata in localStorage/relatedApps ed è Android o Desktop con supporto ai prompt:
// attendiamo 1.5s per dare tempo all'evento 'beforeinstallprompt' di scattare.
// Se non scatta, significa che l'app è già installata.
if (!isInstalled && !this.settingsService.isIos() && ('onbeforeinstallprompt' in window)) {
await new Promise(resolve => setTimeout(resolve, 1500));
isInstalled = localStorage.getItem('pwa-installed') === 'true';
if (!isInstalled) {
const hasPrompt = this.settingsService.showInstallButton() || this.settingsService.deferredPrompt();
if (!hasPrompt) {
console.log('[AppComponent] PWA detected as already installed (onbeforeinstallprompt supported but no prompt fired).');
isInstalled = true;
localStorage.setItem('pwa-installed', 'true');
}
// Tentiamo il reindirizzamento automatico
setTimeout(() => {
window.location.href = this.protocolLink;
// Se dopo 2 secondi l'utente è ancora qui, probabilmente l'app non è installata
setTimeout(() => {
if (this.showRedirectOverlay()) {
this.redirectFailed.set(true);
localStorage.setItem('pwa-installed', 'false');
if ((window as any).PwaLoader) {
(window as any).PwaLoader.hide();
}
}
}, 2000);
}, 800);
}
}
this.isPwaInstalled.set(isInstalled);
if (isInstalled) {
if (sessionStorage.getItem('skip-pwa-redirect') === 'true') {
this.showRedirectOverlay.set(false);
this.checkLoaderDismissal();
return;
}
this.showRedirectOverlay.set(true);
this.redirectFailed.set(false);
if ((window as any).PwaLoader) {
(window as any).PwaLoader.update({ isRedirect: true });
}
// Tentiamo il reindirizzamento automatico
setTimeout(() => {
window.location.href = this.protocolLink;
// Se dopo 2 secondi l'utente è ancora qui, mostriamo lo stato fallito per aprire manualmente o indicare la disinstallazione
setTimeout(() => {
if (this.showRedirectOverlay()) {
this.redirectFailed.set(true);
if ((window as any).PwaLoader) {
(window as any).PwaLoader.hide();
}
}
}, 2000);
}, 800);
} else {
// Se non è installata, proponiamo l'installazione immediata per evitare la cache del browser e avere un'esperienza ottimale
const skipInstall = sessionStorage.getItem('skip-pwa-install') === 'true';
@@ -335,6 +428,8 @@ export class AppComponent implements OnInit {
const hasDesktopPrompt = this.settingsService.showInstallButton() || this.settingsService.deferredPrompt();
if (!skipInstall && (isMobile || hasDesktopPrompt)) {
this.showInstallOverlay.set(true);
} else {
this.checkLoaderDismissal();
}
}
}
@@ -360,22 +455,80 @@ export class AppComponent implements OnInit {
stayInBrowser() {
sessionStorage.setItem('skip-pwa-redirect', 'true');
this.showRedirectOverlay.set(false);
this.checkLoaderDismissal();
}
stayInBrowserForceUninstallCheck() {
localStorage.setItem('pwa-installed', 'false');
this.isPwaInstalled.set(false);
this.stayInBrowser();
}
closeInstallOverlay() {
sessionStorage.setItem('skip-pwa-install', 'true');
this.showInstallOverlay.set(false);
this.checkLoaderDismissal();
}
async triggerInstall() {
await this.settingsService.installPwa();
this.closeInstallOverlay();
this.isInstalling.set(true);
this.showInstallOverlay.set(false);
if ((window as any).PwaLoader) {
(window as any).PwaLoader.show();
(window as any).PwaLoader.update({
title: 'Installazione in corso...',
desc: 'Completa l\'installazione tramite la finestra del browser.'
});
}
const outcome = await this.settingsService.installPwa();
if (outcome === 'accepted') {
this.isInstalling.set(false);
this.isRedirecting.set(true);
if ((window as any).PwaLoader) {
(window as any).PwaLoader.update({
title: 'Apertura Applicazione...',
desc: 'Installazione completata! Chiudi il browser e continua sulla PWA.',
isRedirect: true
});
}
} else {
this.isInstalling.set(false);
this.isRedirecting.set(false);
this.showInstallOverlay.set(true);
this.checkLoaderDismissal();
}
}
async triggerInstallFromRedirect() {
await this.settingsService.installPwa();
sessionStorage.setItem('skip-pwa-redirect', 'true');
this.isInstalling.set(true);
this.showRedirectOverlay.set(false);
if ((window as any).PwaLoader) {
(window as any).PwaLoader.show();
(window as any).PwaLoader.update({
title: 'Installazione in corso...',
desc: 'Completa l\'installazione tramite la finestra del browser.'
});
}
const outcome = await this.settingsService.installPwa();
if (outcome === 'accepted') {
sessionStorage.setItem('skip-pwa-redirect', 'true');
this.isInstalling.set(false);
this.isRedirecting.set(true);
if ((window as any).PwaLoader) {
(window as any).PwaLoader.update({
title: 'Apertura Applicazione...',
desc: 'Installazione completata! Chiudi il browser e continua sulla PWA.',
isRedirect: true
});
}
} else {
this.isInstalling.set(false);
this.isRedirecting.set(false);
this.showRedirectOverlay.set(true);
this.checkLoaderDismissal();
}
}
}