eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1import Foundation
2
3// Per-viewer names (#577): what *I* call people, independent of the name they
4// publish. The map is one blob on the server, sealed to the member's OWN
5// public key (ECIES) — other members can't open it; the member's devices,
6// which share the keypair, can. The two RULES here are pure twins of
7// web/static/ui.js displayNameFor / renameNotice — the single source both
8// platforms mirror; drift is a bug (NicknamesTests pins them).
9
10public struct NicknameEntry: Codable, Equatable, Sendable {
11 /// What the viewer calls them.
12 public var name: String
13 /// The published label the viewer last acknowledged — rename detection
14 /// compares against it, so each rename asks exactly once.
15 public var seen: String
16
17 public init(name: String, seen: String) {
18 self.name = name
19 self.seen = seen
20 }
21}
22
23public enum Nicknames {
24 /// The name to show: the viewer's own name for someone wins over the name
25 /// they publish. "" when neither exists (callers pick their own fallback).
26 public static func displayName(_ nick: NicknameEntry?, label: String?) -> String {
27 if let nick, !nick.name.isEmpty { return nick.name }
28 return label ?? ""
29 }
30
31 /// When the published name has moved on from what the viewer last
32 /// acknowledged, they get a choice (keep my name / use theirs) — nil when
33 /// there's nothing to ask: no nickname, no published name yet, the change
34 /// is already acknowledged, or they renamed to exactly what I call them.
35 public static func renameNotice(_ nick: NicknameEntry?, label: String?) -> (from: String, to: String)? {
36 guard let nick, !nick.name.isEmpty, let label, !label.isEmpty else { return nil }
37 if label == nick.seen || label == nick.name { return nil }
38 return (from: nick.name, to: label)
39 }
40
41 // The blob's JSON shape — { v: 1, names: { uid: { name, seen } } },
42 // exactly what the web seals (shell.js saveNicknamesNow).
43 private struct Blob: Codable {
44 var v: Int
45 var names: [String: NicknameEntry]
46 }
47
48 /// Opens a sealed nicknames blob with the member's PKCS#8 private key.
49 public static func open(blobB64: String, privPkcs8B64: String) throws -> [String: NicknameEntry] {
50 let plain = try ECIES.open(sealedB64: blobB64, privPkcs8B64: privPkcs8B64)
51 return (try JSONDecoder().decode(Blob.self, from: plain)).names
52 }
53
54 /// Seals a nicknames map to the member's own public key.
55 public static func seal(_ names: [String: NicknameEntry], recipientPubB64: String) throws -> String {
56 let plain = try JSONEncoder().encode(Blob(v: 1, names: names))
57 return try ECIES.seal(bytes: plain, recipientPubB64: recipientPubB64)
58 }
59}
60