| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- import Foundation
- import StoreKit
- /// Maps backend, StoreKit, and file errors to short, safe copy for the UI (always via `L()`).
- enum UserFacingErrorMessage {
- enum PurchaseContext {
- case purchase
- case restore
- }
- static func jobSearchFailure(_ error: Error) -> String {
- let ns = error as NSError
- if ns.domain == "OpenAIJobSearchService", ns.code == 1 {
- return L("Job search is unavailable.")
- }
- if let urlError = error as? URLError, urlError.code == .cancelled {
- return L("The search was cancelled. Try again when you're ready.")
- }
- if let message = connectivityFailure(for: error) {
- return message
- }
- return L("Something went wrong while searching. Please try again in a moment.")
- }
- static func purchaseFailure(_ error: Error, context: PurchaseContext = .purchase) -> String {
- if let subscription = error as? SubscriptionStoreError {
- return subscription.userFacingMessage
- }
- if let storeKit = error as? StoreKitError {
- return message(for: storeKit, context: context)
- }
- if let purchase = error as? Product.PurchaseError {
- return message(for: purchase)
- }
- if let message = connectivityFailure(for: error) {
- return message
- }
- switch context {
- case .purchase:
- return L("We couldn't complete your purchase. Please try again.")
- case .restore:
- return L("We couldn't restore your purchases. Please try again.")
- }
- }
- static func pdfSaveFailure(_ error: Error) -> String {
- if let cocoa = error as? CocoaError {
- switch cocoa.code {
- case .fileWriteNoPermission:
- return L("We don't have permission to save to that folder. Choose another location or grant access.")
- case .fileWriteVolumeReadOnly:
- return L("This disk is read-only. Choose another folder to save your PDF.")
- case .fileWriteOutOfSpace:
- return L("The disk is full. Free up space, then try again.")
- default:
- break
- }
- }
- if let urlError = error as? URLError {
- switch urlError.code {
- case .fileDoesNotExist, .noPermissionsToReadFile:
- return L("We don't have permission to save to that folder. Choose another location or grant access.")
- default:
- break
- }
- }
- let ns = error as NSError
- if ns.domain == NSCocoaErrorDomain,
- ns.code == NSFileWriteOutOfSpaceError || ns.code == NSFileWriteVolumeReadOnlyError {
- if ns.code == NSFileWriteOutOfSpaceError {
- return L("The disk is full. Free up space, then try again.")
- }
- return L("This disk is read-only. Choose another folder to save your PDF.")
- }
- if let message = connectivityFailure(for: error) {
- return message
- }
- return L("We couldn't save the PDF. Try again or choose a different folder.")
- }
- private static func connectivityFailure(for error: Error) -> String? {
- if let storeKit = error as? StoreKitError,
- case .networkError(let urlError) = storeKit {
- return connectivityFailure(for: urlError)
- }
- if let urlError = error as? URLError {
- return connectivityFailure(for: urlError)
- }
- return nil
- }
- private static func connectivityFailure(for urlError: URLError) -> String? {
- switch urlError.code {
- case .notConnectedToInternet,
- .networkConnectionLost,
- .timedOut,
- .cannotFindHost,
- .cannotConnectToHost,
- .dnsLookupFailed:
- return L("We couldn't reach the server. Check your internet connection and try again.")
- case .cancelled:
- return L("The request was cancelled. Try again when you're ready.")
- default:
- return nil
- }
- }
- private static func message(for error: StoreKitError, context: PurchaseContext) -> String {
- switch error {
- case .userCancelled:
- return L("Purchase was cancelled.")
- case .networkError(let urlError):
- return connectivityFailure(for: urlError)
- ?? (context == .restore
- ? L("We couldn't restore your purchases. Please try again.")
- : L("We couldn't complete your purchase. Please try again."))
- case .notAvailableInStorefront:
- return L("This subscription isn't available in your App Store region.")
- case .notEntitled:
- return L("The purchase couldn't be verified. Please try again.")
- case .unsupported, .unknown, .systemError:
- return context == .restore
- ? L("We couldn't restore your purchases. Please try again.")
- : L("We couldn't complete your purchase. Please try again.")
- @unknown default:
- return context == .restore
- ? L("We couldn't restore your purchases. Please try again.")
- : L("We couldn't complete your purchase. Please try again.")
- }
- }
- private static func message(for error: Product.PurchaseError) -> String {
- switch error {
- case .productUnavailable:
- return L("That subscription isn’t available from the App Store right now.")
- case .purchaseNotAllowed:
- return L("Purchases aren't allowed on this account or device.")
- case .ineligibleForOffer:
- return L("This introductory offer isn't available for your account.")
- case .invalidQuantity, .invalidOfferIdentifier, .invalidOfferPrice,
- .invalidOfferSignature, .missingOfferParameters:
- return L("We couldn't complete your purchase. Please try again.")
- @unknown default:
- return L("We couldn't complete your purchase. Please try again.")
- }
- }
- }
- private extension SubscriptionStoreError {
- var userFacingMessage: String {
- switch self {
- case .productUnavailable:
- return L("That subscription isn’t available from the App Store right now.")
- }
- }
- }
|