Методы и события виджета
Виджет Caputchin поставляет два пользовательских элемента, и твоя страница взаимодействует с ними в двух направлениях: ты вызываешь методы на элементе, чтобы им управлять, и ты слушаешь события, которые он испускает, чтобы реагировать. Эта страница это исчерпывающий справочник обеих поверхностей.
Каждое событие это CustomEvent, диспетчеризуемое с bubbles: true и composed: true, так что оно выходит из shadow DOM виджета, и ты можешь слушать его прямо на элементе (или на любом предке). Данные всегда на event.detail.
Два элемента с первого взгляда
| Элемент | Назначение | Методы |
|---|---|---|
<caputchin-widget> | Только проверка (proof-of-work / галочка). | start() |
<caputchin-game> | Игровой хост, с опциональной проверкой. | pass(), fail() |
У галочного виджета только start(); он не выставляет pass() / fail(). У игрового хоста нет start() (его раунд становится живым при монтировании для inline или при первом открытии диалога для modal / fullscreen). Оба элемента испускают start, pass, error и degraded; игровой хост дополнительно испускает dialog-shown и dialog-hidden. Покрытие в таблице событий ниже.
Методы
start() (только <caputchin-widget>)
Начинает проверку немедленно. Его назначение это ручной триггер: когда у элемента trigger="manual", ты вызываешь start(), чтобы запустить решение proof-of-work от своего собственного жеста (например, при отправке твоей формы) вместо встроенной галочки. С триггерами по умолчанию auto / click элемент подключает свою собственную активацию, так что ты это не вызываешь.
const widget = document.querySelector("caputchin-widget");
widget.start();У игрового хоста нет start(). Смотри ручной режим для полного прохождения управления-этим-самому.
pass(payload) (только <caputchin-game>)
Отпускает ворота проверки: посетитель успел. Валиден только когда trigger="manual"; вызов его на не-ручной игре испускает error с кодом invalid-call. Первый pass() погашает и блокирует вердикт; поздние вызовы молча игнорируются (один раунд на сессию).
const widget = document.querySelector("caputchin-game");
widget.pass({ trace: roundRecord });Поле payload | Тип | Значение |
|---|---|---|
trace | string | Непрозрачная запись раунда, которую производит твоя игра. На раунде с воротами сервер реплеит её, чтобы вывести авторитетный вердикт; на ключе без ворот или только-игровом она принимается как есть. Передавай пустую строку, если у твоего взаимодействия нет записи для реплея. |
На этом методе нет поля score, ворота это реплей trace сервером, а не заявленный клиентом счёт. (score действительно появляется на событии pass, которое это виджет, отчитывающийся обратно тебе, а не ты виджету.)
Вызов pass() до того, как раунд начался (modal / fullscreen, диалог ещё не открыт), испускает error с кодом invalid-call. На только-игровом ключе (нет sitekey) нет ворот для отпускания, так что pass() просто испускает событие pass с token: null.
fail(payload?) (только <caputchin-game>)
Прерывает раунд: окончательный проигрыш. Валиден только когда trigger="manual" (иначе invalid-call). Опционален, незавершённый раунд всё равно трактуется как непрохождение. Он прерывает проверку в полёте (проверки proof-of-work и инструментирования) и всплывает событием error с кодом game-error-relayed.
widget.fail({ code: "out-of-moves", message: "no moves left" });Поле payload | Тип | Значение |
|---|---|---|
code | string (опционально) | Твой диагностический код; всплывает как originalCode события error. По умолчанию game-failed. |
message | string (опционально) | Читаемая человеком причина. |
События
Слушай через addEventListener(type, handler). Каждая нагрузка живёт на event.detail.
| Событие | Испускается | event.detail | Срабатывает когда |
|---|---|---|---|
start | оба | { gameId: string | null } | Проверка начинается (авто при монтировании, при активации или через start()). gameId задан для игрового раунда, null для простого виджета. |
pass | оба | { token: string | null, score: number | null, durationMs: number | null } | Проверка отпущена. token это обёрнутый токен для отправки на твой бэкенд; null на только-игровом ключе (нет sitekey). |
error | оба | { code, message, severity, originalCode? } | Что угодно от безобидного предупреждения конфигурации до жёсткого сбоя. Смотри событие error. |
degraded | оба | { reason: "timeout" | "network" | "http" | "malformed" } | Виджет не смог вовремя разрешить настроенные тобой размер / скин / локаль, поэтому отрендерился со своими встроенными значениями по умолчанию. Это не ошибка. Смотри событие degraded. |
dialog-shown | <caputchin-game> | { layout: "modal" | "fullscreen" } | Диалог оверлея игры открывается (программно или первый клик). |
dialog-hidden | <caputchin-game> | { layout: "modal" | "fullscreen" } | Диалог оверлея игры закрывается (программно, Escape или клик по затемнению). |
Событие pass и токен
Событие pass это твой сигнал, что проверка удалась, но это не решение о доверии. Решение о доверии это твой бэкенд, подтверждающий detail.token. Всегда проверяй токен на своём бэкенде; никогда не давай доступ по одному фронтенд-событию. В форме виджет также вставляет токен как поле caputchin-token, чтобы обычный POST формы его нёс. score и durationMs на этом событии это аналитика, сообщённая клиентом, и могут быть null; никогда не трактуй их как сигнал доверия.
Событие error
Событие error несёт стабильный code, человеческий message, severity и иногда originalCode. У каждого кода фиксированная серьёзность по умолчанию, так что ты можешь отфильтровать предупреждения «продолжило работать» от сбоев «реально сломалось» без жёсткой таблицы код-в-серьёзность, просто читай detail.severity. Полный набор кодов:
code | severity | Значение |
|---|---|---|
invalid-config | warn | Атрибут или сочетание, которое виджет отклонил; он деградирует мягко и продолжает работать. |
invalid-call | warn | Метод, вызванный, когда он был невалиден (например pass() на не-ручной игре или до старта раунда). |
verification-failed | error | Проверка proof-of-work или инструментирования (или погашение токена) сбоила; токен не выдан. |
game-load-failed | error | Бандл игры не удалось разрешить, загрузить или зарегистрировать. |
gate-unavailable | error | Сервер вернул авторитетный отказ при бутстрапе (ключ с воротами не смог поставить валидную игру). |
game-error-relayed | error | Ошибка всплыла изнутри игры (ретранслирована из iframe или из ручного fail()). |
detail.originalCode присутствует, когда публичный code это обобщение более конкретной внутренней причины (например, сбой загрузки iframe, ретранслированный как game-load-failed, несёт сырой iframe-load-failed / iframe-script-blocked / game-not-registered в originalCode). Используй code для ветвления, а originalCode только для диагностики.
Событие degraded
При монтировании виджет делает один короткий вызов к Caputchin, чтобы разрешить настроенные тобой размер, скин и локаль (а для игры из маркетплейса, её предпочтительный след). Если этот вызов сбоит или слишком медленный, виджет не блокируется и не ломается: он рендерится со значениями, запечёнными в бандл, и испускает degraded, так что откат к умолчанию никогда не бывает молчаливым. Виджет остаётся полностью рабочим после события degraded, лишь разрешённое представление может отличаться от того, что ты настроил (игра может появиться в своём размере по умолчанию, или галочка в своей теме по умолчанию). Виджет повторяет медленное разрешение перед откатом, так что degraded значит, что попытки исчерпаны, а не что одна попытка была медленной.
detail.reason говорит тебе, почему разрешение откатилось, чтобы ты мог показать это в своей собственной телеметрии:
reason | Значение |
|---|---|
timeout | Разрешение не ответило в рамках своего бюджета времени (сервис медленный или недостижим). |
network | Сам запрос провалился (офлайн, DNS, заблокирован, или сброс соединения). |
http | Сервис ответил статусом ошибки. |
malformed | Сервис ответил, но тело было невалидным. |
degraded это уведомление, а не сбой, поэтому это намеренно не событие error: медленное разрешение не должно срабатывать обработчики, которые ты подключаешь для настоящих отказов. Слушай его только если хочешь наблюдать деградировавшие рендеры, например чтобы оповещать, когда твои посетители видят запасное представление.
Слушание на практике
const widget = document.querySelector("caputchin-game");
const onPass = (e) => {
// send e.detail.token to your backend to verify
};
const onError = (e) => {
if (e.detail.severity === "error") {
console.error(e.detail.code, e.detail.message);
}
};
widget.addEventListener("pass", onPass);
widget.addEventListener("error", onError);
// Clean up when you tear down your view:
widget.removeEventListener("pass", onPass);
widget.removeEventListener("error", onError);Это нативные DOM-CustomEvent, так что в React ты подключаешь их через ref и addEventListener (а не JSX-пропсы onPass); примеры фронтенда показывают фреймворк-специфичное подключение. Поскольку каждое событие всплывает и composed, ты можешь также делегировать с контейнерного элемента, а не привязывать каждый виджет по отдельности.
См. также
- Добавь виджет: атрибуты конфигурации, которые ты задаёшь в разметке (входная сторона к этой выходной).
- Управляй проверкой сам через ручной режим: руководство, которое использует
start()/pass()/fail(). - Проверь на своём бэкенде: что делать с токеном события
pass. - Примеры фронтенда: подключение этих событий в React, Vue и чистом JS.