Merge branch 'main' into file-share

# Conflicts:
#	client/ios/NetBirdSDK/client.go
This commit is contained in:
Zoltán Papp
2026-09-01 18:06:26 +02:00
485 changed files with 36221 additions and 5501 deletions
@@ -18,6 +18,11 @@ import { formatRemaining } from "@/lib/formatters";
const DEFAULT_SECONDS = 360;
const WINDOW_WIDTH = 360;
const SOON_THRESHOLD_SECONDS = 60 * 60;
const DEADLINE_TOLERANCE_MS = 5 * 1000;
// The final-warning deadline reaches the Go side as RFC3339 truncated to whole
// seconds, while the status snapshot carries millisecond precision, so an
// unchanged deadline can look up to 999 ms newer than the exact URL value.
const EXACT_DEADLINE_TOLERANCE_MS = 999;
export default function SessionExpirationDialog() {
const { t } = useTranslation();
@@ -29,11 +34,19 @@ export default function SessionExpirationDialog() {
const n = Number.parseInt(raw, 10);
return Number.isFinite(n) && n > 0 ? n : DEFAULT_SECONDS;
}, [params]);
const initialDeadline = useMemo(() => {
const raw = params.get("deadline");
if (!raw) return null;
const n = Number.parseInt(raw, 10);
return Number.isFinite(n) && n > 0 ? n : null;
}, [params]);
const [remaining, setRemaining] = useState(initialSeconds);
const [busy, setBusy] = useState(false);
const busyRef = useRef(busy);
busyRef.current = busy;
const openedDeadlineRef = useRef(initialDeadline ?? Date.now() + initialSeconds * 1000);
const exactDeadlineRef = useRef(initialDeadline !== null);
const expired = remaining <= 0;
const expiredRef = useRef(expired);
expiredRef.current = expired;
@@ -45,23 +58,45 @@ export default function SessionExpirationDialog() {
useEffect(() => {
setRemaining(initialSeconds);
}, [initialSeconds]);
openedDeadlineRef.current = initialDeadline ?? Date.now() + initialSeconds * 1000;
exactDeadlineRef.current = initialDeadline !== null;
}, [initialSeconds, initialDeadline]);
// Recompute from the absolute deadline instead of decrementing per tick: webview
// timers get suspended for tens of seconds (App Nap / hidden-window throttling),
// so a tick counter drifts behind the wall clock by the suspended time.
useEffect(() => {
const id = globalThis.setInterval(() => {
setRemaining((s) => (s <= 1 ? 0 : s - 1));
setRemaining(Math.max(0, Math.ceil((openedDeadlineRef.current - Date.now()) / 1000)));
}, 1000);
return () => globalThis.clearInterval(id);
}, [initialSeconds]);
// Auto-close only when the session was actually renewed elsewhere (tray action, CLI,
// main window): the daemon keeps emitting Connected snapshots regardless of session
// state, so the signal is the deadline jumping past the one this dialog was opened for.
// With the exact deadline from the URL any jump past its sub-second precision loss
// counts; the seconds-derived fallback needs a wider tolerance for the Go-side
// truncation and mount latency.
// Don't auto-close while busy (aborts our WaitExtend) or expired (hides the state).
useEffect(() => {
const off = Events.On("netbird:status", (ev: { data: { status?: string } }) => {
if (busyRef.current || expiredRef.current) return;
if (ev?.data?.status === "Connected") {
WindowManager.CloseSessionExpiration().catch(console.error);
}
});
const off = Events.On(
"netbird:status",
(ev: { data: { status?: string; sessionExpiresAt?: string | null } }) => {
if (busyRef.current || expiredRef.current) return;
if (ev?.data?.status !== "Connected") return;
const raw = ev?.data?.sessionExpiresAt;
if (!raw) return;
const renewed = Date.parse(raw);
if (!Number.isFinite(renewed)) return;
const tolerance = exactDeadlineRef.current
? EXACT_DEADLINE_TOLERANCE_MS
: DEADLINE_TOLERANCE_MS;
if (renewed - openedDeadlineRef.current > tolerance) {
WindowManager.CloseSessionExpiration().catch(console.error);
}
},
);
return () => {
off();
};