From 1afd3acb7f43cc067cd2335785e965c73052e64d Mon Sep 17 00:00:00 2001 From: simpgps Date: Tue, 9 Jun 2026 07:56:21 +0800 Subject: [PATCH 1/3] fix: open target URLs in default browser when running standalone PWA --- src/ts/modules/Home.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/ts/modules/Home.ts b/src/ts/modules/Home.ts index a342aefd1..5e334126f 100644 --- a/src/ts/modules/Home.ts +++ b/src/ts/modules/Home.ts @@ -352,13 +352,31 @@ export default class Home { return; } + let newWindow: Window | null = null; + const isStandalone = this.env.isRunningStandalone(); + if (isStandalone) { + try { + newWindow = window.open("about:blank", "_blank"); + } catch (e) { + this.env.logger.error("Failed to open blank window: " + String(e)); + } + } + // Must create new env instance here, // because extraNamespace might have changed reachability, // or asking for a not yet parsed Github namespace. const envQuery = new Env({ context: "index" }); const params: EnvParams = Env.getParamsFromUrl(); params.query = this.queryInput.value; - await envQuery.populate(params); + + try { + await envQuery.populate(params); + } catch (error) { + if (newWindow) { + newWindow.close(); + } + throw error; + } const response: RedirectResponse = CallHandler.getRedirectResponse(envQuery); @@ -367,6 +385,9 @@ export default class Home { const processUrl = this.env.buildProcessUrl({ query: this.queryInput.value, }); + if (newWindow) { + newWindow.close(); + } window.location.href = processUrl; return; } @@ -374,8 +395,15 @@ export default class Home { let redirectUrl: string; if (response.status === "found") { redirectUrl = response.redirectUrl as string; + if (isStandalone && newWindow) { + newWindow.location.href = redirectUrl; + return; + } } else { redirectUrl = CallHandler.getRedirectUrlToHome(envQuery, response); + if (newWindow) { + newWindow.close(); + } } window.location.href = redirectUrl; }; From ac8c0d8acba86bab5b64b1d53e6b550d2fd27608 Mon Sep 17 00:00:00 2001 From: simpgps Date: Wed, 10 Jun 2026 06:28:16 +0800 Subject: [PATCH 2/3] fix: optimize redirect in standalone PWA mode using synchronous window.open --- src/ts/modules/Home.ts | 48 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/ts/modules/Home.ts b/src/ts/modules/Home.ts index 5e334126f..fbc683293 100644 --- a/src/ts/modules/Home.ts +++ b/src/ts/modules/Home.ts @@ -352,8 +352,50 @@ export default class Home { return; } - let newWindow: Window | null = null; + const query = this.queryInput.value; const isStandalone = this.env.isRunningStandalone(); + + if (isStandalone) { + try { + const envQuery = new Env({ + context: "index", + data: this.env.data, + namespaceInfos: this.env.namespaceInfos, + language: this.env.language, + country: this.env.country, + defaultKeyword: this.env.defaultKeyword, + namespaces: Array.isArray(this.env.namespaces) ? [...this.env.namespaces] : [], + debug: this.env.debug, + }); + const params = Env.getParamsFromUrl(); + params.query = query; + const paramsFromQuery = envQuery.getQueryParams(params); + const preloadParams = envQuery.getPreloadParams(params, paramsFromQuery); + Object.assign(envQuery, preloadParams); + envQuery.setDefaults(); + if (envQuery.extraNamespaceName) { + if (envQuery.namespaces && !envQuery.namespaces.includes(envQuery.extraNamespaceName)) { + envQuery.namespaces.push(envQuery.extraNamespaceName); + } + } + + const needsAsync = envQuery.extraNamespaceName && !envQuery.isValidNamespace(envQuery.extraNamespaceName); + + if (!needsAsync) { + const response = CallHandler.getRedirectResponse(envQuery); + if (response.status === "found" && typeof response.redirectUrl === "string") { + if (!envQuery.debug) { + window.open(response.redirectUrl, "_blank"); + return; + } + } + } + } catch (e) { + this.env.logger.error("Failed synchronous redirect check: " + String(e)); + } + } + + let newWindow: Window | null = null; if (isStandalone) { try { newWindow = window.open("about:blank", "_blank"); @@ -367,7 +409,7 @@ export default class Home { // or asking for a not yet parsed Github namespace. const envQuery = new Env({ context: "index" }); const params: EnvParams = Env.getParamsFromUrl(); - params.query = this.queryInput.value; + params.query = query; try { await envQuery.populate(params); @@ -383,7 +425,7 @@ export default class Home { // Send debug to /process. if (envQuery.debug) { const processUrl = this.env.buildProcessUrl({ - query: this.queryInput.value, + query: query, }); if (newWindow) { newWindow.close(); From 1bc0b7dbc6c03c62842ea941a05b3c28a9331451 Mon Sep 17 00:00:00 2001 From: simpgps Date: Wed, 10 Jun 2026 06:57:46 +0800 Subject: [PATCH 3/3] fix: open PWA redirects in system browser on iOS and Android Use x-safari-https/http on iOS standalone PWAs instead of window.open, which only opens the in-app browser without Safari UI. Co-authored-by: Cursor --- src/ts/modules/Home.ts | 63 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/src/ts/modules/Home.ts b/src/ts/modules/Home.ts index fbc683293..273479fc2 100644 --- a/src/ts/modules/Home.ts +++ b/src/ts/modules/Home.ts @@ -336,6 +336,56 @@ export default class Home { } } + /** + * Open an external URL from standalone PWA mode in the system browser when possible. + * + * Android uses an intent URL. iOS 17+ can use the undocumented x-safari-https/http + * scheme; window.open alone opens the PWA in-app browser without Safari UI. + * + * @return {boolean} True if navigation was handled here. + */ + static openExternalUrlInStandalone(redirectUrl: string, newWindow: Window | null = null): boolean { + const isAndroid = /android/i.test(navigator.userAgent); + const isIOS = + /iPad|iPhone|iPod/i.test(navigator.userAgent) || + (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1); + + if (isAndroid) { + try { + const targetUrl = new URL(redirectUrl); + const scheme = targetUrl.protocol.replace(/:$/, ""); + const intentUrl = `intent:${targetUrl.href.replace(/^[^:]+/, "")}#Intent;scheme=${scheme};action=android.intent.action.VIEW;end`; + window.location.href = intentUrl; + return true; + } catch { + return false; + } + } + + if (isIOS) { + try { + const targetUrl = new URL(redirectUrl); + const protocol = targetUrl.protocol.replace(/:$/, ""); + if (protocol === "https" || protocol === "http") { + window.location.href = redirectUrl.replace(/^(https?):/, "x-safari-$1:"); + return true; + } + window.location.href = redirectUrl; + return true; + } catch { + return false; + } + } + + if (newWindow) { + newWindow.location.href = redirectUrl; + return true; + } + + window.open(redirectUrl, "_blank"); + return true; + } + /** * On submitting the query. * @@ -354,6 +404,11 @@ export default class Home { const query = this.queryInput.value; const isStandalone = this.env.isRunningStandalone(); + const isMobileStandalone = + isStandalone && + (/android/i.test(navigator.userAgent) || + /iPad|iPhone|iPod/i.test(navigator.userAgent) || + (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1)); if (isStandalone) { try { @@ -385,7 +440,7 @@ export default class Home { const response = CallHandler.getRedirectResponse(envQuery); if (response.status === "found" && typeof response.redirectUrl === "string") { if (!envQuery.debug) { - window.open(response.redirectUrl, "_blank"); + Home.openExternalUrlInStandalone(response.redirectUrl); return; } } @@ -396,7 +451,7 @@ export default class Home { } let newWindow: Window | null = null; - if (isStandalone) { + if (isStandalone && !isMobileStandalone) { try { newWindow = window.open("about:blank", "_blank"); } catch (e) { @@ -437,8 +492,8 @@ export default class Home { let redirectUrl: string; if (response.status === "found") { redirectUrl = response.redirectUrl as string; - if (isStandalone && newWindow) { - newWindow.location.href = redirectUrl; + if (isStandalone) { + Home.openExternalUrlInStandalone(redirectUrl, newWindow); return; } } else {