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

@fixback/expo

v0.10.1

Published

The Fixback capture SDK for Expo / React Native apps — shake the device to report feedback with trace Evidence.

Downloads

1,506

Readme

@fixback/expo

The Fixback capture SDK for Expo / React Native apps: shake the device to open a feedback composer, and every report ships with trace Evidence — console output, network metadata, screen changes — plus an optional screenshot. Uncaught JS errors report themselves (source: auto), exactly like the web SDK.

📖 Full documentation: docs.fixback.dev/sdk/expo — guides, concepts, and the complete API reference.

Install

npm install @fixback/expo
npx expo install expo-sensors react-native-view-shot @react-native-async-storage/async-storage expo-web-browser expo-secure-store

The five Expo-ecosystem packages are peer dependencies (they carry the native modules for the accelerometer, screenshots, anonymous-id persistence, the in-app auth session that powers Connect, and the secure store the Reporter session lives in). All of them work in Expo Go, and so does everything in this README except session replay, which needs a development build.

Adopting the SDK costs a native build. Every peer above ships native code, so adding Fixback to an app already on the App Store or Play Store needs a new binary and a submission — not an over-the-air update — and a version bump if your runtime version tracks your app version. Upgrading the SDK later rides an OTA update as normal, as long as the peer set has not changed.

Quick start

import { FixbackProvider } from "@fixback/expo";

export default function App() {
  return (
    <FixbackProvider
      options={{
        key: "pk_…", // your environment's publishable key
      }}
    >
      <RootNavigator />
    </FixbackProvider>
  );
}

Shake the device → the composer opens → write a comment, review (or remove) the screenshot, send. There is no Kind to pick — Fixback's analysis classifies the Issue (bug / improvement / idea) server-side.

Point at your API

apiUrl defaults to https://api.fixback.dev — the hosted Fixback API. Set it only when your publishable key was minted elsewhere: your own origin for a self-hosted or local deployment.

options={{ key: "pk_…", apiUrl: "https://fixback.example.com" /* self-hosted only */ }}

One key per environment

The key selects the environment. Each environment in the Fixback dashboard — Production, Staging, … — has its own publishable key and its own Gate, and a report is filed under the environment of the key it arrived with. So a build sets one variable, FIXBACK_KEY — its environment's publishable key (e.g. exposed as EXPO_PUBLIC_FIXBACK_KEY) — and passes it as key. There is no environment option to keep in step with it: to report from a new build flavour, create the environment in the dashboard and ship its key. An environment passed in options is ignored, with a one-time development warning.

The origin option — optional

Fixback's ingest checks a request's Origin header against the Project's allowed origins when one is present. Browsers send that header themselves and page script cannot override it, so the check is a real control there. A native app sends none, which means any origin it sent would be a value it invented — so the SDK no longer needs one and ingest no longer asks for one. Omit origin and the app reports normally.

Set one when you want mobile reports to carry an origin, so the queue can filter by surface:

options={{ key: "pk_…", origin: "https://com.acme.myapp" }}

An origin you do present is still checked, so add the same value to the Project's allowed origins in the dashboard — the card's Mobile app mode composes it from your bundle id. The SDK canonicalizes what you configure exactly as the dashboard stores allowlist entries (lowercased host, default ports elided, path dropped), so a pasted mixed-case bundle id still matches. Convention: the app's reverse-DNS id as an https host (https://com.acme.myapp), or your product's existing web origin.

Triggers

  • Shake (default): tuned to a deliberate shake — threshold, peak count, window, and cooldown are configurable via options.shake; shake: { enabled: false } turns the gesture off.
  • Programmatic: useFixback().present() in a component, or the module-level Fixback.present() anywhere.

Development builds: the Expo dev menu also opens on shake, so both appear. Use present() while developing; production builds have no dev menu.

Screens in reports

React Native has no URL, so tell Fixback where the Reporter is:

const { trackScreen } = useFixback();
// e.g. in your navigation container's state-change handler:
trackScreen(routeName);

Each change records a navigation trace crumb, and the active screen names the report's URL: <origin>/<screen> when an origin is configured, otherwise the screen on its own.

What the trace captures, and when

Three independent rings, each with its own size budget and its own age cap:

| Stream | Holds | Ages out after | | --- | --- | --- | | Network | 100 entries | 3 minutes | | Console | 80 entries | 15 minutes | | Breadcrumbs | 40 entries | 15 minutes |

