@bravemobile/react-native-code-push 12.4.1 → 13.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CodePush.podspec +47 -2
  2. package/README.md +118 -3
  3. package/THIRD-PARTY-NOTICES +96 -0
  4. package/android/app/build.gradle +26 -0
  5. package/android/app/proguard-rules.pro +5 -0
  6. package/android/app/src/main/cpp/CMakeLists.txt +38 -0
  7. package/android/app/src/main/cpp/binarypatch_jni.c +181 -0
  8. package/android/app/src/main/java/com/microsoft/codepush/react/BinaryPatchAttempt.java +92 -0
  9. package/android/app/src/main/java/com/microsoft/codepush/react/BinaryPatchResult.java +64 -0
  10. package/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +46 -11
  11. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushBinaryPatch.java +293 -0
  12. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushConstants.java +25 -0
  13. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +79 -88
  14. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java +159 -4
  15. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java +13 -0
  16. package/android/app/src/main/java/com/microsoft/codepush/react/HDiffPatchNative.java +52 -0
  17. package/android/app/src/test/java/com/microsoft/codepush/react/CodePushBinaryPatchTest.java +393 -0
  18. package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerBinaryPatchTest.java +207 -0
  19. package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerDownloadTest.java +449 -0
  20. package/android/codepush-export.gradle +115 -0
  21. package/cli/commands/bundleCommand/bundleCodePush.ts +39 -7
  22. package/cli/commands/bundleCommand/index.ts +9 -1
  23. package/cli/commands/releaseCommand/addToReleaseHistory.ts +7 -0
  24. package/cli/commands/releaseCommand/index.test.ts +118 -0
  25. package/cli/commands/releaseCommand/index.ts +20 -2
  26. package/cli/commands/releaseCommand/release.test.ts +582 -0
  27. package/cli/commands/releaseCommand/release.ts +232 -15
  28. package/cli/dist/commands/bundleCommand/bundleCodePush.js +26 -7
  29. package/cli/dist/commands/bundleCommand/index.js +5 -1
  30. package/cli/dist/commands/releaseCommand/addToReleaseHistory.js +6 -1
  31. package/cli/dist/commands/releaseCommand/index.js +8 -1
  32. package/cli/dist/commands/releaseCommand/index.test.js +96 -0
  33. package/cli/dist/commands/releaseCommand/release.js +165 -17
  34. package/cli/dist/commands/releaseCommand/release.test.js +367 -0
  35. package/cli/dist/functions/makeBinaryPatchBundle.js +248 -0
  36. package/cli/dist/functions/makeBinaryPatchBundle.test.js +321 -0
  37. package/cli/dist/functions/makeCodePushBundle.js +4 -2
  38. package/cli/dist/functions/runHermesEmitBinaryCommand.js +98 -27
  39. package/cli/dist/functions/runHermesEmitBinaryCommand.test.js +100 -0
  40. package/cli/dist/jest.globalSetup.js +38 -0
  41. package/cli/dist/utils/binaryPatch.js +86 -0
  42. package/cli/dist/utils/binaryPatch.test.js +190 -0
  43. package/cli/functions/makeBinaryPatchBundle.test.ts +410 -0
  44. package/cli/functions/makeBinaryPatchBundle.ts +367 -0
  45. package/cli/functions/makeCodePushBundle.ts +4 -2
  46. package/cli/functions/runHermesEmitBinaryCommand.test.ts +124 -0
  47. package/cli/functions/runHermesEmitBinaryCommand.ts +112 -26
  48. package/cli/jest.globalSetup.ts +43 -0
  49. package/cli/utils/binaryPatch.test.ts +234 -0
  50. package/cli/utils/binaryPatch.ts +103 -0
  51. package/cpp/binarypatch/binarypatch_zstd_decompressor.c +187 -0
  52. package/cpp/binarypatch/binarypatch_zstd_decompressor.h +47 -0
  53. package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/checksum_plugin.h +52 -0
  54. package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/hpatch_mt/hpatch_mt.h +147 -0
  55. package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/patch.c +3315 -0
  56. package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/patch.h +268 -0
  57. package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/patch_private.h +297 -0
  58. package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/patch_types.h +434 -0
  59. package/cpp/binarypatch/vendor/zstd/common/allocations.h +55 -0
  60. package/cpp/binarypatch/vendor/zstd/common/bits.h +205 -0
  61. package/cpp/binarypatch/vendor/zstd/common/bitstream.h +454 -0
  62. package/cpp/binarypatch/vendor/zstd/common/compiler.h +464 -0
  63. package/cpp/binarypatch/vendor/zstd/common/cpu.h +249 -0
  64. package/cpp/binarypatch/vendor/zstd/common/debug.c +30 -0
  65. package/cpp/binarypatch/vendor/zstd/common/debug.h +107 -0
  66. package/cpp/binarypatch/vendor/zstd/common/entropy_common.c +340 -0
  67. package/cpp/binarypatch/vendor/zstd/common/error_private.c +64 -0
  68. package/cpp/binarypatch/vendor/zstd/common/error_private.h +158 -0
  69. package/cpp/binarypatch/vendor/zstd/common/fse.h +625 -0
  70. package/cpp/binarypatch/vendor/zstd/common/fse_decompress.c +315 -0
  71. package/cpp/binarypatch/vendor/zstd/common/huf.h +277 -0
  72. package/cpp/binarypatch/vendor/zstd/common/mem.h +422 -0
  73. package/cpp/binarypatch/vendor/zstd/common/pool.c +371 -0
  74. package/cpp/binarypatch/vendor/zstd/common/pool.h +81 -0
  75. package/cpp/binarypatch/vendor/zstd/common/portability_macros.h +171 -0
  76. package/cpp/binarypatch/vendor/zstd/common/threading.c +182 -0
  77. package/cpp/binarypatch/vendor/zstd/common/threading.h +142 -0
  78. package/cpp/binarypatch/vendor/zstd/common/xxhash.c +18 -0
  79. package/cpp/binarypatch/vendor/zstd/common/xxhash.h +7094 -0
  80. package/cpp/binarypatch/vendor/zstd/common/zstd_common.c +48 -0
  81. package/cpp/binarypatch/vendor/zstd/common/zstd_deps.h +123 -0
  82. package/cpp/binarypatch/vendor/zstd/common/zstd_internal.h +324 -0
  83. package/cpp/binarypatch/vendor/zstd/common/zstd_trace.h +156 -0
  84. package/cpp/binarypatch/vendor/zstd/decompress/huf_decompress.c +1944 -0
  85. package/cpp/binarypatch/vendor/zstd/decompress/zstd_ddict.c +244 -0
  86. package/cpp/binarypatch/vendor/zstd/decompress/zstd_ddict.h +44 -0
  87. package/cpp/binarypatch/vendor/zstd/decompress/zstd_decompress.c +2410 -0
  88. package/cpp/binarypatch/vendor/zstd/decompress/zstd_decompress_block.c +2209 -0
  89. package/cpp/binarypatch/vendor/zstd/decompress/zstd_decompress_block.h +73 -0
  90. package/cpp/binarypatch/vendor/zstd/decompress/zstd_decompress_internal.h +240 -0
  91. package/cpp/binarypatch/vendor/zstd/zstd.h +3206 -0
  92. package/cpp/binarypatch/vendor/zstd/zstd_errors.h +107 -0
  93. package/ios/CodePush/CodePush.h +58 -9
  94. package/ios/CodePush/{CodePush.m → CodePush.mm} +77 -63
  95. package/ios/CodePush/CodePushBinaryPatch.h +80 -0
  96. package/ios/CodePush/CodePushBinaryPatch.m +473 -0
  97. package/ios/CodePush/CodePushPackage.m +183 -2
  98. package/ios/CodePush/CodePushUpdateUtils.m +49 -1
  99. package/ios/CodePush.xcodeproj/project.pbxproj +6 -6
  100. package/package.json +28 -4
  101. package/react-native.config.js +3 -1
  102. package/scripts/binary-patch/build-hdiffpatch.sh +86 -0
  103. package/scripts/export-embedded-bundle.sh +76 -0
  104. package/src/CodePush.js +190 -96
  105. package/src/CodePush.test.js +382 -0
  106. package/src/native/NativeCodePush.ts +162 -0
  107. package/src/package-mixins.js +24 -11
  108. package/src/specs/NativeCodePush.ts +43 -0
  109. package/typings/react-native-code-push.d.ts +77 -0
  110. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java +0 -102
  111. package/src/AlertAdapter.js +0 -24
