From 1f889898fa8fd72dfb4a2a59bdb0daf358e049e5 Mon Sep 17 00:00:00 2001 From: Unies Ananda Raja Date: Sat, 23 Nov 2024 20:56:10 +0700 Subject: [PATCH 01/12] refactor: replace react with solid --- bun.lockb | Bin 228037 -> 233071 bytes package.json | 2 ++ src/main.jsx | 19 ++++++------------- vite.config.js | 19 +++++++++++-------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/bun.lockb b/bun.lockb index d81811e493112846c551e535e2099881facd6c2b..ccdf99a1d3981420a1a62a2bd37d56ca36b9bd3c 100755 GIT binary patch delta 48647 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
    1xzIC|jJt+vrjG^F!R&=Il)AK0YMV*Wwg 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 Isy{yT|D9OXjQ{`u delta 45321 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 C_pze@ diff --git a/package.json b/package.json index 7968bfd..2c7d53e 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "jssha": "^3.3.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "solid-js": "^1.8.15", "xz-decompress": "^0.2.1" }, "devDependencies": { @@ -29,6 +30,7 @@ "@testing-library/react": "^15.0.7", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "vite-plugin-solid": "^2.10.1", "@vitejs/plugin-react": "^4.3.0", "autoprefixer": "10.4.14", "eslint": "^8.57.0", diff --git a/src/main.jsx b/src/main.jsx index 40fea3e..bcb2f95 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,14 +1,7 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' +import { render } from "solid-js/web"; +import "@fontsource-variable/inter"; +import "@fontsource-variable/jetbrains-mono"; +import "./index.css"; +import App from "./app"; -import '@fontsource-variable/inter' -import '@fontsource-variable/jetbrains-mono' - -import './index.css' -import App from './app' - -ReactDOM.createRoot(document.getElementById('root')).render( - - - , -) +render(() => , document.getElementById("root")); diff --git a/vite.config.js b/vite.config.js index ba64cd9..3a54ae6 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,18 +1,21 @@ -import { fileURLToPath, URL } from 'node:url'; -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { fileURLToPath, URL } from "node:url"; +import { defineConfig } from "vite"; +import solid from "vite-plugin-solid"; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [solid()], resolve: { alias: [ - { find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) }, + { + find: "@", + replacement: fileURLToPath(new URL("./src", import.meta.url)), + }, ], }, test: { globals: true, - environment: 'jsdom', - setupFiles: './src/test/setup.js', + environment: "jsdom", + setupFiles: "./src/test/setup.js", }, -}) +}); From dbf97859d01b3688a79a58741abdfe4970975807 Mon Sep 17 00:00:00 2001 From: Unies Ananda Raja Date: Sat, 23 Nov 2024 20:56:58 +0700 Subject: [PATCH 02/12] refactor react components to solid --- src/app/Flash.jsx | 344 +++++++++++++++++--------------- src/utils/flash.js | 478 ++++++++++++++++++++++----------------------- src/utils/image.js | 29 +-- 3 files changed, 429 insertions(+), 422 deletions(-) diff --git a/src/app/Flash.jsx b/src/app/Flash.jsx index 1bff6d1..939e2ad 100644 --- a/src/app/Flash.jsx +++ b/src/app/Flash.jsx @@ -1,274 +1,294 @@ -import { useCallback, useState } from 'react' +import { createSignal, onCleanup, createEffect } from "solid-js"; -import { Step, Error, useQdl } from '@/utils/flash' - -import bolt from '@/assets/bolt.svg' -import cable from '@/assets/cable.svg' -import cloud from '@/assets/cloud.svg' -import cloudDownload from '@/assets/cloud_download.svg' -import cloudError from '@/assets/cloud_error.svg' -import deviceExclamation from '@/assets/device_exclamation_c3.svg' -import deviceQuestion from '@/assets/device_question_c3.svg' -import done from '@/assets/done.svg' -import exclamation from '@/assets/exclamation.svg' -import frameAlert from '@/assets/frame_alert.svg' -import systemUpdate from '@/assets/system_update_c3.svg' +import { Step, Error, useQdl } from "@/utils/flash"; +import bolt from "@/assets/bolt.svg"; +import cable from "@/assets/cable.svg"; +import cloud from "@/assets/cloud.svg"; +import cloudDownload from "@/assets/cloud_download.svg"; +import cloudError from "@/assets/cloud_error.svg"; +import deviceExclamation from "@/assets/device_exclamation_c3.svg"; +import deviceQuestion from "@/assets/device_question_c3.svg"; +import done from "@/assets/done.svg"; +import exclamation from "@/assets/exclamation.svg"; +import frameAlert from "@/assets/frame_alert.svg"; +import systemUpdate from "@/assets/system_update_c3.svg"; const steps = { [Step.INITIALIZING]: { - status: 'Initializing...', - bgColor: 'bg-gray-400 dark:bg-gray-700', + status: "Initializing...", + bgColor: "bg-gray-400 dark:bg-gray-700", icon: cloud, }, [Step.READY]: { - status: 'Ready', - description: 'Tap the button above to begin', - bgColor: 'bg-[#51ff00]', + status: "Ready", + description: "Tap the button above to begin", + bgColor: "bg-[#51ff00]", icon: bolt, - iconStyle: '', + iconStyle: "", }, [Step.CONNECTING]: { - status: 'Waiting for connection', - description: 'Follow the instructions to connect your device to your computer', - bgColor: 'bg-yellow-500', + status: "Waiting for connection", + description: + "Follow the instructions to connect your device to your computer", + bgColor: "bg-yellow-500", icon: cable, }, [Step.DOWNLOADING]: { - status: 'Downloading...', - bgColor: 'bg-blue-500', + status: "Downloading...", + bgColor: "bg-blue-500", icon: cloudDownload, }, [Step.UNPACKING]: { - status: 'Unpacking...', - bgColor: 'bg-blue-500', + status: "Unpacking...", + bgColor: "bg-blue-500", icon: cloudDownload, }, [Step.FLASHING]: { - status: 'Flashing device...', - description: 'Do not unplug your device until the process is complete.', - bgColor: 'bg-lime-400', + status: "Flashing device...", + description: "Do not unplug your device until the process is complete.", + bgColor: "bg-lime-400", icon: systemUpdate, }, [Step.ERASING]: { - status: 'Erasing device...', - bgColor: 'bg-lime-400', + status: "Erasing device...", + bgColor: "bg-lime-400", icon: systemUpdate, }, [Step.DONE]: { - status: 'Done', - description: 'Your device has been updated successfully. You can now unplug the all cables from your device, ' - +'and wait for the light to stop blinking then plug the power cord in again. ' - +' To complete the system reset, follow the instructions on your device.', - bgColor: 'bg-green-500', + status: "Done", + description: + "Your device has been updated successfully. You can now unplug the all cables from your device, " + + "and wait for the light to stop blinking then plug the power cord in again. " + + " To complete the system reset, follow the instructions on your device.", + bgColor: "bg-green-500", icon: done, }, -} +}; const errors = { [Error.UNKNOWN]: { - status: 'Unknown error', - description: 'An unknown error has occurred. Unplug your device and wait for 20s. ' + - 'Restart your browser and try again.', - bgColor: 'bg-red-500', + status: "Unknown error", + description: + "An unknown error has occurred. Unplug your device and wait for 20s. " + + "Restart your browser and try again.", + bgColor: "bg-red-500", icon: exclamation, }, [Error.UNRECOGNIZED_DEVICE]: { - status: 'Unrecognized device', - description: 'The device connected to your computer is not supported.', - bgColor: 'bg-yellow-500', + status: "Unrecognized device", + description: "The device connected to your computer is not supported.", + bgColor: "bg-yellow-500", icon: deviceQuestion, }, [Error.LOST_CONNECTION]: { - status: 'Lost connection', - description: 'The connection to your device was lost. Check that your cables are connected properly and try again. ' + - 'Unplug your device and wait for around 20s.', + status: "Lost connection", + description: + "The connection to your device was lost. Check that your cables are connected properly and try again. " + + "Unplug your device and wait for around 20s.", icon: cable, }, [Error.DOWNLOAD_FAILED]: { - status: 'Download failed', - description:'The system image could not be downloaded. Unplug your device and wait for 20s. ' + - 'Check your internet connection and try again.', + status: "Download failed", + description: + "The system image could not be downloaded. Unplug your device and wait for 20s. " + + "Check your internet connection and try again.", icon: cloudError, }, [Error.CHECKSUM_MISMATCH]: { - status: 'Download mismatch', - description: 'The system image downloaded does not match the expected checksum. Try again.', + status: "Download mismatch", + description: + "The system image downloaded does not match the expected checksum. Try again.", icon: frameAlert, }, [Error.FLASH_FAILED]: { - status: 'Flash failed', - description: 'The system image could not be flashed to your device. Try using a different cable, USB port, or ' + - 'computer. If the problem persists, join the #hw-three-3x channel on Discord for help.', + status: "Flash failed", + description: + "The system image could not be flashed to your device. Try using a different cable, USB port, or " + + "computer. If the problem persists, join the #hw-three-3x channel on Discord for help.", icon: deviceExclamation, }, [Error.ERASE_FAILED]: { - status: 'Erase failed', - description: 'The device could not be erased. Try using a different cable, USB port, or computer. If the problem ' + - 'persists, join the #hw-three-3x channel on Discord for help.', + status: "Erase failed", + description: + "The device could not be erased. Try using a different cable, USB port, or computer. If the problem " + + "persists, join the #hw-three-3x channel on Discord for help.", icon: deviceExclamation, }, [Error.REQUIREMENTS_NOT_MET]: { - status: 'Requirements not met', - description: 'Your system does not meet the requirements to flash your device. Make sure to use a browser which ' + - 'supports WebUSB and is up to date.', + status: "Requirements not met", + description: + "Your system does not meet the requirements to flash your device. Make sure to use a browser which " + + "supports WebUSB and is up to date.", }, -} +}; const detachScript = [ - "for d in /sys/bus/usb/drivers/qcserial/*-*; do [ -e \"$d\" ] && echo -n \"$(basename $d)\" | sudo tee /sys/bus/usb/drivers/qcserial/unbind > /dev/null; done" + 'for d in /sys/bus/usb/drivers/qcserial/*-*; do [ -e "$d" ] && echo -n "$(basename $d)" | sudo tee /sys/bus/usb/drivers/qcserial/unbind > /dev/null; done', ]; -const isLinux = navigator.userAgent.toLowerCase().includes('linux'); +const isLinux = navigator.userAgent.toLowerCase().includes("linux"); -function LinearProgress({ value, barColor }) { - if (value === -1 || value > 100) value = 100 +function LinearProgress(props) { + let value = props.value; + if (value === -1 || value > 100) value = 100; return (
    - ) + ); } - function USBIndicator() { - return
    - - - - Device connected -
    + return ( +
    + + + + Device connected +
    + ); } - -function SerialIndicator({ serial }) { - return
    - - Serial: - {serial || 'unknown'} - -
    +function SerialIndicator(props) { + return ( +
    + + Serial: + {props.serial() || "unknown"} + +
    + ); } - -function DeviceState({ serial }) { +function DeviceState(props) { return (
    | - +
    - ) + ); } - function beforeUnloadListener(event) { - // NOTE: not all browsers will show this message - event.preventDefault() - return (event.returnValue = "Flash in progress. Are you sure you want to leave?") + event.preventDefault(); + return (event.returnValue = + "Flash in progress. Are you sure you want to leave?"); } - export default function Flash() { const { step, message, progress, error, - onContinue, onRetry, - connected, serial, - } = useQdl() - - const handleContinue = useCallback(() => { - onContinue?.() - }, [onContinue]) + } = useQdl(); - const handleRetry = useCallback(() => { - onRetry?.() - }, [onRetry]) + const [copied, setCopied] = createSignal(false); - const uiState = steps[step] - if (error) { - Object.assign(uiState, errors[Error.UNKNOWN], errors[error]) - } - const { status, description, bgColor, icon, iconStyle = 'invert' } = uiState + const handleCopy = () => { + setCopied(true); + setTimeout(() => setCopied(false), 1000); + }; - let title - if (message && !error) { - title = message + '...' - if (progress >= 0) { - title += ` (${(progress * 100).toFixed(0)}%)` + createEffect(() => { + if (Step.DOWNLOADING <= step() && step() <= Step.ERASING) { + window.addEventListener("beforeunload", beforeUnloadListener, { + capture: true, + }); + onCleanup(() => + window.removeEventListener("beforeunload", beforeUnloadListener, { + capture: true, + }), + ); } - } else { - title = status - } + }); - // warn the user if they try to leave the page while flashing - if (Step.DOWNLOADING <= step && step <= Step.ERASING) { - window.addEventListener("beforeunload", beforeUnloadListener, { capture: true }) - } else { - window.removeEventListener("beforeunload", beforeUnloadListener, { capture: true }) - } - - const [copied, setCopied] = useState(false); - const handleCopy = () => { - setCopied(true); - setTimeout(() => { - setCopied(false); - }, 1000); + const uiState = () => { + const state = steps[step()]; + if (error()) { + return { ...state, ...errors[Error.UNKNOWN], ...errors[error()] }; + } + return state; }; + const title = () => { + if (message() && !error()) { + let text = message() + "..."; + if (progress() >= 0) { + text += ` (${(progress() * 100).toFixed(0)}%)`; + } + return text; + } + return uiState().status; + }; return ( -
    +
    onContinue()?.()} > cable
    -
    - + +
    +
    - {title} - {description} - {(title === "Lost connection" || title === "Ready") && isLinux && ( + + + {title()} + + + {uiState().description} + + + {/* Linux instructions */} + {(title() === "Lost connection" || title() === "Ready") && isLinux && ( <> - - It seems that you're on Linux, make sure to run the script below in your terminal after plugging in your device. + + It seems that you're on Linux, make sure to run the script + below in your terminal after plugging in your device.
                       {detachScript.map((line, index) => (
    -                    
    +                    
                           {line}
                         
                       ))}
    @@ -276,10 +296,10 @@ export default function Flash() {
                     
    @@ -289,15 +309,17 @@ export default function Flash() {
    )} - {error && ( + + {error() && ( - ) || false} - {connected && } + )} + + {connected() && }
    - ) + ); } diff --git a/src/utils/flash.js b/src/utils/flash.js index 0fe2776..7ab6775 100644 --- a/src/utils/flash.js +++ b/src/utils/flash.js @@ -1,18 +1,12 @@ -import { useEffect, useRef, useState } from 'react' - -import { concatUint8Array } from '@/QDL/utils' -import { qdlDevice } from '@/QDL/qdl' -import * as Comlink from 'comlink' - -import config from '@/config' -import { download } from '@/utils/blob' -import { useImageWorker } from '@/utils/image' -import { createManifest } from '@/utils/manifest' -import { withProgress } from '@/utils/progress' - -/** - * @typedef {import('./manifest.js').Image} Image - */ +import { createSignal, createEffect } from "solid-js"; +import { concatUint8Array } from "@/QDL/utils"; +import { qdlDevice } from "@/QDL/qdl"; +import * as Comlink from "comlink"; +import config from "@/config"; +import { download } from "@/utils/blob"; +import { useImageWorker } from "@/utils/image"; +import { createManifest } from "@/utils/manifest"; +import { withProgress } from "@/utils/progress"; export const Step = { INITIALIZING: 0, @@ -23,7 +17,7 @@ export const Step = { FLASHING: 6, ERASING: 7, DONE: 8, -} +}; export const Error = { UNKNOWN: -1, @@ -36,307 +30,293 @@ export const Error = { FLASH_FAILED: 6, ERASE_FAILED: 7, REQUIREMENTS_NOT_MET: 8, -} +}; function isRecognizedDevice(slotCount, partitions) { - if (slotCount !== 2) { - console.error('[QDL] Unrecognised device (slotCount)') - return false + console.error("[QDL] Unrecognised device (slotCount)"); + return false; } - // check we have the expected partitions to make sure it's a comma three const expectedPartitions = [ - "ALIGN_TO_128K_1", "ALIGN_TO_128K_2", "ImageFv", "abl", "aop", "apdp", "bluetooth", "boot", "cache", - "cdt", "cmnlib", "cmnlib64", "ddr", "devcfg", "devinfo", "dip", "dsp", "fdemeta", "frp", "fsc", "fsg", - "hyp", "keymaster", "keystore", "limits", "logdump", "logfs", "mdtp", "mdtpsecapp", "misc", "modem", - "modemst1", "modemst2", "msadp", "persist", "qupfw", "rawdump", "sec", "splash", "spunvm", "ssd", - "sti", "storsec", "system", "systemrw", "toolsfv", "tz", "userdata", "vm-linux", "vm-system", "xbl", - "xbl_config" - ] - if (!partitions.every(partition => expectedPartitions.includes(partition))) { - console.error('[QDL] Unrecognised device (partitions)', partitions) - return false + "ALIGN_TO_128K_1", + "ALIGN_TO_128K_2", + "ImageFv", + "abl", + "aop", + "apdp", + "bluetooth", + "boot", + "cache", + "cdt", + "cmnlib", + "cmnlib64", + "ddr", + "devcfg", + "devinfo", + "dip", + "dsp", + "fdemeta", + "frp", + "fsc", + "fsg", + "hyp", + "keymaster", + "keystore", + "limits", + "logdump", + "logfs", + "mdtp", + "mdtpsecapp", + "misc", + "modem", + "modemst1", + "modemst2", + "msadp", + "persist", + "qupfw", + "rawdump", + "sec", + "splash", + "spunvm", + "ssd", + "sti", + "storsec", + "system", + "systemrw", + "toolsfv", + "tz", + "userdata", + "vm-linux", + "vm-system", + "xbl", + "xbl_config", + ]; + if ( + !partitions.every((partition) => expectedPartitions.includes(partition)) + ) { + console.error("[QDL] Unrecognised device (partitions)", partitions); + return false; } - return true + return true; } - export function useQdl() { - const [step, _setStep] = useState(Step.INITIALIZING) - const [message, _setMessage] = useState('') - const [progress, setProgress] = useState(0) - const [error, _setError] = useState(Error.NONE) - - const [connected, setConnected] = useState(false) - const [serial, setSerial] = useState(null) - - const [onContinue, setOnContinue] = useState(null) - const [onRetry, setOnRetry] = useState(null) - - const imageWorker = useImageWorker() - const qdl = useRef(new qdlDevice()) - - /** @type {React.RefObject} */ - const manifest = useRef(null) - - function setStep(step) { - _setStep(step) - } - - function setMessage(message = '') { - if (message) console.info('[QDL]', message) - _setMessage(message) - } - - function setError(error) { - _setError(error) - } - useEffect(() => { - setProgress(-1) - setMessage() - - if (error) return - if (!imageWorker.current) { - console.debug('[QDL] Waiting for image worker') - return - } - - switch (step) { + const [step, setStep] = createSignal(Step.INITIALIZING); + const [message, setMessage] = createSignal(""); + const [progress, setProgress] = createSignal(0); + const [error, setError] = createSignal(Error.NONE); + const [connected, setConnected] = createSignal(false); + const [serial, setSerial] = createSignal(null); + const [onContinue, setOnContinue] = createSignal(null); + const [onRetry, setOnRetry] = createSignal(null); + + const imageWorker = useImageWorker(); + const qdl = new qdlDevice(); + let manifest = null; + + const updateMessage = (msg = "") => { + if (msg) console.info("[QDL]", msg); + setMessage(msg); + }; + + createEffect(() => { + setProgress(-1); + updateMessage(); + + if (error()) return; + if (!imageWorker) return; + + switch (step()) { case Step.INITIALIZING: { - // Check that the browser supports WebUSB - if (typeof navigator.usb === 'undefined') { - console.error('[QDL] WebUSB not supported') - setError(Error.REQUIREMENTS_NOT_MET) - break - } - - // Check that the browser supports Web Workers - if (typeof Worker === 'undefined') { - console.error('[QDL] Web Workers not supported') - setError(Error.REQUIREMENTS_NOT_MET) - break + if ( + typeof navigator.usb === "undefined" || + typeof Worker === "undefined" || + typeof Storage === "undefined" + ) { + console.error("[QDL] Requirements not met"); + setError(Error.REQUIREMENTS_NOT_MET); + break; } - // Check that the browser supports Storage API - if (typeof Storage === 'undefined') { - console.error('[QDL] Storage API not supported') - setError(Error.REQUIREMENTS_NOT_MET) - break - } - - imageWorker.current?.init() - .then(() => download(config.manifests['release'])) - .then(blob => blob.text()) - .then(text => { - manifest.current = createManifest(text) - - // sanity check - if (manifest.current.length === 0) { - throw 'Manifest is empty' - } - - console.debug('[QDL] Loaded manifest', manifest.current) - setStep(Step.READY) + imageWorker + .init() + .then(() => download(config.manifests["release"])) + .then((blob) => blob.text()) + .then((text) => { + manifest = createManifest(text); + if (manifest.length === 0) throw "Manifest is empty"; + console.debug("[QDL] Loaded manifest", manifest); + setStep(Step.READY); }) .catch((err) => { - console.error('[QDL] Initialization error', err) - setError(Error.UNKNOWN) - }) - break + console.error("[QDL] Initialization error", err); + setError(Error.UNKNOWN); + }); + break; } case Step.READY: { - // wait for user interaction (we can't use WebUSB without user event) setOnContinue(() => () => { - setOnContinue(null) - setStep(Step.CONNECTING) - }) - break + setOnContinue(null); + setStep(Step.CONNECTING); + }); + break; } case Step.CONNECTING: { - qdl.current.waitForConnect() + qdl + .waitForConnect() .then(() => { - console.info('[QDL] Connected') - return qdl.current.getDevicePartitionsInfo() + console.info("[QDL] Connected"); + return qdl + .getDevicePartitionsInfo() .then(([slotCount, partitions]) => { - const recognized = isRecognizedDevice(slotCount, partitions) - console.debug('[QDL] Device info', { recognized, partitions}) - + const recognized = isRecognizedDevice(slotCount, partitions); if (!recognized) { - setError(Error.UNRECOGNIZED_DEVICE) - return + setError(Error.UNRECOGNIZED_DEVICE); + return; } - - setSerial(qdl.current.sahara.serial || 'unknown') - setConnected(true) - setStep(Step.DOWNLOADING) - }) - .catch((err) => { - console.error('[QDL] Error getting device information', err) - setError(Error.UNKNOWN) - }) + setSerial(qdl.sahara.serial || "unknown"); + setConnected(true); + setStep(Step.DOWNLOADING); + }); }) .catch((err) => { - console.error('[QDL] Connection lost', err) - setError(Error.LOST_CONNECTION) - setConnected(false) - }) - qdl.current.connect() - .catch((err) => { - console.error('[QDL] Connection error', err) - setStep(Step.READY) - }) - break + console.error("[QDL] Connection error", err); + setError(Error.LOST_CONNECTION); + setConnected(false); + }); + qdl.connect().catch(() => setStep(Step.READY)); + break; } case Step.DOWNLOADING: { - setProgress(0) - + setProgress(0); async function downloadImages() { - for await (const [image, onProgress] of withProgress(manifest.current, setProgress)) { - setMessage(`Downloading ${image.name}`) - await imageWorker.current.downloadImage(image, Comlink.proxy(onProgress)) + for await (const [image, onProgress] of withProgress( + manifest, + setProgress, + )) { + updateMessage(`Downloading ${image.name}`); + await imageWorker.downloadImage(image, Comlink.proxy(onProgress)); } } downloadImages() - .then(() => { - console.debug('[QDL] Downloaded all images') - setStep(Step.UNPACKING) - }) + .then(() => setStep(Step.UNPACKING)) .catch((err) => { - console.error('[QDL] Download error', err) - setError(Error.DOWNLOAD_FAILED) - }) - break + console.error("[QDL] Download error", err); + setError(Error.DOWNLOAD_FAILED); + }); + break; } case Step.UNPACKING: { - setProgress(0) - + setProgress(0); async function unpackImages() { - for await (const [image, onProgress] of withProgress(manifest.current, setProgress)) { - setMessage(`Unpacking ${image.name}`) - await imageWorker.current.unpackImage(image, Comlink.proxy(onProgress)) + for await (const [image, onProgress] of withProgress( + manifest, + setProgress, + )) { + updateMessage(`Unpacking ${image.name}`); + await imageWorker.unpackImage(image, Comlink.proxy(onProgress)); } } unpackImages() - .then(() => { - console.debug('[QDL] Unpacked all images') - setStep(Step.FLASHING) - }) + .then(() => setStep(Step.FLASHING)) .catch((err) => { - console.error('[QDL] Unpack error', err) - if (err.startsWith('Checksum mismatch')) { - setError(Error.CHECKSUM_MISMATCH) - } else { - setError(Error.UNPACK_FAILED) - } - }) - break + console.error("[QDL] Unpack error", err); + setError( + err.startsWith("Checksum mismatch") + ? Error.CHECKSUM_MISMATCH + : Error.UNPACK_FAILED, + ); + }); + break; } case Step.FLASHING: { - setProgress(0) - + setProgress(0); async function flashDevice() { - const currentSlot = await qdl.current.getActiveSlot(); - if (!['a', 'b'].includes(currentSlot)) { - throw `Unknown current slot ${currentSlot}` + const currentSlot = await qdl.getActiveSlot(); + if (!["a", "b"].includes(currentSlot)) + throw `Unknown current slot ${currentSlot}`; + const otherSlot = currentSlot === "a" ? "b" : "a"; + + await qdl.erase("xbl" + `_${currentSlot}`); + + for await (const [image, onProgress] of withProgress( + manifest, + setProgress, + )) { + const fileHandle = await imageWorker.getImage(image); + const blob = await fileHandle.getFile(); + updateMessage(`Flashing ${image.name}`); + await qdl.flashBlob(image.name + `_${otherSlot}`, blob, onProgress); } - const otherSlot = currentSlot === 'a' ? 'b' : 'a' - - // Erase current xbl partition so if users try to power up device - // with corrupted primary gpt header, it would not update the backup - await qdl.current.erase("xbl"+`_${currentSlot}`) - - for await (const [image, onProgress] of withProgress(manifest.current, setProgress)) { - const fileHandle = await imageWorker.current.getImage(image) - const blob = await fileHandle.getFile() - setMessage(`Flashing ${image.name}`) - const partitionName = image.name + `_${otherSlot}` - await qdl.current.flashBlob(partitionName, blob, onProgress) - } - console.debug('[QDL] Flashed all partitions') - - setMessage(`Changing slot to ${otherSlot}`) - await qdl.current.setActiveSlot(otherSlot) + updateMessage(`Changing slot to ${otherSlot}`); + await qdl.setActiveSlot(otherSlot); } flashDevice() - .then(() => { - console.debug('[QDL] Flash complete') - setStep(Step.ERASING) - }) + .then(() => setStep(Step.ERASING)) .catch((err) => { - console.error('[QDL] Flashing error', err) - setError(Error.FLASH_FAILED) - }) - break + console.error("[QDL] Flashing error", err); + setError(Error.FLASH_FAILED); + }); + break; } case Step.ERASING: { - setProgress(0) - - async function resetUserdata() { - let wData = new TextEncoder().encode("COMMA_RESET") - wData = new Blob([concatUint8Array([wData, new Uint8Array(28 - wData.length).fill(0)])]) // make equal sparseHeaderSize - await qdl.current.flashBlob("userdata", wData) - } - + setProgress(0); async function eraseDevice() { - setMessage('Erasing userdata') - await resetUserdata() - setProgress(0.9) - - setMessage('Rebooting') - await qdl.current.reset() - setProgress(1) - setConnected(false) + updateMessage("Erasing userdata"); + let wData = new TextEncoder().encode("COMMA_RESET"); + wData = new Blob([ + concatUint8Array([ + wData, + new Uint8Array(28 - wData.length).fill(0), + ]), + ]); + await qdl.flashBlob("userdata", wData); + setProgress(0.9); + + updateMessage("Rebooting"); + await qdl.reset(); + setProgress(1); + setConnected(false); } eraseDevice() - .then(() => { - console.debug('[QDL] Erase complete') - setStep(Step.DONE) - }) + .then(() => setStep(Step.DONE)) .catch((err) => { - console.error('[QDL] Erase error', err) - setError(Error.ERASE_FAILED) - }) - break + console.error("[QDL] Erase error", err); + setError(Error.ERASE_FAILED); + }); + break; } } - }, [error, imageWorker, step]) + }); - useEffect(() => { - if (error !== Error.NONE) { - console.debug('[QDL] error', error) - setProgress(-1) - setOnContinue(null) - - setOnRetry(() => () => { - console.debug('[QDL] on retry') - window.location.reload() - }) + createEffect(() => { + if (error() !== Error.NONE) { + setProgress(-1); + setOnContinue(null); + setOnRetry(() => () => window.location.reload()); } - }, [error]) + }); return { - step, - message, - progress, - error, - - connected, - serial, - - onContinue, - onRetry, - } + step: step, + message: message, + progress: progress, + error: error, + connected: connected, + serial: serial, + onContinue: onContinue, + onRetry: onRetry, + }; } - diff --git a/src/utils/image.js b/src/utils/image.js index ed27f09..285ec20 100644 --- a/src/utils/image.js +++ b/src/utils/image.js @@ -1,17 +1,22 @@ -import { useEffect, useRef } from 'react' - -import * as Comlink from 'comlink' +import { onCleanup } from "solid-js"; +import * as Comlink from "comlink"; export function useImageWorker() { - const apiRef = useRef() + let worker = new Worker(new URL("../workers/image.worker", import.meta.url), { + type: "module", + }); + + const api = Comlink.wrap(worker); - useEffect(() => { - const worker = new Worker(new URL('../workers/image.worker', import.meta.url), { - type: 'module', - }) - apiRef.current = Comlink.wrap(worker) - return () => worker.terminate() - }, []) + onCleanup(() => { + worker.terminate(); + worker = null; + }); - return apiRef + return { + init: () => api.init(), + downloadImage: (image, progress) => api.downloadImage(image, progress), + unpackImage: (image, progress) => api.unpackImage(image, progress), + getImage: (image) => api.getImage(image), + }; } From d446110ec38ade0e2516c76449256631ca865fde Mon Sep 17 00:00:00 2001 From: Unies Ananda Raja Date: Sat, 23 Nov 2024 20:57:51 +0700 Subject: [PATCH 03/12] refactor main guides to solid --- src/app/index.jsx | 119 +++++++++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 48 deletions(-) diff --git a/src/app/index.jsx b/src/app/index.jsx index cd4369f..ccbd84b 100644 --- a/src/app/index.jsx +++ b/src/app/index.jsx @@ -1,26 +1,35 @@ -import { Suspense, lazy } from 'react' +import { Suspense, lazy } from "solid-js"; -import comma from '../assets/comma.svg' -import fastbootPorts from '../assets/fastboot-ports.svg' -import zadigCreateNewDevice from '../assets/zadig_create_new_device.png' -import zadigForm from '../assets/zadig_form.png' +import comma from "../assets/comma.svg"; +import fastbootPorts from "../assets/fastboot-ports.svg"; +import zadigCreateNewDevice from "../assets/zadig_create_new_device.png"; +import zadigForm from "../assets/zadig_form.png"; -const Flash = lazy(() => import('./Flash')) +const Flash = lazy(() => import("./Flash")); export default function App() { - const version = import.meta.env.VITE_PUBLIC_GIT_SHA || 'dev' + const version = import.meta.env.VITE_PUBLIC_GIT_SHA || "dev"; console.info(`flash.comma.ai version: ${version}`); return (
    - comma + comma

    flash.comma.ai

    This tool allows you to flash AGNOS onto your comma device.

    AGNOS is the Ubuntu-based operating system for your{" "} - comma 3/3X. + + comma 3/3X + + .


    @@ -29,14 +38,12 @@ export default function App() {

    Requirements

    • - A web browser which supports WebUSB (such as Google Chrome, Microsoft Edge, Opera), running on Windows, macOS, Linux, or Android. -
    • -
    • - A USB-C cable to power your device outside the car. -
    • -
    • - Another USB-C cable to connect the device to your computer. + A web browser which supports WebUSB (such as Google Chrome, + Microsoft Edge, Opera), running on Windows, macOS, Linux, or + Android.
    • +
    • A USB-C cable to power your device outside the car.
    • +
    • Another USB-C cable to connect the device to your computer.

    USB Driver

    @@ -48,7 +55,8 @@ export default function App() { Download and install Zadig.

  1. - Under Device in the menu bar, select Create New Device. + Under Device in the menu bar, select{" "} + Create New Device. Zadig Create New Device
  2. Fill in three fields. The first field is just a description and - you can fill in anything. The next two fields are very important. - Fill them in with 05C6 and 9008 respectively. - Press "Install Driver" and give it a few minutes to install. - Zadig Form + you can fill in anything. The next two fields are very important. + Fill them in with 05C6 and 9008{" "} + respectively. Press "Install Driver" and give it a few + minutes to install. + Zadig Form
-

- No additional software is required for macOS or Linux. -

+

No additional software is required for macOS or Linux.


@@ -80,9 +82,17 @@ export default function App() {

Follow these steps to put your device into QDL mode:

  1. Power off the device and wait for the LEDs to switch off.
  2. -
  3. Connect the device to your computer using the USB-C port (port 2).
  4. -
  5. Connect power to the OBD-C port (port 1).
  6. -
  7. The device then should be visible as an option when choosing the device to flash
  8. +
  9. + Connect the device to your computer using the USB-C port{" "} + (port 2). +
  10. +
  11. + Connect power to the OBD-C port (port 1). +
  12. +
  13. + The device then should be visible as an option when choosing the + device to flash +

Flashing

- After your device is in QDL mode, you can click the button to start flashing. A prompt may appear to - select a device; choose the device starts with QUSB_BULK. + After your device is in QDL mode, you can click the button to start + flashing. A prompt may appear to select a device; choose the device + starts with QUSB_BULK.

- The process can take 30+ minutes depending on your internet connection and system performance. Do not - unplug the device until all steps are complete. + The process can take 30+ minutes depending on your internet + connection and system performance. Do not unplug the device until + all steps are complete.


@@ -110,33 +122,42 @@ export default function App() {

Troubleshooting

Too slow

- It is recommended that you use a USB 3.0 cable when flashing since it will speed up the flashing time by a lot. + It is recommended that you use a USB 3.0 cable when flashing since + it will speed up the flashing time by a lot.

Cannot enter QDL

- Try using a different USB cable or USB port. Sometimes USB 2.0 ports work better than USB 3.0 (blue) ports. - If you're using a USB hub, try connecting the device directly to your computer, or alternatively use a - USB hub between your computer and the device. + Try using a different USB cable or USB port. Sometimes USB 2.0 ports + work better than USB 3.0 (blue) ports. If you're using a USB + hub, try connecting the device directly to your computer, or + alternatively use a USB hub between your computer and the device.

My device's screen is blank

- The device screen will be blank in QDL mode, but you can verify that it is in QDL if the device shows up - when you press the Flash icon. + The device screen will be blank in QDL mode, but you can verify that + it is in QDL if the device shows up when you press the Flash icon.

After flashing, device says unable to mount data partition

- This is expected after the filesystem is erased. Press confirm to finish resetting your device. + This is expected after the filesystem is erased. Press confirm to + finish resetting your device.

General Tips

  • Try another computer or OS
  • Try different USB ports on your computer
  • -
  • Try different USB-C cables, including the OBD-C cable that came with the device
  • +
  • + Try different USB-C cables, including the OBD-C cable that came + with the device +

Other questions

- If you need help, join our Discord server and go to - the #hw-three-3x channel. + If you need help, join our{" "} + + Discord server + {" "} + and go to the #hw-three-3x channel.

@@ -147,7 +168,9 @@ export default function App() {
- Loading...

}> + Loading...

} + >
@@ -156,5 +179,5 @@ export default function App() { flash.comma.ai version: {version.substring(0, 7)} - ) + ); } From 4e2671b6d29eaa37fdfd8ac95a940d4f6ce0cd26 Mon Sep 17 00:00:00 2001 From: Unies Ananda Raja Date: Sat, 23 Nov 2024 21:29:07 +0700 Subject: [PATCH 04/12] fix: make iconstyle default --- src/app/Flash.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/Flash.jsx b/src/app/Flash.jsx index 939e2ad..148e558 100644 --- a/src/app/Flash.jsx +++ b/src/app/Flash.jsx @@ -258,7 +258,7 @@ export default function Flash() { alt="status" width={128} height={128} - className={`${uiState().iconStyle || "invert"} ${!error() && step() !== Step.DONE ? "animate-pulse" : ""}`} + className={`${uiState().iconStyle} ${!error() && step() !== Step.DONE ? "animate-pulse" : ""}`} /> From 298791db95dad11450f6faf1b08657b7f8c74522 Mon Sep 17 00:00:00 2001 From: Unies Ananda Raja Date: Sat, 23 Nov 2024 21:56:16 +0700 Subject: [PATCH 05/12] fix: typo --- src/app/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/index.jsx b/src/app/index.jsx index ccbd84b..d12c7b0 100644 --- a/src/app/index.jsx +++ b/src/app/index.jsx @@ -169,7 +169,7 @@ export default function App() {
Loading...

} + fallback={

Loading...

} >
From 995fa904ca28d8a5cff26f23ef74b82c03bab14e Mon Sep 17 00:00:00 2001 From: Unies Ananda Raja Date: Sat, 23 Nov 2024 22:00:44 +0700 Subject: [PATCH 06/12] perf: use show component --- src/app/Flash.jsx | 64 +++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/app/Flash.jsx b/src/app/Flash.jsx index 148e558..64d13b4 100644 --- a/src/app/Flash.jsx +++ b/src/app/Flash.jsx @@ -1,4 +1,4 @@ -import { createSignal, onCleanup, createEffect } from "solid-js"; +import { createSignal, onCleanup, createEffect, Show } from "solid-js"; import { Step, Error, useQdl } from "@/utils/flash"; @@ -277,47 +277,47 @@ export default function Flash() { {/* Linux instructions */} - {(title() === "Lost connection" || title() === "Ready") && isLinux && ( - <> - - It seems that you're on Linux, make sure to run the script - below in your terminal after plugging in your device. - -
-
-
-
-                  {detachScript.map((line, index) => (
-                    
-                      {line}
-                    
-                  ))}
-                
-
- -
+ + + It seems that you're on Linux, make sure to run the script below + in your terminal after plugging in your device. + +
+
+
+
+                {detachScript.map((line, index) => (
+                  
+                    {line}
+                  
+                ))}
+              
+
+
- - )} +
+
- {error() && ( + - )} + {connected() && }
From ac2bce24ea1110256ee2aab7e97b442c6014f6c8 Mon Sep 17 00:00:00 2001 From: Unies Ananda Raja Date: Sat, 23 Nov 2024 22:09:41 +0700 Subject: [PATCH 07/12] perf: memoize ui state --- src/app/Flash.jsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/app/Flash.jsx b/src/app/Flash.jsx index 64d13b4..4675c02 100644 --- a/src/app/Flash.jsx +++ b/src/app/Flash.jsx @@ -1,4 +1,10 @@ -import { createSignal, onCleanup, createEffect, Show } from "solid-js"; +import { + createSignal, + onCleanup, + createEffect, + createMemo, + Show, +} from "solid-js"; import { Step, Error, useQdl } from "@/utils/flash"; @@ -224,15 +230,15 @@ export default function Flash() { } }); - const uiState = () => { + const uiState = createMemo(() => { const state = steps[step()]; if (error()) { return { ...state, ...errors[Error.UNKNOWN], ...errors[error()] }; } return state; - }; + }); - const title = () => { + const title = createMemo(() => { if (message() && !error()) { let text = message() + "..."; if (progress() >= 0) { @@ -241,7 +247,7 @@ export default function Flash() { return text; } return uiState().status; - }; + }); return (
Date: Sat, 23 Nov 2024 22:10:16 +0700 Subject: [PATCH 08/12] perf: load plausible async --- index.html | 5 ----- src/main.jsx | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index 3060b4a..2da0072 100644 --- a/index.html +++ b/index.html @@ -13,10 +13,5 @@
- diff --git a/src/main.jsx b/src/main.jsx index bcb2f95..d11c5ca 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -5,3 +5,9 @@ import "./index.css"; import App from "./app"; render(() => , document.getElementById("root")); + +const script = document.createElement("script"); +script.defer = true; +script.dataset.domain = "flash.comma.ai"; +script.src = "https://plausible.io/js/script.outbound-links.js"; +document.body.appendChild(script); From 6cf316e778179ef3d312536955fa11310bff4297 Mon Sep 17 00:00:00 2001 From: Unies Ananda Raja Date: Sat, 23 Nov 2024 22:25:32 +0700 Subject: [PATCH 09/12] perf: preload manifest --- src/main.jsx | 8 +++++++- src/utils/flash.js | 10 ++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index d11c5ca..a874678 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -2,9 +2,15 @@ import { render } from "solid-js/web"; import "@fontsource-variable/inter"; import "@fontsource-variable/jetbrains-mono"; import "./index.css"; +import config from "./config"; import App from "./app"; -render(() => , document.getElementById("root")); +const manifestPromise = fetch(config.manifests.release).then((r) => r.text()); + +render( + () => , + document.getElementById("root"), +); const script = document.createElement("script"); script.defer = true; diff --git a/src/utils/flash.js b/src/utils/flash.js index 7ab6775..c0d6256 100644 --- a/src/utils/flash.js +++ b/src/utils/flash.js @@ -101,7 +101,7 @@ function isRecognizedDevice(slotCount, partitions) { return true; } -export function useQdl() { +export function useQdl(manifestPromise) { const [step, setStep] = createSignal(Step.INITIALIZING); const [message, setMessage] = createSignal(""); const [progress, setProgress] = createSignal(0); @@ -141,12 +141,14 @@ export function useQdl() { imageWorker .init() - .then(() => download(config.manifests["release"])) - .then((blob) => blob.text()) + .then( + () => + manifestPromise || + download(config.manifests["release"]).then((b) => b.text()), + ) .then((text) => { manifest = createManifest(text); if (manifest.length === 0) throw "Manifest is empty"; - console.debug("[QDL] Loaded manifest", manifest); setStep(Step.READY); }) .catch((err) => { From e3ab1083dc6dff374fbf9a716e4f28deef95f3fc Mon Sep 17 00:00:00 2001 From: Unies Ananda Raja Date: Sat, 23 Nov 2024 22:56:06 +0700 Subject: [PATCH 10/12] perf: move steps and errors to constants --- src/app/Flash.jsx | 123 +--------------------------------------- src/constants/errors.js | 64 +++++++++++++++++++++ src/constants/steps.js | 57 +++++++++++++++++++ 3 files changed, 123 insertions(+), 121 deletions(-) create mode 100644 src/constants/errors.js create mode 100644 src/constants/steps.js diff --git a/src/app/Flash.jsx b/src/app/Flash.jsx index 4675c02..8965073 100644 --- a/src/app/Flash.jsx +++ b/src/app/Flash.jsx @@ -7,127 +7,8 @@ import { } from "solid-js"; import { Step, Error, useQdl } from "@/utils/flash"; - -import bolt from "@/assets/bolt.svg"; -import cable from "@/assets/cable.svg"; -import cloud from "@/assets/cloud.svg"; -import cloudDownload from "@/assets/cloud_download.svg"; -import cloudError from "@/assets/cloud_error.svg"; -import deviceExclamation from "@/assets/device_exclamation_c3.svg"; -import deviceQuestion from "@/assets/device_question_c3.svg"; -import done from "@/assets/done.svg"; -import exclamation from "@/assets/exclamation.svg"; -import frameAlert from "@/assets/frame_alert.svg"; -import systemUpdate from "@/assets/system_update_c3.svg"; - -const steps = { - [Step.INITIALIZING]: { - status: "Initializing...", - bgColor: "bg-gray-400 dark:bg-gray-700", - icon: cloud, - }, - [Step.READY]: { - status: "Ready", - description: "Tap the button above to begin", - bgColor: "bg-[#51ff00]", - icon: bolt, - iconStyle: "", - }, - [Step.CONNECTING]: { - status: "Waiting for connection", - description: - "Follow the instructions to connect your device to your computer", - bgColor: "bg-yellow-500", - icon: cable, - }, - [Step.DOWNLOADING]: { - status: "Downloading...", - bgColor: "bg-blue-500", - icon: cloudDownload, - }, - [Step.UNPACKING]: { - status: "Unpacking...", - bgColor: "bg-blue-500", - icon: cloudDownload, - }, - [Step.FLASHING]: { - status: "Flashing device...", - description: "Do not unplug your device until the process is complete.", - bgColor: "bg-lime-400", - icon: systemUpdate, - }, - [Step.ERASING]: { - status: "Erasing device...", - bgColor: "bg-lime-400", - icon: systemUpdate, - }, - [Step.DONE]: { - status: "Done", - description: - "Your device has been updated successfully. You can now unplug the all cables from your device, " + - "and wait for the light to stop blinking then plug the power cord in again. " + - " To complete the system reset, follow the instructions on your device.", - bgColor: "bg-green-500", - icon: done, - }, -}; - -const errors = { - [Error.UNKNOWN]: { - status: "Unknown error", - description: - "An unknown error has occurred. Unplug your device and wait for 20s. " + - "Restart your browser and try again.", - bgColor: "bg-red-500", - icon: exclamation, - }, - [Error.UNRECOGNIZED_DEVICE]: { - status: "Unrecognized device", - description: "The device connected to your computer is not supported.", - bgColor: "bg-yellow-500", - icon: deviceQuestion, - }, - [Error.LOST_CONNECTION]: { - status: "Lost connection", - description: - "The connection to your device was lost. Check that your cables are connected properly and try again. " + - "Unplug your device and wait for around 20s.", - icon: cable, - }, - [Error.DOWNLOAD_FAILED]: { - status: "Download failed", - description: - "The system image could not be downloaded. Unplug your device and wait for 20s. " + - "Check your internet connection and try again.", - icon: cloudError, - }, - [Error.CHECKSUM_MISMATCH]: { - status: "Download mismatch", - description: - "The system image downloaded does not match the expected checksum. Try again.", - icon: frameAlert, - }, - [Error.FLASH_FAILED]: { - status: "Flash failed", - description: - "The system image could not be flashed to your device. Try using a different cable, USB port, or " + - "computer. If the problem persists, join the #hw-three-3x channel on Discord for help.", - icon: deviceExclamation, - }, - [Error.ERASE_FAILED]: { - status: "Erase failed", - description: - "The device could not be erased. Try using a different cable, USB port, or computer. If the problem " + - "persists, join the #hw-three-3x channel on Discord for help.", - icon: deviceExclamation, - }, - [Error.REQUIREMENTS_NOT_MET]: { - status: "Requirements not met", - description: - "Your system does not meet the requirements to flash your device. Make sure to use a browser which " + - "supports WebUSB and is up to date.", - }, -}; +import { errors } from "../constants/errors"; +import { steps } from "../constants/steps"; const detachScript = [ 'for d in /sys/bus/usb/drivers/qcserial/*-*; do [ -e "$d" ] && echo -n "$(basename $d)" | sudo tee /sys/bus/usb/drivers/qcserial/unbind > /dev/null; done', diff --git a/src/constants/errors.js b/src/constants/errors.js new file mode 100644 index 0000000..078fba5 --- /dev/null +++ b/src/constants/errors.js @@ -0,0 +1,64 @@ +import exclamation from "@/assets/exclamation.svg"; +import deviceQuestion from "@/assets/device_question_c3.svg"; +import cable from "@/assets/cable.svg"; +import cloudError from "@/assets/cloud_error.svg"; +import frameAlert from "@/assets/frame_alert.svg"; +import deviceExclamation from "@/assets/device_exclamation_c3.svg"; +import { Error } from "@/utils/flash"; + +export const errors = { + [Error.UNKNOWN]: { + status: "Unknown error", + description: + "An unknown error has occurred. Unplug your device and wait for 20s. " + + "Restart your browser and try again.", + bgColor: "bg-red-500", + icon: exclamation, + }, + [Error.UNRECOGNIZED_DEVICE]: { + status: "Unrecognized device", + description: "The device connected to your computer is not supported.", + bgColor: "bg-yellow-500", + icon: deviceQuestion, + }, + [Error.LOST_CONNECTION]: { + status: "Lost connection", + description: + "The connection to your device was lost. Check that your cables are connected properly and try again. " + + "Unplug your device and wait for around 20s.", + icon: cable, + }, + [Error.DOWNLOAD_FAILED]: { + status: "Download failed", + description: + "The system image could not be downloaded. Unplug your device and wait for 20s. " + + "Check your internet connection and try again.", + icon: cloudError, + }, + [Error.CHECKSUM_MISMATCH]: { + status: "Download mismatch", + description: + "The system image downloaded does not match the expected checksum. Try again.", + icon: frameAlert, + }, + [Error.FLASH_FAILED]: { + status: "Flash failed", + description: + "The system image could not be flashed to your device. Try using a different cable, USB port, or " + + "computer. If the problem persists, join the #hw-three-3x channel on Discord for help.", + icon: deviceExclamation, + }, + [Error.ERASE_FAILED]: { + status: "Erase failed", + description: + "The device could not be erased. Try using a different cable, USB port, or computer. If the problem " + + "persists, join the #hw-three-3x channel on Discord for help.", + icon: deviceExclamation, + }, + [Error.REQUIREMENTS_NOT_MET]: { + status: "Requirements not met", + description: + "Your system does not meet the requirements to flash your device. Make sure to use a browser which " + + "supports WebUSB and is up to date.", + }, +}; diff --git a/src/constants/steps.js b/src/constants/steps.js new file mode 100644 index 0000000..1d93cef --- /dev/null +++ b/src/constants/steps.js @@ -0,0 +1,57 @@ +import bolt from "@/assets/bolt.svg"; +import cable from "@/assets/cable.svg"; +import cloud from "@/assets/cloud.svg"; +import cloudDownload from "@/assets/cloud_download.svg"; +import systemUpdate from "@/assets/system_update_c3.svg"; +import done from "@/assets/done.svg"; +import { Step } from "@/utils/flash"; + +export const steps = { + [Step.INITIALIZING]: { + status: "Initializing...", + bgColor: "bg-gray-400 dark:bg-gray-700", + icon: cloud, + }, + [Step.READY]: { + status: "Ready", + description: "Tap the button above to begin", + bgColor: "bg-[#51ff00]", + icon: bolt, + iconStyle: "", + }, + [Step.CONNECTING]: { + status: "Waiting for connection", + description: + "Follow the instructions to connect your device to your computer", + bgColor: "bg-yellow-500", + icon: cable, + }, + [Step.DOWNLOADING]: { + status: "Downloading...", + bgColor: "bg-blue-500", + icon: cloudDownload, + }, + [Step.UNPACKING]: { + status: "Unpacking...", + bgColor: "bg-blue-500", + icon: cloudDownload, + }, + [Step.FLASHING]: { + status: "Flashing device...", + description: "Do not unplug your device until the process is complete.", + bgColor: "bg-lime-400", + icon: systemUpdate, + }, + [Step.ERASING]: { + status: "Erasing device...", + bgColor: "bg-lime-400", + icon: systemUpdate, + }, + [Step.DONE]: { + status: "Done", + description: + "Your device has been updated successfully. You can now unplug the all cables from your device, and wait for the light to stop blinking then plug the power cord in again. To complete the system reset, follow the instructions on your device.", + bgColor: "bg-green-500", + icon: done, + }, +}; From 5a58db89719d229f438c89d69869ddbd543dadd8 Mon Sep 17 00:00:00 2001 From: Unies Ananda Raja Date: Sat, 23 Nov 2024 23:47:25 +0700 Subject: [PATCH 11/12] refactor: migrate testing library to solidjs --- bun.lockb | Bin 233071 -> 233574 bytes package.json | 3 ++- src/app/App.test.jsx | 25 ++++++++++++++++--------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/bun.lockb b/bun.lockb index ccdf99a1d3981420a1a62a2bd37d56ca36b9bd3c..0b03b559661c46b18fd9bb56bfbf699e26459b95 100755 GIT binary patch delta 33823 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 delta 33737 zcmeHwcYGB^*Z=O43t1FH350N|A|*fqAqh7S$ffrty+{d3fD7pfB?u&_C>8`Naz8ou%sF#r=FFKhXJ%(_?tZ@d zo@>kRnI9JN(X~crD{T0sbX3HGa*wR}eeR)WT3Skmo3JzbAXzBsp(#;eYLYJ@bVBAS&;g_qGg2qVC28@QnOWIsiJBLD6a0+C zxcDp-KZ}a)1|5&u$v?i}Os$;i5ULBp0Imi+*_V}=3F)6)npO$)#?n$a(>KnSGB#EF z9Q^X&k1ivXuqzMsWgY;17x+6Af4aY>RRFyn@@%v`T66=a#bu38gfj1gkIszA-GKyq z6H(S?2+Z$xjPS@+JGSv^PMr!DF(b+$F%IvwSJpnQ7Tt{P;K@ zZDv?!^q}ycp#yWj1g5PHs8$_Y%ma-Eb7w0&-3m6E<}5)^*=9;yN~$kG(-JesO!6fq zXtiLOAn;A#D!|`Yl=y$B5SP&%yE1h z7#7X_zM;g)jimV<-%U)(@@4rFGhYXv+OBLYwXM@c%H0G_t9=SgYg9&koKy=!?RHz` z<|4rc{8U4?&Z5l8<5H0#M%Nl89Lx^&R= zq;#*G!_XxzDJ~%~0W`<1FEG2&5twrQAy3b42h7$xqF!j1llx68IYm<2OLf!YGBOik zqcNaaQFq{~z$-h*NbA_Sqw2=_j#2@~S#EWbBP!o(xVAv;#1m zEee=&81U37IVqapmP+)fYQmdIlX1MN#L`A zvVCNI6BYd|Xev4;BQ8EM6T{J0%Jl<|q9*H+pg^1|;Pk8yX!4U2GsY!0$6$e`0voM- z|DfRU{iJD7%v8%ZS55sU=Ti2Q_WWAXy_8 zw-okzp`up-bMW`094C$4Psk?@k@CsD6kl3gW~Sqn4}eciCl8f690z94`b&OJZo)7* zNP~fG_Z%(@)CH!^zH>10>8`ULkWMifm|ecZY|QQZKhz`3yS3a8M@o&HiaJ0udQD3! zylm5)pfPe*Ou_K*DhHZsU5JzXtc*y61x-tvl#(5vnvi${eA@b&!kz>vKNpzlzXd++ zb`Y3!DgrX?a|oE--vvzj!~-KgCpRrKiS=uHP=Ni-jGIbV(WWLRAy8#xZc_Y|DVcF; zK23W9bZy9G1GAy0v06}JA82-~I%LQn02LLSD@L-8w#HR4hEf&n3#rLnl=sPVDDnx3jKqTX zTj6)tiW>p3dqA}UrWLQCS$3lRB-I(v8>nKP( zHZ>y@+x+o+mHcwWU-5)&Cx(e{Hr(_3B*>G;1xpa3dDm#&2 z1K+C#*MeqG7XWjl=cxQDz#N^QACnFI0eO1RCD63QQH2Xs{t94P^fxHLQTqj!V14(3 z&w6G9SB2Gc(~#iAivy-07K@C;afws=f@T3=dGl+y!Mop^^el{@gVMYSeo_E@xwn2U!=bX^={Xy> z=b#uBaOly{oC7;RS)k=MX|Y()wD)DuoNKq!m9E#4WpYz`6$iRqJYAFoA$ zLDRp33OJw{`Eo$(pT;_O9a~cR$`Oc2L252gd zy6vIl2C@Rj^ulhb@d@DayG~T>QPHYhud6=L^40U|!pg(v$5vrIuj@WT(^^

&2ME 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 diff --git a/package.json b/package.json index 2c7d53e..9f2a144 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,12 @@ "xz-decompress": "^0.2.1" }, "devDependencies": { + "@solidjs/testing-library": "^0.8.10", "@tailwindcss/typography": "^0.5.13", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^15.0.7", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "vite-plugin-solid": "^2.10.1", "@vitejs/plugin-react": "^4.3.0", "autoprefixer": "10.4.14", "eslint": "^8.57.0", @@ -41,6 +41,7 @@ "postcss": "^8.4.38", "tailwindcss": "^3.4.3", "vite": "^5.2.12", + "vite-plugin-solid": "^2.10.1", "vite-svg-loader": "^5.1.0", "vitest": "^1.6.0" } diff --git a/src/app/App.test.jsx b/src/app/App.test.jsx index 206de72..fd9b9d8 100644 --- a/src/app/App.test.jsx +++ b/src/app/App.test.jsx @@ -1,10 +1,17 @@ -import { Suspense } from 'react' -import { expect, test } from 'vitest' -import { render, screen } from '@testing-library/react' +import { describe, expect, test } from "vitest"; +import { render, screen } from "@solidjs/testing-library"; +import { Suspense } from "solid-js"; +import App from "."; -import App from '.' - -test('renders without crashing', () => { - render() - expect(screen.getByText('flash.comma.ai')).toBeInTheDocument() -}) +describe("App", () => { + test("renders without crashing", () => { + render(() => ( +

+ + + +
+ )); + expect(screen.getByText("flash.comma.ai")).toBeInTheDocument(); + }); +}); From ff1fff228c573ab962decb1b4f1d4a1303cf5223 Mon Sep 17 00:00:00 2001 From: Unies Ananda Raja Date: Sun, 24 Nov 2024 01:00:22 +0700 Subject: [PATCH 12/12] fix: sparse assumption on manifest test --- src/utils/manifest.test.js | 119 ++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/src/utils/manifest.test.js b/src/utils/manifest.test.js index 94b22df..e905765 100644 --- a/src/utils/manifest.test.js +++ b/src/utils/manifest.test.js @@ -1,76 +1,83 @@ -import { describe, expect, test, vi } from 'vitest' +import { describe, expect, test, vi } from "vitest"; -import * as Comlink from 'comlink' +import * as Comlink from "comlink"; -import config from '../config' -import { getManifest } from './manifest' +import config from "../config"; +import { getManifest } from "./manifest"; async function getImageWorker() { - let imageWorker + let imageWorker; - vi.mock('comlink') - vi.mocked(Comlink.expose).mockImplementation(worker => { - imageWorker = worker - imageWorker.init() - }) + vi.mock("comlink"); + vi.mocked(Comlink.expose).mockImplementation((worker) => { + imageWorker = worker; + imageWorker.init(); + }); - vi.resetModules() // this makes the import be reevaluated on each call - await import('./../workers/image.worker') + vi.resetModules(); // this makes the import be reevaluated on each call + await import("./../workers/image.worker"); - return imageWorker + return imageWorker; } for (const [branch, manifestUrl] of Object.entries(config.manifests)) { describe(`${branch} manifest`, async () => { - const images = await getManifest(manifestUrl) + const images = await getManifest(manifestUrl); // Check all images are present - expect(images.length).toBe(7) + expect(images.length).toBe(7); for (const image of images) { describe(`${image.name} image`, async () => { - test('xz archive', () => { - expect(image.archiveFileName, 'archive to be in xz format').toContain('.xz') - expect(image.archiveUrl, 'archive url to be in xz format').toContain('.xz') - }) - - if (image.name === 'system') { - test('alt image', () => { - expect(image.sparse, 'system image to be sparse').toBe(true) - expect(image.fileName, 'system image to be skip chunks').toContain('-skip-chunks-') - expect(image.archiveUrl, 'system image to point to skip chunks').toContain('-skip-chunks-') - }) + test("xz archive", () => { + expect(image.archiveFileName, "archive to be in xz format").toContain( + ".xz", + ); + expect(image.archiveUrl, "archive url to be in xz format").toContain( + ".xz", + ); + }); + + if (image.name === "system" && image.sparse) { + test("alt image", () => { + expect(image.fileName).toContain("-skip-chunks-"); + expect(image.archiveUrl).toContain("-skip-chunks-"); + }); } - test('image and checksum', async () => { - const imageWorkerFileHandler = { - getFile: vi.fn(), - createWritable: vi.fn().mockImplementation(() => ({ - write: vi.fn(), - close: vi.fn(), - })), - } - - globalThis.navigator = { - storage: { - getDirectory: () => ({ - getFileHandle: () => imageWorkerFileHandler, - }) - } - } - - imageWorkerFileHandler.getFile.mockImplementation(async () => { - const response = await fetch(image.archiveUrl) - expect(response.ok, 'to be uploaded').toBe(true) - - return response.blob() - }) - - const imageWorker = await getImageWorker() - - await imageWorker.unpackImage(image) - }, 8 * 60 * 1000) - }) + test( + "image and checksum", + async () => { + const imageWorkerFileHandler = { + getFile: vi.fn(), + createWritable: vi.fn().mockImplementation(() => ({ + write: vi.fn(), + close: vi.fn(), + })), + }; + + globalThis.navigator = { + storage: { + getDirectory: () => ({ + getFileHandle: () => imageWorkerFileHandler, + }), + }, + }; + + imageWorkerFileHandler.getFile.mockImplementation(async () => { + const response = await fetch(image.archiveUrl); + expect(response.ok, "to be uploaded").toBe(true); + + return response.blob(); + }); + + const imageWorker = await getImageWorker(); + + await imageWorker.unpackImage(image); + }, + 8 * 60 * 1000, + ); + }); } - }) + }); }