204 lines
5.9 KiB
TypeScript
204 lines
5.9 KiB
TypeScript
import { ref, computed, onMounted, onUnmounted } from "vue";
|
|
import { useTauri, type IphoneDeviceState, type DesktopProtectionState, type MagicDeviceInfo } from "~/composables/useTauri";
|
|
import { useIphoneDevice, useMagicDevices, useMagicSession } from "~/composables/useMagicState";
|
|
|
|
export interface ProtectionStatus {
|
|
overall: "protected" | "partial" | "unprotected" | "unknown";
|
|
label: string;
|
|
message: string;
|
|
iosConnected: boolean;
|
|
iosProtected: boolean;
|
|
desktopProtected: boolean;
|
|
anyBackendDevice: boolean;
|
|
}
|
|
|
|
const IPHONE_POLL_INTERVAL = 5000;
|
|
const DEVICE_REFRESH_INTERVAL = 30000;
|
|
|
|
function normalizeHostname(value: string): string {
|
|
return (value.toLowerCase().split(".")[0] ?? "").trim();
|
|
}
|
|
|
|
export function useProtectionStatus() {
|
|
const {
|
|
detectIphoneState,
|
|
getDesktopProtectionStatus,
|
|
getMagicDevices,
|
|
getMagicStatus,
|
|
getHostname,
|
|
} = useTauri();
|
|
|
|
const iphone = useIphoneDevice();
|
|
const devices = useMagicDevices();
|
|
const session = useMagicSession();
|
|
|
|
const desktopProtection = ref<DesktopProtectionState | null>(null);
|
|
const localHostname = ref<string | null>(null);
|
|
const loading = ref(false);
|
|
const lastError = ref<string | null>(null);
|
|
const lastUpdated = ref<Date | null>(null);
|
|
|
|
let iphoneTimer: ReturnType<typeof setInterval> | null = null;
|
|
let deviceTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
const iosConnected = computed(() => !!iphone.value);
|
|
const iosProtected = computed(() => {
|
|
if (!iphone.value) return false;
|
|
const hasEnrollment = iphone.value.installedProfileIDs?.includes("org.rebreak.mdm.enrollment");
|
|
const hasLock = iphone.value.installedProfileIDs?.includes("org.rebreak.protection.contentfilter.sideload");
|
|
return iphone.value.isSupervised && hasEnrollment && hasLock;
|
|
});
|
|
const currentBackendDevice = computed<MagicDeviceInfo | null>(() => {
|
|
if (!localHostname.value) return null;
|
|
const local = normalizeHostname(localHostname.value);
|
|
return devices.value.find((d) => normalizeHostname(d.hostname) === local) ?? null;
|
|
});
|
|
const desktopProtected = computed(() => !!desktopProtection.value?.active || !!currentBackendDevice.value);
|
|
const anyBackendDevice = computed(() => devices.value.length > 0);
|
|
|
|
const status = computed<ProtectionStatus>(() => {
|
|
const ios = iosConnected.value;
|
|
const iosOk = iosProtected.value;
|
|
const desktopOk = desktopProtected.value;
|
|
const backendOk = anyBackendDevice.value;
|
|
|
|
if (!ios && !desktopOk && !backendOk) {
|
|
return {
|
|
overall: "unprotected",
|
|
label: "Kein Schutz aktiv",
|
|
message: "Verbinde ein iPhone oder aktiviere den Desktop-Schutz.",
|
|
iosConnected: false,
|
|
iosProtected: false,
|
|
desktopProtected: false,
|
|
anyBackendDevice: false,
|
|
};
|
|
}
|
|
|
|
if (ios && !iosOk) {
|
|
return {
|
|
overall: "partial",
|
|
label: "iOS-Schutz unvollständig",
|
|
message: "Supervision oder Schutz-Profile fehlen. Führe das Setup fort.",
|
|
iosConnected: true,
|
|
iosProtected: false,
|
|
desktopProtected: desktopOk,
|
|
anyBackendDevice: backendOk,
|
|
};
|
|
}
|
|
|
|
if (iosOk && desktopOk) {
|
|
return {
|
|
overall: "protected",
|
|
label: "Vollständig geschützt",
|
|
message: "iPhone und Desktop sind aktiv geschützt.",
|
|
iosConnected: true,
|
|
iosProtected: true,
|
|
desktopProtected: true,
|
|
anyBackendDevice: backendOk,
|
|
};
|
|
}
|
|
|
|
if (iosOk || desktopOk || backendOk) {
|
|
return {
|
|
overall: "partial",
|
|
label: "Teilweise geschützt",
|
|
message: iosOk
|
|
? "iPhone geschützt. Desktop-Schutz ist optional."
|
|
: desktopOk
|
|
? "Desktop geschützt. iPhone noch nicht verbunden."
|
|
: "Ein registriertes Gerät vorhanden, aber keine lokale Schutzkontrolle.",
|
|
iosConnected: ios,
|
|
iosProtected: iosOk,
|
|
desktopProtected: desktopOk,
|
|
anyBackendDevice: backendOk,
|
|
};
|
|
}
|
|
|
|
return {
|
|
overall: "unknown",
|
|
label: "Status unbekannt",
|
|
message: "Schutzstatus konnte nicht ermittelt werden.",
|
|
iosConnected: false,
|
|
iosProtected: false,
|
|
desktopProtected: false,
|
|
anyBackendDevice: false,
|
|
};
|
|
});
|
|
|
|
async function refreshIphone() {
|
|
try {
|
|
const state = await detectIphoneState();
|
|
iphone.value = state;
|
|
} catch (e: any) {
|
|
// Bei Fehler nicht crashen — Gerät ist einfach nicht verbunden
|
|
iphone.value = null;
|
|
}
|
|
}
|
|
|
|
async function refreshDesktop() {
|
|
try {
|
|
desktopProtection.value = await getDesktopProtectionStatus();
|
|
} catch (e: any) {
|
|
lastError.value = e?.message ?? "Desktop-Schutzstatus konnte nicht geladen werden";
|
|
}
|
|
}
|
|
|
|
async function refreshBackendDevices() {
|
|
try {
|
|
devices.value = await getMagicDevices();
|
|
if (session.value?.dnsToken) {
|
|
await getMagicStatus(session.value.dnsToken);
|
|
}
|
|
} catch (e: any) {
|
|
lastError.value = e?.message ?? "Geräte konnten nicht geladen werden";
|
|
}
|
|
}
|
|
|
|
async function refresh() {
|
|
loading.value = true;
|
|
lastError.value = null;
|
|
await Promise.all([
|
|
refreshIphone(),
|
|
refreshDesktop(),
|
|
refreshBackendDevices(),
|
|
refreshHostname(),
|
|
]);
|
|
lastUpdated.value = new Date();
|
|
loading.value = false;
|
|
}
|
|
|
|
async function refreshHostname() {
|
|
try {
|
|
localHostname.value = await getHostname();
|
|
} catch (e: any) {
|
|
localHostname.value = null;
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
refresh();
|
|
iphoneTimer = setInterval(refreshIphone, IPHONE_POLL_INTERVAL);
|
|
deviceTimer = setInterval(refreshBackendDevices, DEVICE_REFRESH_INTERVAL);
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
if (iphoneTimer) clearInterval(iphoneTimer);
|
|
if (deviceTimer) clearInterval(deviceTimer);
|
|
});
|
|
|
|
return {
|
|
status,
|
|
iphone: computed(() => iphone.value),
|
|
desktopProtection,
|
|
localHostname,
|
|
loading,
|
|
lastError,
|
|
lastUpdated,
|
|
iosConnected,
|
|
iosProtected,
|
|
desktopProtected,
|
|
anyBackendDevice,
|
|
refresh,
|
|
};
|
|
}
|