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

ahize

v0.2.1

Published

One unified API for 18 live-chat & customer-support widgets — Intercom, Crisp, Tawk.to, Zendesk, HubSpot, Chatwoot, LiveChat, Drift, Freshchat, Olark, Userlike, HelpScout, Smartsupp, LiveAgent, Gist, JivoChat, Tidio, Sendbird. Zero deps, tree-shakeable, S

Downloads

515

Readme

Why

Every live-chat vendor ships its own snippet, its own globals, its own quirks. Wrappers exist for individual ones (react-use-intercom, react-zendesk, tawk-messenger-react, …) — but each pulls in a framework, locks you to one provider, and re-introduces the bugs the underlying snippet already had: SSR crashes, calls dropped before boot, HMAC fields silently missing, no shutdown→boot reversibility.

ahize is one zero-dependency layer over all of them. Same surface, swap providers by changing the import path. Pre-boot calls are buffered. Identity verification is typed. SSR is a no-op. CSP is documented per provider.

Today you're on Intercom:

import * as chat from "ahize/intercom"

await chat.load({ appId: "abc" })
chat.identify({ id: "u1", email: "[email protected]" })
chat.track("plan_upgraded", { tier: "pro" })
chat.show()

Tomorrow you switch to Crisp — only the import path and the load() options change:

import * as chat from "ahize/crisp"

await chat.load({ websiteId: "uuid" })
chat.identify({ id: "u1", email: "[email protected]" })
chat.track("plan_upgraded", { tier: "pro" })
chat.show()

Install

npm install ahize
# pnpm add ahize / yarn add ahize / bun add ahize

Hello, world

import { load, identify, track, show } from "ahize/intercom"

await load({ appId: "abc123" })
await identify({
  id: "user_1",
  email: "[email protected]",
  name: "Ada Lovelace",
})
await track("plan_upgraded", { tier: "pro" })
await show()

That's it. The Intercom CDN is injected, boot is fired, and your identify/track/show calls were buffered and drained in order.

The unified surface

Every provider exports the exact same functions:

load(options); // inject CDN & boot — Promise resolves when widget API attaches
identify(identity); // set user; verification: hmac | jwt | callback
track(event, metadata?); // emit a custom event; generic <T extends EventMetadata>
pageView({ path, locale }); // notify on SPA route change
show();
hide(); // toggle widget visibility
shutdown(); // end session — keeps config so you can re-identify
destroy(); // hard reset — removes script, globals, listeners
ready(); // Promise<void> resolved once the real API is live
isReady();
state(); // synchronous lifecycle ('idle'|'loading'|'ready'|'shutdown')
getIdentity();
onIdentityChange(cb); // typed identity transitions

If you call any method before load() resolves, the call is queued and flushed in order once the provider is ready. No more "Intercom is not defined" warnings.

On top of the unified surface, most providers expose a typed on(event, handler) for their documented lifecycle events (widget open/close, message sent/received, conversation started, unread count, …) and a handful of vendor-native methods — see the Providers table for the per-provider extras.

Identity verification

Most providers want a server-issued HMAC or JWT to prevent users from spoofing each other's profiles. ahize makes it a typed required field:

// Intercom — HMAC of user_id with your app secret
await identify({
  id: "user_1",
  email: "[email protected]",
  verification: { kind: "hmac", hash: process.env.INTERCOM_USER_HASH! },
})

// HubSpot — JWT
await identify({
  id: "user_1",
  email: "[email protected]",
  verification: { kind: "jwt", token: serverIssuedJwt },
})

// Zendesk Messenger — callback for token refresh on 401
await identify({
  id: "user_1",
  verification: {
    kind: "callback",
    getToken: async () => fetchFreshJwtFromServer(),
  },
})

Pass the wrong kind for a provider and you get a typed rejection — no silent drop. See ahize/capabilities for who supports what.

SSR

ahize is safe to import from any server runtime. Every method short-circuits when window/document are unavailable, so this works in Next.js App Router, Nuxt 4, Remix, SvelteKit, Astro, and Cloudflare Workers without guards.

