Caputchin
Справочники

Методы и события виджета

Виджет 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ТипЗначение
tracestringНепрозрачная запись раунда, которую производит твоя игра. На раунде с воротами сервер реплеит её, чтобы вывести авторитетный вердикт; на ключе без ворот или только-игровом она принимается как есть. Передавай пустую строку, если у твоего взаимодействия нет записи для реплея.

На этом методе нет поля 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ТипЗначение
codestring (опционально)Твой диагностический код; всплывает как originalCode события error. По умолчанию game-failed.
messagestring (опционально)Читаемая человеком причина.

События

Слушай через 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. Полный набор кодов:

codeseverityЗначение
invalid-configwarnАтрибут или сочетание, которое виджет отклонил; он деградирует мягко и продолжает работать.
invalid-callwarnМетод, вызванный, когда он был невалиден (например pass() на не-ручной игре или до старта раунда).
verification-failederrorПроверка proof-of-work или инструментирования (или погашение токена) сбоила; токен не выдан.
game-load-failederrorБандл игры не удалось разрешить, загрузить или зарегистрировать.
gate-unavailableerrorСервер вернул авторитетный отказ при бутстрапе (ключ с воротами не смог поставить валидную игру).
game-error-relayederrorОшибка всплыла изнутри игры (ретранслирована из 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, ты можешь также делегировать с контейнерного элемента, а не привязывать каждый виджет по отдельности.

См. также

На этой странице