Skip to content

Commit 37affcd

Browse files
committed
refactor error handling
1 parent 508eaa0 commit 37affcd

File tree

3 files changed

+273
-234
lines changed

3 files changed

+273
-234
lines changed

src/app/Flash.jsx

+46-72
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useEffect, useRef, useState } from 'react'
22

3-
import { FlashManager, StepCode, ErrorCode } from '../utils/manager'
3+
import { FlashManager, StepCode } from '../utils/manager'
44
import { useImageManager } from '../utils/image'
5-
import { isLinux } from '../utils/platform'
5+
import { parseError } from '../utils/errors'
66
import config from '../config'
77

88
import bolt from '../assets/bolt.svg'
@@ -13,6 +13,16 @@ import done from '../assets/done.svg'
1313
import exclamation from '../assets/exclamation.svg'
1414
import systemUpdate from '../assets/system_update_c3.svg'
1515

16+
// Icons mapped by name for error handling
17+
const icons = {
18+
bolt,
19+
cable,
20+
deviceExclamation,
21+
deviceQuestion,
22+
done,
23+
exclamation,
24+
systemUpdate
25+
}
1626

1727
const steps = {
1828
[StepCode.INITIALIZING]: {
@@ -64,60 +74,6 @@ const steps = {
6474
},
6575
}
6676

67-
const errors = {
68-
[ErrorCode.UNKNOWN]: {
69-
status: 'Unknown error',
70-
description: 'An unknown error has occurred. Unplug your device, restart your browser and try again.',
71-
bgColor: 'bg-red-500',
72-
icon: exclamation,
73-
},
74-
[ErrorCode.REQUIREMENTS_NOT_MET]: {
75-
status: 'Requirements not met',
76-
description: 'Your system does not meet the requirements to flash your device. Make sure to use a browser which ' +
77-
'supports WebUSB and is up to date.',
78-
},
79-
[ErrorCode.STORAGE_SPACE]: {
80-
description: 'Your system does not have enough space available to download AGNOS. Your browser may be restricting' +
81-
' the available space if you are in a private, incognito or guest session.',
82-
},
83-
[ErrorCode.UNRECOGNIZED_DEVICE]: {
84-
status: 'Unrecognized device',
85-
description: 'The device connected to your computer is not supported. Try using a different cable, USB port, or ' +
86-
'computer. If the problem persists, join the #hw-three-3x channel on Discord for help.',
87-
bgColor: 'bg-yellow-500',
88-
icon: deviceQuestion,
89-
},
90-
[ErrorCode.LOST_CONNECTION]: {
91-
status: 'Lost connection',
92-
description: 'The connection to your device was lost. Unplug your device and try again.',
93-
icon: cable,
94-
},
95-
[ErrorCode.REPAIR_PARTITION_TABLES_FAILED]: {
96-
status: 'Repairing partition tables failed',
97-
description: 'Your device\'s partition tables could not be repaired. Try using a different cable, USB port, or ' +
98-
'computer. If the problem persists, join the #hw-three-3x channel on Discord for help.',
99-
icon: deviceExclamation,
100-
},
101-
[ErrorCode.ERASE_FAILED]: {
102-
status: 'Erase failed',
103-
description: 'The device could not be erased. Try using a different cable, USB port, or computer. If the problem ' +
104-
'persists, join the #hw-three-3x channel on Discord for help.',
105-
icon: deviceExclamation,
106-
},
107-
[ErrorCode.FLASH_SYSTEM_FAILED]: {
108-
status: 'Flash failed',
109-
description: 'AGNOS could not be flashed to your device. Try using a different cable, USB port, or computer. If ' +
110-
'the problem persists, join the #hw-three-3x channel on Discord for help.',
111-
icon: deviceExclamation,
112-
},
113-
}
114-
115-
if (isLinux) {
116-
// this is likely in StepCode.CONNECTING
117-
errors[ErrorCode.LOST_CONNECTION].description += ' Did you forget to unbind the device from qcserial?'
118-
}
119-
120-
12177
function LinearProgress({ value, barColor }) {
12278
if (value === -1 || value > 100) value = 100
12379
return (
@@ -130,7 +86,6 @@ function LinearProgress({ value, barColor }) {
13086
)
13187
}
13288

133-
13489
function USBIndicator() {
13590
return <div className="flex flex-row gap-2">
13691
<svg
@@ -149,7 +104,6 @@ function USBIndicator() {
149104
</div>
150105
}
151106

152-
153107
function SerialIndicator({ serial }) {
154108
return <div className="flex flex-row gap-2">
155109
<span>
@@ -159,7 +113,6 @@ function SerialIndicator({ serial }) {
159113
</div>
160114
}
161115

162-
163116
function DeviceState({ serial }) {
164117
return (
165118
<div
@@ -173,19 +126,17 @@ function DeviceState({ serial }) {
173126
)
174127
}
175128

176-
177129
function beforeUnloadListener(event) {
178130
// NOTE: not all browsers will show this message
179131
event.preventDefault()
180132
return (event.returnValue = "Flash in progress. Are you sure you want to leave?")
181133
}
182134

183-
184135
export default function Flash() {
185136
const [step, setStep] = useState(StepCode.INITIALIZING)
186137
const [message, setMessage] = useState('')
187138
const [progress, setProgress] = useState(-1)
188-
const [error, setError] = useState(ErrorCode.NONE)
139+
const [error, setError] = useState(null)
189140
const [connected, setConnected] = useState(false)
190141
const [serial, setSerial] = useState(null)
191142

@@ -210,36 +161,59 @@ export default function Flash() {
210161

211162
// Initialize the manager
212163
qdlManager.current.initialize(imageManager.current)
213-
});
164+
.catch(err => setError(err))
165+
})
166+
.catch(err => setError(err))
214167
}, [config, imageManager.current])
215168

216169
// Handle user clicking the start button
217-
const handleStart = () => qdlManager.current?.start()
170+
const handleStart = async () => {
171+
if (!qdlManager.current) return
172+
173+
try {
174+
await qdlManager.current.start()
175+
} catch (err) {
176+
// Error is already set via onErrorChange callback
177+
console.error('Flash failed:', err)
178+
}
179+
}
180+
218181
const canStart = step === StepCode.READY && !error
219182

220183
// Handle retry on error
221184
const handleRetry = () => window.location.reload()
222185

186+
// Get the UI state based on current step or error
223187
const uiState = steps[step]
188+
let errorState = {}
189+
224190
if (error) {
225-
Object.assign(uiState, errors[ErrorCode.UNKNOWN], errors[error])
191+
errorState = parseError(error)
192+
// Map icon name to actual icon
193+
if (errorState.icon && icons[errorState.icon]) {
194+
errorState.icon = icons[errorState.icon]
195+
} else {
196+
errorState.icon = exclamation
197+
}
226198
}
227-
const { status, description, bgColor, icon, iconStyle = 'invert' } = uiState
228199

200+
// Combine step state with error state if there's an error
201+
const { status, description, bgColor, icon, iconStyle = 'invert' } =
202+
error ? { ...uiState, ...errorState } : uiState
203+
204+
// Determine the title to display
229205
let title
230206
if (message && !error) {
231207
title = message + '...'
232208
if (progress >= 0) {
233209
title += ` (${(progress * 100).toFixed(0)}%)`
234210
}
235-
} else if (error === ErrorCode.STORAGE_SPACE) {
236-
title = message
237211
} else {
238212
title = status
239213
}
240214

241215
// warn the user if they try to leave the page while flashing
242-
if (step >= StepCode.FLASH_GPT && step <= StepCode.FLASH_SYSTEM) {
216+
if (step >= StepCode.REPAIR_PARTITION_TABLES && step <= StepCode.FINALIZING) {
243217
window.addEventListener("beforeunload", beforeUnloadListener, { capture: true })
244218
} else {
245219
window.removeEventListener("beforeunload", beforeUnloadListener, { capture: true })
@@ -254,7 +228,7 @@ export default function Flash() {
254228
>
255229
<img
256230
src={icon}
257-
alt="cable"
231+
alt="status icon"
258232
width={128}
259233
height={128}
260234
className={`${iconStyle} ${!error && step !== StepCode.DONE ? 'animate-pulse' : ''}`}
@@ -264,15 +238,15 @@ export default function Flash() {
264238
<LinearProgress value={progress * 100} barColor={bgColor} />
265239
</div>
266240
<span className="text-3xl dark:text-white font-mono font-light">{title}</span>
267-
<span className="text-xl dark:text-white px-8 max-w-xl">{description}</span>
241+
<span className="text-xl dark:text-white px-8 max-w-xl text-center">{description}</span>
268242
{error && (
269243
<button
270244
className="px-4 py-2 rounded-md bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200 transition-colors"
271245
onClick={handleRetry}
272246
>
273247
Retry
274248
</button>
275-
) || false}
249+
)}
276250
{connected && <DeviceState connected={connected} serial={serial} />}
277251
</div>
278252
)

src/utils/errors.js

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { isLinux } from './platform'
2+
3+
/**
4+
* Parses an error object to extract user-friendly message and troubleshooting steps
5+
* @param {Error|string} error - The error to parse
6+
* @returns {{ message: string, description: string, icon?: string, bgColor?: string }}
7+
*/
8+
export function parseError(error) {
9+
let result = {
10+
message: 'Something went wrong',
11+
description: 'An unexpected error occurred. Please try again.',
12+
icon: 'exclamation',
13+
bgColor: 'bg-red-500',
14+
}
15+
16+
if (!error) return result
17+
18+
const errorMessage = getErrorMessage(error)
19+
20+
if (
21+
errorMessage.includes('WebUSB not supported') ||
22+
errorMessage.includes('Web Workers not supported') ||
23+
errorMessage.includes('Storage API not supported')
24+
) {
25+
result.message = 'Browser not supported'
26+
result.description =
27+
'Your browser does not support the required technologies. Please use Chrome, Edge, or another browser with WebUSB support.'
28+
} else if (errorMessage.includes('Not enough storage')) {
29+
result.message = errorMessage
30+
result.description =
31+
'Your system does not have enough space available to download AGNOS. Your browser may be restricting the available space if you are in a private, incognito or guest session.'
32+
} else if (errorMessage.includes('Could not identify')) {
33+
result.message = 'Unrecognized device'
34+
result.description =
35+
'The device connected to your computer is not supported. Try using a different cable, USB port, or computer.'
36+
result.icon = 'deviceQuestion'
37+
result.bgColor = 'bg-yellow-500'
38+
} else if (
39+
errorMessage.includes('connect') ||
40+
errorMessage.includes('Connection lost') ||
41+
errorMessage.includes('Not connected')
42+
) {
43+
result.message = 'Connection lost'
44+
result.description = 'The connection to your device was lost. Unplug your device and try again.'
45+
result.icon = 'cable'
46+
47+
if (isLinux) {
48+
result.description += ' Did you forget to unbind the device from qcserial?'
49+
}
50+
} else if (errorMessage.includes('partition') || errorMessage.includes('GPT')) {
51+
result.message = 'Repairing partition tables failed'
52+
result.description =
53+
"Your device's partition tables could not be repaired. Try using a different cable, USB port, or computer."
54+
result.icon = 'deviceExclamation'
55+
} else if (errorMessage.includes('eras')) {
56+
result.message = 'Erase failed'
57+
result.description = 'The device could not be erased. Try using a different cable, USB port, or computer.'
58+
result.icon = 'deviceExclamation'
59+
} else if (errorMessage.includes('flash')) {
60+
result.message = 'Flash failed'
61+
result.description =
62+
'AGNOS could not be flashed to your device. Try using a different cable, USB port, or computer.'
63+
result.icon = 'deviceExclamation'
64+
} else if (errorMessage.includes('finaliz') || errorMessage.includes('slot')) {
65+
result.message = 'Finalizing failed'
66+
result.description =
67+
'The final setup steps could not be completed. Try using a different cable, USB port, or computer.'
68+
result.icon = 'deviceExclamation'
69+
} else {
70+
// For unknown errors, use a truncated version of the original message
71+
const shortMessage = errorMessage.split('\n')[0].substring(0, 50)
72+
result.message = shortMessage.length < errorMessage.length ? `${shortMessage}...` : shortMessage
73+
}
74+
75+
if (!result.description.includes('Discord')) {
76+
result.description += ' If the problem persists, join the #hw-three-3x channel on Discord for help.'
77+
}
78+
79+
return result
80+
}
81+
82+
/**
83+
* Extracts the most specific error message from an error object or chain
84+
* @param {Error|string} error - The error to extract a message from
85+
* @returns {string} The most specific error message
86+
*/
87+
function getErrorMessage(error) {
88+
if (!error) return 'Unknown error'
89+
90+
if (typeof error === 'string') return error
91+
92+
if (error instanceof Error) {
93+
// Check for a more specific cause
94+
if (error.cause) {
95+
const causeMessage = getErrorMessage(error.cause)
96+
if (causeMessage && causeMessage !== 'Unknown error') {
97+
return causeMessage
98+
}
99+
}
100+
return error.message
101+
}
102+
103+
return String(error)
104+
}

0 commit comments

Comments
 (0)