110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
import { toast } from "sonner";
|
|
|
|
import {
|
|
hasDeploymentChanged,
|
|
normalizeDeploymentId,
|
|
} from "@/lib/deployment/version";
|
|
import {
|
|
parseDeploymentStatusEvent,
|
|
type DeploymentStatus,
|
|
} from "@/lib/deployment/status";
|
|
|
|
const UPDATE_TOAST_ID = "deployment-update";
|
|
const NOTICE_DURATION_MS = 10_000;
|
|
|
|
function noticeKey(deploymentId: string, status: DeploymentStatus): string {
|
|
return `deployment-notice:${deploymentId}:${status}`;
|
|
}
|
|
|
|
function rememberNotice(seen: Set<string>, key: string): boolean {
|
|
if (seen.has(key)) return false;
|
|
try {
|
|
if (sessionStorage.getItem(key)) return false;
|
|
sessionStorage.setItem(key, "1");
|
|
} catch {
|
|
// In-memory deduplication still works when storage is unavailable.
|
|
}
|
|
seen.add(key);
|
|
return true;
|
|
}
|
|
|
|
export function DeploymentUpdateNotifier({
|
|
deploymentId,
|
|
}: {
|
|
deploymentId: string;
|
|
}) {
|
|
const currentDeployment = useRef(normalizeDeploymentId(deploymentId));
|
|
const updateAvailable = useRef(false);
|
|
const seenNotices = useRef(new Set<string>());
|
|
|
|
useEffect(() => {
|
|
const source = new EventSource("/api/active");
|
|
const showReady = () => {
|
|
if (updateAvailable.current) return;
|
|
updateAvailable.current = true;
|
|
toast("มีอัปเดตใหม่พร้อมใช้งาน", {
|
|
id: UPDATE_TOAST_ID,
|
|
description: "รีโหลดเพื่ออัปเดตเป็นเวอร์ชันล่าสุด",
|
|
action: {
|
|
label: "รีโหลด",
|
|
onClick: () => window.location.reload(),
|
|
},
|
|
duration: Infinity,
|
|
});
|
|
};
|
|
const receiveDeployment = (event: Event) => {
|
|
if (!(event instanceof MessageEvent) || typeof event.data !== "string") {
|
|
return;
|
|
}
|
|
const nextDeployment = normalizeDeploymentId(event.data);
|
|
if (hasDeploymentChanged(currentDeployment.current, nextDeployment)) {
|
|
showReady();
|
|
}
|
|
};
|
|
const receiveStatus = (event: Event) => {
|
|
if (!(event instanceof MessageEvent) || typeof event.data !== "string") {
|
|
return;
|
|
}
|
|
const update = parseDeploymentStatusEvent(event.data);
|
|
if (
|
|
!update ||
|
|
update.deploymentId === currentDeployment.current ||
|
|
updateAvailable.current
|
|
) {
|
|
return;
|
|
}
|
|
const key = noticeKey(update.deploymentId, update.status);
|
|
if (!rememberNotice(seenNotices.current, key)) return;
|
|
|
|
if (update.status === "ready") {
|
|
showReady();
|
|
} else if (update.status === "deploying") {
|
|
toast("กำลังอัปเดตเว็บไซต์", {
|
|
id: UPDATE_TOAST_ID,
|
|
description: "เวอร์ชันใหม่จะพร้อมใช้งานในอีกไม่กี่นาที",
|
|
duration: NOTICE_DURATION_MS,
|
|
});
|
|
} else {
|
|
toast("การอัปเดตล่าช้า", {
|
|
id: UPDATE_TOAST_ID,
|
|
description: "เวอร์ชันปัจจุบันยังใช้งานได้ตามปกติ",
|
|
duration: NOTICE_DURATION_MS,
|
|
});
|
|
}
|
|
};
|
|
|
|
source.addEventListener("deployment", receiveDeployment);
|
|
source.addEventListener("deployment-status", receiveStatus);
|
|
return () => {
|
|
source.removeEventListener("deployment", receiveDeployment);
|
|
source.removeEventListener("deployment-status", receiveStatus);
|
|
source.close();
|
|
};
|
|
}, []);
|
|
|
|
return null;
|
|
}
|