// app/layout.tsx — no "use client" needed for the import itself
import { load } from "ahize/intercom"

await load({ appId: "..." }) // resolves to undefined on the server, no-op

If you want to be belt-and-braces sure no DOM code enters your SSR bundle, import the matching no-op stub:

import { load, identify, track } from "ahize/server"

Framework adapters

Each adapter is framework-agnostic — bring your own React/Vue/Angular, no peer dependencies.

Next.js (App Router)

"use client"
import * as React from "react"
import * as intercom from "ahize/intercom"
import { createAhizeComponent } from "ahize/next"
import { usePathname, useSearchParams } from "next/navigation"

const Ahize = createAhizeComponent(React, { usePathname, useSearchParams })

export default function ChatBoot() {
  return <Ahize provider={intercom} options={{ appId: "abc123" }} autoPageView />
}

Mount once in your root layout. pageView() auto-fires on every route change — fixes HubSpot's targeting rules, keeps Intercom's session tracking accurate.

Nuxt 4 (and Nuxt 3)

// app/plugins/ahize.client.ts   (Nuxt 4 default srcDir)
// plugins/ahize.client.ts       (Nuxt 3, or Nuxt 4 with custom srcDir)
import { defineNuxtPlugin } from "#app"
import * as intercom from "ahize/intercom"
import { createNuxtAhizePlugin } from "ahize/nuxt"

export default defineNuxtPlugin(
  createNuxtAhizePlugin({
    provider: intercom,
    options: { appId: "abc123" },
    autoPageView: true,
  }),
)

The plugin & defineNuxtPlugin API is identical between Nuxt 3 and 4 — only the default source directory changed (app/ in Nuxt 4). Use $ahize from useNuxtApp() to access the provider in components.

React (any meta-framework)

import * as React from "react"
import * as crisp from "ahize/crisp"
import { createUseAhize } from "ahize/react"

const useAhize = createUseAhize(React)

function App() {
  const { isReady, identify, show } = useAhize({
    provider: crisp,
    options: { websiteId: "..." },
  })

  return (
    <button disabled={!isReady} onClick={() => show()}>
      Open chat
    </button>
  )
}

Vue 3, Svelte, SvelteKit, Remix, Astro, Angular

All shipped — see ahize/vue, ahize/svelte, ahize/sveltekit, ahize/remix, ahize/astro, ahize/angular. Same factory pattern: pass the framework primitives in.

GDPR & consent

load() never fires on import. Pair it with your CMP:

import * as intercom from "ahize/intercom"

OneTrust.OnConsentChanged(() => {
  if (OnetrustActiveGroups.includes("C0004")) {
    intercom.load({ appId: "abc123" })
  } else {
    intercom.destroy() // removes script, globals, cookies
  }
})

Defer strategies for LCP-sensitive pages:

load({ appId: "...", defer: "idle" }) // requestIdleCallback
load({ appId: "...", defer: "interaction" }) // first pointerdown/scroll/keydown
load({ appId: "...", defer: "manual" }) // never auto-injects
load({ appId: "...", consent: hasConsent }) // gate behind a flag

EU region selection is built in:

intercom.load({ appId: "...", region: "eu" }) // → api-iam.eu.intercom.io
hubspot.load({ portalId: "...", region: "eu1" }) // → js-eu1.hs-scripts.com

CSP

Strict CSP breaks every chat widget by default. ahize/csp ships the exact directive list per provider, including the WSS endpoints competitor wrappers always forget:

import { cspDirectives, mergeCsp, toHeaderString } from "ahize/csp"

const policy = mergeCsp(
  cspDirectives("intercom"),
  cspDirectives("crisp"),
  cspDirectives("chatwoot", { chatwootBaseUrl: "https://chat.acme.com" }),
)

response.setHeader("Content-Security-Policy", toHeaderString(policy))

Pass a nonce through load() and the same nonce through your CSP:

load({ appId: "...", nonce: cspNonce })

Want to catch violations in dev?

import { watchCspViolations } from "ahize/csp"

