Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [18.x, 20.x, 22.x, 24.x, 26.x]
node-version: [22.x, 24.x, 26.x]
steps:
- uses: actions/checkout@v7
with:
Expand All @@ -41,10 +41,8 @@ jobs:
uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
- name: Run ESM test with Node.js ${{ matrix.node-version }}
- name: Run tests with Node.js ${{ matrix.node-version }}
run: npm run test-node
- name: Run CJS test with Node.js ${{ matrix.node-version }}
run: npm run test-node-cjs
test-karma:
runs-on: ubuntu-latest
timeout-minutes: 10
Expand Down
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
# @digitalbazaar/http-client ChangeLog

## 5.0.0 - 2026-xx-xx

### Changed
- **BREAKING**: Revert CJS related workarounds from v3.0.0.
- `kyOriginalPromise` no longer exported.
- `ky` is again exported.
- Change from using `ky` promises to regular instances.
- **BREAKING**: Update proxied method list.
- Remove `push`.
- Add `query`, `options`, and `trace` to align with `ky@2`.
- **BREAKING**: Update dependencies:
- `ky@2`.
- For most use cases the wrapped API is expected to be the same.
- See `ky` docs for exported `ky` API changes.
- Note that some errors can now have `cause` property chains and may use a
`NetworkError`.
- `undici@7`.
- Aligns with the undici built into the current Node.js LTS release.
- A v7 dispatcher is usable by the `fetch` built into Node.js 22, 24, and
26, so the legacy `agent`/`httpsAgent` options now use the platform
`fetch` on every supported release rather than an internal override.
- Update dev dependencies.
- Update README.md.
- **NOTE**: Update supported platforms.
- Test on Node.js >=22.
- Update `engines.node` to `>=22`.
- Update README requirements section.

### Removed
- **BREAKING**: Remove CJS support.

## 4.4.0 - 2026-08-06

### Changed
Expand Down
62 changes: 60 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
# http-client
An opinionated, isomorphic HTTP client for Node.js, browsers, and React Native.
# http-client _(@digitalbazaar/http-client)_

> An opinionated, isomorphic HTTP client for Node.js, browsers, and React Native.

## Table of Contents

