eCourt-note/docs/WORDADDIN-DEV.md
Your Name 4bcd01a95f
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 1m7s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 4s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
feat(wordaddin): serve manifest with automatic origin rewrite
manifest ของ add-in เดิมเป็น static ใน public/ ฝัง URL dev
(localhost:3000) ทั้งไฟล์ — deploy จริงต้อง rewrite มือเองทุกครั้ง
ก่อนนำไป sideload ใน Word

ตอนนี้ serve ผ่าน route แทน: app/wordaddin/manifest.xml/route.ts
อ่าน template (public/wordaddin/manifest.template.xml — ย้ายจาก
manifest.xml ด้วย git mv) แล้วเขียนทับ origin ให้ตรงโฮสต์ที่ขอ
ไฟล์ — dev/UAT/production ดาวน์โหลด manifest.xml ขอ environment
ตัวเองแล้ว sideload ได้ทันที

- ถอด requestOrigin จาก N10 route เป็น utils/request-origin.ts
  ใช้ร่วม + เพิ่ม heuristic: โฮสต์ไม่ใช่ localhost + proto http =
  บังคับ https (Office รับ https เท่านั้น และ hop ภายในหลัง IIS
  เป็น http เสมอ — Next server ก็ฉีด x-forwarded-proto เอง จึง
  ชี้ขาดจากโฮสต์ปลายทาง ไม่เดาว่า header มาจาก proxy หรือถูกฉีด)
  deep link ของ N10 ได้พฤติกรรมเดียวกันเป็นผลพวง
- ตรวจ nft trace แล้ว template ถูกดึงเข้า standalone ด้วย (คู่
  กับ COPY public จาก commit ก่อน = ชัวร์สองชั้น)

ยืนยัน: tsc + vitest 215 tests ผ่าน · ทดสอบบน dev server ด้วย curl
3 โหมด — direct ได้ http://localhost:3000 (Word ยกเว้น localhost),
ผ่าน proxy ส่ง header ครบได้ https://<โฮสต์>, ผ่าน proxy ไม่ส่ง
proto (กรณี IIS) ได้ https://<โฮสต์> จากการ bump · next build
compile route ผ่าน (ขั้น standalone symlink พังเฉพาะเครื่อง local
Windows สิทธิ์ symlink — ไม่เกี่ยวกับโค้ด Docker build ผ่าน)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-16 13:54:25 +07:00

11 KiB

Word Add-in (wordaddin) — คู่มือ dev และการทดสอบ

Taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตมาเป็น route ของ Next.js ที่ /docedit/wordaddin (ต้นฉบับ webpack + Fluent UI: D:\word-addin-poc\addin) — spec พฤติกรรม UI ทั้งหมดอยู่ที่ docs/addin-ui-flow.md

ทางเข้า: เปิด /docedit เปล่า ๆ จะ redirect มาที่ taskpane ทันที (ไม่ผ่าน login — คำขอผู้ใช้ 2026-10-14) ยกเว้นลิงก์ e-Court /docedit/?fileUrl=&fileId=&typeId= ที่ยังส่งต่อให้ /note ตามสัญญา N8 · manifest ชี้ /docedit/wordaddin ตรง ๆ (ไม่ว่าผ่าน root เพื่อลด hop ใน webview)

สถานะช่วงนี้: auth และ API เป็น mock (login ทันที + version store เก็บ metadata ค้างข้าม session ใน localStorage ของ taskpane เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ — สลับกลับของจริงด้วยการลบ wordaddin-mock-versions หรือเคลียร์ storage ของ origin) รอ backend/auth จริง — ดูหัว "การสลับกลับของจริง" ด้านล่าง

ไฟล์หลัก

