diff --git a/apps/rebreak-magic-mac/Sources/Services/MagicAPIClient.swift b/apps/rebreak-magic-mac/Sources/Services/MagicAPIClient.swift
index b779358..c49aba8 100644
--- a/apps/rebreak-magic-mac/Sources/Services/MagicAPIClient.swift
+++ b/apps/rebreak-magic-mac/Sources/Services/MagicAPIClient.swift
@@ -8,7 +8,13 @@ struct MagicRegistration: Codable {
let existing: Bool
}
+enum MagicDeviceSource: String, Codable {
+ case magic
+ case protected
+}
+
struct MagicDevice: Codable, Identifiable {
+ let source: MagicDeviceSource?
let deviceId: String
let hostname: String
let model: String?
@@ -16,8 +22,11 @@ struct MagicDevice: Codable, Identifiable {
let magicEnrolledAt: String
let releaseRequestedAt: String?
let releaseAvailableAt: String?
-
- var id: String { deviceId }
+
+ var id: String { "\(source?.rawValue ?? "magic"):\(deviceId)" }
+
+ /// Default zu `.magic` falls Backend (alte Version) das Feld nicht setzt.
+ var resolvedSource: MagicDeviceSource { source ?? .magic }
var enrolledDate: Date? {
ISO8601DateFormatter().date(from: magicEnrolledAt)
diff --git a/apps/rebreak-magic-mac/Sources/Views/DeviceHubView.swift b/apps/rebreak-magic-mac/Sources/Views/DeviceHubView.swift
index 37ac58a..438480b 100644
--- a/apps/rebreak-magic-mac/Sources/Views/DeviceHubView.swift
+++ b/apps/rebreak-magic-mac/Sources/Views/DeviceHubView.swift
@@ -279,8 +279,19 @@ private struct HubDeviceRow: View {
.frame(width: 28)
VStack(alignment: .leading, spacing: 2) {
- Text(device.hostname)
- .font(.callout.bold())
+ HStack(spacing: 6) {
+ Text(device.hostname)
+ .font(.callout.bold())
+ if device.resolvedSource == .protected {
+ Text("Native-App")
+ .font(.caption2.bold())
+ .padding(.horizontal, 6)
+ .padding(.vertical, 2)
+ .background(Color.gray.opacity(0.15))
+ .foregroundStyle(.secondary)
+ .clipShape(Capsule())
+ }
+ }
HStack(spacing: 8) {
if let model = device.model {
Text(model).font(.caption2).foregroundStyle(.secondary)
@@ -294,7 +305,11 @@ private struct HubDeviceRow: View {
Spacer()
- if device.isReleasing {
+ if device.resolvedSource == .protected {
+ Text("Verwaltung in der ReBreak-App")
+ .font(.caption2)
+ .foregroundStyle(.tertiary)
+ } else if device.isReleasing {
VStack(alignment: .trailing, spacing: 2) {
Label("Freigabe läuft", systemImage: "hourglass")
.font(.caption2.bold())
diff --git a/apps/rebreak-magic-mac/Sources/Views/MacRegistrationView.swift b/apps/rebreak-magic-mac/Sources/Views/MacRegistrationView.swift
index 6ac62e5..f399f80 100644
--- a/apps/rebreak-magic-mac/Sources/Views/MacRegistrationView.swift
+++ b/apps/rebreak-magic-mac/Sources/Views/MacRegistrationView.swift
@@ -221,7 +221,7 @@ struct MacRegistrationView: View {
await MainActor.run {
isInstallingProfile = false
- successMessage = "System Settings → Profile geöffnet. Bitte dort „Installieren" klicken und Admin-Passwort eingeben."
+ successMessage = "System Settings → Profile geöffnet. Bitte dort „Installieren“ klicken und Admin-Passwort eingeben."
}
// Re-check profile status nach kurzer Wartezeit (User muss in UI bestätigen)
diff --git a/apps/rebreak-native/app.config.ts b/apps/rebreak-native/app.config.ts
index b750ef2..131e3d2 100644
--- a/apps/rebreak-native/app.config.ts
+++ b/apps/rebreak-native/app.config.ts
@@ -36,7 +36,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
ios: {
supportsTablet: true,
bundleIdentifier: MAIN_BUNDLE,
- buildNumber: "67",
+ buildNumber: "68",
// Apple Sign-In Entitlement — Pflicht für expo-apple-authentication nativen
// signInAsync()-Flow. Ohne flag generiert Expo's prebuild den
// com.apple.developer.applesignin-Entitlement nicht in die .entitlements.
@@ -59,7 +59,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
android: {
package: "org.rebreak.app",
- versionCode: 50,
+ versionCode: 51,
adaptiveIcon: {
// Foreground muss in der ~66%-Safe-Zone bleiben (Launcher-Mask clippt den
// Außenring) → adaptive-foreground.png ist das Logo auf transparentem
diff --git a/apps/rebreak-native/modules/rebreak-protection/ios/RebreakContentFilter/Info.plist b/apps/rebreak-native/modules/rebreak-protection/ios/RebreakContentFilter/Info.plist
index fb8d354..5e6716b 100644
--- a/apps/rebreak-native/modules/rebreak-protection/ios/RebreakContentFilter/Info.plist
+++ b/apps/rebreak-native/modules/rebreak-protection/ios/RebreakContentFilter/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
0.3.13
CFBundleVersion
- 67
+ 68
NSExtension
NSExtensionPointIdentifier
diff --git a/apps/rebreak-native/modules/rebreak-protection/ios/RebreakPacketTunnelExtension/Info.plist b/apps/rebreak-native/modules/rebreak-protection/ios/RebreakPacketTunnelExtension/Info.plist
index 3db75c4..6d7a147 100644
--- a/apps/rebreak-native/modules/rebreak-protection/ios/RebreakPacketTunnelExtension/Info.plist
+++ b/apps/rebreak-native/modules/rebreak-protection/ios/RebreakPacketTunnelExtension/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
0.3.13
CFBundleVersion
- 67
+ 68
NSExtension
NSExtensionPointIdentifier
diff --git a/apps/rebreak-native/modules/rebreak-protection/ios/RebreakURLFilterExtension/Info.plist b/apps/rebreak-native/modules/rebreak-protection/ios/RebreakURLFilterExtension/Info.plist
index 44ba003..205e74f 100644
--- a/apps/rebreak-native/modules/rebreak-protection/ios/RebreakURLFilterExtension/Info.plist
+++ b/apps/rebreak-native/modules/rebreak-protection/ios/RebreakURLFilterExtension/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
0.3.13
CFBundleVersion
- 67
+ 68
EXAppExtensionAttributes
EXExtensionPointIdentifier
diff --git a/backend/server/api/magic/devices.get.ts b/backend/server/api/magic/devices.get.ts
index 0480d52..fd8ee31 100644
--- a/backend/server/api/magic/devices.get.ts
+++ b/backend/server/api/magic/devices.get.ts
@@ -1,18 +1,27 @@
import { listMagicDevices } from "../../db/devices";
+import { listProtectedDevices } from "../../db/protectedDevices";
import { requireUser } from "../../utils/auth";
/**
* GET /api/magic/devices
*
- * Listet alle aktiven Magic-Bindings des Users für UI.
- * Response: [{ deviceId, hostname, model, osVersion, magicEnrolledAt, releaseRequestedAt, releaseAvailableAt }]
+ * Listet alle gesch\u00fctzten Ger\u00e4te des Users f\u00fcr den Magic-Hub. Vereinigt:
+ * - Magic-Bindings (UserDevice.magicEnrolledAt) \u2014 via Magic-App registriert
+ * - ProtectedDevices \u2014 alter Native-App-DNS-Schutz-Flow (Multi-Device)
+ *
+ * Response-Items haben ein `source`-Flag:
+ * "magic" \u2192 voll verwaltet, unterst\u00fctzt request-release
+ * "protected" \u2192 alter Flow, nur Anzeige + revoke (TODO: own action)
*/
export default defineEventHandler(async (event) => {
const user = await requireUser(event);
- const devices = await listMagicDevices(user.id);
- // Berechne releaseAvailableAt (releaseRequestedAt + 24h)
- const enriched = devices.map((d) => {
+ const [magic, protectedDevices] = await Promise.all([
+ listMagicDevices(user.id),
+ listProtectedDevices(user.id),
+ ]);
+
+ const magicItems = magic.map((d) => {
let releaseAvailableAt: string | null = null;
if (d.releaseRequestedAt) {
const availableAt = new Date(
@@ -22,8 +31,9 @@ export default defineEventHandler(async (event) => {
}
return {
+ source: "magic" as const,
deviceId: d.deviceId,
- hostname: d.hostname,
+ hostname: d.hostname ?? "Unbenanntes Ger\u00e4t",
model: d.model,
osVersion: d.osVersion,
magicEnrolledAt: d.magicEnrolledAt.toISOString(),
@@ -32,8 +42,20 @@ export default defineEventHandler(async (event) => {
};
});
+ const protectedItems = protectedDevices.map((d) => ({
+ source: "protected" as const,
+ deviceId: d.id,
+ hostname: d.label,
+ model: d.platform,
+ osVersion: null as string | null,
+ magicEnrolledAt: (d.installedAt ?? d.createdAt).toISOString(),
+ releaseRequestedAt: null as string | null,
+ releaseAvailableAt: null as string | null,
+ }));
+
+ // Magic-Bindings zuerst (neuste), dann alte ProtectedDevices
return {
success: true,
- data: enriched,
+ data: [...magicItems, ...protectedItems],
};
});