Встраиваемый виджет
Полноценный поисковый интерфейс одним тегом script и короткоживущим токеном сессии — контракт сессии, все data-атрибуты, события, темизация и CSP.
aacsearch.js — самодостаточный бандл на InstantSearch. Добавьте на страницу два элемента, и он отрисует строку поиска, счётчик результатов, список попаданий, фасеты-чекбоксы, сортировку, пагинацию и недавние запросы — обращаясь к вашему индексу через прокси встраивания AACSearch и не раскрывая API-ключ платформы.
На странице никогда нет API-ключа. Ваш сервер обменивает ключ на короткоживущий токен сессии только для поиска через POST /api/v1/embed/session и подставляет этот токен в страницу как data-aac-token.
Тег script
Сначала выпустите токен на бэкенде (см. Сессия встраивания):
curl -sS -X POST "$AAC_API/embed/session" \
-H "Authorization: Bearer $AAC_KEY" \
-H "Content-Type: application/json" \
-d '{ "index": "documentation", "expires_in": 900 }'Затем положите полученный token на страницу:
<script
src="https://search.example.com/embed/v1/aacsearch.js"
data-aac-token="aac_sess_…"
data-aac-target="#aacsearch"
></script>
<div id="aacsearch"></div>Это вся интеграция со стороны страницы. Скрипт находит цель, отправляет токен на /api/v1/embed/config, добавляет свои стили и монтирует интерфейс — без сборки, фреймворка и npm-пакета.
| Атрибут | Обязателен | По умолчанию | Значение |
|---|---|---|---|
data-aac-token | да | — | Токен сессии из POST /api/v1/embed/session. Без него скрипт не монтируется автоматически |
data-aac-target | нет | #aacsearch | CSS-селектор точки монтирования |
data-aac-endpoint | нет | вычисляется из script.src | Базовый URL развёртывания, например https://search.example.com |
data-aac-placeholder | нет | из настроек сессии | Переопределяет подсказку в поле ввода |
data-aac-client-id | нет | анонимный UUID в localStorage | Постоянный идентификатор посетителя (например, id вошедшего пользователя): поиски и клики складываются в одну историю на разных устройствах |
Атрибута data-aac-key нет: API-ключ платформы не должен появляться ни в HTML, ни в data--атрибуте, ни в URL. Токен — только для поиска, привязан к одному индексу и истекает сам.
Если data-aac-endpoint не указан, скрипт вычисляет его из собственного src, отбрасывая всё начиная с /embed/ — поэтому тег, скопированный из панели встраивания консоли, работает на любом домене.
Если тег стоит в <head>, виджет ждёт DOMContentLoaded; если скрипт выполняется после построения DOM, монтирование происходит сразу. В обоих случаях целевой элемент должен существовать к моменту монтирования.
Версионный и безверсионный URL
| URL | Кеш | Применение |
|---|---|---|
/embed/v1/aacsearch.js | max-age=31536000, immutable | Продакшен. Версия в пути: v1 не меняется |
/embed/aacsearch.js | max-age=300 | Стейджинг и предпросмотр; исправления подхватываются за пять минут |
Оба отдаются с access-control-allow-origin: * и cross-origin-resource-policy: cross-origin, поэтому работают с любого домена. В продакшене фиксируйте версионный URL: скрипт без версии, который сам себя обновляет, — неожиданная зависимость.
Ручная инициализация
Для одностраничного приложения или когда нужно смонтировать виджет после смены маршрута, вызывайте глобальный объект вместо data-атрибутов:
<script src="https://search.example.com/embed/v1/aacsearch.js"></script>
<script>
window.AACSearch.init({
token: "aac_sess_…", // выпущен на сервере, это не API-ключ
target: "#docs-search",
endpoint: "https://search.example.com",
placeholder: "Поиск по документации…",
clientId: "user_123", // необязательно
});
</script>interface InitOptions {
token: string; // токен сессии встраивания, НЕ API-ключ
target: string | HTMLElement;
endpoint?: string;
placeholder?: string;
clientId?: string;
}init возвращает промис, который завершается после настройки виджета. При сбое он не бросает исключение, а рисует сообщение на месте:
| Ситуация | Что видит пользователь |
|---|---|
Нет token | Search is not configured for this page. (и console.error со ссылкой на POST /api/v1/embed/session) |
Токен истёк (401) | Search session expired — reload the page. |
Любой другой сбой /embed/config | Search is unavailable right now (503). (код статуса меняется) |
Если вызвать init до появления цели, в консоль попадёт [AACSearch] target not found, и функция вернётся.
window.AACSearch также предоставляет version (1.0.0 в текущей сборке — самый быстрый способ ответить на вопрос «та ли сборка на странице?»), trackConversion() и getRecommendations().
Контракт сессии
Все запросы бандла идут на прокси /api/v1/embed/* вашего развёртывания и аутентифицируются токеном сессии в JSON-теле — никогда в URL, поэтому токен не попадает в историю браузера, заголовки referrer и журналы доступа.
| Запрос | Эндпоинт | Когда |
|---|---|---|
| Настройки | POST /api/v1/embed/config | Один раз при монтировании: возвращает index, scope, expires_at и settings |
| Поиск | POST /api/v1/embed/search | Каждый запрос, смена страницы, фасета или сортировки |
| События | POST /api/v1/embed/events | view при первом показе результата, click по результату, conversion из trackConversion() |
| Рекомендации | POST /api/v1/embed/recommendations | Когда включён showRelated, и из getRecommendations() |
Поиск не идёт из браузера напрямую в поисковый сервис. Прокси проверяет токен в каждом запросе, применяет фильтр тенанта на сервере и использует тот же конвейер, что и публичный API (журнал запросов, правила, значения по умолчанию, ослабление запроса, did-you-mean), поэтому трафик виджета виден в аналитике. Браузер не видит ни адреса движка, ни ключа, ни имени коллекции. Каждый проксируемый поиск списывается с квоты searches; один запрос к прокси несёт не более 5 поисков, page ≤ 20, а per_page обрезается до 50.
Срок жизни сессии — по умолчанию 900 секунд (максимум 3600). Виджет не обновляет токен сам: после истечения запросы возвращают 401, и виджет показывает The search session expired — reload the page. Для страниц, которые остаются открытыми, выпускайте свежий токен до истечения и снова вызывайте init — или перезагружайте страницу.
События
Виджет сам отправляет события поведения — по возможности и с keepalive, поэтому сбой учёта никогда не ломает страницу:
// Вызывается из вашего кода, например после оформления заказа:
window.AACSearch.trackConversion({ documentId: "sku-123", valueCents: 4900, query: "кроссовки" });События несут token сессии, идентификатор посетителя session_id (client id виджета), document_id, position в выдаче и query. Они попадают в те же таблицы поведения, что и REST API событий, с атрибуцией по виджету, если токен выпущен для сохранённого виджета.
Рекомендации
const { items, mode } = await window.AACSearch.getRecommendations({
seedDocumentId: "sku-123",
limit: 4,
});getRecommendations возвращает { items: [], mode: "unavailable" }, пока виджет не смонтирован, и { items: [], mode: "error" } при сбое запроса. Встроенный блок «For you» появляется только когда для индекса включён showRelated и включены рекомендации.
Темизация и раскладка
Палитра берётся из настроек встраивания индекса, они редактируются в консоли в разделе Search → Embed с живым предпросмотром — изменения применяются при следующей загрузке страницы (и следующем выпуске токена), без передеплоя.
| Настройка | По умолчанию | Действие |
|---|---|---|
accent | #2563eb | Кнопки, обводка фокуса, активная страница, ссылки, фон mark |
dark | false | Тёмная палитра (фон #0b1220) |
perPage | 8 | Результатов на страницу |
placeholder | Search the docs… | Подсказка в поле ввода |
showStats | true | Строка с числом результатов |
showRecent | true | Чипы недавних запросов |
showVoice | false | Голосовой ввод браузера в строке поиска |
showImage | false | Изображение результата, если оно есть в документе |
showRelated | false | Блок «For you» под результатами |
facets | [] | Поля фасетов; консоль ограничивает выбор шестью |
sortOptions | релевантность + created_at:desc | Список сортировки, скрыт при единственном варианте |
queryBy | title,content | Поля поиска |
CSS-переменные задаются на корневом элементе, поэтому стили сайта могут их переопределить без правки бандла:
.aac-ui {
--aac-accent: #0f766e;
--aac-bg: #ffffff;
--aac-fg: #0f172a;
--aac-muted: #64748b;
--aac-border: #e2e8f0;
--aac-soft: #f8fafc;
border-radius: 14px;
}Раскладка — колонка фасетов 200 px плюс результаты, ниже 720 px переходит в одну колонку. Для попадания используются title, content, url и необязательное поле category для чипа; попадание без url рендерится как <div>, а не ссылка.
Что виджет делает во время работы
- Поиск по мере ввода с явной кнопкой отправки и кнопкой сброса.
- Фасеты в виде списков с чекбоксами (до 8 значений), построенные на коннекторе refinement-list, чтобы пустые значения не ломали рендер.
- Сортировка через
setQueryParameter("sortBy", …); первая опция должна иметь значениеdefault. - Недавние запросы в
localStorageпод ключомaac:recent:<index>, не более 5 записей, только для запросов от 3 символов, вернувших результаты. Очистка данных сайта их удаляет. - Идентификатор посетителя хранится в
localStorage(aac_client_id), если вы не передалиdata-aac-client-id; по нему строятся история поиска и атрибуция кликов. Cookie не устанавливаются. - Голос через виджет
voiceSearchиз InstantSearch, когда включён: используется распознавание речи браузера, аудио не уходит на ваш бэкенд. - Скелетоны на время первого запроса и дружелюбное пустое состояние:
No resultsс предложением сократить запрос. - Локализация нескольких встроенных строк (поиск, сброс, нет результатов, результаты) для
enиruпоnavigator.language; остальные языки показывают английский. Всё остальное берётся из ваших документов.
Ограничения
- Нет состояния в URL. Виджет не синхронизирует запрос и фильтры с адресной строкой, поэтому выдачу нельзя передать ссылкой, а кнопка «назад» не восстанавливает поиск. Нужен свой роутинг — добавьте его.
- Один виджет на страницу — документированная конфигурация. Стили добавляются один раз, а CSS-переменные глобальны, поэтому второй экземпляр унаследует палитру первого.
- Нет шаблонов результатов. Разметка попадания фиксирована; для своих карточек либо переопределяйте CSS, либо стройте интерфейс поверх
/embed/search. - Нет автоматического обновления токена. Сессия живёт по умолчанию 15 минут, максимум час; для долгоживущих страниц выпускайте токен заново и повторно вызывайте
init. - Заголовки фасетов — имена полей, а не человекочитаемые названия, поэтому называйте поля соответственно.
Content Security Policy
Весь трафик виджета идёт на один origin — ваше развёртывание: сам скрипт и вызовы /api/v1/embed/*. Браузер не обращается к поисковому движку, поэтому origin движка в политике не нужен.
script-src 'self' https://search.example.com;
connect-src 'self' https://search.example.com;
style-src 'self' 'unsafe-inline';Бандл вставляет стили инлайном, поэтому style-src требует 'unsafe-inline' (или аккуратного переопределения, если вы заменяете стили своими). connect-src — директива, о которой забывают: скрипт загружается, но вызовы /embed/config, /embed/search и /embed/events без неё падают во вкладке Network. Если API отдаётся с другого хоста, чем скрипт, разрешите оба origin.