2024-07-28 17:54:58 +01:00
//
// E r r o r A l e r t . s w i f t
// S i m p l e X C h a t
//
// C r e a t e d b y L e v i t a t i n g P i n e a p p l e o n 2 0 / 0 7 / 2 0 2 4 .
// C o p y r i g h t © 2 0 2 4 S i m p l e X C h a t . A l l r i g h t s r e s e r v e d .
//
import SwiftUI
public struct ErrorAlert : Error {
public let title : LocalizedStringKey
public let message : LocalizedStringKey ?
public let actions : Optional < ( ) -> AnyView >
public init (
title : LocalizedStringKey ,
message : LocalizedStringKey ? = nil
) {
self . title = title
self . message = message
self . actions = nil
}
public init < A : View > (
title : LocalizedStringKey ,
message : LocalizedStringKey ? = nil ,
@ ViewBuilder actions : @ escaping ( ) -> A
) {
self . title = title
self . message = message
self . actions = { AnyView ( actions ( ) ) }
}
public init ( _ title : LocalizedStringKey ) {
self = ErrorAlert ( title : title )
}
public init ( _ error : any Error ) {
2025-05-05 11:51:22 +01:00
self = if let e = error as ? ChatError {
ErrorAlert ( e )
2024-07-28 17:54:58 +01:00
} else {
2025-02-03 20:47:32 +00:00
ErrorAlert ( " \( error . localizedDescription ) " )
2024-07-28 17:54:58 +01:00
}
}
public init ( _ chatError : ChatError ) {
2025-05-05 11:51:22 +01:00
self = if let networkErrorAlert = getNetworkErrorAlert ( chatError ) {
2024-07-28 17:54:58 +01:00
networkErrorAlert
} else {
2025-05-05 11:51:22 +01:00
ErrorAlert ( " \( chatErrorString ( chatError ) ) " )
2024-07-28 17:54:58 +01:00
}
}
}
extension LocalizedStringKey : @ unchecked Sendable { }
extension View {
// / B r i d g e s ` ` E r r o r A l e r t ` ` t o t h e g e n e r i c a l e r t A P I .
// / - P a r a m e t e r s :
// / - e r r o r A l e r t : B i n d i n g t o t h e E r r o r , w h i c h i s r e n d e r e d i n t h e a l e r t
// / - a c t i o n s : V i e w B u i l d e r c o n t a i n i n g a c t i o n b u t t o n s .
// / S y s t e m d e f a u l t s t o ` O k ` d i s m i s s e r r o r a c t i o n , w h e n n o a c t i o n s a r e p r o v i d e d .
// / S y s t e m i m p l i c i t l y a d d s ` C a n c e l ` a c t i o n , i f a d e s t r u c t i v e a c t i o n i s p r e s e n t
// /
// / - R e t u r n s : V i e w , w h i c h d i s p l a y s E r r o r A l e r t ? , w h e n s e t .
@ ViewBuilder public func alert < A : View > (
_ errorAlert : Binding < ErrorAlert ? > ,
@ ViewBuilder actions : ( ErrorAlert ) -> A = { _ in EmptyView ( ) }
) -> some View {
2024-08-01 12:21:31 +03:00
alert (
errorAlert . wrappedValue ? . title ? ? " " ,
isPresented : Binding < Bool > (
get : { errorAlert . wrappedValue != nil } ,
set : { if ! $0 { errorAlert . wrappedValue = nil } }
) ,
actions : {
if let actions_ = errorAlert . wrappedValue ? . actions {
actions_ ( )
} else {
if let alert = errorAlert . wrappedValue { actions ( alert ) }
2024-07-28 17:54:58 +01:00
}
2024-08-01 12:21:31 +03:00
} ,
message : {
if let message = errorAlert . wrappedValue ? . message {
Text ( message )
}
}
)
2024-07-28 17:54:58 +01:00
}
}
2025-05-05 11:51:22 +01:00
public func getNetworkErrorAlert ( _ e : ChatError ) -> ErrorAlert ? {
switch e {
2025-05-02 12:27:08 +01:00
case let . errorAgent ( . BROKER ( addr , . TIMEOUT ) ) :
ErrorAlert ( title : " Connection timeout " , message : " Please check your network connection with \( serverHostname ( addr ) ) and try again. " )
case let . errorAgent ( . BROKER ( addr , . NETWORK ) ) :
ErrorAlert ( title : " Connection error " , message : " Please check your network connection with \( serverHostname ( addr ) ) and try again. " )
case let . errorAgent ( . BROKER ( addr , . HOST ) ) :
ErrorAlert ( title : " Connection error " , message : " Server address is incompatible with network settings: \( serverHostname ( addr ) ) . " )
case let . errorAgent ( . BROKER ( addr , . TRANSPORT ( . version ) ) ) :
ErrorAlert ( title : " Connection error " , message : " Server version is incompatible with your app: \( serverHostname ( addr ) ) . " )
case let . errorAgent ( . SMP ( serverAddress , . PROXY ( proxyErr ) ) ) :
smpProxyErrorAlert ( proxyErr , serverAddress )
case let . errorAgent ( . PROXY ( proxyServer , relayServer , . protocolError ( . PROXY ( proxyErr ) ) ) ) :
proxyDestinationErrorAlert ( proxyErr , proxyServer , relayServer )
default : nil
2024-07-28 17:54:58 +01:00
}
}
private func smpProxyErrorAlert ( _ proxyErr : ProxyError , _ srvAddr : String ) -> ErrorAlert ? {
switch proxyErr {
case . BROKER ( brokerErr : . TIMEOUT ) :
return ErrorAlert ( title : " Private routing error " , message : " Error connecting to forwarding server \( serverHostname ( srvAddr ) ) . Please try later. " )
case . BROKER ( brokerErr : . NETWORK ) :
return ErrorAlert ( title : " Private routing error " , message : " Error connecting to forwarding server \( serverHostname ( srvAddr ) ) . Please try later. " )
case . BROKER ( brokerErr : . HOST ) :
return ErrorAlert ( title : " Private routing error " , message : " Forwarding server address is incompatible with network settings: \( serverHostname ( srvAddr ) ) . " )
case . BROKER ( brokerErr : . TRANSPORT ( . version ) ) :
return ErrorAlert ( title : " Private routing error " , message : " Forwarding server version is incompatible with network settings: \( serverHostname ( srvAddr ) ) . " )
default :
return nil
}
}
private func proxyDestinationErrorAlert ( _ proxyErr : ProxyError , _ proxyServer : String , _ relayServer : String ) -> ErrorAlert ? {
switch proxyErr {
case . BROKER ( brokerErr : . TIMEOUT ) :
return ErrorAlert ( title : " Private routing error " , message : " Forwarding server \( serverHostname ( proxyServer ) ) failed to connect to destination server \( serverHostname ( relayServer ) ) . Please try later. " )
case . BROKER ( brokerErr : . NETWORK ) :
return ErrorAlert ( title : " Private routing error " , message : " Forwarding server \( serverHostname ( proxyServer ) ) failed to connect to destination server \( serverHostname ( relayServer ) ) . Please try later. " )
case . NO_SESSION :
return ErrorAlert ( title : " Private routing error " , message : " Forwarding server \( serverHostname ( proxyServer ) ) failed to connect to destination server \( serverHostname ( relayServer ) ) . Please try later. " )
case . BROKER ( brokerErr : . HOST ) :
return ErrorAlert ( title : " Private routing error " , message : " Destination server address of \( serverHostname ( relayServer ) ) is incompatible with forwarding server \( serverHostname ( proxyServer ) ) settings. " )
case . BROKER ( brokerErr : . TRANSPORT ( . version ) ) :
return ErrorAlert ( title : " Private routing error " , message : " Destination server version of \( serverHostname ( relayServer ) ) is incompatible with forwarding server \( serverHostname ( proxyServer ) ) . " )
default :
return nil
}
}
public func serverHostname ( _ srv : String ) -> String {
parseServerAddress ( srv ) ? . hostnames . first ? ? srv
}
public func mtrErrorDescription ( _ err : MTRError ) -> LocalizedStringKey {
switch err {
case let . noDown ( dbMigrations ) :
" database version is newer than the app, but no down migration for: \( dbMigrations . joined ( separator : " , " ) ) "
case let . different ( appMigration , dbMigration ) :
" different migration in the app/database: \( appMigration ) / \( dbMigration ) "
}
}