Compare commits

..

65 Commits

Author SHA1 Message Date
e7e6f4b84f fix(android): enable camera permission for WebView getUserMedia
Declare CAMERA in AndroidManifest so RustWebChromeClient can show the system permission dialog. Inject Permissions-Policy meta for camera access in the Tauri shell.
2026-07-03 20:28:27 +08:00
github-actions[bot]
e6741f7039 chore: cleanup build NhwFTosc3j 2026-07-03 12:20:58 +00:00
e21e02321b chore: upload logo.png for build NhwFTosc3j 2026-07-03 20:15:59 +08:00
dbc9d04ed4 chore: upload version.txt for build NhwFTosc3j 2026-07-03 20:15:58 +08:00
5bbe23496f chore: upload site.zip for build NhwFTosc3j 2026-07-03 20:15:57 +08:00
0c48dc8897 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.
2026-07-03 20:10:45 +08:00
github-actions[bot]
6ed4ca576d chore: cleanup build LKfLydwq5n 2026-07-03 11:50:48 +00:00
acadfa2ab7 chore: upload logo.png for build LKfLydwq5n 2026-07-03 19:46:12 +08:00
e95d1ebfcf chore: upload version.txt for build LKfLydwq5n 2026-07-03 19:46:12 +08:00
c39ecad01b chore: upload site.zip for build LKfLydwq5n 2026-07-03 19:46:11 +08:00
github-actions[bot]
04cadb47f8 chore: cleanup build rlFZgIuQa1 2026-07-03 09:06:06 +00:00
07ae454661 chore: upload logo.png for build rlFZgIuQa1 2026-07-03 17:00:40 +08:00
1095e702fb chore: upload version.txt for build rlFZgIuQa1 2026-07-03 17:00:38 +08:00
7ab5157fb0 chore: upload site.zip for build rlFZgIuQa1 2026-07-03 17:00:36 +08:00
85b9210221 fix: pass app_version to CI and ship Windows NSIS installer only
Add app_version to workflow_dispatch inputs and worker dispatch payload. Collect only setup.exe from bundle/nsis, prefer installer URLs in release lookup, and use git-tag-safe job IDs.
2026-07-03 16:55:41 +08:00
e3d3eb7006 docs: add MIT LICENSE and .dev.vars.example referenced by README
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-03 16:55:40 +08:00
2d42c7ef71 feat: desktop build support and build list UI improvements
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-03 16:55:40 +08:00
112337fd9e chore: upload logo.png for build 321e_TEtP- 2026-07-03 16:34:27 +08:00
ed88c223a0 chore: upload version.txt for build 321e_TEtP- 2026-07-03 16:34:25 +08:00
5a6bacf54f chore: upload site.zip for build 321e_TEtP- 2026-07-03 16:34:24 +08:00
9575e9129c chore: upload favicon.ico for build DpGVZlP-Fq 2026-07-03 16:27:00 +08:00
40516af937 chore: upload version.txt for build DpGVZlP-Fq 2026-07-03 16:26:59 +08:00
de5e54215b chore: upload site.zip for build DpGVZlP-Fq 2026-07-03 16:26:57 +08:00
215c6d49ce chore: upload version.txt for build opbpiRjcUO 2026-07-03 16:24:15 +08:00
bf1495c105 chore: upload site.zip for build opbpiRjcUO 2026-07-03 16:24:13 +08:00
github-actions[bot]
5a18719ac8 chore: cleanup build S7RlSKReoQ 2026-07-03 07:22:21 +00:00
def91f2052 chore: upload logo.png for build S7RlSKReoQ 2026-07-03 15:17:25 +08:00
acb424ca27 chore: upload version.txt for build S7RlSKReoQ 2026-07-03 15:17:24 +08:00
4e851f2e39 chore: upload site.zip for build S7RlSKReoQ 2026-07-03 15:17:24 +08:00
github-actions[bot]
00387f2599 chore: cleanup build aLsGL-0e0O 2026-07-03 07:04:43 +00:00
20071f361d chore: upload logo.png for build aLsGL-0e0O 2026-07-03 14:59:55 +08:00
5a1571141c chore: upload version.txt for build aLsGL-0e0O 2026-07-03 14:59:54 +08:00
c14fd4a408 chore: upload site.zip for build aLsGL-0e0O 2026-07-03 14:59:53 +08:00
github-actions[bot]
f44fd932bc chore: cleanup build KwsSx9ylIh 2026-07-01 08:46:12 +00:00
480f355f19 chore: upload logo.png for build KwsSx9ylIh 2026-07-01 16:38:07 +08:00
e50ef8f482 chore: upload version.txt for build KwsSx9ylIh 2026-07-01 16:38:06 +08:00
cc13833d0b chore: upload site.zip for build KwsSx9ylIh 2026-07-01 16:38:04 +08:00
github-actions[bot]
4c5cf0ac35 chore: cleanup build bCN2ovXhxf 2026-06-08 03:30:26 +00:00
4bb6192312 chore: upload logo.png for build bCN2ovXhxf 2026-06-08 11:24:48 +08:00
99c8d86fc8 chore: upload version.txt for build bCN2ovXhxf 2026-06-08 11:24:45 +08:00
97e1fc5ee1 chore: upload site.zip for build bCN2ovXhxf 2026-06-08 11:24:43 +08:00
github-actions[bot]
afd69b75e7 chore: cleanup build ZvFfF6lz4Z 2026-06-07 06:30:28 +00:00
196a65b611 chore: upload logo.png for build ZvFfF6lz4Z 2026-06-07 14:25:31 +08:00
a6f9f4730a chore: upload version.txt for build ZvFfF6lz4Z 2026-06-07 14:25:30 +08:00
069774798c chore: upload site.zip for build ZvFfF6lz4Z 2026-06-07 14:25:29 +08:00
ac2a070c40 chore: upload version.txt for build THbyXdMiH4 2026-06-06 20:33:55 +08:00
595bdcb809 chore: upload site.zip for build THbyXdMiH4 2026-06-06 20:33:53 +08:00
github-actions[bot]
db7158fd13 chore: cleanup build 3ILcnyxRgh 2026-06-06 09:50:11 +00:00
4fa7bc0346 chore: upload logo.png for build 3ILcnyxRgh 2026-06-06 17:45:23 +08:00
71c1577bfd chore: upload version.txt for build 3ILcnyxRgh 2026-06-06 17:45:23 +08:00
07955ab48b chore: upload site.zip for build 3ILcnyxRgh 2026-06-06 17:45:21 +08:00
github-actions[bot]
2766bea112 chore: cleanup build JDGPnhOn1_ 2026-06-06 09:37:31 +00:00
eb2b6fa792 chore: upload logo.png for build JDGPnhOn1_ 2026-06-06 17:32:32 +08:00
764c053c28 chore: upload version.txt for build JDGPnhOn1_ 2026-06-06 17:32:31 +08:00
fa4e09e5f4 chore: upload site.zip for build JDGPnhOn1_ 2026-06-06 17:32:30 +08:00
github-actions[bot]
7e2a75d6c5 chore: cleanup build 5wPK-omehe 2026-06-06 07:08:43 +00:00
95a3663d07 chore: upload logo.png for build 5wPK-omehe 2026-06-06 15:04:29 +08:00
e81101e3b8 chore: upload version.txt for build 5wPK-omehe 2026-06-06 15:04:28 +08:00
be77438392 chore: upload site.zip for build 5wPK-omehe 2026-06-06 15:04:26 +08:00
8af54dde0e chore: upload version.txt for build NfzpJAOEUO 2026-06-06 15:02:45 +08:00
c6ba211081 chore: upload site.zip for build NfzpJAOEUO 2026-06-06 15:02:43 +08:00
github-actions[bot]
9422ecf1aa chore: cleanup build XLfZ3xTnXV 2026-06-06 06:25:48 +00:00
af566f22a1 chore: upload logo.png for build XLfZ3xTnXV 2026-06-06 14:20:41 +08:00
747aaf09ad chore: upload version.txt for build XLfZ3xTnXV 2026-06-06 14:20:39 +08:00
e143ae6c0f chore: upload site.zip for build XLfZ3xTnXV 2026-06-06 14:20:37 +08:00
41 changed files with 220 additions and 3 deletions

