Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 12 additions & 21 deletions .maestro/flows/_assets-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,20 @@ appId: ${APP_ID}
---
- runFlow:
when:
notVisible: "Assets (Android)"
notVisible: "Bundled Assets"
commands:
- tapOn: "Playground"
- scrollUntilVisible:
element:
text: "Assets (Android)"
text: "Bundled Assets"
direction: DOWN
- tapOn: "Assets (Android)"
- runFlow:
when:
visible: "Not Supported"
commands:
- assertVisible: "Not Supported"
- runFlow:
when:
visible: "Android Assets Demo"
commands:
- assertVisible: "Android Assets Demo"
- tapOn: "Unzip Assets"
- waitForAnimationToEnd:
timeout: 10000
- assertVisible: "Extracted To"
- extendedWaitUntil:
visible: "Files:"
timeout: 10000
- assertVisible: "Files:"
- tapOn: "Bundled Assets"
- assertVisible: "Bundled Assets Demo"
- tapOn: "Unzip Assets"
- waitForAnimationToEnd:
timeout: 10000
- assertVisible: "Extracted To"
- extendedWaitUntil:
visible: "Files:"
timeout: 10000
- assertVisible: "Files:"
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# Changelog

## [9.4.0] - 2026-07-25

### Added
- iOS: `unzipAssets` reads archives from the main app bundle (parity with Android `assets/`) (#368)
- iOS: preserve empty directories when zipping directory items in a files array (#368)

### Changed
- iOS: non-UTF-8 `charset` arguments now reject with `ERR_UNSUPPORTED` instead of being silently ignored (#368)
- iOS: `getUncompressedSize` rejects on failure (previously resolved `-1`) for parity with Android

### Fixed
- iOS: `unzip` / `unzipAssets` emit 0% progress on failure (matches Android) instead of a 100% event before reject

## [9.3.0] - 2026-07-25

### Changed
- iOS: `zipWithPassword` with a files array now honors `encryptionType`. Omitting it (JS default, treated as `'STANDARD'`) writes ZipCrypto instead of the previous always-AES (WinZip-AES) default. ZipCrypto is weaker encryption than AES; pass `'AES-128'` or `'AES-256'` to keep AES. This matches Android's default and common server unzippers (#367).

### Fixed
- iOS: `zipFilesWithPassword` now honors `encryptionType` — `'STANDARD'` uses ZipCrypto instead of always writing WinZip-AES (improves server-side unzip with Node/Java tools) (#367, #333, #323)
- iOS: fsync zip output after successful `zip` / `zipWithPassword` so immediate uploads/reads see full bytes (#367)
- iOS: file-array `zip` / `zipWithPassword` now apply the requested compression level (previously always `Z_DEFAULT_COMPRESSION`)

### Added
- `scripts/validate-zip-header.js` — checks local-file and EOCD signatures for interoperability smoke tests
- README guidance for server-side unzip compatibility

## [9.2.0] - 2026-07-25

### Added
Expand Down
29 changes: 29 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
# Migration Guide

## v9.2 / v9.3 / v9.4

These releases add APIs and align iOS with Android. Most JavaScript call sites keep working; the notes below are the native/default changes that existing apps may observe.

### iOS file-array `zipWithPassword` default is ZipCrypto (9.3.0)

On iOS, `zipWithPassword([files], target, password)` used to always write **WinZip-AES**, even when `encryptionType` was omitted. It now follows `encryptionType` the same way folders and Android do:

| Call | Before 9.3 (iOS file array) | 9.3+ |
|---|---|---|
| `zipWithPassword(files, dest, password)` | WinZip-AES | ZipCrypto (`STANDARD`) |
| `zipWithPassword(files, dest, password, 'STANDARD')` | WinZip-AES | ZipCrypto |
| `zipWithPassword(files, dest, password, 'AES-256')` | WinZip-AES | WinZip-AES |

ZipCrypto is **weaker encryption** than AES. It is the default so Node `unzipper`, Java `ZipInputStream`, and stock `unzip` can open the archive. Pass `'AES-128'` or `'AES-256'` if you need AES.

Existing AES archives are unchanged; only newly created file-array zips on iOS pick up the new default.

### Other 9.2–9.4 notes

- **9.2:** `cancel()` and stable `ErrorCodes` (`ERR_CANCELLED`, `ERR_WRONG_PASSWORD`, …).
- **9.2:** Android `'STANDARD'` encryption is ZipCrypto (`ZIP_STANDARD`), not PKWARE Strong Encryption.
- **9.4:** iOS `unzipAssets` reads from the app bundle; non-UTF-8 `charset` rejects with `ERR_UNSUPPORTED`; `getUncompressedSize` rejects on failure instead of resolving `-1`.

```bash
npm install react-native-zip-archive@^9.4.0
cd ios && pod install && cd ..
```

## v8.x to v9.0

### What's Changed
Expand Down
49 changes: 33 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Zip with password protection.
- `'AES-128'` — AES 128-bit
- `'AES-256'` — AES 256-bit

> **iOS:** Both AES-128 and AES-256 use AES-256 internally. AES encryption is **not supported** for file arrays on iOS — only `STANDARD` works.
> **iOS:** Both AES-128 and AES-256 use AES-256 internally. File arrays honor `encryptionType` the same as folders. The default is ZipCrypto (`'STANDARD'`), including when the 4th argument is omitted — file arrays previously always wrote WinZip-AES. Pass `'AES-128'` or `'AES-256'` if you need AES. Prefer `'STANDARD'` when the archive will be unzipped by Node, Java, or other non-WinZip tools.

```js
const sourcePath = DocumentDirectoryPath
Expand All @@ -123,7 +123,7 @@ Or with an explicit charset:
unzip(sourcePath, targetPath, 'UTF-8', ['readme.md', 'docs'])
```

> The `charset` parameter is only supported on Android (default: `UTF-8`). On iOS it is ignored.
> The `charset` parameter defaults to `UTF-8`. On Android, other charsets are supported. On iOS, non-UTF-8 values reject with `ERR_UNSUPPORTED`.

```js
const sourcePath = `${DocumentDirectoryPath}/myFile.zip`
Expand Down Expand Up @@ -162,7 +162,7 @@ type ZipEntry = {
}
```

> The `charset` parameter is only supported on Android (default: `UTF-8`). On iOS it is ignored.
> The `charset` parameter defaults to `UTF-8`. On Android, other charsets are supported. On iOS, non-UTF-8 values reject with `ERR_UNSUPPORTED`.

```js
listContents(sourcePath)
Expand All @@ -176,9 +176,12 @@ listContents(sourcePath)

### `unzipAssets(assetPath: string, target: string): Promise<string>`

Unzip a file from the Android `assets` folder. **Android only.**
Unzip a bundled archive.

`assetPath` is the relative path inside the pre-bundled assets folder (e.g. `folder/myFile.zip`). Do not pass an absolute path.
- **Android:** relative path inside the APK `assets/` folder (also accepts `content://` URIs).
- **iOS:** relative path inside the main app bundle (e.g. a file copied with Xcode “Copy Bundle Resources”).

Do not pass an absolute filesystem path.

```js
unzipAssets('./myFile.zip', DocumentDirectoryPath)
Expand Down Expand Up @@ -262,24 +265,38 @@ useEffect(() => {
| Feature | iOS | Android | Notes |
|---------|-----|---------|-------|
| `zip` (folder) | ✅ | ✅ | — |
| `zip` (files array) | ✅ | ✅ | Compression level ignored on iOS |
| `zipWithPassword` (folder) | ✅ | ✅ | AES encryption supported |
| `zipWithPassword` (files array) | ⚠️ | ✅ | iOS: only `STANDARD` encryption |
| `unzip` | ✅ | ✅ | Optional `entries` for selective extract; charset ignored on iOS |
| `zip` (files array) | ✅ | ✅ | |
| `zipWithPassword` (folder) | ✅ | ✅ | Prefer `STANDARD` for server unzip |
| `zipWithPassword` (files array) | | ✅ | iOS honors `STANDARD` vs AES |
| `unzip` | ✅ | ✅ | Optional `entries`; non-UTF-8 charset → `ERR_UNSUPPORTED` on iOS |
| `unzipWithPassword` | ✅ | ✅ | Optional `entries` for selective extract |
| `listContents` | ✅ | ✅ | Charset ignored on iOS |
| `unzipAssets` | | ✅ | Android only |
| `listContents` | ✅ | ✅ | Non-UTF-8 charset → `ERR_UNSUPPORTED` on iOS |
| `unzipAssets` | | ✅ | Android `assets/` (+ `content://`); iOS main bundle |
| `cancel` | ✅ | ✅ | Best-effort mid-operation abort |
| `isPasswordProtected` | ✅ | ✅ | — |
| `getUncompressedSize` | ✅ | ✅ | Charset ignored on iOS |
| `getUncompressedSize` | ✅ | ✅ | Non-UTF-8 charset → `ERR_UNSUPPORTED` on iOS |
| Progress Events | ✅ | ✅ | File path empty on iOS for zip |

### Cross-Platform Notes

- **Compression levels:** Android supports 0–9 for all operations. iOS supports them only for folder operations.
- **Encryption:** Android supports AES-128, AES-256, and Standard ZIP encryption for all operations. iOS supports AES and Standard for folders, but only Standard for file arrays.
- **Charset:** Android supports custom charsets (default UTF-8). iOS always uses UTF-8.
- **unzipAssets:** Supports `assets/` folder and `content://` URIs on Android. Not supported on iOS.
- **Compression levels:** Android supports 0–9 for all operations. iOS supports 0–9 for folder and file-array zips.
- **Encryption:** Android supports AES-128, AES-256, and Standard ZIP encryption for all operations. On iOS, pass `'STANDARD'` (default) for ZipCrypto archives that Node `unzipper` / Java `ZipInputStream` can read; `'AES-128'` / `'AES-256'` produce WinZip-AES archives that many server tools cannot open.
- **Charset:** Android supports custom charsets (default UTF-8). iOS accepts only UTF-8; other values reject with `ERR_UNSUPPORTED`.
- **unzipAssets:** Android reads `assets/` (and `content://`). iOS reads from the main app bundle using the same relative path.
- **Empty directories:** Preserved when zipping directory contents via a files/folders array on both platforms.

### Server-side unzip interoperability

Plain (non-AES) zips created on iOS and Android are intended to open with common server unzippers (`unzip`, Node `unzipper`, Java `ZipInputStream`). Practical tips:

- Prefer `zip(...)` or `zipWithPassword(..., 'STANDARD')` when the archive will be extracted off-device.
- Avoid AES password zips if the consumer is stock Java/`unzipper` — use `'STANDARD'` instead.
- Decode URL-encoded paths (`decodeURIComponent`) before passing them in; `%20` in paths has been mistaken for corrupt archives (#333).
- After upgrading, you can sanity-check a produced file with:

```bash
node scripts/validate-zip-header.js /path/to/archive.zip
```

## Expo

Expand Down
6 changes: 3 additions & 3 deletions RNZipArchive.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ Pod::Spec.new do |s|
s.source = { :git => 'https://github.com/mockingbot/react-native-zip-archive.git', :tag => "#{s.version}"}
s.platform = :ios, '15.5'
s.preserve_paths = '*.js'
s.pod_target_xcconfig = {
'HEADER_SEARCH_PATHS' => '$(inherited) "$(PODS_ROOT)/SSZipArchive/SSZipArchive/minizip"'
}

if defined?(install_modules_dependencies) != nil
install_modules_dependencies(s)
else
s.dependency 'React-Core'
end
s.dependency 'SSZipArchive', '~>2.5.5'
s.pod_target_xcconfig = {
'HEADER_SEARCH_PATHS' => '$(inherited) "$(PODS_ROOT)/SSZipArchive" "$(PODS_ROOT)/SSZipArchive/SSZipArchive/minizip"'
}

s.source_files = 'ios/*.{h,m,mm}'
s.public_header_files = ['ios/RNZipArchive.h']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ private void zipWithPassword(final List<String> filesOrDirectory, final String d
}
} else if ("STANDARD".equals(encryptionMethod)) {
// ZipCrypto (ZIP_STANDARD). ZIP_STANDARD_VARIANT_STRONG is write-only in zip4j
// and fails extract with "encryption method is not supported".
// and fails create/extract with "encryption method is not supported".
parameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);
Log.d(TAG, "Standard Encryption");
} else {
Expand Down
Loading
Loading