From 0c48dc8897a6376a5634a4f59ae7f2164be3fc06 Mon Sep 17 00:00:00 2001 From: shumengya Date: Fri, 3 Jul 2026 20:10:35 +0800 Subject: [PATCH] fix(android): cross-origin Bearer auth in Tauri shell Inject a Tauri shell script during CI prep to disable service workers and attach Bearer tokens on cross-origin API requests. Enable Android third-party cookies in MainActivity and eval the same script from Rust on page load. --- .github/scripts/inject-tauri-shell.mjs | 32 +++++ .github/scripts/patch-android-system-bars.mjs | 9 ++ .github/scripts/prepare-template.mjs | 2 + template/scripts/tauri-shell.js | 126 ++++++++++++++++++ template/src-tauri/src/lib.rs | 4 + 5 files changed, 173 insertions(+) create mode 100644 .github/scripts/inject-tauri-shell.mjs create mode 100644 template/scripts/tauri-shell.js diff --git a/.github/scripts/inject-tauri-shell.mjs b/.github/scripts/inject-tauri-shell.mjs new file mode 100644 index 0000000..2e06f9b --- /dev/null +++ b/.github/scripts/inject-tauri-shell.mjs @@ -0,0 +1,32 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(__dirname, "../.."); +const MARKER = "data-web2app-tauri-shell"; +const scriptPath = path.join(root, "template", "scripts", "tauri-shell.js"); + +export function injectTauriShell(distDir) { + const indexPath = path.join(distDir, "index.html"); + if (!fs.existsSync(indexPath)) return; + + let html = fs.readFileSync(indexPath, "utf8"); + if (html.includes(MARKER)) return; + + const script = fs.readFileSync(scriptPath, "utf8").trim(); + const tag = ``; + + if (html.includes("")) { + html = html.replace("", `\n${tag}`); + } else if (html.includes("")) { + html = html.replace("", `${tag}\n`); + } else if (html.includes("")) { + html = html.replace("", `${tag}\n`); + } else { + html += `\n${tag}\n`; + } + + fs.writeFileSync(indexPath, html); + console.log("Injected Tauri shell script into index.html"); +} \ No newline at end of file diff --git a/.github/scripts/patch-android-system-bars.mjs b/.github/scripts/patch-android-system-bars.mjs index 2c196bc..65695bb 100644 --- a/.github/scripts/patch-android-system-bars.mjs +++ b/.github/scripts/patch-android-system-bars.mjs @@ -32,6 +32,8 @@ function patchMainActivity(filePath) { const content = `package ${pkg} import android.os.Bundle +import android.webkit.CookieManager +import android.webkit.WebView import androidx.core.view.WindowCompat class MainActivity : TauriActivity() { @@ -39,6 +41,13 @@ class MainActivity : TauriActivity() { super.onCreate(savedInstanceState) // 内容不延伸到状态栏 / 导航栏区域 WindowCompat.setDecorFitsSystemWindows(window, true) + CookieManager.getInstance().setAcceptCookie(true) + } + + override fun onWebViewCreate(webView: WebView) { + super.onWebViewCreate(webView) + // Android WebView 默认拦截跨站 Cookie;桌面壳请求远程 API 时需放行 + CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true) } } `; diff --git a/.github/scripts/prepare-template.mjs b/.github/scripts/prepare-template.mjs index 43e50b7..21f4870 100644 --- a/.github/scripts/prepare-template.mjs +++ b/.github/scripts/prepare-template.mjs @@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url"; import AdmZip from "adm-zip"; import { BUNDLE_ICONS, generateAppIcons } from "./generate-app-icons.mjs"; import { injectInAppNav } from "./inject-in-app-nav.mjs"; +import { injectTauriShell } from "./inject-tauri-shell.mjs"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(__dirname, "../.."); @@ -109,6 +110,7 @@ function patchTauriConfig(filePath) { fs.writeFileSync(filePath, `${JSON.stringify(conf, null, 2)}\n`); } +injectTauriShell(distDir); injectInAppNav(distDir); patchTauriConfig(confPath); diff --git a/template/scripts/tauri-shell.js b/template/scripts/tauri-shell.js new file mode 100644 index 0000000..fcdb3ea --- /dev/null +++ b/template/scripts/tauri-shell.js @@ -0,0 +1,126 @@ +(function () { + if (window.__web2appShellInstalled) return; + window.__web2appShellInstalled = true; + + function isTauriShell() { + var protocol = location.protocol; + var host = location.hostname; + return ( + protocol === "tauri:" || + protocol === "file:" || + host === "asset.localhost" || + host === "tauri.localhost" + ); + } + + if (!isTauriShell()) return; + + var TOKEN_KEYS = ["sprout2fa_session", "__web2app_bearer_token"]; + + function readToken() { + try { + for (var i = 0; i < TOKEN_KEYS.length; i++) { + var value = localStorage.getItem(TOKEN_KEYS[i]); + if (value) return value; + } + } catch (_) { + /* ignore */ + } + return null; + } + + function writeToken(token) { + if (!token) return; + try { + for (var i = 0; i < TOKEN_KEYS.length; i++) { + localStorage.setItem(TOKEN_KEYS[i], token); + } + } catch (_) { + /* ignore */ + } + } + + function clearToken() { + try { + for (var i = 0; i < TOKEN_KEYS.length; i++) { + localStorage.removeItem(TOKEN_KEYS[i]); + } + } catch (_) { + /* ignore */ + } + } + + if ("serviceWorker" in navigator) { + navigator.serviceWorker.getRegistrations().then(function (regs) { + for (var i = 0; i < regs.length; i++) regs[i].unregister(); + }); + navigator.serviceWorker.register = function () { + return Promise.resolve({ + active: null, + installing: null, + waiting: null, + unregister: function () { + return Promise.resolve(true); + }, + update: function () { + return Promise.resolve(); + }, + }); + }; + } + + var originalFetch = window.fetch; + window.fetch = function (input, init) { + init = init || {}; + var url = + typeof input === "string" + ? input + : input instanceof URL + ? input.href + : input && input.url + ? input.url + : ""; + + var requestUrl; + try { + requestUrl = new URL(url, location.href); + } catch (_) { + return originalFetch.call(this, input, init); + } + + var crossOrigin = requestUrl.origin !== location.origin; + if (crossOrigin) { + var headers = new Headers(init.headers || {}); + if (!headers.has("Authorization")) { + var token = readToken(); + if (token) headers.set("Authorization", "Bearer " + token); + } + init = Object.assign({}, init, { headers: headers }); + } + + return originalFetch.call(this, input, init).then(function (response) { + if (!response.ok) return response; + + var path = requestUrl.pathname.replace(/\/$/, ""); + if (/\/auth\/login$/i.test(path)) { + response + .clone() + .json() + .then(function (data) { + if (data && typeof data.token === "string" && data.token) { + writeToken(data.token); + } + }) + .catch(function () { + /* ignore */ + }); + } + + if (/\/auth\/logout$/i.test(path)) { + clearToken(); + } + + return response; + }); + }; +})(); \ No newline at end of file diff --git a/template/src-tauri/src/lib.rs b/template/src-tauri/src/lib.rs index eca4b5e..240d249 100644 --- a/template/src-tauri/src/lib.rs +++ b/template/src-tauri/src/lib.rs @@ -1,5 +1,7 @@ use tauri::Manager; +const TAURI_SHELL_SCRIPT: &str = include_str!("../../scripts/tauri-shell.js"); + const IN_APP_NAV_SCRIPT: &str = r##" (function () { if (window.__web2appNavInstalled) return; @@ -68,12 +70,14 @@ pub fn run() { tauri::Builder::default() .setup(|app| { if let Some(window) = app.get_webview_window("main") { + let _ = window.eval(TAURI_SHELL_SCRIPT); let _ = window.eval(IN_APP_NAV_SCRIPT); } Ok(()) }) .on_page_load(|webview, payload| { if payload.event() == tauri::webview::PageLoadEvent::Finished { + let _ = webview.eval(TAURI_SHELL_SCRIPT); let _ = webview.eval(IN_APP_NAV_SCRIPT); } })