diff --git a/.env b/.env deleted file mode 100644 index 834c94d..0000000 --- a/.env +++ /dev/null @@ -1,2 +0,0 @@ - # Scrubbed by Glitch 2020-11-13T22:00:25+0000 - diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e53a9ab --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..29ce27a --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,9 @@ +categories: + - title: "⬆️ Dependencies" + collapse-after: 1 + labels: + - "dependencies" +template: | + ## What's Changed + + $CHANGES diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c16832c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + - run: npm ci + - run: script/build + - run: npm exec -- prettier --check src diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml new file mode 100644 index 0000000..a450cf2 --- /dev/null +++ b/.github/workflows/npmpublish.yml @@ -0,0 +1,22 @@ +# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created +# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages + +name: Node.js Package + +on: + release: + types: [published] + +jobs: + publish-npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16 + registry-url: https://registry.npmjs.org/ + - run: npm ci + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 0000000..896edaf --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,14 @@ +name: Release Drafter + +on: + push: + branches: + - main + +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 5509140..0f2ab60 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *.DS_Store +dist +node_modules diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..cd5ce26 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +src/stubs diff --git a/README.md b/README.md index 58e53d7..49cfec7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,26 @@ -# Adafruit ESPTool +# This package is deprecated. Use [esptool-js](https://github.com/espressif/esptool-js/) instead. -A Web Serial tool for updating your ESP bootloader. Please submit any new issues or pull requests to https://github.com/NabuCasa/esp-web-flasher. +# ESP Web Flasher -A live copy of the tool is hosted here: https://nabucasa.github.io/esp-web-flasher/ +JavaScript package to install firmware on ESP devices via the browser using WebSerial. + +_This project is a collaboration between [Adafruit](https://www.adafruit.com/) and [Nabu Casa](https://www.nabucasa.com/)._ + +## Used by + +- [Adafruit WipperSnapper](https://learn.adafruit.com/quickstart-adafruit-io-wippersnapper) +- [ESP Web Tools](https://github.com/esphome/esp-web-tools), the one click button to install your ESP devices via the browser. + +## Local development + +- Clone this repository. +- Install dependencies with `npm` +- Run `script/develop` +- Open http://localhost:5004/ + +## Origin + +This project was originally written by [Melissa LeBlanc-Williams](https://github.com/makermelissa). [Nabu Casa](https://www.nabucasa.com) ported the code over to TypeScript and in March 2022 took over maintenance from Adafruit. In July 2022, the Nabucasa stopped maintaining the project in favor of an official, but very early release of Espressif's [esptool-js](https://github.com/espressif/esptool-js/). Due to the instability of the tool, Adafruit updated their fork with Nabucasa's changes and took over maintenance once again. + + +A live copy of the tool is hosted here: https://adafruit.github.io/Adafruit_WebSerial_ESPTool/ \ No newline at end of file diff --git a/assets/adafruit-logo.svg b/assets/adafruit-logo.svg deleted file mode 100644 index 60c4468..0000000 --- a/assets/adafruit-logo.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/css/dark.css b/css/dark.css index ec0ae9c..b2cf609 100644 --- a/css/dark.css +++ b/css/dark.css @@ -45,7 +45,7 @@ input, select, button { color: #333; } -#notSupported { +.notSupported, .notSupported a { background-color: red; color: white; } diff --git a/css/light.css b/css/light.css index 426d82d..6babb39 100644 --- a/css/light.css +++ b/css/light.css @@ -13,7 +13,7 @@ input, select, button { color: #000; } -#notSupported { +.notSupported, .notSupported a { background-color: red; color: white; } @@ -76,4 +76,4 @@ input, select, button { .progress-bar { border-color: #333; -} \ No newline at end of file +} diff --git a/css/style.css b/css/style.css index b45690b..170cc2a 100644 --- a/css/style.css +++ b/css/style.css @@ -34,7 +34,8 @@ color: #000; } -button, .firmware { +button, +.firmware { height: 25px; font-size: 16px; border-radius: 15px; @@ -54,7 +55,10 @@ p { margin: 0; } -input, select, button, label { +input, +select, +button, +label { font-weight: 600; outline: none; } @@ -75,12 +79,6 @@ div.clear { clear: both; } -.Adafruit-Logo { - width: 115px; - height: 40px; - object-fit: contain; -} - .main { overflow-x: hidden; overflow-y: auto; @@ -125,7 +123,8 @@ div.clear { height: calc(100vh - 330px); } -#commands, #log { +#commands, +#log { transition: height 0.5s; } @@ -144,7 +143,7 @@ div.clear { font-size: 16px; overflow-x: hidden; overflow-x: auto; - transition : color 0.1s linear; + transition: color 0.1s linear; padding: 0 50px; border: 20px solid #000; -ms-overflow-style: none; @@ -158,12 +157,18 @@ div.clear { } .footer { - height: 45px; - line-height: 45px; padding-left: 70px; padding-right: 70px; } +.footer a { + color: blue; +} + +.footer .controls { + line-height: 45px; +} + .footer button { font-size: 14px; margin: 0 10px; @@ -276,10 +281,7 @@ div.clear { .footer .onoffswitch { margin-right: 20px; -} - -#fpsCounter { - margin-right: 10px; + top: 7px; } #butClear { @@ -310,12 +312,12 @@ div.clear { } .firmware > input { - width: 0.1px; - height: 0.1px; - opacity: 0; - overflow: hidden; - position: absolute; - z-index: -1; + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; } .firmware { @@ -372,3 +374,10 @@ div.clear { border-width: 2px; border-style: solid; } +.justify { + display: flex; + justify-content: space-between; +} +.center { + text-align: center; +} diff --git a/index.html b/index.html index 57f12b7..5b40232 100644 --- a/index.html +++ b/index.html @@ -1,16 +1,212 @@ - Redirecting to NabuCasa ESP Web Flasher - - - + ESP Web Flasher + + + + + + + + + + + + + - Please use https://nabucasa.github.io/esp-web-flasher/ to flash your ESP boards. +
+
+
+ + +
+
+
+
+ This website is no longer maintained. + Use esptool-js instead. +
+
+ Sorry, Web Serial is not supported on this device, make sure + you're running Chrome 89 or later. +
+
+
ESP Web Flasher
+
+ +
+ + +
+
+
+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
+
+
+
+
+ \ No newline at end of file diff --git a/js/script.js b/js/script.js index c154543..6cdf4a7 100644 --- a/js/script.js +++ b/js/script.js @@ -1,103 +1,64 @@ -// Note: the code will still work without this line, but without it you -// will see an error in the editor -/* global EspLoader, ESP_ROM_BAUD, port, reader, inputBuffer */ -'use strict'; - -let espTool; -let isConnected = false; +let espStub; const baudRates = [921600, 115200, 230400, 460800]; const bufferSize = 512; -const colors = ['#00a7e9', '#f89521', '#be1e2d']; -const measurementPeriodId = '0001'; +const colors = ["#00a7e9", "#f89521", "#be1e2d"]; +const measurementPeriodId = "0001"; const maxLogLength = 100; -const log = document.getElementById('log'); -const butConnect = document.getElementById('butConnect'); -const baudRate = document.getElementById('baudRate'); -const butClear = document.getElementById('butClear'); -const butErase = document.getElementById('butErase'); -const butProgram = document.getElementById('butProgram'); -const autoscroll = document.getElementById('autoscroll'); -const lightSS = document.getElementById('light'); -const darkSS = document.getElementById('dark'); -const darkMode = document.getElementById('darkmode'); +const log = document.getElementById("log"); +const butConnect = document.getElementById("butConnect"); +const baudRate = document.getElementById("baudRate"); +const butClear = document.getElementById("butClear"); +const butErase = document.getElementById("butErase"); +const butProgram = document.getElementById("butProgram"); +const autoscroll = document.getElementById("autoscroll"); +const lightSS = document.getElementById("light"); +const darkSS = document.getElementById("dark"); +const darkMode = document.getElementById("darkmode"); const firmware = document.querySelectorAll(".upload .firmware input"); const progress = document.querySelectorAll(".upload .progress-bar"); -const offsets = document.querySelectorAll('.upload .offset'); -const appDiv = document.getElementById('app'); - -let colorIndex = 0; -let activePanels = []; -let bytesReceived = 0; -let currentBoard; -let buttonState = 0; - -document.addEventListener('DOMContentLoaded', () => { - let debug = 0; - var getParams = {} - location.search.substr(1).split("&").forEach(function(item) {getParams[item.split("=")[0]] = item.split("=")[1]}) - if (getParams["debug"] !== undefined) { - if (getParams["debug"] == "1" || getParams["debug"] == "2") { - debug = parseInt(getParams["debug"]); - } - } +const offsets = document.querySelectorAll(".upload .offset"); +const appDiv = document.getElementById("app"); - espTool = new EspLoader({ - updateProgress: updateProgress, - logMsg: logMsg, - debugMsg: debugMsg, - debug: debug}) - butConnect.addEventListener('click', () => { +document.addEventListener("DOMContentLoaded", () => { + butConnect.addEventListener("click", () => { clickConnect().catch(async (e) => { - errorHandle(e); - disconnect(); + console.error(e); + errorMsg(e.message || e); + if (espStub) { + await espStub.disconnect(); + } toggleUIConnected(false); }); }); - butClear.addEventListener('click', clickClear); - butErase.addEventListener('click', clickErase); - butProgram.addEventListener('click', clickProgram); + butClear.addEventListener("click", clickClear); + butErase.addEventListener("click", clickErase); + butProgram.addEventListener("click", clickProgram); for (let i = 0; i < firmware.length; i++) { - firmware[i].addEventListener('change', checkFirmware); + firmware[i].addEventListener("change", checkFirmware); } for (let i = 0; i < offsets.length; i++) { - offsets[i].addEventListener('change', checkProgrammable); + offsets[i].addEventListener("change", checkProgrammable); } - autoscroll.addEventListener('click', clickAutoscroll); - baudRate.addEventListener('change', changeBaudRate); - darkMode.addEventListener('click', clickDarkMode); - window.addEventListener('error', function(event) { - console.log("Got an uncaught error: ", event.error) + autoscroll.addEventListener("click", clickAutoscroll); + baudRate.addEventListener("change", changeBaudRate); + darkMode.addEventListener("click", clickDarkMode); + window.addEventListener("error", function (event) { + console.log("Got an uncaught error: ", event.error); }); - if ('serial' in navigator) { - const notSupported = document.getElementById('notSupported'); - notSupported.classList.add('hidden'); + if ("serial" in navigator) { + const notSupported = document.getElementById("notSupported"); + notSupported.classList.add("hidden"); } initBaudRate(); loadAllSettings(); updateTheme(); - logMsg("Adafruit WebSerial ESPTool loaded."); - if (debug) { - logMsg("Debugging Level " + debug + " Enabled."); - } + logMsg("ESP Web Flasher loaded."); }); -/** - * @name connect - * Opens a Web Serial connection to a micro:bit and sets up the input and - * output stream. - */ -async function connect() { - logMsg("Connecting...") - await espTool.connect() - readLoop().catch((error) => { - toggleUIConnected(false); - }); -} - function initBaudRate() { for (let rate of baudRates) { var option = document.createElement("option"); @@ -107,38 +68,8 @@ function initBaudRate() { } } -function updateProgress(part, percentage) { - let progressBar = progress[part].querySelector("div"); - progressBar.style.width = percentage + "%"; -} - -/** - * @name disconnect - * Closes the Web Serial connection. - */ -async function disconnect() { - toggleUIToolbar(false); - await espTool.disconnect() -} - -/** - * @name readLoop - * Reads data from the input stream and places it in the inputBuffer - */ -async function readLoop() { - reader = port.readable.getReader(); - while (true) { - const { value, done } = await reader.read(); - if (done) { - reader.releaseLock(); - break; - } - inputBuffer = inputBuffer.concat(Array.from(value)); - } -} - function logMsg(text) { - log.innerHTML += text+ "
"; + log.innerHTML += text + "
"; // Remove old log content if (log.textContent.split("\n").length > maxLogLength + 1) { @@ -147,24 +78,24 @@ function logMsg(text) { } if (autoscroll.checked) { - log.scrollTop = log.scrollHeight + log.scrollTop = log.scrollHeight; } } function debugMsg(debugLevel, ...args) { function getStackTrace() { let stack = new Error().stack; - stack = stack.split("\n").map(v => v.trim()); - for (let i=0; i<3; i++) { - stack.shift(); - } + //console.log(stack); + stack = stack.split("\n").map((v) => v.trim()); + stack.shift(); + stack.shift(); let trace = []; for (let line of stack) { line = line.replace("at ", ""); trace.push({ - "func": line.substr(0, line.indexOf("(") - 1), - "pos": line.substring(line.indexOf(".js:") + 4, line.lastIndexOf(":")) + func: line.substr(0, line.indexOf("(") - 1), + pos: line.substring(line.indexOf(".js:") + 4, line.lastIndexOf(":")), }); } @@ -176,7 +107,8 @@ function debugMsg(debugLevel, ...args) { let stack = getStackTrace(); stack.shift(); let top = stack.shift(); - let prefix = '[' + top.func + ":" + top.pos + '] '; + let prefix = + '[' + top.func + ":" + top.pos + "] "; for (let arg of args) { if (arg === undefined) { logMsg(prefix + "undefined"); @@ -189,9 +121,16 @@ function debugMsg(debugLevel, ...args) { } else if (typeof arg == "boolean") { logMsg(prefix + (arg ? "true" : "false")); } else if (Array.isArray(arg)) { - logMsg(prefix + "[" + arg.map(value => espTool.toHex(value)).join(", ") + "]"); - } else if (typeof arg == "object" && (arg instanceof Uint8Array)) { - logMsg(prefix + "[" + Array.from(arg).map(value => espTool.toHex(value)).join(", ") + "]"); + logMsg(prefix + "[" + arg.map((value) => toHex(value)).join(", ") + "]"); + } else if (typeof arg == "object" && arg instanceof Uint8Array) { + logMsg( + prefix + + "[" + + Array.from(arg) + .map((value) => toHex(value)) + .join(", ") + + "]" + ); } else { logMsg(prefix + "Unhandled type of argument:" + typeof arg); console.log(arg); @@ -200,24 +139,11 @@ function debugMsg(debugLevel, ...args) { } } -function errorHandle(error) { - console.error(error); - if (error.message) { - errorMsg(error.message) - } else { - errorMsg(error); - } -} - function errorMsg(text) { logMsg('Error: ' + text); console.error(text); } -function formatMacAddr(macAddr) { - return macAddr.map(value => value.toString(16).toUpperCase().padStart(2, "0")).join(":"); -} - /** * @name updateTheme * Sets the theme to Adafruit (dark) mode. Can be refactored later for more themes @@ -225,7 +151,7 @@ function formatMacAddr(macAddr) { function updateTheme() { // Disable all themes document - .querySelectorAll('link[rel=stylesheet].alternate') + .querySelectorAll("link[rel=stylesheet].alternate") .forEach((styleSheet) => { enableStyleSheet(styleSheet, false); }); @@ -241,15 +167,10 @@ function enableStyleSheet(node, enabled) { node.disabled = !enabled; } -/** - * @name reset - * Reset the Panels, Log, and associated data - */ -async function reset() { - bytesReceived = 0; - - // Clear the log - log.innerHTML = ""; +function formatMacAddr(macAddr) { + return macAddr + .map((value) => value.toString(16).toUpperCase().padStart(2, "0")) + .join(":"); } /** @@ -257,37 +178,37 @@ async function reset() { * Click handler for the connect/disconnect button. */ async function clickConnect() { - if (espTool.connected()) { - await disconnect(); + if (espStub) { + await espStub.disconnect(); + await espStub.port.close(); toggleUIConnected(false); + espStub = undefined; return; } - await connect(); + const esploaderMod = await window.esptoolPackage; - toggleUIConnected(true); + const esploader = await esploaderMod.connect({ + log: (...args) => logMsg(...args), + debug: (...args) => debugMsg(...args), + error: (...args) => errorMsg(...args), + }); try { - if (await espTool.sync()) { - toggleUIToolbar(true); - appDiv.classList.add("connected"); - let baud = parseInt(baudRate.value); - logMsg("Connected to " + await espTool.chipName()); - logMsg("MAC Address: " + formatMacAddr(espTool.macAddr())); - espTool = await espTool.runStub(); - if (baud != ESP_ROM_BAUD) { - let chipType = await espTool.chipType(); - if ([ESP32, ESP32C3].includes(chipType)) { - logMsg("WARNING: ESP32 and ESP32C3 are having issues working at speeds faster than 115200. Continuing at 115200 for now...") - } else { - await changeBaudRate(baud); - } - } - } - } catch(e) { - errorHandle(e); - await disconnect(); - toggleUIConnected(false); - return; + await esploader.initialize(); + + logMsg("Connected to " + esploader.chipName); + logMsg("MAC Address: " + formatMacAddr(esploader.macAddr())); + + espStub = await esploader.runStub(); + toggleUIConnected(true); + toggleUIToolbar(true); + espStub.addEventListener("disconnect", () => { + toggleUIConnected(false); + espStub = false; + }); + } catch (err) { + await esploader.disconnect(); + throw err; } } @@ -296,11 +217,11 @@ async function clickConnect() { * Change handler for the Baud Rate selector. */ async function changeBaudRate() { - saveSetting('baudrate', baudRate.value); - if (isConnected) { + saveSetting("baudrate", baudRate.value); + if (espStub) { let baud = parseInt(baudRate.value); if (baudRates.includes(baud)) { - await espTool.setBaudrate(baud); + await espStub.setBaudrate(baud); } } } @@ -310,7 +231,7 @@ async function changeBaudRate() { * Change handler for the Autoscroll checkbox. */ async function clickAutoscroll() { - saveSetting('autoscroll', autoscroll.checked); + saveSetting("autoscroll", autoscroll.checked); } /** @@ -319,7 +240,7 @@ async function clickAutoscroll() { */ async function clickDarkMode() { updateTheme(); - saveSetting('darkmode', darkMode.checked); + saveSetting("darkmode", darkMode.checked); } /** @@ -327,17 +248,19 @@ async function clickDarkMode() { * Click handler for the erase button. */ async function clickErase() { - if (window.confirm("This will erase the entire flash. Click OK to continue.")) { + if ( + window.confirm("This will erase the entire flash. Click OK to continue.") + ) { baudRate.disabled = true; butErase.disabled = true; butProgram.disabled = true; try { logMsg("Erasing flash memory. Please wait..."); let stamp = Date.now(); - await espTool.eraseFlash(); + await espStub.eraseFlash(); logMsg("Finished. Took " + (Date.now() - stamp) + "ms to erase."); - } catch(e) { - errorHandle(e); + } catch (e) { + errorMsg(e); } finally { butErase.disabled = false; baudRate.disabled = false; @@ -370,7 +293,7 @@ async function clickProgram() { baudRate.disabled = true; butErase.disabled = true; butProgram.disabled = true; - for (let i=0; i< 4; i++) { + for (let i = 0; i < 4; i++) { firmware[i].disabled = true; offsets[i].disabled = true; } @@ -380,14 +303,21 @@ async function clickProgram() { let contents = await readUploadedFileAsArrayBuffer(binfile); try { let offset = parseInt(offsets[file].value, 16); - await espTool.flashData(contents, offset, file); + const progressBar = progress[file].querySelector("div"); + await espStub.flashData( + contents, + (bytesWritten, totalBytes) => { + progressBar.style.width = + Math.floor((bytesWritten / totalBytes) * 100) + "%"; + }, + offset + ); await sleep(100); - logMsg("To run the new firmware, please reset your device."); - } catch(e) { - errorHandle(e); + } catch (e) { + errorMsg(e); } } - for (let i=0; i< 4; i++) { + for (let i = 0; i < 4; i++) { firmware[i].disabled = false; offsets[i].disabled = false; progress[i].classList.add("hidden"); @@ -396,6 +326,7 @@ async function clickProgram() { butErase.disabled = false; baudRate.disabled = false; butProgram.disabled = getValidFiles().length == 0; + logMsg("To run the new firmware, please reset your device."); } function getValidFiles() { @@ -404,7 +335,7 @@ function getValidFiles() { // and will also return a list of files to program let validFiles = []; let offsetVals = []; - for (let i=0; i<4; i++) { + for (let i = 0; i < 4; i++) { let offs = parseInt(offsets[i].value, 16); if (firmware[i].files.length > 0 && !offsetVals.includes(offs)) { validFiles.push(i); @@ -427,7 +358,7 @@ async function checkProgrammable() { * Handler for firmware upload changes */ async function checkFirmware(event) { - let filename = event.target.value.split("\\" ).pop(); + let filename = event.target.value.split("\\").pop(); let label = event.target.parentNode.querySelector("span"); let icon = event.target.parentNode.querySelector("svg"); if (filename != "") { @@ -464,7 +395,7 @@ function convertJSON(chunk) { function toggleUIToolbar(show) { isConnected = show; - for (let i=0; i< 4; i++) { + for (let i = 0; i < 4; i++) { progress[i].classList.add("hidden"); progress[i].querySelector("div").style.width = "0"; } @@ -477,9 +408,9 @@ function toggleUIToolbar(show) { } function toggleUIConnected(connected) { - let lbl = 'Connect'; + let lbl = "Connect"; if (connected) { - lbl = 'Disconnect'; + lbl = "Disconnect"; } else { toggleUIToolbar(false); } @@ -488,9 +419,9 @@ function toggleUIConnected(connected) { function loadAllSettings() { // Load all saved settings or defaults - autoscroll.checked = loadSetting('autoscroll', true); - baudRate.value = loadSetting('baudrate', baudRates[0]); - darkMode.checked = loadSetting('darkmode', false); + autoscroll.checked = loadSetting("autoscroll", true); + baudRate.value = loadSetting("baudrate", 115200); + darkMode.checked = loadSetting("darkmode", false); } function loadSetting(setting, defaultValue) { @@ -506,6 +437,13 @@ function saveSetting(setting, value) { window.localStorage.setItem(setting, JSON.stringify(value)); } +function ucWords(text) { + return text + .replace("_", " ") + .toLowerCase() + .replace(/(?<= )[^\s]|^./g, (a) => a.toUpperCase()); +} + function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } diff --git a/license.md b/license.md index 833ee29..ae925e9 100644 --- a/license.md +++ b/license.md @@ -1,5 +1,6 @@ MIT License +Copyright (c) 2021 Nabu Casa Copyright (c) 2020 Melissa LeBlanc-Williams for Adafruit Industries Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5dbe68c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2338 @@ +{ + "name": "esp-web-flasher", + "version": "5.1.5", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "esp-web-flasher", + "version": "5.1.5", + "license": "MIT", + "dependencies": { + "pako": "^2.0.3", + "tslib": "^2.2.0" + }, + "devDependencies": { + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^13.0.0", + "@rollup/plugin-typescript": "^8.2.1", + "@types/pako": "^2.0.0", + "@types/w3c-web-serial": "^1.0.1", + "prettier": "^2.3.0", + "rollup": "^2.48.0", + "rollup-plugin-terser": "^7.0.2", + "serve": "^13.0.2", + "typescript": "^4.6.2" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.12.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.12.13" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.14.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/highlight": { + "version": "7.14.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@rollup/plugin-json": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^3.0.8" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz", + "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "rollup": "^2.42.0" + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.3.tgz", + "integrity": "sha512-55L9SyiYu3r/JtqdjhwcwaECXP7JeJ9h1Sg1VWRJKIutla2MdZQodTgcCNybXLMCnqpNLEhS2vGENww98L1npg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "resolve": "^1.17.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@types/estree": { + "version": "0.0.39", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "15.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/pako": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.0.tgz", + "integrity": "sha512-10+iaz93qR5WYxTo+PMifD5TSxiOtdRaxBf7INGGXMQgTCu8Z/7GYWYFUOS3q/G0nE5boj1r4FEB+WSy7s5gbA==", + "dev": true + }, + "node_modules/@types/resolve": { + "version": "1.17.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/w3c-web-serial": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/w3c-web-serial/-/w3c-web-serial-1.0.2.tgz", + "integrity": "sha512-Ftx4BtLxgAnel7V7GbHylCYjSq827A+jeEE3SnTS7huCGUN0pSwUn+CchTCT9TkZj9w+NVMUq4Bk2R0GvUNmAQ==", + "dev": true + }, + "node_modules/@zeit/schemas": { + "version": "2.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.7", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "2.1.30", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.47.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/arg": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/boxen/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/boxen/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", + "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", + "dev": true, + "dependencies": { + "arch": "^2.1.1", + "execa": "^1.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.3", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.14", + "debug": "2.6.9", + "on-headers": "~1.0.1", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/estree-walker": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-url-parser": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^1.3.2" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/has": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "dev": true, + "license": "ISC" + }, + "node_modules/is-builtin-module": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.1.0.tgz", + "integrity": "sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-core-module": { + "version": "2.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/jest-worker": { + "version": "26.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.47.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.33.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/path-key": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "2.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.20.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "2.75.7", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz", + "integrity": "sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-terser": { + "version": "7.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "terser": "^5.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/serialize-javascript": { + "version": "4.0.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.2.tgz", + "integrity": "sha512-71R6fKvNgKrqARAag6lYJNnxDzpH7DCNrMuvPY5PLVaC2PDhJsGTj/34o4o4tPWhTuLgEXqvgnAWbATQ9zGZTQ==", + "dev": true, + "dependencies": { + "@zeit/schemas": "2.6.0", + "ajv": "6.12.6", + "arg": "2.0.0", + "boxen": "5.1.2", + "chalk": "2.4.1", + "clipboardy": "2.3.0", + "compression": "1.7.3", + "serve-handler": "6.1.3", + "update-check": "1.5.2" + }, + "bin": { + "serve": "bin/serve.js" + } + }, + "node_modules/serve-handler": { + "version": "6.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.0.4", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" + } + }, + "node_modules/serve/node_modules/chalk": { + "version": "2.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.7.3", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/terser": { + "version": "5.7.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/update-check": { + "version": "1.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "1.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.0", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.0", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@rollup/plugin-json": { + "version": "4.1.0", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.8" + } + }, + "@rollup/plugin-node-resolve": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz", + "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + } + }, + "@rollup/plugin-typescript": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.3.tgz", + "integrity": "sha512-55L9SyiYu3r/JtqdjhwcwaECXP7JeJ9h1Sg1VWRJKIutla2MdZQodTgcCNybXLMCnqpNLEhS2vGENww98L1npg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "resolve": "^1.17.0" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + } + }, + "@types/estree": { + "version": "0.0.39", + "dev": true + }, + "@types/node": { + "version": "15.6.0", + "dev": true + }, + "@types/pako": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.0.tgz", + "integrity": "sha512-10+iaz93qR5WYxTo+PMifD5TSxiOtdRaxBf7INGGXMQgTCu8Z/7GYWYFUOS3q/G0nE5boj1r4FEB+WSy7s5gbA==", + "dev": true + }, + "@types/resolve": { + "version": "1.17.1", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/w3c-web-serial": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/w3c-web-serial/-/w3c-web-serial-1.0.2.tgz", + "integrity": "sha512-Ftx4BtLxgAnel7V7GbHylCYjSq827A+jeEE3SnTS7huCGUN0pSwUn+CchTCT9TkZj9w+NVMUq4Bk2R0GvUNmAQ==", + "dev": true + }, + "@zeit/schemas": { + "version": "2.6.0", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "dependencies": { + "mime-types": { + "version": "2.1.30", + "dev": true, + "requires": { + "mime-db": "1.47.0" + } + } + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "requires": { + "string-width": "^4.1.0" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true + }, + "arg": { + "version": "2.0.0", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "dev": true + }, + "boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "dev": true + }, + "builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "dev": true + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true + }, + "clipboardy": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", + "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", + "dev": true, + "requires": { + "arch": "^2.1.1", + "execa": "^1.0.0", + "is-wsl": "^2.1.1" + } + }, + "color-convert": { + "version": "1.9.3", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "dev": true + }, + "commander": { + "version": "2.20.3", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.3", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.14", + "debug": "2.6.9", + "on-headers": "~1.0.1", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "dev": true + }, + "content-disposition": { + "version": "0.5.2", + "dev": true + }, + "debug": { + "version": "2.6.9", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "dev": true + }, + "estree-walker": { + "version": "1.0.1", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-url-parser": { + "version": "1.1.3", + "dev": true, + "requires": { + "punycode": "^1.3.2" + } + }, + "fsevents": { + "version": "2.3.2", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "dev": true + }, + "has": { + "version": "1.0.3", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "dev": true + }, + "ini": { + "version": "1.3.8", + "dev": true + }, + "is-builtin-module": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.1.0.tgz", + "integrity": "sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==", + "dev": true, + "requires": { + "builtin-modules": "^3.0.0" + } + }, + "is-core-module": { + "version": "2.4.0", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-module": { + "version": "1.0.0", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isexe": { + "version": "2.0.0", + "dev": true + }, + "jest-worker": { + "version": "26.6.2", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "dev": true + }, + "mime-db": { + "version": "1.47.0", + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "dev": true, + "requires": { + "mime-db": "~1.33.0" + }, + "dependencies": { + "mime-db": { + "version": "1.33.0", + "dev": true + } + } + }, + "minimatch": { + "version": "3.0.4", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "npm-run-path": { + "version": "2.0.2", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "on-headers": { + "version": "1.0.2", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-finally": { + "version": "1.0.0", + "dev": true + }, + "pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" + }, + "path-is-inside": { + "version": "1.0.2", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-to-regexp": { + "version": "2.2.1", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "dev": true + }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "1.4.1", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.0", + "dev": true + }, + "rc": { + "version": "1.2.8", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "registry-auth-token": { + "version": "3.3.2", + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, + "resolve": { + "version": "1.20.0", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "rollup": { + "version": "2.75.7", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz", + "integrity": "sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "rollup-plugin-terser": { + "version": "7.0.2", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "terser": "^5.0.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "serialize-javascript": { + "version": "4.0.0", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.2.tgz", + "integrity": "sha512-71R6fKvNgKrqARAag6lYJNnxDzpH7DCNrMuvPY5PLVaC2PDhJsGTj/34o4o4tPWhTuLgEXqvgnAWbATQ9zGZTQ==", + "dev": true, + "requires": { + "@zeit/schemas": "2.6.0", + "ajv": "6.12.6", + "arg": "2.0.0", + "boxen": "5.1.2", + "chalk": "2.4.1", + "clipboardy": "2.3.0", + "compression": "1.7.3", + "serve-handler": "6.1.3", + "update-check": "1.5.2" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "serve-handler": { + "version": "6.1.3", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.0.4", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" + } + }, + "shebang-command": { + "version": "1.2.0", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "dev": true + }, + "source-map": { + "version": "0.7.3", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "dev": true + } + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "terser": { + "version": "5.7.0", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true + }, + "update-check": { + "version": "1.5.2", + "dev": true, + "requires": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, + "vary": { + "version": "1.1.2", + "dev": true + }, + "which": { + "version": "1.3.1", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "requires": { + "string-width": "^4.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9000e50 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "esp-web-flasher", + "version": "5.1.5", + "description": "Flash ESP devices using WebSerial", + "main": "dist/index.js", + "repository": "https://github.com/nabucasa/ESP-Web-flasher", + "author": "Nabu Casa", + "license": "MIT", + "scripts": { + "prepublishOnly": "script/build" + }, + "devDependencies": { + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^13.0.0", + "@rollup/plugin-typescript": "^8.2.1", + "@types/pako": "^2.0.0", + "@types/w3c-web-serial": "^1.0.1", + "prettier": "^2.3.0", + "rollup": "^2.48.0", + "rollup-plugin-terser": "^7.0.2", + "serve": "^13.0.2", + "typescript": "^4.6.2" + }, + "dependencies": { + "pako": "^2.0.3", + "tslib": "^2.2.0" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..7c41f82 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,27 @@ +import { nodeResolve } from "@rollup/plugin-node-resolve"; +import json from "@rollup/plugin-json"; +import { terser } from "rollup-plugin-terser"; + +const config = { + input: "dist/index.js", + output: { + dir: "dist/web", + format: "module", + }, + // preserveEntrySignatures: false, + plugins: [nodeResolve(), json()], +}; + +if (process.env.NODE_ENV === "production") { + config.plugins.push( + terser({ + ecma: 2019, + toplevel: true, + output: { + comments: false, + }, + }) + ); +} + +export default config; diff --git a/script/build b/script/build new file mode 100755 index 0000000..7b68f07 --- /dev/null +++ b/script/build @@ -0,0 +1,8 @@ +# Stop on errors +set -e + +cd "$(dirname "$0")/.." + +rm -rf dist +NODE_ENV=production npm exec -- tsc +NODE_ENV=production npm exec -- rollup -c diff --git a/script/develop b/script/develop new file mode 100755 index 0000000..4899333 --- /dev/null +++ b/script/develop @@ -0,0 +1,17 @@ +# Stop on errors +set -e + +cd "$(dirname "$0")/.." + +rm -rf dist + +# Quit all background tasks when script exits +trap "kill 0" EXIT + +# Run tsc once as rollup expects those files +npm exec -- tsc || true + +npm exec -- serve -p 5004 & +npm exec -- tsc --watch & +npm exec -- rollup -c --watch & +wait diff --git a/extras/stubgen.py b/script/stubgen.py similarity index 95% rename from extras/stubgen.py rename to script/stubgen.py index 19d4ce1..0356825 100644 --- a/extras/stubgen.py +++ b/script/stubgen.py @@ -42,6 +42,6 @@ for key, stub in stubs.items(): jsondata = json.dumps(code) - f = open("../stubs/" + key + ".json", "w+") + f = open("../src/stubs/" + key + ".json", "w+") f.write(jsondata) - f.close() \ No newline at end of file + f.close() diff --git a/src/const.ts b/src/const.ts new file mode 100644 index 0000000..16b93ad --- /dev/null +++ b/src/const.ts @@ -0,0 +1,303 @@ +import { toByteArray } from "./util"; + +export interface Logger { + log(msg: string, ...args: any[]): void; + error(msg: string, ...args: any[]): void; + debug(msg: string, ...args: any[]): void; +} + +export const baudRates = [ + 115200, 128000, 153600, 230400, 460800, 921600, 1500000, 2000000, +]; +export const FLASH_SIZES = { + "512KB": 0x00, + "256KB": 0x10, + "1MB": 0x20, + "2MB": 0x30, + "4MB": 0x40, + "2MB-c1": 0x50, + "4MB-c1": 0x60, + "8MB": 0x80, + "16MB": 0x90, +}; + +export const ESP32_FLASH_SIZES = { + "1MB": 0x00, + "2MB": 0x10, + "4MB": 0x20, + "8MB": 0x30, + "16MB": 0x40, + "32MB": 0x50, + "64MB": 0x60, + "128MB": 0x70, +}; + +export const DETECTED_FLASH_SIZES = { + 0x12: "256KB", + 0x13: "512KB", + 0x14: "1MB", + 0x15: "2MB", + 0x16: "4MB", + 0x17: "8MB", + 0x18: "16MB", + 0x19: "32MB", + 0x1a: "64MB", +}; + +export const FLASH_WRITE_SIZE = 0x400; +export const STUB_FLASH_WRITE_SIZE = 0x4000; +export const FLASH_SECTOR_SIZE = 0x1000; // Flash sector size, minimum unit of erase. +export const ESP_ROM_BAUD = 115200; + +export const ESP8266_SPI_REG_BASE = 0x60000200; +export const ESP8266_BASEFUSEADDR = 0x3ff00050; +export const ESP8266_MACFUSEADDR = 0x3ff00050; +export const ESP8266_SPI_USR_OFFS = 0x1c; +export const ESP8266_SPI_USR1_OFFS = 0x20; +export const ESP8266_SPI_USR2_OFFS = 0x24; +export const ESP8266_SPI_MOSI_DLEN_OFFS = -1; +export const ESP8266_SPI_MISO_DLEN_OFFS = -1; +export const ESP8266_SPI_W0_OFFS = 0x40; +export const ESP8266_UART_DATE_REG_ADDR = 0x60000078; +export const ESP8266_BOOTLOADER_FLASH_OFFSET = 0x0; + +export const ESP32_SPI_REG_BASE = 0x3ff42000; +export const ESP32_BASEFUSEADDR = 0x3ff5a000; +export const ESP32_MACFUSEADDR = 0x3ff5a000; +export const ESP32_SPI_USR_OFFS = 0x1c; +export const ESP32_SPI_USR1_OFFS = 0x20; +export const ESP32_SPI_USR2_OFFS = 0x24; +export const ESP32_SPI_MOSI_DLEN_OFFS = 0x28; +export const ESP32_SPI_MISO_DLEN_OFFS = 0x2c; +export const ESP32_SPI_W0_OFFS = 0x80; +export const ESP32_UART_DATE_REG_ADDR = 0x60000078; +export const ESP32_BOOTLOADER_FLASH_OFFSET = 0x1000; + +export const ESP32S2_SPI_REG_BASE = 0x3f402000; +export const ESP32S2_BASEFUSEADDR = 0x3f41a000; +export const ESP32S2_MACFUSEADDR = 0x3f41a044; +export const ESP32S2_SPI_USR_OFFS = 0x18; +export const ESP32S2_SPI_USR1_OFFS = 0x1c; +export const ESP32S2_SPI_USR2_OFFS = 0x20; +export const ESP32S2_SPI_MOSI_DLEN_OFFS = 0x24; +export const ESP32S2_SPI_MISO_DLEN_OFFS = 0x28; +export const ESP32S2_SPI_W0_OFFS = 0x58; +export const ESP32S2_UART_DATE_REG_ADDR = 0x60000078; +export const ESP32S2_BOOTLOADER_FLASH_OFFSET = 0x1000; + +export const ESP32S3_SPI_REG_BASE = 0x60002000; +export const ESP32S3_BASEFUSEADDR = 0x60007000; +export const ESP32S3_MACFUSEADDR = 0x60007000 + 0x044; +export const ESP32S3_SPI_USR_OFFS = 0x18; +export const ESP32S3_SPI_USR1_OFFS = 0x1c; +export const ESP32S3_SPI_USR2_OFFS = 0x20; +export const ESP32S3_SPI_MOSI_DLEN_OFFS = 0x24; +export const ESP32S3_SPI_MISO_DLEN_OFFS = 0x28; +export const ESP32S3_SPI_W0_OFFS = 0x58; +export const ESP32S3_UART_DATE_REG_ADDR = 0x60000080; +export const ESP32S3_BOOTLOADER_FLASH_OFFSET = 0x0; + +export const ESP32C3_SPI_REG_BASE = 0x60002000; +export const ESP32C3_BASEFUSEADDR = 0x60008800; +export const ESP32C3_MACFUSEADDR = 0x60008800 + 0x044; +export const ESP32C3_SPI_USR_OFFS = 0x18; +export const ESP32C3_SPI_USR1_OFFS = 0x1c; +export const ESP32C3_SPI_USR2_OFFS = 0x20; +export const ESP32C3_SPI_MOSI_DLEN_OFFS = 0x24; +export const ESP32C3_SPI_MISO_DLEN_OFFS = 0x28; +export const ESP32C3_SPI_W0_OFFS = 0x58; +export const ESP32C3_UART_DATE_REG_ADDR = 0x6000007c; +export const ESP32C3_BOOTLOADER_FLASH_OFFSET = 0x0; + +export interface SpiFlashAddresses { + regBase: number; + baseFuse: number; + macFuse: number; + usrOffs: number; + usr1Offs: number; + usr2Offs: number; + mosiDlenOffs: number; + misoDlenOffs: number; + w0Offs: number; + uartDateReg: number; + flashOffs: number; +} + +export const SYNC_PACKET = toByteArray( + "\x07\x07\x12 UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU" +); +export const CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000; +// These values for the families are made up; nothing that esptool uses. +export const CHIP_FAMILY_ESP8266 = 0x8266; +export const CHIP_FAMILY_ESP32 = 0x32; +export const CHIP_FAMILY_ESP32S2 = 0x3252; +export const CHIP_FAMILY_ESP32S3 = 0x3253; +export const CHIP_FAMILY_ESP32C3 = 0x32c3; +export const CHIP_FAMILY_ESP32C6 = 0x32c6; +export const CHIP_FAMILY_ESP32H2 = 0x3272; +export type ChipFamily = + | typeof CHIP_FAMILY_ESP8266 + | typeof CHIP_FAMILY_ESP32 + | typeof CHIP_FAMILY_ESP32S2 + | typeof CHIP_FAMILY_ESP32S3 + | typeof CHIP_FAMILY_ESP32C3 + | typeof CHIP_FAMILY_ESP32C6 + | typeof CHIP_FAMILY_ESP32H2; + +export const CHIP_DETECT_MAGIC_VALUES = { + 0xfff0c101: { name: "ESP8266", family: CHIP_FAMILY_ESP8266 }, + 0x00f01d83: { name: "ESP32", family: CHIP_FAMILY_ESP32 }, + 0x000007c6: { name: "ESP32-S2", family: CHIP_FAMILY_ESP32S2 }, + 0x9: { name: "ESP32-S3", family: CHIP_FAMILY_ESP32S3 }, + 0xeb004136: { name: "ESP32-S3(beta2)", family: CHIP_FAMILY_ESP32S3 }, + 0x6921506f: { name: "ESP32-C3", family: CHIP_FAMILY_ESP32C3 }, + 0x1b31506f: { name: "ESP32-C3", family: CHIP_FAMILY_ESP32C3 }, + 0xca26cc22: { name: "ESP32-H2", family: CHIP_FAMILY_ESP32H2 }, + 0x0da1806f: { name: "ESP32-C6(beta)", family: CHIP_FAMILY_ESP32C6 }, +}; + +// Commands supported by ESP8266 ROM bootloader +export const ESP_FLASH_BEGIN = 0x02; +export const ESP_FLASH_DATA = 0x03; +export const ESP_FLASH_END = 0x04; +export const ESP_MEM_BEGIN = 0x05; +export const ESP_MEM_END = 0x06; +export const ESP_MEM_DATA = 0x07; +export const ESP_SYNC = 0x08; +export const ESP_WRITE_REG = 0x09; +export const ESP_READ_REG = 0x0a; + +export const ESP_ERASE_FLASH = 0xd0; +export const ESP_ERASE_REGION = 0xd1; + +export const ESP_SPI_SET_PARAMS = 0x0b; +export const ESP_SPI_ATTACH = 0x0d; +export const ESP_CHANGE_BAUDRATE = 0x0f; +export const ESP_SPI_FLASH_MD5 = 0x13; +export const ESP_CHECKSUM_MAGIC = 0xef; +export const ESP_FLASH_DEFL_BEGIN = 0x10; +export const ESP_FLASH_DEFL_DATA = 0x11; +export const ESP_FLASH_DEFL_END = 0x12; + +export const ROM_INVALID_RECV_MSG = 0x05; + +export const USB_RAM_BLOCK = 0x800; +export const ESP_RAM_BLOCK = 0x1800; + +// Timeouts +export const DEFAULT_TIMEOUT = 3000; +export const CHIP_ERASE_TIMEOUT = 600000; // timeout for full chip erase in ms +export const MAX_TIMEOUT = CHIP_ERASE_TIMEOUT * 2; // longest any command can run in ms +export const SYNC_TIMEOUT = 100; // timeout for syncing with bootloader in ms +export const ERASE_REGION_TIMEOUT_PER_MB = 30000; // timeout (per megabyte) for erasing a region in ms +export const MEM_END_ROM_TIMEOUT = 500; + +/** + * @name timeoutPerMb + * Scales timeouts which are size-specific + */ +export const timeoutPerMb = (secondsPerMb: number, sizeBytes: number) => { + let result = Math.floor(secondsPerMb * (sizeBytes / 0x1e6)); + if (result < DEFAULT_TIMEOUT) { + return DEFAULT_TIMEOUT; + } + return result; +}; + +export const getSpiFlashAddresses = ( + chipFamily: ChipFamily +): SpiFlashAddresses => { + switch (chipFamily) { + case CHIP_FAMILY_ESP32: + return { + regBase: ESP32_SPI_REG_BASE, + baseFuse: ESP32_BASEFUSEADDR, + macFuse: ESP32_MACFUSEADDR, + usrOffs: ESP32_SPI_USR_OFFS, + usr1Offs: ESP32_SPI_USR1_OFFS, + usr2Offs: ESP32_SPI_USR2_OFFS, + mosiDlenOffs: ESP32_SPI_MOSI_DLEN_OFFS, + misoDlenOffs: ESP32_SPI_MISO_DLEN_OFFS, + w0Offs: ESP32_SPI_W0_OFFS, + uartDateReg: ESP32_UART_DATE_REG_ADDR, + flashOffs: ESP32_BOOTLOADER_FLASH_OFFSET, + }; + case CHIP_FAMILY_ESP32S2: + return { + regBase: ESP32S2_SPI_REG_BASE, + baseFuse: ESP32S2_BASEFUSEADDR, + macFuse: ESP32S2_MACFUSEADDR, + usrOffs: ESP32S2_SPI_USR_OFFS, + usr1Offs: ESP32S2_SPI_USR1_OFFS, + usr2Offs: ESP32S2_SPI_USR2_OFFS, + mosiDlenOffs: ESP32S2_SPI_MOSI_DLEN_OFFS, + misoDlenOffs: ESP32S2_SPI_MISO_DLEN_OFFS, + w0Offs: ESP32S2_SPI_W0_OFFS, + uartDateReg: ESP32S2_UART_DATE_REG_ADDR, + flashOffs: ESP32S2_BOOTLOADER_FLASH_OFFSET, + }; + case CHIP_FAMILY_ESP32S3: + return { + regBase: ESP32S3_SPI_REG_BASE, + usrOffs: ESP32S3_SPI_USR_OFFS, + baseFuse: ESP32S3_BASEFUSEADDR, + macFuse: ESP32S3_MACFUSEADDR, + usr1Offs: ESP32S3_SPI_USR1_OFFS, + usr2Offs: ESP32S3_SPI_USR2_OFFS, + mosiDlenOffs: ESP32S3_SPI_MOSI_DLEN_OFFS, + misoDlenOffs: ESP32S3_SPI_MISO_DLEN_OFFS, + w0Offs: ESP32S3_SPI_W0_OFFS, + uartDateReg: ESP32S3_UART_DATE_REG_ADDR, + flashOffs: ESP32S3_BOOTLOADER_FLASH_OFFSET, + }; + case CHIP_FAMILY_ESP8266: + return { + regBase: ESP8266_SPI_REG_BASE, + usrOffs: ESP8266_SPI_USR_OFFS, + baseFuse: ESP8266_BASEFUSEADDR, + macFuse: ESP8266_MACFUSEADDR, + usr1Offs: ESP8266_SPI_USR1_OFFS, + usr2Offs: ESP8266_SPI_USR2_OFFS, + mosiDlenOffs: ESP8266_SPI_MOSI_DLEN_OFFS, + misoDlenOffs: ESP8266_SPI_MISO_DLEN_OFFS, + w0Offs: ESP8266_SPI_W0_OFFS, + uartDateReg: ESP8266_UART_DATE_REG_ADDR, + flashOffs: ESP8266_BOOTLOADER_FLASH_OFFSET, + }; + case CHIP_FAMILY_ESP32C3: + return { + regBase: ESP32C3_SPI_REG_BASE, + baseFuse: ESP32C3_BASEFUSEADDR, + macFuse: ESP32C3_MACFUSEADDR, + usrOffs: ESP32C3_SPI_USR_OFFS, + usr1Offs: ESP32C3_SPI_USR1_OFFS, + usr2Offs: ESP32C3_SPI_USR2_OFFS, + mosiDlenOffs: ESP32C3_SPI_MOSI_DLEN_OFFS, + misoDlenOffs: ESP32C3_SPI_MISO_DLEN_OFFS, + w0Offs: ESP32C3_SPI_W0_OFFS, + uartDateReg: ESP32C3_UART_DATE_REG_ADDR, + flashOffs: ESP32C3_BOOTLOADER_FLASH_OFFSET, + }; + default: + return { + regBase: -1, + baseFuse: -1, + macFuse: -1, + usrOffs: -1, + usr1Offs: -1, + usr2Offs: -1, + mosiDlenOffs: -1, + misoDlenOffs: -1, + w0Offs: -1, + uartDateReg: -1, + flashOffs: -1, + }; + } +}; + +export class SlipReadError extends Error { + constructor(message: string) { + super(message); + this.name = "SlipReadError"; + } +} diff --git a/src/esp_loader.ts b/src/esp_loader.ts new file mode 100644 index 0000000..58d0f8d --- /dev/null +++ b/src/esp_loader.ts @@ -0,0 +1,1153 @@ +import { + CHIP_FAMILY_ESP32, + CHIP_FAMILY_ESP32S2, + CHIP_FAMILY_ESP32S3, + CHIP_FAMILY_ESP32C3, + CHIP_FAMILY_ESP8266, + MAX_TIMEOUT, + Logger, + DEFAULT_TIMEOUT, + ERASE_REGION_TIMEOUT_PER_MB, + ESP_CHANGE_BAUDRATE, + ESP_CHECKSUM_MAGIC, + ESP_FLASH_BEGIN, + ESP_FLASH_DATA, + ESP_FLASH_END, + ESP_MEM_BEGIN, + ESP_MEM_DATA, + ESP_MEM_END, + ESP_READ_REG, + ESP_WRITE_REG, + ESP_SPI_ATTACH, + ESP_SYNC, + FLASH_SECTOR_SIZE, + FLASH_WRITE_SIZE, + STUB_FLASH_WRITE_SIZE, + MEM_END_ROM_TIMEOUT, + ROM_INVALID_RECV_MSG, + SYNC_PACKET, + SYNC_TIMEOUT, + USB_RAM_BLOCK, + ChipFamily, + ESP_ERASE_FLASH, + CHIP_ERASE_TIMEOUT, + timeoutPerMb, + ESP_ROM_BAUD, + ESP_FLASH_DEFL_BEGIN, + ESP_FLASH_DEFL_DATA, + ESP_FLASH_DEFL_END, + getSpiFlashAddresses, + SpiFlashAddresses, + DETECTED_FLASH_SIZES, + CHIP_DETECT_MAGIC_REG_ADDR, + CHIP_DETECT_MAGIC_VALUES, + SlipReadError, +} from "./const"; +import { getStubCode } from "./stubs"; +import { hexFormatter, sleep, slipEncode, toHex } from "./util"; +// @ts-ignore +import { deflate } from "pako/dist/pako.esm.mjs"; +import { pack, unpack } from "./struct"; + +export class ESPLoader extends EventTarget { + chipFamily!: ChipFamily; + chipName: string | null = null; + _efuses = new Array(4).fill(0); + _flashsize = 4 * 1024 * 1024; + debug = false; + IS_STUB = false; + connected = true; + flashSize: string | null = null; + + __inputBuffer?: number[]; + private _reader?: ReadableStreamDefaultReader; + + constructor( + public port: SerialPort, + public logger: Logger, + private _parent?: ESPLoader + ) { + super(); + } + + private get _inputBuffer(): number[] { + return this._parent ? this._parent._inputBuffer : this.__inputBuffer!; + } + + async initialize() { + await this.hardReset(true); + + if (!this._parent) { + this.__inputBuffer = []; + // Don't await this promise so it doesn't block rest of method. + this.readLoop(); + } + await this.sync(); + + // Determine chip family and name + let chipMagicValue = await this.readRegister(CHIP_DETECT_MAGIC_REG_ADDR); + let chip = CHIP_DETECT_MAGIC_VALUES[chipMagicValue >>> 0]; + if (chip === undefined) { + throw new Error( + `Unknown Chip: Hex: ${toHex( + chipMagicValue >>> 0, + 8 + ).toLowerCase()} Number: ${chipMagicValue}` + ); + } + this.chipName = chip.name; + this.chipFamily = chip.family; + + // Read the OTP data for this chip and store into this.efuses array + let FlAddr = getSpiFlashAddresses(this.getChipFamily()); + let AddrMAC = FlAddr.macFuse; + for (let i = 0; i < 4; i++) { + this._efuses[i] = await this.readRegister(AddrMAC + 4 * i); + } + this.logger.log(`Chip type ${this.chipName}`); + //this.logger.log("FLASHID"); + } + + /** + * @name readLoop + * Reads data from the input stream and places it in the inputBuffer + */ + async readLoop() { + if (this.debug) { + this.logger.debug("Starting read loop"); + } + + this._reader = this.port.readable!.getReader(); + + try { + while (true) { + const { value, done } = await this._reader.read(); + if (done) { + this._reader.releaseLock(); + break; + } + if (!value || value.length === 0) { + continue; + } + this._inputBuffer.push(...Array.from(value)); + } + } catch (err) { + console.error("Read loop got disconnected"); + } + // Disconnected! + this.connected = false; + this.dispatchEvent(new Event("disconnect")); + this.logger.debug("Finished read loop"); + } + + async hardReset(bootloader = false) { + this.logger.log("Try hard reset."); + await this.port.setSignals({ + dataTerminalReady: false, + requestToSend: true, + }); + await this.port.setSignals({ + dataTerminalReady: bootloader, + requestToSend: false, + }); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + /** + * @name macAddr + * The MAC address burned into the OTP memory of the ESP chip + */ + macAddr() { + let macAddr = new Array(6).fill(0); + let mac0 = this._efuses[0]; + let mac1 = this._efuses[1]; + let mac2 = this._efuses[2]; + let mac3 = this._efuses[3]; + let oui; + if (this.chipFamily == CHIP_FAMILY_ESP8266) { + if (mac3 != 0) { + oui = [(mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff]; + } else if (((mac1 >> 16) & 0xff) == 0) { + oui = [0x18, 0xfe, 0x34]; + } else if (((mac1 >> 16) & 0xff) == 1) { + oui = [0xac, 0xd0, 0x74]; + } else { + throw new Error("Couldnt determine OUI"); + } + + macAddr[0] = oui[0]; + macAddr[1] = oui[1]; + macAddr[2] = oui[2]; + macAddr[3] = (mac1 >> 8) & 0xff; + macAddr[4] = mac1 & 0xff; + macAddr[5] = (mac0 >> 24) & 0xff; + } else if (this.chipFamily == CHIP_FAMILY_ESP32) { + macAddr[0] = (mac2 >> 8) & 0xff; + macAddr[1] = mac2 & 0xff; + macAddr[2] = (mac1 >> 24) & 0xff; + macAddr[3] = (mac1 >> 16) & 0xff; + macAddr[4] = (mac1 >> 8) & 0xff; + macAddr[5] = mac1 & 0xff; + } else if ( + this.chipFamily == CHIP_FAMILY_ESP32S2 || + this.chipFamily == CHIP_FAMILY_ESP32S3 || + this.chipFamily == CHIP_FAMILY_ESP32C3 + ) { + macAddr[0] = (mac1 >> 8) & 0xff; + macAddr[1] = mac1 & 0xff; + macAddr[2] = (mac0 >> 24) & 0xff; + macAddr[3] = (mac0 >> 16) & 0xff; + macAddr[4] = (mac0 >> 8) & 0xff; + macAddr[5] = mac0 & 0xff; + } else { + throw new Error("Unknown chip family"); + } + return macAddr; + } + + async readRegister(reg: number) { + if (this.debug) { + this.logger.debug("Reading from Register " + toHex(reg, 8)); + } + let packet = pack(" { + timeout = Math.min(timeout, MAX_TIMEOUT); + await this.sendCommand(opcode, buffer, checksum); + let [value, data] = await this.getResponse(opcode, timeout); + + if (data === null) { + throw new Error("Didn't get enough status bytes"); + } + + let statusLen = 0; + + if (this.IS_STUB || this.chipFamily == CHIP_FAMILY_ESP8266) { + statusLen = 2; + } else if ( + [ + CHIP_FAMILY_ESP32, + CHIP_FAMILY_ESP32S2, + CHIP_FAMILY_ESP32S3, + CHIP_FAMILY_ESP32C3, + ].includes(this.chipFamily) + ) { + statusLen = 4; + } else { + if ([2, 4].includes(data.length)) { + statusLen = data.length; + } + } + + if (data.length < statusLen) { + throw new Error("Didn't get enough status bytes"); + } + let status = data.slice(-statusLen, data.length); + data = data.slice(0, -statusLen); + if (this.debug) { + this.logger.debug("status", status); + this.logger.debug("value", value); + this.logger.debug("data", data); + } + if (status[0] == 1) { + if (status[1] == ROM_INVALID_RECV_MSG) { + throw new Error("Invalid (unsupported) command " + toHex(opcode)); + } else { + throw new Error("Command failure error code " + toHex(status[1])); + } + } + + return [value, data]; + } + + /** + * @name sendCommand + * Send a slip-encoded, checksummed command over the UART, + * does not check response + */ + async sendCommand(opcode: number, buffer: number[], checksum = 0) { + let packet = slipEncode([ + ...pack(" { + let partialPacket: number[] | null = null; + let inEscape = false; + let readBytes: number[] = []; + while (true) { + let stamp = Date.now(); + readBytes = []; + while (Date.now() - stamp < timeout) { + if (this._inputBuffer.length > 0) { + readBytes.push(this._inputBuffer.shift()!); + break; + } else { + await sleep(10); + } + } + if (readBytes.length == 0) { + let waitingFor = partialPacket === null ? "header" : "content"; + throw new SlipReadError("Timed out waiting for packet " + waitingFor); + } + if (this.debug) + this.logger.debug( + "Read " + readBytes.length + " bytes: " + hexFormatter(readBytes) + ); + for (let b of readBytes) { + if (partialPacket === null) { + // waiting for packet header + if (b == 0xc0) { + partialPacket = []; + } else { + if (this.debug) { + this.logger.debug( + "Read invalid data: " + hexFormatter(readBytes) + ); + this.logger.debug( + "Remaining data in serial buffer: " + + hexFormatter(this._inputBuffer) + ); + } + throw new SlipReadError( + "Invalid head of packet (" + toHex(b) + ")" + ); + } + } else if (inEscape) { + // part-way through escape sequence + inEscape = false; + if (b == 0xdc) { + partialPacket.push(0xc0); + } else if (b == 0xdd) { + partialPacket.push(0xdb); + } else { + if (this.debug) { + this.logger.debug( + "Read invalid data: " + hexFormatter(readBytes) + ); + this.logger.debug( + "Remaining data in serial buffer: " + + hexFormatter(this._inputBuffer) + ); + } + throw new SlipReadError( + "Invalid SLIP escape (0xdb, " + toHex(b) + ")" + ); + } + } else if (b == 0xdb) { + // start of escape sequence + inEscape = true; + } else if (b == 0xc0) { + // end of packet + if (this.debug) + this.logger.debug( + "Received full packet: " + hexFormatter(partialPacket) + ); + return partialPacket; + } else { + // normal byte in packet + partialPacket.push(b); + } + } + } + throw new SlipReadError("Invalid state"); + } + + /** + * @name getResponse + * Read response data and decodes the slip packet, then parses + * out the value/data and returns as a tuple of (value, data) where + * each is a list of bytes + */ + async getResponse( + opcode: number, + timeout = DEFAULT_TIMEOUT + ): Promise<[number, number[]]> { + for (let i = 0; i < 100; i++) { + const packet = await this.readPacket(timeout); + + if (packet.length < 8) { + continue; + } + + const [resp, opRet, _lenRet, val] = unpack(" 1 && data[0] == 0 && data[1] == 0) { + return true; + } + } catch (err) { + // If read packet fails. + } + } + return false; + } + + /** + * @name getFlashWriteSize + * Get the Flash write size based on the chip + */ + getFlashWriteSize() { + if (this.IS_STUB) { + return STUB_FLASH_WRITE_SIZE; + } + return FLASH_WRITE_SIZE; + } + + /** + * @name flashData + * Program a full, uncompressed binary file into SPI Flash at + * a given offset. If an ESP32 and md5 string is passed in, will also + * verify memory. ESP8266 does not have checksum memory verification in + * ROM + */ + async flashData( + binaryData: ArrayBuffer, + updateProgress: (bytesWritten: number, totalBytes: number) => void, + offset = 0, + compress = false + ) { + if (binaryData.byteLength >= 8) { + // unpack the (potential) image header + var header = Array.from(new Uint8Array(binaryData, 0, 4)); + let headerMagic = header[0]; + let headerFlashMode = header[2]; + let headerFlashSizeFreq = header[3]; + + this.logger.log( + `Image header, Magic=${toHex(headerMagic)}, FlashMode=${toHex( + headerFlashMode + )}, FlashSizeFreq=${toHex(headerFlashSizeFreq)}` + ); + } + + let uncompressedFilesize = binaryData.byteLength; + let compressedFilesize = 0; + + let dataToFlash; + let timeout = DEFAULT_TIMEOUT; + + if (compress) { + dataToFlash = deflate(new Uint8Array(binaryData), { + level: 9, + }).buffer; + compressedFilesize = dataToFlash.byteLength; + this.logger.log( + `Writing data with filesize: ${uncompressedFilesize}. Compressed Size: ${compressedFilesize}` + ); + timeout = await this.flashDeflBegin( + uncompressedFilesize, + compressedFilesize, + offset + ); + } else { + this.logger.log(`Writing data with filesize: ${uncompressedFilesize}`); + dataToFlash = binaryData; + await this.flashBegin(uncompressedFilesize, offset); + } + + let block = []; + let seq = 0; + let written = 0; + let position = 0; + let stamp = Date.now(); + let flashWriteSize = this.getFlashWriteSize(); + + let filesize = compress ? compressedFilesize : uncompressedFilesize; + + while (filesize - position > 0) { + if (this.debug) { + this.logger.log( + `Writing at ${toHex(offset + seq * flashWriteSize, 8)} ` + ); + } + if (filesize - position >= flashWriteSize) { + block = Array.from( + new Uint8Array(dataToFlash, position, flashWriteSize) + ); + } else { + // Pad the last block only if we are sending uncompressed data. + block = Array.from( + new Uint8Array(dataToFlash, position, filesize - position) + ); + if (!compress) { + block = block.concat( + new Array(flashWriteSize - block.length).fill(0xff) + ); + } + } + if (compress) { + await this.flashDeflBlock(block, seq, timeout); + } else { + await this.flashBlock(block, seq); + } + seq += 1; + // If using compression we update the progress with the proportional size of the block taking into account the compression ratio. + // This way we report progress on the uncompressed size + written += compress + ? Math.round((block.length * uncompressedFilesize) / compressedFilesize) + : block.length; + position += flashWriteSize; + updateProgress( + Math.min(written, uncompressedFilesize), + uncompressedFilesize + ); + } + this.logger.log( + "Took " + (Date.now() - stamp) + "ms to write " + filesize + " bytes" + ); + + // Only send flashF finish if running the stub because ir causes the ROM to exit and run user code + if (this.IS_STUB) { + await this.flashBegin(0, 0); + if (compress) { + await this.flashDeflFinish(); + } else { + await this.flashFinish(); + } + } + } + + /** + * @name flashBlock + * Send one block of data to program into SPI Flash memory + */ + async flashBlock(data: number[], seq: number, timeout = DEFAULT_TIMEOUT) { + await this.checkCommand( + ESP_FLASH_DATA, + pack(" 0) { + // add a dummy write to a date register as an excuse to have a delay + buffer.concat( + pack( + " 0) { + await this.writeRegister(SPI_MOSI_DLEN_REG, mosiBits - 1); + } + if (misoBits > 0) { + await this.writeRegister(SPI_MISO_DLEN_REG, misoBits - 1); + } + } else { + let SPI_DATA_LEN_REG = spiAddresses.regBase + spiAddresses.usr1Offs; + let SPI_MOSI_BITLEN_S = 17; + let SPI_MISO_BITLEN_S = 8; + let mosiMask = mosiBits == 0 ? 0 : mosiBits - 1; + let misoMask = misoBits == 0 ? 0 : misoBits - 1; + let value = + (misoMask << SPI_MISO_BITLEN_S) | (mosiMask << SPI_MOSI_BITLEN_S); + await this.writeRegister(SPI_DATA_LEN_REG, value); + } + } + async waitDone(spiCmdReg: number, spiCmdUsr: number) { + for (let i = 0; i < 10; i++) { + let cmdValue = await this.readRegister(spiCmdReg); + if ((cmdValue & spiCmdUsr) == 0) { + return; + } + } + throw Error("SPI command did not complete in time"); + } + + async runSpiFlashCommand( + spiflashCommand: number, + data: number[], + readBits = 0 + ) { + // Run an arbitrary SPI flash command. + + // This function uses the "USR_COMMAND" functionality in the ESP + // SPI hardware, rather than the precanned commands supported by + // hardware. So the value of spiflash_command is an actual command + // byte, sent over the wire. + + // After writing command byte, writes 'data' to MOSI and then + // reads back 'read_bits' of reply on MISO. Result is a number. + + // SPI_USR register flags + let SPI_USR_COMMAND = 1 << 31; + let SPI_USR_MISO = 1 << 28; + let SPI_USR_MOSI = 1 << 27; + + // SPI registers, base address differs ESP32* vs 8266 + let spiAddresses = getSpiFlashAddresses(this.getChipFamily()); + let base = spiAddresses.regBase; + let SPI_CMD_REG = base; + let SPI_USR_REG = base + spiAddresses.usrOffs; + let SPI_USR2_REG = base + spiAddresses.usr2Offs; + let SPI_W0_REG = base + spiAddresses.w0Offs; + + // SPI peripheral "command" bitmasks for SPI_CMD_REG + let SPI_CMD_USR = 1 << 18; + + // shift values + let SPI_USR2_COMMAND_LEN_SHIFT = 28; + + if (readBits > 32) { + throw new Error( + "Reading more than 32 bits back from a SPI flash operation is unsupported" + ); + } + if (data.length > 64) { + throw new Error( + "Writing more than 64 bytes of data with one SPI command is unsupported" + ); + } + + let dataBits = data.length * 8; + let oldSpiUsr = await this.readRegister(SPI_USR_REG); + let oldSpiUsr2 = await this.readRegister(SPI_USR2_REG); + + let flags = SPI_USR_COMMAND; + + if (readBits > 0) { + flags |= SPI_USR_MISO; + } + if (dataBits > 0) { + flags |= SPI_USR_MOSI; + } + + await this.setDataLengths(spiAddresses, dataBits, readBits); + + await this.writeRegister(SPI_USR_REG, flags); + await this.writeRegister( + SPI_USR2_REG, + (7 << SPI_USR2_COMMAND_LEN_SHIFT) | spiflashCommand + ); + if (dataBits == 0) { + await this.writeRegister(SPI_W0_REG, 0); // clear data register before we read it + } else { + data.concat(new Array(data.length % 4).fill(0x00)); // pad to 32-bit multiple + + let words = unpack("I".repeat(Math.floor(data.length / 4)), data); + let nextReg = SPI_W0_REG; + + this.logger.debug(`Words Length: ${words.length}`); + + for (const word of words) { + this.logger.debug( + `Writing word ${toHex(word)} to register offset ${toHex(nextReg)}` + ); + await this.writeRegister(nextReg, word); + nextReg += 4; + } + } + await this.writeRegister(SPI_CMD_REG, SPI_CMD_USR); + await this.waitDone(SPI_CMD_REG, SPI_CMD_USR); + + let status = await this.readRegister(SPI_W0_REG); + // restore some SPI controller registers + await this.writeRegister(SPI_USR_REG, oldSpiUsr); + await this.writeRegister(SPI_USR2_REG, oldSpiUsr2); + return status; + } + async detectFlashSize() { + this.logger.log("Detecting Flash Size"); + + let flashId = await this.flashId(); + let manufacturer = flashId & 0xff; + let flashIdLowbyte = (flashId >> 16) & 0xff; + + this.logger.log(`FlashId: ${toHex(flashId)}`); + this.logger.log(`Flash Manufacturer: ${manufacturer.toString(16)}`); + this.logger.log( + `Flash Device: ${((flashId >> 8) & 0xff).toString( + 16 + )}${flashIdLowbyte.toString(16)}` + ); + + this.flashSize = DETECTED_FLASH_SIZES[flashIdLowbyte]; + this.logger.log(`Auto-detected Flash size: ${this.flashSize}`); + } + + /** + * @name getEraseSize + * Calculate an erase size given a specific size in bytes. + * Provides a workaround for the bootloader erase bug on ESP8266. + */ + getEraseSize(offset: number, size: number) { + let sectorsPerBlock = 16; + let sectorSize = FLASH_SECTOR_SIZE; + let numSectors = Math.floor((size + sectorSize - 1) / sectorSize); + let startSector = Math.floor(offset / sectorSize); + + let headSectors = sectorsPerBlock - (startSector % sectorsPerBlock); + if (numSectors < headSectors) { + headSectors = numSectors; + } + + if (numSectors < 2 * headSectors) { + return Math.floor(((numSectors + 1) / 2) * sectorSize); + } + + return (numSectors - headSectors) * sectorSize; + } + + /** + * @name memBegin (592) + * Start downloading an application image to RAM + */ + async memBegin( + size: number, + blocks: number, + blocksize: number, + offset: number + ) { + return await this.checkCommand( + ESP_MEM_BEGIN, + pack(" { + const stub = await getStubCode(this.chipFamily); + + // We're transferring over USB, right? + let ramBlock = USB_RAM_BLOCK; + + // Upload + this.logger.log("Uploading stub..."); + for (let field of ["text", "data"]) { + if (Object.keys(stub).includes(field)) { + let offset = stub[field + "_start"]; + let length = stub[field].length; + let blocks = Math.floor((length + ramBlock - 1) / ramBlock); + await this.memBegin(length, blocks, ramBlock, offset); + for (let seq of Array(blocks).keys()) { + let fromOffs = seq * ramBlock; + let toOffs = fromOffs + ramBlock; + if (toOffs > length) { + toOffs = length; + } + await this.memBlock(stub[field].slice(fromOffs, toOffs), seq); + } + } + } + this.logger.log("Running stub..."); + await this.memFinish(stub["entry"]); + + let pChar: string; + + const p = await this.readPacket(500); + pChar = String.fromCharCode(...p); + + if (pChar != "OHAI") { + throw new Error("Failed to start stub. Unexpected response: " + pChar); + } + this.logger.log("Stub is now running..."); + const espStubLoader = new EspStubLoader(this.port, this.logger, this); + + // Try to autodetect the flash size as soon as the stub is running. + await espStubLoader.detectFlashSize(); + + return espStubLoader; + } + + async writeToStream(data: number[]) { + const writer = this.port.writable!.getWriter(); + await writer.write(new Uint8Array(data)); + try { + writer.releaseLock(); + } catch (err) { + console.error("Ignoring release lock error", err); + } + } + + async disconnect() { + if (this._parent) { + await this._parent.disconnect(); + return; + } + await this.port.writable!.getWriter().close(); + await new Promise((resolve) => { + if (!this._reader) { + resolve(undefined); + } + this.addEventListener("disconnect", resolve, { once: true }); + this._reader!.cancel(); + }); + this.connected = false; + } +} + +class EspStubLoader extends ESPLoader { + /* + The Stubloader has commands that run on the uploaded Stub Code in RAM + rather than built in commands. + */ + IS_STUB = true; + + /** + * @name memBegin (592) + * Start downloading an application image to RAM + */ + async memBegin( + size: number, + blocks: number, + blocksize: number, + offset: number + ): Promise { + let stub = await getStubCode(this.chipFamily); + let load_start = offset; + let load_end = offset + size; + console.log(load_start, load_end); + console.log( + stub.data_start, + stub.data.length, + stub.text_start, + stub.text.length + ); + for (let [start, end] of [ + [stub.data_start, stub.data_start + stub.data.length], + [stub.text_start, stub.text_start + stub.text.length], + ]) { + if (load_start < end && load_end > start) { + throw new Error( + "Software loader is resident at " + + toHex(start, 8) + + "-" + + toHex(end, 8) + + ". " + + "Can't load binary at overlapping address range " + + toHex(load_start, 8) + + "-" + + toHex(load_end, 8) + + ". " + + "Try changing the binary loading address." + ); + } + } + } + + /** + * @name getEraseSize + * depending on flash chip model the erase may take this long (maybe longer!) + */ + async eraseFlash() { + await this.checkCommand(ESP_ERASE_FLASH, [], 0, CHIP_ERASE_TIMEOUT); + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..fb7768d --- /dev/null +++ b/src/index.ts @@ -0,0 +1,27 @@ +import { ESP_ROM_BAUD, Logger } from "./const"; +import { ESPLoader } from "./esp_loader"; + +export type { Logger } from "./const"; +export { ESPLoader } from "./esp_loader"; + +export { + CHIP_FAMILY_ESP32, + CHIP_FAMILY_ESP32S2, + CHIP_FAMILY_ESP32S3, + CHIP_FAMILY_ESP8266, + CHIP_FAMILY_ESP32C3, + CHIP_FAMILY_ESP32C6, + CHIP_FAMILY_ESP32H2, +} from "./const"; + +export const connect = async (logger: Logger) => { + // - Request a port and open a connection. + const port = await navigator.serial.requestPort(); + + logger.log("Connecting..."); + await port.open({ baudRate: ESP_ROM_BAUD }); + + logger.log("Connected successfully."); + + return new ESPLoader(port, logger); +}; diff --git a/src/struct.ts b/src/struct.ts new file mode 100644 index 0000000..ce7691a --- /dev/null +++ b/src/struct.ts @@ -0,0 +1,108 @@ +const lut = { + b: { u: DataView.prototype.getInt8, p: DataView.prototype.setInt8, bytes: 1 }, + B: { + u: DataView.prototype.getUint8, + p: DataView.prototype.setUint8, + bytes: 1, + }, + h: { + u: DataView.prototype.getInt16, + p: DataView.prototype.setInt16, + bytes: 2, + }, + H: { + u: DataView.prototype.getUint16, + p: DataView.prototype.setUint16, + bytes: 2, + }, + i: { + u: DataView.prototype.getInt32, + p: DataView.prototype.setInt32, + bytes: 4, + }, + I: { + u: DataView.prototype.getUint32, + p: DataView.prototype.setUint32, + bytes: 4, + }, + q: { + // @ts-ignore + u: DataView.prototype.getInt64, + // @ts-ignore + p: DataView.prototype.setInt64, + bytes: 8, + }, + Q: { + // @ts-ignore + u: DataView.prototype.getUint64, + // @ts-ignore + p: DataView.prototype.setUint64, + bytes: 8, + }, +}; + +export const pack = (format: string, ...data: number[]) => { + let pointer = 0; + if (format.replace(/[<>]/, "").length != data.length) { + throw "Pack format to Argument count mismatch"; + } + let bytes: number[] = []; + let littleEndian = true; + for (let i = 0; i < format.length; i++) { + if (format[i] == "<") { + littleEndian = true; + } else if (format[i] == ">") { + littleEndian = false; + } else { + pushBytes(format[i], data[pointer]); + pointer++; + } + } + + function pushBytes(formatChar: string, value: number) { + if (!(formatChar in lut)) { + throw "Unhandled character '" + formatChar + "' in pack format"; + } + let dataSize = lut[formatChar].bytes; + let view = new DataView(new ArrayBuffer(dataSize)); + let dataViewFn = lut[formatChar].p.bind(view); + dataViewFn(0, value, littleEndian); + for (let i = 0; i < dataSize; i++) { + bytes.push(view.getUint8(i)); + } + } + + return bytes; +}; + +export const unpack = (format: string, bytes: number[]) => { + let pointer = 0; + let data: number[] = []; + let littleEndian = true; + + for (let c of format) { + if (c == "<") { + littleEndian = true; + } else if (c == ">") { + littleEndian = false; + } else { + pushData(c); + } + } + + function pushData(formatChar: string) { + if (!(formatChar in lut)) { + throw "Unhandled character '" + formatChar + "' in unpack format"; + } + let dataSize = lut[formatChar].bytes; + let view = new DataView(new ArrayBuffer(dataSize)); + for (let i = 0; i < dataSize; i++) { + view.setUint8(i, bytes[pointer + i] & 0xff); + } + let dataViewFn = lut[formatChar].u.bind(view); + data.push(dataViewFn(0, littleEndian)); + pointer += dataSize; + } + + return data; +}; diff --git a/stubs/esp32.json b/src/stubs/esp32.json similarity index 100% rename from stubs/esp32.json rename to src/stubs/esp32.json diff --git a/src/stubs/esp32c2.json b/src/stubs/esp32c2.json new file mode 100644 index 0000000..1fb35d2 --- /dev/null +++ b/src/stubs/esp32c2.json @@ -0,0 +1 @@ +{"text": "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", "text_start": 1077411840, "entry": 1077413328, "data": "DEDKPw==", "data_start": 1070295976} \ No newline at end of file diff --git a/src/stubs/esp32c3.json b/src/stubs/esp32c3.json new file mode 100644 index 0000000..47d2a0e --- /dev/null +++ b/src/stubs/esp32c3.json @@ -0,0 +1 @@ +{"text": "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", "text_start": 1077411840, "entry": 1077413488, "data": "DEDIPw==", "data_start": 1070164904} \ No newline at end of file diff --git a/src/stubs/esp32h2.json b/src/stubs/esp32h2.json new file mode 100644 index 0000000..70fd052 --- /dev/null +++ b/src/stubs/esp32h2.json @@ -0,0 +1 @@ +{"text": "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", "text_start": 1077411840, "entry": 1077413328, "data": "DEDIPw==", "data_start": 1070164904} \ No newline at end of file diff --git a/stubs/esp32s2.json b/src/stubs/esp32s2.json similarity index 100% rename from stubs/esp32s2.json rename to src/stubs/esp32s2.json diff --git a/src/stubs/esp32s3.json b/src/stubs/esp32s3.json new file mode 100644 index 0000000..739861f --- /dev/null +++ b/src/stubs/esp32s3.json @@ -0,0 +1 @@ +{"text": "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", "text_start": 1077379072, "entry": 1077381496, "data": "CADKPw==", "data_start": 1070279668} \ No newline at end of file diff --git a/stubs/esp8266.json b/src/stubs/esp8266.json similarity index 100% rename from stubs/esp8266.json rename to src/stubs/esp8266.json diff --git a/src/stubs/index.ts b/src/stubs/index.ts new file mode 100644 index 0000000..f5f325a --- /dev/null +++ b/src/stubs/index.ts @@ -0,0 +1,48 @@ +import { + ChipFamily, + CHIP_FAMILY_ESP32, + CHIP_FAMILY_ESP32S2, + CHIP_FAMILY_ESP32S3, + CHIP_FAMILY_ESP8266, + CHIP_FAMILY_ESP32C3 +} from "../const"; +import { toByteArray } from "../util"; + +interface LoadedStub { + text: string; + data: string; + text_start: number; + entry: number; + data_start: number; +} + +interface Stub { + text: number[]; + data: number[]; + text_start: number; + entry: number; + data_start: number; +} + +export const getStubCode = async (chipFamily: ChipFamily): Promise => { + let stubcode!: LoadedStub; + + if (chipFamily == CHIP_FAMILY_ESP32) { + stubcode = await import("./esp32.json"); + } else if (chipFamily == CHIP_FAMILY_ESP32S2) { + stubcode = await import("./esp32s2.json"); + } else if (chipFamily == CHIP_FAMILY_ESP32S3) { + stubcode = await import("./esp32s3.json"); + } else if (chipFamily == CHIP_FAMILY_ESP8266) { + stubcode = await import("./esp8266.json"); + } else if (chipFamily == CHIP_FAMILY_ESP32C3) { + stubcode = await import("./esp32c3.json"); + } + + // Base64 decode the text and data + return { + ...stubcode, + text: toByteArray(atob(stubcode.text)), + data: toByteArray(atob(stubcode.data)), + }; +}; diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..0640300 --- /dev/null +++ b/src/util.ts @@ -0,0 +1,49 @@ +/** + * @name slipEncode + * Take an array buffer and return back a new array where + * 0xdb is replaced with 0xdb 0xdd and 0xc0 is replaced with 0xdb 0xdc + */ +export const slipEncode = (buffer: number[]): number[] => { + let encoded = [0xc0]; + for (let byte of buffer) { + if (byte == 0xdb) { + encoded = encoded.concat([0xdb, 0xdd]); + } else if (byte == 0xc0) { + encoded = encoded.concat([0xdb, 0xdc]); + } else { + encoded.push(byte); + } + } + encoded.push(0xc0); + return encoded; +}; + +/** + * @name toByteArray + * Convert a string to a byte array + */ +export const toByteArray = (str: string): number[] => { + let byteArray: number[] = []; + for (let i = 0; i < str.length; i++) { + let charcode = str.charCodeAt(i); + if (charcode <= 0xff) { + byteArray.push(charcode); + } + } + return byteArray; +}; + +export const hexFormatter = (bytes: number[]) => + "[" + bytes.map((value) => toHex(value)).join(", ") + "]"; + +export const toHex = (value: number, size = 2) => { + let hex = value.toString(16).toUpperCase(); + if (hex.startsWith("-")) { + return "-0x" + hex.substring(1).padStart(size, "0"); + } else { + return "0x" + hex.padStart(size, "0"); + } +}; + +export const sleep = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f62773f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "lib": ["es2019", "dom"], + "target": "es2019", + "module": "es2020", + "moduleResolution": "node", + "resolveJsonModule": true, + "outDir": "dist", + "declaration": true, + "experimentalDecorators": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "suppressImplicitAnyIndexErrors": true, + "importHelpers": true + }, + "include": ["src/*"] +}