Files
damus/damus/Models/Purple/DamusPurpleURL.swift
Daniel D’Aquino 8066fa1bf8 Improve robustness of the URL handler
This commit improves reliability on the handling of
external URLs.

This was achieved through the following improvements:
1. The URL handler interface is now well-defined, with more clear inputs
   and outputs, to avoid silent failures and error paths that are hard to see
   within convoluted logic paths
2. Side effects during URL parsing were almost completely removed for
   more predictable behavior
3. Error handling logic was added to present errors to the user in a user-friendly manner,
   instead of silently failing
4. Event loading logic was moved into a special new thread view, which
   makes its own internal state evident to the user (i.e. whether
   the note is loading, loaded, or if the note could not be found)

These changes make the URL opening logic more predictable, easy to
refactor, and helps ensure the user always gets some outcome from
opening a URL, even if it means showing a "not found" or "error" screen,
to eliminate cases where nothing seems to happen.

Closes: https://github.com/damus-io/damus/issues/2429
Changelog-Fixed: Improved robustness of the URL handler
Changelog-Added: Added user-friendly error view for errors around the app that would not fit in other places
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-01-24 10:05:55 -08:00

64 lines
1.9 KiB
Swift

//
// DamusPurpleURL.swift
// damus
//
// Created by Daniel D'Aquino on 2024-01-13.
//
import Foundation
struct DamusPurpleURL: Equatable {
let is_staging: Bool
let variant: Self.Variant
enum Variant: Equatable {
case verify_npub(checkout_id: String)
case welcome(checkout_id: String)
case landing
}
init(is_staging: Bool, variant: Self.Variant) {
self.is_staging = is_staging
self.variant = variant
}
init?(url: URL) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return nil }
guard components.scheme == "damus" else { return nil }
let is_staging = components.find("staging") != nil
switch components.path {
case "purple:verify":
guard let checkout_id = components.find("id") else { return nil }
self = .init(is_staging: is_staging, variant: .verify_npub(checkout_id: checkout_id))
case "purple:welcome":
guard let checkout_id = components.find("id") else { return nil }
self = .init(is_staging: is_staging, variant: .welcome(checkout_id: checkout_id))
case "purple:landing":
self = .init(is_staging: is_staging, variant: .landing)
default:
return nil
}
}
func url_string() -> String {
let staging = is_staging ? "&staging=true" : ""
switch self.variant {
case .verify_npub(let id):
return "damus:purple:verify?id=\(id)\(staging)"
case .welcome(let id):
return "damus:purple:welcome?id=\(id)\(staging)"
case .landing:
let staging = is_staging ? "?staging=true" : ""
return "damus:purple:landing\(staging)"
}
}
}
extension URLComponents {
func find(_ name: String) -> String? {
self.queryItems?.first(where: { qi in qi.name == name })?.value
}
}