package/CodePush.podspec CHANGED
@@ -12,14 +12,59 @@ Pod::Spec.new do |s|
12
12
  s.source = { :git => 'https://github.com/Soomgo-Mobile/react-native-code-push.git', :tag => "v#{s.version}"}
13
13
  s.ios.deployment_target = '15.5'
14
14
  s.tvos.deployment_target = '15.5'
15
- s.preserve_paths = '*.js'
15
+ s.preserve_paths = ['*.js', 'cpp/binarypatch/**/*.h']
16
16
  s.library = 'z'
17
- s.source_files = 'ios/CodePush/*.{h,m}'
17
+ # The applier that installs a binary patch update is compiled from the C sources the
18
+ # other platform and the host build compile as well, referenced where they live rather
19
+ # than copied here, which is what keeps the appliers of the platforms from drifting
20
+ # apart. Only the decompressing half of zstd is needed, and its assembly fast path is
21
+ # deliberately not vendored, so this list is the same one the other platform builds.
22
+ s.source_files = [
23
+ 'ios/CodePush/*.{h,m,mm}',
24
+ 'cpp/binarypatch/binarypatch_zstd_decompressor.c',
25
+ 'cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/patch.c',
26
+ 'cpp/binarypatch/vendor/zstd/common/debug.c',
27
+ 'cpp/binarypatch/vendor/zstd/common/entropy_common.c',
28
+ 'cpp/binarypatch/vendor/zstd/common/error_private.c',
29
+ 'cpp/binarypatch/vendor/zstd/common/fse_decompress.c',
30
+ 'cpp/binarypatch/vendor/zstd/common/xxhash.c',
31
+ 'cpp/binarypatch/vendor/zstd/common/zstd_common.c',
32
+ 'cpp/binarypatch/vendor/zstd/decompress/huf_decompress.c',
33
+ 'cpp/binarypatch/vendor/zstd/decompress/zstd_ddict.c',
34
+ 'cpp/binarypatch/vendor/zstd/decompress/zstd_decompress.c',
35
+ 'cpp/binarypatch/vendor/zstd/decompress/zstd_decompress_block.c'
36
+ ]
18
37
  s.public_header_files = ['ios/CodePush/CodePush.h']
19
38
 
39
+ binary_patch_header_search_paths = '"$(PODS_TARGET_SRCROOT)/cpp/binarypatch" "$(PODS_TARGET_SRCROOT)/cpp/binarypatch/vendor/HDiffPatch" "$(PODS_TARGET_SRCROOT)/cpp/binarypatch/vendor/zstd"'
40
+ # ZSTD_DISABLE_ASM: the assembly fast path is intentionally not vendored.
41
+ # _IS_USED_MULTITHREAD=0: patches are applied on the thread that downloads them.
42
+ binary_patch_preprocessor_definitions = '$(inherited) ZSTD_DISABLE_ASM=1 _IS_USED_MULTITHREAD=0'
43
+
44
+ s.pod_target_xcconfig = {
45
+ 'HEADER_SEARCH_PATHS' => binary_patch_header_search_paths,
46
+ 'GCC_PREPROCESSOR_DEFINITIONS' => binary_patch_preprocessor_definitions
47
+ }
48
+
20
49
  # Note: Even though there are copy/pasted versions of some of these dependencies in the repo,
21
50
  # we explicitly let CocoaPods pull in the versions below so all dependencies are resolved and
22
51
  # linked properly at a parent workspace level.
23
52
  s.dependency 'React-Core'
24
53
  s.dependency 'SSZipArchive', '~> 2.5.5'
54
+
55
+ if ENV['RCT_NEW_ARCH_ENABLED'] == '1'
56
+ s.compiler_flags = '-DRCT_NEW_ARCH_ENABLED=1'
57
+ # This replaces pod_target_xcconfig rather than adding to it, so what the applier
58
+ # needs to build has to be carried into it as well.
59
+ s.pod_target_xcconfig = {
60
+ 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17',
61
+ 'HEADER_SEARCH_PATHS' => "\"$(PODS_ROOT)/Headers/Public/ReactCodegen\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCodegen/ReactCodegen.framework/Headers\" #{binary_patch_header_search_paths}",
62
+ 'GCC_PREPROCESSOR_DEFINITIONS' => binary_patch_preprocessor_definitions
63
+ }
64
+
65
+ s.dependency 'ReactCodegen'
66
+ s.dependency 'RCTRequired'
67
+ s.dependency 'RCTTypeSafety'
68
+ s.dependency 'ReactCommon/turbomodule/core'
69
+ end
25
70
  end
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # @bravemobile/react-native-code-push
2
2
 
3
- ### Seamless Transition from AppCenter to a Fully Self-Hosted CodePush
3
+ ### CodePush without an update server
4
4
 
5
5
  - **No API Server Needed** – Use static hosting solutions (e.g., AWS S3) without maintaining additional API servers.
6
- - **Familiar API** – Built on `microsoft/react-native-code-push`, ensuring compatibility and stability.
6
+ - **Familiar API** – Built on `microsoft/react-native-code-push`, with the native module migrated to TurboModule.
7
7
  - **Flexible Deployment** – Implement your own release workflow, giving you complete control over the deployment process.
8
8
 
9
9
  ### 🚀 New Architecture support
@@ -13,6 +13,9 @@ Supports React Native 0.77 ~ 0.86.
13
13
  > [!NOTE]
14
14
  > If you are using React Native 0.76 or lower, please use version `12.0.2` of this library.
15
15
 
16
+ > [!IMPORTANT]
17
+ > `13.0.0` migrates the native module to TurboModule. A `13.0.0` binary only runs OTA bundles built with `13.0.0` or later, so ship a new binary version and start its release history fresh when you upgrade.
18
+
16
19
  (Tested on the React Native CLI template apps)
17
20
 
18
21
  ### ✅ Requirements
@@ -33,7 +36,7 @@ npm install @bravemobile/react-native-code-push
33
36
  1. Edit `android/app/build.gradle` file to remove the `apply from: "../../node_modules/..../codepush.gradle"` line.
34
37
 
35
38
  2. The following changes are optional but recommended for cleaning up the old configuration:
36
- - Since the deployment key is no longer needed due to the retirement of AppCenter, it is recommended to remove it from your `Info.plist`, `strings.xml`, or JavaScript code.
39
+ - Since the deployment key is no longer used, it is recommended to remove it from your `Info.plist`, `strings.xml`, or JavaScript code.
37
40
  - Thanks to Auto Linking, you can remove the `react-native-code-push` module settings from `settings.gradle`.
