Refactor Word Add-in authentication to use e-Court pass-through; remove Keycloak integration
- Updated documentation to reflect removal of login and transition to e-Court pass-through authentication. - Modified `authService` to handle authentication via custom properties in documents without user login. - Adjusted `apiClient` to immediately throw `AuthRequiredError` for invalid tokens, as refresh tokens are no longer applicable. - Revised UI flow to accommodate new authentication method, including changes to state management and error handling. - Added new requirements document outlining the add-in workflow and authentication process.
This commit is contained in:
parent
6ce071ee14
commit
50af6c10fa
9 changed files with 161 additions and 197 deletions
|
|
@ -4,7 +4,7 @@ Taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตม
|
|||
|
||||
**ทางเข้า:** เปิด `/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 จริง — ดูหัว "การสลับกลับของจริง" ด้านล่าง
|
||||
สถานะช่วงนี้: **ไม่มี login แล้ว** (ยกเลิก Keycloak 2026-10-27 — auth เข้าทางเดียวคือ e-Court pass-through จาก token/owner ในไฟล์ ดูหัว "ทดสอบ e-Court pass-through") ส่วน **API ยังเป็น mock** (version store เก็บ metadata ค้างข้าม session ใน localStorage ของ taskpane เพื่อทดสอบวงปิดเอกสาร→เปิดใหม่ — สลับกลับของจริงด้วยการลบ `wordaddin-mock-versions` หรือเคลียร์ storage ของ origin) รอ backend จริง — ดูหัว "การสลับกลับของจริง" ด้านล่าง
|
||||
|
||||
## ไฟล์หลัก
|
||||
|
||||
|
|
@ -12,8 +12,8 @@ Taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตม
|
|||
|---|---|
|
||||
| `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 |
|
||||
| `components/wordaddin/TaskpaneApp.tsx` | state machine `checking/no-access/untracked/tracking` + ทุก handler |
|
||||
| `wordaddin/services/*` | ชั้น Office.js พอร์ตดิบจากต้นฉบับ + `versioningService` เป็น mock (`authService` = pass-through จริง ไม่มี login) |
|
||||
| `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_*` (ตารางด้านล่าง) |
|
||||
|
|
@ -23,7 +23,7 @@ Taskpane ของ Word add-in "e-Court Documents Versioning" พอร์ตม
|
|||
| ตัวแปร | default | ใช้เมื่อไหร่ |
|
||||
|---|---|---|
|
||||
| `NEXT_PUBLIC_ADDIN_API_BASE_URL` | `""` (ว่าง) | ปลด mock ต่อ backend จริง (ต้องเป็น HTTPS) |
|
||||
| `NEXT_PUBLIC_ADDIN_MOCK_USERNAME` | `"tester01"` | ชื่อผู้ใช้ของ mock — แสดง header + เป็น `createdByUserId` |
|
||||
| `NEXT_PUBLIC_ADDIN_MOCK_USERNAME` | `"tester01"` | ชื่อ fallback — ใช้ตอนไฟล์มี token แต่ไม่มี owner/uid และโหมด `?addindemo` (แสดง header + เป็น `createdByUserId` ของ mock) |
|
||||
|
||||
## รัน dev ด้วย HTTPS (จำเป็นสำหรับ sideload ใน Word)
|
||||
|
||||
|
|
@ -52,20 +52,20 @@ Deploy จริง (intranet): **ไม่ต้อง rewrite อะไรแ
|
|||
|
||||
## ทดสอบใน 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
|
||||
- **`/docedit/wordaddin`** — เส้นทางจริง: Office.js โหลดได้แม้นอก host (host เป็น null) → `detect()` อ่าน custom properties ไม่ได้ (ไม่มี `Word.run`) → เห็น `no-access` — ตรวจ state machine ได้แค่นี้ใน browser ปกติ ส่วน phase `untracked`/`tracking` ต้องใช้ demo ด้านล่าง (หรือ sideload ใน Word จริง)
|
||||
- **`/docedit/wordaddin?addindemo=untracked`** / **`?addindemo=tracking`** — dev-only: ข้าม Office.js + ตั้ง session 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 pass-through (auth ทางเข้าเดียว)
|
||||
|
||||
ไฟล์ที่ 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()` ใช้ต่อ)
|
||||
ไฟล์ที่ e-Court stamp custom properties `token` และ/หรือ `owner` ไว้ จะใช้ได้เลย — ไม่มี login ให้กดตั้งแต่ยกเลิก Keycloak (2026-10-27): `detect()` อ่านค่าทั้งสองตอนเปิด task pane แล้วเรียก `authService.signInFromDocument()` (username ได้จาก `owner` → ถอด `uid` ใน JWT payload → `NEXT_PUBLIC_ADDIN_MOCK_USERNAME`, token จากไฟล์เก็บใน memory ให้ `getAccessToken()` ใช้ต่อ) · ไฟล์ที่ไม่มีทั้งคู่ = phase `no-access` (ขั้นตอนสำหรับเอกสารทั่วไปจะมีในอนาคต)
|
||||
|
||||
ทดสอบเองใน Word โดยไม่ต้องรอ e-Court: เปิดไฟล์ → **File → Info → Properties → Advanced Properties → แท็บ Custom** → เพิ่ม property ชื่อ `token` (ค่า JWT ตัวอย่าง `eyJ...` — payload มี `uid` ก็แสดงเป็นชื่อผู้ใช้ได้) และ/หรือ `owner` (เช่น `u1001`) → เปิด task pane ใหม่ → ต้องเข้าเฟส untracked/tracking ทันทีไม่ผ่านหน้า login
|
||||
ทดสอบเองใน Word โดยไม่ต้องรอ e-Court: เปิดไฟล์ → **File → Info → Properties → Advanced Properties → แท็บ Custom** → เพิ่ม property ชื่อ `token` (ค่า JWT ตัวอย่าง `eyJ...` — payload มี `uid` ก็แสดงเป็นชื่อผู้ใช้ได้) และ/หรือ `owner` (เช่น `u1001`) → เปิด task pane ใหม่ → ต้องเข้าเฟส untracked/tracking ทันทีไม่ผ่านหน้า no-access · ไฟล์ที่ไม่ใส่อะไรเลย = หน้า no-access
|
||||
|
||||
## การสลับกลับของจริง (วันหน้า)
|
||||
|
||||
- **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`
|
||||
- **Auth:** ไม่มี login ให้สลับแล้ว — **ยกเลิก Keycloak ถาวร 2026-10-27** (ต้นฉบับ OIDC/PKCE + Office Dialog API ของ poc เป็น historical record ที่ `D:\word-addin-poc\addin` เท่านั้น) · auth ของ add-in คือ e-Court pass-through ผ่าน `signInFromDocument` (token/owner ในไฟล์) — งานต่อยอดคือรูปแบบ/วิธี verify ของ credential ที่ e-Court stamp (ร่าง: `docs/requirement/addin-workflow.md`) · โดน 401 กลาง flow = `AuthRequiredError` → หน้า `no-access` (token จากไฟล์ต่ออายุไม่ได้ — ต้องได้ไฟล์ใหม่จาก e-Court)
|
||||
- **API:** `wordaddin/services/versioningService.ts` เก็บชื่อ/ลายเซ็นเหมือนต้นฉบับเป๊ะ — ก้อบ body เดิม (multipart POST ผ่าน `apiClient` ซึ่งพอร์ตไว้แล้ว) กลับมา + ตั้ง `NEXT_PUBLIC_ADDIN_API_BASE_URL` (contract ปัจจุบันของกลุ่ม docver สรุปไว้ใน docblock ของไฟล์นั้น + docs/PLAN-DOCVER.md — identity/docId แกะจาก Bearer token ทั้งหมด)
|
||||
- ชุด unit test ของ services (jest → vitest) ยังไม่พอร์ต — ต้นฉบับอยู่ที่ `D:\word-addin-poc\addin\src\services\*.test.ts` + mock ที่ `src/test/`
|
||||
|
||||
## Docker
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@
|
|||
|
||||
Task pane ทั้งหมดเป็น state machine เดียว **ไม่มี routing** — component เดียว (`App`) สลับหน้าตาตาม `phase`:
|
||||
|
||||
ตั้งแต่ 2026-10-19 taskpane มี **2 แท็บ** (toggle กลุ่มปุ่ม `aria-pressed` ใต้ header — แสดงเฉพาะ phase `untracked`/`tracking`): **Versioning** คือทุกอย่างในเอกสารนี้ตามเดิม ส่วน **เครื่องมือ Template** (ดูหัวข้อ 10) คือเครื่องมือจัดการ Content Controls ฝั่ง Office.js ล้วน ไม่ยิง backend จึงใช้ได้ทั้งสอง phase ปุ่ม toggle **ทั้งสองปิดระหว่าง flow ที่เขียนเอกสาร** — `saving || starting` (versioning), `saveBusy` (บันทึก local/ปิดเอกสาร), `templateBusy` (operation ของแท็บ template รายงานผ่าน `onBusyChange` — กันสลับกลาง operation แล้ว flow versioning มา stamp/capture ซ้อน หรือกลับกัน; code review 2026-10-20) · สถานะแท็บรีเซ็ตกลับ `versioning` ตอน sign out / AuthRequiredError กัน surprise หลัง login ใหม่ · banner auto-save error (FR-17) ยกไว้เหนือ toggle ให้เห็นทั้งสองแท็บ
|
||||
ตั้งแต่ 2026-10-19 taskpane มี **2 แท็บ** (toggle กลุ่มปุ่ม `aria-pressed` ใต้ header — แสดงเฉพาะ phase `untracked`/`tracking`): **Versioning** คือทุกอย่างในเอกสารนี้ตามเดิม ส่วน **เครื่องมือ Template** (ดูหัวข้อ 10) คือเครื่องมือจัดการ Content Controls ฝั่ง Office.js ล้วน ไม่ยิง backend จึงใช้ได้ทั้งสอง phase ปุ่ม toggle **ทั้งสองปิดระหว่าง flow ที่เขียนเอกสาร** — `saving || starting` (versioning), `saveBusy` (บันทึก local/ปิดเอกสาร), `templateBusy` (operation ของแท็บ template รายงานผ่าน `onBusyChange` — กันสลับกลาง operation แล้ว flow versioning มา stamp/capture ซ้อน หรือกลับกัน; code review 2026-10-20) · สถานะแท็บรีเซ็ตกลับ `versioning` ตอนเข้า `no-access` กัน surprise · banner auto-save error (FR-17) ยกไว้เหนือ toggle ให้เห็นทั้งสองแท็บ
|
||||
|
||||
**ไม่มี login ตั้งแต่ 2026-10-27** (ยกเลิก Keycloak ตามคำขอผู้ใช้) — auth ทางเข้าเดียวคือ e-Court pass-through: `detect()` อ่าน custom properties `token`/`owner` ที่ e-Court stamp มากับไฟล์ แล้วเรียก `authService.signInFromDocument()` ผ่านเข้าเลย
|
||||
|
||||
```
|
||||
checking ──(ไม่มี token)──────────────→ signed-out
|
||||
checking ──(ไม่มี token/owner ในไฟล์)──→ no-access
|
||||
│
|
||||
├─(มี token + ไม่ใช่ tracked doc)──→ untracked
|
||||
└─(มี token + tracked doc)────────→ tracking
|
||||
|
|
@ -21,21 +23,20 @@ checking ──(ไม่มี token)──────────────
|
|||
|
||||
การเปลี่ยน phase อื่นๆ:
|
||||
|
||||
- `signed-out` → (sign in สำเร็จ) → `checking` → ตัดสินใหม่ตามไฟล์ปัจจุบัน
|
||||
- `untracked` → (Start Versioning สำเร็จ) → `tracking`
|
||||
- ทุก phase → (401 ที่ refresh token ไม่ช่วย / กดออกจากระบบ) → `signed-out`
|
||||
- ปุ่ม "ลองใหม่" มีจริงแค่ 2 จุด: `bootError` ใน `checking` และ history error ใน `VersionHistoryPanel` — `untracked`/`tracking` **ไม่มี**ปุ่มลองใหม่แยกต่างหาก (เริ่ม flow ใหม่ด้วยการกดปุ่ม flow ซ้ำ) · กลไกเดียวที่วิ่งกลับมา `checking` คือ sign-in สำเร็จ
|
||||
- ทุก phase → (ไม่มี token ตอน detect ใหม่ / 401 = token หมดอายุไม่มีทางต่ออายุ) → `no-access`
|
||||
- ปุ่ม "ลองใหม่" มีจริงแค่ 2 จุด: `bootError` ใน `checking` และ history error ใน `VersionHistoryPanel` — `untracked`/`tracking` **ไม่มี**ปุ่มลองใหม่แยกต่างหาก (เริ่ม flow ใหม่ด้วยการกดปุ่ม flow ซ้ำ)
|
||||
|
||||
State ที่ UI ถือนอกเหนือจาก phase:
|
||||
|
||||
| State | ความหมาย |
|
||||
|---|---|
|
||||
| `activeTab` | แท็บที่เปิดอยู่ `versioning` (default) / `template` — ดูหัวข้อ 10 |
|
||||
| `username` | ชื่อผู้ใช้จาก token (แสดงใน header) |
|
||||
| `username` | ชื่อเจ้าของไฟล์จาก pass-through (owner → uid ใน token → fallback) — แสดงเป็น chip เฉย ๆ ใน header |
|
||||
| `documentId` | tracking id ของเอกสาร (มีเฉพาะ `tracking`) |
|
||||
| `documentTemplateId` | template ต้นทางของ**ไฟล์ร่าง** (มีเฉพาะ `untracked` ที่เป็นไฟล์ร่าง — FR-16) |
|
||||
| `versions` + `historyLoading` + `historyError` | version history ของ `documentId` |
|
||||
| `signingIn`, `loginError` | สถานะหน้า sign-in |
|
||||
| `noAccessNotice` | ข้อความบนหน้า `no-access` — ต่างกันตามสาเหตุ (ไม่มี token/owner ในไฟล์ หรือ token หมดอายุจาก 401) |
|
||||
| `starting`, `saving`, `progressMessage` | busy + ข้อความ progress ของ flow Start Versioning / Save & Version |
|
||||
| `confirmOpen`, `startConfirmOpen` | เปิด dialog ยืนยันของ flow ไหน |
|
||||
| `flowError`, `flowWarning`, `flowSuccess` | MessageBar 3 แบบของ flow หลัก (แสดงเฉพาะ `tracking`) |
|
||||
|
|
@ -53,21 +54,20 @@ State ที่ UI ถือนอกเหนือจาก phase:
|
|||
### Header (ทุก phase)
|
||||
|
||||
- ชื่อแอป: **e-Court Documents Versioning** (semibold)
|
||||
- ฝั่งขวา (เมื่อ login แล้วและไม่ใช่ `checking`/`signed-out`): ปุ่มเล็ก icon + `ออกจากระบบ` — username อยู่ใน `title`/`aria-label` (จอแคบ ไม่กินพื้นที่แถบหัว) — กดแล้ว sign out + กลับ `signed-out`
|
||||
- ฝั่งขวา (เมื่อผ่าน pass-through แล้วและไม่ใช่ `no-access`/`checking`): chip เล็กพื้น `canvas` แสดง `username` เฉย ๆ — ไม่ใช่ปุ่ม (ไม่มี login จึงไม่มีออกจากระบบ) · จอแคบตัดด้วย truncate ชื่อเต็มอยู่ใน `title`
|
||||
|
||||
### `checking`
|
||||
|
||||
- `Spinner "กำลังตรวจสอบเอกสาร…"` ตรงกลาง
|
||||
- ถ้า detect พัง (`bootError`): MessageBar **error** + ปุ่ม `ลองใหม่` ข้างใน bar (รัน detect ใหม่)
|
||||
|
||||
### `signed-out`
|
||||
### `no-access`
|
||||
|
||||
- ข้อความ: "เข้าสู่ระบบเพื่อเริ่มใช้งาน versioning"
|
||||
- คำอธิบายจางๆ: "ใช้บัญชีของ realm `word-versioning` — หน้าต่าง login จะเปิดแยกต่างหาก"
|
||||
- ปุ่ม primary `เข้าสู่ระบบ` → ระหว่างรันเปลี่ยนเป็น `กำลังเข้าสู่ระบบ…` + disable
|
||||
- ถ้า login พัง: MessageBar **warning** (`loginError`) — เช่น "เซสชันหมดอายุ — กรุณา login ใหม่" ตอนโดน 401 กลาง flow
|
||||
|
||||
> ใน project จริง login เปิดหน้าต่างแยกผ่าน Office Dialog API (ห้าม redirect iframe ไป Keycloak ตรงๆ) — ถ้าโปรเจกต์ใหม่ยังไม่มี auth ให้ mock `login()` คืน `{ ok: true }` ไปก่อน
|
||||
- ข้อความ: "เอกสารนี้ยังใช้งาน add-in ไม่ได้"
|
||||
- คำอธิบายจางๆ (`noAccessNotice` ตามสาเหตุ):
|
||||
- ตอน detect ไม่เจอข้อมูลในไฟล์: "ไม่พบ token/owner ของ e-Court ในไฟล์ — ขณะนี้ add-in ใช้ได้กับเอกสารที่ส่งมาจาก e-Court เท่านั้น"
|
||||
- ตอนโดน 401 กลาง flow: "token ของ e-Court หมดอายุหรือไม่ถูกต้อง — โปรดขอไฟล์ใหม่จาก e-Court แล้วเปิดอีกครั้ง"
|
||||
- **ไม่มีปุ่มใด ๆ** — ไม่มี login ให้กด (ยกเลิก Keycloak 2026-10-27; ขั้นตอนสำหรับเอกสารทั่วไปจะมีในอนาคต) ผู้ใช้ได้ไฟล์ใหม่จาก e-Court แล้วเปิดใหม่เท่านั้น
|
||||
|
||||
### `untracked`
|
||||
|
||||
|
|
@ -162,10 +162,10 @@ Dialog เดียวใช้ 2 flow (ปรับข้อความด้
|
|||
|
||||
ข้อความ flow (`flowSuccess`/`flowWarning`/`flowError`) ถูกเคลียร์เป็น null ทุกครั้งที่**เริ่ม** flow ใหม่ (Start Versioning / Save & Version / ดาวน์โหลด — ไม่ใช่ตอนจบ ไม่มี timeout)
|
||||
|
||||
### Detect (FR-1) — ตอนเปิด task pane / หลัง login / กดลองใหม่
|
||||
### Detect (FR-1) — ตอนเปิด task pane / กดลองใหม่
|
||||
|
||||
1. ตรวจ token — ไม่มี → `signed-out`
|
||||
2. อ่าน custom properties ครั้งเดียวทั้งชุด: `SavedByAddIn`, `DocumentTrackingId`, `LastVersionId`, `LastVersionTimestamp`, `TemplateId`
|
||||
1. อ่าน custom properties ครั้งเดียวทั้งชุด: `token`, `owner`, `SavedByAddIn`, `DocumentTrackingId`, `LastVersionId`, `LastVersionTimestamp`, `TemplateId`
|
||||
2. e-Court pass-through: ไม่มี `token`/`owner` เลย (หรืออ่านไฟล์ไม่ได้) → `no-access` · มีอย่างน้อยหนึ่งตัว → `signInFromDocument` (username จาก owner → uid ใน token) แล้วไปต่อ
|
||||
3. `tracked = (SavedByAddIn === GUID ของ add-in)`:
|
||||
- tracked **และมี** `DocumentTrackingId` → `tracking` + โหลด history
|
||||
- ไม่ track หรือ track แต่ id หาย → `untracked` (เก็บ `TemplateId` ไว้ใช้ตอน Start Versioning — ไฟล์ร่างอยู่กลุ่มนี้)
|
||||
|
|
@ -200,7 +200,7 @@ dialog ยืนยัน → progress ทั้ง 7 ขั้นลำดั
|
|||
|
||||
### Auto-save (FR-17)
|
||||
|
||||
- เริ่มตั้งแต่ mount **ไม่รอ sign-in**
|
||||
- เริ่มตั้งแต่ mount **ไม่ผูกกับ phase** (detect ยังไม่จบก็รันได้)
|
||||
- periodic ทุก 30 วิ: ไฟล์ dirty → save + ตั้ง flag `hasUncommitted`; สะอาด → ข้าม
|
||||
- save ล้ม → `autoSaveError` (warning bar); สำเร็จครั้งถัดไป → เคลียร์ bar
|
||||
- ทุก flow versioning ต้อง **suspend** auto-save ตอนเริ่มและ **resume** ตอนจบ (กัน save แทรกกลาง stamp/capture)
|
||||
|
|
@ -251,17 +251,17 @@ interface VersionRecord {
|
|||
|
||||
response: `{ url: string, expiresAt: string }` — UI เปิด `url` ในเบราว์เซอร์เครื่องผู้ใช้เท่านั้น
|
||||
|
||||
### Auth — OIDC PKCE ผ่าน Office Dialog API
|
||||
### Auth — e-Court pass-through (ไม่มี login)
|
||||
|
||||
`displayDialogAsync` เปิดหน้า login แยก → `messageParent` ส่งผลกลับ task pane · token เก็บ **ใน memory เท่านั้น** (ห้าม localStorage) · refresh token auto-refresh เงียบๆ จน 401 → สถานะ `AuthRequiredError` · mock ช่วงรอ: `login()` คืน `{ ok: true }`, `getAccessToken()` คืน token ปลอม, `getUsername()` คืนชื่อ test
|
||||
ยกเลิก Keycloak OIDC/PKCE แล้ว (2026-10-27) — ไม่มี Office Dialog ไม่มีหน้า sign-in: e-Court stamp custom properties `token`/`owner` ลงไฟล์ตอนสร้าง แล้ว `signInFromDocument()` ใช้ได้ทันที · token เก็บ **ใน memory เท่านั้น** (ห้าม localStorage) และ**ต่ออายุไม่ได้** — โดน 401 = `AuthRequiredError` → หน้า `no-access` ให้ขอไฟล์ใหม่จาก e-Court · token นี้คือสิ่งที่ `apiClient` แนบเป็น `Authorization: Bearer` ทุก request ตอนต่อ backend จริง (กลุ่ม docver บังคับ Bearer — docs/PLAN-DOCVER.md) · **รูปแบบ/วิธี verify ของ credential ที่ e-Court stamp ลงไฟล์ยังเป็นงานออกแบบต่อ** (ร่าง requirement: `docs/requirement/addin-workflow.md`)
|
||||
|
||||
> **หมายเหตุสถานะปัจจุบัน (2026-10-14):** UI นี้ถูก implement แล้วใน repo นี้ที่ route `/docedit/wordaddin` (ดู `components/wordaddin/` + `wordaddin/`) — ทั้ง auth (แทนด้วย mock `wordaddin/services/authService.ts`) และ API ทั้งสามข้างบน (แทนด้วย in-memory mock `wordaddin/services/versioningService.ts`) · วิธีสลับกลับของจริง: `docs/WORDADDIN-DEV.md`
|
||||
> **หมายเหตุสถานะปัจจุบัน:** UI นี้ถูก implement แล้วใน repo นี้ที่ route `/docedit/wordaddin` (ดู `components/wordaddin/` + `wordaddin/`) — auth เป็น e-Court pass-through จริง (`wordaddin/services/authService.ts` — ไม่มี mock ให้ login แล้ว) ส่วน API ทั้งสามข้างบนยังเป็น in-memory mock (`wordaddin/services/versioningService.ts`) · วิธีสลับกลับของจริง: `docs/WORDADDIN-DEV.md`
|
||||
|
||||
---
|
||||
|
||||
## 8. กติกา error ที่ UI ต้องรองรับ
|
||||
|
||||
1. **`AuthRequiredError` (401 ที่ refresh ไม่ช่วย)** — ทุก flow ต้อง catch แล้ว: กลับ `signed-out` + เคลียร์ `documentId`/`documentTemplateId`/`versions` + ตั้ง `loginError` = "เซสชันหมดอายุ — กรุณา login ใหม่" — **ไม่ throw ต่อ ไม่โชว์ error ทับ phase**
|
||||
1. **`AuthRequiredError` (ไม่มี token / 401)** — ทุก flow ต้อง catch แล้ว: กลับ `no-access` + เคลียร์ `documentId`/`documentTemplateId`/`versions` + ตั้ง `noAccessNotice` = "token ของ e-Court หมดอายุหรือไม่ถูกต้อง — โปรดขอไฟล์ใหม่จาก e-Court แล้วเปิดอีกครั้ง" — **ไม่ throw ต่อ ไม่โชว์ error ทับ phase**
|
||||
2. error อื่น → MessageBar ณ จุดที่เกิด (login/history/flow/download แยกกัน) — ห้ามปล่อย unhandled rejection
|
||||
3. แยก error ของ "ตัว version" ออกจาก "ปิดเอกสารไม่สำเร็จ" — อันหลังเป็น warning เพราะข้อมูลปลอดภัยแล้ว
|
||||
4. PDF เป็น best-effort เสมอ — ล้มแค่เตือน ไม่ fail flow
|
||||
|
|
|
|||
44
docs/requirement/addin-workflow.md
Normal file
44
docs/requirement/addin-workflow.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# ขั้นตอนการทำงานของ Addin
|
||||
|
||||
## 1. เมื่อเปิดเอกสาร
|
||||
|
||||
- อ่าน custom property ของ word
|
||||
1. ticket_data ซึ่งจะมีข้อมูลเบื้องต้น ดังนี้
|
||||
```jsonc
|
||||
{
|
||||
"exp": 1793067433,
|
||||
"fid": "T12_FD155_3s-long_ZZZ",
|
||||
"tid": "T12",
|
||||
"uid": "oom",
|
||||
"uname": "สรวิชญ์",
|
||||
}
|
||||
```
|
||||
2. นำค่า ticket_data ที่เป็น string ก่อนนำไปแปลง json ทำการ sign HMAC-SHA256 โดยใช้ secret "super_secret" ซึ่งต้องรองรับการ config ค่านี้ในอนาคต
|
||||
3. นำค่าที่ sign ได้ มาเทียบกับค่า property "HmacSha256"
|
||||
- ขั้นตอนการทำ authen
|
||||
```
|
||||
ขั้นตอน
|
||||
1. เมื่อเปิดไฟล์ Word ให้อ่าน Custom Property 2 ค่าที่ฝังมาในไฟล์ คือ value จริง และ Hash Value (HMAC) เพื่อตรวจสอบว่าไฟล์สร้างผ่านระบบอย่างถูกต้อง
|
||||
2. ถ้า HMAC ไม่ตรงกัน แสดงว่าไฟล์ถูกแก้ไข ให้ Add-in disable การทำงานทั้งหมด
|
||||
3. ถ้า HMAC ตรงกัน ให้เก็บค่า uId, uName, fId, tid ไว้ แล้วตรวจสอบว่า userId นี้มีใน localStorage อยู่แล้วหรือไม่
|
||||
4. ถ้ายังไม่มีให้ตรวจค่า Exp ที่ได้จากไฟล์ Word
|
||||
- หมดอายุ → disable Add-in ทั้งหมด
|
||||
- ยังไม่หมดอายุ → เก็บใน localStorage
|
||||
- userName: ค่าจาก uName
|
||||
- คีย์คือ fid: เอาค่า {
|
||||
"userid": uid,
|
||||
"username": uname,
|
||||
"fid": fid (เป็น string),
|
||||
"tid": tid
|
||||
}
|
||||
ไปทำการขอ token ที่ /api/documents/token แล้วนำค่าที่ได้มาเก็บไว้ที่ตัวแปรนี้ เพื่อให้อ่านค่าไปใช้ให้ตรงกับค่า fid ที่ได้รับ เนื่องจาก api ในส่วน /documents จะต้องแนบ token ไปไม่ส่งค่าไปตรงๆ เพื่อความถูกต้อง
|
||||
5. ถ้ามี Session อยู่แล้ว ให้ตรวจว่า userId ตรงกับ Session หรือไม่
|
||||
6. ถ้า userId ไม่ตรงกัน (user ใหม่) ให้ตรวจค่า Expire
|
||||
- ยังไม่หมดอายุ → เคลียร์ Session เดิม แล้วสร้าง Session 2 ค่าใหม่
|
||||
- หมดอายุ → disable Add-in ทั้งหมด
|
||||
7. ถ้า userId ตรงกัน (คนเดิม) ให้ตรวจว่า fid มีใน localStorage แล้วหรือไม่
|
||||
- มีแล้ว (กลับมาทำเอกสารเดิม) → ผ่านได้เลย ไม่ต้องตรวจ Expire
|
||||
- ยังไม่มี (เอกสารใหม่) → ตรวจค่า Expire
|
||||
- ยังไม่หมดอายุ → ทำการขอ token แล้วเก็บใน localStorage ตามขั้นตอนก่อนหน้า
|
||||
- หมดอายุ → disable Add-in ทั้งหมด
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue