1import React, {Component} from 'react' 2import logger from '../../logger' 3import * as Kb from '../../common-adapters' 4import * as Styles from '../../styles' 5import {ignoreDisconnectOverlay} from '../../local-debug.desktop' 6import {RPCError} from '../../util/errors' 7import {Props} from './index' 8 9type Size = 'Closed' | 'Small' | 'Big' 10 11type State = { 12 size: Size 13 cachedSummary?: string 14 cachedDetails?: string 15} 16 17class GlobalError extends Component<Props, State> { 18 state: State 19 private timerID?: ReturnType<typeof setInterval> 20 private mounted: boolean = false 21 22 constructor(props: Props) { 23 super(props) 24 25 this.state = { 26 cachedDetails: this.detailsForError(props.error), 27 cachedSummary: this.summaryForError(props.error), 28 size: 'Closed', 29 } 30 } 31 32 componentWillUnmount() { 33 this.mounted = false 34 this.clearCountdown() 35 } 36 37 componentDidMount() { 38 this.mounted = true 39 this.resetError(!!this.props.error) 40 } 41 42 private onExpandClick = () => { 43 this.setState({size: 'Big'}) 44 this.clearCountdown() 45 } 46 47 private clearCountdown() { 48 if (this.timerID) { 49 clearTimeout(this.timerID) 50 } 51 this.timerID = undefined 52 } 53 54 private resetError(newError: boolean) { 55 this.clearCountdown() 56 this.setState({size: newError ? 'Small' : 'Closed'}) 57 58 if (newError) { 59 this.timerID = setTimeout(() => { 60 this.props.onDismiss() 61 }, 10000) 62 } 63 } 64 65 private summaryForError(err?: Error | RPCError) { 66 return err?.message 67 } 68 69 private detailsForError(err?: Error | RPCError) { 70 return err?.stack 71 } 72 73 componentDidUpdate(prevProps: Props) { 74 if (prevProps.error !== this.props.error) { 75 setTimeout( 76 () => { 77 if (this.mounted) { 78 this.setState({ 79 cachedDetails: this.detailsForError(this.props.error), 80 cachedSummary: this.summaryForError(this.props.error), 81 }) 82 } 83 }, 84 this.props.error ? 0 : 7000 85 ) // if it's set, do it immediately, if it's cleared set it in a bit 86 this.resetError(!!this.props.error) 87 } 88 } 89 90 static maxHeightForSize(size: Size) { 91 return { 92 Big: 900, 93 Closed: 0, 94 Small: 35, 95 }[size] 96 } 97 98 private renderDaemonError() { 99 if (ignoreDisconnectOverlay) { 100 logger.warn('Ignoring disconnect overlay') 101 return null 102 } 103 104 const message = 105 (this.props.daemonError && this.props.daemonError.message) || 106 'Keybase is currently unreachable. Trying to reconnect you…' 107 return ( 108 <Kb.Box style={styles.containerOverlay}> 109 <Kb.Box style={styles.overlayRow}> 110 <Kb.Text center={true} type="BodySmallSemibold" style={styles.message}> 111 {message} 112 </Kb.Text> 113 </Kb.Box> 114 <Kb.Box style={styles.overlayFill}> 115 <Kb.Animation animationType="disconnected" height={175} width={600} /> 116 </Kb.Box> 117 </Kb.Box> 118 ) 119 } 120 121 private renderError() { 122 const {onDismiss} = this.props 123 const summary = this.state.cachedSummary 124 const details = this.state.cachedDetails 125 126 let stylesContainer: Styles.StylesCrossPlatform 127 switch (this.state.size) { 128 case 'Big': 129 stylesContainer = styles.containerBig 130 break 131 case 'Closed': 132 stylesContainer = styles.containerClosed 133 break 134 case 'Small': 135 stylesContainer = styles.containerSmall 136 break 137 } 138 139 return ( 140 <Kb.Box style={stylesContainer} onClick={this.onExpandClick}> 141 <Kb.Box style={styles.innerContainer}> 142 <Kb.Text center={true} type="BodyBig" style={styles.summary}> 143 {summary} 144 </Kb.Text> 145 <Kb.Button 146 label="Please tell us" 147 onClick={this.props.onFeedback} 148 small={true} 149 type="Dim" 150 style={styles.feedbackButton} 151 /> 152 {summary && ( 153 <Kb.Icon 154 color={Styles.globalColors.white_75} 155 hoverColor={Styles.globalColors.white} 156 onClick={onDismiss} 157 style={styles.closeIcon} 158 type="iconfont-close" 159 /> 160 )} 161 </Kb.Box> 162 <Kb.Text center={true} type="BodyBig" selectable={true} style={styles.details}> 163 {details} 164 </Kb.Text> 165 </Kb.Box> 166 ) 167 } 168 169 render() { 170 if (this.props.daemonError) { 171 return this.renderDaemonError() 172 } 173 return this.renderError() 174 } 175} 176 177const containerBase = { 178 ...Styles.globalStyles.flexBoxColumn, 179 left: 0, 180 overflow: 'hidden', 181 position: 'absolute', 182 right: 0, 183 top: 0, 184 zIndex: 1000, 185 ...Styles.transition('max-height'), 186} 187 188const styles = Styles.styleSheetCreate( 189 () => 190 ({ 191 closeIcon: Styles.platformStyles({ 192 isElectron: { 193 position: 'absolute', 194 right: Styles.globalMargins.xsmall, 195 top: 10, 196 }, 197 }), 198 containerBig: {...containerBase, maxHeight: GlobalError.maxHeightForSize('Big')}, 199 containerClosed: {...containerBase, maxHeight: GlobalError.maxHeightForSize('Closed')}, 200 containerOverlay: { 201 ...Styles.globalStyles.flexBoxColumn, 202 bottom: 0, 203 left: 0, 204 position: 'absolute', 205 right: 0, 206 top: 0, 207 zIndex: 1000, 208 }, 209 containerSmall: {...containerBase, maxHeight: GlobalError.maxHeightForSize('Small')}, 210 details: { 211 backgroundColor: Styles.globalColors.black, 212 color: Styles.globalColors.white_75, 213 padding: 8, 214 paddingLeft: Styles.globalMargins.xlarge, 215 paddingRight: Styles.globalMargins.xlarge, 216 }, 217 feedbackButton: { 218 marginRight: Styles.globalMargins.large, 219 }, 220 innerContainer: { 221 ...Styles.globalStyles.flexBoxRow, 222 alignItems: 'center', 223 backgroundColor: Styles.globalColors.black, 224 flex: 1, 225 justifyContent: 'center', 226 minHeight: GlobalError.maxHeightForSize('Small'), 227 padding: Styles.globalMargins.xtiny, 228 position: 'relative', 229 }, 230 message: { 231 color: Styles.globalColors.white, 232 }, 233 overlayFill: { 234 ...Styles.globalStyles.flexBoxColumn, 235 alignItems: 'center', 236 backgroundColor: Styles.globalColors.white, 237 flex: 1, 238 justifyContent: 'center', 239 }, 240 overlayRow: { 241 ...Styles.globalStyles.flexBoxRow, 242 alignItems: 'center', 243 backgroundColor: Styles.globalColors.blue, 244 justifyContent: 'center', 245 padding: 8, 246 }, 247 summary: { 248 color: Styles.globalColors.white, 249 flex: 1, 250 }, 251 summaryRow: { 252 ...Styles.globalStyles.flexBoxRow, 253 alignItems: 'center', 254 flex: 1, 255 justifyContent: 'center', 256 padding: Styles.globalMargins.xtiny, 257 position: 'relative', 258 }, 259 summaryRowError: { 260 backgroundColor: Styles.globalColors.black, 261 minHeight: GlobalError.maxHeightForSize('Small'), 262 }, 263 } as const) 264) 265 266export default GlobalError 267