Skip to content

Commit

Permalink
Merge pull request #1052 from planetary-social/984/improve_error_catc…
Browse files Browse the repository at this point in the history
…hing

Improve error handling when publishing the metadata
  • Loading branch information
joshuatbrown authored Apr 25, 2024
2 parents 09f399c + c872891 commit 0d2ffff
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Fixed an issue where registering a NIP-05 username field could fail silently.
- Fixed an issue where users named with valid urls were unable to be mentioned correctly.
- Fixed an issue where pasting an npub while composing a note created an invalid mention.
- Changed "Report note" button to "Flag this content"
Expand Down
11 changes: 11 additions & 0 deletions Nos/Assets/Localization/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -10072,6 +10072,17 @@
}
}
},
"retry" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Retry"
}
}
}
},
"returnToChooseName" : {
"extractionState" : "manual",
"localizations" : {
Expand Down
2 changes: 1 addition & 1 deletion Nos/Controller/UNSWizardController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class UNSWizardController: ObservableObject {
author.nip05 = nip05
}
try context.save()
await currentUser.publishMetaData()
try await currentUser.publishMetadata()
state = .success
}
}
92 changes: 66 additions & 26 deletions Nos/Service/CurrentUser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ import Dependencies

enum CurrentUserError: Error {
case authorNotFound
case encodingError
case errorWhilePublishingToRelays

var description: String? {
switch self {
case .authorNotFound:
return "Current user's author not found"
case .encodingError:
return "An encoding error happened while saving the user data"
case .errorWhilePublishingToRelays:
return "An encoding error happened while publishing to relays"
}
}
}
Expand Down Expand Up @@ -263,13 +269,9 @@ enum CurrentUserError: Error {
return followKeys.contains(key)
}

