add-in: auto-open taskpane phase 1 + final save after versioning + tracking id = fid

- manifest: TaskpaneId -> Office.AutoShowTaskpaneWithDocument (auto-open marker), bump 1.0.0.4
- metadataService.enableAutoOpenTaskpane: tag doc setting (idempotent, best-effort) called from detect() after sign-in
- versionFlow: final saveDocumentVerified at end of both versioning flows (bug: stamped docx never persisted -> reopen showed untracked)
- tracking id now uses ticket fid (TRACKING_ID_SOURCE="fid", rollback by switching to "guid"), resolveTrackingId + authService.getCurrentFid
- WordAddinGate: restore history.pushState/replaceState that office.js nullifies in taskpane (fixes Next App Router crash)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Suphonchai Phoonsawat 2026-11-05 12:42:02 +07:00
parent 02894f2705
commit 9fecaa7bca
8 changed files with 256 additions and 6 deletions

View file

@ -212,6 +212,10 @@ const TaskpaneApp: React.FC<{ demo?: "untracked" | "tracking" }> = ({ demo }) =>
}
setUsername(authService.getUsername() ?? null);
// auto-open (2026-11-05) — sign-in ผ่าน = ไฟล์ e-Court: tag ให้ taskpane เด้งเองครั้งถัดไป
// ที่เปิดไฟล์นี้ · fire-and-forget (service กลืน error ทั้งหมด — ไฟล์ทั่วไปไม่มาทางนี้อยู่แล้ว)
void metadataService.enableAutoOpenTaskpane();
const tracked = properties.savedByAddIn === metadataService.ADDIN_SIGNATURE_GUID;
const id = tracked ? properties.documentTrackingId : undefined;
if (!id) {

View file

@ -31,6 +31,33 @@ const ONREADY_FALLBACK_MS = 5_000;
/** เพดานรวมหลัง inject script — ครบแล้วยังไม่มี Office = ไปหน้า error (มีปุ่มลองใหม่) */
const ONREADY_HARD_CAP_MS = 60_000;
/**
* office.js **ลบ `history.pushState`/`replaceState` ทิ้ง** ใน taskpane (set เป็น null —
* กัน add-in พา host ไปที่อื่น: office-js#429) แต่ App Router ของ Next ต้องใช้ History API
* ตอน mount ไม่งั้นพัง "window.history.replaceState is not a function" — เก็บตัวจริงไว้
* ก่อนฉีด script แล้วคืนคืนหลัง office.js โหลด/พร้อม (workaround มาตรฐานของ SPA ใน
* add-in — taskpane เป็น iframe ของตัวเอง Next ใช้ history เฉพาะ SPA state ภายใน)
*/
const nativeHistory: { pushState?: History["pushState"]; replaceState?: History["replaceState"] } =
typeof window === "undefined"
? {}
: {
pushState: typeof window.history.pushState === "function" ? window.history.pushState.bind(window.history) : undefined,
replaceState:
typeof window.history.replaceState === "function" ? window.history.replaceState.bind(window.history) : undefined,
};
/** คืน History API ที่ office.js ลบทิ้ง — idempotent (มีอยู่แล้ว = ไม่แตะ) */
function restoreHistoryApi(): void {
if (typeof window === "undefined") return;
if (typeof window.history.pushState !== "function" && nativeHistory.pushState) {
window.history.pushState = nativeHistory.pushState;
}
if (typeof window.history.replaceState !== "function" && nativeHistory.replaceState) {
window.history.replaceState = nativeHistory.replaceState;
}
}
/**
* โหลด office.js ครั้งเดียวต่อ page เป็น singleton promise ระดับ module —
* ห้ามฉีด <script> ซ้ำ: React StrictMode รัน effect สองรอบใน dev และ guard
@ -45,7 +72,11 @@ function ensureOfficeJs(): Promise<boolean> {
if (!officeReadyPromise) {
officeReadyPromise = new Promise<boolean>((resolve) => {
if (typeof Office !== "undefined") {
void Office.onReady(() => resolve(true));
restoreHistoryApi(); // office.js evaluate ไปแล้ว — คืนก่อนให้ router ใช้ต่อได้
void Office.onReady(() => {
restoreHistoryApi(); // host handshake อาจลบซ้ำตอน initialize
resolve(true);
});
return;
}
const script = document.createElement("script");
@ -56,6 +87,7 @@ function ensureOfficeJs(): Promise<boolean> {
// Office โผล่แล้ว (script evaluate แล้ว) = proceed ได้เลย ไม่รอ onReady —
// กัน webview ที่โหลดสำเร็จแต่ callback เงียบ (เจตนาเดิมของ fallback)
if (typeof Office !== "undefined") {
restoreHistoryApi(); // office.js evaluate แล้วอาจลบ history ทิ้ง — คืนก่อน proceed
resolve(true);
return;
}
@ -74,8 +106,10 @@ function ensureOfficeJs(): Promise<boolean> {
const onReady = () => {
// เคลียร์ timer เฉพาะเมื่อ callback ของ Office.onReady ยิงจริง — ถ้าเคลียร์ตั้งแต่ onload
// แล้ว onReady เงียบ fallback จะตายไปก่อนทำหน้าที่ (ค้าง loading ตลอดไป)
restoreHistoryApi(); // script evaluate แล้ว — office.js ลบ history ตรงนี้ คืนก่อน router ใช้
void Office.onReady(() => {
clearTimeout(fallbackTimer);
restoreHistoryApi(); // handshake ของ host อาจลบซ้ำ — คืนอีกรอบก่อน mount TaskpaneApp
resolve(true);
});
};