Files

66 lines
2.8 KiB
Swift

import Foundation
import OrgSocialKit
/// Orchestrates Guideline 5.1.1(v) account deletion.
///
/// - For vfile accounts, calls `POST /delete` on the host (irreversible
/// server-side removal of the social.org file).
/// - For every method, wipes all locally-stored credentials, drafts,
/// block / mute lists and cached app state from `UserDefaults`. The
/// only key preserved is `eulaAcceptedVersion`, since the EULA
/// acceptance is per-install, not per-account.
enum AccountDeletion {
/// Deletes the account on the host (vfile only) and then wipes every
/// `UserDefaults` key the app writes. Throws if the remote delete fails;
/// in that case the local data is left intact so the user can retry.
@MainActor
static func deleteCurrentAccount() async throws {
let defaults = UserDefaults.standard
let methodRaw = defaults.string(forKey: "uploadMethod") ?? UploadMethod.vfile.rawValue
let method = UploadMethod(rawValue: methodRaw) ?? .vfile
if method == .vfile,
let raw = defaults.string(forKey: "vfileURL"),
let url = URL(string: raw),
!raw.isEmpty {
try await HostDeleteClient().delete(vfileURL: url)
}
await PushRegistration.shared.unsubscribe()
wipeLocalState()
}
/// Resets every persisted UserDefaults key except `eulaAcceptedVersion`
/// and clears in-memory observable singletons. `eulaAcceptedVersion` is
/// per-install so the user is not re-prompted after deleting their
/// account on the same device.
///
/// The keys that drive `@AppStorage`-backed gates (`publicFeedURL`,
/// `vfileURL`, `githubToken`, `codebergToken`, `webdavURL`) are
/// explicitly written to "" first so the property wrappers publish a
/// change notification. `removeObject` alone is not always observed by
/// `@AppStorage` in iOS 17 / 18, which would leave the root view stuck
/// on the configured tabs after deletion.
@MainActor
static func wipeLocalState() {
let defaults = UserDefaults.standard
let credentialKeys: Set<String> = [
"publicFeedURL", "vfileURL",
"githubToken", "codebergToken", "webdavURL"
]
let dict = defaults.dictionaryRepresentation()
for key in dict.keys where key != "eulaAcceptedVersion" && !credentialKeys.contains(key) {
defaults.removeObject(forKey: key)
}
// Credential keys are written last with `set("")` so `@AppStorage`
// wrappers observing them publish a change. `removeObject` alone is
// not always observed by `@AppStorage` in iOS 17 / 18, which would
// leave the root view stuck on the configured tabs after deletion.
for key in credentialKeys {
defaults.set("", forKey: key)
}
BlockList.shared.reset()
}
}