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.
This commit is contained in:
2026-07-03 20:28:13 +08:00
parent e6741f7039
commit e7e6f4b84f
2 changed files with 37 additions and 10 deletions

View File

@@ -5,6 +5,8 @@ import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(__dirname, "../.."); const root = path.resolve(__dirname, "../..");
const MARKER = "data-web2app-tauri-shell"; 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"); const scriptPath = path.join(root, "template", "scripts", "tauri-shell.js");
export function injectTauriShell(distDir) { export function injectTauriShell(distDir) {
@@ -12,19 +14,26 @@ export function injectTauriShell(distDir) {
if (!fs.existsSync(indexPath)) return; if (!fs.existsSync(indexPath)) return;
let html = fs.readFileSync(indexPath, "utf8"); let html = fs.readFileSync(indexPath, "utf8");
if (html.includes(MARKER)) return; const needsPolicy = !html.includes(PERMISSIONS_POLICY_MARKER);
const needsScript = !html.includes(MARKER);
if (!needsPolicy && !needsScript) return;
const script = fs.readFileSync(scriptPath, "utf8").trim(); const injections = [];
const tag = `<script ${MARKER}>${script}</script>`; 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>")) { if (html.includes("<head>")) {
html = html.replace("<head>", `<head>\n${tag}`); html = html.replace("<head>", `<head>\n${block}`);
} else if (html.includes("</head>")) { } else if (html.includes("</head>")) {
html = html.replace("</head>", `${tag}\n</head>`); html = html.replace("</head>", `${block}</head>`);
} else if (html.includes("</body>")) { } else if (html.includes("</body>")) {
html = html.replace("</body>", `${tag}\n</body>`); html = html.replace("</body>", `${block}</body>`);
} else { } else {
html += `\n${tag}\n`; html += `\n${block}`;
} }
fs.writeFileSync(indexPath, html); fs.writeFileSync(indexPath, html);

View File

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