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

@deepak.pfnp/chat-sdk

v0.1.2

Published

Headless chat SDK with socket transport and server-side bot support. Works on React web and React Native.

Readme

@deepak.pfnp/chat-sdk

Headless chat SDK — WebSocket transport, optimistic sends, offline queue, and a server-side rules bot. One install, one import path, works on React web and React Native.

You bring the UI. The SDK owns the data.

npm install @deepak.pfnp/chat-sdk

Quick start

1. Initialise once, at app startup.

import { initChat } from '@deepak.pfnp/chat-sdk'

initChat({
  url:  'wss://chat.example.com/socket',
  user: { id: 'u_1', name: 'Deepak' },
  token: authToken,
})

2. Use it anywhere. No provider, no context, nothing to thread through props.

import { useChat } from '@deepak.pfnp/chat-sdk'

function Conversation() {
  const { messages, send, choices, selectChoice, typingUserIds, isConnected } =
    useChat('thread_1')

  return (
    <>
      {messages.map(m => (
        <div key={m.id} data-pending={m.status === 'pending'}>
          <b>{m.author.name ?? m.author.id}</b>: {m.body}
        </div>
      ))}

      {typingUserIds.length > 0 && <em>typing…</em>}

      {choices?.map(c => (
        <button key={c.id} onClick={() => selectChoice(c.id)}>{c.label}</button>
      ))}

      <button disabled={!isConnected} onClick={() => send('hello')}>Send</button>
    </>
  )
}

3. Or drive it imperatively, outside React entirely.

import { chat } from '@deepak.pfnp/chat-sdk'

chat.send('thread_1', 'hello')
chat.on('message', m => showNotification(m))
chat.setTyping('thread_1', true)
chat.disconnect()

Drop-in web widget

Don't want to build the UI? Mount one component and you have a working "Need help?" support popover:

import { ChatWidget } from '@deepak.pfnp/chat-sdk/ui'

<ChatWidget threadId="support:u_1" />

Open it from anywhere — including outside React. No refs, no context, no prop drilling:

import { openChat, closeChat, toggleChat } from '@deepak.pfnp/chat-sdk/ui'

<button onClick={openChat}>Contact support</button>

// or from a plain module, an error handler, a router guard…
window.addEventListener('unhandledrejection', () => openChat())

| Prop | Default | | |---|---|---| | threadId | — | required | | title / subtitle | "Need help?" | header copy | | launcherLabel | "Need help?" | floating button text | | placeholder | "Type your message…" | composer | | position | bottom-right | or bottom-left | | theme | — | Partial<Theme> — see below | | defaultOpen | false | open on mount | | renderMessage | — | replace the bubble entirely | | onOpenChange | — | fires on every open/close |

Theming is a plain object — override only what you want:

<ChatWidget threadId="t1" theme={{ primary: '#7c3aed', radius: 8 }} />

Want your own trigger or placement? ChatPanel is the popover without the launcher:

import { ChatPanel } from '@deepak.pfnp/chat-sdk/ui'

{isOpen && <ChatPanel threadId="t1" onClose={() => setIsOpen(false)} />}

Included: unread badge, typing indicator, quick-reply chips, optimistic send with pending/failed states, auto-scroll, Escape-to-close, focus management, prefers-reduced-motion, and a full-screen mobile layout.

No CSS import needed. Styling is inline React styles plus a ~40-line stylesheet injected once for the things inline styles can't express (:hover, :focus-visible, keyframes, media queries).

Web only today. The React Native widget is next — tokens.ts, types.ts and useChatView.ts are already shared, so it's one more file.

What you get for free

| | | |---|---| | Optimistic sends | Messages appear instantly as status: 'pending', then collapse into the authoritative server copy on ack — never duplicated | | Offline queue | Sends made while disconnected persist and replay on reconnect | | Reconnection | Exponential backoff with jitter, so a server restart doesn't get stampeded | | Network awareness | Reconnects immediately when the device comes back online instead of waiting out the backoff | | Stable ordering | Sorted by (sentAt, id) — equal timestamps won't make messages swap places between renders | | Typing indicators | Auto-expire, so the flag can't stick on when someone stops typing without sending |

React Native

Everything works out of the box, but the default storage is in-memory — the offline queue won't survive an app restart. Pass a real adapter:

