Replaces the manual "I've installed it" button in AddMacSheet with an auto-advancing waiting-pill. As soon as the backend flips status from pending → active (triggered by the DoH handshake from the AdGuard watcher), the sheet jumps to the success step automatically. - useProtectedDevicesRealtime hook subscribes to rebreak.protected_devices UPDATE events for the current user, with auto-reconnect on CHANNEL_ERROR - AddMacSheet listens only while in step 2 (download/install) - devices.tsx keeps a list-level subscription so the table refreshes even if the user dismissed the sheet before activation - i18n: waiting_install / waiting_hint / activated_toast (DE + EN)
73 lines
2.1 KiB
TypeScript
73 lines
2.1 KiB
TypeScript
import { useEffect, useRef } from "react";
|
|
import { supabase } from "../lib/supabase";
|
|
import type { RealtimeChannel } from "@supabase/supabase-js";
|
|
import { useProtectedDevicesStore } from "../stores/protectedDevices";
|
|
|
|
type OnActivated = (deviceId: string) => void;
|
|
|
|
export function useProtectedDevicesRealtime(
|
|
onActivated?: OnActivated,
|
|
enabled: boolean = true,
|
|
) {
|
|
const onActivatedRef = useRef<OnActivated | undefined>(onActivated);
|
|
onActivatedRef.current = onActivated;
|
|
|
|
useEffect(() => {
|
|
if (!enabled) return;
|
|
let channel: RealtimeChannel | null = null;
|
|
let cancelled = false;
|
|
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
async function subscribe() {
|
|
const { data } = await supabase.auth.getSession();
|
|
const session = data.session;
|
|
if (!session?.access_token || cancelled) return;
|
|
|
|
const userId = session.user.id;
|
|
|
|
channel = supabase
|
|
.channel(`protected:${userId}:${Date.now()}`)
|
|
.on(
|
|
"postgres_changes",
|
|
{
|
|
event: "UPDATE",
|
|
schema: "rebreak",
|
|
table: "protected_devices",
|
|
filter: `user_id=eq.${userId}`,
|
|
},
|
|
(payload: any) => {
|
|
const updated = payload.new;
|
|
useProtectedDevicesStore.getState().load();
|
|
if (updated?.status === "active" && payload.old?.status === "pending") {
|
|
onActivatedRef.current?.(updated.id);
|
|
}
|
|
},
|
|
)
|
|
.subscribe((status) => {
|
|
if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
|
|
cleanup();
|
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
reconnectTimer = setTimeout(() => {
|
|
if (!cancelled) subscribe();
|
|
}, 3000);
|
|
}
|
|
});
|
|
}
|
|
|
|
function cleanup() {
|
|
if (channel) {
|
|
supabase.removeChannel(channel);
|
|
channel = null;
|
|
}
|
|
}
|
|
|
|
subscribe();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
cleanup();
|
|
};
|
|
}, [enabled]);
|
|
}
|