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

@valet.red/sdk

v0.3.0

Published

Browser SDK for Valet's per-convo SSE event stream + outbound stream_message

Readme

@valet.red/sdk

Browser SDK for Valet's per-convo Server-Sent Events stream + outbound stream_message. Embed an AI agent chat into your app with reconnect/dedupe/JWT-refresh handled for you.

npm install @valet.red/sdk

Or load directly in a browser without a bundler:

<script type="module">
  import { ValetClient } from "https://unpkg.com/@valet.red/[email protected]/dist/index.js"
</script>

Quickstart (3 steps)

1. Configure CORS for your origin

In the Valet portal, add your app's origin (e.g. https://yourapp.com) to your company's CORS allowlist. Without this the browser will refuse to connect.

2. Mint JWTs from your backend

The browser SDK never holds your company's signing secret. Your backend mints a 15-min HS256 JWT and exposes it via an authenticated endpoint:

// /api/valet/jwt — your backend
import jwt from "jsonwebtoken"

export async function GET(req: Request) {
  const user = await getCurrentUser(req) // your existing auth
  if (!user) return new Response("unauthorized", { status: 401 })

  const token = jwt.sign(
    {
      iss:             process.env.VALET_COMPANY_API_KEY,
      aud:             "valet-sdk",
      company_api_key: process.env.VALET_COMPANY_API_KEY,
      agent_uuid:      process.env.VALET_AGENT_UUID,
      source_key:      user.opaque_id,             // NOT user.email
      iat:             Math.floor(Date.now() / 1000),
      exp:             Math.floor(Date.now() / 1000) + 15 * 60
    },
    process.env.VALET_JWT_SECRET!,
    { algorithm: "HS256" }
  )
  return new Response(token)
}

source_key must NOT be an email. Use an opaque internal user ID. The Valet API rejects email-shaped values at the gate.

3. Drop in the SDK

import { ValetClient } from "@valet/sdk"

const valet = new ValetClient({
  agentId:  "your-agent-uuid",
  fetchJwt: () => fetch("/api/valet/jwt").then(r => r.text())
})

const { convoId } = await valet.startSession()
const convo = await valet.openConvo({ convoId })

convo.on("message",     ({ message }) => render(message))
convo.on("typing",      ({ label })   => showTyping(label))
convo.on("convo_state", ({ state })   => updateBadge(state))

await convo.send("Hi, I need help with my order")

// when the user navigates away
convo.close()

That's the whole integration. The SDK handles:

  • One long-lived SSE connection per (agent, convo) pair
  • JWT refresh — proactive at 5 min before expiry, forced on auth_expiring close
  • Reconnect protocol — exponential backoff on errors, immediate reconnect on graceful server closes
  • Reconcile fetch on every reconnect to fill any gap
  • Message UUID dedupe — your handlers fire exactly once per message even after a reconcile
  • Multi-tab safety — only one tab opens the SSE; others receive forwarded events via BroadcastChannel

Public API

new ValetClient({ agentId, fetchJwt, baseUrl?, debug?, fetchJwtTimeoutMs? })

| Field | Type | Required | Description | |---|---|---|---| | agentId | string | yes | The agent uuid your end-user is chatting with. | | fetchJwt | () => Promise<string> | yes | Returns a fresh JWT. SDK calls this on demand. | | baseUrl | string | no | Defaults to https://api.valet.red. | | debug | boolean | no | Verbose console.debug logging. | | fetchJwtTimeoutMs | number | no | Hard cap on a single fetchJwt() call. Defaults to 10000. |

valet.startSession() → Promise<{ convoId }>

Mints a brand-new open convo for the JWT-scoped appuser and returns its uuid.

valet.openConvo({ convoId }) → Promise<Convo>

Opens a per-convo SSE stream against an existing convo uuid. Pair with startSession() for new chats, or pass a previously stored convoId to resume an existing one.

convo.on(event, handler) → unsubscribe

Typed event subscription. Returns an unsubscribe function.

| Event | Payload | When it fires | |---|---|---| | message | MessageEvent | a new convo message persisted (any participant) | | typing | TypingEvent | someone started/stopped typing | | convo_state | ConvoStateEvent | the convo's state flipped (open → escalated → resolved → ...) | | ready | ReadyEvent | stream is live (fires once per connect) | | closed | ClosedEvent | server is closing the stream (you don't usually need to handle this — SDK reconnects for you) | | error | {error, phase} | non-recoverable error |

convo.send(text) → Promise<void>

POSTs a user message. Fire-and-forget — the agent's reply arrives via the message event.

convo.close()

Aborts the SSE stream and stops reconnect. Call this when your chat UI unmounts.

Design notes

The SDK enforces a security and correctness contract that's worth understanding:

  • JWT auth, no API-key fallback. Your company API key never enters the browser. The SDK exclusively uses Bearer JWTs minted by your backend.
  • Per-convo isolation. The server filters every event by convo ownership; an end-user's stream cannot see another user's events.
  • source_key is rejected if email-shaped. Use opaque IDs. The privacy posture only works if you don't put PII in identifiers.
  • 15-min JWT lifetime. Long enough for the SDK's 5-min auth_expiring warning to be useful; short enough to limit replay risk.
  • Per-(appuser, agent) cap of 2. Multi-tab is handled via tab-leader election. A 3rd concurrent connection gets 429 with Retry-After: 30.

For the full design rationale (reconnect protocol, replay model, capacity, observability), see docs/platform/realtime-events.mdx in the Valet repo.

Examples

See examples/:

  • vanilla.html — drop-in HTML page
  • react.tsx — minimal React component
  • nextjs/page.tsx — Next.js App Router

License

MIT. See LICENSE.