vtrc-mobile
vtrc-mobile (branch: master, 1055 tracked files) คือแอปมือถือหลักของพนักงานสภากาชาดไทย — React Native ที่ดูแลมายาวนานที่สุดในกลุ่ม Mobile และมี business logic กว้างที่สุด (120 container ระดับบน + 21 ไฟล์ใน meeting-management/ = 141 ไฟล์ใต้ src/containers/, scene key ใน App.js มากกว่า 120 รายการ)
เทียบกับ sibling ใน Volume 8:
| Repo | บทบาท | Screen / ขอบเขต |
|---|---|---|
vtrc-mobile (master) | Production ESS app | 121+ scene, leave / welfare / slip / PMS / recruitment / meeting |
new-vtrc-mobile | Rewrite ระยะต้น | screen น้อยมาก (ดู new-vtrc-mobile) |
vtrc-application-front | Landing / stub | ไม่มี screen business จริง |
ชื่อ package ภายในคือ rn_redcross ไม่ใช่ vtrc-mobile — เป็นชื่อตอน scaffold ที่ไม่เคยถูกเปลี่ยน (vtrc-mobile/package.json:2, vtrc-mobile/app.json:1-4)
Stack
| องค์ประกอบ | ค่า | หลักฐาน |
|---|---|---|
| React Native | 0.63.2 | vtrc-mobile/package.json:64 |
| React | 16.13.1 | vtrc-mobile/package.json:63 |
| Navigation | react-native-router-flux ^4.2.0 (Actions.* imperative, ไม่ใช่ @react-navigation) | vtrc-mobile/package.json:99 |
| State | @reduxjs/toolkit ^1.4.0 + redux-persist ^6.0.0 | vtrc-mobile/package.json:31,124 |
| Persist storage | react-native-sensitive-info ผ่าน custom adapter (keychain / SharedPreferences) — ไม่ใช่ AsyncStorage สำหรับ token | vtrc-mobile/src/app/store.js:9,55-58, vtrc-mobile/src/app/redux-persist-sensitive-storage.js:1-38 |
| GraphQL client | @apollo/client ^3.1.3 มีอยู่ แต่ path หลักยิงผ่าน react-native-ssl-pinning ใน utils/fetch.js | vtrc-mobile/package.json:19, vtrc-mobile/src/gql/client.js:1-27, vtrc-mobile/src/utils/fetch.js:1-48 |
| REST | axios + fetchRestFull.js + meetingAPI | vtrc-mobile/package.json:38, vtrc-mobile/src/utils/fetchRestFull.js:1-36, vtrc-mobile/src/utils/axios.js:1-23 |
| Push | @react-native-firebase/messaging ^7.8.6 + react-native-push-notification + iOS community push | vtrc-mobile/package.json:27,30,95 |
| Biometric | react-native-biometrics ^2.1.4 | vtrc-mobile/package.json:65, vtrc-mobile/src/features/authSlice.js:953-1062 |
| RSA (csId) | crypto (Node polyfill) + PEM ใน login.js | vtrc-mobile/src/utils/login.js:6-74 |
| RSA (biometric keypair) | react-native-rsa-native สร้างคู่กุญแจตอนเปิด biometric ใน Setting | vtrc-mobile/src/containers/Setting.js:36-43 |
| Test | 1 ไฟล์ template (__tests__/App-test.js) | vtrc-mobile/__tests__/App-test.js:1-14 |
| Package manager | yarn (yarn.lock) | vtrc-mobile/yarn.lock |
Entrypoint
ลำดับ boot:
index.js
├── import gesture-handler / reanimated
├── polyfills.js + performanceShim.js + shim.js (Node crypto/stream polyfill)
├── initializeNotificationHandlers() (ก่อน register component)
└── AppRegistry.registerComponent(rn_redcross)
│ `vtrc-mobile/index.js:1-23`, `vtrc-mobile/app.json:1-4`
▼
App.js
Provider(store) → PersistGate → SafeAreaProvider → Router/Stack/Scene×120+
`vtrc-mobile/App.js:178-1071`PersistGate.onBeforeLift (vtrc-mobile/App.js:194-224) ทำสามอย่างก่อน mount UI จริง:
- ถ้าไม่มี key
firstRunใน AsyncStorage →persist/PURGEแล้วตั้งfirstRun=true(App.js:195-200) - เช็ก jailbreak/root ด้วย
jail-monkey— ถ้าเจอและไม่ใช่ dev mode จะบังคับRNExitApp.exitApp()(App.js:202-208) - เรียก
refreshToken(store.getState().auth.token)เพื่อ auto-login (App.js:210)
บล็อก fetchCheckUpdateVersion ใน onBeforeLift ถูก comment ปิดทั้งหมด (App.js:213-224) — ฟีเจอร์บังคับอัปเดตตอน cold start ไม่ทำงานจากจุดนี้ (หน้า CheckLogin ยังเรียกเองแยก — ดู 02-core-pipeline)
App.js ยาว 1,071 บรรทัด — import container ทั้งชุดแล้วประกาศ {Scene} แบบ flat ในไฟล์เดียว ไม่มี nested navigator / lazy load (vtrc-mobile/App.js:15-176 import, 228-1062 scenes)
Build config
| แพลตฟอร์ม | ค่า | หลักฐาน |
|---|---|---|
Android applicationId | com.redcross.vtrc | vtrc-mobile/android/app/build.gradle:149 |
Android versionName / versionCode | 1.9.12 / 139 | vtrc-mobile/android/app/build.gradle:154-155 |
| Android SDK | min 23 / compile 34 / target 35 | vtrc-mobile/android/build.gradle:6-8 |
| Android release signing | path + password hardcode ในซอร์ส ชี้เครื่อง dev คนหนึ่ง | vtrc-mobile/android/app/build.gradle:174-179 — 04-scorecard § SEC-MOBILE-02 |
| Hermes | ปิด (enableHermes: false) | vtrc-mobile/android/app/build.gradle:81-82 |
| iOS deployment target | 12.0 | vtrc-mobile/ios/Podfile:5 |
| iOS target name | rn_redcross | vtrc-mobile/ios/Podfile:6 |
| SSL pin cert asset | android/app/src/main/assets/digicert2025.cer | ใช้ชื่อ digicert2025 ใน fetch.js:36 |
Scripts สำคัญใน package.json:5-16: start, ios / run-ios, run-android, build:apk-release, aab-ios (ชื่อสคริปต์ทำให้เข้าใจผิด — รัน Gradle Android จริง), postinstall รัน patch-package + rebuild react-native-sensitive-info + rn-nodeify
การเชื่อมต่อ backend
ต่างจาก vtrc-web ที่เลือก GraphQL endpoint ตอน runtime ด้วย checkLinkURL() จาก window.location.href — vtrc-mobile ไม่มี runtime resolver Endpoint เป็นค่าคงที่ในซอร์ส ต้อง comment/uncomment แล้ว rebuild ทุกครั้งที่สลับ PROD/UAT/local
ค่าคงที่ชุดเดียวกันถูกประกาศซ้ำใน 3 ไฟล์ที่ไม่ประสานกัน (path suffix ไม่ตรงกันจริง):
| ไฟล์ | ตัวแปรหลัก | ค่า PROD ที่ uncomment อยู่ |
|---|---|---|
src/config/index.js:3-4 | Config.API | https://vtrcapi.redcross.or.th/api/graphql |
src/constants/HostApi.js:21-23 | HOST, HOSTGRAHQL | host + /api (ไม่มี /graphql) |
src/utils/hostUrl.js:4-5 | HOST | host อย่างเดียว (ไม่มี path) |
นอกจากนี้ Config.MEETING_API hardcode ชี้ UAT เสมอ (src/config/index.js:12) — ดู 02-core-pipeline และ 04-scorecard § CORR-MOBILE-01 / LEG-MOBILE-02
Backend ที่แอปคุยด้วย (ยืนยันจากโค้ด):
vtrc-mobile
├── GraphQL → Config.API (vtrc-api) via fetch.js SSL pin
├── GraphQL → HOSTGRAHQL/graphql (multipart upload) via actions/withdrawSlice.js
├── REST → HOST/SSOService/* (SSO) via actions/ssoSlice.js
├── REST → HOSTRECRUITMENT/* (recruitment-api) via actions/recruitment.js
├── REST → HOSTWORKFORCE/* (workforce) via actions/userSlice.js
├── REST → Config.MEETING_API (meeting, UAT host) via meetingService.js
└── WebView → HOST/graphql/conicle/* via containers/Conicle.js + hostUrl.jsAuth model (สรุป)
ทุก GraphQL request ผ่าน utils/fetch.js แนบ:
Authorization: Bearer {accessToken}จาก reduxauth.token(ถ้ามี)apiKey: 346155c3-cea8-41f3-a9a0-305d0cc7ccf4(production device key hardcode)
ดูรายละเอียด login / refresh / biometric / token storage ที่ 02-core-pipeline
สิ่งที่พบซึ่งกระทบทั้งแพลตฟอร์ม (candidate canonical truths)
- RSA private key (PEM) hardcode ใน client bundle —
vtrc-mobile/src/utils/login.js:17-42และถูก copy ไปnew-vtrc-mobile(ดู scorecard ของ sibling) — SEC-MOBILE-01 - ไม่มี
checkLinkURL()pattern — environment switch = edit source + rebuild - Host config drift 3 ไฟล์ —
Config.APIvsHOSTGRAHQLvshostUrl.HOSTpath ไม่ตรงกันแล้วในซอร์สปัจจุบัน - Token auth อยู่ sensitive storage; biometric private key อยู่ AsyncStorage key
Encode— ชั้นความปลอดภัยไม่เท่ากัน (store.jsvssetting.js:90-105) - Meeting feature ใน production build ยิง UAT meeting host —
Config.MEETING_APIไม่มี branch prod
สารบัญ
| บท | หัวข้อ |
|---|---|
| 01 | Repository map — โครงสร้าง src/, containers, gql, state, native |
| 02 | Endpoint drift, auth/login, token refresh, RSA, AsyncStorage vs keychain |
| 03 | แผนที่ฟีเจอร์ — cluster ตาม domain + scene keys + notification deep-link |
| 04 | Scorecard + debt ID (SEC / LEG / CORR / QUAL / OBS) |
สถิติ repo (master ณ เวลาเขียน)
| เมตริก | ค่า | วิธีนับ |
|---|---|---|
| Tracked files | 1055 | git ls-files |
ไฟล์ใต้ src/ | ~610 | find src -type f |
| Containers (top + meeting) | 120 + 21 | ls src/containers |
| Components | 218 | find src/components -type f |
| Features slices | 14 | ls src/features |
| Actions helpers | 6 | ls src/actions |
| GQL query / mutation files | 14 / 7 | ls src/gql/query / mutation |
App.js | 1071 บรรทัด | wc -l |
authSlice.js | 1084 บรรทัด | wc -l |
| Android version | 1.9.12 (139) | android/app/build.gradle:154-155 |
เส้นอ่านแนะนำ
- อ่านบทนี้ให้จบ stack + entrypoint + candidate truths
- 01-repository-map — หาไฟล์ก่อนแตะโค้ด
- 02-core-pipeline — บังคับ ถ้าแตะ login / host / RSA / token
- 03-features-map — หา cluster ก่อนแก้ UI
- 04-scorecard — ก่อนเสนอ refactor หรือ security fix
ดูภาพรวมกลุ่ม Mobile ที่ Volume 8 index.