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

@proofofbot/client-akamai

v3.1.1

Published

Runs an Akamai Bot Manager sensor headlessly and carries the session it produces

Downloads

685

Readme

@proofofbot/client-akamai

Generated client for the akamai target. It talks to a wred sidecar over a pipe.

Runs an Akamai Bot Manager sensor headlessly and carries the session it produces

Install

npm install @proofofbot/client-akamai

The binary for your platform arrives as an optional dependency. Set WRE_BINARY to an absolute path to override it with a local build.

Use

import { AkamaiClient } from "@proofofbot/client-akamai";

const client = await AkamaiClient.open({});
const result = await client.solve({ url: "https://example.com", rounds: 1 });
console.log(result);
await client.close();

One client owns one session, which owns the mounted realm. Open it once and reuse it. Opening one per call pays the warmup cost every time.

How a run works

solve is the whole flow. It loads the page, mounts the page's own sensor script in the sandbox, plays a pointer and key stream into it, reads back the telemetry the script builds, and posts that payload to the collection endpoint the script names. What comes back is the payload it sent, the endpoint it used, and the cookie jar afterwards, _abck included.

Every other op runs against the session solve opened, so call it first. request sends a request of yours through that same jar, transport fingerprint and user agent, which is the point of warming a session at all. payload builds another sensor payload without reloading the page, post sends one, cookies reads the jar, and page returns the last HTML with its form fields already parsed.

discover is the cheap look: it fetches the page and reports which Akamai pieces it carries, without mounting anything.

Sessions and cookies

A session is a client. One client holds one jar, one sandbox realm and one transport fingerprint, so keep it open for the whole flow instead of opening one per call. reset drops the realm and starts a new one, and reset with cookies set also empties the jar.

A valid _abck is not a guarantee. The edge scores what the payload contains, and a site can also demand the sec_cpt proof of work, which pow answers, or serve the pixel challenge, which runs automatically unless you turn pixel off.

Profiles

The sensor is fingerprinted against a browser, so the sandbox mounts a captured device profile. Without a workspace on disk the client falls back to the profile built into the binary, which carries a whole browser surface and is served. Capture your own with wre sandbox capture, list them with wre sandbox list, then name one in profile, or set random_profile to pick from the library per session. A captured profile carries the canvas the machine renders and the boxes its pages lay out, which the built in one cannot. The transport fingerprint follows the profile's user agent unless fingerprint overrides it.

farble perturbs what the profile renders, per session, the way a privacy browser does: the canvas comes back a pixel different, the audio render sums a fraction off and text measures on a scale of its own. The device the profile describes does not change. run.rendered names what a session rendered, so two of them can be told apart.

Time

A session takes about two seconds, most of it round trips to the origin. The waits are ceilings on conditions rather than durations: load_posts_ms ends when the script's own load posts have been answered, wait_ms when the edge has validated the jar and the page has nothing left in flight. warp is how much faster the page's clock runs than the wall while those run, which is what keeps a script that reposts every few seconds from costing every one of them.

Warming a session is the expensive part, and request on a warm one is a single round trip, so a hundred requests want one session rather than a hundred.

Configuration

These fields go in the object you pass to open. The session keeps them for its whole life.

| Field | Type | Default | Description | | --- | --- | --- | --- | | page_url | string \| null | optional | Page the session warms against when an op does not name one | | profile | string \| null | optional | Sandbox profile id, from wre sandbox list | | random_profile | boolean | false | Pick a captured profile at random | | random_machine | boolean | true | Put every session on a different Mac: the chip, its core count and its memory, picked together so they agree. What the machine renders is left as captured, because two Macs running the same macOS draw the same canvas | | proxy | string \| null | optional | Proxy url the session and the sandbox both go through, http or socks5 | | fingerprint | string \| null | optional | Transport fingerprint as profile[:platform], defaults to the sandbox profile's user agent | | user_agent | string \| null | optional | Overrides the user agent the sandbox profile carries | | wait_ms | number | 6000 | Ceiling on the wait after the input stream. It ends as soon as the edge has validated the jar and the page has nothing left in flight, so a session that is believed early pays none of it | | init_cost_ms | number | 25 | Clock charge applied when the sensor writes bmak.startTs | | friction_ms | number | 0.12 | Virtual cost of one DOM operation | | script_rate | number | 0.07 | How much of a script's own run time the page's clock counts. The interpreter is slower than V8, so counting all of it puts the sensor's own timings an order out | | behaviour | boolean | true | Play a pointer, click and key stream into the page | | pixel | boolean | true | Run the pixel challenge client when the page serves one | | keep_payloads | boolean | false | Carry the sensor payload of every post in the answer, for diffing | | prelude | string \| null | optional | Source run in the realm before the sensor script, for recorders | | load_posts_ms | number | 4000.0 | How long to let the script's own load-time posts land before input starts | | warp | number | 16.0 | How much faster the page's clock runs than the wall while the session waits. The scripts schedule their posts seconds apart and the edge reads the payload, not the wall, so 1 spends every one of those seconds and 12 spends a twelfth of them | | typed | string | "" | Text typed into the page's first text field. Empty picks a short word from the session seed | | live_xhr | boolean | true | Let the sensor's own requests leave the sandbox, which is what the script does in a browser. Turning it off has the host post what the sandbox builds instead, one payload per round, and the edge reads that differently | | wire_pace | boolean | false | Hold each live request until the wall clock reaches the time the sandbox believes it is. The edge does not read a payload against when it arrives, so this is off by default and a session finishes in the time the work takes rather than the time the page would have spent | | rounds | number | 2 | Payloads posted per solve | | timeout_ms | number | 90000 | Cap on one http request the session makes | | seed | number \| null | optional | Seed the behaviour stream and the random source | | workers | number | 0 | Threads the proof of work search uses, 0 picks four | | max_attempts | number | 5000000 | Highest nonce the proof of work search tries per round |

