@fixback/sdk
v0.2.0
Published
The Fixback capture SDK — a boot-gated, self-isolating on-page feedback launcher.
Maintainers
Readme
@fixback/sdk
The Fixback capture SDK — a tiny, self-isolating on-page feedback widget.
init({ key }) asks Fixback whether a submission would be accepted for your
publishable key, the visitor's origin, and the Project's Gate, and mounts a
launcher only when it would. Nothing renders when the origin isn't
allowlisted or the Gate turns the visitor away, and if Fixback can't be reached
the SDK stays completely silent — it never throws into the host page.
Activating the launcher opens the report overlay: the Reporter picks a Kind (bug / improve / idea), writes a comment, optionally points at the element they mean, and hits Send — which captures a masked screenshot of the current view, assembles the annotation, and submits it to Fixback. A successful send shows a confirmation; a refusal or an unreachable Fixback fails quietly, leaving the host page untouched.
- MIT-licensed and dependency-free.
- Isolated styles — the launcher and overlay each live in a Shadow DOM, so the host page's CSS can't reach in and the SDK's CSS can't leak out.
- Private by default — input values are masked before the screenshot is captured, so private text never leaves the page.
- Two builds — an ESM entry for bundlers and a single minified
<script>file for no-build sites.
Install
npm install @fixback/sdkWith a bundler (ESM)
import { init } from "@fixback/sdk";
init({ key: "pk_live_your_publishable_key" });No build step (<script> tag)
The minified UMD build is served from any npm CDN and exposes a Fixback
global:
<script src="https://unpkg.com/@fixback/sdk"></script>
<script>
Fixback.init({ key: "pk_live_your_publishable_key" });
</script>Options
init(options) returns a promise that resolves to an instance with a
destroy() method. All options other than key are optional.
| Option | Type | Default | Description |
| ---------------- | ------------- | -------------------------- | ----------------------------------------------------------------------- |
| key | string | — | Your Project's publishable key (an identifier, not a secret). |
| apiUrl | string | https://api.fixback.dev | The Fixback API origin. Override for a self-hosted or local deployment. |
| signedIdentity | string | — | A Signed-identity token minted by your server, for trusted reporters. |
| reporterId | string | — | The handle returned when a reporter redeems an invite. |
| anonymousId | string | a persisted per-browser id | A stable first-party id for an anonymous reporter. |
| target | HTMLElement | document.body | Where to mount the launcher. |
| reduceMotion | boolean | false | Still the launcher's pulse and motion (see The launcher). |
| autoCapture | boolean | true | Automatic error capture — file uncaught errors with no prompt (see Automatic error capture). Set false to turn it off. |
None of the identity fields is a trust tier — Fixback derives trust on the server and never honours a self-declared tier.
const fixback = await init({ key: "pk_live_..." });
// later, to tear the launcher down:
fixback.destroy();The launcher
The launcher is a bottom-right Feedback pill, and it stays out of the way:
- Hover-peek & tuck-away — the pill's caret tucks it off-screen behind a small edge nub. Hovering the bottom-right corner (or the nub) peeks it back; clicking the nub — or pressing Enter/Space on it — brings it fully back, which also covers pointers that can't hover (touch, keyboard). A brief hint appears the first time it's tucked, pointing at the corner.
- First-visit welcome — a one-time toast greets a new visitor, drawing the eye with a gentle pulse. It shows once per publishable key per browser.
- Reduce motion — pass
reduceMotion: trueto still the pulse and the slide/fade transitions. The launcher also honours the visitor's OS-levelprefers-reduced-motion: reduceon its own, with no configuration.
All of this chrome lives inside the launcher's Shadow DOM, so it never adds a global style or touches the host page's markup.
The launch event
Activating the launcher opens the SDK's own report overlay. It also dispatches a
composed, bubbling fixback:launch event from the SDK's host element, so the
host page can react to it too:
import { LAUNCH_EVENT } from "@fixback/sdk";
document.addEventListener(LAUNCH_EVENT, () => {
// the report overlay is opening — react here if you need to
});Automatic error capture
The SDK's signature capability: errors report themselves, with no prompt. Two
capture-phase global handlers (error + unhandledrejection) turn uncaught
exceptions and unhandled promise rejections into source: auto, Kind = bug
Feedback for the current session's reporter — carrying the same masked screenshot
and trace buffer a manual report does, plus a per-session fingerprint. It is
on by default across every Gate; pass autoCapture: false to turn it off.
- Deduped & rate-limited. The same error reported many times collapses to one
Feedback with a rising occurrence count. A token-bucket burst limiter and a
per-session cap keep a runaway error loop from flooding the queue; the excess is
dropped and kept only as a local count. On a
429+Retry-Afterfrom ingest, auto-reporting backs off until the window clears. - Gated like a manual report. Auto-capture only runs where a submission would
be accepted (boot's
canSubmit), so a public visitor's crash is tracked at their server-derived tier — never auto-shipped.console.erroris not promoted to a report; it stays breadcrumb-only Evidence. - Private-by-default. Every auto-report passes through the same client-side
beforeSendscrub choke point as a manual one — input values and request/response bodies never leave the browser, and your hook can drop an auto-report entirely.
Invite redemption
An invited tester needs no account and no setup. When a page loads with an invite
token in its URL (?fixback_invite=<token>), init reads the invite's status and
renders the SDK's own onboarding modal — the site you were invited to, the
access tier the redemption grants (server-derived, never guessed on the
client), a private-by-default note, and a "Continue as" name / email:
init({ key: "pk_live_..." }); // auto-detects ?fixback_invite= on the pageOn confirm the SDK redeems the invite, persists the returned reporterId in
localStorage (scoped to your publishable key), strips the token from the URL
(one-time consumption), and mounts the launcher. On later visits init presents
that stored reporterId, so a returning tester is recognised at their correct
tier without re-onboarding — and on an Invited or Internal Gate the
launcher appears only after redemption, while an Open Gate shows it to anyone.
The name / email are self-provided display fields — never a trust signal; the tier is always the server's to derive. If the token reaches the page some way other than the URL, redeem it explicitly:
import { redeem } from "@fixback/sdk";
redeem({ key: "pk_live_...", token: "the-invite-token" });How the boot gate works
On init, the SDK POSTs to ${apiUrl}/api/ingest/boot with your key (the
browser attaches the Origin header itself). The server answers with
canSubmit, and the launcher is mounted only when that is true. Any
non-answer — an unreachable API, a refused key, an unexpected error — is treated
as "don't show the launcher", silently.
Development
pnpm --filter @fixback/sdk test # DOM tests (jsdom)
pnpm --filter @fixback/sdk build # dist/index.mjs + dist/fixback.umd.js + typesexamples/index.html is a minimal page that loads the built UMD file and calls
Fixback.init — run the build first, then open it in a browser.
License
MIT — see LICENSE.
