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

@flamioai/web-sdk

v0.9.0

Published

Lightweight SDK for continuous UX session recording on production sites

Readme

@flamioai/web-sdk

Lightweight JavaScript SDK for continuous UX session recording on production sites. It captures rrweb DOM recordings, console errors, network failures, Web Vitals, scroll depth and SPA navigation, then batches and uploads them to FlamioAI for real-time UX analysis.

Dashboard: create a project, grab your projectKey, and watch the recorded sessions & AI analysis at admin.flamio.org (Projects → On-Live → Setup).

Installation

npm install @flamioai/web-sdk

Or load it directly in the browser:

<script type="module">
  import { Flamio } from 'https://esm.sh/@flamioai/web-sdk';
  Flamio.init({ projectKey: 'flm_...', endpoint: 'https://api.flamio.org/api' });
</script>

Quick start

import { Flamio } from '@flamioai/web-sdk';

Flamio.init({
  projectKey: 'flm_your_project_key', // from admin.flamio.org → On-Live → Setup
  endpoint: 'https://api.flamio.org/api',
  version: '1.4.2', // your release version (optional but recommended)
});

init() starts recording immediately and returns the instance, or null when recording doesn't start (session not sampled, running server-side, or consent is denied/wait). Call it once, as early as possible in your app.

Configuration

All options are passed to Flamio.init(config).

| Option | Type | Default | Description | | ----------------------- | --------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | projectKey | string | — | Required. Your project's ingest key from the dashboard (On-Live → Setup). | | endpoint | string | — | Required. FlamioAI ingestion URL, e.g. https://api.flamio.org/api. | | version | string | — | Your site/release version (e.g. '1.4.2'). Attached to every session so the dashboard can filter sitemap & analytics per version and measure release impact. | | sampleRate | number (0–1) | 1.0 | Fraction of sessions to record. 0.25 records ~25%. The decision is drawn once per visit and kept in localStorage with it, so every page of a visit agrees. | | maxConcurrentSessions | number | 15 | Max sessions recorded at once for this project (hard-capped at 100 server-side). Beyond it, new visitors aren't recorded — a safety valve against traffic spikes. | | consent | 'granted' \| 'denied' \| 'wait' | 'granted' | Consent gate. 'granted' records now; 'denied' records nothing; 'wait' holds until you call Flamio.consent('granted'). See Consent. | | maskTextContent | boolean | false | Mask all visible text in the recording, not just inputs. Use for text-sensitive apps. (Input values are always masked regardless — see Privacy.) | | maskSelectors | string[] | [] | CSS selectors whose text is blanked (same effect as the flamio-mask class, no markup changes). See Privacy. | | blockSelectors | string[] | [] | CSS selectors whose elements are omitted from the recording (same effect as the flamio-block class). | | urlAllowlist | string[] | [] | If non-empty, record only on URLs matching one of these patterns. * is the only wildcard; case-insensitive substring. Enforced in the browser. | | urlBlocklist | string[] | [] | Never record on URLs matching one of these patterns (takes precedence over the allowlist). Blocked pages send nothing. | | checkoutEveryNms | number (ms) | 30000 | How often rrweb takes a full DOM snapshot. Snapshots are seek anchors for replay and screenshots — lower = more accurate seeking, larger recordings. | | batchInterval | number (ms) | 5000 | How often buffered events are flushed and uploaded. | | batchMaxSize | number (chars of JSON) | 1048576 | Largest batch, in uncompressed JSON characters (≈ bytes). A buffer this full is sent early; a larger backlog goes in several batches. Was 50000 before 0.9.0. | | idlePauseMs | number (ms) | 300000 | A visible tab with no click, scroll, typing or touch this long stops DOM recording; the next input resumes the same session with a fresh snapshot. 0 turns it off. | | captureConsoleWarn | boolean | false | Also record console.warn (errors are always recorded). | | retryDelayMs | number (ms) | 1000 | Base delay for retrying a failed upload (exponential backoff from here). | | debug | boolean | false | Log SDK lifecycle events to the console. Turn off in production. | | guide | GuideConfig | — | The in-page Guide. { launcher: true } shows the "Guide" pill; without it the guide opens only from Flamio.guide(). |

API

Flamio.init(config): Flamio | null

