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

facer-widget

v1.0.9

Published

Drop-in camera widget for the Facer face recognition API — vanilla & React

Downloads

1,172

Readme

facer-widget

Drop-in face recognition camera widget for the Facer API. Works as a plain HTML custom element or a React component — no framework required.

Installation

npm install facer-widget

Or via CDN (no install needed):

<script src="https://unpkg.com/facer-widget/dist/facer-widget.js"></script>

Quick Start

Vanilla HTML

<!doctype html>
<html>
  <head>
    <script src="https://unpkg.com/facer-widget/dist/facer-widget.js"></script>
  </head>
  <body>
    <facer-cam
      mode="search"
      api-url="https://your-api.com"
      token-url="https://your-backend.com/session-token"
      collection="staff"
    ></facer-cam>

    <script>
      document.querySelector("facer-cam").addEventListener("facer:result", (e) => {
        console.log("Result:", e.detail);
      });
    </script>
  </body>
</html>

React

npm install facer-widget

Search

import { useState } from "react";
import { FacerCam } from "facer-widget/react";
import type { SearchResult } from "facer-widget";

export function FaceSearch() {
  const [result, setResult] = useState<SearchResult | null>(null);

  return (
    <div>
      <FacerCam
        mode="search"
        apiUrl="https://your-api.com"
        tokenUrl="https://your-backend.com/session-token"
        collection="staff"
        topK={5}
        onResult={(r) => setResult(r as SearchResult)}
        onError={(e) => console.error(e.error)}
        style={{ width: "100%", maxWidth: 400 }}
      />

      {result?.results.map((match) => (
        <p key={match.face_id}>
          {match.person_id} — {Math.round(match.confidence * 100)}%
        </p>
      ))}
    </div>
  );
}

Enroll

import { FacerCam } from "facer-widget/react";
import type { EnrollResult } from "facer-widget";

export function FaceEnroll() {
  return (
    <FacerCam
      mode="enroll"
      apiUrl="https://your-api.com"
      tokenUrl="https://your-backend.com/session-token"
      collection="staff"
      personId="john_doe"
      metadata={JSON.stringify({ department: "engineering" })}
      onResult={(r) => {
        const res = r as EnrollResult;
        if (res.enrolled) console.log("Enrolled:", res.face_id);
      }}
      style={{ width: "100%", maxWidth: 400 }}
    />
  );
}

Match (compare two faces)

import { FacerCam } from "facer-widget/react";
import type { MatchResult } from "facer-widget";

export function FaceMatch() {
  return (
    <FacerCam
      mode="match"
      apiUrl="https://your-api.com"
      tokenUrl="https://your-backend.com/session-token"
      onResult={(r) => {
        const res = r as MatchResult;
        console.log(res.match ? "Match!" : "No match", res.confidence);
      }}
      style={{ width: "100%", maxWidth: 400 }}
    />
  );
}

ID Verify (selfie vs ID photo)

import { FacerCam } from "facer-widget/react";
import type { VerifyResult } from "facer-widget";

export function IDVerify() {
  return (
    <FacerCam
      mode="verify"
      apiUrl="https://your-api.com"
      tokenUrl="https://your-backend.com/session-token"
      onResult={(r) => {
        const res = r as VerifyResult;
        console.log(res.match ? "Verified" : "Failed", res.confidence);
      }}
      style={{ width: "100%", maxWidth: 400 }}
    />
  );
}

Multi-mode with tab strip

import { FacerCam } from "facer-widget/react";

export function MultiMode() {
  return (
    <FacerCam
      modes="detect,search,enroll"
      apiUrl="https://your-api.com"
      tokenUrl="https://your-backend.com/session-token"
      collection="staff"
      onResult={(r) => console.log(r)}
      style={{ width: "100%", maxWidth: 400 }}
    />
  );
}

Fetch session token manually

If you prefer to manage the token yourself instead of using tokenUrl:

import { useState, useEffect } from "react";
import { FacerCam } from "facer-widget/react";

export function WithManualToken() {
  const [token, setToken] = useState<string | null>(null);

  useEffect(() => {
    async function fetchToken() {
      const res  = await fetch("https://your-backend.com/session-token", { method: "POST" });
      const data = await res.json();
      setToken(data.token);
    }
    fetchToken();
    // Refresh every 4 minutes — tokens expire in 5 minutes by default
    const id = setInterval(fetchToken, 4 * 60 * 1000);
    return () => clearInterval(id);
  }, []);

  if (!token) return <p>Loading…</p>;

  return (
    <FacerCam
      mode="search"
      apiUrl="https://your-api.com"
      sessionToken={token}
      collection="staff"
      onResult={(r) => console.log(r)}
      style={{ width: "100%", maxWidth: 400 }}
    />
  );
}