import AsyncStorage from '@react-native-async-storage/async-storage'
import { initChat } from '@deepak.pfnp/chat-sdk'

initChat({
  url:  'wss://chat.example.com/socket',
  user: { id: 'u_1' },
  storage: {
    get:    (k)    => AsyncStorage.getItem(k),
    set:    (k, v) => AsyncStorage.setItem(k, v),
    remove: (k)    => AsyncStorage.removeItem(k),
  },
})

Core never imports a platform API directly — storage and network arrive through these adapters. That's what keeps one build running on both targets.

Server

The server lives at a separate subpath, so a Node install never pulls React in:

import { createChatServer, defineBot } from '@deepak.pfnp/chat-sdk/server'

const bot = defineBot({
  start: 'greeting',
  steps: {
    greeting: {
      say: 'Hi! What do you need help with?',
      choices: [
        { id: 'billing', label: 'Billing' },
        { id: 'tech',    label: 'Tech support' },
      ],
      next:     { billing: 'billing', tech: 'tech' },
      match:    [{ pattern: /price|invoice/i, next: 'billing' }],
      fallback: 'unknown',
    },
    billing: { say: 'Billing it is. What is your invoice number?', end: true },
    tech:    { say: 'Tech support here. What is broken?',          end: true },
    unknown: { say: "Sorry, I didn't catch that." },
  },
})

createChatServer({
  port: 8787,   // 8080 is often already taken — nginx, dev servers, Docker
  bot,
  authenticate: async (token) => {
    const user = await verifyJwt(token)
    return user ? { id: user.id, name: user.name } : null   // null rejects
  },
})

Bot logic is server-side by design. The client only renders messages flagged author.type === 'bot' and sends the selected choice back. That means you can change a flow and redeploy without shipping a new app build.

Both sides import their event types from the same protocol.ts, so if you change an event shape, whichever side is stale fails to compile.

The reference server keeps history in memory — restart and it's gone. Swap the history and botSessions maps for your database when you productionise; the socket handling doesn't change.

Config

initChat({
  url:   'wss://…',              // required
  user:  { id, name?, avatarUrl? },  // required
  token: string,                 // sent as ?token=… by the default transport

  transport: TransportFactory,   // swap in Socket.IO, or a fake for tests
  storage:   StorageAdapter,     // default: localStorage on web, memory elsewhere
  network:   NetworkAdapter,     // default: navigator.onLine on web

  backoff: { initialMs: 500, maxMs: 30_000, factor: 2, jitter: 0.3 },
  autoConnect:      true,
  maxOutboxEntries: 200,
  maxSendAttempts:  5,
  typingTimeoutMs:  3000,
  debug:            false,
})

Next.js / SSR

initChat() deliberately does not connect when it runs on a server. The singleton is module state, which is shared across requests — a live socket there would leak one user's chat into another user's render.

Call it from a client component or an effect. Hooks are safe to render before initialisation; they return empty state until the client exists.

API

| Export | | |---|---| | initChat(config) | Bootstrap the singleton. Call once. | | chat | Imperative facade — send, join, on, setTyping, disconnect… | | useChat(threadId) | { messages, send, choices, typingUserIds, status, isConnected, … } | | useConnectionStatus() | 'idle' \| 'connecting' \| 'connected' \| 'reconnecting' \| 'closed' | | useChatError() | Latest error string, or null | | resetChat() | Tear down the singleton — use on logout and between tests | | createChatClient(config) | Unmanaged instance, if you need more than one |

Try it locally

npm install
npm run build

# terminal 1
npm run example:server

# terminal 2
npm run example:client -- alice

Then type. billing, tech, and human pick a quick reply; anything else is routed by the bot's regex rules. /quit exits.

[connected]
  bot: Hi! What do you need help with?
    choices: [billing] Billing  [tech] Tech support  [human] Talk to a human
> alice: my invoice is wrong
  bot: Billing it is. What is your invoice number?

Each client gets its own thread (support:<name>) by default, because a bot session belongs to a thread and ends when the flow reaches a step marked end. Pass a thread name to put two users in the same room:

npm run example:client -- alice general
npm run example:client -- bob   general

Development

npm run typecheck
npm run build
npm test          # spins up a real server and talks to it over a socket

License

MIT