Mục tiêu
Bật cập nhật OTA (EAS Update) cho bản staging, để mỗi lần đổi code JS là app tự nhận bản mới, không cần gỡ và cài lại APK.
Phạm vi: chỉ staging
OTA_ENABLED_ENVS trong app.config.ts chỉ chứa 'staging'. Các môi trường còn lại không được gán updates.url, nên expo-updates tắt hẳn ở tầng native.
APP_ENV |
updates.url |
Nhận OTA |
|---|---|---|
staging |
có | Có |
production |
không | Không |
development / ui
|
không | Không |
Vì sao production bị loại: expo-updates codesigning:configure nhúng chứng chỉ vào binary lúc build, nên code signing không thể thêm bằng OTA. Bật updates.url cho production trước khi có code signing đồng nghĩa mọi bản đang lưu hành mang một đường nhận-mã-từ-xa không ký, và ranh giới tin cậy duy nhất là quyền truy cập tài khoản EAS. Với một app tài chính thì không đánh đổi được. Các bước bắt buộc để bật về sau đã ghi trong runbook.
Thay đổi chính
-
app.config.ts— cổng chặnOTA_ENABLED_ENVS;runtimeVersion: { policy: 'fingerprint' }đặt cho mọi môi trường, chỉupdates.urllà có điều kiện. -
src/lib/hooks/use-app-update.ts— module duy nhất được importexpo-updates. Kiểm tra bản mới lúc khởi động và mỗi lần app trở lạiactive. -
src/components/update-banner.tsx— banner + nút khởi động lại, chỉ hiện ở route an toàn. -
fingerprint.config.js— làm fingerprint tất định giữa máy local và EAS builder. -
env.js— đổi URL schemeMoanaPay→ahad. -
docs/ota-updates.md— runbook. - Test: 15 test mới (9 cho hook, 6 cho banner).
Ba chỗ đáng review kỹ
1. runtimeVersion đặt không điều kiện — cố ý
Thứ thật sự bật expo-updates là updates.url, không phải runtimeVersion. Nếu chỉ đặt runtimeVersion cho staging thì chạy prebuild:staging rồi prebuild:production sẽ chết, vì prebuild tái dùng thư mục native sẵn có:
A runtime version is set in your AndroidManifest.xml, but is missing from your Expo app config
Đã kiểm chứng cả hai chiều prebuild sạch, và production vẫn ENABLED=false, không có URL.
2. Banner dùng allowlist route, không phải denylist
RESTART_SAFE_ROUTES = ['/home']. Một màn hình thêm về sau bị loại cho tới khi có người review — mặc định an toàn cho app tài chính.
useIsMutating() chỉ là lớp thứ hai, không thể là lớp duy nhất: KYC gọi client.post trực tiếp (src/api/verification/index.ts:18) và chạy qua state cục bộ + SDK native (src/hooks/use-sumsub-verification.ts:63), React Query không thấy. Một lần restart không được phép cắt ngang giao dịch hay phiên KYC.
3. Nhánh rollback là bắt buộc
Chỉ thị rollback của EAS mang isAvailable: false và isRollBackToEmbedded: true. Hook chỉ kiểm tra isAvailable sẽ lặng lẽ bỏ qua mọi rollback, làm eas update:roll-back-to-embedded mất tác dụng hoàn toàn — tức là mất luôn đường thoát khi đẩy nhầm bản hỏng. Đã có test riêng cho cả hai nhánh.
Đổi scheme sang ahad
EAS Update validate manifest theo ^[a-z][a-z0-9+.-]*$ và từ chối publish nếu scheme không khớp; EAS Build thì không kiểm tra, nên scheme: 'MoanaPay' tồn tại rất lâu mà không ai phát hiện — chỉ vỡ ra ở lần publish OTA đầu tiên. Dạng array cũng bị từ chối (từng phần tử vẫn phải khớp).
Scheme cũ bỏ hoàn toàn, không đăng ký lại ở tầng native — đã xác nhận với chủ dự án là chưa có QR thanh toán nào phát hành ra ngoài.
Mọi chỗ sinh/quét QR giờ đọc Env.SCHEME, không hardcode.
src/lib/web3/appkit.tsx có redirect.native đi theo scheme mới. Luồng quay lại app sau khi ký ví WalletConnect chưa kiểm chứng được trên thiết bị thật. Ngoài ra name: 'MoanaPay' / url: 'https://moanapay.app' trong file đó cố ý để nguyên — đổi là quyết định thương hiệu và cần domain ahad thật.
Giới hạn đã biết: AppDelegate.swift và Podfile
expo-updates loại cứng toàn bộ ios/ và android/ khỏi fingerprint với project CNG, nên sửa hai file này KHÔNG làm đổi runtimeVersion — bản OTA publish sau đó vẫn lọt xuống binary build trước khi sửa, và chạy JS mới trên native cũ.
Đã thử ba cách bịt, đều không được (chi tiết trong runbook, mục "Ngoại lệ đã biết"): extraSources kiểu file bị chính ignorePaths lọc; kiểu contents không tất định vì eas-cli upload bằng git clone --no-checkout rồi chép đè từ đĩa local; git show HEAD: thì .git không tới được builder EAS.
Kiểm soát bằng quy trình, vì không có kiểm soát kỹ thuật: sau khi sửa một trong hai file đó, bắt buộc build lại và phát bản mới cho tester trước khi publish bất kỳ bản OTA nào.
Đã kiểm chứng
-
pnpm check-allsạch — 320 test / 48 suite pass, lint + type-check + translations OK. - Build EAS staging Android thành công, runtime
7acadc7291de6d270c265e2cb0dc2423107b0bd1, channelstaging. -
Tính an toàn của fingerprint chạy đúng cả hai chiều: đổi native (scheme) làm
runtimeVersionđổifb5cb0fa…→12b2aeb4…→7acadc72…; đổi riêng JS thì giữ nguyên. - Publish OTA lên channel
stagingthành công, runtime khớp APK. - Codex review (gate 3,
git diff 8872c8b..HEAD): APPROVED, không có finding.
Chưa kiểm chứng
Xác nhận trên thiết bị Android thật: banner hiện → bấm khởi động lại → thấy code mới. Máy dev không có thiết bị cắm vào và không có emulator. Đang nhờ chủ dự án test tay trên máy thật.
Hai commit thử nghiệm (đổi chuỗi Deposit để test bằng mắt) đã được revert, nhánh này không mang chúng.
Ngoài phạm vi lần này
Tự động publish qua GitLab CI; OTA cho production/development/ui; code signing; rollout theo phần trăm.