react-native-bspatch 1.0.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/LICENSE +21 -0
  2. package/README.md +471 -0
  3. package/android/build.gradle +100 -0
  4. package/android/src/main/AndroidManifest.xml +3 -0
  5. package/android/src/main/java/vn/reactnativebspatch/BsPatchModule.kt +139 -0
  6. package/android/src/main/java/vn/reactnativebspatch/BsPatchPackage.kt +16 -0
  7. package/android/src/main/java/vn/reactnativebspatch/OtaBundleResolver.kt +227 -0
  8. package/cpp/CMakeLists.txt +43 -0
  9. package/cpp/bsdiff/bspatch.c +194 -0
  10. package/cpp/bsdiff/bspatch.h +41 -0
  11. package/cpp/bsdiff/bspatch_wrapper.c +150 -0
  12. package/cpp/bsdiff/bspatch_wrapper.h +6 -0
  13. package/cpp/bspatch_jni.cpp +41 -0
  14. package/cpp/bzip2/LICENSE +42 -0
  15. package/cpp/bzip2/blocksort.c +1094 -0
  16. package/cpp/bzip2/bzlib.c +1572 -0
  17. package/cpp/bzip2/bzlib.h +282 -0
  18. package/cpp/bzip2/bzlib_private.h +509 -0
  19. package/cpp/bzip2/compress.c +672 -0
  20. package/cpp/bzip2/crctable.c +104 -0
  21. package/cpp/bzip2/decompress.c +652 -0
  22. package/cpp/bzip2/huffman.c +205 -0
  23. package/cpp/bzip2/randtable.c +84 -0
  24. package/ios/OtaBundleResolver.swift +241 -0
  25. package/ios/RNBsPatch.h +8 -0
  26. package/ios/RNBsPatch.mm +198 -0
  27. package/lib/commonjs/NativeBsPatch.js +9 -0
  28. package/lib/commonjs/NativeBsPatch.js.map +1 -0
  29. package/lib/commonjs/crypto-js.d.js +2 -0
  30. package/lib/commonjs/crypto-js.d.js.map +1 -0
  31. package/lib/commonjs/index.js +90 -0
  32. package/lib/commonjs/index.js.map +1 -0
  33. package/lib/commonjs/ota/OtaProvider.js +306 -0
  34. package/lib/commonjs/ota/OtaProvider.js.map +1 -0
  35. package/lib/commonjs/ota/otaAssetResolver.js +145 -0
  36. package/lib/commonjs/ota/otaAssetResolver.js.map +1 -0
  37. package/lib/commonjs/ota/otaCore.js +736 -0
  38. package/lib/commonjs/ota/otaCore.js.map +1 -0
  39. package/lib/commonjs/ota/otaLogger.js +64 -0
  40. package/lib/commonjs/ota/otaLogger.js.map +1 -0
  41. package/lib/commonjs/ota/otaMetadata.js +138 -0
  42. package/lib/commonjs/ota/otaMetadata.js.map +1 -0
  43. package/lib/commonjs/ota/storage/CustomStorage.js +140 -0
  44. package/lib/commonjs/ota/storage/CustomStorage.js.map +1 -0
  45. package/lib/commonjs/ota/storage/GitHubReleaseStorage.js +96 -0
  46. package/lib/commonjs/ota/storage/GitHubReleaseStorage.js.map +1 -0
  47. package/lib/commonjs/ota/types.js +39 -0
  48. package/lib/commonjs/ota/types.js.map +1 -0
  49. package/lib/commonjs/package.json +1 -0
  50. package/lib/module/NativeBsPatch.js +5 -0
  51. package/lib/module/NativeBsPatch.js.map +1 -0
  52. package/lib/module/crypto-js.d.js +2 -0
  53. package/lib/module/crypto-js.d.js.map +1 -0
  54. package/lib/module/index.js +23 -0
  55. package/lib/module/index.js.map +1 -0
  56. package/lib/module/ota/OtaProvider.js +299 -0
  57. package/lib/module/ota/OtaProvider.js.map +1 -0
  58. package/lib/module/ota/otaAssetResolver.js +138 -0
  59. package/lib/module/ota/otaAssetResolver.js.map +1 -0
  60. package/lib/module/ota/otaCore.js +725 -0
  61. package/lib/module/ota/otaCore.js.map +1 -0
  62. package/lib/module/ota/otaLogger.js +59 -0
  63. package/lib/module/ota/otaLogger.js.map +1 -0
  64. package/lib/module/ota/otaMetadata.js +127 -0
  65. package/lib/module/ota/otaMetadata.js.map +1 -0
  66. package/lib/module/ota/storage/CustomStorage.js +134 -0
  67. package/lib/module/ota/storage/CustomStorage.js.map +1 -0
  68. package/lib/module/ota/storage/GitHubReleaseStorage.js +90 -0
  69. package/lib/module/ota/storage/GitHubReleaseStorage.js.map +1 -0
  70. package/lib/module/ota/types.js +38 -0
  71. package/lib/module/ota/types.js.map +1 -0
  72. package/lib/typescript/NativeBsPatch.d.ts +39 -0
  73. package/lib/typescript/NativeBsPatch.d.ts.map +1 -0
  74. package/lib/typescript/index.d.ts +15 -0
  75. package/lib/typescript/index.d.ts.map +1 -0
  76. package/lib/typescript/ota/OtaProvider.d.ts +10 -0
  77. package/lib/typescript/ota/OtaProvider.d.ts.map +1 -0
  78. package/lib/typescript/ota/otaAssetResolver.d.ts +15 -0
  79. package/lib/typescript/ota/otaAssetResolver.d.ts.map +1 -0
  80. package/lib/typescript/ota/otaCore.d.ts +109 -0
  81. package/lib/typescript/ota/otaCore.d.ts.map +1 -0
  82. package/lib/typescript/ota/otaLogger.d.ts +44 -0
  83. package/lib/typescript/ota/otaLogger.d.ts.map +1 -0
  84. package/lib/typescript/ota/otaMetadata.d.ts +46 -0
  85. package/lib/typescript/ota/otaMetadata.d.ts.map +1 -0
  86. package/lib/typescript/ota/storage/CustomStorage.d.ts +12 -0
  87. package/lib/typescript/ota/storage/CustomStorage.d.ts.map +1 -0
  88. package/lib/typescript/ota/storage/GitHubReleaseStorage.d.ts +20 -0
  89. package/lib/typescript/ota/storage/GitHubReleaseStorage.d.ts.map +1 -0
  90. package/lib/typescript/ota/types.d.ts +191 -0
  91. package/lib/typescript/ota/types.d.ts.map +1 -0
  92. package/package.json +89 -0
  93. package/react-native-bspatch.podspec +46 -0
  94. package/src/NativeBsPatch.ts +52 -0
  95. package/src/crypto-js.d.ts +1 -0
  96. package/src/index.ts +29 -0
  97. package/src/ota/OtaProvider.tsx +399 -0
  98. package/src/ota/otaAssetResolver.ts +182 -0
  99. package/src/ota/otaCore.ts +963 -0
  100. package/src/ota/otaLogger.ts +72 -0
  101. package/src/ota/otaMetadata.ts +149 -0
  102. package/src/ota/storage/CustomStorage.ts +143 -0
  103. package/src/ota/storage/GitHubReleaseStorage.ts +98 -0
  104. package/src/ota/types.ts +211 -0
  105. package/tools/bsdiff +0 -0
  106. package/tools/bsdiff.c +445 -0
  107. package/tools/bsdiff.h +45 -0
  108. package/tools/build_ota.sh +457 -0
  109. package/tools/setup_project.js +273 -0
  110. package/tools/update_registry.js +145 -0
  111. package/tools/upload_ota_custom.sh +138 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hung Bui
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,471 @@
1
+ # react-native-bspatch
2
+
3
+ Premium OTA update solution for React Native using **TurboModules** + **bsdiff binary patching**. Delivers significantly smaller delta updates compared to full bundle replacement.
4
+
5
+ ## Features
6
+
7
+ - **Binary delta patches** — only download the diff between old and new bundle (typically 5–30% of full bundle size)
8
+ - **Full-bundle fallback** — if patch fails, automatically downloads the complete bundle
9
+ - **Atomic bundle swap** — never leaves the app in a broken state
10
+ - **Crash detection + auto-rollback** — native-side protection runs **before** JS boots; if the updated bundle crashes, the app automatically rolls back to the previous version
11
+ - **Integrity verification** — SHA-256 hash check on both source bundle and result bundle
12
+ - **HMAC signature verification** — optional patch signature check to prevent MITM attacks
13
+ - **Gzip decompression** — native gzip decompression on both Android and iOS (when `compressed: true`)
14
+ - **Content-addressed asset store** — images/assets downloaded per-version and redirected via `setCustomSourceTransformer`
15
+ - **Structured telemetry** — all OTA events forwarded to your analytics via `onEvent` callback
16
+ - **Version history** — keeps up to 3 previous versions for safe multi-step rollback
17
+ - **New Architecture ready** — built with TurboModules (no legacy Bridge)
18
+
19
+ ---
20
+
21
+ ## Requirements
22
+
23
+ - React Native **0.76+** (New Architecture enabled by default)
24
+ - `react-native-blob-util` — required peer dependency for file I/O
25
+
26
+ ---
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install react-native-bspatch react-native-blob-util
32
+ cd ios && pod install
33
+ ```
34
+
35
+ ### Android
36
+
37
+ CMake will be invoked automatically via Gradle. Ensure `android/app/build.gradle` includes:
38
+
39
+ ```groovy
40
+ android {
41
+ defaultConfig {
42
+ ndk {
43
+ abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ### iOS
50
+
51
+ No additional steps — `react-native-bspatch` and its `SSZipArchive` dependency are auto-linked via CocoaPods.
52
+
53
+ ---
54
+
55
+ ## Quick Start
56
+
57
+ ### 1. Wrap your app with `OtaProvider`
58
+
59
+ ```tsx
60
+ import { OtaProvider } from 'react-native-bspatch';
61
+
62
+ const config = {
63
+ appVersion: '1.0.0', // matches versionName in build.gradle
64
+ baseAppVersion: 1, // OTA version baked into the first APK/IPA (usually 1)
65
+ updateMode: OtaUpdateMode.MANUAL,
66
+ customServer: {
67
+ baseUrl: 'https://ota.yourcompany.com',
68
+ username: 'your-user',
69
+ password: 'your-pass',
70
+ channel: 'production',
71
+ baseFolder: 'bspatch',
72
+ useMinio: false,
73
+ },
74
+ };
75
+
76
+ export default function App() {
77
+ return (
78
+ <OtaProvider config={config}>
79
+ {/* rest of your app */}
80
+ </OtaProvider>
81
+ );
82
+ }
83
+ ```
84
+
85
+ ### 2. Check for updates and apply
86
+
87
+ ```tsx
88
+ import { useOta, OtaUpdateMode, OtaStatus } from 'react-native-bspatch';
89
+
90
+ function UpdateBanner() {
91
+ const { status, currentVersion, latestVersion, checkUpdate, applyUpdate } = useOta();
92
+
93
+ if (status === OtaStatus.UPDATE_AVAILABLE) {
94
+ return (
95
+ <Button
96
+ title={`Update to V${latestVersion} →`}
97
+ onPress={applyUpdate}
98
+ />
99
+ );
100
+ }
101
+
102
+ return <Text>Already up to date (V{currentVersion})</Text>;
103
+ }
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Configuration Reference
109
+
110
+ ```ts
111
+ type OtaConfig = {
112
+ /** Current app versionName, e.g. "1.0.0" */
113
+ appVersion: string;
114
+
115
+ /**
116
+ * OTA version number baked into the APK/IPA at build time.
117
+ * Must match the version field in your server's ota_registry.json.
118
+ * Usually starts at 1.
119
+ */
120
+ baseAppVersion: number;
121
+
122
+ /** Fallback patches used when manifest fetch fails */
123
+ bundledPatches?: OtaPatch[];
124
+
125
+ /**
126
+ * When to check / apply updates.
127
+ * - MANUAL: call checkUpdate() / applyUpdate() manually
128
+ * - ON_APP_START: check automatically on every cold start
129
+ * - ON_APP_FOREGROUND: check when app returns to foreground
130
+ * - BACKGROUND_DOWNLOAD: check on start; download in background (coming soon)
131
+ */
132
+ updateMode?: OtaUpdateMode;
133
+
134
+ /** Max rollback attempts before resetting to factory build (default: 3) */
135
+ maxRollback?: number;
136
+
137
+ /** Network retry and timeout settings */
138
+ network?: {
139
+ retry?: number; // max retry attempts (default: 3)
140
+ timeoutMs?: number; // request timeout in ms (default: 30000)
141
+ wifiOnly?: boolean; // TODO (default: false)
142
+ };
143
+
144
+ /**
145
+ * Optional telemetry callback — all OTA lifecycle events are forwarded here.
146
+ * Use it to pipe events to your analytics service.
147
+ */
148
+ onEvent?: (entry: OtaLogEntry) => void;
149
+
150
+ /**
151
+ * Base64-encoded public key for HMAC-SHA256 patch signature verification.
152
+ * Recommended for production to prevent MITM attacks.
153
+ */
154
+ publicKey?: string;
155
+
156
+ /** Custom storage provider. Omit to use CustomStorageProvider. */
157
+ storage?: OtaStorageProvider;
158
+
159
+ /** Custom server configuration (used by CustomStorageProvider) */
160
+ customServer?: {
161
+ baseUrl: string;
162
+ username: string;
163
+ password: string;
164
+ channel: string;
165
+ baseFolder?: string; // default: 'bspatch'
166
+ useMinio?: boolean;
167
+ };
168
+ };
169
+ ```
170
+
171
+ ### OtaUpdateMode values
172
+
173
+ ```ts
174
+ enum OtaUpdateMode {
175
+ MANUAL = 'MANUAL', // No auto behavior
176
+ ON_APP_START = 'ON_APP_START', // Check + apply on every cold start
177
+ ON_APP_FOREGROUND = 'ON_APP_FOREGROUND', // Check when app returns to foreground
178
+ BACKGROUND_DOWNLOAD = 'BACKGROUND_DOWNLOAD', // TODO: download in background
179
+ }
180
+ ```
181
+
182
+ ### OtaStatus values
183
+
184
+ | Status | Meaning |
185
+ |---|---|
186
+ | `IDLE` | Initial state |
187
+ | `CHECKING` | Fetching manifest |
188
+ | `UPDATE_AVAILABLE` | A newer version exists |
189
+ | `UP_TO_DATE` | Already on the latest version |
190
+ | `DOWNLOADING` | Downloading patch or full bundle |
191
+ | `APPLYING` | Applying binary patch or decompressing |
192
+ | `SYNCING_ASSETS` | Downloading version-specific assets |
193
+ | `SUCCESS` | Update applied; app is reloading |
194
+ | `ERROR` | Update failed |
195
+
196
+ ---
197
+
198
+ ## Server-Side Manifest Format
199
+
200
+ Your OTA server must expose a `ota_registry.json` at the configured path (e.g. `https://ota.yourcompany.com/bspatch/ota_registry.json`):
201
+
202
+ ```json
203
+ {
204
+ "apps": {
205
+ "1.0.0": {
206
+ "flavors": {
207
+ "production": {
208
+ "patches": [
209
+ {
210
+ "id": "v1-to-v2",
211
+ "label": "Feature release",
212
+ "appVersion": "1.0.0",
213
+ "filename": "v1-to-v2.patch",
214
+ "fromVersion": 1,
215
+ "toVersion": 2,
216
+ "baseHash": "sha256_of_v1_bundle",
217
+ "bundleHash": "sha256_of_v2_bundle",
218
+ "platform": "android",
219
+ "compressed": false,
220
+ "mandatory": false,
221
+ "fullBundleUrl": "v2-full.zip"
222
+ }
223
+ ]
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+ ```
230
+
231
+ > **Important:** `baseHash` must be the exact SHA-256 of the bundle that users currently have installed (after OTA patching, not just the APK bundle). The most reliable way to get this is to record it **after** each successful patch application, store it server-side, and reference it as `baseHash` for the next patch in the chain.
232
+
233
+ ---
234
+
235
+ ## Bundle Resolution & Rollback
236
+
237
+ ### How it works
238
+
239
+ The native `OtaBundleResolver` runs **before JS boots** on every cold start:
240
+
241
+ ```
242
+ 1. Read metadata.json
243
+ 2. If lastLaunchSuccessful == false → rollback to previous version
244
+ 3. If currentVersion == baseAppVersion → use APK/IPA bundle
245
+ 4. Otherwise → load OTA bundle from ota/v{version}/index.android.bundle (or main.jsbundle)
246
+ 5. Mark lastLaunchSuccessful = false (will reset to true when JS calls markAsHealthy)
247
+ 6. JS boots → runs bootstrap → calls markAsHealthy()
248
+ 7. If JS never calls markAsHealthy() (crash) → next boot sees false → rolls back
249
+ ```
250
+
251
+ This means crash protection is **native-side and zero-cost** — it doesn't require JS to run first.
252
+
253
+ ---
254
+
255
+ ## Custom Storage Provider
256
+
257
+ The library ships with `CustomStorageProvider` for MinIO-based private servers. To use a different backend, implement the interface:
258
+
259
+ ```ts
260
+ interface OtaStorageProvider {
261
+ /** Fetch the list of available patches */
262
+ fetchManifest(config: OtaConfig): Promise<OtaPatch[]>;
263
+
264
+ /** Get a signed download URL for a patch or full bundle */
265
+ getUpdateFileUrl(filename: string, config: OtaConfig): Promise<string>;
266
+
267
+ /** Get a signed download URL for a content-addressed asset */
268
+ getAssetUrl(hash: string, ext: string, config: OtaConfig): Promise<string>;
269
+ }
270
+ ```
271
+
272
+ Pass your implementation via `config.storage`.
273
+
274
+ ---
275
+
276
+ ## API Reference
277
+
278
+ ### `useOta()` — React hook (inside `OtaProvider`)
279
+
280
+ ```ts
281
+ const {
282
+ status, // OtaStatus
283
+ message, // Human-readable status message
284
+ progress, // 0–100 download/progress percentage
285
+ currentVersion, // OTA version number currently running
286
+ latestVersion, // Highest version available on server
287
+ pendingCount, // Number of pending updates
288
+ isChecking, // True while fetching manifest
289
+ metrics, // OtaMetrics | null — timing/size data after update
290
+ applyUpdate, // () => Promise<void>
291
+ resetToFactory, // () => Promise<void> — wipe OTA state, reload APK/IPA
292
+ markAsHealthy, // () => Promise<void> — manually mark launch OK
293
+ checkUpdate, // () => Promise<void>
294
+ } = useOta();
295
+ ```
296
+
297
+ ### `applyPatch(oldPath, newPath, patchPath)` — Low-level
298
+
299
+ Applies a bsdiff patch directly. Used internally; available for advanced use cases.
300
+
301
+ ```ts
302
+ import { applyPatch } from 'react-native-bspatch';
303
+ await applyPatch('/path/to/old.bundle', '/path/to/new.bundle', '/path/to/patch.patch');
304
+ ```
305
+
306
+ ### `markAsHealthy()` / `reloadBundle()` — Advanced
307
+
308
+ These are called automatically by `OtaProvider`. Only call manually if you manage OTA outside the provider.
309
+
310
+ ---
311
+
312
+ ## OTA Build Process
313
+
314
+ The library ships with an **automated build pipeline** (`tools/build_ota.sh`) that handles the entire release workflow: bundling, Hermes compilation, asset discovery, bsdiff patch generation, and server upload.
315
+
316
+ ### Project Scripts
317
+
318
+ Add these to your `package.json` (or use the existing ones in this project):
319
+
320
+ ```json
321
+ {
322
+ "scripts": {
323
+ "react-native-bspatch": "bash ./src/native_modules/react-native-bspatch/tools/build_ota.sh",
324
+ "ota:setup": "yarn react-native-bspatch setup",
325
+ "ota:init": "yarn react-native-bspatch base",
326
+ "ota:build": "yarn react-native-bspatch",
327
+ "ota:clean": "yarn react-native-bspatch reset"
328
+ }
329
+ }
330
+ ```
331
+
332
+ ### Workflow
333
+
334
+ ```
335
+ ota:setup ──► ota:init ──► ota:build ──► (ota:build ◄── repeat for each release)
336
+ │ │ │
337
+ │ │ └── 1. Bundle JS → Hermes bytecode (.hbc)
338
+ │ │ 2. Compare assets → generate asset-manifest-{ver}.json
339
+ │ │ 3. bsdiff against previous bundle → .patch files
340
+ │ │ 4. Upload to server + update ota_registry.json
341
+ │ │ 5. Write otaPatches.generated.ts for bundled patches fallback
342
+ │ │
343
+ │ └── First build only: creates base bundle from APK/IPA.
344
+ │ Stores base asset manifest so subsequent builds know
345
+ │ which assets are already in the APK and skip them.
346
+ │
347
+ └── Patches native integration into project files:
348
+ • iOS: AppDelegate.swift — adds bundleURL() override
349
+ • iOS: project.pbxproj — adds build-phase script to sync bundle → ios/output/
350
+ • Android: MainApplication.kt — adds OtaBundleResolver.resolve() call
351
+ Idempotent: safe to run multiple times; skips already-patched files.
352
+ ```
353
+
354
+ ### Build Commands
355
+
356
+ | Command | Action |
357
+ |---|---|
358
+ | `yarn ota:setup` | Inject OTA integration into AppDelegate.swift, MainApplication.kt, project.pbxproj. Run once per project. |
359
+ | `yarn ota:init` | First release: build base APK/IPA + bundle + base asset manifest. Creates `ota_registry.json`. |
360
+ | `yarn ota:build` | Subsequent releases: generate incremental .patch from previous bundle, upload to server, update registry. |
361
+ | `yarn ota:build --platform android` | Build Android only |
362
+ | `yarn ota:build --platform ios` | Build iOS only |
363
+ | `yarn ota:build --flavor staging` | Build with flavor "staging" (default: "dev") |
364
+ | `yarn ota:clean` | Wipe `ota_registry.json` + output files. Preserves base asset manifests so `ota:build` still works after clean. |
365
+
366
+ ### How It Works
367
+
368
+ 1. **Bundle** — `npx react-native bundle` generates Hermes bytecode (`.hbc`). iOS base build uses the Xcode-compiled bundle to guarantee byte-exact match with the IPA.
369
+ 2. **Assets** — Compares against `base-assets_{platform}_{flavor}.json` (saved at `ota:init`). Unchanged assets are skipped; changed/new assets are copied to `asset-store/` and included in the per-version manifest.
370
+ 3. **Patch** — `bsdiff` generates a binary delta from the previous bundle to the new one. The diff is typically 5–30% the size of a full bundle.
371
+ 4. **Registry** — Updates `ota_registry.json` with new `baseHash`, `bundleHash`, and asset manifest URL for each platform/channel.
372
+ 5. **Upload** — Uploads bundles, patches, and asset store to the configured server.
373
+
374
+ ### One-time Setup
375
+
376
+ ```bash
377
+ # 1. Add the npm scripts to package.json (or run directly)
378
+ bash ./src/native_modules/react-native-bspatch/tools/build_ota.sh setup
379
+
380
+ # 2. Initialize the first release (creates base APK + registry)
381
+ yarn ota:init
382
+ # Or per platform:
383
+ yarn ota:init --platform android # Android only
384
+ yarn ota:init --platform ios # iOS only
385
+
386
+ # 3. For each subsequent release:
387
+ yarn ota:build
388
+ ```
389
+
390
+ ### Environment Variables
391
+
392
+ The build script requires server credentials. You can provide them via:
393
+
394
+ **Option 1: `.env` file (recommended)**
395
+
396
+ Create a `.env` file in your **React Native project root** (not in the module directory):
397
+
398
+ ```bash
399
+ # In your project root (e.g., /path/to/YourApp/)
400
+ OTA_USER=your-username
401
+ OTA_PASSWORD=your-password
402
+ ```
403
+
404
+ The build script automatically loads `.env` from the project root.
405
+
406
+ **Option 2: Export environment variables**
407
+
408
+ ```bash
409
+ export OTA_USER="your-username"
410
+ export OTA_PASSWORD="your-password"
411
+ yarn ota:build
412
+ ```
413
+
414
+ | Variable | Required | Default | Description |
415
+ |----------|----------|---------|-------------|
416
+ | `OTA_USER` | Yes | - | Server username |
417
+ | `OTA_PASSWORD` | Yes | - | Server password |
418
+ | `OTA_BASE_URL` | No | `https://sync-server.osp.com.vn` | Server base URL |
419
+ | `OTA_FOLDER` | No | `congplqg` | Base folder path |
420
+
421
+ > **Security Warning:** Never commit credentials to source control. The `.env` file is gitignored.
422
+
423
+ ---
424
+
425
+ ## Security Notes
426
+
427
+ - **PATCH verification is STRONGLY recommended in production.** Enable by setting `config.publicKey`. Without it, a MITM could serve a malicious patch.
428
+ - Signature verification uses SHA-256 hash comparison. For higher security, consider RSA/ECDSA signature verification.
429
+ - `markAsHealthy()` is called automatically by `OtaProvider`. If you use a custom integration, you **must** call it after successful JS bootstrap, or the native resolver will rollback on the next cold start.
430
+
431
+ ---
432
+
433
+ ## File Structure
434
+
435
+ ```
436
+ src/native_modules/react-native-bspatch/
437
+ ├── index.ts # Public JS entry point
438
+ ├── src/
439
+ │ ├── NativeBsPatch.ts # TurboModule TypeScript wrapper
440
+ │ └── ota/
441
+ │ ├── otaCore.ts # Core pipeline: download → patch → fallback
442
+ │ ├── otaMetadata.ts # Metadata load/save with race-condition guard
443
+ │ ├── otaLogger.ts # Structured event telemetry
444
+ │ ├── otaAssetResolver.ts # Image source interceptor + asset store
445
+ │ ├── OtaProvider.tsx # React Context provider + auto-update logic
446
+ │ ├── types.ts # All TypeScript type definitions
447
+ │ └── storage/
448
+ │ ├── CustomStorage.ts # MinIO-based storage implementation
449
+ │ └── GitHubReleaseStorage.ts # GitHub Releases storage (public / token-auth)
450
+ ├── android/src/main/java/vn/reactnativebspatch/
451
+ │ ├── BsPatchModule.kt # Native: applyPatch, unzip, extract, gzip
452
+ │ └── OtaBundleResolver.kt # Native: bundle resolution + rollback
453
+ ├── ios/
454
+ │ ├── RNBsPatch.mm # Native iOS bridge + gzip decompress
455
+ │ └── OtaBundleResolver.swift # Native iOS bundle resolution + rollback
456
+ ├── cpp/
457
+ │ ├── CMakeLists.txt # CMake build for bspatch C library
458
+ │ └── bsdiff/ # bsdiff/bspatch source
459
+ └── tools/
460
+ ├── build_ota.sh # Main build pipeline: bundle → patch → upload
461
+ ├── setup_project.js # Idempotent project integration injector
462
+ └── update_registry.js # Updates ota_registry.json after each build
463
+ ```
464
+
465
+ ---
466
+
467
+ ## Known Limitations
468
+
469
+ - `BACKGROUND_DOWNLOAD` mode is a stub — auto-update currently works in `MANUAL`, `ON_APP_START`, and `ON_APP_FOREGROUND` modes only
470
+ - Patch chain must be **sequential** (e.g. V1→V2→V3). The library resolves the best available direct patch for your current version; it does not chain multiple patches in a single update
471
+ - Asset redirection via `setCustomSourceTransformer` requires React Native **0.69+**
@@ -0,0 +1,100 @@
1
+ buildscript {
2
+ def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["BsPatch_kotlinVersion"]
3
+
4
+ repositories {
5
+ google()
6
+ mavenCentral()
7
+ }
8
+
9
+ dependencies {
10
+ classpath "com.android.tools.build:gradle:9.0.0"
11
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12
+ }
13
+ }
14
+
15
+ def isNewArchitectureEnabled() {
16
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
17
+ }
18
+
19
+ apply plugin: "com.android.library"
20
+ apply plugin: "kotlin-android"
21
+
22
+ if (isNewArchitectureEnabled()) {
23
+ apply plugin: "com.facebook.react"
24
+ }
25
+
26
+ def getExtOrDefault(name) {
27
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["BsPatch_" + name]
28
+ }
29
+
30
+ def getExtOrIntegerDefault(name) {
31
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["BsPatch_" + name]).toInteger()
32
+ }
33
+
34
+ android {
35
+ namespace "vn.reactnativebspatch"
36
+
37
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
38
+
39
+ defaultConfig {
40
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
41
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
42
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
43
+ externalNativeBuild {
44
+ cmake {
45
+ cppFlags "-frtti -fexceptions -Wall -fstack-protector-all"
46
+ abiFilters "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
47
+ }
48
+ }
49
+ }
50
+
51
+ externalNativeBuild {
52
+ cmake {
53
+ path "../cpp/CMakeLists.txt"
54
+ }
55
+ }
56
+
57
+ buildTypes {
58
+ release {
59
+ minifyEnabled false
60
+ }
61
+ }
62
+
63
+ compileOptions {
64
+ sourceCompatibility JavaVersion.VERSION_1_8
65
+ targetCompatibility JavaVersion.VERSION_1_8
66
+ }
67
+
68
+ sourceSets {
69
+ main {
70
+ if (isNewArchitectureEnabled()) {
71
+ java.srcDirs += [
72
+ "src/newarch",
73
+ "${project.buildDir}/generated/source/codegen/java"
74
+ ]
75
+ } else {
76
+ java.srcDirs += ["src/oldarch"]
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ repositories {
83
+ mavenCentral()
84
+ google()
85
+ }
86
+
87
+ def kotlin_version = getExtOrDefault("kotlinVersion")
88
+
89
+ dependencies {
90
+ implementation "com.facebook.react:react-native:+"
91
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
92
+ }
93
+
94
+ if (isNewArchitectureEnabled()) {
95
+ react {
96
+ jsRootDir = file("../src/")
97
+ libraryName = "RNBsPatchSpec"
98
+ codegenJavaPackageName = "vn.reactnativebspatch"
99
+ }
100
+ }
@@ -0,0 +1,3 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="vn.reactnativebspatch">
3
+ </manifest>