Skip to content

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.

  • JS interceptors for fetch, XMLHttpRequest, and WebSocket
  • A bounded ring-buffer store with age-based retention
  • Mock, throttle, and breakpoint engines
  • HAR, OpenTelemetry, Postman, and cURL export
  • The cross-platform ContractRecord wire format
  • A plugin system for adding capture sources, sinks, and UI panels

Every platform target re-exports the same Hakka singleton from hakka-core.

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.
Hakka.pause() // buffer incoming requests without dropping them
Hakka.resume() // flush the buffer and resume dispatch
Hakka.clear() // empty the ring buffer and persisted store
Hakka.stop() // tear down interceptors and plugins
Hakka.show({ as: 'bubble' }) // 'bubble' | 'sheet' | 'fullscreen' — returns boolean
Hakka.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.

Hakka.getLogs() // NetworkRequest[]
Hakka.getLog(id) // NetworkRequest | undefined
Hakka.getLogCount() // number
Hakka.getSnapshot() // Promise<NetworkRequest[]> — merges native buffer
Hakka.onRequest(listener) // subscribe; returns unsubscribe fn
// 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 } })
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.

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

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 order
Hakka.getBodyRenderers() // HakkaBodyRenderer[]
Hakka.getContextMenuItems() // HakkaContextMenuItem[]

use() is idempotent. Calling it with the same plugin twice is a no-op.

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-only

On 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.

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

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.