Files
frontend/plugins/withHuaweiPush.js

240 lines
9.9 KiB
JavaScript
Raw Permalink Normal View History

const {
withAppBuildGradle,
withProjectBuildGradle,
withSettingsGradle,
withDangerousMod,
withAndroidManifest,
} = require('@expo/config-plugins');
const fs = require('fs');
const path = require('path');
const withHuaweiPush = (config, options = {}) => {
const { jpushVersion = '6.1.0' } = options;
config = withSettingsGradle(config, (config) => {
const contents = config.modResults.contents;
// Add Huawei Maven repo to pluginManagement.repositories for buildscript resolution.
// NOTE: We intentionally do NOT add `plugins { id 'com.huawei.agconnect' ... }` here.
// The Huawei agcp plugin transitively depends on agconnect-apms-plugin:1.6.2.300,
// which depends on AGP 4.0.1. That old AGP references BuildCompletionListener (removed
// from Gradle 8.x+), causing NoClassDefFoundError at build time. Instead, the plugin
// is applied via buildscript classpath in build.gradle with the apms-plugin excluded.
if (!contents.includes('developer.huawei.com/repo')) {
const repoLine = ` maven { url 'https://developer.huawei.com/repo/' }`;
const hasPluginManagement = contents.includes('pluginManagement');
const hasRepositoriesInPM = /pluginManagement\s*\{[\s\S]*?repositories\s*\{/.test(contents);
if (hasRepositoriesInPM) {
config.modResults.contents = contents.replace(
/(pluginManagement\s*\{[\s\S]*?repositories\s*\{)/,
`$1\n ${repoLine}`
);
} else if (hasPluginManagement) {
config.modResults.contents = contents.replace(
/(pluginManagement\s*\{)/,
`$1\n repositories {\n gradlePluginPortal()\n google()\n mavenCentral()\n ${repoLine}\n }`
);
} else {
config.modResults.contents =
`pluginManagement {\n repositories {\n ${repoLine}\n }\n}\n\n` +
config.modResults.contents;
}
}
// Ensure Huawei Maven repo in dependencyResolutionManagement (Gradle 9+)
if (!config.modResults.contents.match(/dependencyResolutionManagement[\s\S]*developer\.huawei\.com\/repo/)) {
const depMgmtPattern = /dependencyResolutionManagement\s*\{[\s\S]*?repositories\s*\{/;
if (depMgmtPattern.test(config.modResults.contents)) {
config.modResults.contents = config.modResults.contents.replace(
depMgmtPattern,
`$&\n maven { url 'https://developer.huawei.com/repo/' }`
);
}
}
return config;
});
config = withProjectBuildGradle(config, (config) => {
const contents = config.modResults.contents;
if (!contents.includes('developer.huawei.com/repo')) {
config.modResults.contents = contents.replace(
/(repositories\s*\{)/g,
(match) => {
return match + `\n maven { url 'https://developer.huawei.com/repo/' }`;
}
);
}
return config;
});
config = withProjectBuildGradle(config, (config) => {
const contents = config.modResults.contents;
// Add Huawei AGConnect classpath with apms-plugin excluded.
// The apms-plugin transitively depends on AGP 4.0.1 which is incompatible
// with Gradle 8.14+ (references removed BuildCompletionListener class).
if (!contents.includes('com.huawei.agconnect:agcp')) {
if (contents.includes('buildscript')) {
const depsPattern = /(buildscript\s*\{[\s\S]*?dependencies\s*\{)/;
if (depsPattern.test(contents)) {
config.modResults.contents = contents.replace(
depsPattern,
`$1\n classpath('com.huawei.agconnect:agcp:1.9.1.301') {\n exclude group: 'com.huawei.agconnect', module: 'agconnect-apms-plugin'\n }`
);
}
}
}
// Set explicit AGP version (needed by Huawei plugin validation)
if (!contents.includes('com.android.tools.build:gradle:8.12.0')) {
config.modResults.contents = config.modResults.contents.replace(
/classpath\(['"]com\.android\.tools\.build:gradle['"]\)/,
"classpath('com.android.tools.build:gradle:8.12.0')"
);
}
// Google Services plugin classpath — required for Firebase init via google-services.json.
if (!contents.includes('com.google.gms:google-services')) {
const depsPattern = /(buildscript\s*\{[\s\S]*?dependencies\s*\{[\s\S]*?classpath\(['"]org\.jetbrains\.kotlin:kotlin-gradle-plugin['"]\))/;
if (depsPattern.test(config.modResults.contents)) {
config.modResults.contents = config.modResults.contents.replace(
depsPattern,
`$1\n classpath('com.google.gms:google-services:4.4.4')`
);
}
}
if (!config.modResults.contents.includes('developer.huawei.com/repo')) {
const bsRepoPattern = /(buildscript\s*\{[\s\S]*?repositories\s*\{)/;
if (bsRepoPattern.test(config.modResults.contents)) {
config.modResults.contents = config.modResults.contents.replace(
bsRepoPattern,
`$1\n maven { url 'https://developer.huawei.com/repo/' }`
);
}
}
return config;
});
config = withAppBuildGradle(config, (config) => {
const contents = config.modResults.contents;
// Apply Google Services plugin (Firebase) before the Huawei plugin.
// The googleServicesFile in app.json only sets up the file copy + plugin
// when run through `expo prebuild`, but it does not coexist with this
// Huawei plugin reliably, so we apply it explicitly here.
if (!contents.includes('com.google.gms.google-services')) {
const reactApply = /apply plugin:\s*['"]com\.facebook\.react['"]\s*\n/;
if (reactApply.test(contents)) {
config.modResults.contents = contents.replace(
reactApply,
`apply plugin: "com.facebook.react"\napply plugin: 'com.google.gms.google-services'\n`
);
} else {
config.modResults.contents = `apply plugin: 'com.google.gms.google-services'\n` + contents;
}
}
if (!contents.includes('com.huawei.agconnect')) {
const applyPluginPattern = /apply plugin:\s*['"]com\.google\.gms\.google-services['"]/;
if (applyPluginPattern.test(contents)) {
config.modResults.contents = contents.replace(
applyPluginPattern,
`apply plugin: 'com.google.gms.google-services'\napply plugin: 'com.huawei.agconnect'`
);
} else {
const lastApplyPlugin = /apply plugin:[^\n]*(?:\n)/g;
const matches = contents.match(lastApplyPlugin);
if (matches && matches.length > 0) {
const lastMatch = matches[matches.length - 1];
const lastIndex = contents.lastIndexOf(lastMatch);
config.modResults.contents =
contents.slice(0, lastIndex + lastMatch.length) +
"apply plugin: 'com.huawei.agconnect'\n" +
contents.slice(lastIndex + lastMatch.length);
} else {
config.modResults.contents = contents + "\napply plugin: 'com.huawei.agconnect'\n";
}
}
}
if (!contents.includes('cn.jiguang.sdk.plugin:huawei')) {
const depsPattern = /dependencies\s*\{/;
if (depsPattern.test(config.modResults.contents)) {
config.modResults.contents = config.modResults.contents.replace(
depsPattern,
`dependencies {\n implementation 'cn.jiguang.sdk.plugin:huawei:${jpushVersion}'`
);
}
}
return config;
});
config = withDangerousMod(config, [
'android',
async (config) => {
const appDir = path.join(config.modRequest.platformProjectRoot, 'app');
const srcFile = path.join(config.modRequest.projectRoot, 'plugins', 'agconnect-services.json');
const destFile = path.join(appDir, 'agconnect-services.json');
if (fs.existsSync(srcFile)) {
fs.copyFileSync(srcFile, destFile);
console.log('[withHuaweiPush] copied agconnect-services.json to android/app/');
} else {
console.warn('[withHuaweiPush] agconnect-services.json not found at project root');
}
// Copy google-services.json to android/app/ so the Google Services
// Gradle plugin can find it. Without this, FCM / Firebase init fails
// at runtime with "default FirebaseApp is not initialized".
const gmsSrc = path.join(config.modRequest.projectRoot, 'google-services.json');
const gmsDest = path.join(appDir, 'google-services.json');
if (fs.existsSync(gmsSrc)) {
fs.copyFileSync(gmsSrc, gmsDest);
console.log('[withHuaweiPush] copied google-services.json to android/app/');
} else {
console.warn('[withHuaweiPush] google-services.json not found at project root');
}
// RN 0.85.3 ships Gradle 9.3.1 in its template, but Kotlin 2.1.20 is incompatible
// with Gradle 9.x (CompilerEnvironment ClassNotFoundException). Downgrade to 8.14.5.
const wrapperFile = path.join(config.modRequest.platformProjectRoot, 'gradle', 'wrapper', 'gradle-wrapper.properties');
if (fs.existsSync(wrapperFile)) {
let wrapper = fs.readFileSync(wrapperFile, 'utf8');
if (wrapper.includes('gradle-9.')) {
wrapper = wrapper.replace(/gradle-[\d.]+-bin\.zip/, 'gradle-8.14.5-bin.zip');
fs.writeFileSync(wrapperFile, wrapper);
console.log('[withHuaweiPush] downgraded Gradle to 8.14.5 for Kotlin 2.1.20 compatibility');
}
}
// Ensure local.properties with SDK path exists (gets cleared on prebuild --clean)
const localPropsFile = path.join(config.modRequest.platformProjectRoot, 'local.properties');
if (!fs.existsSync(localPropsFile)) {
const homeDir = process.env.USERPROFILE || process.env.HOME || '';
const sdkPath = path.join(homeDir, 'AppData', 'Local', 'Android', 'Sdk');
if (fs.existsSync(sdkPath)) {
fs.writeFileSync(localPropsFile, `sdk.dir=${sdkPath.replace(/\\/g, '\\\\')}\n`);
console.log('[withHuaweiPush] created local.properties with SDK path');
}
}
return config;
},
]);
config = withAndroidManifest(config, (config) => {
return config;
});
return config;
};
module.exports = withHuaweiPush;