62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
import { useEffect, useState } from "react";
|
||
import { useTimeSkew } from "../hooks/useTimeSkew";
|
||
|
||
function formatMs(ms: number) {
|
||
const sign = ms > 0 ? "+" : "";
|
||
return `${sign}${ms}ms`;
|
||
}
|
||
|
||
export default function TimeSyncNotice({
|
||
thresholdMs = 500,
|
||
endpoint,
|
||
intervalMs,
|
||
lang = "ja",
|
||
}: {
|
||
thresholdMs?: number;
|
||
endpoint?: string;
|
||
intervalMs?: number;
|
||
lang?: "ja" | "en";
|
||
}) {
|
||
// removed `error`
|
||
const { skewMs, rttMs, recheck } = useTimeSkew({ endpoint, intervalMs });
|
||
|
||
const [dismissed, setDismissed] = useState<boolean>(() => {
|
||
try { return sessionStorage.getItem("timesync.dismissed") === "1"; } catch { return false; }
|
||
});
|
||
|
||
useEffect(() => {
|
||
if (skewMs != null && Math.abs(skewMs) <= thresholdMs && dismissed) {
|
||
setDismissed(false);
|
||
try { sessionStorage.removeItem("timesync.dismissed"); } catch { }
|
||
}
|
||
}, [skewMs, thresholdMs, dismissed]);
|
||
|
||
if (dismissed || skewMs == null || Math.abs(skewMs) <= thresholdMs) return null;
|
||
|
||
const ahead = skewMs > 0;
|
||
const msgJa = ahead
|
||
? `端末の時計が正確な時刻より ${formatMs(skewMs)} 進んでいます(往復遅延 ${rttMs ?? "-"}ms)`
|
||
: `端末の時計が正確な時刻より ${formatMs(-skewMs)} 遅れています(往復遅延 ${rttMs ?? "-"}ms)`;
|
||
const msgEn = ahead
|
||
? `Your device clock is ${formatMs(skewMs)} ahead of the correct time (RTT ${rttMs ?? "-"}ms).`
|
||
: `Your device clock is ${formatMs(-skewMs)} behind the correct time (RTT ${rttMs ?? "-"}ms).`;
|
||
|
||
const onClose = () => {
|
||
setDismissed(true);
|
||
try { sessionStorage.setItem("timesync.dismissed", "1"); } catch { }
|
||
};
|
||
|
||
return (
|
||
<div className="timesync-banner" role="status" aria-live="polite">
|
||
<div className="timesync-msg">{lang === "ja" ? msgJa : msgEn}</div>
|
||
<div className="timesync-actions">
|
||
<button className="timesync-btn" onClick={() => recheck?.()}>
|
||
{lang === "ja" ? "再測定" : "Re-check"}
|
||
</button>
|
||
<button className="timesync-close" onClick={onClose} aria-label={lang === "ja" ? "閉じる" : "Close"}>
|
||
×
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
} |