const job = startPatch(oldPath, outputPath, patchPath, {
maxInputBytes: 64 * 1024 * 1024,
maxOutputBytes: 128 * 1024 * 1024,
@@ -257,7 +292,9 @@ Progress and cancel on native. AbortSignal on Web.
await job.result.finally(off);
diff --git a/README.md b/README.md index f94da73..78f84a9 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,101 @@ # react-native-bs-diff-patch -Create a compact binary patch from two versions of a file, then reconstruct the -new version from the old file and that patch. Android, iOS, and React Native Web -all use the compatible `ENDSLEY/BSDIFF43` wire format. - -[Documentation](https://bs-dff-patch.corerobin.com/docs/) · -[Playground](https://bs-dff-patch.corerobin.com/#playground) · -[中文说明](./README.zh-CN.md) · [npm](https://www.npmjs.com/package/react-native-bs-diff-patch) - -## Why use it? - -- **One patch format:** generate on one supported runtime and apply on another. -- **Both React Native architectures:** legacy bridge and TurboModule/New Architecture. -- **Responsive by default:** native work uses dedicated serial queues; Web work - reuses a module Worker and cached WebAssembly instance off the page thread. -- **Control expensive work:** native jobs expose progress, cooperative - cancellation, input/output limits, and atomic output; Web uses - `AbortSignal` and binary limits. -- **No Web service required:** the browser implementation is the same bundled C - core compiled to WebAssembly. - -| Runtime | Input model | Create a patch | Apply a patch | -| ------------ | ------------------ | -------------- | ------------- | -| Android, iOS | Absolute paths | `diff` | `patch` | -| Web | In-memory binaries | `diffBytes` | `patchBytes` | - -## Installation +
+
+
+
+
+ Turn two versions of a file into a compact binary patch, then reconstruct the new file from the old file plus that patch.
+ One compatible format across React Native Android, iOS, and Web.
+
+ Documentation · + Live Playground · + 中文说明 · + npm +
+ +## What does it do? + +Use it when your app already has an old version of a file and you want to move +to a new version without transporting the complete replacement file. + +| 1. Create the delta | 2. Deliver it your way | 3. Reconstruct the file | +| ------------------------------------------------------------ | ------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| Compare `old.bin` with `new.bin` and produce `update.patch`. | Send or store the patch with your existing CDN, API, or offline workflow. | Apply `update.patch` to `old.bin` and write the restored `new.bin`. | + +The library handles binary diffing and patching. Your application remains in +control of transport, authentication, integrity checks, and when an output +replaces live data. + +## Why this package? + +- **One wire format:** Android, iOS, and Web produce compatible + `ENDSLEY/BSDIFF43` patches. +- **Every current RN runtime:** legacy bridge and TurboModule/New Architecture + are both supported. +- **Native performance, browser reach:** JNI/ObjC++ use the bundled C core; + React Native Web runs that core as WebAssembly in a reusable module Worker. +- **Control expensive native work:** observe progress, cancel cooperatively, + cap input/output sizes, and avoid exposing partial output files. +- **No patch service required:** Web diffing and patching happen locally in the + browser. + +## Platform overview + +| | Android / iOS | React Native Web | +| -------------- | ------------------------------ | -------------------------------------------------- | +| Input | Absolute file paths | `ArrayBuffer`, typed arrays, `DataView`, or `Blob` | +| Basic API | `diff()` / `patch()` | `diffBytes()` / `patchBytes()` | +| Controlled API | `startDiff()` / `startPatch()` | `AbortSignal` and binary limits | +| Engine | Native C via JNI / ObjC++ | Same C core via WASM Worker | + +## Install ```sh npm install react-native-bs-diff-patch ``` -After adding the package, install iOS pods and rebuild the native app: +For iOS, install Pods and rebuild the native application: ```sh npx pod-install ``` -React Native autolinking handles native registration. A Metro reload alone is -not enough after adding a native dependency. +React Native autolinking handles native registration. Adding a native module +requires a native rebuild; a Metro reload is not enough. -## Native quick start +## Native: first round trip -The native API works with absolute file paths. Use the filesystem library -already present in your app to select a writable cache directory. +Native APIs use absolute paths. Pick unique output paths in a writable cache or +documents directory through the filesystem library already used by your app. ```ts import { diff, patch } from 'react-native-bs-diff-patch'; -type NativeRoundTripOptions = { - oldFilePath: string; - newFilePath: string; - cacheDirectory: string; -}; - -export async function nativeRoundTrip({ - oldFilePath, - newFilePath, - cacheDirectory, -}: NativeRoundTripOptions) { - const runId = Date.now(); - const patchPath = `${cacheDirectory}/update-${runId}.patch`; - const restoredPath = `${cacheDirectory}/restored-${runId}.bin`; - - await diff(oldFilePath, newFilePath, patchPath); - await patch(oldFilePath, restoredPath, patchPath); - - return { patchPath, restoredPath }; -} +const patchPath = `${cacheDirectory}/content-v2.patch`; +const restoredPath = `${cacheDirectory}/content-v2.restored`; + +await diff(oldFilePath, newFilePath, patchPath); +await patch(oldFilePath, restoredPath, patchPath); ``` -Output paths must not exist, all paths in one call must be different, and the -required input files must already exist. Both functions resolve to `0` on -success. +Input files must already exist. Output paths must not exist, and all paths in a +single call must be different. Both functions resolve to `0` on success. + +### Progress, cancellation, and limits -Use the job API when the operation needs progress, cancellation, or resource -bounds: +Use the job API for work that needs lifecycle control: ```ts import { startPatch } from 'react-native-bs-diff-patch'; @@ -84,47 +104,41 @@ const job = startPatch(oldPath, outputPath, patchPath, { maxInputBytes: 64 * 1024 * 1024, maxOutputBytes: 128 * 1024 * 1024, }); + const unsubscribe = job.onProgress(({ phase, progress }) => { renderProgress(phase, progress); }); try { await job.result; + // await job.cancel(); // cancel from your UI when needed } finally { unsubscribe(); } ``` -## React Native Web quick start +## Web: first round trip ```ts import { diffBytes, patchBytes } from 'react-native-bs-diff-patch'; -export async function webRoundTrip( - oldFile: File, - newFile: File, - signal?: AbortSignal -) { - const oldData = await oldFile.arrayBuffer(); - const newData = await newFile.arrayBuffer(); - const options = { - signal, - maxInputBytes: 64 * 1024 * 1024, - maxOutputBytes: 64 * 1024 * 1024, - }; - const patchData = await diffBytes(oldData, newData, options); - const restoredData = await patchBytes(oldData, patchData, options); - - return { patchData, restoredData }; -} +const oldBytes = await oldFile.arrayBuffer(); +const newBytes = await newFile.arrayBuffer(); + +const patchBytesValue = await diffBytes(oldBytes, newBytes, { + signal: abortController.signal, + maxInputBytes: 64 * 1024 * 1024, +}); +const restoredBytes = await patchBytes(oldBytes, patchBytesValue, { + maxOutputBytes: 64 * 1024 * 1024, +}); ``` -`diffBytes` and `patchBytes` accept `ArrayBuffer`, any `ArrayBufferView` -(including typed arrays and `DataView`), or `Blob`. They resolve to a new -`Uint8Array` and leave the caller's buffers usable. Aborted operations reject -with `EABORTED`; configured size limits reject with `ERESOURCE`. +Web calls return a new `Uint8Array` and leave caller-owned buffers usable. +Aborted operations reject with `EABORTED`; configured binary limits reject with +`ERESOURCE`. -## Platform API matrix +## API matrix | API | Android | iOS | Web | | ------------------------------------------ | ------- | --- | --- | @@ -133,29 +147,31 @@ with `EABORTED`; configured size limits reject with `ERESOURCE`. | `startDiff(...)` / `startPatch(...)` | Yes | Yes | No | | `diffBytes(oldData, newData, options?)` | No | No | Yes | | `patchBytes(oldData, patchData, options?)` | No | No | Yes | -| Legacy architecture (when provided by RN) | Yes | Yes | N/A | +| Legacy architecture, while supplied by RN | Yes | Yes | N/A | | New Architecture / TurboModule | Yes | Yes | N/A | -Calling an API family that is unavailable on the current platform rejects with -`EUNSUPPORTED` instead of silently choosing different behavior. +Unavailable platform APIs reject with `EUNSUPPORTED`; the package never +silently switches to a different input model. -## Production checklist +## Production safety -- Verify the restored output before replacing application data. - Authenticate patches from remote or otherwise untrusted sources. -- Use unique native output paths and clean temporary files after success or failure. -- Set product-specific input-size and time limits; binary diffing can use - several times the input size in peak memory. +- Verify restored output before replacing application data. +- Use unique native output paths and remove outputs you no longer need. +- Set product-specific resource limits. Binary diffing can use several times + the input size in peak memory. - Generate and apply patches with this library. Generic `BSDIFF40` patches are not interchangeable with `ENDSLEY/BSDIFF43` patches. -See [Production recipes](./docs/recipes.md) for error handling, downloads, -cross-runtime patch exchange, and integrity checks. +See [Production recipes](./docs/recipes.md) for integrity checks, downloads, +cross-runtime exchange, error handling, and cleanup patterns. + +## Verified compatibility -CI directly compiles the Android New Architecture sources against React Native -0.73.11, 0.74.7, and 0.86.0. Packed-consumer tests also verify that browser, -ESM, CommonJS, and TypeScript resolution work without installing optional React -Native peers for Web-only consumers. +CI compiles the Android and iOS APIs against React Native 0.73.11, 0.74.7, and +0.86.0, and runs device-level New Architecture assertions on Android and iOS. +Packed-consumer tests verify browser, ESM, CommonJS, Metro, and TypeScript +resolution from the real npm package shape. ## Documentation diff --git a/README.zh-CN.md b/README.zh-CN.md index 36e11da..adcecec 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,26 +1,61 @@ # react-native-bs-diff-patch -根据文件的两个版本生成紧凑的二进制补丁,再用旧文件和补丁还原新文件。 -Android、iOS 与 React Native Web 共用兼容的 `ENDSLEY/BSDIFF43` 补丁格式。 - -[中文文档](https://bs-dff-patch.corerobin.com/docs/zh-CN/) · -[在线 Playground](https://bs-dff-patch.corerobin.com/#playground) · -[English](./README.md) · [npm](https://www.npmjs.com/package/react-native-bs-diff-patch) - -## 为什么使用它? - -- **统一补丁格式:** 可以在一个受支持的运行时生成补丁,在另一个运行时应用。 -- **兼容 RN 两种架构:** 同时支持旧桥接架构和 TurboModule / 新架构。 -- **默认不阻塞 UI:** 原生端使用专用串行队列;Web 端复用模块 Worker 和已初始化的 - WebAssembly 实例,让计算离开页面线程。 -- **控制高成本任务:** 原生 job 提供进度、协作式取消、输入/输出限制和原子输出; - Web 使用 `AbortSignal` 与二进制限制。 -- **Web 无需后端服务:** 浏览器直接运行由同一套 C 核心编译而来的 WebAssembly。 - -| 运行时 | 输入方式 | 生成补丁 | 应用补丁 | -| ------------ | -------------- | ----------- | ------------ | -| Android、iOS | 绝对文件路径 | `diff` | `patch` | -| Web | 内存二进制数据 | `diffBytes` | `patchBytes` | +
+
+
+
+
+ 比较文件的两个版本,生成紧凑的二进制补丁;再用旧文件和补丁还原新文件。
+ React Native Android、iOS 与 Web 共用同一种兼容格式。
+
+ 中文文档 · + 在线 Playground · + English · + npm +
+ +## 它解决什么问题? + +当应用中已经存在某个文件的旧版本,而你不想再次传输完整新文件时,可以只传输 +两个版本之间的二进制补丁。 + +| 1. 生成差量 | 2. 按业务方式分发 | 3. 还原新文件 | +| -------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------ | +| 比较 `old.bin` 与 `new.bin`,生成 `update.patch`。 | 通过已有 CDN、API 或离线流程存储和传输补丁。 | 将 `update.patch` 应用于 `old.bin`,写出还原后的 `new.bin`。 | + +本库只负责二进制差分和还原;补丁传输、身份认证、完整性校验,以及何时替换线上 +数据,仍然由你的应用控制。 + +## 为什么选择它? + +- **统一补丁格式:** Android、iOS 与 Web 生成兼容的 + `ENDSLEY/BSDIFF43` 补丁。 +- **覆盖 RN 两种架构:** 同时支持旧桥接架构和 TurboModule / 新架构。 +- **原生性能,也能运行在浏览器:** JNI / ObjC++ 使用内置 C 核心;React Native + Web 则通过可复用的 Worker 运行同一核心编译出的 WebAssembly。 +- **可控制高成本任务:** 原生 job 支持进度、协作式取消、输入/输出限制,并避免 + 暴露未完成的输出文件。 +- **Web 无需补丁服务:** 差分和还原完全在浏览器本地执行。 + +## 平台概览 + +| | Android / iOS | React Native Web | +| -------- | ------------------------------ | ----------------------------------------------- | +| 输入 | 绝对文件路径 | `ArrayBuffer`、TypedArray、`DataView` 或 `Blob` | +| 基础 API | `diff()` / `patch()` | `diffBytes()` / `patchBytes()` | +| 可控 API | `startDiff()` / `startPatch()` | `AbortSignal` 与二进制大小限制 | +| 执行核心 | JNI / ObjC++ 调用原生 C | WASM Worker 运行同一 C 核心 | ## 安装 @@ -28,48 +63,36 @@ Android、iOS 与 React Native Web 共用兼容的 `ENDSLEY/BSDIFF43` 补丁格 npm install react-native-bs-diff-patch ``` -添加依赖后,iOS 还需要安装 Pods,并重新构建原生应用: +iOS 还需要安装 Pods,并重新构建原生应用: ```sh npx pod-install ``` -React Native autolinking 会完成原生模块注册。新增原生依赖后,只刷新 Metro -不能让模块进入已经安装的应用二进制。 +React Native autolinking 会完成原生模块注册。新增原生模块后必须重新构建应用, +只刷新 Metro 不会让模块进入已安装的二进制。 -## 原生端快速开始 +## 原生端:第一次往返 -原生 API 使用绝对文件路径。请通过项目已经使用的文件系统库选择可写缓存目录。 +原生 API 使用绝对路径。请通过项目已有的文件系统库,在可写缓存目录或文档目录中 +生成唯一输出路径。 ```ts import { diff, patch } from 'react-native-bs-diff-patch'; -type NativeRoundTripOptions = { - oldFilePath: string; - newFilePath: string; - cacheDirectory: string; -}; - -export async function nativeRoundTrip({ - oldFilePath, - newFilePath, - cacheDirectory, -}: NativeRoundTripOptions) { - const runId = Date.now(); - const patchPath = `${cacheDirectory}/update-${runId}.patch`; - const restoredPath = `${cacheDirectory}/restored-${runId}.bin`; - - await diff(oldFilePath, newFilePath, patchPath); - await patch(oldFilePath, restoredPath, patchPath); - - return { patchPath, restoredPath }; -} +const patchPath = `${cacheDirectory}/content-v2.patch`; +const restoredPath = `${cacheDirectory}/content-v2.restored`; + +await diff(oldFilePath, newFilePath, patchPath); +await patch(oldFilePath, restoredPath, patchPath); ``` -输出路径不能已经存在,同一次调用中的所有路径必须不同,所需输入文件必须已经 -写入完成。两个函数成功时都返回 `0`。 +输入文件必须已经存在;输出路径不能已经存在;同一次调用中的所有路径必须不同。 +两个函数成功时都返回 `0`。 + +### 进度、取消与资源限制 -需要进度、取消或资源边界时使用 job API: +需要控制任务生命周期时使用 job API: ```ts import { startPatch } from 'react-native-bs-diff-patch'; @@ -78,47 +101,40 @@ const job = startPatch(oldPath, outputPath, patchPath, { maxInputBytes: 64 * 1024 * 1024, maxOutputBytes: 128 * 1024 * 1024, }); + const unsubscribe = job.onProgress(({ phase, progress }) => { renderProgress(phase, progress); }); try { await job.result; + // await job.cancel(); // 用户主动取消时调用 } finally { unsubscribe(); } ``` -## React Native Web 快速开始 +## Web:第一次往返 ```ts import { diffBytes, patchBytes } from 'react-native-bs-diff-patch'; -export async function webRoundTrip( - oldFile: File, - newFile: File, - signal?: AbortSignal -) { - const oldData = await oldFile.arrayBuffer(); - const newData = await newFile.arrayBuffer(); - const options = { - signal, - maxInputBytes: 64 * 1024 * 1024, - maxOutputBytes: 64 * 1024 * 1024, - }; - const patchData = await diffBytes(oldData, newData, options); - const restoredData = await patchBytes(oldData, patchData, options); - - return { patchData, restoredData }; -} +const oldBytes = await oldFile.arrayBuffer(); +const newBytes = await newFile.arrayBuffer(); + +const patchBytesValue = await diffBytes(oldBytes, newBytes, { + signal: abortController.signal, + maxInputBytes: 64 * 1024 * 1024, +}); +const restoredBytes = await patchBytes(oldBytes, patchBytesValue, { + maxOutputBytes: 64 * 1024 * 1024, +}); ``` -`diffBytes` 和 `patchBytes` 接受 `ArrayBuffer`、任意 `ArrayBufferView` -(包括 TypedArray 和 `DataView`)或 `Blob`。它们返回新的 `Uint8Array`,且不会 -转移或失效调用方传入的缓冲区。取消以 `EABORTED` 拒绝,命中配置的大小上限时以 -`ERESOURCE` 拒绝。 +Web API 返回新的 `Uint8Array`,不会转移或失效调用方的缓冲区。主动取消以 +`EABORTED` 拒绝;命中二进制大小限制时以 `ERESOURCE` 拒绝。 -## 平台能力矩阵 +## API 矩阵 | API | Android | iOS | Web | | ------------------------------------------ | ------- | ------ | ------ | @@ -130,23 +146,26 @@ export async function webRoundTrip( | 旧架构(限 RN 仍提供时) | 支持 | 支持 | 不适用 | | 新架构 / TurboModule | 支持 | 支持 | 不适用 | -调用当前平台不可用的 API 会以 `EUNSUPPORTED` 拒绝,不会静默切换成其他行为。 +调用当前平台不可用的 API 会以 `EUNSUPPORTED` 拒绝,不会静默切换成其他输入 +模型。 -## 生产环境检查清单 +## 生产安全 +- 对远程或其他不可信来源的补丁进行身份认证。 - 替换业务数据前,验证还原结果与目标文件完全一致。 -- 对远程或其他不可信来源的补丁进行来源认证和完整性校验。 -- 原生端使用唯一输出路径,并在成功或失败后清理临时文件。 -- 按业务设置输入大小和执行时间限制;二进制差分的峰值内存可能达到输入大小的数倍。 -- 使用本库配套生成和应用补丁;通用 `BSDIFF40` 补丁与 +- 原生端使用唯一输出路径,并清理不再需要的输出文件。 +- 按业务设置资源限制;二进制差分的峰值内存可能达到输入大小的数倍。 +- 使用本库配套生成和应用补丁;通用 `BSDIFF40` 与 `ENDSLEY/BSDIFF43` 不兼容。 -错误处理、补丁下载、跨运行时交换和完整性校验示例见 +完整性校验、补丁下载、跨运行时交换、错误处理与清理模式见 [生产实践](./docs/zh-CN/recipes.md)。 -CI 会直接使用 React Native 0.73.11、0.74.7 与 0.86.0 编译 Android 新架构 -源码。真实 npm tarball 消费测试还会验证 browser、ESM、CommonJS 与 TypeScript -解析,并确保仅使用 Web 的消费者不会被强制安装可选 React Native peer。 +## 已验证的兼容性 + +CI 会使用 React Native 0.73.11、0.74.7 与 0.86.0 编译 Android 和 iOS API, +并在 Android 与 iOS 上执行新架构设备级断言。真实 npm 包消费测试还覆盖 browser、 +ESM、CommonJS、Metro 与 TypeScript 解析。 ## 完整文档 diff --git a/branding/social-preview/background.png b/branding/social-preview/background.png new file mode 100644 index 0000000..ecbabe5 Binary files /dev/null and b/branding/social-preview/background.png differ diff --git a/branding/social-preview/social-preview.svg b/branding/social-preview/social-preview.svg new file mode 100644 index 0000000..a5a47c9 --- /dev/null +++ b/branding/social-preview/social-preview.svg @@ -0,0 +1,57 @@ + diff --git a/package.json b/package.json index c806b15..b3e02fb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-bs-diff-patch", "version": "0.3.0", - "description": "Binary diff and patch support for React Native and React Native Web", + "description": "Create and apply compact binary patches across React Native Android, iOS, and Web", "main": "lib/commonjs/index", "module": "lib/module/index", "browser": "web/index.mjs", @@ -72,6 +72,10 @@ "react-native", "react-native-web", "webassembly", + "bsdiff", + "binary-diff", + "binary-patch", + "delta-update", "ios", "android" ], diff --git a/scripts/build-site.mjs b/scripts/build-site.mjs index 0792b48..b6725f4 100644 --- a/scripts/build-site.mjs +++ b/scripts/build-site.mjs @@ -363,10 +363,27 @@ function documentationLayout({ slug, title, description, content, items, ui }) { title )} — react-native-bs-diff-patch" /> + + + + + + + + + + + + + + +Same controls, explicit contracts
-The playground above executes the browser API. Use the matching path-based job facade in Android and iOS applications. @@ -243,7 +276,9 @@
const job = startPatch(oldPath, outputPath, patchPath, {
maxInputBytes: 64 * 1024 * 1024,
maxOutputBytes: 128 * 1024 * 1024,
@@ -257,7 +292,9 @@ Progress and cancel on native. AbortSignal on Web.
await job.result.finally(off);
const controller = new AbortController();
const options = {
signal: controller.signal,
@@ -399,8 +436,8 @@
452 KiB unpacked · 58 files
+ 125 KiB packed +459 KiB unpacked · 58 files