Operations

client.info(options)

What this build is carrying

Takes no arguments.

Returns:

| Field | Type | Description | | --- | --- | --- | | target | string | - | | version | string | - | | profile | string | Sandbox profile this session mounted | | profiles | Array<string> | Every profile id the workspace library holds | | user_agent | string | - | | fingerprint | string | Transport fingerprint, or where it is being derived from | | open | boolean | Whether a page session is open |

client.discover(params, options)

Report the Akamai surface of a page without running anything

Server side deadline: 45 s. Pass a shorter one per call to cap it further.

Takes:

| Field | Type | Default | Description | | --- | --- | --- | --- | | url | string \| null | optional | Page to fetch, defaults to page_url from the config |

Returns:

| Field | Type | Description | | --- | --- | --- | | url | string | Url after redirects | | status | number | - | | protected | boolean | Whether the page carries an Akamai sensor | | surface | unknown | Script urls, the collection endpoint and the challenge page when there is one | | cookies | unknown | Jar after the fetch, with _abck and sec_cpt broken out | | set_cookie | Array<string> | Cookie names the navigation response set |

client.solve(params, options)

Load the page, run its sensor and post what it builds

Server side deadline: 3 min. Pass a shorter one per call to cap it further.

Takes:

| Field | Type | Default | Description | | --- | --- | --- | --- | | url | string \| null | optional | Page to load, defaults to page_url from the config | | rounds | number \| null | optional | How many payloads to post before returning | | wait_ms | number \| null | optional | Overrides how long the sensor is left running after load | | post | boolean \| null | optional | Post the payload, on by default |

Returns:

| Field | Type | Description | | --- | --- | --- | | url | string | Page the session loaded | | telemetry | string \| null | Value for the akamai-bm-telemetry header | | payload | string \| null | Sensor payload as posted, sensor_data and all | | endpoint | string \| null | Collection endpoint the script named | | posts | unknown | One entry per post, with its status and response | | cookies | unknown | Jar after the run, with _abck and sec_cpt broken out | | challenge | unknown | Proof of work items and challenge page, when the edge asked for either | | run | unknown | What the sandbox did: scripts, timers, requests and misses |

client.payload(params, options)

Build a fresh payload from the session that is already open

Server side deadline: 1 min. Pass a shorter one per call to cap it further.

Takes:

| Field | Type | Default | Description | | --- | --- | --- | --- | | nudge_ms | number \| null | optional | Advance the page clock this far first, so the payload is not a repeat |

Returns:

| Field | Type | Description | | --- | --- | --- | | telemetry | string \| null | Value for the akamai-bm-telemetry header | | payload | string \| null | Sensor payload, ready to post | | endpoint | string \| null | Collection endpoint the script named |

client.post(params, options)

Post a payload to the collection endpoint

Server side deadline: 90 s. Pass a shorter one per call to cap it further.

Takes:

| Field | Type | Default | Description | | --- | --- | --- | --- | | payload | string \| null | optional | Payload to send, defaults to a fresh one from the session | | endpoint | string \| null | optional | Where to send it, defaults to the endpoint the script named | | rounds | number \| null | optional | Posts to make, one by default |

Returns:

| Field | Type | Description | | --- | --- | --- | | posts | unknown | One entry per post, with its status and response | | cookies | unknown | Jar after the posts |

client.request(params, options)

Send a request carrying the session this client warmed

Server side deadline: 90 s. Pass a shorter one per call to cap it further.

Takes:

| Field | Type | Default | Description | | --- | --- | --- | --- | | url | string | required | - | | method | string \| null | optional | GET by default | | headers | Record<string, string> \| null | optional | Added on top of the ones the session already sends | | body | string \| null | optional | Raw body, which wins over form and json | | telemetry | boolean \| null | optional | Attach a fresh akamai-bm-telemetry header | | form | Record<string, string> \| null | optional | Form encode these fields as the body | | json | unknown \| null | optional | Send this as a json body | | kind | string \| null | optional | form sends it the way the page submits a form, otherwise it goes as an XHR |

Returns:

| Field | Type | Description | | --- | --- | --- | | status | number | - | | url | string | Url after redirects | | headers | unknown | Response headers as name and value pairs, without set-cookie | | body | string | - | | cookies | unknown | Jar after the request | | refused | boolean | True on 403, 429, an access denied body or a challenge redirect |

