URLHandler.swift (4370B)
1 // 2 // URLHandler.swift 3 // damus 4 // 5 // Created by Daniel D’Aquino on 2024-09-06. 6 // 7 8 import Foundation 9 10 /// Parses URLs into actions within the app. 11 /// 12 /// ## Implementation notes 13 /// 14 /// - 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 15 struct DamusURLHandler { 16 /// Parses a URL, handles any needed actions within damus state, and returns the view to be opened in the app 17 /// 18 /// Side effects: May mutate `damus_state` in some circumstances 19 /// 20 /// - Parameters: 21 /// - damus_state: The Damus state. May be mutated as part of this function 22 /// - url: The URL to be opened 23 /// - Returns: A view to be shown to the user 24 static func handle_opening_url_and_compute_view_action(damus_state: DamusState, url: URL) async -> ContentView.ViewOpenAction { 25 let parsed_url_info = parse_url(url: url) 26 27 switch parsed_url_info { 28 case .profile(let pubkey): 29 return .route(.ProfileByKey(pubkey: pubkey)) 30 case .filter(let nostrFilter): 31 let search = SearchModel(state: damus_state, search: nostrFilter) 32 return .route(.Search(search: search)) 33 case .event(let nostrEvent): 34 let thread = await ThreadModel(event: nostrEvent, damus_state: damus_state) 35 return .route(.Thread(thread: thread)) 36 case .event_reference(let event_reference): 37 return .route(.LoadableNostrEvent(note_reference: event_reference)) 38 case .wallet_connect(let walletConnectURL): 39 damus_state.wallet.new(walletConnectURL) 40 return .route(.Wallet(wallet: damus_state.wallet)) 41 case .script(let data): 42 let model = ScriptModel(data: data, state: .not_loaded) 43 return .route(.Script(script: model)) 44 case .purple(let purple_url): 45 return await damus_state.purple.handle(purple_url: purple_url) 46 case nil: 47 break 48 } 49 return .sheet(.error(ErrorView.UserPresentableError( 50 user_visible_description: NSLocalizedString("Could not parse the URL you are trying to open.", comment: "User visible error description"), 51 tip: NSLocalizedString("Please try again, check the URL for typos, or contact support for further help.", comment: "User visible error tips"), 52 technical_info: "Could not find a suitable open action. User tried to open this URL: \(url.absoluteString)" 53 ))) 54 } 55 56 /// Parses a URL into a structured information object. 57 /// 58 /// This function does not cause any mutations on the app, or any side-effects. 59 /// 60 /// - Parameter url: The URL to be parsed 61 /// - 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. 62 static func parse_url(url: URL) -> ParsedURLInfo? { 63 if let purple_url = DamusPurpleURL(url: url) { 64 return .purple(purple_url) 65 } 66 67 if let nwc = WalletConnectURL(str: url.absoluteString) { 68 return .wallet_connect(nwc) 69 } 70 71 guard let link = decode_nostr_uri(url.absoluteString) else { 72 return nil 73 } 74 75 switch link { 76 case .ref(let ref): 77 switch ref { 78 case .pubkey(let pk): 79 return .profile(pk) 80 case .event(let noteid): 81 return .event_reference(.note_id(noteid)) 82 case .hashtag(let ht): 83 return .filter(.filter_hashtag([ht.hashtag])) 84 case .param, .quote, .reference: 85 // doesn't really make sense here 86 break 87 case .naddr(let naddr): 88 return .event_reference(.naddr(naddr)) 89 } 90 case .filter(let filt): 91 return .filter(filt) 92 case .script(let script): 93 return .script(script) 94 } 95 return nil 96 } 97 98 enum ParsedURLInfo { 99 case profile(Pubkey) 100 case filter(NostrFilter) 101 case event(NostrEvent) 102 case event_reference(LoadableNostrEventViewModel.NoteReference) 103 case wallet_connect(WalletConnectURL) 104 case script([UInt8]) 105 case purple(DamusPurpleURL) 106 } 107 }