From 0e79dbf6550b491a5b7a5cdafcc5531889bdfee4 Mon Sep 17 00:00:00 2001 From: lafay <2021211506@stu.hit.edu.cn> Date: Thu, 18 Jun 2026 12:46:34 +0800 Subject: [PATCH] feat(push): add OPPO and vivo push plugins, bump JPush to 6.1.0 - Add withOppoPush.js and withVivoPush.js config plugins - Register new plugins in app.json - Bump JPush version from 5.8.0 to 6.1.0 in Honor/Huawei/Xiaomi plugins --- app.json | 2 + plugins/withHonorPush.js | 2 +- plugins/withHuaweiPush.js | 2 +- plugins/withOppoPush.js | 144 ++++++++++++++++++++++++++++++++++++++ plugins/withVivoPush.js | 141 +++++++++++++++++++++++++++++++++++++ plugins/withXiaomiPush.js | 2 +- 6 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 plugins/withOppoPush.js create mode 100644 plugins/withVivoPush.js diff --git a/app.json b/app.json index ae786b4..d5aceef 100644 --- a/app.json +++ b/app.json @@ -157,6 +157,8 @@ "./plugins/withHuaweiPush", "./plugins/withXiaomiPush", "./plugins/withHonorPush", + "./plugins/withVivoPush", + "./plugins/withOppoPush", "expo-callkit-telecom", [ "expo-splash-screen", diff --git a/plugins/withHonorPush.js b/plugins/withHonorPush.js index 0982e6e..d5b6501 100644 --- a/plugins/withHonorPush.js +++ b/plugins/withHonorPush.js @@ -21,7 +21,7 @@ const path = require('path'); // 没有这个问题。 const withHonorPush = (config, options = {}) => { const { - jpushVersion = '5.8.0', + jpushVersion = '6.1.0', appId = '104562917', } = options; diff --git a/plugins/withHuaweiPush.js b/plugins/withHuaweiPush.js index 92b7c2e..27d729c 100644 --- a/plugins/withHuaweiPush.js +++ b/plugins/withHuaweiPush.js @@ -9,7 +9,7 @@ const fs = require('fs'); const path = require('path'); const withHuaweiPush = (config, options = {}) => { - const { jpushVersion = '5.8.0' } = options; + const { jpushVersion = '6.1.0' } = options; config = withSettingsGradle(config, (config) => { const contents = config.modResults.contents; diff --git a/plugins/withOppoPush.js b/plugins/withOppoPush.js new file mode 100644 index 0000000..e38bea3 --- /dev/null +++ b/plugins/withOppoPush.js @@ -0,0 +1,144 @@ +const { withDangerousMod } = require('@expo/config-plugins'); +const fs = require('fs'); +const path = require('path'); + +// OPPO 厂商推送通道的 Expo config plugin. +// +// 客户端职责(依据极光官方文档 https://docs.jiguang.cn/jpush/client/Android/android_3rd_guide): +// 1) 添加 cn.jiguang.sdk.plugin:oppo 依赖(5.9.0+ 自动包含 heytap 官方 aar) +// 2) 注入 manifestPlaceholders: OPPO_APPKEY / OPPO_APPID / OPPO_APPSECRET(带 OP- 前缀) +// 3) 添加 Proguard 规则 (coloros.mcsdk / heytap / mcs 三个包保留) +// +// 注意事项: +// - 极光 JPUSH_CHANNEL 是「APK 分发渠道」统计字段,不是厂商通道开关,**不应覆盖**。 +// - OPPO 与小米/vivo/荣耀不同:客户端需要 3 个占位符(含 AppSecret),且必须带 `OP-` 前缀。 +// - OPPO AAR 在 JPush 5.9.0+ 通过 maven 自动拉取,不需要手动下载 aar 文件。 +// - OPPO SDK 3.1.0+ 依赖 gson 2.6.2 和 androidx.annotation 1.1.0,5.9.0 maven 已自动包含。 +// +// 实现说明:用 withDangerousMod 直接读写磁盘文件。 +// 原因:Expo SDK 56+ 的 withAppBuildGradle hook 链中,第二个以后的 plugin 写回 +// modResults.contents 会被静默忽略,导致多个 vendor plugin 只能写入第一个的修改。 +const withOppoPush = (config, options = {}) => { + const { + jpushVersion = '6.1.0', + appId = '37219099', + appKey = '4eb532fcddd147c0bf7c356a149b2058', + appSecret = '9a0c290b2b8049e1893246ed7b9367d0', + } = options; + + config = withDangerousMod(config, [ + 'android', + async (config) => { + const appBuildGradlePath = path.join( + config.modRequest.platformProjectRoot, + 'app', + 'build.gradle' + ); + const proguardFilePath = path.join( + config.modRequest.platformProjectRoot, + 'app', + 'proguard-rules.pro' + ); + + // --- 1. 处理 build.gradle --- + if (fs.existsSync(appBuildGradlePath)) { + let gradle = fs.readFileSync(appBuildGradlePath, 'utf-8'); + let changed = false; + + // 1a. 注入 AAR 依赖 + if (!gradle.includes('cn.jiguang.sdk.plugin:oppo')) { + const depsPattern = /dependencies\s*\{/; + if (depsPattern.test(gradle)) { + gradle = gradle.replace( + depsPattern, + `dependencies {\n implementation 'cn.jiguang.sdk.plugin:oppo:${jpushVersion}'` + ); + changed = true; + } + } + + // 1b. 注入 manifestPlaceholders(带 OP- 前缀) + const manifestPlaceholderRegex = /manifestPlaceholders\s*=\s*\[([\s\S]*?)\]/; + const match = gradle.match(manifestPlaceholderRegex); + + // OPPO 官方要求占位符值必须以 "OP-" 前缀开头 + const requiredPlaceholders = { + OPPO_APPKEY: `OP-${appKey}`, + OPPO_APPID: `OP-${appId}`, + OPPO_APPSECRET: `OP-${appSecret}`, + }; + + if (match) { + const existingBlock = match[1]; + const existingEntries = {}; + const entryRegex = /(\w+)\s*:\s*"([^"]*)"/g; + let entryMatch; + while ((entryMatch = entryRegex.exec(existingBlock)) !== null) { + existingEntries[entryMatch[1]] = entryMatch[2]; + } + + let placeholdersChanged = false; + for (const [key, value] of Object.entries(requiredPlaceholders)) { + if (existingEntries[key] !== value) { + existingEntries[key] = value; + placeholdersChanged = true; + } + } + + if (placeholdersChanged) { + const entriesStr = Object.entries(existingEntries) + .map(([k, v]) => `${k}: "${v}"`) + .join(',\n '); + const newBlock = `\n manifestPlaceholders = [\n ${entriesStr}\n ]`; + gradle = gradle.replace(manifestPlaceholderRegex, newBlock.trim()); + changed = true; + } + } else if (gradle.includes('REACT_NATIVE_RELEASE_LEVEL')) { + // 没有 manifestPlaceholders block 时新建 + const entriesStr = Object.entries(requiredPlaceholders) + .map(([k, v]) => `${k}: "${v}"`) + .join(',\n '); + const block = `\n manifestPlaceholders = [\n ${entriesStr}\n ]\n`; + const lines = gradle.split('\n'); + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes('REACT_NATIVE_RELEASE_LEVEL')) { + lines.splice(i + 1, 0, block); + break; + } + } + gradle = lines.join('\n'); + changed = true; + } + + if (changed) { + fs.writeFileSync(appBuildGradlePath, gradle); + console.log('[withOppoPush] updated app/build.gradle (AAR + manifestPlaceholders)'); + } + } + + // --- 2. 处理 proguard-rules.pro --- + if (fs.existsSync(proguardFilePath)) { + let proguard = fs.readFileSync(proguardFilePath, 'utf-8'); + if (!proguard.includes('-keep class com.heytap.**')) { + const oppoRules = ` +# JPush OPPO vendor channel — keep rules (https://docs.jiguang.cn/jpush/client/Android/android_3rd_guide) +-dontwarn com.coloros.mcsdk.** +-dontwarn com.heytap.** +-dontwarn com.mcs.** +-keep class com.coloros.mcsdk.** { *; } +-keep class com.heytap.** { *; } +-keep class com.mcs.** { *; } +`; + fs.writeFileSync(proguardFilePath, proguard + oppoRules); + console.log('[withOppoPush] appended OPPO Proguard rules to proguard-rules.pro'); + } + } + + return config; + }, + ]); + + return config; +}; + +module.exports = withOppoPush; diff --git a/plugins/withVivoPush.js b/plugins/withVivoPush.js new file mode 100644 index 0000000..24bb933 --- /dev/null +++ b/plugins/withVivoPush.js @@ -0,0 +1,141 @@ +const { withDangerousMod } = require('@expo/config-plugins'); +const fs = require('fs'); +const path = require('path'); + +// vivo 厂商推送通道的 Expo config plugin. +// +// 客户端职责(依据极光官方文档 https://docs.jiguang.cn/jpush/client/Android/android_3rd_guide): +// 1) 添加 cn.jiguang.sdk.plugin:vivo 依赖 +// 2) 注入 manifestPlaceholders: VIVO_APPKEY / VIVO_APPID(AAR 自带 vivo Receiver) +// 3) 添加 Proguard 规则 (com.vivo.push.** + com.vivo.vms.** 保留) +// +// 注意事项: +// - 极光 JPUSH_CHANNEL 是「APK 分发渠道」统计字段,不是厂商通道开关,**不应覆盖**。 +// - 客户端仅需要 VIVO_APPKEY / VIVO_APPID 两个占位符。 +// VIVO_APP_SECRET 只在极光控制台「集成设置」页面填写,客户端不需要。 +// - vivo AAR 已发布到 Maven Central,不需要额外 Maven 仓库。 +// - vivo 平台限制:若应用未上架 vivo 商店,测试推送时需在 vivo 推送平台添加测试设备, +// 且需通过 API 指定 push_mode=1(测试推送)下发。 +// +// 实现说明:用 withDangerousMod 直接读写磁盘文件。 +// 原因:Expo SDK 56+ 的 withAppBuildGradle hook 链中,第二个以后的 plugin 写回 +// modResults.contents 会被静默忽略,导致多个 vendor plugin 只能写入第一个的修改。 +// withDangerousMod 走文件系统,串行、可靠,幂等检查保证可重复执行 prebuild。 +const withVivoPush = (config, options = {}) => { + const { + jpushVersion = '6.1.0', + appId = '106100295', + appKey = '2110cb5f6dc5bd7abea82c38f8d0a1ec', + } = options; + + config = withDangerousMod(config, [ + 'android', + async (config) => { + const appBuildGradlePath = path.join( + config.modRequest.platformProjectRoot, + 'app', + 'build.gradle' + ); + const proguardFilePath = path.join( + config.modRequest.platformProjectRoot, + 'app', + 'proguard-rules.pro' + ); + + // --- 1. 处理 build.gradle --- + if (fs.existsSync(appBuildGradlePath)) { + let gradle = fs.readFileSync(appBuildGradlePath, 'utf-8'); + let changed = false; + + // 1a. 注入 AAR 依赖 + if (!gradle.includes('cn.jiguang.sdk.plugin:vivo')) { + const depsPattern = /dependencies\s*\{/; + if (depsPattern.test(gradle)) { + gradle = gradle.replace( + depsPattern, + `dependencies {\n implementation 'cn.jiguang.sdk.plugin:vivo:${jpushVersion}'` + ); + changed = true; + } + } + + // 1b. 注入 manifestPlaceholders + const manifestPlaceholderRegex = /manifestPlaceholders\s*=\s*\[([\s\S]*?)\]/; + const match = gradle.match(manifestPlaceholderRegex); + + const requiredPlaceholders = { + VIVO_APPKEY: appKey, + VIVO_APPID: appId, + }; + + if (match) { + const existingBlock = match[1]; + const existingEntries = {}; + const entryRegex = /(\w+)\s*:\s*"([^"]*)"/g; + let entryMatch; + while ((entryMatch = entryRegex.exec(existingBlock)) !== null) { + existingEntries[entryMatch[1]] = entryMatch[2]; + } + + let placeholdersChanged = false; + for (const [key, value] of Object.entries(requiredPlaceholders)) { + if (existingEntries[key] !== value) { + existingEntries[key] = value; + placeholdersChanged = true; + } + } + + if (placeholdersChanged) { + const entriesStr = Object.entries(existingEntries) + .map(([k, v]) => `${k}: "${v}"`) + .join(',\n '); + const newBlock = `\n manifestPlaceholders = [\n ${entriesStr}\n ]`; + gradle = gradle.replace(manifestPlaceholderRegex, newBlock.trim()); + changed = true; + } + } else if (gradle.includes('REACT_NATIVE_RELEASE_LEVEL')) { + // 没有 manifestPlaceholders block 时新建 + const entriesStr = Object.entries(requiredPlaceholders) + .map(([k, v]) => `${k}: "${v}"`) + .join(',\n '); + const block = `\n manifestPlaceholders = [\n ${entriesStr}\n ]\n`; + const lines = gradle.split('\n'); + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes('REACT_NATIVE_RELEASE_LEVEL')) { + lines.splice(i + 1, 0, block); + break; + } + } + gradle = lines.join('\n'); + changed = true; + } + + if (changed) { + fs.writeFileSync(appBuildGradlePath, gradle); + console.log('[withVivoPush] updated app/build.gradle (AAR + manifestPlaceholders)'); + } + } + + // --- 2. 处理 proguard-rules.pro --- + if (fs.existsSync(proguardFilePath)) { + let proguard = fs.readFileSync(proguardFilePath, 'utf-8'); + if (!proguard.includes('-keep class com.vivo.push.**')) { + const vivoRules = ` +# JPush vivo vendor channel — keep rules (https://docs.jiguang.cn/jpush/client/Android/android_3rd_guide) +-dontwarn com.vivo.push.** +-keep class com.vivo.push.** { *; } +-keep class com.vivo.vms.** { *; } +`; + fs.writeFileSync(proguardFilePath, proguard + vivoRules); + console.log('[withVivoPush] appended vivo Proguard rules to proguard-rules.pro'); + } + } + + return config; + }, + ]); + + return config; +}; + +module.exports = withVivoPush; diff --git a/plugins/withXiaomiPush.js b/plugins/withXiaomiPush.js index 1557ce7..ab8c8d3 100644 --- a/plugins/withXiaomiPush.js +++ b/plugins/withXiaomiPush.js @@ -16,7 +16,7 @@ const path = require('path'); // 串行、可靠,幂等检查保证可重复执行 prebuild。 const withXiaomiPush = (config, options = {}) => { const { - jpushVersion = '5.8.0', + jpushVersion = '6.1.0', appId = '2882303761520539252', appKey = '5792053978252', } = options;