client.relay(params, options)

Relay a request byte for byte through this session's transport and jar

Server side deadline: 90 s. Pass a shorter one per call to cap it further.

Takes:

| Field | Type | Default | Description | | --- | --- | --- | --- | | url | string | required | - | | method | string \| null | optional | GET by default | | headers | unknown \| null | optional | Name and value pairs sent verbatim, in this order, with nothing added | | body_b64 | string \| null | optional | Request body, base64 |

Returns:

| Field | Type | Description | | --- | --- | --- | | status | number | - | | url | string | Url after redirects | | headers | unknown | Every response header, set-cookie included | | body_b64 | string | Response body, base64 | | cookies | unknown | Jar after the request |

client.page(options)

The page this session last loaded, with the form fields it declares

Takes no arguments.

Returns:

| Field | Type | Description | | --- | --- | --- | | url | string | - | | html | string | - | | fields | Record<string, string> | Every input the page declares, by name, with its value | | bytes | number | Size of the html |

client.cookies(params, options)

What the jar is holding for this session

Takes:

| Field | Type | Default | Description | | --- | --- | --- | --- | | set | string \| null | optional | A Cookie header to seed the jar with before reading it back |

Returns:

| Field | Type | Description | | --- | --- | --- | | header | string | The jar as a Cookie header, ready to send elsewhere | | summary | unknown | _abck and sec_cpt read out, with the work items they carry | | names | Array<string> | - |

client.pow(params, options)

Solve the proof of work the _abck cookie asks for

Server side deadline: 2 min. Pass a shorter one per call to cap it further.

Takes:

| Field | Type | Default | Description | | --- | --- | --- | --- | | abck | string \| null | optional | Cookie to read the work items from, defaults to the session's | | challenge | string \| null | optional | A single challenge string, id-token-salt-difficulty-delay-slice | | start_ts | number \| null | optional | Page start timestamp the hash is built from, taken from the open session otherwise | | rounds | number \| null | optional | Nonces to find, one per slice |

Returns:

| Field | Type | Description | | --- | --- | --- | | challenge | unknown | Work item as parsed: id, token, salt, difficulty, delay and slice | | prefix | string | String each nonce is appended to | | nonces | Array<string> | - | | attempts | Array<number> | Hashes tried per round | | elapsed_ms | Array<number> | - | | answer | string | Formatted answer, what the sec_cpt endpoint expects |

client.pixel(options)

Run the pixel challenge client in the session that is open

Server side deadline: 1 min. Pass a shorter one per call to cap it further.

Takes no arguments.

Returns:

| Field | Type | Description | | --- | --- | --- | | pixel | unknown | What the pixel client sent and what came back, or why it was skipped |

client.script(options)

Hand back the sensor script this session actually ran

Takes no arguments.

Returns:

| Field | Type | Description | | --- | --- | --- | | url | string | Where the sensor script came from | | bytes | number | - | | source | string | The exact body the sandbox ran, which the edge varies per fetch |

client.eval(params, options)

Evaluate an expression inside the sandbox the sensor is running in

Server side deadline: 1 min. Pass a shorter one per call to cap it further.

Takes:

| Field | Type | Default | Description | | --- | --- | --- | --- | | expression | string | required | JavaScript evaluated in the mounted realm, result serialised as JSON | | nudge_ms | number \| null | optional | Advance the page clock this far before evaluating |

Returns:

| Field | Type | Description | | --- | --- | --- | | value | unknown | What the expression returned | | error | string \| null | Set instead of value when the expression threw |

client.reset(params, options)

Drop the sandbox, and the cookies when asked

Takes:

| Field | Type | Default | Description | | --- | --- | --- | --- | | cookies | boolean \| null | optional | Empty the jar too, off by default |

Returns:

| Field | Type | Description | | --- | --- | --- | | open | boolean | - |

Deadlines and cancellation

Every op takes a second options argument: deadlineMs caps the call and fails it with kind === "timeout", signal takes an AbortSignal and fails it with kind === "cancelled". Both stop the work inside the sidecar, they do not only abandon the promise.

const abort = new AbortController();
setTimeout(() => abort.abort(), 5000);
await client.solve({ url: "https://example.com", rounds: 1 }, { signal: abort.signal, deadlineMs: 180000 });

Errors

Every rejection is a WreError with a stable kind: bad_input, unsupported, target_drift, blocked, timeout, cancelled, resource, protocol, internal. Branch on kind, never on the message. error.retryable says whether the same call is worth repeating.

Sidecar output and diagnostics

The sidecar logs to its own stderr, which is discarded by default. Pass { stderr: "inherit" } to open, or set WRE_STDERR=inherit, to see it.

A failing call writes a diagnostic report and puts its path in error.detail.diagnostics. WRE_DIAG=always records every call, WRE_DIAG=off records none, and await client.diagnose(true) writes one on demand. Send that file with a bug report.

Pinned build

  • bundle default
  • binary version 0.1.0
  • schema hash 26bd761bdc493953

The schema hash is checked at connect time. A mismatch means this package and the installed binary disagree about the callable surface, and the connect call fails.