- [Install](#install)
- [Usage](#usage)
- [Contribute](#contribute)
- [Commercial Support](#commercial-support)
- [License](#license)

## Install

This software requires and supports maintained recent versions of Node.js and
browsers. Updates may remove support for older unmaintained platform versions.
Please use dependency version lock files and testing to ensure compatibility
with this software.

To install from NPM:

https://www.npmjs.com/package/@digitalbazaar/http-client

```sh
npm install @digitalbazaar/http-client
```

To install locally (for development):

```sh
git clone https://github.com/digitalbazaar/http-client.git
cd http-client
npm install
```

### Usage

#### Import httpClient (Node.js, browsers, or React Native)

```js
import {httpClient} from '@digitalbazaar/http-client';
```

#### Import and initialize a custom Bearer Token client

```js
import {httpClient} from '@digitalbazaar/http-client';

Expand All @@ -21,6 +55,7 @@ const client = httpClient.extend({headers});
```

#### Disable self-signed TLS/SSL cert checks for development purposes only

```js
import {Agent} from 'https';
import {httpClient} from '@digitalbazaar/http-client';
Expand All @@ -32,6 +67,7 @@ const client = httpClient.extend({headers, agent});
```

#### GET a JSON response in the browser

```js
try {
const response = await httpClient.get('http://httpbin.org/json');
Expand All @@ -45,6 +81,7 @@ try {
```

#### GET a JSON response in Node with an HTTP Agent

```js
import https from 'https';
// use an agent to avoid self-signed certificate errors
Expand All @@ -61,6 +98,7 @@ try {
```

#### GET HTML by overriding default headers

```js
const headers = {Accept: 'text/html'};
try {
Expand All @@ -76,6 +114,7 @@ try {
```

#### POST a JSON payload

```js
try {
const response = await httpClient.post('http://httpbin.org/json', {
Expand All @@ -92,6 +131,7 @@ try {
```

#### POST a JSON payload in Node with an HTTP Agent

```js
import https from 'https';
// use an agent to avoid self-signed certificate errors
Expand All @@ -110,3 +150,21 @@ try {
throw e;
}
```

## Contribute

See [the contribute file](https://github.com/digitalbazaar/bedrock/blob/master/CONTRIBUTING.md)!

PRs accepted.

If editing the Readme, please conform to the
[standard-readme](https://github.com/RichardLitt/standard-readme) specification.

## Commercial Support

Commercial support for this library is available upon request from
Digital Bazaar: support@digitalbazaar.com

## License

[New BSD License (3-clause)](LICENSE) © 2026 Digital Bazaar
20 changes: 18 additions & 2 deletions karma.conf.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Copyright (c) 2020-2026 Digital Bazaar, Inc.
*/

const {startServers} = require('./tests/utils.cjs');
const {startServers} = require('./tests/utils.js');
const webpack = require('webpack');

module.exports = async function(config) {
Expand Down Expand Up @@ -70,7 +70,23 @@ module.exports = async function(config) {
// start these browsers
// browser launchers: https://npmjs.org/browse/keyword/karma-launcher
//browsers: ['ChromeHeadless', 'Chrome', 'Firefox', 'Safari'],
browsers: ['ChromeHeadless'],
browsers: ['ChromeHeadlessNoSandbox'],
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: [
// Essential: Bypasses container namespace errors
'--no-sandbox',
// Prevents extra privilege-dropping failures
'--disable-setuid-sandbox',
// Speeds up headless execution in CI environments
'--disable-gpu',
'--disable-software-rasterizer',
// Accept the self-signed cert used by the local HTTPS test server
'--ignore-certificate-errors'
]
}
},

// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
Expand Down
53 changes: 30 additions & 23 deletions lib/agentCompatibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ import {versions} from 'node:process';
/*
Background: node ships its own copy of undici in the platform but does not
expose it (there is no `node:undici`), so this package installs its own. A
dispatcher only works with the undici that created it -- the handler contract
changed across majors, so handing an installed v6 dispatcher to a platform v7
or v8 `fetch` fails with "invalid onError method". Which major the platform
provides varies by release line (node 22 has 6, node 24 has 7, node 26 has 8),
so no single installed version matches every supported runtime -- with undici 6
installed, both node 24 and node 26 take the fallback path below. See
digitalbazaar/http-client#43.
dispatcher is only usable by an undici that speaks its handler dialect --
that contract changed across majors, so a mismatched pairing fails with
"invalid onError method" or "invalid onRequestStart method". Which major the
platform provides varies by release line (node 22 has 6, node 24 has 7, node
26 has 8). See digitalbazaar/http-client#43.
*/

// as long as an agent has a reference to it, its associated dispatcher will
Expand All @@ -26,34 +24,43 @@ const DISPATCHER_CACHE = new WeakMap();
// its agent lives, so the override has the same lifetime as the agent
const FETCH_CACHE = new WeakMap();

// can only convert agent to dispatcher option on node 18.2+
const [major, minor] = versions.node.split('.').map(v => parseInt(v, 10));
const canConvert = (major > 18) || (major === 18 && minor >= 2);
/*
Platform undici majors that each installed undici major's dispatcher can be
driven by. undici 7 is a transition release: it ships both `wrap-handler` and
`unwrap-handler` and translates between the old (v6 `onError`) and new (v8
`onRequestStart`) handler dialects in both directions, so a v7 dispatcher
works with platform undici 6, 7, and 8 -- every node this package supports.

Revisit when bumping undici. An installed major that is not listed falls back
to requiring an exact match, so a missing or stale entry only costs the
fallback path below -- still correct, just not the platform `fetch`.
*/
const COMPATIBLE_PLATFORM_MAJORS = {
7: [6, 7, 8]
};

/*
True when the installed and platform undici majors match, meaning their
dispatchers are interchangeable. Both reads are guarded: a future undici could
hide `package.json` behind an `exports` map, and `versions.undici` may be
absent. Either way fall back to `false` and use the installed undici's own
fetch -- the always-safe path -- rather than throwing at module load and
breaking `import` for every consumer.
True when the installed undici's dispatcher can be handed to the platform
`fetch`. Both reads are guarded: a future undici could hide `package.json`
behind an `exports` map, and `versions.undici` may be absent. Either way fall
back to `false` and use the installed undici's own fetch -- the always-safe
path -- rather than throwing at module load and breaking `import` for every
consumer.
*/
const platformFetchCompatible = (() => {
try {
const installedMajor = parseInt(undiciPkg.version, 10);
const platformMajor = parseInt(versions.undici, 10);
return platformMajor === installedMajor;
} catch{
const compatible =
COMPATIBLE_PLATFORM_MAJORS[installedMajor] ?? [installedMajor];
return compatible.includes(platformMajor);
} catch {
return false;
}
})();

// converts `agent`/`httpsAgent` option to a dispatcher option
export function convertAgent(options) {
if(!canConvert) {
return options;
}

// do not override custom fetch function from another lib
if(options?.fetch && !options.fetch._httpClientCustomFetch) {
return options;
Expand All @@ -77,7 +84,7 @@ export function convertAgent(options) {
delete rest.agent;
delete rest.httpsAgent;

// majors match: hand the dispatcher to `ky`, which forwards it to the
// compatible: hand the dispatcher to `ky`, which forwards it to the
// platform `fetch` (`ky` deliberately keeps `dispatcher` out of its
// request-option registry so it reaches fetch) -- no wrapper needed
if(platformFetchCompatible) {
Expand Down
18 changes: 0 additions & 18 deletions lib/deferred.js

This file was deleted.

Loading