Skip to content

feat(pos-app): add production OTA update support#403

Draft
ignaciosantise wants to merge 14 commits intomainfrom
feat/ota-updates
Draft

feat(pos-app): add production OTA update support#403
ignaciosantise wants to merge 14 commits intomainfrom
feat/ota-updates

Conversation

@ignaciosantise
Copy link
Collaborator

Summary

This PR adds production OTA update support for dapps/pos-app with Expo Updates, including runtime update checks, update metadata in Settings, and production-only channel configuration.
It also wires release workflows to publish native fingerprint artifacts and adds a dedicated OTA publish workflow that blocks updates when Android or iOS native fingerprints drift.
Documentation was updated in dapps/pos-app/README.md and dapps/pos-app/AGENTS.md to describe publish, rollback, and operational constraints for JS-only OTA releases.
Validation run: npm run lint, npx tsc --noEmit, and npm test -- --watch=false in dapps/pos-app.

OTA Flow

flowchart TD
  A[Release POS workflow] --> B[Expo prebuild with production channel]
  B --> C[Generate Android and iOS native fingerprints]
  C --> D[Upload fingerprint artifacts]
  E[Manual OTA workflow dispatch] --> F[Compute current Android and iOS fingerprints]
  F --> G[Download last production fingerprints]
  G --> H{Fingerprints match?}
  H -- No --> I[Block OTA publish]
  H -- Yes --> J[eas update --channel production]
Loading

@claude
Copy link

claude bot commented Mar 3, 2026

Claude finished @ignaciosantise's task in 3m 20s —— View job


PR Review: feat(pos-app): add production OTA update support

  • Gather context and read changed files
  • Review core implementation files
  • Run subagent checks (license, breaking changes, data classification)
  • Compile final review

Subagent results: ✅ No license issues | ✅ No breaking changes | ✅ No data classification issues

Found 3 issue(s)

Issue 1: Silent immediate reload during active payment transaction

ID: use-ota-updates-silent-reload-a3f1
File: dapps/pos-app/hooks/use-ota-updates.ts:34-42
Severity: HIGH
Category: code_quality

Context:

  • Pattern: Updates.reloadAsync() is called immediately after fetchResult.isNew without any user-facing prompt or transaction-state check. The hook fires on every AppState -> "active" transition (respecting 5-min debounce), including when the user briefly backgrounds the app mid-payment.
  • Risk: A payment flow in progress (scan, QR display, waiting for on-chain confirmation) is silently destroyed mid-transaction when a new OTA bundle is available.
  • Impact: Lost in-flight payment state; customer-facing disruption on a live POS terminal.
  • Trigger: Any OTA update published while a device has the app backgrounded during checkout, then foregrounded.

Recommendation: Check for an active payment route or expose an imperative API that the caller (layout) can use to defer the reload. At minimum, notify the user before reloading:

// e.g., defer until the app returns to idle state
if (fetchResult.isNew) {
  addLog("info", "OTA update ready — will apply on next launch", ...);
  // store a flag and call Updates.reloadAsync() when no transaction is active
}

Fix this →


Issue 2: Third-party GitHub Actions action pinned by tag, not commit SHA

ID: ota-update-pos-unpinned-action-b7c2
File: .github/workflows/ota-update-pos.yaml:52,59
Severity: MEDIUM
Category: security

Context:

  • Pattern: dawidd6/action-download-artifact@v6 is referenced by a mutable tag. Tags can be force-pushed to point to arbitrary commits.
  • Risk: A compromised or updated dawidd6/action-download-artifact could exfiltrate EXPO_TOKEN or POS_ENV_FILE from the runner environment.
  • Impact: Supply chain compromise of the OTA publish step and all credentials it handles.
  • Trigger: Any tag reassignment in the upstream dawidd6/action-download-artifact repository.

Recommendation: Pin to a specific commit SHA:

uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc  # v6.x.x

Fix this →


Issue 3: External domain URL in app configuration

ID: app-json-external-domain-url-e9a4
File: dapps/pos-app/app.json:15
Severity: LOW
Category: security

🔒 External Domain URL (Non-blocking)
URL: https://u.expo.dev/caf3a0d7-e413-45c2-b3b9-879cd30b3501
File: dapps/pos-app/app.json:15

This is the Expo Updates manifest endpoint, required for OTA updates to function. Verify the project ID (caf3a0d7-e413-45c2-b3b9-879cd30b3501) matches the intended EAS project and that the owner: "reown-mobile" field in app.json correctly scopes it to the right organization.

@vercel
Copy link

vercel bot commented Mar 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-native-examples Ready Ready Preview, Comment Mar 4, 2026 8:08pm

Request Review

…ading

Restore the expo-asset plugin to ensure assets are embedded in native
builds and included in OTA update manifests. Add patch for expo-updates
to load .env files before config evaluation (upstream fix pending in
expo/expo#43635). Defer API URL validation to request time to avoid
crash when env vars load asynchronously. Add expo-channel-name header
for production channel routing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolve merge conflicts after Expo 55 upgrade on main. Update
expo-updates to v55.0.12 and regenerate env loading patch for
the new version.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The expo-updates fingerprint:generate command outputs a full JSON object
containing parentheses and special characters. When interpolated directly
into shell scripts via ${{ }}, this caused syntax errors. Fix by piping
through jq to extract just the hash, and using env blocks instead of
inline interpolation in the OTA workflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rison

Query fingerprints directly from EAS server using eas build:list and
eas fingerprint:compare, eliminating the need to save/download fingerprint
artifacts across workflows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Capture eas CLI output before parsing with jq to gracefully handle
cases where no builds exist yet and the CLI returns non-JSON output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Required for eas CLI commands (build:list, fingerprint:compare) to
identify the project in non-interactive mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EAS fingerprint API only works with EAS Build, but we build natively
with Gradle/Fastlane. Revert to artifact-based approach with the
original fixes: jq hash extraction and env block for safe shell
interpolation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…facts

Replace GitHub Actions artifacts (30-day retention limit) with a
dedicated `fingerprints-dont-remove` branch for storing native build
fingerprints. This ensures fingerprints never expire, so the OTA
safety check always works regardless of time between releases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Called workflows (release-android-base, release-ios-base) now require
contents:write to push fingerprints to the fingerprints-dont-remove
branch. All caller workflows must grant at least that level.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant