| 1 | import Foundation |
| 2 | |
| 3 | // Private moderation preferences (#578): who *I've* muted, synced across my |
| 4 | // devices as one blob sealed to my OWN public key — the exact posture the |
| 5 | // nicknames blob uses (Nicknames.swift). Nothing changes for a muted person |
| 6 | // and the server can't read the set; only this member's devices can. |
| 7 | // |
| 8 | // The JSON shape is the cross-platform contract — { v: 1, mutedPeople: |
| 9 | // ["uid", …] }, exactly what the web seals (shell.js saveModPrefsNow). Match |
| 10 | // it byte-for-name; drift here would silently fork the muted set per device. |
| 11 | public enum ModPrefs { |
| 12 | private struct Blob: Codable { |
| 13 | var v: Int |
| 14 | // Optional on decode so a future blob that dropped/renamed the field |
| 15 | // reads as "nobody muted" instead of failing the whole open. |
| 16 | var mutedPeople: [String]? |
| 17 | } |
| 18 | |
| 19 | /// Opens a sealed prefs blob with the member's PKCS#8 private key. |
| 20 | public static func open(blobB64: String, privPkcs8B64: String) throws -> Set<String> { |
| 21 | let plain = try ECIES.open(sealedB64: blobB64, privPkcs8B64: privPkcs8B64) |
| 22 | return Set((try JSONDecoder().decode(Blob.self, from: plain)).mutedPeople ?? []) |
| 23 | } |
| 24 | |
| 25 | /// Seals a muted-people set to the member's own public key. Sorted so the |
| 26 | /// same set always seals the same plaintext (the ciphertext still varies — |
| 27 | /// fresh ephemeral key — but the contract stays deterministic). |
| 28 | public static func seal(_ muted: Set<String>, recipientPubB64: String) throws -> String { |
| 29 | let plain = try JSONEncoder().encode(Blob(v: 1, mutedPeople: muted.sorted())) |
| 30 | return try ECIES.seal(bytes: plain, recipientPubB64: recipientPubB64) |
| 31 | } |
| 32 | } |
| 33 | |