| 1 | import Foundation |
| 2 | |
| 3 | // Invite-link parsing, shared by the iOS and Mac targets (and standalone-testable |
| 4 | // like Crypto.swift — Foundation only, no UI imports). |
| 5 | // |
| 6 | // Two kinds of URL reach the apps from outside: |
| 7 | // - the custom scheme the web welcome page fires: |
| 8 | // elevenmessenger://add?url=<urlencoded https invite link> |
| 9 | // - a universal link: the raw https invite link itself, #fragment included. |
| 10 | // `inviteLink(fromOpenedURL:)` reduces either to the https invite-link string; |
| 11 | // validating that string is the caller's job (each platform has its own parser). |
| 12 | public func inviteLink(fromOpenedURL url: URL) -> String? { |
| 13 | switch url.scheme { |
| 14 | case "elevenmessenger": |
| 15 | // elevenmessenger://add?url=… — URLComponents percent-decodes the value. |
| 16 | guard let c = URLComponents(url: url, resolvingAgainstBaseURL: false), |
| 17 | c.host == "add", |
| 18 | let link = c.queryItems?.first(where: { $0.name == "url" })?.value |
| 19 | else { return nil } |
| 20 | return link |
| 21 | case "http", "https": |
| 22 | return url.absoluteString |
| 23 | default: |
| 24 | return nil |
| 25 | } |
| 26 | } |
| 27 | |
| 28 | // Validate a new-format `/join/<slug>#<T>` link and reduce it to (origin, joinURL). |
| 29 | // The fragment secret may carry the "e1." app-hint prefix or not (older links) — |
| 30 | // both pass; join.js strips the prefix. Legacy `#k=` links are rejected — this |
| 31 | // client targets only the keypair model. |
| 32 | public func parseJoinLink(_ raw: String) -> (origin: String, joinURL: URL)? { |
| 33 | let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) |
| 34 | guard let c = URLComponents(string: trimmed), |
| 35 | let scheme = c.scheme, let host = c.host, |
| 36 | scheme == "http" || scheme == "https", |
| 37 | c.path.contains("/join/"), |
| 38 | let frag = c.fragment, !frag.isEmpty, !frag.contains("k="), |
| 39 | let joinURL = URL(string: trimmed) |
| 40 | else { return nil } |
| 41 | var o = URLComponents() |
| 42 | o.scheme = scheme |
| 43 | o.host = host |
| 44 | o.port = c.port |
| 45 | guard let origin = o.url else { return nil } |
| 46 | return (origin.absoluteString, joinURL) |
| 47 | } |
| 48 | |