Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes alerts when uploading big files and suggests users subscribe to nostr.build #1321

Merged
merged 30 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b17ed98
non-functional update of layout for upload alerts
rabble Jul 20, 2024
fde083b
fixed one problem with localization strings, still stuck on calling t…
rabble Jul 23, 2024
4564fbf
fixed one problem with localization strings, still stuck on calling t…
rabble Jul 23, 2024
e66b0b7
added fix to help users with suggestion to pay for nostr.build to upl…
rabble Jul 23, 2024
f477447
Merge branch 'main' into large_file_upload_error
rabble Jul 23, 2024
c9f73fb
fixes error message when file size is too big by adding error to File…
rabble Jul 24, 2024
b74027b
fixes error message when file size is too big by adding error to File…
rabble Jul 24, 2024
877edd5
Update Nos/Assets/Localization/ImagePicker.xcstrings
rabble Jul 25, 2024
6f61dea
Update Nos/Assets/Localization/ImagePicker.xcstrings
rabble Jul 25, 2024
37d18a3
Update Nos/Assets/Localization/ImagePicker.xcstrings
rabble Jul 25, 2024
25cc94b
Update Nos/Assets/Localization/ImagePicker.xcstrings
rabble Jul 25, 2024
dc1dd5b
reverting package.resolved file
rabble Jul 25, 2024
862c384
adding changelog for file upload error screen
rabble Jul 30, 2024
b3f88fd
Update Nos/Views/New Note/ComposerActionBar.swift
rabble Jul 30, 2024
b4c64f1
Update Nos/Views/New Note/ComposerActionBar.swift
rabble Jul 30, 2024
e6cd12e
Merge branch 'main' into large_file_upload_error
mplorentz Jul 31, 2024
c74b82a
Fix swiftlint errors
mplorentz Jul 31, 2024
ac9c72f
Merge branch 'main' into large_file_upload_error
mplorentz Jul 31, 2024
e1eb233
Merge remote-tracking branch 'origin/main' into large_file_upload_error
pelumy Sep 11, 2024
19ede8b
differentiate buttons to show on alert for large files from other errors
pelumy Sep 11, 2024
781ab37
fix minor swiftlint warnings
pelumy Sep 11, 2024
cd298aa
make function body length shorter to fix swiftlint error
pelumy Sep 11, 2024
a3129c1
fix more swiftlint warnings
pelumy Sep 11, 2024
8276f18
Merge branch 'main' into large_file_upload_error
pelumy Sep 11, 2024
a24d659
Merge branch 'main' into large_file_upload_error
pelumy Sep 12, 2024
4be1b8c
check for error code 413
pelumy Sep 12, 2024
0b5eef9
Merge branch 'main' into large_file_upload_error
pelumy Sep 12, 2024
d9d357a
refactored regular expression for file size limit
joshuatbrown Sep 13, 2024
2aeab85
refactor createAlert function
pelumy Sep 13, 2024
d15cdf4
Update premium nostr url string
pelumy Sep 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed a bug where toggles in the settings screen were white instead of green when toggled on. [#1251](https://github.com/planetary-social/nos/issues/1251)
- Added routing to profile when tapping on follow notification. [#1447](https://github.com/planetary-social/nos/issues/1447)
- Localized follows notifications. [#1446](https://github.com/planetary-social/nos/issues/1446)
- Fixed alert when uploading big files suggesting users pay for nostr.build. [#1321](https://github.com/planetary-social/nos/issues/1321)

### Internal Changes
- Use NIP-92 media metadata to display media in the proper orientation. Currently behind the “Enable new media display” feature flag. [#1172](https://github.com/planetary-social/nos/issues/1172)
Expand Down
117 changes: 117 additions & 0 deletions Nos/Assets/Localization/ImagePicker.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
"value" : "Camera"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cámara"
}
},
"fa" : {
"stringUnit" : {
"state" : "translated",
Expand Down Expand Up @@ -57,6 +63,12 @@
"value" : "Camera is not available on this device"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "La cámara no esta disponible en este aparato"
}
},
"fa" : {
"stringUnit" : {
"state" : "translated",
Expand Down Expand Up @@ -98,6 +110,12 @@
"value" : "Error uploading the file"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Error subiendo el archivo"
}
},
"fa" : {
"stringUnit" : {
"state" : "translated",
Expand All @@ -124,6 +142,40 @@
}
}
},
"errorUploadingFileExceedsLimit" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "The max file size for free media uploads is %@. To upload large files, upgrade to a Nostr.build Professional account."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "El maximo tamaño para publicacion de achivos gratis es %@. Para subir archives mas grande puedes pagar para subscribir a una cuenta Nostr.build pro."
}
}
}
},
"errorUploadingFileExceedsSizeLimit" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Get a Nostr.build account to upload larger files"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Registarse para una cuenta pro de Nostr.build para la abilidad de subir archivos mas grande"
}
}
}
},
"errorUploadingFileMessage" : {
"extractionState" : "manual",
"localizations" : {
Expand All @@ -139,6 +191,12 @@
"value" : "An error was encountered when uploading the file you provided. Please try again."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Hubo un error en el intento de subir archivos. Porfavor intenta de vuelta."
}
},
"fa" : {
"stringUnit" : {
"state" : "translated",
Expand Down Expand Up @@ -173,6 +231,29 @@
"state" : "translated",
"value" : "An error was encountered when uploading the file you provided. The message was: \"%@\""
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Hubo un error en el intento de subir el archivo. El mensaje de error era\"%@\""
}
}
}
},
"getAccount" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Get Account"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Registar una Cuenta"
}
}
}
},
Expand All @@ -191,6 +272,12 @@
"value" : "You can allow camera permissions by opening the Settings app."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Puedes dar permiso a aceder la cámara en el app de configuraciones."
}
},
"fa" : {
"stringUnit" : {
"state" : "translated",
Expand Down Expand Up @@ -232,6 +319,12 @@
"value" : "Permissions required for %@"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Prescisa permiso para %@"
}
},
"fa" : {
"stringUnit" : {
"state" : "translated",
Expand Down Expand Up @@ -273,6 +366,12 @@
"value" : "Photo Library"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bibiloteca de Fotos"
}
},
"fa" : {
"stringUnit" : {
"state" : "translated",
Expand Down Expand Up @@ -314,6 +413,12 @@
"value" : "Select from Photo Library"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Elijir fotos en la biblioteca"
}
},
"fa" : {
"stringUnit" : {
"state" : "translated",
Expand Down Expand Up @@ -366,6 +471,12 @@
"value" : "Take photo or video"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sacar un foto o video"
}
},
"fa" : {
"stringUnit" : {
"state" : "needs_review",
Expand Down Expand Up @@ -407,6 +518,12 @@
"value" : "Uploading..."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Subiendo... "
}
},
"fa" : {
"stringUnit" : {
"state" : "translated",
Expand Down
41 changes: 33 additions & 8 deletions Nos/Service/FileStorage/FileStorageAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum FileStorageAPIClientError: Error {
case invalidResponseURL(String)
case invalidURLRequest
case missingKeyPair
case fileTooBig(String?)
case uploadFailed(String?)
}

Expand Down Expand Up @@ -53,8 +54,9 @@ class NostrBuildAPIClient: FileStorageAPIClient {
assert(fileURL.isFileURL, "The URL must point to a file.")
let apiURL = try await apiURL()
let (request, data) = try uploadRequest(fileAt: fileURL, isProfilePhoto: isProfilePhoto, apiURL: apiURL)
let (responseData, _) = try await URLSession.shared.upload(for: request, from: data)
return try assetURL(from: responseData)
let (responseData, response) = try await URLSession.shared.upload(for: request, from: data)
/// Attempt to retrieve the asset URL from the response data and HTTP response.
return try assetURL(from: responseData, response: response as? HTTPURLResponse)
}

// MARK: - Internal
Expand All @@ -74,19 +76,29 @@ class NostrBuildAPIClient: FileStorageAPIClient {
}

/// The URL of the uploaded asset parsed from the API's response.
private func assetURL(from responseData: Data) throws -> URL {
let response = try decoder.decode(FileStorageUploadResponseJSON.self, from: responseData)
guard let urlString = response.nip94Event?.urlString else {
throw FileStorageAPIClientError.uploadFailed(response.message)
private func assetURL(from responseData: Data, response: HTTPURLResponse?) throws -> URL {
let decodedResponse = try decoder.decode(FileStorageUploadResponseJSON.self, from: responseData)

guard let urlString = decodedResponse.nip94Event?.urlString else {
// Assign an empty string if the response message is nil.
let message = decodedResponse.message ?? ""

// Checks if the response contains a status code of `413 Payload Too Large`.
if let errorCode = response?.statusCode, errorCode == 413 {
// Verify if the error message indicates the file size exceeds the limit.
let fileSizeLimit = fileSizeLimit(from: message)
throw FileStorageAPIClientError.fileTooBig(fileSizeLimit)
}
// Throw an error indicating the upload failed with the provided message.
throw FileStorageAPIClientError.uploadFailed(message)
}

guard let url = URL(string: urlString) else {
throw FileStorageAPIClientError.invalidResponseURL(urlString)
}
return url
}

// MARK: - Internal

/// Fetches server info from the file storage API.
/// - Returns: the decoded JSON containing server info for the file storage API.
func fetchServerInfo() async throws -> FileStorageServerInfoResponseJSON {
Expand All @@ -102,6 +114,19 @@ class NostrBuildAPIClient: FileStorageAPIClient {
throw FileStorageAPIClientError.decodingError
}
}

/// Gets the file size limit from the error message.
/// - Parameter message: The error message from nostr.build.
/// - Returns: The file size limit from the error message.
func fileSizeLimit(from message: String) -> String? {
let pattern = /File size exceeds the limit of (\d*\.\d* [MKGT]B)/

guard let match = message.firstMatch(of: pattern) else {
return nil
}

return String(match.1)
}

/// Creates a URLRequest and Data from a file URL to be uploaded to the file storage API.
func uploadRequest(fileAt fileURL: URL, isProfilePhoto: Bool, apiURL: URL) throws -> (URLRequest, Data) {
Expand Down
66 changes: 53 additions & 13 deletions Nos/Views/NoteComposer/ComposerActionBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ struct ComposerActionBar: View {
@State private var alert: AlertState<AlertAction>?

fileprivate enum AlertAction {
case cancel
case getAccount
}

var backArrow: some View {
Expand Down Expand Up @@ -68,7 +70,15 @@ struct ComposerActionBar: View {
.onChange(of: expirationTime) { _, _ in
subMenu = .none
}
.alert(unwrapping: $alert) { (_: AlertAction?) in
.alert(unwrapping: $alert) { action in
switch action {
case .getAccount:
if let url = URL(string: "https://nostr.build/plans/") {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
default:
break
}
}
.background(
LinearGradient(
Expand Down Expand Up @@ -152,7 +162,9 @@ struct ComposerActionBar: View {

/// Uploads an image at the given URL to a file storage service.
/// - Parameter imageURL: File URL of the image the user wants to upload.
private func uploadImage(at imageURL: URL) async {
private func uploadImage(
at imageURL: URL
) async {
do {
startUploadingImage()
let url = try await fileStorageAPIClient.upload(fileAt: imageURL, isProfilePhoto: false)
Expand All @@ -161,17 +173,7 @@ struct ComposerActionBar: View {
} catch {
endUploadingImage()

alert = AlertState {
TextState(String(localized: .imagePicker.errorUploadingFile))
} message: {
if case let FileStorageAPIClientError.uploadFailed(message) = error, let message {
TextState(
String(localized: .imagePicker.errorUploadingFileWithMessage(message))
)
} else {
TextState(String(localized: .imagePicker.errorUploadingFileMessage))
}
}
alert = createAlert(for: error)
}
}

Expand All @@ -183,6 +185,44 @@ struct ComposerActionBar: View {
self.isUploadingImage = false
self.subMenu = .none
}

/// Creates an alert based on the error
private func createAlert(
for error: Error
) -> AlertState<ComposerActionBar.AlertAction> {
joshuatbrown marked this conversation as resolved.
Show resolved Hide resolved
var title = String(localized: .imagePicker.errorUploadingFile)
var message: String
var buttons: [ButtonState<ComposerActionBar.AlertAction>] = [
.default(
TextState(String(localized: .localizable.ok)),
action: .send(.cancel)
)
]

if case let FileStorageAPIClientError.fileTooBig(errorMessage) = error, let errorMessage {
title = String(localized: .imagePicker.errorUploadingFileExceedsSizeLimit)
message = String(localized: .imagePicker.errorUploadingFileExceedsLimit(errorMessage))
buttons = [
.cancel(
TextState(String(localized: .localizable.cancel)), action: .send(.cancel)
),
.default(
TextState(String(localized: .imagePicker.getAccount)),
action: .send(.getAccount)
)
]
} else if case let FileStorageAPIClientError.uploadFailed(errorMessage) = error, let errorMessage {
message = String(localized: .imagePicker.errorUploadingFileWithMessage(errorMessage))
} else {
message = String(localized: .imagePicker.errorUploadingFileMessage)
}

return AlertState(
title: TextState(title),
message: TextState(message),
buttons: buttons
)
}
}

struct ComposerActionBar_Previews: PreviewProvider {
Expand Down
Loading
Loading