38
41
 
39
42
  3. Follow the installation guide starting from **'4. "CodePush-ify" your app'**.
@@ -78,6 +81,10 @@ Run `cd ios && pod install && cd ..`
78
81
 
79
82
  (`npx pod-install`, `bundle exec pod install --project-directory=./ios`, ..)
80
83
 
84
+ The pod compiles the applier that installs binary patch updates from the C sources it
85
+ carries, so there is nothing else to install: CocoaPods builds them along with the rest
86
+ of the pod.
87
+
81
88
 
82
89
  #### (2) Edit `AppDelegate` Code
83
90
 
@@ -291,6 +298,36 @@ Please refer to the [CodePushOptions](https://github.com/Soomgo-Mobile/react-nat
291
298
  - **onDownloadStart:** Triggered when the bundle download begins.
292
299
  - **onDownloadSuccess:** Triggered when the bundle download completes successfully.
293
300
  - **onSyncError:** Triggered when an unknown error occurs during the update process. (`CodePush.SyncStatus.UNKNOWN_ERROR` status)
301
+ - **onBinaryPatchResult:** Triggered when an update that was published with a binary patch has been downloaded, with the release label and how the patch went. Unlike the callbacks above, this one can also be passed to a single `sync()` call - see below.
302
+
303
+ `onBinaryPatchResult` is called with `{ status: "applied" | "fallback", fallbackReason?: string, applyDurationMs: number }`.
304
+ A `"fallback"` is not a failed update: the update is downloaded in full instead and installed as usual, so the result
305
+ is there to be observed and nothing more. The library neither stores it nor sends it anywhere - an app that wants it in
306
+ its telemetry sends it itself. A callback that throws never fails the update - the error is only logged to the console.
307
+
308
+ Register it on `CodePush({ ... })` like the callbacks above and it covers every sync, whatever the `checkFrequency` is
309
+ and including the `CodePush.sync()` calls you make yourself.
310
+
311
+ ```typescript
312
+ export default CodePush({
313
+ checkFrequency: CodePush.CheckFrequency.MANUAL, // or something else
314
+ releaseHistoryFetcher: releaseHistoryFetcher,
315
+ onBinaryPatchResult: (label, result) => {
316
+ // Send it to your own telemetry, if you want it there.
317
+ },
318
+ })(MyApp);
319
+ ```
320
+
321
+ Pass it to a `sync()` call to override the registered one for that call - to tag the result of one particular sync,
322
+ for instance.
323
+
324
+ ```typescript
325
+ CodePush.sync({
326
+ onBinaryPatchResult: (label, result) => {
327
+ // Send it to your own telemetry, if you want it there.
328
+ },
329
+ });
330
+ ```
294
331
 
295
332
 
296
333
  ### 5. Configure the CLI Tool
@@ -393,6 +430,84 @@ module.exports = Config;
393
430
  ```
394
431
 
395
432
 
433
+ ### 6. Export the Embedded Bundle (Optional)
434
+
435
+ Only needed if you want to release **binary patch updates** (`release --binary-bundle-path`).
436
+ A binary patch is the difference between the update and the JS bundle that is already
437
+ inside the installed app, so releasing one means holding on to that bundle: the exact
438
+ bytes the build you shipped to the store embedded.
439
+
440
+ The library ships a hook for each platform that copies the freshly compiled bundle out of
441
+ the build, together with a `binary-patch-base.json` record describing it.
442
+
443
+ **Android** - apply the Gradle script in your app module's `android/app/build.gradle`:
444
+
445
+ ```groovy
446
+ apply plugin: "com.android.application"
447
+ apply plugin: "com.facebook.react"
448
+
449
+ react {
450
+ // ...
451
+ }
452
+
453
+ apply from: "../../node_modules/@bravemobile/react-native-code-push/android/codepush-export.gradle"
454
+ ```
455
+
456
+ The line can go anywhere in the file (if you use `ext.codePushExportDir`, set it before the
457
+ `apply from:` line). Every variant that bundles JS then exports to
458
+ `android/app/build/codepush/embedded-bundle/<variant>/` after it is bundled. Pass
459
+ `-PcodePushExportDir=<path>` (or set `ext.codePushExportDir`) to export somewhere else; the
460
+ `<variant>` directory is appended either way.
461
+
462
+ **iOS** - call the export script at the end of the **"Bundle React Native code and images"**
463
+ build phase. In Xcode, open that phase
464
+ in your app target's **Build Phases** tab and append the last line below to its script:
465
+
466
+ ```bash
467
+ /bin/sh -c "\"$WITH_ENVIRONMENT\" \"$REACT_NATIVE_XCODE\""
468
+
469
+ "$SRCROOT/../node_modules/@bravemobile/react-native-code-push/scripts/export-embedded-bundle.sh"
470
+ ```
471
+
472
+ The export lands in `$BUILD_DIR/codepush/embedded-bundle/$CONFIGURATION-$PLATFORM_NAME/`.
473
+ Set the `CODEPUSH_EXPORT_DIR` environment variable to export somewhere else; the
474
+ `$CONFIGURATION-$PLATFORM_NAME` directory is appended either way.
475
+
476
+ **Archive the export per binary release.** Whatever pipeline builds your store binary
477
+ should keep that build's export somewhere durable, organized by binary version, so a later
478
+ release can fetch the bundle that matches the binary it targets:
479
+
480
+ ```bash
481
+ # Android, after ./gradlew :app:assembleRelease
482
+ aws s3 cp --recursive \
483
+ android/app/build/codepush/embedded-bundle/release \
484
+ "s3://your-bucket/binaries/android/$BINARY_VERSION/"
485
+
486
+ # iOS - point the export at a path the pipeline knows, since $BUILD_DIR only exists inside the build
487
+ export CODEPUSH_EXPORT_DIR="$PWD/codepush-export"
488
+ xcodebuild -workspace ios/YourApp.xcworkspace -scheme YourApp -configuration Release archive # ...
489
+ aws s3 cp --recursive \
490
+ "$CODEPUSH_EXPORT_DIR/Release-iphoneos" \
491
+ "s3://your-bucket/binaries/ios/$BINARY_VERSION/"
492
+ ```
493
+
494
+ To release a patch later, download the export and pass the bundle's path to
495
+ `--binary-bundle-path`:
496
+
497
+ ```bash
498
+ aws s3 cp --recursive "s3://your-bucket/binaries/android/1.0.0/" ./binary/
499
+ npx code-push release -b 1.0.0 -v 1.0.1 -p android \
500
+ --binary-bundle-path ./binary/index.android.bundle
501
+ ```
502
+
503
+ `release` also uses the record to verify the base bundle it was handed - see
504
+ [Verifying the base bundle](cli/README.md#verifying-the-base-bundle) for details.
505
+
506
+ > [!NOTE]
507
+ > Applying these hooks automatically through the Expo config plugin (`app.plugin.js`) is
508
+ > not implemented yet.
509
+
510
+
396
511
  ## 🚀 CLI Tool Usage
397
512
 
398
513
  > [!TIP]
@@ -0,0 +1,96 @@
1
+ THIRD PARTY NOTICES
2
+ ===================
3
+
4
+ This package includes third party source code. The notices below apply to that
5
+ code only; the rest of this project is covered by LICENSE.md.
6
+
7
+
8
+ -------------------------------------------------------------------------------
9
+ HDiffPatch
10
+ -------------------------------------------------------------------------------
11
+
12
+ Version: v5.1.3
13
+ Source: https://github.com/sisong/HDiffPatch/tree/v5.1.3
14
+ License: MIT
15
+ Vendored: cpp/binarypatch/vendor/HDiffPatch/
16
+
17
+ Only the files required to apply a patch on device are vendored, from
18
+ libHDiffPatch/HPatch: patch.c, patch.h, patch_private.h, patch_types.h,
19
+ checksum_plugin.h and hpatch_mt/hpatch_mt.h. The diff side of HDiffPatch is not
20
+ vendored; the `hdiffz` command line tool is built from upstream sources by
21
+ scripts/binary-patch/build-hdiffpatch.sh and is not distributed with this package.
22
+
23
+ MIT License
24
+
25
+ HDiffPatch
26
+ Copyright (c) 2012-2025 housisong
27
+
28
+ Permission is hereby granted, free of charge, to any person obtaining a copy
29
+ of this software and associated documentation files (the "Software"), to deal
30
+ in the Software without restriction, including without limitation the rights
31
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32
+ copies of the Software, and to permit persons to whom the Software is
33
+ furnished to do so, subject to the following conditions:
34
+
35
+ The above copyright notice and this permission notice shall be included in all
36
+ copies or substantial portions of the Software.
37
+
38
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44
+ SOFTWARE.
45
+
46
+
47
+ -------------------------------------------------------------------------------
48
+ Zstandard (zstd)
49
+ -------------------------------------------------------------------------------
50
+
51
+ Version: 1.5.7
52
+ Commit: 68c88c7c7ad22b5e6882a5296ef96d27dc8750c4
53
+ Source: https://github.com/sisong/zstd
54
+ (the fork HDiffPatch builds against, derived from
55
+ https://github.com/facebook/zstd)
56
+ The files here are a snapshot of that fork; the copies in this
57
+ repository are the version the appliers are built from, and
58
+ scripts/binary-patch/build-hdiffpatch.sh pins the same commit so the
59
+ `hdiffz` it builds compresses with the code that is vendored here.
60
+ License: BSD-3-Clause
61
+ Vendored: cpp/binarypatch/vendor/zstd/
62
+
63
+ Only the decompression path is vendored (lib/common and lib/decompress). The
64
+ amd64 assembly implementation of the Huffman decoder is not vendored because the
65
+ appliers are compiled with ZSTD_DISABLE_ASM=1.
66
+
67
+ BSD License
68
+
69
+ For Zstandard software
70
+
71
+ Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
72
+
73
+ Redistribution and use in source and binary forms, with or without modification,
74
+ are permitted provided that the following conditions are met:
75
+
76
+ * Redistributions of source code must retain the above copyright notice, this
77
+ list of conditions and the following disclaimer.
78
+
79
+ * Redistributions in binary form must reproduce the above copyright notice,
80
+ this list of conditions and the following disclaimer in the documentation
81
+ and/or other materials provided with the distribution.
82
+
83
+ * Neither the name Facebook, nor Meta, nor the names of its contributors may
84
+ be used to endorse or promote products derived from this software without
85
+ specific prior written permission.
86
+
87
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
88
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
89
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
90
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
91
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
92
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
93
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
94
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
95
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
96
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -1,10 +1,17 @@
1
1
  apply plugin: "com.android.library"
2
+ apply plugin: "com.facebook.react"
2
3
 
3
4
  def DEFAULT_COMPILE_SDK_VERSION = 26
4
5
  def DEFAULT_BUILD_TOOLS_VERSION = "26.0.3"
5
6
  def DEFAULT_TARGET_SDK_VERSION = 26
6
7
  def DEFAULT_MIN_SDK_VERSION = 16
7
8
 
9
+ react {
10
+ jsRootDir = file("../..")
11
+ libraryName = "RNCodePushSpec"
12
+ codegenJavaPackageName = "com.microsoft.codepush.react"
13
+ }
14
+
8
15
  android {
9
16
  namespace "com.microsoft.codepush.react"
10
17
 
@@ -25,8 +32,27 @@ android {
25
32
  defaultConfig {
26
33
  consumerProguardFiles 'proguard-rules.pro'
27
34
  }
35
+
36
+ // Builds the binary patch applier. Its sources are the shared ones at the root of the
37
+ // repository, which the CMake file points at; nothing is compiled twice.
38
+ externalNativeBuild {
39
+ cmake {
40
+ path "src/main/cpp/CMakeLists.txt"
41
+ }
42
+ }
43
+
44
+ testOptions {
45
+ // The unit tests exercise plain logic, so the android framework classes they touch
46
+ // in passing (logging above all) are allowed to do nothing instead of throwing.
47
+ unitTests.returnDefaultValues = true
48
+ }
28
49
  }
29
50
 
30
51
  dependencies {
31
52
  implementation "com.facebook.react:react-native:+"
53
+
54
+ testImplementation "junit:junit:4.13.2"
55
+ // org.json ships as stubs in the android framework jar, and the unit tests parse real
56
+ // manifests, so they need an implementation that actually parses.
57
+ testImplementation "org.json:json:20231013"
32
58
  }
@@ -37,3 +37,8 @@
37
37
 
38
38
  # Can't find referenced class org.bouncycastle.**
39
39
  -dontwarn com.nimbusds.jose.**
40
+
41
+ # The binary patch applier is found by name from the native library.
42
+ -keepclasseswithmembernames class com.microsoft.codepush.react.HDiffPatchNative {
43
+ native <methods>;
44
+ }
@@ -0,0 +1,38 @@
1
+ cmake_minimum_required(VERSION 3.13)
2
+
3
+ project(codepush-binarypatch C)
4
+
5
+ # The applier sources live outside the platform directories because the host build and
6
+ # the other platform compile the very same files. Referencing them where they are is
7
+ # what keeps the appliers of the platforms from drifting apart, so they are deliberately
8
+ # not copied into this directory.
9
+ set(BINARY_PATCH_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../cpp/binarypatch)
10
+
11
+ add_library(codepush-binarypatch SHARED
12
+ ${CMAKE_CURRENT_SOURCE_DIR}/binarypatch_jni.c
13
+ ${BINARY_PATCH_DIR}/binarypatch_zstd_decompressor.c
14
+ ${BINARY_PATCH_DIR}/vendor/HDiffPatch/libHDiffPatch/HPatch/patch.c
15
+ ${BINARY_PATCH_DIR}/vendor/zstd/common/debug.c
16
+ ${BINARY_PATCH_DIR}/vendor/zstd/common/entropy_common.c
17
+ ${BINARY_PATCH_DIR}/vendor/zstd/common/error_private.c
18
+ ${BINARY_PATCH_DIR}/vendor/zstd/common/fse_decompress.c
19
+ ${BINARY_PATCH_DIR}/vendor/zstd/common/xxhash.c
20
+ ${BINARY_PATCH_DIR}/vendor/zstd/common/zstd_common.c
21
+ ${BINARY_PATCH_DIR}/vendor/zstd/decompress/huf_decompress.c
22
+ ${BINARY_PATCH_DIR}/vendor/zstd/decompress/zstd_ddict.c
23
+ ${BINARY_PATCH_DIR}/vendor/zstd/decompress/zstd_decompress.c
24
+ ${BINARY_PATCH_DIR}/vendor/zstd/decompress/zstd_decompress_block.c)
25
+
26
+ target_include_directories(codepush-binarypatch PRIVATE
27
+ ${BINARY_PATCH_DIR}
28
+ ${BINARY_PATCH_DIR}/vendor/HDiffPatch
29
+ ${BINARY_PATCH_DIR}/vendor/zstd)
30
+
31
+ # ZSTD_DISABLE_ASM: the assembly fast path is intentionally not vendored.
32
+ # _IS_USED_MULTITHREAD=0: patches are applied on the thread that downloads them.
33
+ target_compile_definitions(codepush-binarypatch PRIVATE
34
+ ZSTD_DISABLE_ASM=1
35
+ _IS_USED_MULTITHREAD=0)
36
+
37
+ find_library(log-lib log)
38
+ target_link_libraries(codepush-binarypatch ${log-lib})
@@ -0,0 +1,181 @@
1
+ /*
2
+ * JNI entry point of the CodePush binary patch applier.
3
+ *
4
+ * The applier itself is the shared C code one directory tree up, which the host build and
5
+ * the other platform compile as well; this file only moves data across the JNI boundary
6
+ * and turns a failure into the result code `HDiffPatchNative` hands back to Java.
7
+ *
8
+ * Memory contract, the same one the host build documents:
9
+ * - the base bundle is held whole, because the patches are produced with `hdiffz -m`,
10
+ * which patches with random access to the base data
11
+ * - the patch is held whole, being far smaller than either bundle
12
+ * - the restored bundle is written sequentially to a file, so a patch session never
13
+ * holds two bundles at once
14
+ *
15
+ * A patch carries no checksum of the base data and its zstd streams carry no content
16
+ * checksum, so a successful apply here is not proof of a correct result. The caller
17
+ * verifies the base and target hashes; this file cannot.
18
+ */
19
+
20
+ #include <jni.h>
21
+ #include <stdlib.h>
22
+ #include <stdio.h>
23
+ #include <string.h>
24
+
25
+ #include <android/log.h>
26
+
27
+ #include "binarypatch_zstd_decompressor.h"
28
+ #include "libHDiffPatch/HPatch/patch.h"
29
+
30
+ #define LOG_TAG "ReactNative"
31
+ #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
32
+
33
+ /* Mirrored by CodePushBinaryPatch.PatchApplier; the two lists have to stay in step. */
34
+ #define RESULT_OK 0
35
+ #define RESULT_INVALID_ARGUMENT 1
36
+ #define RESULT_IO_ERROR 2
37
+ #define RESULT_INVALID_HEADER 3
38
+ #define RESULT_UNSUPPORTED_COMPRESSION 4
39
+ #define RESULT_SIZE_MISMATCH 5
40
+ #define RESULT_APPLY_FAILED 6
41
+
42
+ /* Scratch buffer handed to patch_decompress_with_cache() to reduce stream reads. */
43
+ #define APPLY_CACHE_SIZE (1024 * 1024)
44
+
45
+ typedef struct {
46
+ FILE* file;
47
+ hpatch_StreamPos_t writtenSize;
48
+ } TSequentialFileWriter;
49
+
50
+ static hpatch_BOOL _write_sequential(const hpatch_TStreamOutput* stream,
51
+ hpatch_StreamPos_t writeToPos,
52
+ const unsigned char* data,
53
+ const unsigned char* data_end) {
54
+ TSequentialFileWriter* self = (TSequentialFileWriter*)stream->streamImport;
55
+ const size_t length = (size_t)(data_end - data);
56
+ /* patch_decompress_with_cache() only ever appends; anything else is a bug. */
57
+ if (writeToPos != self->writtenSize) {
58
+ return hpatch_FALSE;
59
+ }
60
+ if (fwrite(data, 1, length, self->file) != length) {
61
+ return hpatch_FALSE;
62
+ }
63
+ self->writtenSize += length;
64
+ return hpatch_TRUE;
65
+ }
66
+
67
+ JNIEXPORT jint JNICALL
68
+ Java_com_microsoft_codepush_react_HDiffPatchNative_applyPatch(JNIEnv* env,
69
+ jclass clazz,
70
+ jbyteArray base,
71
+ jbyteArray patch,
72
+ jstring outputPath,
73
+ jlong expectedTargetSize) {
74
+ jbyte* baseData = NULL;
75
+ jbyte* patchData = NULL;
76
+ const char* outputPathChars = NULL;
77
+ unsigned char* cache = NULL;
78
+ jsize baseSize = 0;
79
+ jsize patchSize = 0;
80
+ hpatch_compressedDiffInfo diffInfo;
81
+ hpatch_TDecompress decompressor;
82
+ hpatch_TStreamInput baseStream;
83
+ hpatch_TStreamInput patchStream;
84
+ hpatch_TStreamOutput targetStream;
85
+ TSequentialFileWriter writer;
86
+ hpatch_BOOL applied;
87
+ int result = RESULT_OK;
88
+
89
+ (void)clazz;
90
+
91
+ if ((base == NULL) || (patch == NULL) || (outputPath == NULL) || (expectedTargetSize <= 0)) {
92
+ return RESULT_INVALID_ARGUMENT;
93
+ }
94
+
95
+ baseSize = (*env)->GetArrayLength(env, base);
96
+ patchSize = (*env)->GetArrayLength(env, patch);
97
+ baseData = (*env)->GetByteArrayElements(env, base, NULL);
98
+ patchData = (*env)->GetByteArrayElements(env, patch, NULL);
99
+ outputPathChars = (*env)->GetStringUTFChars(env, outputPath, NULL);
100
+ if ((baseData == NULL) || (patchData == NULL) || (outputPathChars == NULL)) {
101
+ LOGE("[CodePush] out of memory while reading the binary patch inputs");
102
+ result = RESULT_IO_ERROR;
103
+ goto cleanup;
104
+ }
105
+
106
+ binarypatch_zstd_decompressor_init(&decompressor);
107
+
108
+ if (!getCompressedDiffInfo_mem(&diffInfo, (const unsigned char*)patchData,
109
+ (const unsigned char*)patchData + patchSize)) {
110
+ LOGE("[CodePush] the binary patch header could not be read");
111
+ result = RESULT_INVALID_HEADER;
112
+ goto cleanup;
113
+ }
114
+ if ((strlen(diffInfo.compressType) > 0) && !decompressor.is_can_open(diffInfo.compressType)) {
115
+ LOGE("[CodePush] the binary patch uses an unsupported codec: %s", diffInfo.compressType);
116
+ result = RESULT_UNSUPPORTED_COMPRESSION;
117
+ goto cleanup;
118
+ }
119
+ if (diffInfo.oldDataSize != (hpatch_StreamPos_t)baseSize) {
120
+ LOGE("[CodePush] the binary patch expects a %llu byte base bundle, this one is %llu bytes",
121
+ (unsigned long long)diffInfo.oldDataSize, (unsigned long long)baseSize);
122
+ result = RESULT_SIZE_MISMATCH;
123
+ goto cleanup;
124
+ }
125
+ if (diffInfo.newDataSize != (hpatch_StreamPos_t)expectedTargetSize) {
126
+ LOGE("[CodePush] the binary patch produces %llu bytes, the manifest promises %llu",
127
+ (unsigned long long)diffInfo.newDataSize, (unsigned long long)expectedTargetSize);
128
+ result = RESULT_SIZE_MISMATCH;
129
+ goto cleanup;
130
+ }
131
+
132
+ cache = (unsigned char*)malloc(APPLY_CACHE_SIZE);
133
+ if (cache == NULL) {
134
+ LOGE("[CodePush] out of memory while allocating the binary patch cache");
135
+ result = RESULT_IO_ERROR;
136
+ goto cleanup;
137
+ }
138
+
139
+ writer.file = fopen(outputPathChars, "wb");
140
+ writer.writtenSize = 0;
141
+ if (writer.file == NULL) {
142
+ LOGE("[CodePush] the restored bundle could not be opened for writing");
143
+ result = RESULT_IO_ERROR;
144
+ goto cleanup;
145
+ }
146
+
147
+ mem_as_hStreamInput(&baseStream, (const unsigned char*)baseData, (const unsigned char*)baseData + baseSize);
148
+ mem_as_hStreamInput(&patchStream, (const unsigned char*)patchData, (const unsigned char*)patchData + patchSize);
149
+ memset(&targetStream, 0, sizeof(targetStream));
150
+ targetStream.streamImport = &writer;
151
+ targetStream.streamSize = diffInfo.newDataSize;
152
+ targetStream.write = _write_sequential;
153
+
154
+ applied = patch_decompress_with_cache(&targetStream, &baseStream, &patchStream, &decompressor,
155
+ cache, cache + APPLY_CACHE_SIZE);
156
+ if (fclose(writer.file) != 0) {
157
+ LOGE("[CodePush] the restored bundle could not be flushed to disk");
158
+ result = RESULT_IO_ERROR;
159
+ goto cleanup;
160
+ }
161
+ if (!applied) {
162
+ LOGE("[CodePush] applying the binary patch failed (decError=%d)", (int)decompressor.decError);
163
+ result = RESULT_APPLY_FAILED;
164
+ goto cleanup;
165
+ }
166
+
167
+ cleanup:
168
+ free(cache);
169
+ if (outputPathChars != NULL) {
170
+ (*env)->ReleaseStringUTFChars(env, outputPath, outputPathChars);
171
+ }
172
+ /* JNI_ABORT: neither array is written to, so nothing has to be copied back. */
173
+ if (patchData != NULL) {
174
+ (*env)->ReleaseByteArrayElements(env, patch, patchData, JNI_ABORT);
175
+ }
176
+ if (baseData != NULL) {
177
+ (*env)->ReleaseByteArrayElements(env, base, baseData, JNI_ABORT);
178
+ }
179
+
180
+ return (jint)result;
181
+ }