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

hazo_embed

v0.2.1

Published

App-agnostic embeddable-widget toolkit — reserved-param contract, iframe snippet generation, HMAC+DB embed keys, and a postMessage auto-resize runtime. Consumers supply the widget component + tool registry.

Readme

hazo_embed

App-agnostic embeddable-widget toolkit. It knows nothing about any particular timer/calculator/tool — consumers supply the widget component and a tool registry (slug ↔ config) on their own side. Generalizes the embed pattern gotimer built bespoke (gotimer/src/lib/embed/*, embed.js route) into a reusable package for a second app.

Installation

npm install hazo_embed hazo_secure hazo_connect hazo_api

Peer Dependencies

| Package | Required | Notes | |---------|----------|-------| | react / react-dom | ^18 or ^19 | Required | | hazo_secure | ^1.4.0 | Required for ./server — HMAC sign/verify (hazo_secure/crypto) | | hazo_connect | ^3.9.2 | Required for ./serverembed_keys store CRUD | | hazo_api | ^2.5.1 | Required for ./apiok()/fail() response envelopes | | next | ^14 or ^16 | Optional — needed for ./api route factories and ./runtime's embedJsRouteBody() | | hazo_auth | ^10.8.2 | Optional — bring your own getUserId resolver for ./api | | hazo_seo | ^0.4.0 | Optional — not imported directly; reserved for future embed-page metadata helpers | | hazo_ui | ^6.0.0 | Optional — required only if you use <EmbedCodeGenerator> (./client); not needed for the embed runtime/iframe/server entries | | hazo_theme | ^1.0.0 | Optional — required alongside hazo_ui for <EmbedCodeGenerator> |

If you use <EmbedCodeGenerator>, also add the Tailwind v4 @source directive so its utility classes compile (see hazo_ui/hazo_theme set up in your app first):

@import "tailwindcss";
@source "../node_modules/hazo_embed/dist";

Subpaths

| Import path | Contents | |-------------|----------| | hazo_embed | Pure, server-safe: EmbedParams/EmbedMode/etc. types, parseEmbedParams/serializeEmbedParams, buildEmbedSnippet | | hazo_embed/client | "use client"<EmbedCodeGenerator> (inline embed-code panel), <EmbedWatermark> (backlink) | | hazo_embed/server | Server-only — createEmbedKeyStore, mintEmbedKey, resolveEmbedRender, HMAC sign/verify wrappers | | hazo_embed/api | Next.js Route Handler factories — createEmbedKeyRoute, createEmbedKeyListRoute, createEmbedKeyRevokeRoute | | hazo_embed/runtime | <EmbedFrame> + useEmbedAutoResize (widget side), embedJsRouteBody() (host-page /embed.js helper) |

Quick Start

1. Run the schema setup

psql "$DATABASE_URL" -f node_modules/hazo_embed/ddl/postgres.sql
# or
sqlite3 path/to/app.sqlite < node_modules/hazo_embed/ddl/sqlite.sql

2. Set HAZO_EMBED_SECRET

HAZO_EMBED_SECRET=$(openssl rand -base64 32)

Without this env var, resolveEmbedRender always degrades to the watermark-only stub (fails closed, never throws).

3. Parse embed params on your embed route

// app/e/[toolSlug]/page.tsx
import { parseEmbedParams } from "hazo_embed";

const params = parseEmbedParams(searchParams, { reserved: ["your_tool_specific_key"] });
// params.config now holds everything your existing ?restore= seed path expects.

4. Resolve the render decision

import { resolveEmbedRender, createEmbedKeyStore, getEmbedSecret } from "hazo_embed/server";
import { getHazoConnectSingleton } from "hazo_connect/nextjs/setup";

const decision = await resolveEmbedRender(
  {
    keyPublic: searchParams.get("key_public"),
    origin: headers().get("origin"),
    referer: headers().get("referer"),
    secFetchSite: headers().get("sec-fetch-site"),
    toolSlug: "your-tool-slug",
  },
  { secret: getEmbedSecret(), store: createEmbedKeyStore(await getHazoConnectSingleton()) },
);

if (decision.full) {
  // render the widget in decision.mode
} else {
  // render <EmbedWatermark> only — still a backlink impression
}

5. Mount the widget runtime + host helper

// widget side
import { EmbedFrame } from "hazo_embed/runtime";
<EmbedFrame><YourWidget /></EmbedFrame>

// app/embed.js/route.ts (host page helper)
import { embedJsRouteBody } from "hazo_embed/runtime";
export async function GET() {
  return new Response(embedJsRouteBody(), {
    headers: { "Content-Type": "application/javascript; charset=utf-8" },
  });
}

6. Wire the key-issuance API (optional — for signed keys)

// app/api/embed/keys/route.ts
import { createEmbedKeyRoute, createEmbedKeyListRoute } from "hazo_embed/api";
import { createEmbedKeyStore, getEmbedSecret } from "hazo_embed/server";
import { getHazoConnectSingleton } from "hazo_connect/nextjs/setup";

const ctx = {
  getUserId: async (req) => (await yourAuthLookup(req))?.id ?? null,
  getStore: async () => createEmbedKeyStore(await getHazoConnectSingleton()),
  getSecret: () => getEmbedSecret(),
};

export const POST = createEmbedKeyRoute(ctx);
export const GET = createEmbedKeyListRoute(ctx);

Reserved param contract

Base presentation keys (theme, size, accent, bg, font, controls, branding, mode) are always pulled onto typed EmbedParams fields. Everything else lands in config. Pass opts.reserved to parseEmbedParams to carve out your own additional typed keys (e.g. a timer app's started/autostart/on_expire) — those land in config as raw, uncoerced strings so your own type logic re-parses them exactly as before.

Embed code generator

<EmbedCodeGenerator> (from hazo_embed/client) is an always-inline panel — it no longer takes open/onClose props; mount it wherever you want the generator to render (a settings page, a dashboard tab, etc.) and control visibility yourself if needed (e.g. with your own {visible && <...>} guard or a hazo_ui tab/dialog). It's built on hazo_ui (Select/Input/ Checkbox/Label/Button/HazoUiDialog). Layout: a controls form on the left (theme/size/accent/controls/mode selectors, a declared-domains input, an extraControls slot for your own fields, and a "Show 'Powered by' branding" checkbox) drives a live-preview iframe on the right; the styled code panel with a copy button spans the full width below. Pass extraControls to inject consumer-specific inputs — wire them to update the config prop so the live preview re-renders as the user edits them.

Props

| Prop | Type | Required | Notes | |------|------|----------|-------| | toolSlug | string | Yes | Becomes part of the embed URL path | | toolName | string | Yes | Shown in the preview title | | origin | string | Yes | Host app origin, e.g. https://example.com | | config | Record<string, unknown> | No | Tool-specific config merged into EmbedParams.config | | keyPublic | string | No | Signed embed key to include in the generated snippet | | watermark | EmbedWatermarkSpec | Yes | .text/.href drive the "Powered by" backlink | | defaults | Partial<{ theme: EmbedTheme; size: EmbedSize; accent: string; controls: EmbedControls; mode: EmbedMode }> | No | Initial values for the presentation fields | | onDeclareDomains | (domains: string[]) => void | No | Called as the user edits the "declared domains" field | | showBrandingToggle | boolean | No | Render the branding checkbox at all. Default true | | defaultBrandingShown | boolean | No | Initial branding-shown state. Default true | | onRemoveBranding | () => boolean \| Promise<boolean> | No | Veto hook called when the user unchecks branding — see below | | upgradeUrl | string | No | CTA href opened by the built-in upgrade dialog | | upgradeDialog | { title?: string; description?: string; ctaText?: string } | No | Overrides the upgrade dialog's copy | | extraControls | ReactNode | No | Consumer fields rendered in the left column below the built-in ones — wire them to config to drive the live preview | | className | string | No | Applied to the panel's root element |

Branding-gate semantics

  • Checking the box (restoring branding) is always free and always allowed.
  • Unchecking it (removing branding) calls onRemoveBranding():
    • undefined (prop omitted) → allowed, branding is removed for free.
    • Returns or resolves truthy → allowed, branding is removed.
    • Returns or resolves falsy, or throws/rejects → branding stays on and the built-in upgrade dialog (HazoUiDialog) opens, using your upgradeDialog copy plus an "Upgrade plan" CTA that opens upgradeUrl in a new tab.
  • The generated snippet reflects the current state: branding shown → watermark.show=true and params.branding="full"; branding removed → watermark.show=false and params.branding="hidden".

Free path — branding removal always allowed

import { EmbedCodeGenerator } from "hazo_embed/client";

<EmbedCodeGenerator
  toolSlug="your-tool-slug"
  toolName="Your Tool"
  origin="https://yourapp.com"
  watermark={{ show: true, text: "Powered by YourApp", href: "https://yourapp.com" }}
  onRemoveBranding={() => true}
/>

Paid gate — veto with upgrade CTA

import { EmbedCodeGenerator } from "hazo_embed/client";

<EmbedCodeGenerator
  toolSlug="your-tool-slug"
  toolName="Your Tool"
  origin="https://yourapp.com"
  watermark={{ show: true, text: "Powered by YourApp", href: "https://yourapp.com" }}
  onRemoveBranding={() => currentUser.plan === "paid"}
  upgradeUrl="https://example.com/pricing"
  upgradeDialog={{
    title: "Upgrade to remove branding",
    description: "Removing the \"Powered by\" badge requires a paid plan.",
    ctaText: "See plans",
  }}
/>

onRemoveBranding can also be async (e.g. a server check) — return/resolve a boolean, or throw/reject to fall back to the upgrade dialog:

<EmbedCodeGenerator
  /* ...required props... */
  onRemoveBranding={async () => {
    const res = await fetch("/api/billing/can-remove-branding");
    if (!res.ok) throw new Error("check failed");
    return (await res.json()).allowed;
  }}
  upgradeUrl="https://example.com/pricing"
/>

Consumer requirements

  • hazo_ui ^6.0.0 and hazo_theme ^1.0.0 are optional peer deps — required only if you use <EmbedCodeGenerator>. The embed runtime/iframe (./runtime) and server entries (./server, ./api) don't need them.
  • Tailwind v4: add @source "../node_modules/hazo_embed/dist"; alongside your existing hazo_ui/hazo_theme @source entries so the generator's utility classes compile.

License

MIT