SHA256
fix(ios): restrict authentication capabilities and validate bootstrap types
Require iOS 18.4 for explicit upload denial in the isolated authentication window. Add origin-bound generated download policy, app-switcher privacy, typed metadata regressions and original community icon assets. Seven portable tests and Swift formatting checks pass; Apple SDK validation remains external.
This commit is contained in:
1 parent
74c2c9e6e8
commit
69c48169e2
15 files changed
+341
-112
No files matched your search
@@ -22,7 +22,10 @@ struct ContentView: View {
|
||||
Image(systemName: "wifi.exclamationmark").font(.largeTitle)
|
||||
Text("Connection interrupted").font(.title2).bold()
|
||||
Text(error)
|
||||
Button("Retry") { browser.error = nil; browser.web.reload() }
|
||||
Button("Retry") {
|
||||
browser.error = nil
|
||||
browser.web.reload()
|
||||
}
|
||||
Button("Keep viewing") { browser.error = nil }
|
||||
Button("Server settings") { settings = true }
|
||||
}
|
||||
@@ -30,12 +33,18 @@ struct ContentView: View {
|
||||
.background(Color(uiColor: .systemGroupedBackground))
|
||||
.accessibilityElement(children: .contain)
|
||||
}
|
||||
if browser.loading { ProgressView("Loading Yuvomi").padding().background(.regularMaterial).cornerRadius(12) }
|
||||
if browser.loading {
|
||||
ProgressView("Loading Yuvomi").padding().background(.regularMaterial).cornerRadius(12)
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button { browser.web.goBack() } label: { Image(systemName: "chevron.backward") }
|
||||
.disabled(!browser.canGoBack).accessibilityLabel("Back")
|
||||
Button {
|
||||
browser.web.goBack()
|
||||
} label: {
|
||||
Image(systemName: "chevron.backward")
|
||||
}
|
||||
.disabled(!browser.canGoBack).accessibilityLabel("Back")
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Server settings") { settings = true }
|
||||
@@ -70,10 +79,11 @@ struct ContentView: View {
|
||||
}
|
||||
.task {
|
||||
if server == nil, let existing = try? ServerAddress(input) {
|
||||
server = existing; browser.open(existing)
|
||||
server = existing
|
||||
browser.open(existing)
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { phase in
|
||||
.onChange(of: scenePhase) { _, phase in
|
||||
if phase == .background { browser.rememberPath() }
|
||||
}
|
||||
.overlay {
|
||||
@@ -93,31 +103,49 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
Section {
|
||||
Text("Changing servers erases cookies, storage and cached household data. Exported files remain. Sign out in Yuvomi first to revoke the server session.")
|
||||
Text(
|
||||
"Changing servers erases cookies, storage and cached household data. Exported files remain. Sign out in Yuvomi first to revoke the server session."
|
||||
)
|
||||
Button("Erase local data and change server", role: .destructive) {
|
||||
settings = false; erasing = true
|
||||
settings = false
|
||||
erasing = true
|
||||
browser.erase {
|
||||
UserDefaults.standard.removeObject(forKey: "origin")
|
||||
UserDefaults.standard.removeObject(forKey: "path")
|
||||
server = nil; input = ""; erasing = false
|
||||
server = nil
|
||||
input = ""
|
||||
erasing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
Section("About") {
|
||||
Text("Independent community contribution. MIT licensed. Not endorsed by upstream Yuvomi. No mobile telemetry. Your server's own privacy settings still apply.")
|
||||
Text(
|
||||
"Independent community contribution. MIT licensed. Not endorsed by upstream Yuvomi. No mobile telemetry. Your server's own privacy settings still apply."
|
||||
)
|
||||
}
|
||||
}.navigationTitle("Server settings")
|
||||
.toolbar { Button("Done") { settings = false } }
|
||||
}
|
||||
}
|
||||
.alert("External destination", isPresented: Binding(
|
||||
get: { browser.externalURL != nil }, set: { if !$0 { browser.externalURL = nil } }
|
||||
), presenting: browser.externalURL) { url in
|
||||
Button("Open in browser") { UIApplication.shared.open(url); browser.externalURL = nil }
|
||||
Button("Isolated sign-in window") { browser.startAuthentication(url); browser.externalURL = nil }
|
||||
.alert(
|
||||
"External destination",
|
||||
isPresented: Binding(
|
||||
get: { browser.externalURL != nil }, set: { if !$0 { browser.externalURL = nil } }
|
||||
), presenting: browser.externalURL
|
||||
) { url in
|
||||
Button("Open in browser") {
|
||||
UIApplication.shared.open(url)
|
||||
browser.externalURL = nil
|
||||
}
|
||||
Button("Isolated sign-in window") {
|
||||
browser.startAuthentication(url)
|
||||
browser.externalURL = nil
|
||||
}
|
||||
Button("Cancel", role: .cancel) { browser.externalURL = nil }
|
||||
} message: { url in
|
||||
Text("Destination: \(url.host ?? "")\nFor SSO, use the isolated window only if you trust this provider. Browser sessions do not transfer into the app.")
|
||||
Text(
|
||||
"Destination: \(url.host ?? "")\nFor SSO, use the isolated window only if you trust this provider. Browser sessions do not transfer into the app."
|
||||
)
|
||||
}
|
||||
.sheet(item: $browser.authentication) { destination in
|
||||
AuthenticationView(destination: destination.url, model: browser)
|
||||
@@ -130,13 +158,15 @@ struct ContentView: View {
|
||||
private func connect() {
|
||||
do {
|
||||
let candidate = try ServerAddress(input)
|
||||
connecting = true; message = nil
|
||||
connecting = true
|
||||
message = nil
|
||||
Task { @MainActor in
|
||||
defer { connecting = false }
|
||||
do {
|
||||
try await ServerProbe.check(candidate)
|
||||
UserDefaults.standard.set(candidate.url.absoluteString, forKey: "origin")
|
||||
server = candidate; browser.open(candidate)
|
||||
server = candidate
|
||||
browser.open(candidate)
|
||||
} catch { message = ConnectionError.unavailable.localizedDescription }
|
||||
}
|
||||
} catch { message = ConnectionError.invalidAddress.localizedDescription }
|
||||
|
||||
Reference in new issue
Block a user