Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
154 changes: 147 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ on:
types:
- checks_requested

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -52,26 +56,160 @@ jobs:
- name: Build package
run: yarn prepare

expo-prebuild:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
features: [none, barcode, 'barcode,text', all]
env:
EXPO_WORK_DIR: ${{ github.workspace }}/.tmp/expo-prebuild-${{ matrix.features }}

steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Setup
uses: ./.github/actions/setup

- name: Expo prebuild smoke test (analysisFeatures=${{ matrix.features }})
env:
DS_FEATURES: ${{ matrix.features }}
run: |
set -euo pipefail

PACKAGE_TGZ="$(npm pack --silent --ignore-scripts | tail -n1)"
rm -rf "$EXPO_WORK_DIR"
mkdir -p "$EXPO_WORK_DIR"

npx --yes create-expo-app@latest "$EXPO_WORK_DIR/app" --template blank-typescript --yes --no-install

cd "$EXPO_WORK_DIR/app"
npm install
npm install --ignore-scripts "$GITHUB_WORKSPACE/$PACKAGE_TGZ"

node <<'NODE'
const fs = require('node:fs');
const path = 'app.json';
const app = JSON.parse(fs.readFileSync(path, 'utf8'));
app.expo = app.expo || {};
app.expo.plugins = [
['@preeternal/react-native-document-scanner-plugin', { analysisFeatures: process.env.DS_FEATURES }],
];
fs.writeFileSync(path, JSON.stringify(app, null, 2) + '\n');
NODE

CI=1 npx expo config --json > "$EXPO_WORK_DIR/expo-config.json" 2> "$EXPO_WORK_DIR/expo-config.stderr.log" || {
echo "::error::expo config failed for analysisFeatures='${DS_FEATURES}'"
if [[ -f "$EXPO_WORK_DIR/expo-config.stderr.log" ]]; then
echo "::group::expo config stderr"
cat "$EXPO_WORK_DIR/expo-config.stderr.log"
echo "::endgroup::"
fi
if [[ -f "$EXPO_WORK_DIR/expo-config.json" ]]; then
echo "::group::expo config stdout"
cat "$EXPO_WORK_DIR/expo-config.json"
echo "::endgroup::"
fi
exit 1
}

node -e "JSON.parse(require(\"node:fs\").readFileSync(process.argv[1], \"utf8\"))" "$EXPO_WORK_DIR/expo-config.json" || {
echo "::error::expo config output is not valid JSON for analysisFeatures='${DS_FEATURES}'"
echo "::group::expo config stdout (raw)"
cat "$EXPO_WORK_DIR/expo-config.json" || true
echo "::endgroup::"
if [[ -f "$EXPO_WORK_DIR/expo-config.stderr.log" ]]; then
echo "::group::expo config stderr"
cat "$EXPO_WORK_DIR/expo-config.stderr.log"
echo "::endgroup::"
fi
exit 1
}

CI=1 npx expo prebuild --platform android --clean --no-install 2>&1 | tee "$EXPO_WORK_DIR/prebuild.log"

ACTUAL_FEATURES="$(awk -F= '/^DocumentScanner_analysisFeatures=/{print $2}' android/gradle.properties | tail -n1 | tr -d '\r')"
if [[ -z "$ACTUAL_FEATURES" ]]; then
echo "::error::Missing DocumentScanner_analysisFeatures in android/gradle.properties"
exit 1
fi
if [[ "$ACTUAL_FEATURES" != "$DS_FEATURES" ]]; then
echo "::error::DocumentScanner_analysisFeatures mismatch. expected='${DS_FEATURES}' actual='${ACTUAL_FEATURES}'"
exit 1
fi

- name: Print Expo prebuild diagnostics (${{ matrix.features }})
if: always()
run: |
set -euo pipefail

echo "::group::app.json"
if [[ -f "$EXPO_WORK_DIR/app/app.json" ]]; then
cat "$EXPO_WORK_DIR/app/app.json"
else
echo "Missing: $EXPO_WORK_DIR/app/app.json"
fi
echo "::endgroup::"

echo "::group::expo config"
if [[ -f "$EXPO_WORK_DIR/expo-config.json" ]]; then
cat "$EXPO_WORK_DIR/expo-config.json"
else
echo "Missing: $EXPO_WORK_DIR/expo-config.json"
fi
echo "::endgroup::"

