Files
damus/damus/Models/URLHandler.swift
T
Daniel D’Aquino 4324b185fe Improve open action handling for notifications
Push notifications were not opened reliably. To improve robustness, the
following changes were introduced:
1. The notification opening logic was updated to become more similar to
   URL handling, in a way that uses better defined interfaces and
   functions that provide better result guarantees, by separating
   complex handling logic, and the side-effects/mutations that
   are made after computing the open action — instead of relying on a
   complex logic function that produces side-effects as a result, which
   obfuscates the actual behavior of the function.
2. The LoadableThreadView was expanded and renamed to
   LoadableNostrEventView, to reflect that it can also handle non-thread
   nostr events, such as DMs, which is a necessity for handling push
   notifications.
3. A new type of Notify object, the `QueueableNotify` was introduced, to
   address issues where the listener/handler is not instantiated at the
   time the app notifies that there is a push notification to be opened.
   This was implemented using async streams, which simplifies the usage
   of this down to a simple "for-in" loop.

Closes: https://github.com/damus-io/damus/issues/2825
Changelog-Fixed: Fixed issue where some push notifications would not open in the app and leave users confused
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-02-21 11:28:26 -08:00

108 lines
4.3 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// URLHandler.swift
// damus
//
// Created by Daniel DAquino on 2024-09-06.
//
import Foundation
/// Parses URLs into actions within the app.
///
/// ## Implementation notes
///
/// - This exists so that we can separate the logic of parsing the URL and the actual action within the app. That makes the code more readable, testable, and extensible
struct DamusURLHandler {
/// Parses a URL, handles any needed actions within damus state, and returns the view to be opened in the app
///
/// Side effects: May mutate `damus_state` in some circumstances
///
/// - Parameters:
/// - damus_state: The Damus state. May be mutated as part of this function
/// - url: The URL to be opened
/// - Returns: A view to be shown to the user
static func handle_opening_url_and_compute_view_action(damus_state: DamusState, url: URL) async -> ContentView.ViewOpenAction {
let parsed_url_info = parse_url(url: url)
switch parsed_url_info {
case .profile(let pubkey):
return .route(.ProfileByKey(pubkey: pubkey))
case .filter(let nostrFilter):
let search = SearchModel(state: damus_state, search: nostrFilter)
return .route(.Search(search: search))
case .event(let nostrEvent):
let thread = await ThreadModel(event: nostrEvent, damus_state: damus_state)
return .route(.Thread(thread: thread))
case .event_reference(let event_reference):
return .route(.LoadableNostrEvent(note_reference: event_reference))
case .wallet_connect(let walletConnectURL):
damus_state.wallet.new(walletConnectURL)
return .route(.Wallet(wallet: damus_state.wallet))
case .script(let data):
let model = ScriptModel(data: data, state: .not_loaded)
return .route(.Script(script: model))
case .purple(let purple_url):
return await damus_state.purple.handle(purple_url: purple_url)
case nil:
break
}
return .sheet(.error(ErrorView.UserPresentableError(
user_visible_description: NSLocalizedString("Could not parse the URL you are trying to open.", comment: "User visible error description"),
tip: NSLocalizedString("Please try again, check the URL for typos, or contact support for further help.", comment: "User visible error tips"),
technical_info: "Could not find a suitable open action. User tried to open this URL: \(url.absoluteString)"
)))
}
/// Parses a URL into a structured information object.
///
/// This function does not cause any mutations on the app, or any side-effects.
///
/// - Parameter url: The URL to be parsed
/// - Returns: Structured information about the contents inside the URL. Returns `nil` if URL is not compatible, invalid, or could not be parsed for some reason.
static func parse_url(url: URL) -> ParsedURLInfo? {
if let purple_url = DamusPurpleURL(url: url) {
return .purple(purple_url)
}
if let nwc = WalletConnectURL(str: url.absoluteString) {
return .wallet_connect(nwc)
}
guard let link = decode_nostr_uri(url.absoluteString) else {
return nil
}
switch link {
case .ref(let ref):
switch ref {
case .pubkey(let pk):
return .profile(pk)
case .event(let noteid):
return .event_reference(.note_id(noteid))
case .hashtag(let ht):
return .filter(.filter_hashtag([ht.hashtag]))
case .param, .quote, .reference:
// doesn't really make sense here
break
case .naddr(let naddr):
return .event_reference(.naddr(naddr))
}
case .filter(let filt):
return .filter(filt)
case .script(let script):
return .script(script)
}
return nil
}
enum ParsedURLInfo {
case profile(Pubkey)
case filter(NostrFilter)
case event(NostrEvent)
case event_reference(LoadableNostrEventViewModel.NoteReference)
case wallet_connect(WalletConnectURL)
case script([UInt8])
case purple(DamusPurpleURL)
}
}