npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

espcdcflash

v0.1.57

Published

Streamlined ESP32-S3 USB CDC WebSerial flasher

Downloads

109

Readme

espcdcflash

espcdcflash is an opiniated WebSerial flasher for ESP32-S3 firmware in the browser.
It is designed for USB CDC flashing flows with a small, stateless API: isSupported(), inspect(input?), and flash(input, options?).

Requirements

  • Chromium browser with WebSerial support (Chrome/Edge).
  • Secure context (https:// or http://localhost).
  • ESP32-S3 target and compatible firmware image(s).

Install

npm install espcdcflash
import { flash, inspect, isSupported } from "espcdcflash";

Quick Start (minimal)

import { flash } from "espcdcflash";

// No port passed: flash() will open the web serial picker.
// Plain file input defaults to memory offset 0x10000.
const report = await flash(file);

if (!report.ok) {
  console.error(report.error);
} else {
  console.log("Flash successful.");
}

Passing a plain firmware file (File/Uint8Array) to flash assumes the app start address 0x10000.
Use an Image object ({ file, address }) to set a different address.

Optional explicit browser/context guard:

import { isSupported } from "espcdcflash";

if (!isSupported()) {
  throw new Error("WebSerial is unavailable in this browser/context.");
}

Preflight Validation with inspect

Use inspect to validate one or more firmware inputs before flashing.

import { inspect } from "espcdcflash";

const report = await inspect(file);

if (!report.ok) {
  console.error(report.error);
}

for (const item of report.items) {
  console.log(item.index, item.ok, item.address, item.chipId, item.error);
}

Notes:

  • inspect() with no argument returns a non-OK report.
  • inspect is preflight sanity checking, not a guarantee for all target-specific runtime conditions.

Progressive Usage Examples

A) File input (defaults to memory offset 0x10000)

const report = await flash(file);

B) Explicit single image object

const report = await flash({
  file,
  address: 0x10000,
});

C) Multiple images

const report = await flash([
  { file: bootloaderBin, address: 0x0000 },
  { file: partitionsBin, address: 0x8000 },
  { file: appBin, address: 0x10000 },
]);

For image arrays, ranges must not overlap. Range checks use 4-byte padded image size (same as flash write behavior).

// Valid adjacency (no overlap): first image length 33 -> padded to 36 bytes.
await flash([
  { file: first33Bytes, address: 0x10000 }, // [0x10000, 0x10024)
  { file: second, address: 0x10024 },       // starts exactly at previous end
]);

// Invalid overlap:
await flash([
  { file: first33Bytes, address: 0x10000 }, // [0x10000, 0x10024)
  { file: second, address: 0x10020 },       // overlaps previous range
]);

D) Full options with events

const report = await flash(file, {
  port, // optional
  baud: 115200,
  touch1200: true,
  onEvent: (event) => {
    if (event.type === "message") {
      console.log("[message]", event.message);
      return;
    }
    if (event.type === "progress") {
      console.log("[progress]", event.progress.stage, event.progress.percent, event.progress.message);
      return;
    }
    console.log("[report]", event.report);
  },
});

E) Cancellation with AbortController

const abort = new AbortController();

const run = flash(file, {
  signal: abort.signal,
});

// Later:
abort.abort();

const report = await run;
console.log(report);

Full API Reference

export type Stage =
  | "touch"
  | "rebind"
  | "connect"
  | "write"
  | "reset"
  | "done"
  | "error";

export type Firmware = File | Uint8Array;

export interface Image {
  file: Firmware;
  address?: number;
}

export type Input = Firmware | Image | Image[];

export interface Progress {
  stage: Stage;
  percent: number;
  message: string;
}

export interface Report {
  ok: boolean;
  error?: string;
}

export interface InspectItem {
  index: number;
  ok: boolean;
  size: number;
  address: number;
  chipId?: number;
  error?: string;
}

export interface InspectReport {
  ok: boolean;
  items: InspectItem[];
  error?: string;
}

export type Event =
  | { type: "message"; message: string }
  | { type: "progress"; progress: Progress }
  | { type: "report"; report: Report };

export interface Options {
  port?: SerialPort;
  baud?: number;
  touch1200?: boolean;
  signal?: AbortSignal;
  onEvent?: (event: Event) => void;
}

