|
26 | 26 | > - [Expo SDK 53+ 集成极光推送 iOS Swift](https://juejin.cn/post/7554288083597885467) |
27 | 27 | > - [RunoMeow/jpush-expo-config-plugin](https://github.com/RunoMeow/jpush-expo-config-plugin) |
28 | 28 |
|
29 | | -## 最新特性 |
30 | | - |
31 | | -- ✅ **完整厂商通道支持**:覆盖华为、FCM、小米、OPPO、VIVO、魅族、荣耀、蔚来等主流厂商,自动配置SDK依赖与原生参数 |
32 | | -- ✅ **安全密钥管理**:Android 敏感参数支持从环境变量/gradle.properties读取,不再明文写入构建脚本 |
33 | | -- ✅ **iOS 安全优化**:JPush 初始化参数从 Info.plist 读取,不再直接注入 AppDelegate.swift 源码 |
34 | | -- ✅ **多环境适配**:支持生产/开发环境自动切换 APNs 配置,适配 CI/CD 流水线 |
35 | | -- ✅ **幂等注入机制**:Swift 桥接头文件、后台模式配置自动合并,不会覆盖宿主项目已有配置 |
36 | | - |
37 | 29 | ## 目录 |
38 | 30 |
|
39 | 31 | - [为什么使用它](#为什么使用它) |
|
66 | 58 | - 你使用 Expo,但需要 JPush 和厂商通道能力 |
67 | 59 | - 你希望把原生改动交给 Config Plugin 管理,而不是手改生成代码 |
68 | 60 | - 你需要在 CI / 团队协作里稳定复现 `prebuild` 输出 |
69 | | -- 你需要严格的密钥安全管理,避免敏感信息泄露 |
| 61 | +- 你需要严格的密钥安全管理,避免敏感信息泄露 |
| 62 | + |
| 63 | +## 支持矩阵 |
| 64 | + |
| 65 | +| 项目 | 版本 | |
| 66 | +| --- | --- | |
| 67 | +| Expo SDK | `53+` | |
| 68 | +| 仓库开发基线 | `Expo SDK 53` | |
| 69 | +| React Native | `0.76.9` | |
| 70 | +| Node.js | `>= 18.18.0` | |
| 71 | +| `jpush-react-native` | `3.1.9` | |
| 72 | +| `jcore-react-native` | `2.3.0` | |
| 73 | + |
| 74 | +## 快速开始 |
| 75 | + |
| 76 | +### 1. 安装依赖 |
| 77 | + |
| 78 | +```bash |
| 79 | +npm install mx-jpush-expo |
| 80 | +npm install jpush-react-native@3.1.9 jcore-react-native@^2.3.0 |
| 81 | +``` |
| 82 | + |
| 83 | +或使用 `pnpm`: |
| 84 | + |
| 85 | +```bash |
| 86 | +pnpm add mx-jpush-expo |
| 87 | +pnpm add jpush-react-native@3.1.9 jcore-react-native@^2.3.0 |
| 88 | +``` |
| 89 | + |
| 90 | +### 2. 配置插件 |
| 91 | + |
| 92 | +推荐使用 `app.config.ts`,并把 Android 的敏感值交给环境变量或 `gradle.properties`。 |
| 93 | + |
| 94 | +### 3. 生成原生工程 |
| 95 | + |
| 96 | +```bash |
| 97 | +npx expo prebuild |
| 98 | +``` |
| 99 | + |
| 100 | +只刷新 Android: |
| 101 | + |
| 102 | +```bash |
| 103 | +npx expo prebuild -p android |
| 104 | +``` |
| 105 | + |
| 106 | +## 推荐配置 |
| 107 | + |
| 108 | +```ts |
| 109 | +import type { ConfigContext, ExpoConfig } from 'expo/config'; |
| 110 | +import 'dotenv/config'; |
| 111 | + |
| 112 | +export default ({ config }: ConfigContext): ExpoConfig => { |
| 113 | + const isProduction = process.env.EXPO_PUBLIC_ENV === 'production'; |
| 114 | + |
| 115 | + return { |
| 116 | + ...config, |
| 117 | + plugins: [ |
| 118 | + ...(config.plugins || []), |
| 119 | + [ |
| 120 | + 'mx-jpush-expo', |
| 121 | + { |
| 122 | + appKey: process.env.JPUSH_APP_KEY ?? '', |
| 123 | + channel: process.env.JPUSH_CHANNEL ?? 'developer-default', |
| 124 | + packageName: |
| 125 | + process.env.JPUSH_PKGNAME ?? config.android?.package ?? '', |
| 126 | + apsForProduction: isProduction, |
| 127 | + vendorChannels: { |
| 128 | + huawei: { enabled: true }, |
| 129 | + fcm: { enabled: true }, |
| 130 | + xiaomi: { |
| 131 | + appId: process.env.JPUSH_XIAOMI_APP_ID, |
| 132 | + appKey: process.env.JPUSH_XIAOMI_APP_KEY, |
| 133 | + }, |
| 134 | + oppo: { |
| 135 | + appId: process.env.JPUSH_OPPO_APP_ID, |
| 136 | + appKey: process.env.JPUSH_OPPO_APP_KEY, |
| 137 | + appSecret: process.env.JPUSH_OPPO_APP_SECRET, |
| 138 | + }, |
| 139 | + vivo: { |
| 140 | + appId: process.env.JPUSH_VIVO_APP_ID, |
| 141 | + appKey: process.env.JPUSH_VIVO_APP_KEY, |
| 142 | + }, |
| 143 | + meizu: { |
| 144 | + appId: process.env.JPUSH_MEIZU_APP_ID, |
| 145 | + appKey: process.env.JPUSH_MEIZU_APP_KEY, |
| 146 | + }, |
| 147 | + honor: { |
| 148 | + appId: process.env.JPUSH_HONOR_APP_ID, |
| 149 | + }, |
| 150 | + nio: { |
| 151 | + appId: process.env.JPUSH_NIO_APP_ID, |
| 152 | + }, |
| 153 | + }, |
| 154 | + }, |
| 155 | + ], |
| 156 | + ], |
| 157 | + }; |
| 158 | +}; |
| 159 | +``` |
| 160 | + |
| 161 | +### 配置要点 |
| 162 | + |
| 163 | +- `appKey`、`channel`、`packageName` 仍然是插件必填项 |
| 164 | +- iOS 初始化参数会写入 `Info.plist`,不再直接拼进 `AppDelegate.swift` |
| 165 | +- Android `manifestPlaceholders` 优先读取环境变量或 `gradle.properties` |
| 166 | +- `vendorChannels` 决定要注入哪些厂商 SDK 与占位符,厂商密钥本身建议交给环境变量 |
| 167 | + |
| 168 | +## 环境变量与厂商通道 |
| 169 | + |
| 170 | +Android 端的 `manifestPlaceholders` 读取优先级如下: |
| 171 | + |
| 172 | +1. `System.getenv("...")` |
| 173 | +2. `project.findProperty("...")` |
| 174 | +3. 插件收到的默认值,仅 `JPUSH_PKGNAME` |
| 175 | +4. 空字符串,其余字段 |
| 176 | + |
| 177 | +### 可用环境变量 |
| 178 | + |
| 179 | +| 变量名 | 说明 | |
| 180 | +| --- | --- | |
| 181 | +| `JPUSH_APP_KEY` | JPush AppKey | |
| 182 | +| `JPUSH_CHANNEL` | JPush Channel | |
| 183 | +| `JPUSH_PKGNAME` | Android 包名 | |
| 184 | +| `JPUSH_XIAOMI_APP_ID` / `JPUSH_XIAOMI_APP_KEY` | 小米通道 | |
| 185 | +| `JPUSH_OPPO_APP_ID` / `JPUSH_OPPO_APP_KEY` / `JPUSH_OPPO_APP_SECRET` | OPPO 通道 | |
| 186 | +| `JPUSH_VIVO_APP_ID` / `JPUSH_VIVO_APP_KEY` | VIVO 通道 | |
| 187 | +| `JPUSH_MEIZU_APP_ID` / `JPUSH_MEIZU_APP_KEY` | 魅族通道 | |
| 188 | +| `JPUSH_HONOR_APP_ID` | 荣耀通道 | |
| 189 | +| `JPUSH_NIO_APP_ID` | 蔚来通道 | |
| 190 | + |
| 191 | +示例 `.env`: |
| 192 | + |
| 193 | +```bash |
| 194 | +JPUSH_APP_KEY=your-jpush-app-key |
| 195 | +JPUSH_CHANNEL=developer-default |
| 196 | +JPUSH_PKGNAME=com.your.app |
| 197 | +JPUSH_XIAOMI_APP_ID=your-xiaomi-app-id |
| 198 | +JPUSH_XIAOMI_APP_KEY=your-xiaomi-app-key |
| 199 | +``` |
| 200 | + |
| 201 | +### 厂商通道额外要求 |
| 202 | + |
| 203 | +| 厂商 | 额外文件 | 签名要求 | 说明 | |
| 204 | +| --- | --- | --- | --- | |
| 205 | +| 华为 | `agconnect-services.json` | 需要 | 需配置 SHA256 指纹 | |
| 206 | +| FCM | `google-services.json` | 不需要 | 需在 Firebase 控制台申请 | |
| 207 | +| 荣耀 | 无 | 需要 | 需配置 SHA256 指纹 | |
| 208 | +| 蔚来 | 无 | 需要 | 需配置应用签名 | |
| 209 | +| 小米 | 无 | 不需要 | 仅需 AppId / AppKey | |
| 210 | +| OPPO | 无 | 不需要 | 仅需 AppId / AppKey / AppSecret | |
| 211 | +| VIVO | 无 | 不需要 | 仅需 AppId / AppKey | |
| 212 | +| 魅族 | 无 | 不需要 | 仅需 AppId / AppKey | |
| 213 | + |
| 214 | +官方参数说明见:[极光推送 Android 厂商通道参数获取](https://docs.jiguang.cn/jpush/client/Android/android_3rd_param) |
| 215 | + |
| 216 | +## 配置说明 |
| 217 | + |
| 218 | +### iOS 配置 |
| 219 | + |
| 220 | +- `appKey`:JPush 后台创建应用后获得的 AppKey |
| 221 | +- `channel`:渠道标识,默认 `developer-default` |
| 222 | +- `apsForProduction`:是否使用生产环境 APNs,默认 `false`(开发环境) |
| 223 | + |
| 224 | +### Android 配置 |
| 225 | + |
| 226 | +- `packageName`:Android 应用包名,用于 `manifestPlaceholders` |
| 227 | +- 厂商通道通过 `vendorChannels` 对象配置,每个厂商独立开关 |
| 228 | + |
| 229 | +## 插件会修改哪些原生文件 |
| 230 | + |
| 231 | +| 平台 | 文件 | 作用 | |
| 232 | +| --- | --- | --- | |
| 233 | +| iOS | `ios/<app>/Info.plist` | 写入 `JPUSH_*` 配置并合并 `UIBackgroundModes` | |
| 234 | +| iOS | `ios/<app>/AppDelegate.swift` | 注入 JPush 初始化、APNs 回调和代理扩展 | |
| 235 | +| iOS | `ios/<app>/<target>-Bridging-Header.h` | 复用或创建桥接头文件,保证 import 幂等 | |
| 236 | +| Android | `android/app/src/main/AndroidManifest.xml` | 写入 `JPUSH_APPKEY` / `JPUSH_CHANNEL` meta-data | |
| 237 | +| Android | `android/app/build.gradle` | 注入依赖、`manifestPlaceholders`、`abiFilters`、厂商插件 | |
| 238 | +| Android | `android/build.gradle` | 注入厂商 Maven 仓库与 classpath | |
| 239 | +| Android | `android/settings.gradle` | 注册 `jpush-react-native` / `jcore-react-native` 模块 | |
| 240 | +| Android | `android/gradle.properties` | 写入华为 AGC 兼容性开关 | |
| 241 | + |
| 242 | +## 如何验证生成结果 |
| 243 | + |
| 244 | +重新执行 `expo prebuild` 后,建议检查: |
| 245 | + |
| 246 | +### iOS |
| 247 | + |
| 248 | +- `Info.plist` 中存在: |
| 249 | + - `JPUSH_APPKEY` |
| 250 | + - `JPUSH_CHANNEL` |
| 251 | + - `JPUSH_APS_FOR_PRODUCTION` |
| 252 | +- `UIBackgroundModes` 会保留宿主已有值,并补齐: |
| 253 | + - `fetch` |
| 254 | + - `remote-notification` |
| 255 | +- `AppDelegate.swift` 中存在 `JPUSHService.setup` |
| 256 | +- 如果项目使用 Swift,插件会优先复用已有 `SWIFT_OBJC_BRIDGING_HEADER`;未配置时会自动创建 `<target>-Bridging-Header.h` |
| 257 | + |
| 258 | +### Android |
| 259 | + |
| 260 | +`android/app/build.gradle` 中的 `manifestPlaceholders` 应保持"运行时读取",而不是写死明文: |
| 261 | + |
| 262 | +```gradle |
| 263 | +manifestPlaceholders = [ |
| 264 | + JPUSH_PKGNAME: System.getenv("JPUSH_PKGNAME") ?: (project.findProperty("JPUSH_PKGNAME") ?: "com.your.app"), |
| 265 | + JPUSH_APPKEY: System.getenv("JPUSH_APP_KEY") ?: (project.findProperty("JPUSH_APP_KEY") ?: ""), |
| 266 | + JPUSH_CHANNEL: System.getenv("JPUSH_CHANNEL") ?: (project.findProperty("JPUSH_CHANNEL") ?: "") |
| 267 | +] |
| 268 | +``` |
| 269 | + |
| 270 | +## 常见问题 |
| 271 | + |
| 272 | +### 是否支持 Expo Go? |
| 273 | + |
| 274 | +不支持。JPush 需要原生工程和原生依赖,必须通过 `expo prebuild` 进入 Dev Client 或原生构建流程。 |
| 275 | + |
| 276 | +### 为什么 iOS 仍然要求在插件配置里填写 `appKey` / `channel`? |
| 277 | + |
| 278 | +因为参数校验和 `Info.plist` 注入都发生在 Expo 配置阶段。它们不会再被直接拼进 `AppDelegate.swift`,但仍然需要在配置阶段提供。 |
| 279 | + |
| 280 | +### Android 遇到 Gradle 插件版本错误怎么办? |
| 281 | + |
| 282 | +如果你遇到类似 `com.android.tools.build:gradle is no set in the build.gradle file` 的错误,需要检查业务项目自己的 `android/build.gradle` 与 Expo 版本是否匹配。这不是本插件主动引入的行为变更。 |
| 283 | + |
| 284 | +### 直接改 `node_modules/mx-jpush-expo` 可以吗? |
| 285 | + |
| 286 | +不建议。重装依赖后会丢失,正式方式建议使用 `pnpm patch mx-jpush-expo` 或维护自己的 fork。 |
| 287 | + |
| 288 | +## 开发与测试 |
| 289 | + |
| 290 | +```bash |
| 291 | +npm run build |
| 292 | +npm run test -- --runInBand |
| 293 | +npm run lint |
| 294 | +``` |
| 295 | + |
| 296 | +### 测试覆盖重点 |
| 297 | + |
| 298 | +- iOS `Info.plist` 合并与 Bridging Header 创建 / 幂等 |
| 299 | +- iOS `AppDelegate.swift` 注入与幂等 |
| 300 | +- Android `Manifest`、Gradle、Settings 和 `gradle.properties` 原生输出 |
| 301 | +- fixture-based 回归测试,确保 `compileModsAsync` 输出稳定 |
| 302 | + |
| 303 | +更多开发细节见 [DEVELOPMENT.md](./DEVELOPMENT.md)。 |
| 304 | + |
| 305 | +## 项目结构 |
| 306 | + |
| 307 | +```text |
| 308 | +mx-jpush-expo/ |
| 309 | +├── app.plugin.js |
| 310 | +├── plugin/ |
| 311 | +│ ├── src/ |
| 312 | +│ │ ├── index.ts |
| 313 | +│ │ ├── types.ts |
| 314 | +│ │ ├── ios/ |
| 315 | +│ │ │ ├── infoPlist.ts |
| 316 | +│ │ │ ├── appDelegate.ts |
| 317 | +│ │ │ └── bridgingHeader.ts |
| 318 | +│ │ ├── android/ |
| 319 | +│ │ │ ├── androidManifest.ts |
| 320 | +│ │ │ ├── appBuildGradle.ts |
| 321 | +│ │ │ ├── projectBuildGradle.ts |
| 322 | +│ │ │ ├── settingsGradle.ts |
| 323 | +│ │ │ └── gradleProperties.ts |
| 324 | +│ │ └── utils/ |
| 325 | +│ │ ├── codeValidator.ts |
| 326 | +│ │ ├── config.ts |
| 327 | +│ │ ├── generateCode.ts |
| 328 | +│ │ ├── sourceCode.ts |
| 329 | +│ │ └── vendorChannels.ts |
| 330 | +│ ├── __tests__/ |
| 331 | +│ │ ├── fixtures/ |
| 332 | +│ │ ├── iosFixture.ts |
| 333 | +│ │ ├── androidFixture.ts |
| 334 | +│ │ └── *.test.ts |
| 335 | +│ └── build/ |
| 336 | +├── .github/workflows/ci.yml |
| 337 | +├── CHANGELOG.md |
| 338 | +├── DEVELOPMENT.md |
| 339 | +└── README.md |
| 340 | +``` |
| 341 | + |
| 342 | +插件内部说明见 [plugin/README.md](./plugin/README.md)。 |
| 343 | + |
| 344 | +## 最近更新 |
| 345 | + |
| 346 | +完整更新历史请查看 [CHANGELOG.md](./CHANGELOG.md)。 |
| 347 | + |
| 348 | +- iOS `UIBackgroundModes` 改为合并写入,不再覆盖宿主已有后台模式 |
| 349 | +- Swift `Bridging Header` 支持优先复用、缺失自动创建,并保持幂等 |
| 350 | +- 补齐 iOS / Android fixture-based 原生回归测试 |
| 351 | +- 加入 ESLint 与 CI 质量闭环 |
| 352 | +- 对齐 Expo SDK 53 的版本声明与仓库开发基线 |
| 353 | +- Android 敏感参数支持环境变量 / `gradle.properties` 读取,不再明文写入构建脚本 |
| 354 | +- iOS 初始化参数改为从 `Info.plist` 读取,不再直接注入 `AppDelegate.swift` |
| 355 | + |
| 356 | +## 致谢与许可 |
| 357 | + |
| 358 | +感谢以下资料与实现思路的启发: |
| 359 | + |
| 360 | +- [JPush 集成 Expo](https://juejin.cn/post/7423235127716659239) |
| 361 | +- [Expo SDK 53+ 集成极光推送 iOS Swift](https://juejin.cn/post/7554288083597885467) |
| 362 | +- [RunoMeow/jpush-expo-config-plugin](https://github.com/RunoMeow/jpush-expo-config-plugin) |
| 363 | + |
| 364 | +本项目使用 [MIT License](./LICENSE)。 |
0 commit comments