feat(android): APK loads live web, fix cold-start deep links for Google OAuth

APK architecture change: replaces bundled React SPA with minimal redirect
page that always loads live web content. No more APK rebuilds for web changes.

Root cause of OAuth failure on Android:
1. Cold-start deep links lost — APK's old bundled JS called onOpenUrl()
   (warm-start listener only) but NOT getCurrent() which is required for
   cold-start deep links. Fix: redirect page + setupDeepLinkHandler both
   call getCurrent() before redirecting/navigating.
2. Session cookie was dropped — renderTauriDeepLinkPage returned a raw
   new Response() which overwrote the Set-Cookie header set by the
   callback handler. Fix: inject Set-Cookie into the Response.
3. tauri.conf.json frontendDist → "./web/dist-tauri" (redirect page)
4. Added @tauri-apps/plugin-deep-link and @tauri-apps/plugin-opener as
   web app deps so live-web imports work in Tauri WebView.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-16 03:47:22 +07:00
co-authored by Claude
parent 96f337ef46
commit 7148e1026e
6 changed files with 90 additions and 26 deletions
+31 -21
View File
@@ -29,32 +29,42 @@ export async function openUrl(url: string): Promise<void> {
* callback page redirects to zeavisedu://... which triggers this listener.
* We extract the path + query and navigate the WebView there.
*/
function processDeepLinkUrl(url: string): void {
// url looks like: zeavisedu://login/login?token=xxx
// We need to extract path+query and navigate the WebView there
try {
const u = new URL(url);
const target = u.pathname + u.search + u.hash;
if (target && target !== '/') {
window.location.href = target;
return;
}
} catch { /* try fallback below */ }
// Fallback: handle both double-slash (://) and single-slash (:/) schemes
let match = url.match(/^[^:]+:\/\/(?:[^/]+)?(\/.*)?$/);
if (!match) {
match = url.match(/^[^:]+:\/(\/.*)?$/);
}
if (match?.[1]) {
window.location.href = match[1];
}
}
export async function setupDeepLinkHandler(): Promise<void> {
if (!isTauri()) return;
const { onOpenUrl } = await import('@tauri-apps/plugin-deep-link');
const { onOpenUrl, getCurrent } = await import('@tauri-apps/plugin-deep-link');
// Handle cold-start deep links (app opened via intent)
getCurrent().then((urls) => {
if (urls?.[0]) processDeepLinkUrl(urls[0]);
}).catch(() => { /* ignore */ });
// Handle warm-start deep links (app already running, new intent received)
onOpenUrl((urls) => {
for (const url of urls) {
// url looks like: zeavisedu://login/login?token=xxx
// Extract path + query after the host
try {
const u = new URL(url);
const target = u.pathname + u.search + u.hash;
if (target && target !== '/') {
window.location.href = target;
return;
}
} catch { /* try fallback below */ }
// Fallback: extract everything after scheme, handling both // and /
let match = url.match(/^[^:]+:\/\/(?:[^/]+)?(\/.*)?$/);
if (!match) {
// Also handle single-slash non-hierarchical URLs (e.g. zeavisedu:/path)
match = url.match(/^[^:]+:\/(\/.*)?$/);
}
if (match?.[1]) {
window.location.href = match[1];
}
processDeepLinkUrl(url);
}
});
}