ESM (Vite / webpack)

import "facer-widget";
// <facer-cam> is now registered as a custom element

Authentication

The widget supports two credential types:

Session token (recommended for browsers)

Your backend mints a short-lived token using your secret API key and passes it to the widget. The widget never sees your secret key.

<!-- Widget auto-fetches and refreshes the token -->
<facer-cam
  token-url="https://your-backend.com/session-token"
  mode="search"
></facer-cam>

Your backend endpoint (POST /session-token) should call:

POST https://your-api.com/face/session-token
Authorization: Bearer <your-secret-api-key>
Content-Type: application/json

{ "duration_seconds": 300 }

And return:

{ "token": "v4.local...." }

Secret API key (server-side only)

<!-- Never use a secret key in a public webpage -->
<facer-cam api-key="sk_live_..." mode="detect"></facer-cam>

Modes

| Mode | Description | Call limit | |------|-------------|-----------| | detect | Detects faces in the camera frame | 2 per session | | search | Searches enrolled faces in a collection for a match | 2 per session | | enroll | Auto-enrolls a face when detected — requires person-id prop | 3 per session | | match | Compares two faces captured from the camera | unlimited | | verify | Compares a selfie against an ID photo (upload or camera) | unlimited |

Once the per-session limit is reached the widget emits facer:error with context: "limit_reached" and stops scanning. The user must reload to reset.

Enable multiple modes with the modes attribute — the widget renders a tab strip:

<facer-cam modes="detect,search,enroll" ...></facer-cam>

Attributes

| Attribute | Type | Description | |-----------|------|-------------| | mode | string | Active mode: detect | search | enroll | match | verify | | modes | string | Comma-separated list of enabled modes (renders tab strip) | | api-url | string | Base URL of your Facer API (default: https://api.facer.io) | | session-token | string | Short-lived PASETO widget token | | api-key | string | Secret API key (server-side only) | | token-url | string | Your backend URL — widget POSTs here to fetch/refresh a session token | | collection | string | Collection name for enroll and search modes | | person-id | string | Required for enroll. Set by the developer — the widget auto-enrolls as soon as a face is detected. Not a user input. | | metadata | string | Optional JSON metadata string stored with the enrolled face | | top-k | number | Max results returned by search (default: 5) | | auto | boolean | Continuously capture and submit at interval ms | | interval | number | Milliseconds between auto-captures (default: 2000) |


Events

Listen for events on the <facer-cam> element:

const widget = document.querySelector("facer-cam");

widget.addEventListener("facer:result", (e) => {
  // e.detail contains the API response
  console.log(e.detail);
});

widget.addEventListener("facer:error", (e) => {
  // e.detail.context: "token_fetch" | "limit_reached" | undefined
  console.error(e.detail.error, e.detail.context);
});

widget.addEventListener("facer:token", (e) => {
  // Fired when a session token is fetched/refreshed
  console.log("Token expires in", e.detail.expires_in, "seconds");
});

widget.addEventListener("facer:usage", (e) => {
  // Fired before every API call with running totals per mode
  // e.detail: { detect, search, enroll, match, verify, total }
  console.log("API calls so far:", e.detail.total, e.detail);
});

Result shapes

detect

{
  "count": 1,
  "faces": [{ "bbox": [x0, y0, x1, y1], "confidence": 0.99 }]
}

search

{
  "collection": "staff",
  "count": 1,
  "results": [{
    "person_id": "john_doe",
    "confidence": 0.94,
    "distance": 0.21,
    "collection_name": "staff"
  }]
}

enroll

{
  "enrolled": true,
  "person_id": "john_doe",
  "collection": "staff",
  "face_id": "uuid"
}

match / verify

{
  "match": true,
  "confidence": 0.96,
  "distance": 0.18
}

React Props

| Prop | Type | Description | |------|------|-------------| | mode | Mode | Active mode | | modes | string | Comma-separated enabled modes | | apiUrl | string | API base URL | | sessionToken | string | Short-lived widget token | | apiKey | string | Secret API key (server-side only) | | tokenUrl | string | Backend URL for auto token fetch | | collection | string | Collection name | | personId | string | Person ID for enroll | | metadata | string | Metadata for enroll | | topK | number | Max search results | | auto | boolean | Auto-capture mode | | interval | number | Auto-capture interval (ms) | | onResult | (result: FacerResult) => void | Result callback | | onError | (detail: { error: string; context?: string }) => void | Error callback | | onUsage | (counts: { detect: number; search: number; enroll: number; match: number; verify: number; total: number }) => void | Fired before each API call with running totals |


License

MIT