Starts recording. See Configuration. Returns null if the session isn't recorded (not sampled / SSR / consent not granted). Safe to call once; a second call is a no-op.

Flamio.identify(userId, traits?)

Attach a stable user id (and optional traits) to the current session, so the dashboard can group sessions by real user and answer "what did user X do".

Flamio.identify('user_123', { email: '[email protected]', plan: 'pro' });

Flamio.consent(status)

Update consent at runtime — the counterpart to the consent: 'wait' config.

Flamio.init({ projectKey, endpoint, consent: 'wait' }); // recording deferred
// ...after the user accepts your cookie banner:
Flamio.consent('granted'); // starts recording
// or:
Flamio.consent('denied'); // stops and drops the buffer unsent

Flamio.stop()

Stop recording and flush the final batch (e.g. on logout).

Flamio.guide(intent?)

Open the in-page guide, loading its chunk on first use. With an intent the run starts straight away; without one the visitor gets the launcher and types their own goal. Resolves to true once the guide is open — with an intent, once the run has started — and false if it could not open. See Guide.

Flamio.stopGuide()

End the current guided run. The guide stays mounted, so the next Flamio.guide() opens instantly.

Guide

The guide walks a visitor through a goal in their own browser: it reads the page, moves a ghost cursor to the right control, captions why, and performs the click or keystroke while the visitor watches. The first run of a goal is planned by a model, step by step; the successful path is saved as a recipe, so every later run of the same goal replays it with no planner calls, and a replay is billed nothing. A phrasing we haven't seen before still costs one small call to recognise it as the same goal — on a replay that one is on us. When the page has moved under a recipe the guide re-plans the broken stretch; those planned steps are billed, the replayed ones are not.

Flamio.init({
  projectKey: 'flm_your_project_key',
  endpoint: 'https://api.flamio.org/api',
  version: '1.4.2', // strongly recommended — see below
  guide: { launcher: true, position: 'bottom-right' },
});

// or open it from your own button, with or without a goal:
document.querySelector('#help')?.addEventListener('click', () => {
  Flamio.guide('add a teammate');
});

| GuideConfig | Type | Default | Description | | ------------- | --------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------- | | launcher | boolean | false | Show the "Guide" pill once the page goes idle. Without it the guide opens only from your code. | | position | 'bottom-right' \| 'bottom-left' | 'bottom-right' | Which corner the pill and the step bar sit in. |

Enable it per project. The guide routes answer only for a project with the guide feature on and a non-empty origin allowlist — the guide fails closed, so * is refused. Ask us to turn it on for your project's domain.

One allowlist, two features. That same origin list also guards On-Live ingest, where an empty list means "any origin". Filling it in to switch the guide on therefore narrows recording to exactly the hosts it names: every origin that records today has to be listed, or its batches start coming back 403 Origin not allowed for this project. Name them all in the same change that enables the guide.

Set version. Recipes are keyed by your version, so a release that moves a button doesn't replay a stale path: the new version starts from the recipe carried forward from the previous one and re-verifies it. Without version all recipes share one bucket and drift is only caught after a step fails.

Privacy markers are honoured. The page description sent to the planner obeys the same markers as the recording: .flamio-block subtrees are omitted entirely, .flamio-mask / data-flamio-mask / maskSelectors names become •••, and maskTextContent: true masks every name on the page. On top of that the guide never sends what is typed into a field — only that it is filled — and it refuses to type into password, card, CVC/IBAN/SSN and one-time-code fields at all: those it hands back to the visitor ("your details are needed"), which is also what happens at checkout for name, e-mail, address and payment.

Bundle cost. The guide lives in a separate guide chunk (~29 KB gzip) that is fetched only when it is actually used — on Flamio.guide(), on resuming a run after a page navigation, or, with launcher: true, once the page is idle. The UMD <script> build is a single file, so there the guide is inlined and there is nothing to load lazily; use the ES build if the extra bytes matter.

Consent

  • granted (default) — record immediately.
  • denied — record nothing: no request to Flamio, nothing written to localStorage, no exit handlers.
  • wait — the same as denied until you call Flamio.consent('granted'). Ideal behind a GDPR/cookie banner: call init({ consent: 'wait' }) on load, then grant/deny from the banner. The visitor id is created and recording starts only at the grant; an identify() made before it is kept in memory and sent after it.
  • consent('denied') after a grant stops recording and throws away whatever is still buffered without sending it. A later consent('granted') on the same page starts recording again.

