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

1 participant