echo "::group::android/gradle.properties (DocumentScanner lines)"
if [[ -f "$EXPO_WORK_DIR/app/android/gradle.properties" ]]; then
grep -n "DocumentScanner" "$EXPO_WORK_DIR/app/android/gradle.properties" || echo "(no DocumentScanner entries)"
else
echo "Missing: $EXPO_WORK_DIR/app/android/gradle.properties"
fi
echo "::endgroup::"

- name: Upload Expo prebuild diagnostics (${{ matrix.features }})
if: always()
uses: actions/upload-artifact@v4
with:
name: expo-prebuild-${{ matrix.features }}-diagnostics
if-no-files-found: warn
path: |
${{ env.EXPO_WORK_DIR }}/prebuild.log
${{ env.EXPO_WORK_DIR }}/app/app.json
${{ env.EXPO_WORK_DIR }}/expo-config.json
${{ env.EXPO_WORK_DIR }}/expo-config.stderr.log
${{ env.EXPO_WORK_DIR }}/app/android/gradle.properties

- name: Cleanup Expo prebuild workspace (${{ matrix.features }})
if: always()
run: rm -rf "$EXPO_WORK_DIR"

build-android:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
features: [none, barcode, text, all]
env:
TURBO_CACHE_DIR: .turbo/android
TURBO_CACHE_DIR: .turbo/android-${{ matrix.features }}

steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Setup
uses: ./.github/actions/setup

- name: Cache turborepo for Android
- name: Cache turborepo for Android (${{ matrix.features }})
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ${{ env.TURBO_CACHE_DIR }}
key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }}
key: ${{ runner.os }}-turborepo-android-${{ matrix.features }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-turborepo-android-
${{ runner.os }}-turborepo-android-${{ matrix.features }}-

- name: Check turborepo cache for Android
- name: Check turborepo cache for Android (${{ matrix.features }})
run: |
TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status")

Expand All @@ -98,13 +236,15 @@ jobs:
path: |
~/.gradle/wrapper
~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }}
key: ${{ runner.os }}-gradle-${{ matrix.features }}-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-${{ matrix.features }}-
${{ runner.os }}-gradle-

- name: Build example for Android
- name: Build example for Android (features=${{ matrix.features }})
env:
JAVA_OPTS: "-XX:MaxHeapSize=6g"
ORG_GRADLE_PROJECT_DocumentScanner_analysisFeatures: ${{ matrix.features }}
run: |
yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}"

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,6 @@ android/generated

# React Native Nitro Modules
nitrogen/

#Temp files
*.tmp
86 changes: 86 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Releases

## v0.3.0 – Capture + Analysis Pipeline Release

## Highlights

- Added single-call capture + analysis pipeline:
- `scanAndAnalyzeDocument(options?)`
- Added new public analysis APIs for existing images:
- `extractBarcodesFromImages(images, options?)`
- `extractTextFromImages(images, options?)`
- `analyzeScannedImages(images, options?)`
- Added document semantics extraction:
- tables, regions, structured entities and key-value fields
- Improved iOS analysis pipeline: modern Vision path on iOS 26+, with fallback paths for earlier iOS versions and Simulator environments.
- Added Android analysis feature gating via Gradle property (`DocumentScanner_analysisFeatures`), so barcode/text analysis can be explicitly enabled for Android builds.

## Additional Notes

- Core `scanDocument(...)` flow remains supported and backward-compatible.
- This release expands the package from capture-only usage to capture + analysis workflows for scanner output, gallery images and file-based pipelines.
- No breaking changes are expected for existing `scanDocument(...)` integrations.

### Technical hardening (legacy API)

- Android (`scanDocument` flow): synchronized `launcher` initialization to avoid edge-case double registration on concurrent calls.
- Android (`scanDocument` flow): stronger `invalidate()` cleanup (`launcher.unregister()`, pending-state reset).
- Android (`scanDocument` flow): iterative page mapping in scan result processing (reduced recursion risk).

---

## v0.2.2 – Cross-Platform Polish

## Highlights

- Ensure Google’s document scanner respects system bars on Android 15+ and restore window flags when returning to React Native (Fix #2).
- Prevent repeated scans from crashing the Android bridge (`ObjectAlreadyConsumedException`) and validate returned URIs before resolving promises (Fix #142).
- Align Android/iOS responses: base64 conversion is guarded, file paths are checked for readability, and iOS trims/validates `file://` URLs (Fix #142).
- Restore DocumentScanner Objective‑C bridge so both old and new architectures build cleanly without warnings.

## Additional Notes

- Covers testing on both architectures, with New Arch guarding the TurboModule entry point while legacy builds continue to use the classic bridge.
- No API changes; consumers can upgrade directly.

### Docs: Sanitized scanner response

- Since v0.2.2 the module sanitizes scannedImages on both platforms. You no longer need to post‑filter in JS.
- Android: returns only non‑empty base64 strings or readable URIs (verified via ContentResolver).
- iOS: trims strings, normalizes file:// URLs to file paths and checks file existence.
- Backward‑compatible; no API changes.

Example:

```js
const { status, scannedImages } = await DocumentScanner.scanDocument({ responseType: 'imageFilePath' })
if (status === 'success' && scannedImages.length) {
// All items are already valid
setImage(scannedImages[0])
}
```

---

## v0.2.1 – Expo EAS iOS Swift Header Fix

- Fix: Reliable import of generated Swift header (DocumentScanner-Swift.h) using conditional __has_include + VisionKit import so Expo EAS cloud builds no longer fail with ‘DocumentScanner-Swift.h’ or VNDocumentCameraViewControllerDelegate not found.
- Ensures consistent iOS 13+ VisionKit availability for Expo SDK 53 development & production builds.

---

## v0.2.0 – TurboModule support & improvements

### Added

- Full support for React Native New Architecture (TurboModules).
- Prepared builds with react-native-builder-bob.

### Changed

- Updated dependencies to React Native 0.79.2 and React 19.0.0.
- Improved TypeScript definitions.

### Fixed

- Minor issues in Android and iOS build configurations.
81 changes: 81 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,87 @@ The `package.json` file contains various scripts for common tasks:
- `yarn example android`: run the example app on Android.
- `yarn example ios`: run the example app on iOS.

### Native debug logs (iOS)

iOS native logs in this project are opt-in.

- `DOCUMENT_SCANNER_DEBUG_LOGS=1` enables high-level logs.
- `DOCUMENT_SCANNER_TRACE_LOGS=1` enables verbose trace logs (and high-level logs).

Example for physical device launch via `devicectl`:

```sh
REPO="$(git rev-parse --show-toplevel)"
mkdir -p "$REPO/logs"

xcrun devicectl device process launch \
--device <YOUR_DEVICE_ID_OR_NAME> \
--terminate-existing \
--console \
--environment-variables '{"DOCUMENT_SCANNER_TRACE_LOGS":"1"}' \
preeternal.scanner.example 2>&1 \
| grep --line-buffered '\[DocumentScanner\]' \
| tee "$REPO/logs/ios-trace.log"
```

Example for Simulator (`log stream`), after app launch with env flag:

```sh
xcrun simctl spawn booted log stream --level debug \
--predicate 'eventMessage CONTAINS[c] "[DocumentScanner]"'
```

### Native debug logs (Android)

Android native logs are also opt-in.

Enable one of the flags before launching the app:

- `debug.document_scanner.debug_logs=1` enables high-level logs.
- `debug.document_scanner.trace_logs=1` enables verbose trace logs (and high-level logs).

```sh
adb shell setprop debug.document_scanner.debug_logs 1
adb shell setprop debug.document_scanner.trace_logs 1
```

After changing Android debug properties, restart the app process to apply them:

```sh
adb shell am force-stop <YOUR_APP_PACKAGE>
```

If you changed native code, rebuild/reinstall the app before running log capture.

Clear/disable flags:

```sh
adb shell setprop debug.document_scanner.debug_logs 0
adb shell setprop debug.document_scanner.trace_logs 0
adb shell am force-stop <YOUR_APP_PACKAGE>
```

Barcode run to file:

```sh
REPO="$(git rev-parse --show-toplevel)"
mkdir -p "$REPO/logs"
adb logcat -c
adb logcat -v time DocumentScanner:D DocumentScannerBarcode:V '*:S' \
| grep --line-buffered -Ei 'extractBarcodes|runBarcodeAnalysisStage|Barcode|DocumentScannerBarcode' \
| tee "$REPO/logs/android-barcode.log"
```

Text run to file:

```sh
REPO="$(git rev-parse --show-toplevel)"
mkdir -p "$REPO/logs"
adb logcat -c
adb logcat -v time DocumentScanner:D DocumentScannerBarcode:V ReactNative:W AndroidRuntime:E '*:S' \
| tee "$REPO/logs/android-text-full-unfiltered.log"
```

### Sending a pull request

> **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).
Expand Down
Loading