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

@vitrina/widget

v0.3.0

Published

Vitrina embeddable chat widget for dealer websites — shadow-DOM UI, real-time SSE, publishable origin-locked key.

Readme

@vitrina/widget

Embeddable chat widget for dealer websites. Renders a floating launcher + a conversation panel inside a Shadow DOM (fully style-isolated from the host page) and speaks the Vitrina web channel protocol: it creates/resumes an anonymous visitor conversation, POSTs messages, and subscribes to a visitor-scoped SSE stream that invalidates so the widget re-fetches history — all authenticated with a publishable, origin-locked widget key (pk_…).

Two ways to embed: an NPM import (import { init }) for app/storefront codebases, or a <script> loader for any static site.


Install

This package is published to the public npm registry under the @vitrina org scope. No authentication or .npmrc configuration is required.

Option A — NPM import (app/storefront codebases)

npm install @vitrina/widget
# or: pnpm add @vitrina/widget
# or: yarn add @vitrina/widget

Option B — <script> loader (any static site, no build step)

<script>
  window.vitrinaChat = {
    publicKey: 'pk_live_xxx',
    apiBaseUrl: 'https://api.vitrinadev.com/api/v1',
  };
</script>
<script src="https://api.vitrinadev.com/widget.js" defer></script>

The loader is served by the same host the widget talks to, so a Content-Security-Policy only ever needs one Vitrina origin:

script-src https://api.vitrinadev.com;
connect-src https://api.vitrinadev.com;

See "Usage — <script> loader" below for the full config.


Usage — import { init }

import { init } from '@vitrina/widget';

const widget = init({
  publicKey: 'pk_live_xxx',
  apiBaseUrl: 'https://api.vitrinadev.com/api/v1',
  vehicleId: 'veh_123',            // optional: pre-attach the inquiry to a vehicle
  // Appearance is managed in Vitrina and fetched at load. Set any of these only
  // to OVERRIDE it for this site — an inline value always wins.
  locale: 'es',                    // optional: 'es' | 'en' (auto-detected otherwise)
  theme: { accent: '#2563eb', position: 'br', logoUrl: 'https://…/logo.png' },
  welcomeMessage: 'Hola, ¿en qué te puedo ayudar?',
});

// The returned handle lets you drive the widget imperatively:
widget.open();
widget.setVehicle('veh_456');      // e.g. on an SPA route change
widget.close();
widget.destroy();                  // unmounts + aborts the SSE stream

The widget mounts itself on init(); nothing else is required.


Usage — <script> loader

For any site (no build step). Set window.vitrinaChat before the loader script, and it auto-initializes:

<script>
  window.vitrinaChat = {
    publicKey: 'pk_live_xxx',
    apiBaseUrl: 'https://api.vitrinadev.com/api/v1',
  };
</script>
<script src="https://api.vitrinadev.com/widget.js" defer></script>

That is the whole install. Appearance — colour, corner, logo, greeting, language — is managed in Vitrina (Configuración › Conexiones › Web chat) and fetched at load, so changing it never means editing this page again. You can still pin any of it inline, and an inline value always wins:

<script>
  window.vitrinaChat = {
    publicKey: 'pk_live_xxx',
    apiBaseUrl: 'https://api.vitrinadev.com/api/v1',
    // optional, all overrides of what Vitrina serves:
    vehicleId: 'veh_123',
    locale: 'es',
    theme: { accent: '#2563eb', position: 'br', logoUrl: 'https://…/logo.png' },
    welcomeMessage: 'Hola, ¿en qué te puedo ayudar?',
  };
</script>
<script src="https://api.vitrinadev.com/widget.js" defer></script>

https://api.vitrinadev.com/widget.js always serves the current release. The URL is deliberately unversioned so a dealer never edits their HTML to receive a fix; it is cached for five minutes and revalidated with an ETag. Pin a specific version instead — https://cdn.jsdelivr.net/npm/@vitrina/[email protected]/dist/loader.global.js — only if you have a reason to hold one back.

The window.vitrinaChat object is exactly the config table below. After load, the live handle is stashed on window.vitrinaChatInstance, so the host page can call window.vitrinaChatInstance.open() / .close() / .setVehicle(id) / .destroy().

The loader is defensive: it console.warns and no-ops on a missing/invalid config, is idempotent against a double-load, and never throws into the host page.


Configuration options

| Option | Type | Required | Default | Description | | ---------------- | ----------------------------- | -------- | ------------------ | --------------------------------------------------------------------------- | | publicKey | string | yes | — | Publishable widget key (pk_…), origin-locked. Safe to ship in page source. | | apiBaseUrl | string | yes | — | Vitrina API base, e.g. https://<host>/api/v1. Trailing slash is trimmed. | | vehicleId | string | no | null | Pre-attach the inquiry to a vehicle (the id from /stock). | | locale | 'es' \| 'en' | no | auto (navigator) | Widget chrome language. Falls back to es (Chilean market default). | | theme.accent | string (CSS color) | no | #111827 | Brand accent for the launcher + inbound bubbles. Sanitized; bad values fall back. | | theme.position | 'br' \| 'bl' | no | 'br' | Launcher corner: bottom-right or bottom-left. | | theme.logoUrl | string (http/https URL) | no | — | Optional logo in the panel header. Non-http(s) URLs are ignored. | | welcomeMessage | string | no | localized greeting | Greeting shown before the visitor sends the first message. | | remoteConfig | boolean | no | true | Fetch appearance from Vitrina at load. false = fully self-contained. |

Message content is never parsed as HTML — every bubble is written via textContent, so it is XSS-safe by construction.

Where appearance comes from

locale, theme.* and welcomeMessage are resolved server-side from the dealer's own Vitrina settings and fetched once at load (GET /widget/config). That is what lets a dealer restyle their bubble from the admin UI and have already-installed widgets pick it up — within about a minute — instead of asking every site owner to re-paste a snippet.

Three things worth knowing:

  • Anything you set inline wins. A widget installed before this existed carries a full inline config and therefore renders exactly as it always has. Treat inline values as per-site overrides.
  • It fails open. A network error, an older API without the route, a malformed answer — the widget renders with your inline/default theme and works normally. The request is never on the critical path.
  • It is cached in localStorage (last known good) and by the browser (60s, ETag-revalidated), so repeat visits paint the right colours with no network wait. Pass remoteConfig: false to opt out of the fetch entirely.

Security

  • pk_ is public by design. It is safe to ship in page source: it only works on the dealer's allow-listed origins (origin-locked, Vitrina ADR 0033) and only grants stock:read + leads:intake + widget:chat. It is not a secret and carries no admin capability.
  • Origin lock + CORS. Requests only succeed from the domains configured for that key; the widget sends a fixed, minimal header set (Authorization, Content-Type, X-Vitrina-Visitor) and never credentials: 'include'.
  • AI kill-switch defaults OFF. With AI answers off, a visitor talks to a human via the dealer inbox; replies arrive over the same SSE→refetch path once the dealer enables AI later — no widget change needed.
  • Honeypot. The composer includes a hidden hp_website field (visually hidden off-screen, never display:none) that is always submitted — empty for a human, a spam signal when a bot fills it.

Browser support

Evergreen browsers (Chrome/Edge, Firefox, Safari, and their mobile equivalents). The widget requires Shadow DOM, fetch with streaming ReadableStream bodies (for SSE), AbortController, and CSS custom properties — all baseline in browsers from ~2020 onward. No IE11 support.


Development

pnpm build       # tsup → dist/ (ESM library + IIFE loader + .d.ts)
pnpm typecheck   # tsc --noEmit
pnpm test        # vitest (happy-dom)