refactor: refactor react-native-lite-card to turbo module#1
Conversation
Updated LiteCardInfo to use explicit properties in both Swift and TypeScript, replacing the generic object type. Modified getLiteInfo to return LiteCardInfo or null for better type safety and clarity.
Introduces a new OnekeyLite class for managing NFC card operations, including methods for card info retrieval, permission checks, mnemonic management, PIN changes, and event handling. Adds comprehensive type definitions for callback and promise results, card info, and error codes to improve type safety and maintainability.
|
Caution Review failedFailed to post review comments WalkthroughAdds a comprehensive React Native native module for NFC Lite Card functionality on Android and iOS, including example apps, monorepo setup, and publishing infrastructure. Integrates secure NFC communication via APDU commands and certificate verification across both platforms. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant JS as JavaScript/TypeScript
participant NM as Native Module<br/>(iOS/Android)
participant NFC as NFC Subsystem
participant Card as NFC Card
participant Crypto as Secure Channel<br/>(GPC/SDK)
User->>JS: Call getLiteInfo()
JS->>NM: Request NFC session
NM->>NFC: Begin NFC scan
NFC->>Card: Detect card
Card-->>NFC: Card found
NM->>NM: Determine card version (V1/V2)
alt Card detected
NM->>Card: Send SELECT APDU
Card-->>NM: App selected
NM->>Crypto: Initialize secure channel
Crypto->>Card: Mutual authentication (OpenChannel_1)
Card-->>Crypto: Auth response
Crypto->>Card: Complete auth (OpenChannel_2)
Card-->>Crypto: Channel established
NM->>Card: Get card info
Card-->>NM: Card state (SN, PIN status, etc.)
NM-->>JS: Return CardInfo
JS-->>User: Display card details
else Error occurred
NM-->>JS: Return error code
JS-->>User: Show error message
end
sequenceDiagram
actor User
participant JS as JavaScript
participant NM as Native Module
participant NFC as NFC Tag
participant Crypto as Secure Channel
User->>JS: Call setMnemonic(mnemonic, pwd)
JS->>NM: Initiate NFC session
NM->>NFC: Begin session & scan
NFC-->>NM: Tag detected
NM->>NM: Load card state
NM->>NFC: Select backup applet
NFC-->>NM: App selected
NM->>Crypto: Open secure channel
Crypto->>NFC: Auth flow (mutual + SCP11)
NFC-->>Crypto: Channel ready
NM->>NFC: Send VERIFY_PIN APDU
NFC-->>NM: PIN result
alt PIN verified
NM->>NFC: Send IMPORT_MNEMONIC APDU (encrypted)
NFC-->>NM: Success response
NM->>NM: Update card state
NM-->>JS: Status = Success
JS-->>User: Mnemonic stored
else PIN failed
NM-->>JS: Status = PasswordWrong
JS-->>User: Invalid PIN
else Card wiped/locked
NM-->>JS: Status = CardLock/Wiped
JS-->>User: Card locked, reset required
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Specific areas requiring extra attention:
Comment |
There was a problem hiding this comment.
Actionable comments posted: 199
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro (Legacy)
Cache: Disabled due to data retention organization setting
Knowledge base: Disabled due to data retention organization setting
⛔ Files ignored due to path filters (15)
native-modules/react-native-lite-card/android/src/main/jniLibs/arm64-v8a/libgpchannelNDK.sois excluded by!**/*.sonative-modules/react-native-lite-card/android/src/main/jniLibs/armeabi-v7a/libgpchannelNDK.sois excluded by!**/*.sonative-modules/react-native-lite-card/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.pngis excluded by!**/*.pngnative-modules/react-native-lite-card/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngis excluded by!**/*.pngnative-modules/react-native-lite-card/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.pngis excluded by!**/*.pngnative-modules/react-native-lite-card/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngis excluded by!**/*.pngnative-modules/react-native-lite-card/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.pngis excluded by!**/*.pngnative-modules/react-native-lite-card/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngis excluded by!**/*.pngnative-modules/react-native-lite-card/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngis excluded by!**/*.pngnative-modules/react-native-lite-card/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngis excluded by!**/*.pngnative-modules/react-native-lite-card/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngis excluded by!**/*.pngnative-modules/react-native-lite-card/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngis excluded by!**/*.pngnative-modules/react-native-lite-card/example/android/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jarnative-modules/react-native-lite-card/example/ios/Podfile.lockis excluded by!**/*.lockyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (138)
.github/workflows/package-publish.yml(1 hunks).gitignore(1 hunks).yarnrc.yml(1 hunks)README.md(1 hunks)lefthook.yml(1 hunks)native-modules/react-native-lite-card/.editorconfig(1 hunks)native-modules/react-native-lite-card/.gitattributes(1 hunks)native-modules/react-native-lite-card/.gitignore(1 hunks)native-modules/react-native-lite-card/.nvmrc(1 hunks)native-modules/react-native-lite-card/.watchmanconfig(1 hunks)native-modules/react-native-lite-card/CODE_OF_CONDUCT.md(1 hunks)native-modules/react-native-lite-card/CONTRIBUTING.md(1 hunks)native-modules/react-native-lite-card/LICENSE(1 hunks)native-modules/react-native-lite-card/README.md(1 hunks)native-modules/react-native-lite-card/ReactNativeLiteCard.podspec(1 hunks)native-modules/react-native-lite-card/android/build.gradle(1 hunks)native-modules/react-native-lite-card/android/gradle.properties(1 hunks)native-modules/react-native-lite-card/android/src/main/AndroidManifest.xml(1 hunks)native-modules/react-native-lite-card/android/src/main/config/command.json(1 hunks)native-modules/react-native-lite-card/android/src/main/cpp/CMakeLists.txt(1 hunks)native-modules/react-native-lite-card/android/src/main/cpp/validation.c(1 hunks)native-modules/react-native-lite-card/android/src/main/cpp/validation.h(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/LoggerManager.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/ReactNativeLiteCardModule.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/ReactNativeLiteCardPackage.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/keys/KeysNativeProvider.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/nfc/Exceptions.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/nfc/NfcUtils.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/nfc/broadcast/NfcStatusChangeBroadcastReceiver.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/nfc/gpchannel/GPChannelNatives.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/NfcConstant.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/OnekeyLiteCard.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/entitys/APDUParam.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/entitys/CardInfo.java(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/entitys/CardResponse.java(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/entitys/CardState.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/entitys/ParsedCertInfo.java(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/entitys/SecureChanelParam.java(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/entitys/SendResponse.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/nfc/CommandGenerator.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/nfc/Connection.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/nfc/GPCAPDUGenerator.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/EventUtils.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/GpsUtil.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/HexUtils.java(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/LogUtil.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/MiUtil.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/NfcPermissionUtils.kt(1 hunks)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/Utils.java(1 hunks)native-modules/react-native-lite-card/babel.config.js(1 hunks)native-modules/react-native-lite-card/eslint.config.mjs(1 hunks)native-modules/react-native-lite-card/example/.bundle/config(1 hunks)native-modules/react-native-lite-card/example/.watchmanconfig(1 hunks)native-modules/react-native-lite-card/example/Gemfile(1 hunks)native-modules/react-native-lite-card/example/README.md(1 hunks)native-modules/react-native-lite-card/example/android/app/build.gradle(1 hunks)native-modules/react-native-lite-card/example/android/app/proguard-rules.pro(1 hunks)native-modules/react-native-lite-card/example/android/app/src/main/AndroidManifest.xml(1 hunks)native-modules/react-native-lite-card/example/android/app/src/main/java/onekeyfe/reactnativelitecard/example/MainActivity.kt(1 hunks)native-modules/react-native-lite-card/example/android/app/src/main/java/onekeyfe/reactnativelitecard/example/MainApplication.kt(1 hunks)native-modules/react-native-lite-card/example/android/app/src/main/res/drawable/rn_edit_text_material.xml(1 hunks)native-modules/react-native-lite-card/example/android/app/src/main/res/values/strings.xml(1 hunks)native-modules/react-native-lite-card/example/android/app/src/main/res/values/styles.xml(1 hunks)native-modules/react-native-lite-card/example/android/build.gradle(1 hunks)native-modules/react-native-lite-card/example/android/gradle.properties(1 hunks)native-modules/react-native-lite-card/example/android/gradle/wrapper/gradle-wrapper.properties(1 hunks)native-modules/react-native-lite-card/example/android/gradlew(1 hunks)native-modules/react-native-lite-card/example/android/gradlew.bat(1 hunks)native-modules/react-native-lite-card/example/android/settings.gradle(1 hunks)native-modules/react-native-lite-card/example/app.json(1 hunks)native-modules/react-native-lite-card/example/babel.config.js(1 hunks)native-modules/react-native-lite-card/example/index.js(1 hunks)native-modules/react-native-lite-card/example/ios/.xcode.env(1 hunks)native-modules/react-native-lite-card/example/ios/Podfile(1 hunks)native-modules/react-native-lite-card/example/ios/ReactNativeLiteCardExample.xcodeproj/project.pbxproj(1 hunks)native-modules/react-native-lite-card/example/ios/ReactNativeLiteCardExample.xcodeproj/xcshareddata/xcschemes/ReactNativeLiteCardExample.xcscheme(1 hunks)native-modules/react-native-lite-card/example/ios/ReactNativeLiteCardExample.xcworkspace/contents.xcworkspacedata(1 hunks)native-modules/react-native-lite-card/example/ios/ReactNativeLiteCardExample/AppDelegate.swift(1 hunks)native-modules/react-native-lite-card/example/ios/ReactNativeLiteCardExample/Images.xcassets/AppIcon.appiconset/Contents.json(1 hunks)native-modules/react-native-lite-card/example/ios/ReactNativeLiteCardExample/Images.xcassets/Contents.json(1 hunks)native-modules/react-native-lite-card/example/ios/ReactNativeLiteCardExample/Info.plist(1 hunks)native-modules/react-native-lite-card/example/ios/ReactNativeLiteCardExample/LaunchScreen.storyboard(1 hunks)native-modules/react-native-lite-card/example/ios/ReactNativeLiteCardExample/PrivacyInfo.xcprivacy(1 hunks)native-modules/react-native-lite-card/example/jest.config.js(1 hunks)native-modules/react-native-lite-card/example/metro.config.js(1 hunks)native-modules/react-native-lite-card/example/package.json(1 hunks)native-modules/react-native-lite-card/example/react-native.config.js(1 hunks)native-modules/react-native-lite-card/example/src/App.tsx(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKLiteCommand/OKLiteCommandModal.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKLiteCommand/OKLiteCommandModal.m(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKLiteCommand/OKLiteCommandTool.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKLiteCommand/OKLiteCommandTool.m(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKNFCBridge.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKNFCBridge.mm(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKNFCLiteDefine.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKNFTLite/OKLiteProtocol.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKNFTLite/OKLiteV1.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKNFTLite/OKLiteV1.m(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKNFTLite/OKLiteV2.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKNFTLite/OKLiteV2.m(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKNFTLite/OKNFCManager.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/OKNFTLite/OKNFCManager.m(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/NFCConfig.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/NFCConfig.m(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/NSData+OKNFCHexData.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/NSData+OKNFCHexData.m(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/NSData+StringToData.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/NSData+StringToData.m(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/NSString+OKAdd.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/NSString+OKAdd.m(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/NSString+OKNFCHexStr.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/NSString+OKNFCHexStr.m(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/OKNFCUtility.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/OKNFCUtility.m(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/OKTools.h(1 hunks)native-modules/react-native-lite-card/ios/Classes/Utils/OKTools.m(1 hunks)native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/Info.plist(1 hunks)native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/ios-arm64/GPChannelSDKCore.framework/Headers/GPChannelSDK.h(1 hunks)native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/ios-arm64/GPChannelSDKCore.framework/Headers/GPChannelSDKCore.h(1 hunks)native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/ios-arm64/GPChannelSDKCore.framework/Modules/module.modulemap(1 hunks)native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/ios-arm64/GPChannelSDKCore.framework/_CodeSignature/CodeResources(1 hunks)native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/ios-arm64_x86_64-simulator/GPChannelSDKCore.framework/Headers/GPChannelSDK.h(1 hunks)native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/ios-arm64_x86_64-simulator/GPChannelSDKCore.framework/Headers/GPChannelSDKCore.h(1 hunks)native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/ios-arm64_x86_64-simulator/GPChannelSDKCore.framework/Modules/module.modulemap(1 hunks)native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/ios-arm64_x86_64-simulator/GPChannelSDKCore.framework/_CodeSignature/CodeResources(1 hunks)native-modules/react-native-lite-card/ios/ReactNativeLiteCard.h(1 hunks)native-modules/react-native-lite-card/ios/ReactNativeLiteCard.mm(1 hunks)native-modules/react-native-lite-card/keys/keys.c(1 hunks)native-modules/react-native-lite-card/keys/keys.h(1 hunks)native-modules/react-native-lite-card/lefthook.yml(1 hunks)native-modules/react-native-lite-card/package.json(1 hunks)native-modules/react-native-lite-card/src/NativeReactNativeLiteCard.ts(1 hunks)native-modules/react-native-lite-card/src/__tests__/index.test.tsx(1 hunks)native-modules/react-native-lite-card/src/index.tsx(1 hunks)native-modules/react-native-lite-card/tsconfig.build.json(1 hunks)native-modules/react-native-lite-card/tsconfig.json(1 hunks)native-modules/react-native-lite-card/turbo.json(1 hunks)package.json(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
native-modules/react-native-lite-card/keys/keys.h (1)
native-modules/react-native-lite-card/keys/keys.c (1)
getInitParams(20-26)
native-modules/react-native-lite-card/keys/keys.c (1)
native-modules/react-native-lite-card/android/src/main/cpp/validation.c (1)
checkSecurityPermission(224-227)
native-modules/react-native-lite-card/example/babel.config.js (2)
native-modules/react-native-lite-card/example/metro.config.js (4)
path(1-1)require(2-2)require(3-3)root(5-5)native-modules/react-native-lite-card/example/react-native.config.js (2)
path(1-1)pkg(2-2)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/nfc/GPCAPDUGenerator.kt (1)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/LogUtil.kt (1)
printLog(7-10)
native-modules/react-native-lite-card/example/index.js (1)
native-modules/react-native-lite-card/example/src/App.tsx (1)
App(4-85)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/LoggerManager.kt (1)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/EventUtils.kt (1)
sendEvent(6-14)
native-modules/react-native-lite-card/src/index.tsx (2)
native-modules/react-native-lite-card/src/NativeReactNativeLiteCard.ts (3)
PromiseResult(22-26)CardInfo(9-14)CallbackResult(16-20)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/MiUtil.kt (1)
value(20-42)
native-modules/react-native-lite-card/ios/Classes/OKNFTLite/OKLiteProtocol.h (1)
native-modules/react-native-lite-card/src/index.tsx (5)
getLiteInfo(30-34)setMnemonic(42-51)reset(65-69)getMnemonicWithPin(53-57)changePin(59-63)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/nfc/Connection.kt (3)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/LogUtil.kt (1)
printLog(7-10)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/nfc/GPCAPDUGenerator.kt (2)
buildGPCAPDU(11-33)combCommand(35-55)native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/nfc/CommandGenerator.kt (1)
send(108-116)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/OnekeyLiteCard.kt (1)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/LogUtil.kt (1)
printLog(7-10)
native-modules/react-native-lite-card/android/src/main/cpp/validation.h (1)
native-modules/react-native-lite-card/android/src/main/cpp/validation.c (3)
getSignatureSha1(153-165)checkValidity(211-222)checkSecurityPermission(224-227)
native-modules/react-native-lite-card/ios/Classes/OKNFTLite/OKNFCManager.h (1)
native-modules/react-native-lite-card/src/index.tsx (5)
getLiteInfo(30-34)reset(65-69)setMnemonic(42-51)getMnemonicWithPin(53-57)changePin(59-63)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/ReactNativeLiteCardModule.kt (1)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/nfc/NfcUtils.kt (1)
init(53-60)
native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/ios-arm64_x86_64-simulator/GPChannelSDKCore.framework/Headers/GPChannelSDK.h (1)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/onekeyLite/entitys/APDUParam.kt (1)
cla(3-17)
🪛 actionlint (1.7.9)
.github/workflows/package-publish.yml
9-9: the runner of "actions/checkout@v2" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
10-10: the runner of "actions/setup-node@v2" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🪛 Clang (14.0.6)
native-modules/react-native-lite-card/ios/Classes/Utils/NSData+OKNFCHexData.h
[error] 7-7: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
native-modules/react-native-lite-card/keys/keys.h
[error] 1-1: 'stdlib.h' file not found
(clang-diagnostic-error)
native-modules/react-native-lite-card/ios/Classes/Utils/NFCConfig.h
[error] 8-8: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
[warning] 15-15: variable 'NSString' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/ios-arm64/GPChannelSDKCore.framework/Headers/GPChannelSDKCore.h
[error] 9-9: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
[warning] 12-12: variable 'APDUSDKCoreVersionNumber' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
native-modules/react-native-lite-card/ios/Classes/Utils/NSString+OKNFCHexStr.h
[error] 7-7: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
[warning] 14-14: variable 'NSString' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/ios-arm64_x86_64-simulator/GPChannelSDKCore.framework/Headers/GPChannelSDKCore.h
[error] 9-9: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
[warning] 12-12: variable 'APDUSDKCoreVersionNumber' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
native-modules/react-native-lite-card/keys/keys.c
[error] 1-1: 'string.h' file not found
(clang-diagnostic-error)
native-modules/react-native-lite-card/ios/Classes/OKNFTLite/OKLiteV2.h
[error] 8-8: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
native-modules/react-native-lite-card/ios/ReactNativeLiteCard.h
[error] 1-1: 'ReactNativeLiteCardSpec/ReactNativeLiteCardSpec.h' file not found
(clang-diagnostic-error)
native-modules/react-native-lite-card/ios/Classes/OKLiteCommand/OKLiteCommandTool.h
[error] 8-8: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
native-modules/react-native-lite-card/ios/Classes/Utils/OKTools.h
[error] 8-8: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
native-modules/react-native-lite-card/ios/Classes/OKLiteCommand/OKLiteCommandModal.h
[error] 8-8: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
[warning] 25-25: variable 'instancetype' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 27-27: variable 'NFCISO7816APDU' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 29-29: variable 'NFCISO7816APDU' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 31-31: variable 'NFCISO7816APDU' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 33-33: variable 'NFCISO7816APDU' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 35-35: variable 'NFCISO7816APDU' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
native-modules/react-native-lite-card/ios/Classes/OKNFCBridge.h
[error] 9-9: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
[warning] 17-17: variable 'NSString' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 18-18: variable 'BOOL' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 19-19: variable 'BOOL' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 20-20: variable 'NSString' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 21-21: variable 'BOOL' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 22-22: variable 'BOOL' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 23-23: variable 'NFCISO7816APDU' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
native-modules/react-native-lite-card/ios/Classes/OKNFTLite/OKLiteV1.h
[error] 8-8: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
[warning] 26-26: variable 'instancetype' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 28-28: variable 'BOOL' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 30-30: variable 'BOOL' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 32-32: variable 'BOOL' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 34-34: variable 'BOOL' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 36-36: variable 'BOOL' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 38-38: variable 'id' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 38-38: variable name 'id' is too short, expected at least 3 characters
(readability-identifier-length)
native-modules/react-native-lite-card/ios/Classes/Utils/OKNFCUtility.h
[error] 8-8: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
native-modules/react-native-lite-card/ios/Classes/OKNFTLite/OKLiteProtocol.h
[error] 8-8: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
[warning] 18-18: variable 'BOOL' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 27-27: variable 'BOOL' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 33-33: variable 'OKNFCLitePINVerifyResult' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
native-modules/react-native-lite-card/android/src/main/cpp/validation.h
[error] 4-4: 'stdio.h' file not found
(clang-diagnostic-error)
native-modules/react-native-lite-card/ios/Classes/Utils/NSData+StringToData.h
[error] 8-8: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
native-modules/react-native-lite-card/ios/Classes/OKNFTLite/OKNFCManager.h
[error] 8-8: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
[warning] 19-19: variable 'OKNFCLiteSessionType' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/ios-arm64/GPChannelSDKCore.framework/Headers/GPChannelSDK.h
[error] 201-201: expected ')'
(clang-diagnostic-error)
[note] 201-201: to match this '('
(clang)
[warning] 250-250: parameter name 'p1' is too short, expected at least 3 characters
(readability-identifier-length)
[warning] 250-250: parameter name 'p2' is too short, expected at least 3 characters
(readability-identifier-length)
[warning] 281-281: parameter name 'p1' is too short, expected at least 3 characters
(readability-identifier-length)
[warning] 281-281: parameter name 'p2' is too short, expected at least 3 characters
(readability-identifier-length)
[warning] 309-309: parameter name 'sn' is too short, expected at least 3 characters
(readability-identifier-length)
native-modules/react-native-lite-card/ios/Classes/Utils/NSString+OKAdd.h
[error] 2-2: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
[warning] 9-9: variable 'BOOL' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
[warning] 10-10: variable 'NSString' is non-const and globally accessible, consider making it const
(cppcoreguidelines-avoid-non-const-global-variables)
native-modules/react-native-lite-card/android/src/main/cpp/validation.c
[warning] 16-16: variable 'version' is not initialized
(cppcoreguidelines-init-variables)
[warning] 18-18: 8 is a magic number; consider replacing it with a named constant
(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
[warning] 27-27: parameter 'env' is unused
(misc-unused-parameters)
[warning] 27-27: parameter 'context_object' is unused
(misc-unused-parameters)
[warning] 84-84: parameter 'env' is unused
(misc-unused-parameters)
[warning] 84-84: parameter 'context_object' is unused
(misc-unused-parameters)
[warning] 153-153: parameter 'env' is unused
(misc-unused-parameters)
[warning] 153-153: parameter 'context_object' is unused
(misc-unused-parameters)
[warning] 158-158: if with identical then and else branches
(bugprone-branch-clone)
[note] 160-160: else branch starts here
(clang)
[warning] 158-158: 28 is a magic number; consider replacing it with a named constant
(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
[warning] 167-167: parameter 'env' is unused
(misc-unused-parameters)
[warning] 167-167: parameter 'message' is unused
(misc-unused-parameters)
[warning] 200-200: variable 'sha1' is not initialized
(cppcoreguidelines-init-variables)
[warning] 201-201: variable 'hex_sha' is not initialized
(cppcoreguidelines-init-variables)
[warning] 224-224: parameter 'env' is unused
(misc-unused-parameters)
[warning] 224-224: parameter 'contextObject' is unused
(misc-unused-parameters)
[warning] 224-224: parameter 'size' is unused
(misc-unused-parameters)
[warning] 225-225: variable 'sha1' is not initialized
(cppcoreguidelines-init-variables)
native-modules/react-native-lite-card/ios/GPChannelSDKCore.xcframework/ios-arm64_x86_64-simulator/GPChannelSDKCore.framework/Headers/GPChannelSDK.h
[error] 201-201: expected ')'
(clang-diagnostic-error)
[note] 201-201: to match this '('
(clang)
[warning] 250-250: parameter name 'p1' is too short, expected at least 3 characters
(readability-identifier-length)
[warning] 250-250: parameter name 'p2' is too short, expected at least 3 characters
(readability-identifier-length)
[warning] 281-281: parameter name 'p1' is too short, expected at least 3 characters
(readability-identifier-length)
[warning] 281-281: parameter name 'p2' is too short, expected at least 3 characters
(readability-identifier-length)
[warning] 309-309: parameter name 'sn' is too short, expected at least 3 characters
(readability-identifier-length)
native-modules/react-native-lite-card/ios/Classes/OKNFCLiteDefine.h
[error] 8-8: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
[warning] 12-12: 2 adjacent parameters of 'NS_ENUM' of similar type ('int') are easily swapped by mistake
(bugprone-easily-swappable-parameters)
[note] 12-12: the first parameter in the range is 'NSInteger'
(clang)
[note] 12-12: the last parameter in the range is 'OKLiteCommand'
(clang)
[warning] 30-30: function 'NS_ENUM' has a definition with different parameter names
(readability-inconsistent-declaration-parameter-name)
[note] 12-12: the definition seen here
(clang)
[note] 30-30: differing parameters are named here: ('OKNFCLiteSessionType'), in definition: ('OKLiteCommand')
(clang)
[warning] 30-30: 2 adjacent parameters of 'NS_ENUM' of similar type ('int') are easily swapped by mistake
(bugprone-easily-swappable-parameters)
[note] 30-30: the first parameter in the range is 'NSInteger'
(clang)
[note] 30-30: the last parameter in the range is 'OKNFCLiteSessionType'
(clang)
[warning] 41-41: 2 adjacent parameters of 'NS_ENUM' of similar type ('int') are easily swapped by mistake
(bugprone-easily-swappable-parameters)
[note] 41-41: the first parameter in the range is 'NSInteger'
(clang)
[note] 41-41: the last parameter in the range is 'OKNFCLiteStatus'
(clang)
[warning] 48-48: 2 adjacent parameters of 'NS_ENUM' of similar type ('int') are easily swapped by mistake
(bugprone-easily-swappable-parameters)
[note] 48-48: the first parameter in the range is 'NSInteger'
(clang)
[note] 48-48: the last parameter in the range is 'OKNFCLiteSetMncStatus'
(clang)
[warning] 57-57: 2 adjacent parameters of 'NS_ENUM' of similar type ('int') are easily swapped by mistake
(bugprone-easily-swappable-parameters)
[note] 57-57: the first parameter in the range is 'NSInteger'
(clang)
[note] 57-57: the last parameter in the range is 'OKNFCLiteGetMncStatus'
(clang)
[warning] 66-66: 2 adjacent parameters of 'NS_ENUM' of similar type ('int') are easily swapped by mistake
(bugprone-easily-swappable-parameters)
[note] 66-66: the first parameter in the range is 'NSInteger'
(clang)
[note] 66-66: the last parameter in the range is 'OKNFCLiteChangePinStatus'
(clang)
[warning] 86-86: 2 adjacent parameters of 'NS_ENUM' of similar type ('int') are easily swapped by mistake
(bugprone-easily-swappable-parameters)
[note] 86-86: the first parameter in the range is 'NSInteger'
(clang)
[note] 86-86: the last parameter in the range is 'OKNFCLiteApp'
(clang)
[warning] 92-92: 2 adjacent parameters of 'NS_ENUM' of similar type ('int') are easily swapped by mistake
(bugprone-easily-swappable-parameters)
[note] 92-92: the first parameter in the range is 'NSInteger'
(clang)
[note] 92-92: the last parameter in the range is 'OKNFCLitePINVerifyResult'
(clang)
[warning] 98-98: 2 adjacent parameters of 'NS_ENUM' of similar type ('int') are easily swapped by mistake
(bugprone-easily-swappable-parameters)
[note] 98-98: the first parameter in the range is 'NSInteger'
(clang)
[note] 98-98: the last parameter in the range is 'OKNFCLiteChangePinResult'
(clang)
[warning] 104-104: 2 adjacent parameters of 'NS_ENUM' of similar type ('int') are easily swapped by mistake
(bugprone-easily-swappable-parameters)
[note] 104-104: the first parameter in the range is 'NSInteger'
(clang)
[note] 104-104: the last parameter in the range is 'OKNFCLiteVersion'
(clang)
🪛 Cppcheck (2.18.0)
native-modules/react-native-lite-card/keys/keys.c
[information] 1-1: Include file
(missingIncludeSystem)
[information] 1-1: Include file
(missingIncludeSystem)
[information] 6-6: Include file
(missingIncludeSystem)
[information] 7-7: Include file
(missingIncludeSystem)
[information] 4-4: Include file
(missingIncludeSystem)
[information] 5-5: Include file
(missingIncludeSystem)
[information] 6-6: Include file
(missingIncludeSystem)
[information] 7-7: Include file
(missingIncludeSystem)
[information] 8-8: Include file
(missingIncludeSystem)
[error] 43-43: Deallocation of a pointer pointing to a string literal ("{"scpID"
(autovarInvalidDeallocation)
[style] 31-31: The function 'Java_so_onekey_app_wallet_lite_keys_KeysNativeProvider_getLiteSecureChannelInitParams' is never used.
(unusedFunction)
[style] 20-20: The function 'getInitParams' should have static linkage since it is not used outside of its translation unit.
(staticFunction)
native-modules/react-native-lite-card/android/src/main/cpp/validation.c
[information] 4-4: Include file
(missingIncludeSystem)
[information] 5-5: Include file
(missingIncludeSystem)
[information] 6-6: Include file
(missingIncludeSystem)
[information] 7-7: Include file
(missingIncludeSystem)
[information] 8-8: Include file
(missingIncludeSystem)
[information] 2-2: Include file
(missingIncludeSystem)
[warning] 203-203: If memory allocation fails, then there is a possible null pointer dereference
(nullPointerOutOfMemory)
[warning] 204-204: If memory allocation fails, then there is a possible null pointer dereference
(nullPointerOutOfMemory)
[warning] 206-206: If memory allocation fails, then there is a possible null pointer dereference
(nullPointerOutOfMemory)
[style] 224-224: The function 'checkSecurityPermission' is never used.
(unusedFunction)
[style] 15-15: The function 'currentAndroidOSVersion' should have static linkage since it is not used outside of its translation unit.
(staticFunction)
[style] 27-27: The function 'getSignatureSha1By1' should have static linkage since it is not used outside of its translation unit.
(staticFunction)
[style] 84-84: The function 'getSignatureSha1By28' should have static linkage since it is not used outside of its translation unit.
(staticFunction)
[style] 153-153: The function 'getSignatureSha1' should have static linkage since it is not used outside of its translation unit.
(staticFunction)
[style] 167-167: The function 'signatureSha1' should have static linkage since it is not used outside of its translation unit.
(staticFunction)
[style] 211-211: The function 'checkValidity' should have static linkage since it is not used outside of its translation unit.
(staticFunction)
🪛 detekt (1.23.8)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/utils/MiUtil.kt
[warning] 76-76: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.
(detekt.exceptions.TooGenericExceptionCaught)
[warning] 88-88: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.
(detekt.exceptions.TooGenericExceptionCaught)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/ReactNativeLiteCardModule.kt
[warning] 101-101: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.
(detekt.exceptions.TooGenericExceptionCaught)
[warning] 393-393: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.
(detekt.exceptions.TooGenericExceptionCaught)
[warning] 407-407: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.
(detekt.exceptions.TooGenericExceptionCaught)
native-modules/react-native-lite-card/android/src/main/java/com/onekeyfe/reactnativelitecard/nfc/NfcUtils.kt
[warning] 134-134: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.
(detekt.exceptions.TooGenericExceptionCaught)
🪛 LanguageTool
native-modules/react-native-lite-card/CONTRIBUTING.md
[uncategorized] ~124-~124: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...free series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead....
(EN_COMPOUND_ADJECTIVE_INTERNAL)
native-modules/react-native-lite-card/CODE_OF_CONDUCT.md
[style] ~33-~33: Try using a synonym here to strengthen your wording.
Context: ...ind * Trolling, insulting or derogatory comments, and personal or political attacks * Pu...
(COMMENT_REMARK)
🪛 markdownlint-cli2 (0.18.1)
native-modules/react-native-lite-card/example/README.md
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
README.md
12-12: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
15-15: Files should end with a single newline character
(MD047, single-trailing-newline)
🪛 RuboCop (1.81.7)
native-modules/react-native-lite-card/example/Gemfile
[convention] 8-8: Gems should be sorted in an alphabetical order within their section of the Gemfile. Gem activesupport should appear before cocoapods.
(Bundler/OrderedGems)
[convention] 10-10: Gems should be sorted in an alphabetical order within their section of the Gemfile. Gem concurrent-ruby should appear before xcodeproj.
(Bundler/OrderedGems)
[convention] 15-15: Gems should be sorted in an alphabetical order within their section of the Gemfile. Gem benchmark should appear before logger.
(Bundler/OrderedGems)
native-modules/react-native-lite-card/example/ios/Podfile
[convention] 8-8: Put a comma after the last item of a multiline array.
(Style/TrailingCommaInArrayLiteral)
[convention] 32-32: Avoid comma after the last parameter of a method call.
(Style/TrailingCommaInArguments)
native-modules/react-native-lite-card/ReactNativeLiteCard.podspec
[convention] 14-14: Prefer to_s over string interpolation.
(Style/RedundantInterpolation)
🪛 YAMLlint (1.37.1)
.yarnrc.yml
[error] 6-6: too many blank lines (1 > 0)
(empty-lines)
.github/workflows/package-publish.yml
[warning] 3-3: truthy value should be one of [false, true]
(truthy)
[error] 19-19: too many spaces after colon
(colons)
#1 Align Android `metadataRequiresCommonBundle` with iOS OR semantics. Previously the helper returned the explicit `requiresCommonBundle` value when present (so `requiresCommonBundle=false` would suppress the `bundleFormat=three-bundle` signal), which iOS doesn't do. The helper is also used by `validateBundleDescriptor` and `validateEntryBundlesSha256` on Android, so the divergence let Android accept three-bundle manifests that iOS rejects (missing `common.bundle` allowed through, common.bundle sha256 not enforced on startup hot path). Switch the helper itself to OR semantics; `currentBundleRequiresPerSegmentHash` now just calls it. #2 `validateWebEmbedRecursive` now fails closed on `listFiles() == null`. Java's `File.listFiles()` returns null on I/O error or unreadable dir. Treating that as "nothing to verify" silently allowed a tampered web-embed asset whose containing directory was unlistable. Log the path and return false instead. #3 iOS background-thread refuses IPA fallback when OTA main is active. If the OTA main bundle is loaded but the OTA common/background lookup fails (typical cause: package skew where bundle-update is older than split-bundle-loader/background-thread and doesn't expose the new `currentBundleCommonJSBundle` selector), falling back to the IPA built-in common.jsbundle / background.bundle would moduleId-mismatch the OTA main and crash on first require() — the very crash this patch was added to prevent. Detect "OTA main is active" via the existing reflective lookup and return nil from bundleURL / resolveBackgroundEntryBundlePath in that case. The background runtime aborts loudly instead of crashing silently.
…n abort Two follow-ups from the fourth-pass review of 22036ec. #1 Rename `_bundleURLUsedOtaCommon` -> `_otaActiveAtBundleResolve` and set it in BOTH OTA branches of `bundleURL`: when OTA common resolves AND when OTA main is active but OTA common is unresolvable (the refused-fallback path that returns nil). The flag now captures the invariant "this delegate is locked to OTA territory" rather than the narrower "OTA common was loaded". Under normal RCTHost lifecycle hostDidStart won't fire after bundleURL returns nil, but the broader semantic closes the theoretical hole where a future lifecycle change or host retry could reach `resolveBackgroundEntryBundlePath` with the old flag still NO and silently fall back to IPA built-in background.bundle. #2 Clear `_rctInstance = nil` before the early return in `hostDidStart`'s fatal branch. `_rctInstance` was set on lines 287-288 before the abort, and `registerSegmentWithId` only gates on `_rctInstance != nil` — so any downstream segment registration after the abort would happily call into a runtime where SharedStore / SharedRPC / error handler / `__setupBackgroundRPCHandler` were all skipped. Nilling the ivar makes the half-initialized state visible to those guards.
Two real issues caught by Devin review on PR #49. #1 Android `validateEntryBundlesSha256` was the only sha256 comparison in the file using `String.equals(..., ignoreCase = true)` instead of the constant-time `secureCompare` helper used everywhere else (validateFilesRecursive, validateWebEmbedRecursive, iOS counterpart). Switch to `secureCompare(expected.lowercase(), actual.lowercase())` — preserves the case-insensitive semantic against uppercase-hex manifests while restoring constant-time comparison for the timing-attack guarantee. #2 iOS `bundleFormat == "three-bundle"` was case-sensitive at three sites (currentBundleRequiresPerSegmentHash, validateBundlePairCompatibility, validateEntryBundlesSha256), while Android's `metadataRequiresCommonBundle` already calls `.lowercase()` before comparing. A manifest with `bundleFormat: "Three-Bundle"` would activate three-bundle gating on Android (common bundle required, per-segment hash enforced) but not on iOS, leaving iOS silently in two-bundle mode. Lowercase the comparand on all three iOS sites to match Android's case-insensitive semantic.
* feat(background-thread): prefer OTA-installed common/background bundles The background runtime previously loaded the IPA-bundled common.jsbundle and background.bundle even when an OTA update had installed newer copies to disk, which could crash on moduleId mismatch with the OTA-loaded main bundle. Now reflectively query BundleUpdateStore for the OTA paths (mirroring the SplitBundleLoader cross-framework reflection pattern, since the Swift umbrella header pulls in C++/Nitro headers that break the Clang dependency scanner) and fall back to the built-in resources when no OTA is active. Migrated from app-monorepo patches/@onekeyfe+react-native-background-thread+3.0.20.patch. * feat(bundle-update): three-bundle metadata, validated-bundle cache, fast-path entry sha256 Three changes that ship together because they share the same metadata parsing path on both platforms: 1. Three-bundle metadata: parse and reserve requiresCommonBundle and bundleFormat keys. When set, validateBundleDescriptor requires a common.bundle alongside main.jsbundle.hbc, so OTA installs of the union/split-thread layout fail closed instead of silently shipping a broken main bundle that references moduleIds living in common. The Android metadata parser also now accepts boolean/numeric scalars (skipping nested objects) so manifests can carry future descriptors without breaking sha verification. 2. validatedCurrentBundleInfo cache: every bundleURL / common / main / background path getter on startup re-ran the full signature + sha256 pipeline. Memoize by currentBundleVersion, invalidate on every mutation (clearBundle, resetToBuiltInBundle, clearAllJSBundleData, setCurrentUpdateBundleData) and on the no-version path. 3. Fast-path entry sha256: replace the full-tree validateAllFilesInDir sweep on the startup hot path with validateEntryBundlesSha256, which only hashes main + common + background (gated by metadata flags). Full-tree validation already runs at install time, sandboxed app data plus signed metadata bind every file's expected hash, and per-segment integrity is now enforced at loadSegment time by SplitBundleLoader. Also exposes BundleUpdateStore.currentBundleCommonJSBundle() on iOS so the background runtime can look up the OTA common bundle path. Migrated from app-monorepo patches/@onekeyfe+react-native-bundle-update+3.0.20.patch. * feat(split-bundle-loader): verify segment SHA-256 at load time Pairs with the bundle-update fast-path startup change: BundleUpdate no longer re-hashes the whole bundle directory on every launch, so segment integrity is now enforced lazily here at resolveSegmentPath / loadSegment time instead of trusting install-time validation. Empty expected hash is treated as skip for back-compat with manifests that omit per-segment hashes. Hashes are streamed in 64KB chunks so a large segment doesn't pull the whole file into memory just to verify it. Migrated from app-monorepo patches/@onekeyfe+react-native-split-bundle-loader+3.0.20.patch. * fix(bundle-update,split-bundle-loader): address codex audit findings Three fixes from the audit of the patch migration on this branch. #1 Lazy web-embed sha256 verification + cache. The startup hot path (validateEntryBundlesSha256) only verifies the JS entry bundles, so a tampered web-embed asset (HTML/JS/CSS) would slip through and execute inside the WebView. WebKit/Chromium SRI isn't enforced because the build pipeline doesn't yet inject integrity attributes; until that lands, native must verify. Walk web-embed/** at getWebEmbedPath time, gate against metadata sha256 entries (rejecting files-on-disk-not-in-metadata and metadata-entries-missing-on-disk), and cache the verified bundleVersion so the cost is paid once per (re)install. Cache is invalidated alongside cachedValidatedBundleInfo on every bundle mutation. Both platforms. #2 Per-segment sha256 fail-closed for three-bundle format. SplitBundleLoader's verifySha256 previously treated empty expected hash as a back-compat skip — combined with the startup fast path that only checks entry bundles, a tampered segment with an empty manifest hash would pass both gates. Add a new public API, BundleUpdateStore.currentBundleRequiresPerSegmentHash(), that returns true when the current bundle's metadata declares the three-bundle / split-thread layout (or sets requiresCommonBundle). SplitBundleLoader queries this reflectively (matching the existing OTA-path lookup pattern) and treats empty expected as a hard fail when true. Older formats keep the back-compat skip. #3 installBundle invalidates the validated-bundle cache. Symmetry with the other mutators (clearBundle / resetToBuiltInBundle / clearAllJSBundleData / setCurrentUpdateBundleData). Currently safe in practice because installBundle always writes a new appVersion-bundleVersion folder, so the cache key changes. But signature/native-version writes inside installBundle could otherwise leave callers reading stale validated info if anyone ever adds a same-version reinstall path. One line at the top of each platform's installBundle. * fix(bundle-update): align Android per-segment-hash predicate; close install race Two follow-up fixes from the second-pass review of 88f641a. #1 Android `currentBundleRequiresPerSegmentHash` was reusing `metadataRequiresCommonBundle`, which has explicit-false-overrides-bundleFormat semantics: an explicit `requiresCommonBundle=false` would suppress the `bundleFormat=three-bundle` signal and let SplitBundleLoader allow empty per-segment hashes. iOS's same-named API uses OR semantics (true if either opts in). Inline the OR semantics in Android's API to match iOS, leaving `metadataRequiresCommonBundle` alone so existing `validateBundleDescriptor` behavior is preserved. #2 `installBundle` invalidated the validated-bundle cache *before* the async install body. A reader calling `validatedCurrentBundleInfo()` in the window between the synchronous invalidate and the async `currentBundleVersion` write could re-cache pre-install state, leaving it stale after the install commits. Add a second invalidate at the end of the async body. (Different-version installs are already safe via the version-keyed cache; this closes the same-version reinstall window and any reader race in general.) Both platforms. * chore(split-bundle-loader,background-thread): peer-depend on bundle-update >=3.0.21 Both packages reflectively call BundleUpdateStore APIs that are only available starting in this branch's release: - split-bundle-loader: `currentBundleRequiresPerSegmentHash` (decides whether empty per-segment sha256 is fail-closed or back-compat skip). - background-thread: `currentBundleCommonJSBundle` (OTA-installed common bundle path; the equivalent main/background paths existed in prior versions). Without this peer dep, an app that bumps either package without bumping bundle-update will silently fall through to the back-compat code paths (reflection returns nil, fail-open per-segment, IPA built-in common bundle). Surface that as an install-time peer-dependency warning so the mismatch is caught before runtime. Range is `>=3.0.21` because 3.0.20 was the most recent published release before these APIs were added. * fix: address third-pass codex audit findings #1 Align Android `metadataRequiresCommonBundle` with iOS OR semantics. Previously the helper returned the explicit `requiresCommonBundle` value when present (so `requiresCommonBundle=false` would suppress the `bundleFormat=three-bundle` signal), which iOS doesn't do. The helper is also used by `validateBundleDescriptor` and `validateEntryBundlesSha256` on Android, so the divergence let Android accept three-bundle manifests that iOS rejects (missing `common.bundle` allowed through, common.bundle sha256 not enforced on startup hot path). Switch the helper itself to OR semantics; `currentBundleRequiresPerSegmentHash` now just calls it. #2 `validateWebEmbedRecursive` now fails closed on `listFiles() == null`. Java's `File.listFiles()` returns null on I/O error or unreadable dir. Treating that as "nothing to verify" silently allowed a tampered web-embed asset whose containing directory was unlistable. Log the path and return false instead. #3 iOS background-thread refuses IPA fallback when OTA main is active. If the OTA main bundle is loaded but the OTA common/background lookup fails (typical cause: package skew where bundle-update is older than split-bundle-loader/background-thread and doesn't expose the new `currentBundleCommonJSBundle` selector), falling back to the IPA built-in common.jsbundle / background.bundle would moduleId-mismatch the OTA main and crash on first require() — the very crash this patch was added to prevent. Detect "OTA main is active" via the existing reflective lookup and return nil from bundleURL / resolveBackgroundEntryBundlePath in that case. The background runtime aborts loudly instead of crashing silently. * fix(background-thread): abort hostDidStart when OTA common loaded but OTA background missing The previous fix had `resolveBackgroundEntryBundlePath` return nil when OTA main was active and OTA background couldn't be resolved, but `hostDidStart` treated nil identically to the legitimate "no background bundle" case (warn and continue). It would still install SharedStore / SharedRPC and call `__setupBackgroundRPCHandler` against a runtime with no entry bundle — RPC would be silently broken instead of loudly fatal. Track whether `bundleURL` actually loaded an OTA common bundle on a new instance ivar `_bundleURLUsedOtaCommon`, reset on every `bundleURL` call. `resolveBackgroundEntryBundlePath` consults the same flag (replacing the duplicated `isOtaMainBundleActive()` reflective check) and `hostDidStart` returns early when bgBundlePath is nil AND the flag is set, distinguishing the fatal case from the legitimate "this bundle was never split" case. * fix(background-thread): broaden OTA-active flag; clear _rctInstance on abort Two follow-ups from the fourth-pass review of 22036ec. #1 Rename `_bundleURLUsedOtaCommon` -> `_otaActiveAtBundleResolve` and set it in BOTH OTA branches of `bundleURL`: when OTA common resolves AND when OTA main is active but OTA common is unresolvable (the refused-fallback path that returns nil). The flag now captures the invariant "this delegate is locked to OTA territory" rather than the narrower "OTA common was loaded". Under normal RCTHost lifecycle hostDidStart won't fire after bundleURL returns nil, but the broader semantic closes the theoretical hole where a future lifecycle change or host retry could reach `resolveBackgroundEntryBundlePath` with the old flag still NO and silently fall back to IPA built-in background.bundle. #2 Clear `_rctInstance = nil` before the early return in `hostDidStart`'s fatal branch. `_rctInstance` was set on lines 287-288 before the abort, and `registerSegmentWithId` only gates on `_rctInstance != nil` — so any downstream segment registration after the abort would happily call into a runtime where SharedStore / SharedRPC / error handler / `__setupBackgroundRPCHandler` were all skipped. Nilling the ivar makes the half-initialized state visible to those guards. * fix(bundle-update): address Devin PR review findings Two real issues caught by Devin review on PR #49. #1 Android `validateEntryBundlesSha256` was the only sha256 comparison in the file using `String.equals(..., ignoreCase = true)` instead of the constant-time `secureCompare` helper used everywhere else (validateFilesRecursive, validateWebEmbedRecursive, iOS counterpart). Switch to `secureCompare(expected.lowercase(), actual.lowercase())` — preserves the case-insensitive semantic against uppercase-hex manifests while restoring constant-time comparison for the timing-attack guarantee. #2 iOS `bundleFormat == "three-bundle"` was case-sensitive at three sites (currentBundleRequiresPerSegmentHash, validateBundlePairCompatibility, validateEntryBundlesSha256), while Android's `metadataRequiresCommonBundle` already calls `.lowercase()` before comparing. A manifest with `bundleFormat: "Three-Bundle"` would activate three-bundle gating on Android (common bundle required, per-segment hash enforced) but not on iOS, leaving iOS silently in two-bundle mode. Lowercase the comparand on all three iOS sites to match Android's case-insensitive semantic. * refactor(bundle-update): extract "three-bundle" literal into a named constant Add `bundleFormatThreeBundle` (Swift) / `BUNDLE_FORMAT_THREE_BUNDLE` (Kotlin) alongside the existing metadata-key constants and replace the four call sites that previously inlined the string. Keeps the iOS and Android sides in lockstep and prevents the prior class of bug where one platform got updated to a new format token (or a typo) while the other silently kept comparing against the old literal. * 3.0.21 * docs(background-thread,split-bundle-loader): correct jsbundle→bundle in comments Comment references were out of sync with the actual resource name used in code (common.bundle). No runtime change. * 3.0.22 * docs(changelog): document 3.0.21 and 3.0.22 releases - 3.0.21: three-bundle OTA metadata + validated-bundle cache, segment SHA-256 verification at load time, background-thread prefers OTA-installed common/background bundles, lazy web-embed verification, and a cluster of audit-driven fixes (Android/iOS parity, constant-time hash compare, install-race cache invalidation, OTA-active abort path). - 3.0.22: comment-only correction (jsbundle → bundle) and version bump.
Reviewer flagged a mix of integration-contract gaps, an Android error- propagation bug, and a couple of C++ housekeeping items. Fixes: iOS — host-integration contract (#1, high) - Document the post-reload contract explicitly on `installSharedBridgeInMainRuntime:` and `restartWithMode:` headers. The host AppDelegate's `hostDidStart:` must re-invoke both the SharedBridge install (every reload) and `startBackgroundRunner` (mode='all' only). - `restartWithMode:` now arms a one-shot `RCTJavaScriptDidLoadNotification` observer that fires ~1.5s after the new main bridge loads: * mode='all': self-respawns the bg runtime if the host AppDelegate didn't (idempotent via the existing `isStarted` guard, so a correctly- wired host pays nothing). * Both modes: logs `[BTLogger error:]` if the host failed to re-call `installSharedBridgeInMainRuntime:`. Tracked via a new atomic flag `mainSharedBridgeInstalled` flipped in the install lambda. Surfaces integration omissions in production logs instead of silent RPC drop. JS spec — cross-platform asymmetry (#2, high) - `NativeBackgroundThread.ts` JSDoc now spells out: iOS preserves the process across `mode='all'` (soft reload); Android kills it via `Runtime.exit(0)`. Callers depending on native process-level state (timers, singletons, in-memory caches, foreground services) must not assume cross-platform survival. Android — false-success promise resolution (#3, medium) - `triggerProcessRestart` returns `Boolean` so failure (intent not resolvable, `startActivity` `SecurityException`) propagates instead of being swallowed; caller rejects the Promise with `BG_RESTART_ERROR`. - `mode='ui'` soft-reload moves `promise.resolve(null)` inside the `Handler.post` success branch — a synchronous `ReactHost.reload()` throw now routes through the same fallback-then-reject path instead of reporting false success because resolve already happened. SharedRPC — clean up dead listener entries (#6, low) - `invalidate(runtimeId)` now erases the listener entry from `listeners_` instead of leaving it with `alive=false`. Already-dispatched executor lambdas hold their own `shared_ptr<alive>` snapshot so erasing is race-safe; it only prevents NEW `notifyOtherRuntime()` snapshots from picking up the dead entry. Stops a permanently-dead listener from hanging around when a `mode='all'` restart never gets its post-reload re-install. - `install()`'s defensive dedup loop is now documented as the legacy / no-prior-invalidate fallback path (#7, low). Reviewer #4 (no tests) and #2's Android-side TODO (soft-reload both runtimes via `ReactHost.reload`) remain follow-ups — flagged in PR description, not in scope here.
Round-1 self-heal observer hinged on RCTJavaScriptDidLoadNotification, which reviewer flagged as unreliable in bridgeless / NewArch. The fix also surfaced two secondary hazards (observer leak when notification never fires; cross-generation flag misreporting on concurrent restarts). All three close together by dropping the notification entirely. iOS — replace notification observer with plain dispatch_after (#1, #2, high) - The two signals the post-reload check needs (mainSharedBridgeInstalled, isStarted) are this module's own state. No external timing anchor is required; the notification was only ever an attempt at one. A direct dispatch_after on the main queue at kRestartHealthCheckDelaySeconds (3s, constant — empirical 6× margin over baseline hostDidStart chain on low-end devices) eliminates the bridgeless-fire-or-not gamble AND the observer-leak/cross-generation misfire that the observer pattern introduced. iOS — generation counter for concurrent restart() safety (#3, medium) - Adds `restartGeneration` (nonatomic; main-thread-only by construction). Each restartWithMode: bumps it and the health-check captures its own `myGen`; on fire, it bails if `restartGeneration != myGen` so a later restart's flag reset can't make an earlier restart's check misreport. iOS — self-respawn loses custom OTA entry URL (#6, low) - The bg self-respawn path falls through `startBackgroundRunner` → default URL (`background.bundle` / DEBUG URL), losing any custom entry URL the host set via startBackgroundRunnerWithEntryURL: (typically an OTA-resolved bundle path) because reactNativeFactoryDelegate is released for mode='all'. Added an explicit `[BTLogger warn:]` so this trade-off is visible in logs when the host's AppDelegate wiring is broken AND OTA paths matter. Android — soften over-confident resolve-delivery comment (#4, medium) - Round-1 comment claimed "the JS thread is still live and can deliver the success callback before being destroyed". Closer to truth: the CallInvoker is bound to the outgoing ReactInstance which reload invalidates very quickly, and `await restart(...)` callers' continu- ations are typically superseded by the reload. Position-of-resolve only matters for the synchronous-throw path. Updated the comment. Not changed: - 1.5s → 3s on the health-check delay is the right call but isn't itself a magic-number fix — `kRestartHealthCheckDelaySeconds` is a named constant with rationale comment. Polling-until-flag-flips was considered and skipped: extra complexity for a one-line log output. Still pending end-to-end verification on the OneKey host app: - mode='ui' / mode='all' on iOS bridgeless: confirm health-check logs fire only when integration is actually broken (deliberate AppDelegate.hostDidStart: omission test). - Android: confirm BG_RESTART_ERROR Promise rejection on synthetic startActivity failure (e.g. invalid intent).
Round-2 left three open concerns reviewer flagged as merge-blockers for the OTA path plus a memory-ordering nit. Fixes: iOS — cache lastEntryURL for OTA-safe self-respawn (#1, medium) - Round-2's self-respawn fell through to `startBackgroundRunner` → default `background.bundle`. On OTA-equipped hosts that's a moduleId- mismatch crash waiting to happen: main runs the OTA-updated bundle, bg loads the IPA's bundled bg bundle, next cross-runtime RPC crashes. - Add `lastEntryURL` (nonatomic, copy) cached unconditionally inside `startBackgroundRunnerWithEntryURL:`. Self-respawn now replays the cached URL via `startBackgroundRunnerWithEntryURL:` instead of the default, keeping the bg moduleId table aligned with main. - Falls back to the default-URL path only when no URL was ever cached (host never called start before triggering restart('all') — implausible in practice), with the original warn log preserved. iOS — two-stage health-check tolerates slow devices (#2, medium) - The 3s `dispatch_after` was measured from restart() dispatch time, not new-host ready. On low-end devices an OTA-multi-bundle reload chain can eat most of that budget, leaving the host's hostDidStart no time to flip the flags — false self-respawn / false error log. - Extract the health-check into `scheduleHealthCheckForRestart:isAll: generation:retried:`. Stage 1 at +3s decides: * Both halves healthy → log OK, done. * Main ready but bg not (mode='all') → STABLE signal that the host didn't re-call startBackgroundRunner; self-respawn now. * Main NOT ready → could be a slow device; reschedule stage 2. Stage 2 at +6s total is the final verdict; whatever's still missing is logged as integration failure (and bg self-healed if it can be). Generation check at each stage entry bails on supersession. - Added detailed stage1/stage2 diagnostic logs so production logs distinguish "host omitted the call" from "host was just slow". iOS — isStarted nonatomic + cross-thread reads (#3, medium) - Pre-existing nonatomic property is now read on main thread by the health-check while writes happen on caller's thread (the module's public surface does not pin start... to main). One-line change to (atomic, assign, readwrite); inline comment documents the TOCTOU race on concurrent first-time starts as a separate pre-existing concern. Not changed: - TOCTOU race in start... (concurrent first-time call → double init) is real but pre-existing and out of scope here; flagged in the new property comment so the next maintainer sees it. - kRestartHealthCheckDelaySeconds stays at 3.0s — total budget is now 6s via stage 2, which the reviewer's worst-case "1.5–2s reload + slow hostDidStart" easily fits inside. Health-check method declared in the class extension as a forward declaration so call-site readability is preserved (definition lives after restartWithMode: where the restart flow naturally starts).
…lish) Round-3 left four low-risk items reviewer recommended resolving for consistency / debuggability. All four close cleanly: #1 — lastEntryURL → atomic - Same rationale as the round-3 isStarted change: written from caller's thread (public API doesn't pin start... to main), read on main by the health-check. NSString* assignment isn't atomic under ObjC ARC, so cross-thread readers could observe a torn pointer or stale value. One-line change to (atomic, copy); pre-existing nonatomic was an inconsistency with the round-3 fix. #2 — race-debug warn on startBackgroundRunnerWithEntryURL: early return - The early-return short-circuit (`if (self.isStarted) return;`) is the natural collision point for the self-respawn-vs-host-async-start race: self-respawn at stage 2 boots with the cached URL, then the host's late async start call comes in with a different URL and gets silently dropped. Added a `[BTLogger warn:]` that fires when the requested URL differs from the active one — production traces now show "bg is on URL X but Y was requested" instead of a mystery. #3 — stage 1 always defers bg self-respawn to stage 2 - Round-3 stage 1 had a fast-path that self-respawned immediately when it saw mainReady=YES but bgReady=NO, on the assumption that mainReady was a stable signal the host wasn't going to call start. That's true for hosts whose hostDidStart: synchronously calls both install AND start, but hosts that gate startBackgroundRunner on async work (feature flag fetch, login, network) can be mainReady=YES while the real start is still inflight — the fast-path would race them and the host's late start would be dropped silently (#2 makes that case loud now, but losing the URL is still wrong). Stage 1 now always reschedules stage 2 if anything is missing; stage 2 (+~3s) is sized to give async host paths time to land. Cost is +3s on the actual self-respawn moment, only on broken-integration paths. #4 — treat bundled default "background.bundle" as no-real-cache - startBackgroundRunnerWithEntryURL: caches unconditionally, including the bundled default when host calls the no-arg startBackgroundRunner in release builds. Round-3 self-respawn's "cachedURL.length > 0" branch was treating that as a real cache and skipping the OTA- mismatch warn — but a host that bootstrapped with the default and never swapped to an OTA URL still has the same crash risk. Now filters: `cachedURL.length > 0 && ![cachedURL isEqualTo String:@"background.bundle"]`. Hosts that immediately call startBackgroundRunnerWithEntryURL:OTAUrl overwrite the cache so this filter only triggers in the genuinely-no-custom-URL case. #5 (cosmetic explicit ivar init) is deliberately skipped — reviewer flagged it as not necessary.
… crash (#54) * feat(background-thread): add restart(mode, reason) and fix iOS reload crash - New TurboModule method `restart(mode, reason)` to replace `react-native-restart`: `mode='ui'` reloads the main JS runtime only (bg stays hot — language/devSettings), `mode='all'` reloads both runtimes (OTA install/switch, resetData). - iOS: sequence SharedRPC quiesce → optional bg host release → `RCTTriggerReloadCommandListeners` on the main thread; AppDelegate.hostDidStart re-arms the main SharedRPC listener and re-spawns the bg host for `mode='all'`. - Android: invalidate SharedRPC listeners via new JNI `nativeInvalidateSharedRpc` then process-restart (parity with react-native-restart-newarch). - Fix iOS EXC_BAD_ACCESS on main runtime reload: SharedRPC carries a per-listener `alive` atomic flipped synchronously in `invalidate(runtimeId)`, and executor lambdas on both iOS sites now capture `RCTInstance` `__weak` so a torn-down instance no-ops cleanly instead of dispatching into invalidated memory. - Bump `@onekeyfe/react-native-background-thread` to 3.0.32. * feat(background-thread Android): soft-reload main runtime for restart('ui') Replaces the v1 placeholder where Android did a full-process restart for both modes. Now matches iOS semantics: `mode='ui'` keeps the bg runtime hot, only the main JS runtime reloads. - Capture `mainReactHost` lazily in `installSharedBridgeInMainRuntime` via `(applicationContext as ReactApplication).reactHost`. Stays null on non-bridgeless hosts. - `restart()` with `mode='ui'`: after SharedRPC invalidate('main'), resolve the promise, then post to UI thread and call `ReactHost.reload(reason)`. JS bootstrap re-invokes `installSharedBridge` which refreshes `mainRuntimePtr` and re-arms the "main" SharedRPC listener — symmetric with iOS where AppDelegate.hostDidStart does the same job. - Fallback to process restart when `mainReactHost` is null or `reload()` throws — keeps semantics consistent across host setups. - `mode='all'` unchanged: process restart via `Runtime.exit + makeRestartActivityTask`. OTA install/switch wants a clean re-read of both bundles from disk, and Android has a clean self-relaunch path that iOS lacks, so we don't pay the sequencing complexity of soft-reloading both runtimes. - Clear `mainReactHost` in `destroy()` to match other lifecycle fields. Not yet verified end-to-end — needs a build against the host app to exercise: bridgeless detection, listener re-arm timing, bg-to-main RPC during the reload window. Filed for follow-up testing. * fix(background-thread): address PR #54 audit findings Reviewer flagged a mix of integration-contract gaps, an Android error- propagation bug, and a couple of C++ housekeeping items. Fixes: iOS — host-integration contract (#1, high) - Document the post-reload contract explicitly on `installSharedBridgeInMainRuntime:` and `restartWithMode:` headers. The host AppDelegate's `hostDidStart:` must re-invoke both the SharedBridge install (every reload) and `startBackgroundRunner` (mode='all' only). - `restartWithMode:` now arms a one-shot `RCTJavaScriptDidLoadNotification` observer that fires ~1.5s after the new main bridge loads: * mode='all': self-respawns the bg runtime if the host AppDelegate didn't (idempotent via the existing `isStarted` guard, so a correctly- wired host pays nothing). * Both modes: logs `[BTLogger error:]` if the host failed to re-call `installSharedBridgeInMainRuntime:`. Tracked via a new atomic flag `mainSharedBridgeInstalled` flipped in the install lambda. Surfaces integration omissions in production logs instead of silent RPC drop. JS spec — cross-platform asymmetry (#2, high) - `NativeBackgroundThread.ts` JSDoc now spells out: iOS preserves the process across `mode='all'` (soft reload); Android kills it via `Runtime.exit(0)`. Callers depending on native process-level state (timers, singletons, in-memory caches, foreground services) must not assume cross-platform survival. Android — false-success promise resolution (#3, medium) - `triggerProcessRestart` returns `Boolean` so failure (intent not resolvable, `startActivity` `SecurityException`) propagates instead of being swallowed; caller rejects the Promise with `BG_RESTART_ERROR`. - `mode='ui'` soft-reload moves `promise.resolve(null)` inside the `Handler.post` success branch — a synchronous `ReactHost.reload()` throw now routes through the same fallback-then-reject path instead of reporting false success because resolve already happened. SharedRPC — clean up dead listener entries (#6, low) - `invalidate(runtimeId)` now erases the listener entry from `listeners_` instead of leaving it with `alive=false`. Already-dispatched executor lambdas hold their own `shared_ptr<alive>` snapshot so erasing is race-safe; it only prevents NEW `notifyOtherRuntime()` snapshots from picking up the dead entry. Stops a permanently-dead listener from hanging around when a `mode='all'` restart never gets its post-reload re-install. - `install()`'s defensive dedup loop is now documented as the legacy / no-prior-invalidate fallback path (#7, low). Reviewer #4 (no tests) and #2's Android-side TODO (soft-reload both runtimes via `ReactHost.reload`) remain follow-ups — flagged in PR description, not in scope here. * fix(background-thread): address PR #54 audit round 2 Round-1 self-heal observer hinged on RCTJavaScriptDidLoadNotification, which reviewer flagged as unreliable in bridgeless / NewArch. The fix also surfaced two secondary hazards (observer leak when notification never fires; cross-generation flag misreporting on concurrent restarts). All three close together by dropping the notification entirely. iOS — replace notification observer with plain dispatch_after (#1, #2, high) - The two signals the post-reload check needs (mainSharedBridgeInstalled, isStarted) are this module's own state. No external timing anchor is required; the notification was only ever an attempt at one. A direct dispatch_after on the main queue at kRestartHealthCheckDelaySeconds (3s, constant — empirical 6× margin over baseline hostDidStart chain on low-end devices) eliminates the bridgeless-fire-or-not gamble AND the observer-leak/cross-generation misfire that the observer pattern introduced. iOS — generation counter for concurrent restart() safety (#3, medium) - Adds `restartGeneration` (nonatomic; main-thread-only by construction). Each restartWithMode: bumps it and the health-check captures its own `myGen`; on fire, it bails if `restartGeneration != myGen` so a later restart's flag reset can't make an earlier restart's check misreport. iOS — self-respawn loses custom OTA entry URL (#6, low) - The bg self-respawn path falls through `startBackgroundRunner` → default URL (`background.bundle` / DEBUG URL), losing any custom entry URL the host set via startBackgroundRunnerWithEntryURL: (typically an OTA-resolved bundle path) because reactNativeFactoryDelegate is released for mode='all'. Added an explicit `[BTLogger warn:]` so this trade-off is visible in logs when the host's AppDelegate wiring is broken AND OTA paths matter. Android — soften over-confident resolve-delivery comment (#4, medium) - Round-1 comment claimed "the JS thread is still live and can deliver the success callback before being destroyed". Closer to truth: the CallInvoker is bound to the outgoing ReactInstance which reload invalidates very quickly, and `await restart(...)` callers' continu- ations are typically superseded by the reload. Position-of-resolve only matters for the synchronous-throw path. Updated the comment. Not changed: - 1.5s → 3s on the health-check delay is the right call but isn't itself a magic-number fix — `kRestartHealthCheckDelaySeconds` is a named constant with rationale comment. Polling-until-flag-flips was considered and skipped: extra complexity for a one-line log output. Still pending end-to-end verification on the OneKey host app: - mode='ui' / mode='all' on iOS bridgeless: confirm health-check logs fire only when integration is actually broken (deliberate AppDelegate.hostDidStart: omission test). - Android: confirm BG_RESTART_ERROR Promise rejection on synthetic startActivity failure (e.g. invalid intent). * fix(background-thread): address PR #54 audit round 3 (iOS hardening) Round-2 left three open concerns reviewer flagged as merge-blockers for the OTA path plus a memory-ordering nit. Fixes: iOS — cache lastEntryURL for OTA-safe self-respawn (#1, medium) - Round-2's self-respawn fell through to `startBackgroundRunner` → default `background.bundle`. On OTA-equipped hosts that's a moduleId- mismatch crash waiting to happen: main runs the OTA-updated bundle, bg loads the IPA's bundled bg bundle, next cross-runtime RPC crashes. - Add `lastEntryURL` (nonatomic, copy) cached unconditionally inside `startBackgroundRunnerWithEntryURL:`. Self-respawn now replays the cached URL via `startBackgroundRunnerWithEntryURL:` instead of the default, keeping the bg moduleId table aligned with main. - Falls back to the default-URL path only when no URL was ever cached (host never called start before triggering restart('all') — implausible in practice), with the original warn log preserved. iOS — two-stage health-check tolerates slow devices (#2, medium) - The 3s `dispatch_after` was measured from restart() dispatch time, not new-host ready. On low-end devices an OTA-multi-bundle reload chain can eat most of that budget, leaving the host's hostDidStart no time to flip the flags — false self-respawn / false error log. - Extract the health-check into `scheduleHealthCheckForRestart:isAll: generation:retried:`. Stage 1 at +3s decides: * Both halves healthy → log OK, done. * Main ready but bg not (mode='all') → STABLE signal that the host didn't re-call startBackgroundRunner; self-respawn now. * Main NOT ready → could be a slow device; reschedule stage 2. Stage 2 at +6s total is the final verdict; whatever's still missing is logged as integration failure (and bg self-healed if it can be). Generation check at each stage entry bails on supersession. - Added detailed stage1/stage2 diagnostic logs so production logs distinguish "host omitted the call" from "host was just slow". iOS — isStarted nonatomic + cross-thread reads (#3, medium) - Pre-existing nonatomic property is now read on main thread by the health-check while writes happen on caller's thread (the module's public surface does not pin start... to main). One-line change to (atomic, assign, readwrite); inline comment documents the TOCTOU race on concurrent first-time starts as a separate pre-existing concern. Not changed: - TOCTOU race in start... (concurrent first-time call → double init) is real but pre-existing and out of scope here; flagged in the new property comment so the next maintainer sees it. - kRestartHealthCheckDelaySeconds stays at 3.0s — total budget is now 6s via stage 2, which the reviewer's worst-case "1.5–2s reload + slow hostDidStart" easily fits inside. Health-check method declared in the class extension as a forward declaration so call-site readability is preserved (definition lives after restartWithMode: where the restart flow naturally starts). * fix(background-thread iOS): address PR #54 audit round 4 (low-risk polish) Round-3 left four low-risk items reviewer recommended resolving for consistency / debuggability. All four close cleanly: #1 — lastEntryURL → atomic - Same rationale as the round-3 isStarted change: written from caller's thread (public API doesn't pin start... to main), read on main by the health-check. NSString* assignment isn't atomic under ObjC ARC, so cross-thread readers could observe a torn pointer or stale value. One-line change to (atomic, copy); pre-existing nonatomic was an inconsistency with the round-3 fix. #2 — race-debug warn on startBackgroundRunnerWithEntryURL: early return - The early-return short-circuit (`if (self.isStarted) return;`) is the natural collision point for the self-respawn-vs-host-async-start race: self-respawn at stage 2 boots with the cached URL, then the host's late async start call comes in with a different URL and gets silently dropped. Added a `[BTLogger warn:]` that fires when the requested URL differs from the active one — production traces now show "bg is on URL X but Y was requested" instead of a mystery. #3 — stage 1 always defers bg self-respawn to stage 2 - Round-3 stage 1 had a fast-path that self-respawned immediately when it saw mainReady=YES but bgReady=NO, on the assumption that mainReady was a stable signal the host wasn't going to call start. That's true for hosts whose hostDidStart: synchronously calls both install AND start, but hosts that gate startBackgroundRunner on async work (feature flag fetch, login, network) can be mainReady=YES while the real start is still inflight — the fast-path would race them and the host's late start would be dropped silently (#2 makes that case loud now, but losing the URL is still wrong). Stage 1 now always reschedules stage 2 if anything is missing; stage 2 (+~3s) is sized to give async host paths time to land. Cost is +3s on the actual self-respawn moment, only on broken-integration paths. #4 — treat bundled default "background.bundle" as no-real-cache - startBackgroundRunnerWithEntryURL: caches unconditionally, including the bundled default when host calls the no-arg startBackgroundRunner in release builds. Round-3 self-respawn's "cachedURL.length > 0" branch was treating that as a real cache and skipping the OTA- mismatch warn — but a host that bootstrapped with the default and never swapped to an OTA URL still has the same crash risk. Now filters: `cachedURL.length > 0 && ![cachedURL isEqualTo String:@"background.bundle"]`. Hosts that immediately call startBackgroundRunnerWithEntryURL:OTAUrl overwrite the cache so this filter only triggers in the genuinely-no-custom-URL case. #5 (cosmetic explicit ivar init) is deliberately skipped — reviewer flagged it as not necessary. * refactor(background-thread): type restart() mode as string-literal union Replace the bare `mode: string` parameter on the TurboModule spec with a typed `RestartMode = 'ui' | 'all'` string-literal union exported from NativeBackgroundThread.ts. Why a literal union (not a TS enum): - Zero runtime cost — a regular `enum` compiles to a JS object that ships with the bundle; a literal union erases to nothing. - RN TurboModule codegen's supported-type whitelist is primitives, Object/Array, Promise, and string-literal unions. Plain TS `enum` is not on that list; behaviour across RN versions is unstable and best avoided in spec files. - Consumers can `import type { RestartMode } from '...'` and pass a plain string; no runtime import dependency. Migration note: if a downstream consumer was using `EAppRestartMode` (TS string enum from `@onekeyhq/shared`) as the value, its members aren't assignable to a string-literal union by default. Two options at the call site: 1. `restart(EAppRestartMode.UI as RestartMode, reason)`. 2. Migrate `EAppRestartMode` to a string-literal union / `as const` object so the two stay aligned. JSDoc updated to reference {@link RestartMode}; the native code still validates at runtime (it's the source of truth for which strings the platforms accept), so any cast that smuggles in an unknown mode is caught and rejects the Promise. * fix(background-thread cpp): explicit #include <algorithm> in SharedRPC.cpp std::remove_if is used in both install() and invalidate(); the symbol was only resolved transitively via SharedRPC.h's STL chain, which is fragile across toolchain/header changes. Make the dependency explicit so a future header diet on SharedRPC.h cannot break the build. * fix(background-thread): address PR #54 audit round 5 - iOS: align isStarted public-header declaration with .mm redeclaration (atomic, readonly). The previous nonatomic/atomic mismatch trips Clang -Wproperty-attribute-mismatch and fails CI under -Werror. - src: re-export RestartMode from the package entry so the JSDoc-promised `import type { RestartMode } from '@onekeyfe/react-native-background-thread'` actually resolves; consumers were getting a TS2305 against the prepared index.d.ts. - iOS: refresh the scheduleHealthCheckForRestart: docblock — earlier rounds removed the stage-1 self-respawn-on-bg-not-ready short-circuit, but the function-level comment still described it. Comment now matches the unconditional-reschedule behaviour the body actually implements. * fix(background-thread): address PR #54 audit round 6 - Android restart('ui'): handle async reload-task fault, not just the synchronous throw. ReactHost.reload(reason) returns TaskInterface<Void> in RN 0.83 and the public TaskInterface surface only exposes waitForCompletion + isFaulted/isCancelled/getError (no continueWith). Watch the task on a short-lived daemon thread; on fault, cancellation, or 15s timeout, post back to the main thread and trigger the existing process-restart fallback. Without this, an async reload fault leaves SharedRPC main listener invalidated and main runtime torn down with no rebuild path — app would be permanently broken until manual relaunch. - src/NativeBackgroundThread.ts: fix the Prettier error that was the only true exit-1 lint failure (loadSegmentInBackground signature collapsed onto one line). Also drop the two stale eslint-disable directives in SharedRPC.ts / SharedStore.ts now that the underlying no-var hits no longer fire (warning-level but flagged in the audit). - src/NativeBackgroundThread.ts JSDoc: replace the stale 'post-reload observer' wording with 'host hostDidStart: + dispatch_after health- check fallback' so the public docs match the implementation (which explicitly avoids RCTJavaScriptDidLoadNotification because of its unreliable timing in bridgeless / NewArch). * 3.0.32 * fix(background-thread): loosen bundle-update peerDep to "*" The 3.0.32 release-it commit (0247ebf) updated the yarn.lock entry for background-thread's bundle-update peerDep but missed updating the package.json itself, breaking CI's `yarn install --immutable`. Past releases have hit the same shape — the peerDep version is bumped on every bundle-update release, and any miss in either file desyncs the lockfile. The version floor wasn't load-bearing in practice (bundle-update hasn't shipped a breaking API change for background-thread integration since the floor was first introduced), so drop the floor and use `*`. This eliminates the release-it sync hazard going forward and keeps the runtime contract (consumer must have bundle-update installed) intact via the peerDep entry. * test(background-thread example): add restart(mode, reason) test buttons Three buttons on BackgroundThreadTestPage: - restart('ui'): exercises the main-runtime soft reload path. iOS goes through RCTTriggerReloadCommandListeners; Android goes through ReactHost.reload (bridgeless) with the new TaskInterface watcher falling back to process restart on async fault. - restart('all'): on iOS, soft-reloads main and re-spawns bg; on Android, process-restarts via Runtime.exit + makeRestartActivityTask. - bogus mode: drives the synchronous validation reject path so the log shows a visible BG_RESTART_ERROR rejection. Imports RestartMode from the package entry to verify the new re-export is reachable from a real consumer (the previously-broken JSDoc claim). * Update Podfile.lock * chore(example): pin cocoapods to ~> 1.16.2 (Ruby 3.4-compatible) cocoapods 1.15.x (which the old Gemfile constraint `>= 1.13, != 1.15.0, != 1.15.1` resolves to) crashes on Ruby 3.4 with `cannot load such file -- kconv` — kconv was removed from the Ruby 3.4 stdlib. 1.16.2 dropped the direct kconv dependency, but xcodeproj still caps CFPropertyList to `< 4.0`, and CFPropertyList 3.x has a leftover `require 'kconv'` at file head (no actual Kconv calls). Pin cocoapods to ~> 1.16.2, relax xcodeproj to >= 1.27.0 (1.16.x requires it), and add a tiny local `kconv-shim` gem with an empty `kconv.rb` so the orphan require in CFPropertyList resolves. `bundle exec pod install` is now reproducible across machines and locks the toolchain to a known-good version. * chore(example ios): disable code signing for simulator builds The example target had no DEVELOPMENT_TEAM set (intentionally — it's per-developer and never committed). With Automatic signing, xcodebuild errored "Signing for 'example' requires a development team" even on simulator runs (`yarn ios`, fresh-clone build, CI), because Xcode 14+ asks for a team regardless of SDK when CODE_SIGN_STYLE is Automatic. Add `CODE_SIGNING_ALLOWED[sdk=iphonesimulator*] = NO` on both Debug and Release configs of the example target. Simulator builds now skip signing entirely (the binary doesn't need it to run in the sim); device builds (iphoneos SDK) still require a team, which devs can configure locally in Xcode without committing personal identifiers. * fix(example ios): ad-hoc sign simulator builds instead of disabling signing Previous attempt (cfee72c) set CODE_SIGNING_ALLOWED=NO for the iphonesimulator SDK to bypass the 'requires a development team' error. That worked for the example target's main binary but it also cleared signing on the embedded frameworks Copy-Frameworks step — GPChannelSDKCore.framework (vendored by react-native-lite-card) and hermesvm.framework ended up unsigned. iOS 26 simulator dyld refuses to load unsigned frameworks at startup, so the app SIGABRT'd at launch with 'Library not loaded: GPChannelSDKCore'. Replace the all-off switch with ad-hoc signing on simulator: - CODE_SIGN_STYLE = Manual (skip automatic team resolution) - CODE_SIGN_IDENTITY[sdk=iphonesimulator*] = '-' (Sign to Run Locally) Simulator builds still get a valid (ad-hoc) signature on the main binary and every embedded framework, so dyld accepts them. No team is required because Manual style doesn't try to provision. Device (iphoneos) builds remain untouched — devs configure their own team in Xcode locally as before. * fix(auto-size-input): dispatch focus/blur to UI thread Nitro hybrid view methods can be invoked off the UI thread (JS thread on iOS, arbitrary thread on Android). UIKit's becomeFirstResponder / resignFirstResponder and Android's requestFocus / clearFocus / IMM calls must run on the main thread. On iOS the previous implementation crashed with SIGTRAP via FrontBoardServices' assertBarrierOnQueue. Android would raise CalledFromWrongThreadException. Wrap both focus() and blur() with a main-thread dispatch (Thread.isMainThread short-circuit on iOS, Looper.getMainLooper() + View.post on Android). The isDisposed check is re-run inside the posted block on Android to avoid touching a recycled view. * 3.0.33 * Update CHANGELOG.md
Summary by CodeRabbit
New Features
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.