41
.github/scripts/inject-tauri-shell.mjs vendored Normal file
View File

@@ -0,0 +1,41 @@
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 PERMISSIONS_POLICY_MARKER = "data-web2app-permissions-policy";
const PERMISSIONS_POLICY_TAG = `<meta ${PERMISSIONS_POLICY_MARKER} http-equiv="Permissions-Policy" content="camera=*, microphone=*" />`;
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");
const needsPolicy = !html.includes(PERMISSIONS_POLICY_MARKER);
const needsScript = !html.includes(MARKER);
if (!needsPolicy && !needsScript) return;
const injections = [];
if (needsPolicy) injections.push(PERMISSIONS_POLICY_TAG);
if (needsScript) {
const script = fs.readFileSync(scriptPath, "utf8").trim();
injections.push(`<script ${MARKER}>${script}</script>`);
}
const block = `${injections.join("\n")}\n`;
if (html.includes("<head>")) {
html = html.replace("<head>", `<head>\n${block}`);
} else if (html.includes("</head>")) {
html = html.replace("</head>", `${block}</head>`);
} else if (html.includes("</body>")) {
html = html.replace("</body>", `${block}</body>`);
} else {
html += `\n${block}`;
}
fs.writeFileSync(indexPath, html);
console.log("Injected Tauri shell script into index.html");
}

