From 7148e1026e18237f1e4217fb687f3ad5f38f9165 Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Tue, 16 Jun 2026 03:47:22 +0700 Subject: [PATCH] feat(android): APK loads live web, fix cold-start deep links for Google OAuth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- apps/api/src/routes/auth.ts | 10 +++++-- apps/tauri/tauri.conf.json | 6 ++-- apps/web/dist-tauri/index.html | 44 ++++++++++++++++++++++++++++ apps/web/package.json | 2 ++ apps/web/src/lib/tauri.ts | 52 ++++++++++++++++++++-------------- bun.lock | 2 ++ 6 files changed, 90 insertions(+), 26 deletions(-) create mode 100644 apps/web/dist-tauri/index.html diff --git a/apps/api/src/routes/auth.ts b/apps/api/src/routes/auth.ts index bbc09b4..b105507 100644 --- a/apps/api/src/routes/auth.ts +++ b/apps/api/src/routes/auth.ts @@ -314,12 +314,18 @@ export const authRoutes = new Elysia({ prefix: '/api/v1/auth' }) } const token = await createSession(user.id); - set.headers['Set-Cookie'] = createSessionCookie(token, request.headers); + const sessionCookie = createSessionCookie(token, request.headers); authCounter.labels('login', 'true').inc(); const successUrl = `${env.webAppUrl}/login?token=${encodeURIComponent(token)}`; - if (platform === 'tauri') return renderTauriDeepLinkPage(successUrl); + if (platform === 'tauri') { + // Inject Set-Cookie into the response so the browser gets it on redirect + const resp = renderTauriDeepLinkPage(successUrl); + resp.headers.set('Set-Cookie', sessionCookie); + return resp; + } + set.headers['Set-Cookie'] = sessionCookie; set.status = 302; set.headers['Location'] = successUrl; } catch (err) { diff --git a/apps/tauri/tauri.conf.json b/apps/tauri/tauri.conf.json index cc0c025..d12675c 100644 --- a/apps/tauri/tauri.conf.json +++ b/apps/tauri/tauri.conf.json @@ -4,10 +4,10 @@ "version": "0.1.0", "identifier": "com.zeavis.edu", "build": { - "frontendDist": "../web/dist", + "frontendDist": "../web/dist-tauri", "devUrl": "http://localhost:5173", - "beforeBuildCommand": "cd ../web && bun run build", - "beforeDevCommand": "cd ../web && bun run dev" + "beforeBuildCommand": "echo 'Nothing to build (APK loads live web content)'", + "beforeDevCommand": "echo 'Dev mode - connect to localhost:5173'" }, "app": { "withGlobalTauri": false, diff --git a/apps/web/dist-tauri/index.html b/apps/web/dist-tauri/index.html new file mode 100644 index 0000000..16c3de0 --- /dev/null +++ b/apps/web/dist-tauri/index.html @@ -0,0 +1,44 @@ + + + + + + ZeaVis Edu + + + +

Memuat ZeaVis Edu...

+ + diff --git a/apps/web/package.json b/apps/web/package.json index 47705bf..a66a693 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -12,6 +12,8 @@ "dependencies": { "@radix-ui/react-slot": "^1.2.4", "@tanstack/react-query": "^5.100.11", + "@tauri-apps/plugin-deep-link": "^2", + "@tauri-apps/plugin-opener": "^2", "@vitejs/plugin-react": "^6.0.2", "@zeavis/shared": "workspace:*", "class-variance-authority": "^0.7.1", diff --git a/apps/web/src/lib/tauri.ts b/apps/web/src/lib/tauri.ts index 29b54e7..2784868 100644 --- a/apps/web/src/lib/tauri.ts +++ b/apps/web/src/lib/tauri.ts @@ -29,32 +29,42 @@ export async function openUrl(url: string): Promise { * 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 { 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); } }); } diff --git a/bun.lock b/bun.lock index b192492..64f2273 100644 --- a/bun.lock +++ b/bun.lock @@ -53,6 +53,8 @@ "dependencies": { "@radix-ui/react-slot": "^1.2.4", "@tanstack/react-query": "^5.100.11", + "@tauri-apps/plugin-deep-link": "^2", + "@tauri-apps/plugin-opener": "^2", "@vitejs/plugin-react": "^6.0.2", "@zeavis/shared": "workspace:*", "class-variance-authority": "^0.7.1",