Skip to content

4.1.3 · State/styling/locale + Security/Performance + Scorecard


State — local เท่านั้น ไม่มี global state library

state ทั้งหมดอยู่ 3 ที่ — this.state (component), localStorage (token/refreshToken ถ้า remember, profile, noti counter — ข้าม tab+วัน), sessionStorage (token ถ้าไม่ remember — เฉพาะ tab) ไม่มี "global loading"/"global error banner" — แต่ละ page จัดการเอง, error ผ่าน alertMessage()/message.error() เป็น toast

Key ที่ใช้บ่อยใน storage — token, refreshToken, isRemember, appUsername, sessionID, lsid, organization, IsTrial, profileDetailPW/profileDetailUN (encrypted password/username สำหรับ remember-me)

กฎเหล็ก — ห้ามอ่าน localStorage ตรงๆ ใช้ getToken()/setToken()/clearAllStorage() เสมอ; ห้ามเก็บ object โดยไม่ JSON.stringify


Styling — ผสม 4 แบบ

  1. Global CSS จาก SCSS (assets/sass/main.scss 1,764 บรรทัด compile ด้วย Prepros manual → assets/css/main.css 4,565 บรรทัด)
  2. AntD override compiled (config/custom-antd.css 21,190 บรรทัด — ใหญ่ที่สุดใน repo ห้ามแก้ตรง เป็น output จาก build)
  3. Utility CSS สั้นๆ (config/styles.css 128 บรรทัด)
  4. styled-components ใช้น้อย ส่วนใหญ่ใน Login page

ลำดับความสำคัญ — AntD default → custom-antd.cssmain.cssstyles.css → styled-components inline ฟอนต์ default คือ Kanit-Medium


Locale — พ.ศ. กระจาย 3 ทาง

  1. moment.locale('th') (src/index.js:10-12) — .format('LL') ได้ พ.ศ. อัตโนมัติ แต่ .format('YYYY') ยังได้ ค.ศ.
  2. override moment.fn.format/moment.fn.year แบบ +543 ถูก comment ไว้ (src/index.js:18-33)
  3. helper 22 ตัวใน utils/formatdate.js (442 บรรทัด) ที่ manual +543 — เช่น changeFormatdate(), changeYear()

กฎเหล็ก — ห้าม +543 ตรงๆ ใน component ใช้ helper เสมอ; ห้ามใช้ moment().year() ตรงๆ เพื่อแสดงผล ปัญหาที่พบ — monthThai array ประกาศซ้ำใน 7+ function (ควร refactor เป็น constant กลาง)

Axios instances หลายตัวสำหรับ REST — AxiosBO() (instance หลัก มี interceptor refresh token เมื่อ 401), axiosAPI.js (ใช้ใน Login/SSO), axios-i3.js (I3 internal services)

dangerouslySetInnerHTML พบใน 20+ ไฟล์ (render news/policy/notification content จาก server) — ไม่พบ sanitizer ใดๆ ในระบบ (ดู S-4 ด้านล่าง)


Security findings

IDปัญหาไฟล์ความเสี่ยง
S-1Token เก็บใน localStorage/sessionStorageutils/auth.js:4-35XSS → ขโมย token ได้ทันที ถ้าจะแก้ต้อง migrate เป็น HttpOnly cookie (กระทบทั้ง frontend+backend)
S-2apiKey เข้ารหัส base64 ในบันเดิลutils/apiKey.js:1-12atob() decode ได้ค่าจริงทันที — apiKey จริง (fd77b56f-...) ฝังอยู่ในบันเดิล ระบุ APP_TYPE: WEB ไม่ใช่ authentication จริง
S-3AES key + IV เดียวกัน hardcoded สำหรับ "remember password"utils/auth.js:37-47 ("6A45soD%9eB#")ใครเปิดบันเดิลถอดรหัส password ที่เก็บใน localStorage ได้ทันที — IV ต้องสุ่มและต้องไม่ตรงกับ key
S-4dangerouslySetInnerHTML ไม่มี sanitizer20+ ไฟล์ถ้า content จาก server มี <script>/onerror → XSS ควรเพิ่ม DOMPurify.sanitize()
S-5isShow เป็นเพียง flag ฝั่ง UI ไม่ใช่ auth gateconfig/routes.jsผู้ใช้ไม่มีสิทธิ์เข้า route ได้ (แต่ query จะ FORBIDDEN ที่ server) — ดู 4.1.2
S-6Console override ปิดการ debug productioncontainers/App/index.js:15ควร strip ด้วย terser drop_console ตอน build แทน override runtime
S-7Google Analytics ID เปิดเผยpublic/index.html:33-38public ID โดยธรรมชาติ แต่เปิดช่องให้ spam event เข้า GA
S-8ไม่มี Content Security Policynginx-config/default.confป้องกัน XSS/data exfiltration ยาก

