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
74 changes: 64 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,75 @@
on:
push:
branches:
- main
- next
- 'v*'
pull_request:
paths-ignore:
- LICENSE
- '*.md'

name: CI
on: [push, pull_request]

jobs:
test:
lint:
permissions:
contents: read
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: v22.x
cache: 'npm'
cache-dependency-path: package.json

- name: Install dependencies
run: npm install
- name: Check linting
run: npm run lint:ci

tests:
permissions:
contents: read
name: Tests
strategy:
fail-fast: false
matrix:
node: [20.x, 22.x]
name: Node ${{ matrix.node }}
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [20.x, 22.x, 24.x]
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4
- name: Setup node
uses: actions/setup-node@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
node-version: ${{ matrix.node }}
persist-credentials: false

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: package.json

- run: npm install
- run: npm run test:ci
- name: Install Dependencies
run: npm install

- name: Run Tests
run: npm run test:ci

automerge:
if: >
github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]'
needs:
- tests
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Merge Dependabot PR
uses: fastify/github-action-merge-dependabot@e820d631adb1d8ab16c3b93e5afe713450884a4a # v3.11.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,9 @@ type WaitOnOptions = {
maxRedirects?: number;
followRedirect?: boolean;
headers?: Record<string, string | number>;
validateStatus?: WaitOnValidateStatusCallback
validateStatus?: WaitOnValidateStatusCallback;
happyEyeballs?: boolean; // default `true`
rejectUnauthorized?: boolean; // default `true`
};
socket?: {
timeout?: number;
Expand Down
36 changes: 22 additions & 14 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ type WaitOnResourcesType =
| `socket://${string}`;

type WaitOnValidateStatusCallback = (status: number) => boolean;
type WaitOnHTTPOptions = {
bodyTimeout?: number;
headersTimeout?: number;
maxRedirects?: number;
followRedirect?: boolean;
headers?: Record<string, string | number>;
rejectUnauthorized?: boolean;
happyEyeballs?: boolean;
validateStatus?: WaitOnValidateStatusCallback;
};
type WaitOnSocketOptions = {
timeout?: number;
};
type WaitOnTCPOptions = {
timeout?: number;
};

type WaitOnOptions = {
resources: WaitOnResourcesType[];
Expand All @@ -56,20 +72,9 @@ type WaitOnOptions = {
reverse?: boolean;
any?: boolean;
simultaneous?: number;
http?: {
bodyTimeout?: number;
headersTimeout?: number;
maxRedirects?: number;
followRedirect?: boolean;
headers?: Record<string, string | number>;
validateStatus?: WaitOnValidateStatusCallback
};
socket?: {
timeout?: number;
};
tcp?: {
timeout?: number;
};
http?: WaitOnHTTPOptions;
socket?: WaitOnSocketOptions;
tcp?: WaitOnTCPOptions;
window?: number;
proxy?: WaitOnProxyConfig;
events?: {
Expand All @@ -89,5 +94,8 @@ export {
WaitOnResourcesType,
WaitOnValidateStatusCallback,
WaitOnCallback,
WaitOnHTTPOptions,
WaitOnSocketOptions,
WaitOnTCPOptions,
WaitOn,
};
80 changes: 16 additions & 64 deletions lib/http.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use strict'
const { Agent, ProxyAgent, request } = require('undici')
const { Client, ProxyAgent, interceptors } = require('undici')

const HTTP_GET_RE = /^https?-get:/
const HTTP_UNIX_RE = /^http:\/\/unix:([^:]+):([^:]+)$/
Expand All @@ -17,7 +17,8 @@ function getHTTPAgent (config, href) {
headersTimeout,
followRedirect,
maxRedirections,
rejectUnauthorized
rejectUnauthorized,
happyEyeballs
} = {},
proxy
} = config
Expand All @@ -33,6 +34,7 @@ function getHTTPAgent (config, href) {
headersTimeout,
connections: 1, // Single connection per resource
pipelining: 0, // to disable keep-alive
autoSelectFamily: happyEyeballs != null ? happyEyeballs : true,
connect: {
timeout,
socketPath,
Expand All @@ -42,7 +44,7 @@ function getHTTPAgent (config, href) {

return isProxied
? new ProxyAgent(Object.assign({}, httpOptions, proxy))
: new Agent(httpOptions)
: new Client(href, httpOptions).compose(interceptors.dump())
}

/**
Expand All @@ -58,57 +60,25 @@ function createHTTPResource (config, resource) {
const method = HTTP_GET_RE.test(resource) ? 'GET' : 'HEAD'
const href = source.href.replace('-get:', ':')
const isStatusValid = httpConfig?.validateStatus
// TODO: this will last as long as happy-eyeballs is not implemented
// within node core
/** @type {{ options: import('undici').Dispatcher.RequestOptions, url: URL }} */
const primary = {
options: null,
url: null
}
/** @type {{ options?: import('undici').Dispatcher.RequestOptions, url?: URL }} */
const secondary = {
options: null,
url: null
}

if (href.includes('localhost')) {
primary.url = new URL(href.replace('localhost', '127.0.0.1'))

secondary.url = new URL(href.replace('localhost', '[::1]'))
secondary.options = {
path: secondary.url.pathname,
origin: secondary.url.origin,
query: secondary.url.search,
const url = new URL(href)
const handler = {
dispatcher,
options: {
path: url.pathname,
origin: url.origin,
query: url.search,
method,
dispatcher,
signal: null,
headers: httpConfig?.headers
}
} else {
primary.url = new URL(href)
}

primary.options = {
path: primary.url.pathname,
origin: primary.url.origin,
query: primary.url.search,
method,
dispatcher,
signal: null,
headers: httpConfig?.headers
}

return {
exec,
name: resource
}

async function exec (
signal,
handler = primary,
handlerSecondary = secondary,
isSecondary = false
) {
async function exec (signal) {
const start = Date.now()
const operation = {
successfull: false,
Expand All @@ -117,35 +87,17 @@ function createHTTPResource (config, resource) {

handler.options.signal = signal

if (handlerSecondary.options != null) {
handlerSecondary.options.signal = signal
}

try {
const options = isSecondary ? handlerSecondary.options : handler.options
const { statusCode, body } = await request(options)
const { options, dispatcher } = handler
const { statusCode } = await dispatcher.request(options)
const duration = Date.now() - start

// We allow data to flow without worrying about it
body.resume()
operation.successfull =
isStatusValid != null
? isStatusValid(statusCode)
: statusCode >= 200 && statusCode < 500

if (
!operation.successfull &&
!isSecondary &&
handlerSecondary.url != null
) {
return await exec(signal, handler, handlerSecondary, true)
}
: statusCode > 199 && statusCode < 500
operation.reason = `HTTP(s) request for ${method}-${resource} replied with code ${statusCode} - duration ${duration}ms`
} catch (e) {
if (!isSecondary && handlerSecondary.url != null) {
return await exec(signal, handler, handlerSecondary, true)
}

operation.reason = `HTTP(s) request for ${method}-${resource} errored: ${
e.message
} - duration ${Date.now() - start}`
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
]
},
"devDependencies": {
"@types/node": "^22.7.5",
"@types/node": "^24.0.15",
"expect-legacy": "^1.20.2",
"husky": "^9.0.11",
"mkdirp": "^3.0.0",
Expand All @@ -68,7 +68,7 @@
"minimist": "^1.2.7",
"ora": "^8.0.1",
"signale": "^1.4.0",
"undici": "^6.14.1"
"undici": "^7.13.0"
},
"keywords": [
"wait",
Expand Down
20 changes: 2 additions & 18 deletions test/http.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,33 +148,18 @@ test('Wait-On#HTTP', context => {
context.test(
'Basic HTTP - fallback to ipv6 if ipv4 not available on localhost',
async t => {
let ipv4Called = false
let ipv6Called = false

const server4 = createServer((req, res) => {
ipv4Called = true
res.writeHead(500, { 'Content-Type': 'text/plain' })
res.end('oops!')
})

const server6 = createServer((req, res) => {
ipv6Called = true
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('Hello World')
})

t.plan(3)
t.plan(2)

t.teardown(server4.close.bind(server4))
t.teardown(server6.close.bind(server6))

await new Promise((resolve, reject) => {
server4.listen({ host: '127.0.0.1', port: 3006 }, e => {
if (e != null) reject(e)
else resolve()
})
})

await new Promise((resolve, reject) => {
server6.listen({ host: '::1', port: 3006 }, e => {
if (e != null) reject(e)
Expand All @@ -189,7 +174,6 @@ test('Wait-On#HTTP', context => {
})

t.equal(result, true)
t.ok(ipv4Called)
t.ok(ipv6Called)
}
)
Expand Down Expand Up @@ -224,7 +208,7 @@ test('Wait-On#HTTP', context => {
}
)

context.test('Basic HTTP with timeout', { only: true }, t => {
context.test('Basic HTTP with timeout', t => {
t.plan(1)

waitOn({
Expand Down
Loading