watchCspViolations((event) => {
  console.warn("CSP blocked", event.blockedURI, "for", event.violatedDirective)
})

Facade mode

For pages where chat is below the fold and LCP matters, mount a tiny launcher. The real provider boots on hover or click:

import * as intercom from "ahize/intercom"
import { mountFacade } from "ahize/facade"

mountFacade({
  provider: "intercom",
  boot: () => intercom.load({ appId: "abc123" }),
})

Under 2 KB. No CSS file, no framework. The launcher removes itself once the real widget is ready.

Switching providers

Capability matrix is queryable so you don't hard-code branches:

import { capabilities, supports } from "ahize/capabilities"

if (supports("zendesk", "callback")) {
  await identify({ id: "u1", verification: { kind: "callback", getToken } })
} else if (supports("zendesk", "jwt")) {
  await identify({ id: "u1", verification: { kind: "jwt", token } })
}

Diagnostics

When the snippet refuses to load, ahize/diagnostics probes the CDN and returns a hint:

import { diagnose } from "ahize/diagnostics"

const result = await diagnose("intercom", { appId: "abc123" })
//   { ok: false, status: 404, hint: "Snippet not found — typo in id…" }

Providers

Every provider ships the unified surface (load / identify / track / pageView / show / hide / shutdown / destroy / ready / isReady / state / getIdentity / onIdentityChange) plus a provider-specific extension: on(event, handler) typed event bridge, and vendor-native methods where they exist. Audited against live vendor docs 2026-04-16.

| Provider | Sub-path | Identity / regions | Provider-specific extras | | --------- | ----------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Intercom | ahize/intercom | HMAC, JWT, us/eu/au | showSpace, showNewMessage, startTour/Survey/Checklist, showArticle/News/Ticket, getVisitorId, onShow/onHide/onUserEmailSupplied, 11 typed boot fields | | Crisp | ahize/crisp | HMAC, hot-reconfigure | open/close/toggle, sendMessage, helpdeskSearch, setSessionSegments, setUserAvatar, 13 events, runtime config | | Tawk.to | ahize/tawk | HMAC, login() restores history | maximize/minimize/popup, addTags/removeTags, getStatus/isChat*, visitor preload, 19 event hooks | | Zendesk | ahize/zendesk | JWT, callback | open/close, setConversationTags, setCustomization, newConversation, resetWidget, 13 messenger:on events, cookies/zIndex config | | Chatwoot | ahize/chatwoot | HMAC, self-hosted, settings | setColorScheme, deleteAttribute, popoutChatWindow, setLocale, setBubbleVisibility, on(opened/closed/postback/…), 11 typed settings | | HubSpot | ahize/hubspot | Identification token, na1/eu1/ap1 | on(conversationStarted/…) (8 events), status(), refresh, 7 typed config (cookie banner, inline embed, attachment, CSP) | | LiveChat | ahize/livechat | — | maximize(draft?), minimize, hideGreeting, triggerSalesTracker, getState/CustomerData/ChatData, 10 events, 7 typed __lc fields | | Freshchat | ahize/freshchat | JWT, us/eu/in/au | open/close, setLocale, setTags/setFaqTags, setBotVariables, trackPage, isOpen/isLoaded, 16 events, 8 typed init fields | | Olark | ahize/olark | — | getVisitorDetails(), sendMessage/NotificationTo*, setOperatorGroup, setLocale, 12 events, group/locale boot config | | HelpScout | ahize/helpscout | HMAC | search, article, sessionData, config, reset, toggle, askQuestion, showMessage, info, once, prefill(attachments), full BeaconConfig object | | LiveAgent | ahize/liveagent | self-hosted opt | addUserDetail, addTicketField, setVisitorLocation, createForm, hasOpenedWidget, on(chatStarted/chatEnded/online/offline) | | Gist | ahize/gist | HMAC | open/close, showLauncher/hideLauncher, navigate, showArticle, trigger, setSidebar/setStandard, 12 events | | JivoChat | ahize/jivochat | setUserToken (verification) | setClientAttributes (rate-limited), setCustomData, startCall, sendOfflineMessage, sendPageTitle, 12 events, sync chatMode/getUnreadMessagesCount/getUtm | | Smartsupp | ahize/smartsupp | — | open/close, prefillMessage, sendMessage, setGroup, setLanguage, getVisitorId, on(messageSent/Received/messengerClose), 12 typed _smartsupp fields | | Tidio | ahize/tidio | — | tidioChatApi.track forwarding, setColorPalette, display, messageFromOperator/Visitor, addVisitorTags, setVisitorCurrency, 10 events, pre-load language/identify |

