@codemagic/react-native-patch
客户端 SDK 为 密码补丁:一个自发托管的CodePush接班人,在React Introduct和Expoapps上进行超空更新. 下载、核实和启动JS/资产捆绑在设备上,这样你就可以在没有新商店二进制的情况下运送JS更改.
此软件包只是设备上的一面 。 您还需要一个 Patch 服务器( 自托管) 和 cmpatch 国 际 发布发行。 单相机有两种,外加一个仪表板和一个本地评价堆.
你得到的东西
从 单相数字 :
- 通过 Docker 编译(API, 工人, Postgres, Silo, 仪表板) 完成堆栈
- 与非洲区域局的网络仪表板和发行管理
- 快速尝试OTA更新的在线演示应用程序
从这个SDK:
-
可用时的二进制补丁, 用于较小的下载( 返回到完整捆绑)
-
配置公钥时签名验证
-
如果新软件包在应用程序报告准备好前失败, 则自动回滚
-
用于预建项目的 Expo 配置插件, 外加赤色反射原生接线
-
单行
Patch.wrap(App)根包件, 代码Push 样式sync(),或需要控制时的分步API
所需资源
- 原生反应
0.73+——新架构支持起始时间为RN 0.76;RN 0.73–0.75仅在旧(纸)架构上支持 - 机器人
minSdkVersion23+(图书馆遵循主机rootProject.ext.minSdkVersion;没有一块,它建于23) - 支持 CMake/JNI 的 Android 本地建筑
- 使用 CocoaPods 并混合 Swift/ ObjC++ 编译的 iOS 本地建筑
- 曝光 SDK 52+ (通过捆绑的配置插件 —— 见) 博览应用程序(中文(简体) )
- 博览 无法支持 Go - 本地模块不是世博会的一部分 Go runtime
包括世博会SDK 58 / RN 0.88.0主机在内的参考固定应用程序列于 E2E 固定工作空间。 。 。 .
安装
npm install @codemagic/react-native-patch
# or
yarn add @codemagic/react-native-patch
react (单位:千美元)>=18和 react-native (单位:千美元)>=0.73)是同伴的依赖关系。 在 iOS 上,安装本地 sock :
cd ios && pod install
快开始
配置居住于 本地资源 (部署密钥,API URL,下载基址,可选的公钥),不在JS配置对象中. 博览会插件为您写出这些; 赤裸的应用程序设置在其中 Info.plist 编号 : strings.xml 和钢丝捆绑选择.
博览( 配置插件)
如果您手动维护本地工程, 请遵循 手动本地设置。 。 。 .
{
"plugins": [
["@codemagic/react-native-patch", {
"ios": {
"deploymentKey": "<key>",
"downloadBaseUrl": "<url>",
"apiUrl": "<url>",
"publicKey": "<pem>"
},
"android": {
"deploymentKey": "<key>",
"downloadBaseUrl": "<url>",
"apiUrl": "<url>",
"publicKey": "<pem>"
}
}]
]
}
配置至少一个平台 。 你提供所需的每个街区 deploymentKey, (中文(简体) ), downloadBaseUrl,以及 apiUrl (单位:千美元)publicKey 财务报告和审定财务报表 maxLaunchAttempts )),可选. 然后运行 expo prebuild。见 配置 对于每个值的来源.
裸体反应原生
添加 CodemagicPatchDeploymentKey, (中文(简体) ), CodemagicPatchApiUrl,以及 CodemagicPatchDownloadBaseUrl (可选) CodemagicPatchPublicKey)去安卓号 strings.xml 和iOS系统 Info.plist,然后指出RN的JS捆绑路径 CodemagicPatch.getJSBundleFile 编号 : CodemagicPatch.bundleURL() 因此,启动命令正在等待包 → 当前包 → 嵌入式捆绑 。 完整片段进入 配置 见下文.
环绕根元件
import * as Patch from "@codemagic/react-native-patch";
function App() {
return <AppNavigator />;
}
export default Patch.wrap(App);
wrap() 让你的应用程序马上运行 sync() 一旦根已经挂起。 没有选项, 普通更新在下次重新启动时安装, 强制更新立即安装; 通过 WrapOptions 作为改变这种情况的第二个论据(Patch.wrap(App, { installMode: Patch.InstallMode.ON_NEXT_RESUME })) (中文(简体) ).
设定 checkFrequency: "ON_APP_RESUME" 以检查挂载和前景返回; 默认值 "ON_APP_START" 只检查挂载。 例如: Patch.wrap(App, { checkFrequency: Patch.CheckFrequency.ON_APP_RESUME, installMode: Patch.InstallMode.ON_NEXT_RESTART })。用于手动检查时,使用没有 wrap()。 。 。 . Patch.CheckFrequency 财务报告和审定财务报表 Patch.InstallMode 提供命名常数; 现有的字符串文字仍然支持.
wrap() 向您的根组件转发道具和校对。 它不复制自定义静态属性, 例如 : App.navigationOptions在包装上.
上载被包裹的根 sync() 电话 notifyAppReady() 首先,这证实了运行的包 和武器回滚的下一个。 如果您的应用程序必须完成同步的靴子陷阱( 认证、 数据库迁移) , 才能保证运行的捆绑, 请跳过 wrap() 打电话 sync() 那时你自己:
import { sync } from "@codemagic/react-native-patch";
import { bootstrapApp } from "./bootstrap"; // Your app's initialization routine.
await bootstrapApp(); // If initialization fails, do not confirm the bundle.
const status = await sync();
// "update-installed" | "up-to-date" | "embedded-revert-applied"
// | "sync-in-progress" | "error"
sync() 从来没有扔过。 它检查一个更新,下载和安装 在适当的时候, 和报告应用准备.
检查/ 下载/ 安装为单独的步骤, 重新启动计时帮助器( E) :restartApp, (中文(简体) ), allowRestart 编号 : disallowRestart, (中文(简体) ), getRunningBundleUpdateMetadata() 读取 OTA 发布正在运行中,或 isNextVersionReady() 以了解是否正在等待下一次重新装入已安装的更新,请参见 SDK 类型。 。 。 .
配置
密码补丁通过配置 本地资源,而不是一个JS API——在SDK初始化之前,在本地读取部署密钥,URL,公钥和二进制版本. 每个主机应用程序提供这些值 :
| 资源 | 需求 | 含义 |
|---|---|---|
CodemagicPatchDeploymentKey | 对 | 此应用程序/ 轨道的部署密钥 |
CodemagicPatchApiUrl | 对 | API 服务器来源( 服务器的) SERVER_URL),例如. https://updates.example.com。SDK附件 /v1/... |
CodemagicPatchDownloadBaseUrl | 对 | 艺术来源( 服务器的) PUBLIC_BASE_URL),例如. https://storage.example.com/codemagic-patch。可能包括桶/路径前缀;SDK附件显示/艺术路径 |
CodemagicPatchPublicKey | 没有 | PEM 公钥; 仅在执行客户端签名验证时需要 |
CodemagicPatchMaxLaunchAttempts | 没有 | 正整数; 连续发射未完成的更新可能会启动, 不包含 notifyAppReady() 在坠入前。 默认 2; 见 notifyAppReady() |
这两个URL指向不同的系统(API服务器对对象存储 / CDN),这就是一个通常携带路径而另一个不携带路径的原因.
数值来自: 部署键为 DEPLOYMENT_KEY 页:1 cmpatch deployment list --app <app-name> --format table (或部署在仪表板中的页面);API URL和下载基础URL由自主机安装者在服务器设置时打印出. 使用 iOS和Android的单独应用软件,因此也分别部署密钥。 。 。 .
裸体反应原生
-
申报资源.
iOS - (英语)
ios/<YourApp>/Info.plist数字 :<key>CodemagicPatchDeploymentKey</key> <string>ios-staging-deployment-key</string> <key>CodemagicPatchDownloadBaseUrl</key> <string>https://storage-updates.example.com/codemagic-patch</string> <key>CodemagicPatchApiUrl</key> <string>https://updates.example.com</string> <!-- optional, only when enforcing code signing --> <key>CodemagicPatchPublicKey</key> <string>-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----</string> <!-- optional, defaults to 2 --> <key>CodemagicPatchMaxLaunchAttempts</key> <integer>2</integer>Android(英语:
android/app/src/main/res/values/strings.xml数字 :<resources> <string name="CodemagicPatchDeploymentKey" translatable="false">android-staging-deployment-key</string> <string name="CodemagicPatchDownloadBaseUrl" translatable="false">https://storage-updates.example.com/codemagic-patch</string> <string name="CodemagicPatchApiUrl" translatable="false">https://updates.example.com</string> <!-- optional, only when enforcing code signing --> <string name="CodemagicPatchPublicKey" translatable="false">-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----</string> <!-- optional, defaults to 2 --> <string name="CodemagicPatchMaxLaunchAttempts" translatable="false">2</string> </resources>释放随着资源收缩而形成,保持这些串:从0.5.2起,SDK飞船是保持规则(
res/raw/codemagic_patch_keep.xml合并到应用程序中。 在旧版本中, 在应用程序中添加相同的文件为android/app/src/main/res/raw/codemagic_patch_keep.xml(无)keep.xml,由它执行土著捆绑的任务; 解决问题 有内容. -
有线本地捆绑选择 在RN桥开通之前, 启动命令是 待处理软件包 → 当前软件包 → 嵌入式捆绑。 。 。 .
Android——饲料
CodemagicPatch.getJSBundleFile(applicationContext)进入本地状态MainApplication关于RN 0.81(ReactNativeHost), 覆盖getJSBundleFile()数字 :override fun getJSBundleFile(): String? = CodemagicPatch.getJSBundleFile(applicationContext)在RN 0.82+(新存档)
reactHost,通过它作为jsBundleFilePath数字 :override val reactHost: ReactHost by lazy { getDefaultReactHost( context = applicationContext, packageList = PackageList(this).packages, jsBundleFilePath = CodemagicPatch.getJSBundleFile(applicationContext), ) }iOS - 导入
CodemagicPatchClient并覆盖bundleURL()编号 :sourceURL(for:)内AppDelegate选择顺序相同 留着DEBUG分公司指向Metro,所以本地开发继续工作:import CodemagicPatchClient class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { override func sourceURL(for bridge: RCTBridge) -> URL? { self.bundleURL() } override func bundleURL() -> URL? { #if DEBUG RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") #else CodemagicPatch.bundleURL() ?? Bundle.main.url(forResource: "main", withExtension: "jsbundle") #endif } }在RN Q 0.76上,应用程序模板仍装有目标-C++
AppDelegate.mm覆盖sourceURLForBridge:同样的选择。 原生模块是一个Swift舱 暴露在目标C@objc(CodemagicPatch); 生成-Swift.h不在主机目标头搜索路径上,并且@import在 ObjC++ 中不可用,所以向前声明您称之为表面 :// CodemagicPatch is a Swift pod exposed as @objc(CodemagicPatch); forward-declare it — // the implementation links in from the pod's static library. @interface CodemagicPatch : NSObject + (NSURL *_Nullable)bundleURL; @end - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { #if DEBUG return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; #else return [CodemagicPatch bundleURL] ?: [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; #endif }预期嵌入式捆绑名称为
index.android.bundle(机器人)和main.jsbundle(iOS) (中文(简体) ). 如果 SDK 无法确定非空白二进制版本( S)versionName编号 :CFBundleShortVersionString),它不 -ops并倒回嵌入式捆绑.调试从 Metro 创建负载 JS,所以OTA的更新没有被接收到那里——这是预期的,不是一个接线问题. 要看到更新应用程序, 请运行一个发布样式的构建 .
-
注册本地模块 用于您的架构 — TurboModule (新架构) 或桥模块/包 (旧架构) 。 自动链接在大多数应用程序中处理此操作 .
博览应用程序
对于使用预建工程,请使用捆绑的配置插件( P)app.plugin.js如所示 快开始. 人工维护的本地项目使用本地配置和捆绑选择步骤取而代之。 插件配置为:
{
"plugins": [
["@codemagic/react-native-patch", {
"ios": { "deploymentKey": "<key>", "downloadBaseUrl": "<url>", "apiUrl": "<url>", "publicKey": "<pem>" },
"android": { "deploymentKey": "<key>", "downloadBaseUrl": "<url>", "apiUrl": "<url>", "publicKey": "<pem>" }
}]
]
}
插件会自动写入本地资源并捆绑选择( Confignation steps 1–3) 。 配置 至少一个平台,并完成你提供的每一块块—— deploymentKey, (中文(简体) ), downloadBaseUrl,以及 apiUrl 需要每个区块(publicKey 财务报告和审定财务报表 maxLaunchAttempts )),可选. 插件解析 expo/config-plugins 从你的应用程序的 Expo SDK, 所以没有额外的安装; expo 不是此软件包的运行时间或同行依赖.
使用量
最简单的整合是 Patch.wrap(App),使您的根组件立即运行 sync() 在它升起之后. sync() 检查更新,下载,安装, 并报告应用准备在一个呼叫中。 它从不扔出——它解决了一个状态字符串. 当您需要自己选择时刻时直接拨打它, 例如在同步的靴子后:
import { sync } from "@codemagic/react-native-patch";
import { bootstrapApp } from "./bootstrap"; // Your app's initialization routine.
await bootstrapApp(); // If initialization fails, do not confirm the bundle.
const status = await sync();
// "update-installed" | "up-to-date" | "embedded-revert-applied"
// | "sync-in-progress" | "error"
更精细的控制,你自己开车 并打电话 notifyAppReady() 一旦应用程序成功启动( 所以更新不会回滚) :
import {
checkForUpdate,
downloadUpdate,
installUpdate,
notifyAppReady,
} from "@codemagic/react-native-patch";
restartApp, (中文(简体) ), allowRestart 编号 : disallowRestart,以及 hydrate 也用于控制重装时间, getRunningBundleUpdateMetadata() 报告运行中的OTA发布({ label, packageHash, releaseNotes },或 null 在嵌入的捆绑上 isNextVersionReady() 报告是否安装了新的软件包并等待下一次重新装入。 见 类型定义 对于完整的API表面.
测试
SDK在被导入时解决了它的涡轮Module,所以在Jest下,在没有本土二进制的情况下,一个在任何测试运行前导入失败的套件. 包裹为杰斯特装了个假的本地舱 真正的SDK运行在它上面,所以 sync(), (中文(简体) ), checkForUpdate() 其余的都按程序行事.
保留您已经使用的预设, 添加设置文件并添加 @codemagic 到允许列表 transformIgnorePatterns 已经拥有 :
// jest.config.js
module.exports = {
preset: '@react-native/jest-preset', // or 'react-native' on older RN, or 'jest-expo'
setupFiles: ['@codemagic/react-native-patch/jest-setup'],
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|@react-native(-community)?|@codemagic)/)',
],
};
需要许可名单条目是因为 react-native 导出条件 此软件包会像 React 原住民本身那样解决其类型Script 源头, 而 Jest 也不会变换 node_modules 默认.
设置文件安装了默认情景:应用程序运行商店二进制,服务器没有更新. 这足以进行SDK装入的测试. 对于另一种情况,安装有种子的假象,然后装入SDK:
import { installPatchFake, loadSdk } from '@codemagic/react-native-patch/jest';
test('reports the running release', async () => {
installPatchFake({ running: { label: 'v4' } });
const Patch = await loadSdk();
expect(await Patch.getRunningBundleUpdateMetadata()).toMatchObject({ label: 'v4' });
});
test('an update is ready for the next launch', async () => {
installPatchFake({ server: { release: { label: 'v5' } } });
const Patch = await loadSdk();
const status = await Patch.sync({
installMode: Patch.InstallMode.ON_NEXT_RESTART,
mandatoryInstallMode: Patch.InstallMode.ON_NEXT_RESTART,
});
expect(status).toBe('update-installed');
expect(await Patch.isNextVersionReady()).toBe(true);
});
种子也带走了 binaryVersion, (中文(简体) ), runningUnconfirmed 与 previous, (中文(简体) ), failedInstall,以及 server.latestBinaryVersion 编号 : server.error· ; fake.fail(method, error) 让一个本地人拒绝呼叫 fake.setServer() 更改服务器的答案。 见 src/fake/types.ts。 。 。 .
隔离规则:
- SDK每个模块实例读取原生模块和启动状态一次.
installPatchFake()仅会影响SDK 之后导入的例, 因此在导入正在测试的代码之前安装, 或调用loadSdk()安装后. loadSdk()电话jest.resetModules()并导入新的 SDK,取消上一个loadSdk()案件未决ON_NEXT_SUSPEND重新装入。 需要其他任何使用SDK(例如,你的组件)的,所以他们分享这个实例.loadSdk()不重置假件: 失败、 服务器答覆和下载的软件包 。 以新的方案开始installPatchFake()。 。 。 .sync()先确认一个未确认的运行软件包; 使用checkForUpdate()编号 :downloadUpdate()编号 :installUpdate()保持它未经证实.restartApp()在不重装JS的情况下解决.
在 Vitest 上,添加插件和设置文件,然后使用 installPatchFake 编号 : loadSdk 从 @codemagic/react-native-patch/vitest 同样的方式。 插件在它的TypeScript源头指向包,所以Vitest的模块会模拟到SDK. 适配器替换 react-native 带有一个只有SDK使用的模拟器(TurboModuleRegistry, (中文(简体) ), AppState, (中文(简体) ), DeviceEventEmitter因此,使组件与 react-dom 在一个 jsdom 环境。 在 Jest 下, 导入您使用 SDK 的模块 loadSdk(); React本身没有被重置,所以测试的React和SDK保持不变.
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { codemagicPatch } from '@codemagic/react-native-patch/vitest-plugin';
export default defineConfig({
plugins: [codemagicPatch()],
test: { setupFiles: ['@codemagic/react-native-patch/vitest-setup'] },
});
尚未覆盖:代码签名(假报告没有签名密钥),下载进度,推出百分比,以及检查SDK发送的度量事件.
文档
全面整合指南,更新协议,以及自我托管指令在 编码补丁寄存器。 。 。 .
来自 react-native-code-push♪ The 代码Push 迁移指南 绘制本地配置资源、 JS API 和 CLI 命令的代码补丁.