Console capture wraps all six levels (log, info, warn, error, assert, debug), keeps each argument type-tagged so objects and errors stay expandable, and pins warn / error / assert against eviction so a burst of chatter never buries them.

Capture arms as soon as FixbackProvider mounts — before the SDK's boot call answers — so your app's startup logging and first requests are in the trace. The answer then reconciles: a stream your Project has turned off is uninstrumented and what it recorded is dropped, and a boot that says this account cannot submit clears the buffer entirely. Nothing is ever sent before the composer is opened and the Reporter taps Send.

enabled: false and a missing key still keep the SDK fully dormant: nothing is instrumented at all.

The Correlation id

List your own APIs in propagateTo and every request the app makes to them — with XMLHttpRequest or fetch, which runs on it — goes out with a freshly minted W3C traceparent header, whose 32-hex trace id the request's network entry records. When that backend runs @fixback/node, it adopts the same id for any error the request causes, so the app's Issue for a failed request and the server Issue behind it show the same Correlation id.

<FixbackProvider
  options={{
    key: "pk_…",
    propagateTo: ["https://api.example.com"], // your own APIs, as origins
  }}
>
  <App />
</FixbackProvider>
  • Nothing is sent unless you list it. The web SDK always stamps requests to the page's own origin; an app has no origin of its own, so here the list is the whole rule, and a third-party API never sees a header it did not ask for.
  • Entries are bare origins — scheme://host[:port], no paths, no wildcards — the same shape as the web SDK's option. Anything else is ignored with a warning in development builds.
  • It never breaks a request. The SDK never reads a header value and never overrides one: a traceparent your app or its tracing library sets is the one that goes out, and that request records no id. A request whose header cannot be set simply goes out without it; with network capture off, nothing is sent.
  • The sampled flag is 01, so a parent-based OpenTelemetry sampler on your server records these requests. The id is random: it identifies one request, never a person or a session.

Connect — reporting as an account

A Reporter is a Fixback Account (ADR-0066). The composer's identity row shows "Anonymous · Sign in"; tapping it opens Fixback's connect page — the same doors as the web: Google, GitHub, Sign in with Apple, and magic link. The SDK exchanges the code it hands back for a Reporter session stored in SecureStore, and the composer then shows the Account's name and tier. A Member of the owning Org reports at the internal tier.

The one thing to set up is your app's link on the Project — Allowed origins → App link (myapp://fixback-connect; register the scheme in app.json). It is how Fixback re-opens your app with the session on it, and it is the same row an invite lands through. With it listed, signIn() works on the minimal install below — no returnUrl, nothing else to declare (ADR-0052).

<FixbackProvider options={{ key: "pk_…", origin: "https://com.acme.myapp" }}>

Optionally, declare returnUrl and the sign-in returns in place, through an in-app auth session, instead of sending the person out to the browser and back:

<FixbackProvider
  options={{
    key: "pk_…",
    origin: "https://com.acme.myapp",
    // A scheme or universal link your app handles; its origin must be on the
    // Project's allowed origins. Register the scheme in app.json (`scheme`).
    returnUrl: "myapp://fixback-connect",
  }}
>

What to allowlist for returnUrl. Its origin, and a custom scheme is fine — no universal link, associated-domains entitlement, or apple-app-site-association file is required. In the dashboard's Allowed origins card switch to Return URL and paste the whole link; the card shows the origin it will store before you add it (myapp://fixback-connect/done is stored as myapp://fixback-connect, because an origin carries no path). A development build returns through its own scheme (exp://…, exp+myapp://…), which is a separate origin and needs its own entry. Fixback refuses to mint a Connect code for an unlisted return URL — that is what stops a stolen publishable key redirecting a sign-in somewhere else.

signIn / signOut / identity are on the useFixback() handle and the module-level Fixback, so you can put them on your own settings screen — together with status, canSubmit() and onIdentity(), so the screen can say whether reporting is live without re-deriving the Gate rule in app code:

import { Fixback, useFixback } from "@fixback/expo";

await Fixback.signIn();   // opens the connect page, binds the Account
Fixback.identity();       // { status: "anonymous" } | { status: "connected", name, email, tier }
await Fixback.signOut();  // revokes the session and clears SecureStore

