Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 128 additions & 3 deletions src/ts/modules/Home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -352,30 +402,105 @@ export default class Home {
return;
}

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 {
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) {
Home.openExternalUrlInStandalone(response.redirectUrl);
return;
}
}
}
} catch (e) {
this.env.logger.error("Failed synchronous redirect check: " + String(e));
}
}

let newWindow: Window | null = null;
if (isStandalone && !isMobileStandalone) {
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);
params.query = query;

try {
await envQuery.populate(params);
} catch (error) {
if (newWindow) {
newWindow.close();
}
throw error;
}

const response: RedirectResponse = CallHandler.getRedirectResponse(envQuery);

// Send debug to /process.
if (envQuery.debug) {
const processUrl = this.env.buildProcessUrl({
query: this.queryInput.value,
query: query,
});
if (newWindow) {
newWindow.close();
}
window.location.href = processUrl;
return;
}

let redirectUrl: string;
if (response.status === "found") {
redirectUrl = response.redirectUrl as string;
if (isStandalone) {
Home.openExternalUrlInStandalone(redirectUrl, newWindow);
return;
}
} else {
redirectUrl = CallHandler.getRedirectUrlToHome(envQuery, response);
if (newWindow) {
newWindow.close();
}
}
window.location.href = redirectUrl;
};
Expand Down