View File

@@ -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)
}
}
`;
@@ -127,16 +136,34 @@ function ensureNetworkSecurityXml(androidRoot) {
console.log(`Wrote ${path.relative(root, target)}`);
}
const ANDROID_PERMISSIONS = [
"android.permission.INTERNET",
"android.permission.CAMERA",
];
function ensureUsesPermission(xml, permission) {
if (xml.includes(permission)) return xml;
const tag = ` <uses-permission android:name="${permission}" />\n`;
if (xml.includes("<application")) {
return xml.replace(/<application/, `${tag}\n <application`);
}
return xml.replace(/<\/manifest>/, `${tag}</manifest>`);
}
function patchAndroidManifest(androidRoot) {
for (const file of walk(androidRoot, (p) => p.endsWith("AndroidManifest.xml"))) {
const normalized = file.replace(/\\/g, "/");
if (!normalized.includes("/src/main/")) continue;
let xml = fs.readFileSync(file, "utf8");
if (!xml.includes("android.permission.INTERNET")) {
for (const permission of ANDROID_PERMISSIONS) {
xml = ensureUsesPermission(xml, permission);
}
if (!xml.includes("android.hardware.camera")) {
xml = xml.replace(
/<application/,
' <uses-permission android:name="android.permission.INTERNET" />\n\n <application',
' <uses-feature android:name="android.hardware.camera" android:required="false" />\n\n <application',
);
}
@@ -197,4 +224,4 @@ ensureNetworkSecurityXml(androidRoot);
patchAndroidManifest(androidRoot);
patchGradleCleartext(androidRoot);
console.log("Android app patches applied (system bars + network)");
console.log("Android app patches applied (system bars + network + camera)");

View File

@@ -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);

BIN
builds/321e_TEtP-/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
builds/321e_TEtP-/site.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
2026.7.3

BIN
builds/3ILcnyxRgh/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1 @@
2026.6.6

BIN
builds/5wPK-omehe/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -0,0 +1 @@
2026.6.6

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
builds/DpGVZlP-Fq/site.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
2026.7.3

BIN
builds/JDGPnhOn1_/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1 @@
2026.6.6

BIN
builds/KwsSx9ylIh/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -0,0 +1 @@
2026.7.1

BIN
builds/LKfLydwq5n/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1 @@
2026.7.3

BIN
builds/NfzpJAOEUO/site.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
2026.6.6

BIN
builds/NhwFTosc3j/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1 @@
2026.7.3

BIN
builds/S7RlSKReoQ/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1 @@
2026.7.3

BIN
builds/THbyXdMiH4/site.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
2026.6.6

BIN
builds/XLfZ3xTnXV/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -0,0 +1 @@
2026.6.6

BIN
builds/ZvFfF6lz4Z/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -0,0 +1 @@
2026.6.7

BIN
builds/aLsGL-0e0O/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1 @@
2026.7.3

BIN
builds/bCN2ovXhxf/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 KiB

View File

@@ -0,0 +1 @@
2026.6.8

BIN
builds/opbpiRjcUO/site.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
2026.7.3

BIN
builds/rlFZgIuQa1/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -0,0 +1 @@
2026.7.3

View File

@@ -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;
});
};
})();

View File

@@ -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);
}
})