// In a component — `status` re-renders, `onIdentity` keeps the row current:
const { status, canSubmit, onIdentity } = useFixback();
useEffect(() => onIdentity(setWho), [onIdentity]);
const armed = canSubmit(); // or: status === "ready"

Invited testers on mobile

Where the key's environment is Invited- or Internal-gated, the shake gesture stays disarmed until a Connect makes the Account eligible — so a private beta shows nothing to a signed-out visitor. An invite admits an Account, not a device, and claiming one lands that Account in your app:

  1. List your app's link on the Project — Allowed origins → App link (myapp://fixback-connect; register the scheme in app.json). This is the one piece of setup the journey needs.
  2. Invite the tester by email from the Project.
  3. They open the link on their phone, sign in, and tap Open the app.
  4. Your app opens, the SDK redeems the one-time code it was handed, and the shake gesture arms (ADR-0046, ADR-0050).

The SDK reads the code off the link the app was opened with, or one delivered while it runs, and redeems a given code once however often the OS replays it. This needs no returnUrl — the claim is minted against the app link the Project lists, not against anything in init. signIn() from your own settings screen needs no returnUrl either — it goes out through the same app link. Set returnUrl when you would rather that sign-in returned in place, without leaving the app; when it is set, the SDK also narrows inbound links to that origin.

Fixback.signIn() remains the second door, and the one to use when someone is already in your app. It opens the same connect page, and the browser it opens in shares Safari's cookies on iOS (and Chrome's on Android), so someone already signed in to Fixback is usually one tap from a session rather than a full sign-in. Where a tester has more than one build installed — a store build and a dev client, listed as two app links — the page names each by its link and lets them pick, exactly as the claim page does.

Nothing happening? In a development build the SDK says why it is dormant — an unknown key, an API it could not reach, an unlisted origin, or a Gate this Reporter does not pass, named with the values boot returned, including which environment's Gate it was (so a build carrying another environment's key says so). Production builds stay silent. A gated environment whose tester reads as public has not had its invite land: check the app link is allowlisted, and that tapping the claim page's button opens your app. The same row is what Sign in goes out through, so a Project that lists no app link says so on the connect page rather than failing quietly. Widening the Gate makes the symptom disappear by turning off the control you set — it is a diagnostic, not a fix.

Sign in with Apple

Fixback's connect page does not offer Sign in with Apple for now. It offers Google, GitHub, and the magic link, and the Apple door is hidden until it is set up (ADR). Because Connect offers Google sign-in inside an iOS app, Apple's App Store Review Guideline 4.8 can require an equivalent login option for apps distributed on the App Store. Check your app against it before you submit.

Options

| Option | Default | What it does | | --- | --- | --- | | key | — | The publishable key of the environment this build reports into (required) — see One key per environment. | | origin | — | Optional. The origin sent on every request — set one only to give mobile reports an origin to filter by (see above). | | apiUrl | https://api.fixback.dev | Self-hosted Fixback API base. Set this when your Project lives on a self-hosted deployment — a key minted there is unknown to the public API. | | returnUrl | — | Where Connect returns after a sign-in your app starts — a custom scheme or universal link your app handles; its origin must be on the Project's allowed origins. It makes signIn() return in place through an in-app auth session, and narrows which inbound links the SDK reads a code off. Without it signIn() still works, through the browser and your Project's own app link (ADR-0052); so does an invite (see invited testers). | | hostIdentity | — | A server-minted identity JWT (internal tier). | | capture | server config | { console?, network?, replay? } per-stream toggles. replay is off unless you set it — see session replay. | | propagateTo | [] — nothing sent | Your own APIs, as origins (["https://api.example.com"]), whose requests carry the Correlation id. A request to any other origin is never touched. | | autoCapture | true | Automatic uncaught-error reports. | | screenshots | true | Capture a screenshot when the composer opens. | | replay | off | Session-replay tuning — { fps?, maxEdge?, frameQuality? }. Passing it also turns replay on. | | beforeSend | — | Reshape or drop (null) any report before transport. | | scrub | true | The built-in URL/PII scrubbers. | | beforeBreadcrumb | — | Filter/edit trace entries at the source. | | shake | on | { enabled?, thresholdG?, minPeaks?, windowMs?, minGapMs?, cooldownMs?, sampleIntervalMs? }. | | enabled | true | Master gate — false keeps the SDK completely dormant (no boot call, nothing instrumented). | | release | — | The build identifier stamped on every capture, and shown on the Issue. Not yet symbolicated on this platform — see below. |