export function isSupported(): boolean;
export function inspect(input?: Input): Promise<InspectReport>;
export function flash(input: Input, options?: Options): Promise<Report>;

Options & Behavior

| Option | Type | Default | Behavior | |---|---|---|---| | port | SerialPort | none | If omitted, flash() opens the WebSerial chooser. | | baud | number | 115200 | Positive integer UART baud used for flashing transport. | | touch1200 | boolean | true | Runs 1200-baud touch reset flow before connect. | | signal | AbortSignal | none | Cooperative cancellation for the current run. | | onEvent | (event: Event) => void | none | Receives message/progress/report events during a run. |

Address precedence:

  1. image.address (per item in Input)
  2. default 0x10000 (app start address assumption for plain file input)

For image arrays:

  • Ranges must not overlap.
  • Collision checks use padded size (size rounded up to 4-byte alignment).

Event Flow

Typical successful stage progression:

touch -> rebind -> connect -> write -> reset -> done

On failure, stage error is emitted.

Notes:

  • Progress percentages are stage-oriented UX signals, not exact end-to-end byte percentages.
  • event.type === "report" is terminal and mirrors the returned Report from flash().

Error Handling Guide

Common failure categories:

  • Unsupported browser/context (no WebSerial or not secure context).
  • Permission denied / chooser canceled.
  • Serial busy or restarting.
  • Sync/connect timeout.
  • Invalid firmware image (bad magic/header/chip ID mismatch).

Recommended handling pattern:

const report = await flash(file, { onEvent: console.log });

if (!report.ok) {
  // Treat this as the source of truth for run outcome.
  console.error(report.error);
}

UI Integration: File Picker + Serial Port Selection

Manual port selection flow

<input id="firmware" type="file" accept=".bin,application/octet-stream" />
<button id="select-port">Select Port</button>
<button id="flash" disabled>Flash</button>
<p id="status">Idle</p>

<script type="module">
  import { flash, isSupported } from "espcdcflash";

  const fileInput = document.getElementById("firmware");
  const portBtn = document.getElementById("select-port");
  const flashBtn = document.getElementById("flash");
  const status = document.getElementById("status");

  let file = null;
  let port = null;
  let busy = false;

  function updateUi() {
    flashBtn.disabled = busy || !file || !port;
    portBtn.disabled = busy || !isSupported();
  }

  fileInput.addEventListener("change", () => {
    file = fileInput.files?.[0] ?? null;
    updateUi();
  });

  portBtn.addEventListener("click", async () => {
    try {
      if (!isSupported()) throw new Error("WebSerial unavailable.");
      port = await navigator.serial.requestPort({});
      status.textContent = "Port selected.";
      updateUi();
    } catch (error) {
      status.textContent = error instanceof Error ? error.message : String(error);
    }
  });

  flashBtn.addEventListener("click", async () => {
    if (!file || !port || busy) return;
    busy = true;
    updateUi();
    status.textContent = "Flashing...";

    try {
      const report = await flash(file, {
        port,
        onEvent: (event) => {
          if (event.type === "progress") status.textContent = event.progress.message;
        },
      });
      status.textContent = report.ok ? "Done." : `Failed: ${report.error ?? "Unknown error"}`;
    } finally {
      busy = false;
      updateUi();
    }
  });

  updateUi();
</script>

Auto chooser flow (no manual port selection)

const report = await flash(file, {
  onEvent: (event) => {
    if (event.type === "progress") {
      console.log(event.progress.message);
    }
  },
});

// If no port was provided, flash() prompts for one.
console.log(report);

Limitations / Non-goals

  • ESP32-S3 only.
  • WebSerial only (Chromium secure context required).
  • Browser runtime only (not Node.js).
  • inspect validates firmware format/chip targeting, but is not a full target safety proof.

Attribution

espcdcflash is a focused/narrower alternative to generic flashing stacks and aims to mimic practical esptool.py flashing behavior for USB CDC usage.
Vendored stub data: src/stub/esp32s3.stub.json (from esptool-js, Apache-2.0).
See THIRD_PARTY_NOTICES.md and LICENSE.md.