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

@browsercore/tls

v0.3.0

Published

TLS 1.3 (and 1.2 fallback) client implemented entirely in TypeScript. Depends on @browsercore/transport and @browsercore/crypto — never node:crypto directly.

Downloads

919

Readme

@browsercore/tls

npm version coverage lint

A TLS 1.3 client implemented entirely in TypeScript. TLS 1.2 fallback is intentionally not implemented — requesting a TLS 1.2-only handshake is rejected up front with a typed error rather than failing silently mid-flight.

Responsibility

Owns the full TLS handshake, record layer, key schedule, and X.509 certificate validation. Provides an encrypted byte stream over an existing @browsercore/transport connection so higher layers never touch plaintext on the wire.

What it does NOT know about

  • HTTP (any version)
  • Browser fingerprints
  • Cookies

It knows about byte streams (@browsercore/transport) and cryptographic primitives (@browsercore/crypto). It never imports node:crypto directly — that boundary is @browsercore/crypto's job, which keeps the crypto backend replaceable.

Public API

import { connect } from "@browsercore/transport";
import { connectTls, resolveProfile, TlsHandshakeError } from "@browsercore/tls";

const transport = await connect({ host: "example.com", port: 443 });

const tls = await connectTls({
    transport,
    serverName: "example.com",
    profile: resolveProfile("modern-tls13", "example.com"),
    alpnProtocols: ["h2", "http/1.1"],
    handshakeTimeoutMs: 10_000,
});

const response = await tls.read();
await tls.write(new TextEncoder().encode("GET / HTTP/1.1\r\n"));
await tls.close();

Types

| Export | Kind | Purpose | | --- | --- | --- | | TlsConnection | interface | Public contract higher layers depend on | | connectTls() | function | Perform the TLS handshake over a transport | | TlsConnectionImpl | class | Concrete connection (thin coordinator over ./connection/ modules) | | TlsState | discriminated union | connecting \| handshaking \| open \| closed | | CloseReason | discriminated union | Why a TLS connection closed | | ProtocolVersion | discriminated union | TLS 1.2 / TLS 1.3 with wire codes | | CipherSuite | string-literal union | Negotiated AEAD + hash | | ClientHelloConfig | interface | ClientHello configuration (placeholder for @browsercore/profiles) | | TlsProfile | interface | Named, reusable ClientHello config | | resolveProfile() | function | Look up a profile by name and fill in serverName | | TlsError | class | Base typed error | | TlsHandshakeError | class | Handshake failure at a specific phase | | TlsDecryptError | class | Record decryption / auth failure | | TlsAlertError | class | TLS alert with level + description |

Dependency graph

@browsercore/tls
  ├─ @browsercore/transport
  └─ @browsercore/crypto

No other @browsercore/* packages are imported. Shared build, lint, and test config comes from @browsercore/dev (see Development).

Architecture

The package is a thin coordinator + focused pure-function modules. The connection class (TlsConnectionImpl) owns mutable state (read buffer, transcript, traffic secrets, sequence counters) and the public surface (handshake / read / write / close / on). Every byte-level computation lives in a module under src/connection/ and is written as functions over explicit inputs, so the protocol logic is unit-testable without a live connection.

| Module | Responsibility | | --- | --- | | src/record/record.ts | Record header parse/serialize, AEAD encrypt/decrypt (delegates to @browsercore/crypto) | | src/handshake/client-hello.ts | ClientHello builder (SNI, supported_versions, key_share, signature_algorithms, ALPN) | | src/handshake/server-hello.ts | ServerHello parser — validates cipher suite + version | | src/handshake/state-machine.ts | Handshake phase state machine with phase-tagged errors | | src/crypto/keySchedule.ts | TLS 1.3 key schedule (RFC 8446 §7.1): HKDF-Expand-Label, traffic secrets | | src/certificates/ | X.509 DER parse, hostname validation (RFC 6125), chain verification | | src/extensions/ | Extension types, parsers, and wire encoders | | src/connection/handshake-driver.ts | Handshake choreography (what to send, what to read, when to derive) | | src/connection/record-layer.ts | TLS 1.3 inner-content-type wrapping, nonce XOR, record framing | | src/connection/key-exchange.ts | (EC)DHE shared secret, transcript hash, server Finished verification | | src/connection/handshake-messages.ts | EncryptedExtensions/Certificate/Finished parsing, client Finished builder | | src/connection/lifecycle.ts | Timeouts, alerts, state transitions, post-handshake record dispatch |

Errors all carry a kind discriminator so callers can narrow and inspect without leaking backend specifics (TlsHandshakeError(phase), TlsDecryptError, TlsAlertError(level, description)).

Not implemented

  • TLS 1.2 fallback — the client speaks TLS 1.3 only
  • Post-handshake messages (NewSessionTicket, KeyUpdate)
  • Session resumption / PSK / 0-RTT
  • Mutual TLS (client certificate)
  • Certificate compression
  • HelloRetryRequest
  • Additional key-share groups beyond X25519

Development

This repo shares tooling with the @browsercore/* family via @browsercore/dev. That package is the single source of truth for:

  • tsconfig strict flags — tsconfig.json extends @browsercore/dev/tsconfig.base.json
  • vitest config — vitest.config.ts imports definePackageConfig from @browsercore/dev/vitest
  • oxlint config — oxlint.config.ts imports the base ruleset from @browsercore/dev/oxlint
  • coverage-md bin — shipped via @browsercore/dev's bin/, used as npx coverage-md

@browsercore/dev is declared in devDependencies as "file:../dev" for local development (the monorepo layout). When installing from npm, consumers resolve the published version.

Scripts

npm run typecheck   # tsc --noEmit (strict mode — noUncheckedIndexedAccess, exactOptionalPropertyTypes)
npm run lint        # oxlint --type-aware src/ (tests excluded)
npm test            # vitest run (in-process fixture server, no real network)
npm run build       # tsc -p tsconfig.build.json → dist/
npx coverage-md     # write COVERAGE.md + coverage/badge.json from coverage-summary.json

Run a single test file:

npx vitest run tests/handshake.test.ts

Run tests by name pattern:

npx vitest run -t "rejects a non-browser User-Agent"

Lint config

Linting is type-aware oxlint via oxlint.config.ts. The file extends the shared @browsercore/dev/oxlint base (which enables the typescript, unicorn, import, promise, and node plugins with correctness/suspicious/pedantic as errors). The old .oxlintrc.json has been removed.

Private fields use the native TypeScript private keyword — the legacy _-prefixed naming convention has been fully migrated away, so no no-underscore-dangle allowlist is required.

Requires Node >= 26. ESM only ("type": "module").