Sourcemaps on Expo

Symbolication does not work on Expo yet. release is stamped on captures and shown on the Issue, but a mobile stack trace is not resolved back to your source.

fixback sourcemaps upload discovers .js.map / .mjs.map / .cjs.map only, which matches none of the names a React Native build emits (index.android.bundle.map, main.jsbundle.map, index.ios.bundle.map) — so an upload run against an Expo build finds nothing and exits quietly. Hermes needs more than discovery besides: its bytecode frames carry an address at prefix, always report line 1, and count columns from 0 where the web counts from 1.

Set release anyway if it is useful to you as a label. Web and Node symbolication are unaffected — see @fixback/vite-plugin and @fixback/cli.

Session replay

A report can carry a replay of the screen: a buffered window of the last 30–60 seconds at one frame a second, encoded into a single H.264/MP4 when the report is sent, and played back in the Issue's Replay tab beside the merged console/network timeline. The encoder ships with this package as @fixback/expo-replay-encoder; you never install or call it yourself.

<FixbackProvider
  options={{
    key: "pk_…",
    capture: { replay: true },        // opt in
    replay: { fps: 1, maxEdge: 960 }, // optional tuning (also opts in)
  }}
>
  <App />
</FixbackProvider>

Two things are required, and both are deliberate:

  • You opt in, in code — capture: { replay: true }, or just a replay tuning object. The recording is not masked: it is the screen as your app drew it. Updating this SDK never starts one.
  • The Project enables replay in its capture settings. That toggle is the kill switch: with it off, nothing is recorded whatever your code asks for.

Replay needs a development build. The encoder is native code, and Expo Go loads only the native modules it shipped with. Run npx expo prebuild and build with EAS or locally. In Expo Go the SDK logs why and reports without replay — nothing else changes, and an app that never enables replay keeps working in Expo Go exactly as before.

Every report carries the window, including the automatic ones an uncaught error files with no prompt. Enable it only where recording your users' screens is something you have told them about.

| Key | Default | What it does | | --- | --- | --- | | fps | 1 | Frames per second. Each step up multiplies both the per-second cost on the JS thread and the payload. | | maxEdge | 960 | Longest edge of the encoded video in pixels. A smaller screen is never scaled up. | | frameQuality | 0.6 | JPEG quality of each captured frame, 0–1 — the frames are an intermediate the encoder discards. |

Privacy

The same client-side scrubbing as the web SDK: URLs lose query strings and path PII, console/error text loses emails, bearer tokens, and long digit runs, network crumbs never carry bodies or header values — the one header the SDK touches is the Correlation id it minted itself, sent only to the APIs you list. Two differences are deliberate.

Mobile screenshots are not auto-masked, so a screenshot only ever ships from the composer, where the Reporter sees exactly what will be sent and can remove it. Automatic error reports never attach a screenshot. A screenshot the Reporter removes (or that has been delivered) is deleted from the app's cache, and screenshots: false disables capture entirely.

Session replay is not masked either, and unlike the screenshot it does ride along on automatic reports — which is why it records nothing until you enable it in code and the Project enables it too. Frames live in the app's cache only as long as the window needs them, and the window is deleted once the report is sent or the composer is dismissed.

beforeSend remains the programmatic choke point for everything. It can also group a captured error your own way: set report.fingerprint, in which "{{ default }}" stands for the fingerprint the SDK computed — ["{{ default }}", screen] splits the usual grouping by screen. An error's fingerprint is its type and your app's own top frames by function, never by line, the message counting only when the frames cannot name where it was thrown.

What's not here (yet)

Element annotation/drawing, unhandled-promise-rejection and native-crash capture. An invite now lands in the app itself, so redemption is no longer on this list, and neither is session replay. See the Expo SDK reference for the full contract.

Packaging

The published build is CommonJS (dist/, with a nested {"type":"commonjs"} manifest), exactly like @fixback/node and @fixback/sdk-core. Metro resolves CommonJS natively, and so do Node-based tools — Jest, an Expo config plugin, a script that require()s the package — which the previous extensionless-ESM output could not.

License

MIT