Performance findings

IDปัญหารายละเอียด
P-1Bundle ใหญ่ ~12 MB (build/ รวม 79 MB)ไม่มี code splitting ระดับ route, AntD v3 import ทั้ง library, ใช้ PDF library 4 ตัวซ้อนกัน (@react-pdf/renderer, react-pdf, @react-pdf-viewer, pdfjs-dist), custom-antd.css 21,190 บรรทัดไม่ minify
P-2fetchPolicy: 'no-cache' ทุก queryทุกการเปลี่ยนหน้าดึงข้อมูลใหม่จาก server เสมอ — แก้ได้เฉพาะจุดด้วย fetchPolicy: 'cache-first' สำหรับ dropdown master data
P-3SiderLayout query profile ทุกครั้งที่ reloadทำให้หน้าแรกช้ากว่าที่ควร — แก้ด้วย cache + stale-while-revalidate
P-4ไม่มี lazy loading รูปภาพทุกรูปโหลดทันทีตอน render
P-5Webpack 4 (ไม่มี webpack 5 features)upgrade เป็น breaking change
P-6console.log กระจายหลายสิบจุดเพิ่มขนาดบันเดิลแม้ runtime จะปิดไว้ — ควร strip ด้วย terser
P-7PDF.js โหลด worker แยกเพิ่ม 1 request ทำให้หน้า Slip ช้ากว่าหน้าอื่น

Scorecard

มิติคะแนนหมายเหตุ
Maintainability5/10pattern สม่ำเสมอ แต่ legacy 50+ ไฟล์ + bundle ใหญ่
Security4/10token ใน localStorage + AES key hardcoded + ไม่มี HTML sanitizer
Performance4/10bundle ~12MB + no-cache ทุก query + ไม่มี code splitting
Developer Experience5/10pattern ชัดเจนแต่ console ถูกปิด + build ช้า
Documentation3/10แทบไม่มี inline comment อธิบาย intent
Test coverage1/10ไม่มี unit/integration test
Type safety1/10JavaScript ล้วน ไม่มี TypeScript
Accessibility3/10AntD v3 มี a11y พื้นฐานแต่ไม่มี ARIA เพิ่ม
รวม3.5/10ทำงานได้จริง แต่หนี้เทคนิคสะสมสูง

จุดแข็ง — pattern สม่ำเสมอ (class+withApollo ทั้งระบบ), GraphQL operations รวมในไฟล์เดียว, runtime env resolver (bundle เดียวใช้หลาย env), error handling รวมศูนย์ที่ errorLink, route table ชัดเจน

Known issues (W-1 ถึง W-10) และลำดับการแก้

Phase 1 — quick win (1-2 วัน): console override → terser drop_console; ลบ helper toGqlTypes/Args/Fields ที่ไม่ใช้; เพิ่ม <ErrorBoundary> ครอบ <App>; เลิกใช้ AES remember-password

Phase 2 — security (1 สัปดาห์): เพิ่ม DOMPurify ก่อน dangerouslySetInnerHTML; เพิ่ม CSP header ที่ nginx; ย้าย apiKey ไป server-side proxy

Phase 3 — performance (2-4 สัปดาห์): babel-plugin-import สำหรับ AntD (ROI สูงสุดด้วย effort น้อยที่สุด); ปรับ fetchPolicy ของ dropdown query; รวม constant เดือนไทยกลาง

Phase 4 — long-term (1-3 เดือน): archive ไฟล์ legacy 50+ ไฟล์; setup jest + เขียน test ของ utils/ (pure function) ก่อน; พิจารณาย้าย TypeScript/React 17+ (breaking change ใหญ่ ต้องมีแผนแยก)

เกณฑ์ก่อนแก้ไฟล์ใดใน vtrc-web

  1. ไฟล์นี้เป็นของจริงไหม (เช็คใน routes.js)
  2. pattern ตรงไหม (class + withApollo + Form.create ถ้ามี form)
  3. endpoint ใช้ checkLinkURL() ไหม (ห้าม hardcode)
  4. error handling ผ่าน errorLink ไหม (ห้าม catch เอง)
  5. state ใช้ this.state+localStorage ผ่าน helper ไหม (ห้าม Redux/Context)
  6. locale ใช้ helper ใน formatdate.js ไหม (ห้าม +543 ตรงๆ)