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

@casecore/chat-widget

v0.7.0

Published

Embeddable CaseCore chat widget for marketing and support conversations on a website

Readme

@casecore/chat-widget

Встраиваемый чат для сайта, обращения из которого попадают в CaseCore: маркетинговые вопросы как лид, технические — как обращение поддержки.

Пакет без зависимостей и без фреймворка — обычный ES-модуль, который монтирует себя сам. Так один и тот же виджет работает и в статической сборке (Astro, Hugo, plain HTML), и в приложении на React или Vue, где иначе понадобился бы клиентский boundary и сборщик.

Граница доверия

Виджет никогда не держит credential CaseCore и не обращается к CaseCore напрямую. Он говорит только с backend своего сайта, а тот уже добавляет x-casecore-key и вызывает Intake API. Абсолютный URL в endpoint отклоняется на этапе монтирования: путь должен быть same-origin, иначе credential пришлось бы отдать браузеру, а на стороне CaseCore включать CORS.

Серверную часть удобно собрать на @casecore/intake-client — там есть startChat, appendChatMessage и fetchChatTimeline.

Установка

pnpm add @casecore/chat-widget

Использование

import { mountSupportChat } from '@casecore/chat-widget';

const chat = mountSupportChat({
  endpoint: '/api/chat',
  campaign: 'pricing-page',
  turnstileSiteKey: '0x4AAAA-public-site-key',
});

В статическом HTML:

<script type="module">
  import { mountSupportChat } from '/vendor/support-chat-widget.js';
  mountSupportChat({ endpoint: '/api/chat' });
</script>

Возвращается { open, close, destroy } — например, чтобы открыть панель по кнопке на странице контактов.

Что делает бот

Здоровается, спрашивает тему, собирает имя, e-mail и вопрос — и уходит с дороги: дальше отвечает оператор. Своих ответов по существу он не даёт сознательно. Сценарный ответ, похожий на настоящий, хуже честного ожидания, а KB-ассистент CaseCore требует опознанного контакта, которым посетитель сайта не является.

Согласие на обработку данных запрашивается только у темы продаж: у обращения в поддержку законное основание есть и без него, а чекбокс, который ничего не значит, хуже его отсутствия.

Ожидаемый контракт backend сайта

Три пути относительно endpoint:

| Запрос | Тело | Ответ | | ------------------------ | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | | POST /start | { topic, name, email, message, consent, pageUrl, campaign, turnstileToken, website, idempotencyKey } | { data: { caseId, threadToken, caseNumber } } | | POST /messages | { caseId, threadToken, body } | { data: { id } } | | GET /timeline?caseId=… | заголовок x-support-thread-token | { data: { messages: [{ id, body, direction, createdAt }], closedAt } } |

threadToken — credential посетителя: он привязан к одному обращению и одному контакту, хранится в CaseCore только хэшем и отзывается при закрытии обращения. Виджет держит его в localStorage, чтобы перезагрузка страницы не начинала второе обращение. При каждом открытии он заново загружает полный timeline с сервера. В шапке доступны «История» для переключения между сохранёнными обращениями и «Новый диалог» для явного возврата к выбору «Продажи и демонстрация» / «Получить поддержку». В истории есть явное действие «Назад к диалогу». «Очистить на этом устройстве» после подтверждения удаляет только локальные credentials и список из браузера — кейсы и сообщения в CaseCore остаются. Новый диалог не удаляет доступ к предыдущим обращениям. Панель закрывается видимой кнопкой и клавишей Escape с возвратом фокуса на launcher. По 401/403/410 недействующая сессия не оставляет пользователя в мёртвой форме ответа: виджет предлагает начать новый диалог, а сохранённые обращения остаются в истории.

website — скрытое поле-ловушка: заполненное значение backend передаёт как есть, а CaseCore отвечает обычным успехом, ничего не сохраняя.

Параметры

| Параметр | По умолчанию | Смысл | | ---------------------- | ------------------- | ----------------------------------------------- | | endpoint | /api/chat | Same-origin префикс путей backend сайта | | container | document.body | Куда монтировать | | topics | продажи + поддержка | Темы и их маршрутизация | | copy | русский текст | Переопределение любых строк | | pollIntervalMs | 8000 | Частота опроса ответов, пока панель открыта | | injectStyles | true | Встроенные стили; false, если оформление своё | | pageUrl, campaign | — | Записываются на обращение | | turnstileSiteKey | — | Публичный site key Cloudflare Turnstile | | fetchImpl, storage | глобальные | Подмена для тестов |

Если передан turnstileSiteKey, виджет сам загружает официальный browser script, блокирует отправку до успешной проверки и добавляет одноразовый turnstileToken только в первый запрос. Секрет Turnstile остаётся на CaseCore API; в браузер попадает только публичный site key.

Опрос идёт только при открытой панели: фоновая вкладка не должна расходовать лимиты запросов на разговор, который никто не смотрит.

Оформление

Встроенные стили — одиночные классы support-chat__* и CSS-переменные, без reset. Дефолты объявлены внутри :where(:root), у которого нулевая специфичность, поэтому переопределение хоста выигрывает без !important и независимо от того, подключён ли CSS сайта раньше инжектируемой таблицы:

:root {
  --support-accent: #7c3aed;
  --support-radius-lg: 2px;
}

Полный список — экспорт supportChatThemeTokens; это тот же словарь --support-*, который использует @casecore/support-widget, поэтому сайт с формой и чатом настраивает тему один раз. Тёмная палитра применяется по prefers-color-scheme, а на ширине до 30rem панель разворачивается на весь экран.

Сайт, который владеет внешним видом целиком, передаёт injectStyles: false и пишет свои правила на те же классы.

Время сообщений форматируется по языку страницы (<html lang>); проп locale переопределяет это явно.

Несовместимые изменения в 0.7.0

Имена, которые виджет оставлял в чужой странице, больше не содержат названия системы поддержки — оно описывает CaseCore, а не функцию, которую встраивает сайт:

| Было | Стало | | -------------------------- | ------------------------ | | mountCaseCoreChat | mountSupportChat | | CaseCoreChat* (типы) | SupportChat* | | .casecore-chat* (классы) | .support-chat* | | --casecore-chat-* | --support-* | | casecore.chat.* (ключи) | support.chat.* | | x-casecore-thread-token | x-support-thread-token |

Совместимости со старыми именами нет намеренно. Сохранённые в браузере разговоры под старым ключом не подхватятся: посетитель начнёт новый диалог, кейсы в CaseCore при этом остаются. BFF должен принять новый заголовок x-support-thread-token.