User identification

Sessions are anonymous by default — tied to a first-party visitorId kept in localStorage. Once you know who the user is (e.g. after login), call Flamio.identify() to attach a real identity:

Flamio.identify('user_123', {
  email: '[email protected]',
  plan: 'pro',
  company: 'Acme',
});
  • userId — your stable id for the user. Lets the dashboard group all of a person's sessions and answer "what did user X do".
  • traits — optional key/value metadata (email, plan, role, …). Shown on the session and searchable/filterable in the dashboard.

The identity travels inside the batch metadata (not as separate headers, so any Unicode name is fine). It rides on uploads until one carrying it is accepted — after identify(), after it changes, and at the start of every new session. Traits whose JSON is larger than 4096 UTF-8 bytes are dropped (the userId still goes). Call identify() as soon as the identity is known — the whole session is attributed to that user, including events recorded before the call.

Privacy

Input values are never recorded (maskAllInputs, always on) — password, email and phone fields are masked at the input level too.

Mark elements to skip from recordings. These conventions work identically in the SDK and the browser extension:

| Marker | Effect | | ------------------------------------------- | ------------------------------------------------- | | class="flamio-mask" or data-flamio-mask | Blank the element's text | | class="flamio-block" | Omit the element entirely (replaced by a box) | | class="flamio-ignore" | Don't record input changes inside it |

You can also drive the same rules from Flamio.init() (CSS selectors, no markup changes needed):

Flamio.init({
  projectKey: 'flm_xxx',
  endpoint: 'https://api.flamio.org/api',
  maskSelectors: ['.card-number', '#ssn'], // blank text
  blockSelectors: ['.id-photo'], // omit element
  // Control WHICH pages record (enforced in the browser — blocked pages never
  // send anything). `*` is the only wildcard; matching is case-insensitive substring.
  urlBlocklist: ['/admin', '/account/*'], // never record here (wins over allowlist)
  urlAllowlist: ['/checkout*'], // if set, record ONLY here
});

Opt into masking all page text with maskTextContent: true.

What it captures

DOM interactions (rrweb), console errors (warnings with captureConsoleWarn), failed & slow network requests (with TTFB / transfer size / initiator when available), Web Vitals (LCP, INP, CLS), scroll depth, SPA route changes, and tab visibility. Console messages are cut to 2 000 characters (stacks to 4 000), repeats of the same message within a second after it are folded into one follow-up entry with a count, and a page records at most 100 of them.

Recording pauses on a tab hidden for 2 minutes and on a visible tab nobody touched for idlePauseMs (5 minutes), and the session ends after 30 minutes of inactivity. A session ends only when the page is really left (pagehide, not entering the back/forward cache): a download link, a cancelled "Leave site?" dialog or Back into a cached page keep it going. When the tab is hidden, what is buffered is sent at once.

Uploads are gzipped in a small Web Worker; where a Content-Security-Policy forbids blob: workers they are compressed on the main thread instead. With the ES build the rrweb recorder (~23 KB gzip) is loaded only when a page actually records — never for a visitor who is sampled out, has not consented, or is on a blocked URL.

Wire protocol

0.9.0 speaks ingest protocol v2 (batch metadata base64url-encoded, identity in the metadata, per-status handling of the server's answers: a refused key stops recording for an hour, 413 splits the batch, 429/503 are retried after Retry-After). It requires a Flamio API that includes #1177; older SDKs keep working against that API.

Session metadata

Each session carries context collected automatically — no config or calls required. It rides on every upload until the server has stored one, so a lost first request no longer loses the session:

| Field | Source | | ---------- | ---------------------------------------------------------------------- | | Visitor id | first-party id in localStorage (anonymous until you identify) | | Device | user agent, screen size, language, platform | | Entry URL | the first page of the visit (location.href) | | Referrer | document.referrer | | UTM params | utm_source / medium / campaign / term / content from the URL | | Version | your version config, when set |

Single-page apps

Set version at deploy time so the dashboard can attribute sessions to a release. Route changes are captured automatically — no manual pageview calls.

License

MIT