Files
BizGaze_Remote/mobile/scripts/android-patch.js
T
SravanandClaude Opus 4.8 7cd3c3afff android(build): inject JitPack repo so LiveKit's audioswitch fork resolves
Codemagic build failed at :app:checkDebugAarMetadata — "Could not find
com.github.davidliu:audioswitch". LiveKit Android 2.29.2 pulls in that
JitPack-only fork (commit-hash version), and Gradle resolves the whole
:app transitive graph against the APP module's (+ allprojects) repos, not
the native-call plugin's. The plugin-level jitpack repo was therefore
ignored. android-patch.js now injects `maven { url 'https://jitpack.io' }`
into the generated root build.gradle's allprojects{} (and settings.gradle
dependencyResolutionManagement{} if a newer template uses it). Idempotent,
build-tolerant. TECHSTACK rev 3 note updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-30 19:11:23 +05:30

144 lines
8.0 KiB
JavaScript

// Inject the runtime permissions the web UI needs into the Capacitor-generated AndroidManifest.xml.
// Run on Codemagic from the Android workflow:
// node mobile/scripts/android-patch.js mobile/android/app/src/main/AndroidManifest.xml
//
// WHY: mobile/android/ is gitignored (regenerated in CI by `cap add android`, same as iOS ios/). The
// freshly generated manifest only declares INTERNET, so without this the WebRTC calls in the web UI can't
// get camera/mic and Android 13+ never prompts for notifications. This adds the same permissions listed in
// mobile/resources/android-permissions.xml. (When the native-call Android plugin lands, it will contribute
// its own manifest entries via Capacitor manifest-merging; this only covers the app-level WebView perms.)
//
// TOLERANT: exits 0 and no-ops if anything is off, so it can NEVER fail the build. Idempotent (keyed on
// the bzcAndroidPerms marker), so re-runs never duplicate the block.
const fs = require('fs');
const p = process.argv[2];
if (!p || !fs.existsSync(p)) { console.log(' (AndroidManifest.xml not found — permission patch skipped)'); process.exit(0); }
try {
let s = fs.readFileSync(p, 'utf8');
// (0) LiveKit Android (native call media, Increment 2) requires a higher minSdk than Capacitor's default.
// Bump it in the generated variables.gradle so the library manifest merge holds (a library minSdk above the
// app's fails the build). variables.gradle sits at android/variables.gradle (3 dirs up from app/src/main).
try {
const path = require('path');
const varsPath = path.resolve(path.dirname(p), '..', '..', '..', 'variables.gradle');
if (fs.existsSync(varsPath)) {
let v = fs.readFileSync(varsPath, 'utf8');
const m = v.match(/minSdkVersion\s*=\s*(\d+)/);
if (m && parseInt(m[1], 10) < 24) {
v = v.replace(/minSdkVersion\s*=\s*\d+/, 'minSdkVersion = 24');
fs.writeFileSync(varsPath, v);
console.log(' minSdkVersion bumped to 24 (LiveKit Android floor)');
} else {
console.log(' minSdkVersion already >= 24 or not found — no bump');
}
} else {
console.log(' variables.gradle not found — minSdk bump skipped');
}
} catch (e) { console.log(' minSdk bump skipped:', e.message); }
// (0b) JitPack repository for LiveKit Android's transitive deps (Increment 2). LiveKit 2.29.2 pulls in
// com.github.davidliu:audioswitch — a JitPack-only fork pinned to a commit-hash version. Gradle resolves
// the FULL transitive graph of :app:debugRuntimeClasspath against the APP module's (+ allprojects)
// repositories, NOT the native-call plugin's, so a jitpack repo declared only in the plugin is ignored and
// the build dies with "Could not find com.github.davidliu:audioswitch". Inject jitpack where :app sees it:
// the generated root build.gradle's allprojects{} block (Capacitor's template) and, if a newer template
// uses it instead, settings.gradle's dependencyResolutionManagement{}. Idempotent (keyed on jitpack.io),
// tolerant (any miss just logs + continues — never fails the build).
try {
const path = require('path');
const base = path.resolve(path.dirname(p), '..', '..', '..'); // android/
const jitLine = " maven { url 'https://jitpack.io' } // bzcJitpack: LiveKit Android transitive deps";
// root build.gradle → allprojects { repositories { ... } } (classic Capacitor template)
const rootGradle = path.join(base, 'build.gradle');
if (fs.existsSync(rootGradle)) {
let g = fs.readFileSync(rootGradle, 'utf8');
if (g.includes('jitpack.io')) {
console.log(' jitpack already present in build.gradle');
} else {
const re = /(allprojects\s*\{\s*repositories\s*\{)/;
if (re.test(g)) {
g = g.replace(re, '$1\n' + jitLine);
fs.writeFileSync(rootGradle, g);
console.log(' jitpack repo injected into allprojects (root build.gradle)');
} else {
console.log(' allprojects{repositories{}} not found in build.gradle — jitpack root inject skipped');
}
}
} else {
console.log(' root build.gradle not found — jitpack root inject skipped');
}
// settings.gradle → dependencyResolutionManagement { repositories { ... } } (newer templates, if any)
const settingsGradle = path.join(base, 'settings.gradle');
if (fs.existsSync(settingsGradle)) {
let s2 = fs.readFileSync(settingsGradle, 'utf8');
if (s2.includes('jitpack.io')) {
console.log(' jitpack already present in settings.gradle');
} else {
const re2 = /(dependencyResolutionManagement\s*\{[\s\S]*?repositories\s*\{)/;
if (re2.test(s2)) {
s2 = s2.replace(re2, '$1\n' + jitLine);
fs.writeFileSync(settingsGradle, s2);
console.log(' jitpack repo injected into dependencyResolutionManagement (settings.gradle)');
} else {
console.log(' no dependencyResolutionManagement in settings.gradle — nothing to patch there');
}
}
}
} catch (e) { console.log(' jitpack inject skipped:', e.message); }
// (1) Native-call FirebaseMessagingService swap. The native-call plugin declares its own MESSAGING_EVENT
// service (BzCallMessagingService) that handles call-wake data messages AND forwards regular push to Capacitor
// via super. Remove @capacitor/push-notifications' own MessagingService here — the APP manifest is the highest
// merge priority, so this reliably guarantees exactly one receiver. Needs the `tools` namespace + an insert
// inside <application>. Keyed on bzcCallSvcRemove so re-runs never duplicate it.
if (!s.includes('bzcCallSvcRemove')) {
// Ensure xmlns:tools is declared on <manifest> (tools:node="remove" needs it).
if (!/<manifest[^>]*xmlns:tools=/.test(s)) {
s = s.replace(/<manifest(\s)/, '<manifest xmlns:tools="http://schemas.android.com/tools"$1');
}
const removal = [
'',
' <!-- bzcCallSvcRemove: the native-call plugin\'s BzCallMessagingService replaces this one (it forwards regular push via super) -->',
' <service android:name="com.capacitorjs.plugins.pushnotifications.MessagingService" tools:node="remove" />',
' ',
].join('\n');
const before = s;
s = s.replace(/<\/application>/, removal + '</application>');
if (s !== before) console.log(' Capacitor MessagingService removal injected (native-call takes over MESSAGING_EVENT)');
else console.log(' could not find </application> — MessagingService removal skipped');
fs.writeFileSync(p, s);
} else {
console.log(' Capacitor MessagingService removal already present');
}
// (2) WebView permissions.
if (s.includes('bzcAndroidPerms') || s.includes('android.permission.RECORD_AUDIO')) {
console.log(' Android permissions already present'); process.exit(0);
}
const block = [
'',
' <!-- bzcAndroidPerms: permissions the Biz Connect web UI needs (see mobile/resources/android-permissions.xml) -->',
' <!-- Push notifications: Android 13 (API 33)+ shows a runtime prompt -->',
' <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />',
' <!-- Voice / video calls + camera, used by the WebRTC features in the web UI -->',
' <uses-permission android:name="android.permission.CAMERA" />',
' <uses-permission android:name="android.permission.RECORD_AUDIO" />',
' <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />',
' <!-- Camera is optional hardware (tablets without one can still install) -->',
' <uses-feature android:name="android.hardware.camera" android:required="false" />',
'',
].join('\n');
const orig = s;
// Insert directly before the closing </manifest> tag.
s = s.replace(/<\/manifest>\s*$/, block + '</manifest>\n');
if (s === orig) { console.log(' could not find </manifest> — permission patch skipped'); process.exit(0); }
fs.writeFileSync(p, s);
console.log(' Android permissions injected into ' + p);
} catch (e) {
console.log(' Android permission patch skipped:', e.message);
}
process.exit(0);