ที่ คือ
app/wordaddin/page.tsx + components/wordaddin/WordAddinGate.tsx gate โหลด Office.js (self-host) → Office.onReady → mount TaskpaneApp
app/wordaddin/manifest.xml/route.ts serve manifest พร้อม rewrite origin ตามโฮสต์ที่ขอ (อ่าน x-forwarded-proto/x-forwarded-host ก่อนแล้ว fallback host ตรง ผ่าน utils/request-origin.ts ที่แชร์กับ origin ของ deep link N10 — โฮสต์ที่ไม่ใช่ localhost บังคับ https เพราะ Office รับ https เท่านั้น)
components/wordaddin/TaskpaneApp.tsx state machine checking/signed-out/untracked/tracking + ทุก handler
wordaddin/services/* ชั้น Office.js พอร์ตดิบจากต้นฉบับ + authService/versioningService เป็น mock
public/wordaddin/manifest.template.xml template ของ manifest (ค่า default dev https://localhost:3000) — serve ผ่าน route ด้านบนเท่านั้น ไม่ได้ใช้ตรง ๆ
public/wordaddin/office-js/ Office.js runtime — สร้างโดย postinstall scripts/fetch-office-js.mjs ดาวน์โหลดฉบับล่าสุดจาก CDN ของ Microsoft (gitignore — รัน npm install ทุกครั้งหลัง clone เครื่องต้องออกอินเทอร์เน็ตตอนติดตั้ง ส่วนตัวระบบ deploy ใช้ intranet ล้วน)
wordaddin/config.ts env NEXT_PUBLIC_ADDIN_* (ตารางด้านล่าง)

Env

ตัวแปร default ใช้เมื่อไหร่
NEXT_PUBLIC_ADDIN_API_BASE_URL "" (ว่าง) ปลด mock ต่อ backend จริง (ต้องเป็น HTTPS)
NEXT_PUBLIC_ADDIN_MOCK_USERNAME "tester01" ชื่อผู้ใช้ของ mock — แสดง header + เป็น createdByUserId

รัน dev ด้วย HTTPS (จำเป็นสำหรับ sideload ใน Word)

manifest ต้องชี้ https — 2 ทางเลือก:

  1. self-signed ของ Next (ง่ายสุด): npm run dev:https → https://localhost:3000 — Word อาจเตือน certificate ครั้งแรก (เปิด URL ใน browser กดผ่านก่อน 1 ครั้ง)
  2. ใบรับรอง trusted (แนะนำ ไม่มี warning): รัน npx office-addin-dev-certs install ครั้งเดียว แล้ว copy %USERPROFILE%\.office-addin-dev-certs\localhost.crt + localhost.key มาที่ certs/ (gitignore แล้ว) แล้วใช้ npm run dev:https:cert

Sideload ใน Word Desktop (Windows)

  1. รัน dev server (https) แล้วดาวน์โหลด manifest: https://localhost:3000/docedit/wordaddin/manifest.xml
  2. เอาไฟล์ manifest ไว้โฟลเดอร์ใดโฟลเดอร์หนึ่ง เช่น %USERPROFILE%\Documents\wordaddin-manifest\
  3. Word: File > Options > Trust Center > Trust Center Settings > Trusted Add-in Catalogs เพิ่มโฟลเดอร์นั้น > restart Word
  4. Insert > My Add-ins > SHARED FOLDER > เปิด "e-Court Documents Versioning"

Deploy จริง (intranet): ไม่ต้อง rewrite อะไรแล้ว — manifest serve ผ่าน app/wordaddin/manifest.xml/route.ts ซึ่งเขียนทับ origin ให้ตรงโฮสต์ที่ขอไฟล์อัตโนมัติ ดาวน์โหลด https://<โฮสต์>/docedit/wordaddin/manifest.xml แล้วใช้ sideload ได้ทันทีทุก environment (dev/UAT/production) · proxy ฝั่งหน้าควรส่ง X-Forwarded-Host/X-Forwarded-Proto ตามมาตรฐาน — ถ้าไม่ส่ง route ก็ยังใช้ Host ของ request และบังคับ https ให้โฮสต์ที่ไม่ใช่ localhost อยู่ดี

ทดสอบใน browser ธรรมดา (ไม่ต้องเปิด Word)

  • /docedit/wordaddin — เส้นทางจริง: Office.js โหลดได้แม่นนอก host (host เป็น null) → เห็น signed-out → กด Sign in (mock ผ่านทันที) → detect พังที่ Word.run → bootError + ปุ่มลองใหม่ (ตรวจ state machine/handler ได้ครบ)
  • /docedit/wordaddin?addindemo=untracked / ?addindemo=tracking — dev-only: ข้าม Office.js + sign in mock ทันที + ตั้ง phase ตามค่า (tracking มี history ตัวอย่าง 3 รายการ) — ทุกปุ่มกดได้ แต่ flow ที่แตะ Word จริง (Start Versioning / Save & Version / บันทึก) จะจบที่ error ตาม expected
  • หมายเหตุ: ใน browser ปกติ (ไม่ใช่ demo) banner "บันทึกอัตโนมัติไม่สำเร็จ" จะโผล่หลัง ~30 วิ เพราะ auto-save (FR-17) พยายาม save ทุก 30 วิแต่ไม่มี Word — ถือว่าถูกต้อง

ทดสอบ e-Court pass-through (ข้าม login)

ไฟล์ที่ e-Court stamp custom properties token และ/หรือ owner ไว้ จะเข้าใช้ได้เลยโดยไม่ต้องกด Sign in — detect() อ่านค่าทั้งสองตอนเปิด task pane แล้วเรียก authService.signInFromDocument() (username ได้จาก owner → ถอด uid ใน JWT payload → NEXT_PUBLIC_ADDIN_MOCK_USERNAME, token จากไฟล์เก็บใน memory ให้ getAccessToken() ใช้ต่อ)

ทดสอบเองใน Word โดยไม่ต้องรอ e-Court: เปิดไฟล์ → File → Info → Properties → Advanced Properties → แท็บ Custom → เพิ่ม property ชื่อ token (ค่า JWT ตัวอย่าง eyJ... — payload มี uid ก็แสดงเป็นชื่อผู้ใช้ได้) และ/หรือ owner (เช่น u1001) → เปิด task pane ใหม่ → ต้องเข้าเฟส untracked/tracking ทันทีไม่ผ่านหน้า login

การสลับกลับของจริง (วันหน้า)

  • Auth: wordaddin/services/authService.ts เป็น mock คง surface เดิมครบ (login/getAccessToken/refreshAccessToken/getUsername/signOut/startAutoRefresh/...) — เขียน implementation จริงทับ (ต้นฉบับ Keycloak PKCE + Office Dialog API อยู่ที่ D:\word-addin-poc\addin\src\services\authService.ts; ถ้าใช้ ต้องเพิม DialogAPI กลับเข้า manifest + หน้า auth-start/auth-callback) · signInFromDocument (pass-through จาก token/owner ในไฟล์) เป็น mock-only — วันหน้า token ในไฟล์ควรเป็นแค่ตัวจับคู่เอกสาร ส่วนสิทธิ์ผู้ใช้พิสูจน์ตอน runtime
  • API: wordaddin/services/versioningService.ts เก็บชื่อ/ลายเซ็นเหมือนต้นฉบับเป๊ะ — ก้อบ body เดิม (multipart POST ผ่าน apiClient ซึ่งพอร์ตไว้แล้ว) กลับมา + ตั้ง NEXT_PUBLIC_ADDIN_API_BASE_URL
  • ชุด unit test ของ services (jest → vitest) ยังไม่พอร์ต — ต้นฉบับอยู่ที่ D:\word-addin-poc\addin\src\services\*.test.ts + mock ที่ src/test/

Docker

ไม่ต้องทำอะไรเพิ่ม — docker/web.Dockerfile รัน scripts/fetch-office-js.mjs ใน build อยู่แล้ว (build stage ต้องออกอินเทอร์เน็ตถึง appsforoffice.microsoft.com ได้) และ runner stage คัดลอก public/ เข้า image เอง (COPY /app/public ./public — Next 14.2 standalone ไม่รวม public/ ให้อัตโนมัติ ต้อง copy เองตามเอกสาร deploy ของ Next; เคยเข้าใจผิดว่า Next รวมให้ เพราะบางไฟล์หลุดเข้า image ผ่าน output file tracing ของ Next ที่ตาม fs.readFile ใน server code — แต่ public/wordaddin/office-js ไม่มีใครอ่านผ่าน fs จึงไม่ถูกดึงมา แล้ว taskpane พังใน production 2026-10-16)

ทำไมต้องเป็นฉบับ CDN ล่าสุด: แพ็กเกจ npm @microsoft/office-js เลิกอัปเดตที่ 1.1.110 (~2018) ก่อน WordApi 1.4/1.5 — runtime ตัวนั้นไม่มี Document.close (ปุ่ม "บันทึกเวอร์ชันและปิดเอกสาร" พัง: context.document.close is not a function) และไม่มี enum Word.CloseBehavior/SaveBehavior ทับ ๆ กับ @types/office-js ฉบับใหม่ที่ใช้ compile