All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 4s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 1m8s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 6s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 0s
ไฟล์ที่ e-Court stamp custom properties token/owner มาใช้งานได้เลย ไม่ต้องกด login — detect() ของ TaskpaneApp อ่านค่าทั้งสองตอนเปิด task pane แล้วเรียก signInFromDocument(): username จาก owner → uid ใน JWT payload → NEXT_PUBLIC_ADDIN_MOCK_USERNAME, token จาก ไฟล์เก็บใน memory ให้ getAccessToken() ใช้ต่อ decodeJwtField ถอด base64url เป็น bytes แล้วผ่าน TextDecoder ก่อน JSON.parse — uid ที่ไม่ใช่ ASCII (เช่น ชื่อไทย) ไม่เป็น mojibake อัปเดตเอกสาร CLAUDE.md + docs/WORDADDIN-DEV.md พร้อมวิธีทดสอบ เองใน Word ผ่าน Advanced Properties Co-Authored-By: Claude Code <noreply@anthropic.com>
97 lines
4.4 KiB
TypeScript
97 lines
4.4 KiB
TypeScript
/**
|
|
* MOCK — แทน Keycloak OIDC/PKCE ของต้นฉบับ (ผู้ใช้งานเลือกไม่ใช้ Keycloak ช่วงพอร์ต)
|
|
* ต้นฉบับ: D:\word-addin-poc\addin\src\services\authService.ts (login ผ่าน Office Dialog API,
|
|
* token เก็บใน memory เท่านั้น, auto-refresh)
|
|
*
|
|
* วันสลับเป็น auth จริง: เขียน implementation ใหม่โดยคง export surface ด้านล่างครบทุกตัว
|
|
* (เพิ่ม refreshAccessToken ที่ต้นฉบับมีอยู่แล้วสำหรับ apiClient) — โค้ดอื่นไม่ต้องแตะ
|
|
*/
|
|
|
|
import { config } from "../config";
|
|
|
|
/** discriminated union เหมือนต้นฉบับ — ok:false ต้องมี error เสมอ */
|
|
export type LoginResult = { ok: true } | { ok: false; error: string };
|
|
|
|
/** state เดียวของ mock — null = signed out */
|
|
let username: string | null = null;
|
|
/** token จาก custom properties ของไฟล์ (e-Court pass-through) — เก็บใน memory เท่านั้น */
|
|
let docToken: string | null = null;
|
|
|
|
function delay(ms: number): Promise<void> {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
/** จำลองเปิดหน้าต่าง login — สำเร็จทันที ตั้งชื่อจาก NEXT_PUBLIC_ADDIN_MOCK_USERNAME */
|
|
export async function login(): Promise<LoginResult> {
|
|
await delay(300);
|
|
username = config.mockUsername;
|
|
return { ok: true };
|
|
}
|
|
|
|
/** mock-only — โหมด ?addindemo ใช้ตั้ง session ทันทีไม่ผ่านปุ่ม Sign in */
|
|
export function signInForDemo(): void {
|
|
username = config.mockUsername;
|
|
}
|
|
|
|
/** ถอด field จาก JWT payload (base64url) โดยไม่ตรวจ signature — ใช้ดู uid ประกอบชื่อผู้ใช้เท่านั้น */
|
|
function decodeJwtField(token: string, field: string): string | undefined {
|
|
const payload = token.split(".")[1];
|
|
if (!payload) return undefined;
|
|
try {
|
|
// atob คืน binary string (1 char = 1 byte) — ต้องถอด UTF-8 ด้วย TextDecoder ก่อน JSON.parse
|
|
// ไม่งั้น uid ที่ไม่ใช่ ASCII (เช่น ชื่อไทย) จะกลายเป็น mojibake
|
|
const bytes = Uint8Array.from(atob(payload.replace(/-/g, "+").replace(/_/g, "/")), (char) => char.charCodeAt(0));
|
|
const json: unknown = JSON.parse(new TextDecoder().decode(bytes));
|
|
const value = typeof json === "object" && json !== null ? (json as Record<string, unknown>)[field] : undefined;
|
|
return typeof value === "string" && value !== "" ? value : undefined;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* e-Court pass-through (ระยะแรก) — ไฟล์ที่ e-Court stamp `token`/`owner` ไว้ใน custom properties
|
|
* ใช้งานได้เลยโดยไม่ต้องกด login: username จาก owner → uid ใน token → mockUsername
|
|
* คืน true = ผ่านเข้าได้ (มี token หรือ owner อย่างน้อยหนึ่งตัว)
|
|
*/
|
|
export function signInFromDocument(input: { token?: string; owner?: string }): boolean {
|
|
const token = input.token?.trim() || undefined;
|
|
const owner = input.owner?.trim() || (token ? decodeJwtField(token, "uid") : undefined);
|
|
if (!token && !owner) {
|
|
return false;
|
|
}
|
|
username = owner ?? config.mockUsername;
|
|
docToken = token ?? null;
|
|
return true;
|
|
}
|
|
|
|
export async function getAccessToken(): Promise<string | null> {
|
|
if (docToken) return docToken;
|
|
return username ? `mock-token-${username}` : null;
|
|
}
|
|
|
|
/** mock: token ไม่มีวันหมดอายุจริง — คืนตัวเดิมหรือ null ตามสถานะ login */
|
|
export async function refreshAccessToken(): Promise<string | null> {
|
|
return getAccessToken();
|
|
}
|
|
|
|
export function getUsername(): string | undefined {
|
|
return username ?? undefined;
|
|
}
|
|
|
|
export function isTokenValid(): boolean {
|
|
return username !== null;
|
|
}
|
|
|
|
export function hasRefreshToken(): boolean {
|
|
return username !== null;
|
|
}
|
|
|
|
export function startAutoRefresh(): void {
|
|
// mock: ไม่มีอะไรต้อง refresh
|
|
}
|
|
|
|
export function signOut(): void {
|
|
username = null;
|
|
docToken = null;
|
|
}
|