Deprecated / sunset providers

Still functional but the underlying vendor has announced sunset / EOL. Wrappers emit a one-shot console.warn on first load() and are marked @deprecated in JSDoc. No new feature work planned.

| Provider | Sub-path | Status | | --------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | Drift | ahize/drift | Vendor sunset announced 2026-03-06 (Clari + Salesloft). | | Sendbird | ahize/sendbird | AI Chatbot Widget discontinued; repo archived 2025-07-09 at v1.9.7. Consider Sendbird Desk. | | Userlike | ahize/userlike | v1 CDN EOL 2026-08-01. Vendor rebranded to Lime Connect; v2 is @userlike/messenger with a different surface. | | Zendesk Classic | ahize/zendesk-classic | Limited to Zendesk accounts created before 2023-06-05. Chat Web SDK removal started 2025-04-30. Use ahize/zendesk (Messenger). |

Migrating

From react-use-intercom:

-import { IntercomProvider, useIntercom } from "react-use-intercom";
+import * as intercom from "ahize/intercom";
+import { createUseAhize } from "ahize/react";
+import * as React from "react";
+const useAhize = createUseAhize(React);

-<IntercomProvider appId="abc">{children}</IntercomProvider>
+const { isReady, identify, show, hide, shutdown } = useAhize({
+  provider: intercom,
+  options: { appId: "abc" },
+});

| react-use-intercom | ahize | | ----------------------------- | ---------------------------------------------------- | | boot(props) | load({ appId, ...props }) | | update(props) | identify(props) | | trackEvent(name, meta) | track(name, meta) | | shutdown() | shutdown() | | hardShutdown() | destroy() | | boot({ user_hash }) | identify({ verification: { kind: "hmac", hash } }) | | boot({ intercom_user_jwt }) | identify({ verification: { kind: "jwt", token } }) |

Same shape works for react-zendesk, tawk-messenger-react, @productdevbook/chatwoot, @livechat/widget-react. The notable change across all of them: ahize separates boot (load) from user identity (identify).

Playground

A plain Vite + TypeScript playground is checked into playground/ for trying a real widget in the browser without setting up a framework.

pnpm playground

That installs the playground's own deps (vite, typescript) and opens the dev server on http://localhost:5173. It imports the Chatwoot provider directly from ../src/providers/chatwoot.ts, so any code change in src/ reloads instantly — no build step. Paste your websiteToken (and baseUrl if self-hosted), hit load(), then play with identify/track/show/hide/setLocale and watch the event log.

Sponsors

If ahize saves you a few hours of live-chat integration pain, consider sponsoring on GitHub so work on the next provider + framework adapter keeps going.

Contributing

Issues and PRs welcome at github.com/productdevbook/ahize. Missing a provider? The pattern is small enough to copy from any existing one — src/providers/livechat.ts is a good minimal template.

Credits

ahize exists because every one of these libraries solved part of the problem and showed us the bugs to design around. Many of the design decisions in ahize are direct responses to issues filed on these projects — thank you to every maintainer and reporter.

Intercom

Crisp

Tawk.to

Zendesk

HubSpot

Chatwoot

Performance & deferred load

LiveChat (text.com)

Other providers

  • userlike/messenger — Result<ok, err> pattern we propagate
  • HelpScout Beacon, Drift, Freshchat, Olark, Smartsupp, LiveAgent, Gist, JivoChat, Tidio, Sendbird — official docs & community wrappers

Other unified-chat work

If your library should be on this list and isn't, open an issue — happy to credit.

License

MIT © productdevbook