hakka-core
hakka-core is the shared engine consumed by hakka-react-native, hakka-browser,
and hakka-node. It is ESM-only, framework-agnostic, and carries a single
runtime dependency (fflate, for compressed body and export handling). You
rarely install it directly — use the package that matches your platform instead.
What it provides
Section titled “What it provides”- JS interceptors for
fetch,XMLHttpRequest, andWebSocket - A bounded ring-buffer store with age-based retention
- Mock, throttle, and breakpoint engines
- HAR, OpenTelemetry, Postman, and cURL export
- The cross-platform
ContractRecordwire format - A plugin system for adding capture sources, sinks, and UI panels
The Hakka singleton
Section titled “The Hakka singleton”Every platform target re-exports the same Hakka singleton from hakka-core.
start(config?)
Section titled “start(config?)”Starts capture. Pass config here or call configure() first.
import { Hakka } from 'hakka-react-native' // or 'hakka-browser'
Hakka.start({ mode: 'auto', maxRequests: 500, maxBodySize: 256 * 1024,})start() is idempotent — calling it twice is safe. If enabled is false,
start() returns immediately.
| Mode | Behaviour |
|---|---|
'auto' (default) |
Prefer native interceptors (OkHttp / URLProtocol) when available; fall back to JS monkey-patches. |
'native' |
Native interceptors only. Throws from start() if the TurboModule is not found. |
'js' |
JS monkey-patches only (fetch / XHR / WebSocket). No native module needed. |
'store' |
No interceptors installed. The engine aggregates requests fed externally via ingest() / update(). Used by hakka-browser to host the store inside a Web Worker. |
Lifecycle
Section titled “Lifecycle”Hakka.pause() // buffer incoming requests without dropping themHakka.resume() // flush the buffer and resume dispatchHakka.clear() // empty the ring buffer and persisted storeHakka.stop() // tear down interceptors and pluginsInspector UI
Section titled “Inspector UI”Hakka.show({ as: 'bubble' }) // 'bubble' | 'sheet' | 'fullscreen' — returns booleanHakka.hide()show() returns true when the native module actually handled the request, and
false when it fell back to a no-op — either because no native module is linked
at all, or because the TurboModule exists but the optional native UI package
(HakkaUI on iOS, hakka-ui on Android) isn’t linked into this app target. A dev
build logs a console warning either way, but that’s invisible outside the Metro
console — callers that need to surface “native UI unavailable” to the end user
(rather than silently doing nothing) should check the return value:
if (!Hakka.show({ as: 'bubble' })) { // No native UI linked — fall back to the JS inspector, or show your own notice.}hide() stays void — there’s nothing useful to report on the way out. Both are
no-ops in JS-only mode (no native module at all). This Hakka.show()/hide()
pair is the native-module path only — a JS-rendered inspector such as React
Native’s <HakkaInspector.Wrapper> manages its own visibility through its own
imperative handle (HakkaInspector.show()/hide()), not through these.
Store access
Section titled “Store access”Hakka.getLogs() // NetworkRequest[]Hakka.getLog(id) // NetworkRequest | undefinedHakka.getLogCount() // numberHakka.getSnapshot() // Promise<NetworkRequest[]> — merges native bufferHakka.onRequest(listener) // subscribe; returns unsubscribe fnManual ingest
Section titled “Manual ingest”// Push a request from an external capture source.Hakka.ingest(request)
// Merge timing or other fields into an existing record by id.Hakka.update({ id, timing: { dnsMs: 12 } })HakkaConfig options
Section titled “HakkaConfig options”| Option | Default | Description |
|---|---|---|
mode |
'auto' |
Capture mode (see above). |
enabled |
true |
Kill switch. Set false to disable all capture without relinking. |
maxRequests |
500 |
Ring-buffer capacity. Oldest entries are evicted when full. |
maxBodySize |
262144 |
Body capture limit per request, in bytes (256 KB). Bodies larger than this are truncated. |
redactHeaders |
['authorization', 'proxy-authorization', 'cookie', 'set-cookie'] |
Header names (lowercased) to blank out before storage. |
ignoreHosts |
[] |
Hosts to skip. Supports wildcards: '*.analytics.com'. |
ignorePatterns |
[] |
URL patterns to skip. Supports wildcards. |
persist |
false |
Opt-in: persist captured requests across restarts via a StorageAdapter. |
maxAge |
86400 |
Max age in seconds for persisted requests (24 h). Only applies when persist is enabled. |
shake |
true |
Shake to open inspector (React Native). Disable if your app has its own shake handler. |
Engines
Section titled “Engines”Three optional engines extend capture. Register them with Hakka.use(plugin) —
each engine ships as a ready-made plugin.
| Engine | Purpose | Docs |
|---|---|---|
mockEngine |
Match requests by URL/method and return canned responses or rewrite them. | Mocking |
ThrottleEngine |
Simulate slow or offline conditions by adding latency. | Mocking |
breakpointEngine |
Pause a request or response mid-flight, inspect and edit it, then resume. | Breakpoints |
Plugin system
Section titled “Plugin system”Plugins are how every platform extends the engine the same way.
import type { HakkaPlugin } from 'hakka-core'
const myPlugin: HakkaPlugin = { id: 'my-plugin', panels: [{ id: 'my-panel', title: 'My Panel', order: 10 }], setup(ctx) { // ctx.ingest, ctx.update, ctx.onRequest, ctx.getLogs, ctx.registerSink const unsub = ctx.onRequest((req) => console.log(req.url)) return unsub // teardown called on Hakka.stop() },}
Hakka.use(myPlugin)Hakka.getPanels() // HakkaPanel[] — all panels across plugins, sorted by orderHakka.getBodyRenderers() // HakkaBodyRenderer[]Hakka.getContextMenuItems() // HakkaContextMenuItem[]use() is idempotent. Calling it with the same plugin twice is a no-op.
StorageAdapter
Section titled “StorageAdapter”Implement StorageAdapter to persist the ring buffer across sessions.
interface StorageAdapter { save(records: NetworkRequest[]): void | Promise<void> load(): NetworkRequest[] | Promise<NetworkRequest[]> clear(): void | Promise<void>}
Hakka.setStorageAdapter(adapter) // pass null to revert to in-memory-onlyOn start() the engine hydrates from adapter.load(). Writes are coalesced into
a single save() call per 50 ms burst to avoid O(n²) serialize cost.
Export functions
Section titled “Export functions”All export functions operate on the current Hakka.getLogs() snapshot.
import { exportHarString } from 'hakka-core'import { exportPostmanString } from 'hakka-core'import { recordsToOtelJson } from 'hakka-core'import { buildCurl } from 'hakka-core'| Export | Output |
|---|---|
exportHarString(requests) |
HAR 1.2 JSON string |
exportPostmanString(requests, options?) |
Postman Collection v2.1 JSON string |
recordsToOtelJson(records, options?) |
OpenTelemetry JSON log export |
buildCurl(request) |
cURL command string |
Record contract
Section titled “Record contract”Every captured request is normalised to a ContractRecord before being passed
to sinks. The RECORD_SCHEMA_VERSION is 1; RECORD_SEMCONV_VERSION is '1.40.0'
(OTel semantic conventions).
type RecordKind = | 'network.request' | 'metric.frame' | 'metric.memory' | 'metric.cpu' | 'metric.network_usage' | 'metric.js_thread' | 'breadcrumb' | 'trace' | 'health.report'NetworkRecord (kind 'network.request') carries the full NetworkRequest
shape plus OTel attributes (http.request.method, url.full,
http.response.status_code, hakka.source, etc.).
Use networkRequestToRecord(request, options?) to convert a NetworkRequest
to a ContractRecord for delivery to a RecordSink.