@MainActor func publishMetaData() async {
guard let pubKey = publicKeyHex, let author = try? Author.find(by: pubKey, context: viewContext) else {
Log.debug("Error: no user")
return
}
self.author = author

/// Builds a dictionary to be used as content when publishing a kind 0
/// event.
private func buildMetadataJSONObject(author: Author) -> [String: String] {
var metaEvent = MetadataEventJSON(
displayName: author.displayName,
name: author.name,
Expand All @@ -279,34 +281,72 @@ enum CurrentUserError: Error {
website: author.website,
picture: author.profilePhotoURL?.absoluteString
).dictionary

if let rawData = author.rawMetadata {
// Tack on any unsupported fields back onto the dictionary before publish
let rawJson = try? JSONSerialization.jsonObject(with: rawData)
if let rawJson, let rawDictionary = rawJson as? [String: AnyObject] {
for key in rawDictionary.keys {
if metaEvent[key] == nil, let rawValue = rawDictionary[key] as? String {
metaEvent[key] = rawValue
Log.debug("Added \(key) : \(rawValue)")
// Tack on any unsupported fields back onto the dictionary before
// publish.
do {
let rawJson = try JSONSerialization.jsonObject(with: rawData)
if let rawDictionary = rawJson as? [String: AnyObject] {
for key in rawDictionary.keys {
guard metaEvent[key] == nil else {
continue
}
if let rawValue = rawDictionary[key] as? String {
metaEvent[key] = rawValue
Log.debug("Added \(key) : \(rawValue)")
}
}
}
} catch {
Log.debug("Couldn't parse a JSON from the user raw metadata")
// Continue with the metaEvent object we built previously
}
}
return metaEvent
}

let metaData = try? JSONSerialization.data(withJSONObject: metaEvent)
guard let metaData, let metaString = String(data: metaData, encoding: .utf8) else {
Log.debug("Error: Invalid meta data")
return
@MainActor func publishMetadata() async throws {
guard let pubKey = publicKeyHex else {
Log.debug("Error: no publicKeyHex")
throw CurrentUserError.authorNotFound
}
guard let pair = keyPair else {
Log.debug("Error: no keyPair")
throw CurrentUserError.authorNotFound
}
guard let context = viewContext else {
Log.debug("Error: no context")
throw CurrentUserError.authorNotFound
}
guard let author = try Author.find(by: pubKey, context: context) else {
Log.debug("Error: no author in DB")
throw CurrentUserError.authorNotFound
}

let jsonEvent = JSONEvent(pubKey: pubKey, kind: .metaData, tags: [], content: metaString)
self.author = author

let jsonObject = buildMetadataJSONObject(author: author)
let data = try JSONSerialization.data(withJSONObject: jsonObject)
guard let content = String(data: data, encoding: .utf8) else {
throw CurrentUserError.encodingError
}

if let pair = keyPair {
do {
try await relayService.publishToAll(event: jsonEvent, signingKey: pair, context: viewContext)
} catch {
Log.debug("failed to update Follows \(error.localizedDescription)")
}
let jsonEvent = JSONEvent(
pubKey: pubKey,
kind: .metaData,
tags: [],
content: content
)

do {
try await relayService.publishToAll(
event: jsonEvent,
signingKey: pair,
context: viewContext
)
} catch {
Log.error(error.localizedDescription)
throw CurrentUserError.errorWhilePublishingToRelays
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,11 @@ struct ExcellentChoiceSheet: View {

claimState = .claiming

let oldNIP05 = currentUser.author?.nip05
do {
currentUser.author?.nip05 = "\(username)@nos.social"
try currentUser.viewContext.saveIfNeeded()
try await currentUser.publishMetadata()
let relays = currentUser.author?.relays.compactMap {
$0.addressURL
}
Expand All @@ -111,13 +115,16 @@ struct ExcellentChoiceSheet: View {
keyPair: keyPair,
relays: relays ?? []
)
currentUser.author?.nip05 = "\(username)@nos.social"
try currentUser.viewContext.saveIfNeeded()
await currentUser.publishMetaData()
claimState = .claimed
analytics.registeredNIP05Username()
} catch {
Log.error(error.localizedDescription)

// Do our best reverting the changes.
currentUser.author?.nip05 = oldNIP05
try? currentUser.viewContext.saveIfNeeded()
try? await currentUser.publishMetadata()

claimState = .failed(.unableToClaim(error))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,20 @@ struct NiceWorkSheet: View {

connectState = .connecting

let oldNIP05 = currentUser.author?.nip05
do {
currentUser.author?.nip05 = "\(username)"
try currentUser.viewContext.saveIfNeeded()
await currentUser.publishMetaData()
try await currentUser.publishMetadata()
connectState = .connected
analytics.linkedNIP05Username()
} catch {
Log.error(error.localizedDescription)

// Revert the changes
currentUser.author?.nip05 = oldNIP05
try? currentUser.viewContext.saveIfNeeded()

connectState = .failed(.unableToConnect(error))
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Dependencies
import Logger
import SwiftUI

struct ConfirmUsernameDeletionSheet: View {
Expand Down Expand Up @@ -89,21 +90,34 @@ struct ConfirmUsernameDeletionSheet: View {
deleteState = .deleting
let username = author.nosNIP05Username
let isNosSocialUsername = author.hasNosNIP05
author.nip05 = ""
let oldNIP05 = author.nip05
do {
author.nip05 = ""
try viewContext.save()
await currentUser.publishMetaData()
try await currentUser.publishMetadata()
if isNosSocialUsername {
try? await namesAPI.delete(
username: username,
keyPair: keyPair
)
do {
try await namesAPI.delete(
username: username,
keyPair: keyPair
)
} catch {
Log.debug(error.localizedDescription)
// The delete API could fail if the user didn't have
// connection or the server is down. As we can catch these
// unused usernames later, let the user continue anyway.
}
}
analytics.deletedNIP05Username()
deleteState = .deleted
isPresented = false
} catch {
crashReporting.report(error)

// Reverting the changes
author.nip05 = oldNIP05
try? viewContext.save()

deleteState = .failed(.unableToDelete(error))
}
}
Expand Down
54 changes: 48 additions & 6 deletions Nos/Views/ProfileEdit/ProfileEditView.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Dependencies
import Logger
import SwiftUI

struct ProfileEditView: View {
Expand All @@ -20,11 +21,20 @@ struct ProfileEditView: View {
@State private var showUniversalNameWizard = false
@State private var unsController = UNSWizardController()
@State private var showConfirmationDialog = false
@State private var saveError: SaveError?

init(author: Author) {
self.author = author
}


private var showAlert: Binding<Bool> {
Binding {
saveError != nil
} set: { _ in
saveError = nil
}
}

var body: some View {
NosForm {
AvatarView(imageUrl: URL(string: avatarText), size: 99)
Expand Down Expand Up @@ -130,11 +140,24 @@ struct ProfileEditView: View {
trailing:
ActionButton(title: .localizable.done) {
await save()
// Go back to profile page
router.pop()
}
.offset(y: -3)
)
.alert(isPresented: showAlert, error: saveError) {
Button {
saveError = nil
Task {
await save()
}
} label: {
Text(.localizable.retry)
}
Button {
saveError = nil
} label: {
Text(.localizable.cancel)
}
}
.id(author)
.task {
populateTextFields()
Expand All @@ -153,18 +176,37 @@ struct ProfileEditView: View {
unsText = author.uns ?? ""
}

func save() async {
private func save() async {
author.name = nameText
author.about = bioText
author.profilePhotoURL = URL(string: avatarText)
author.website = website
author.uns = unsText
do {
try viewContext.save()
// Post event
await currentUser.publishMetaData()
try await currentUser.publishMetadata()

// Go back to profile page
router.pop()
} catch CurrentUserError.errorWhilePublishingToRelays {
saveError = SaveError.unableToPublishChanges
} catch {
crashReporting.report(error)
saveError = SaveError.unexpectedError
}
}

enum SaveError: LocalizedError {
case unexpectedError
case unableToPublishChanges

var errorDescription: String? {
switch self {
case .unexpectedError:
return "Something unexpected happened"
case .unableToPublishChanges:
return "We were unable to publish your changes in the network"
}
}
}
}
Expand Down

0 comments on commit 0d2ffff

Please sign in to comment.