@vkid/captcha
v2.0.0
Published
VKID Captcha
Keywords
Readme
Web Captcha
SDK каптчи VK ID: монтирует приложение каптчи в страницу сервиса и возвращает токен, который сервис предъявляет своему бэкенду.
Каптча проходится одним из двух способов, и выбирает его интегратор:
- невидимо — рамка скрыта, проверка идёт в фоне и запускается методом
execute()на целевом действии (обычно отправка формы); - чекбоксом «Я не робот» — привычная каптча, встроенная в вёрстку или поднятая модалкой поверх страницы.
Код отправки формы у обоих способов одинаков: execute() возвращает токен и
в невидимом режиме, и в видимом — там он просто отдаёт уже полученный по клику.
Установка
pnpm add @vkid/captchaБыстрый старт
Ссылку на приложение каптчи (link) выдаёт ваш бэкенд в ответе createSession.
import { CaptchaWidget, CaptchaPublicEvents } from '@vkid/captcha';
const captcha = new CaptchaWidget();
// Невидимая каптча: монтируем на загрузке страницы
await captcha.render({ link, invisible: true });
form.addEventListener('submit', async (event) => {
event.preventDefault();
try {
const token = await captcha.execute();
await sendForm({ ...formData, captchaToken: token });
} catch (code) {
// Токена не будет — решайте по коду, см. «Исходы execute()»
}
});Тот же код с чекбоксом в вёрстке — меняется только вызов render():
await captcha.render({ link, container: '#captcha' });И с чекбоксом, который поднимается модалкой на целевом действии, — контейнера просто нет:
await captcha.render({ link });Методы
| Метод | Назначение |
|---|---|
| render(params) | Монтирует каптчу. Промис резолвится, когда приложение загружено — сколько бы та ни длилась |
| execute(params?) | Запускает проверку и возвращает токен |
| close() | Убирает каптчу со страницы |
| destroy() | Убирает каптчу и возвращает виджет в исходное состояние: можно смонтировать следующую |
| on(event, handler) / off(event, handler) | Подписка на события |
render() вызывается один раз на загрузку страницы; повторный вызов
отклоняется кодом 'already_initialized'. Исключение — неудача: если монтаж
или загрузка сорвались, каптчу поднимают повторным render().
Одностраничным приложениям, где экраны сменяются без перезагрузки, между
каптчами нужен destroy():
await captcha.render({ link, invisible: true });
// ... переход на следующий экран
captcha.destroy();
await captcha.render({ link: nextLink, invisible: true });nextLink — свежая ссылка от вашего бэкенда: сессию каптчи гасит первая же
проверка, и старая ссылка новую каптчу не поднимет. Незавершённые render()
и execute() destroy() отклоняет кодом 'destroyed' и о своей уборке
событий не шлёт.
После close() капча со страницы снята, но виджет остаётся занятым:
execute() отвечает 'close', render() — 'already_initialized'. Поднять
капчу заново можно тем же путём: destroy() и render(). Если проверка не
состоялась (пользователь закрыл модалку до клика), подойдёт прежняя ссылка —
сессия не погашена.
Основные параметры render(): link, invisible (без параметра режим
берётся из query присланной ссылки: invisible=true включает невидимый режим,
иначе капча видимая), container (в невидимом режиме не участвует, рамка
монтируется в document.body), lang, scheme, notice (плашка
уведомления об обработке данных: положение, скрытие, перетаскивание
draggable), slowLoadAfter (через сколько миллисекунд сообщить о
затянувшейся загрузке, по умолчанию 10 000). Полное описание — в jsdoc типов CaptchaRenderParams и
CaptchaExecuteParams.
События
captcha.on(CaptchaPublicEvents.SUCCESS, ({ token }) => { /* ... */ });| Событие | Когда | Аргумент |
|---|---|---|
| captcha: success | Проверка пройдена (в видимом чекбоксе — в момент клика) | { token } |
| captcha: challenge visible | Невидимая проверка развернулась в видимое задание | — |
| captcha: challenge hidden | Видимое задание свёрнуто | — |
| captcha: token expired | Полученный токен протух | — |
| captcha: load slow | Загрузка приложения идёт дольше порога. Не отказ: каптча продолжает грузиться | { slowLoadAfter } |
Исходы execute()
Промис отклоняется строковым кодом:
| Код | Что произошло | Что делать |
|---|---|---|
| not_passed | Чекбокс не нажат | Попросить пользователя пройти проверку |
| timeout | Ответ не пришёл в срок с момента вызова, ожидание загрузки входит в срок; каптча остаётся на странице | Повторить execute() или применить свою политику деградации |
| close | Пользователь закрыл каптчу | Целевое действие не выполнять |
| failed | Проверка провалена терминально либо приложение сломалось | Целевое действие не выполнять |
| token_expired | Сессия каптчи умерла | См. ниже |
| already_checked | Проверка на этой загрузке страницы уже пройдена | Новая проверка — только с новой загрузкой страницы |
| not_initialized | render() не вызывали или он не резолвился | Смонтировать каптчу повторным render() |
| destroyed | Каптчу сняли вызовом destroy() | Целевое действие не выполнять |
Протухший токен
Сессия каптчи одноразовая: её гасит первая же проверка, а запросить новую
с фронта нельзя. Поэтому 'token_expired' (и парное ему событие
'captcha: token expired') означает, что полученный токен предъявлять бэкенду
бессмысленно, — но пройти проверку заново на этой же странице нечем.
Автоматической переинициализации SDK не делает. Рекомендуемая обработка:
запросить у своего бэкенда свежий link и перезагрузить страницу либо
сообщить пользователю, что нужно повторить действие после обновления.
Попасть в этот исход можно, если страница долго не получала процессорного времени — вкладка ушла в bfcache, была заморожена или устройство спало, — и сессия истекла раньше, чем приложение успело её продлить.
Миграция с Yandex SmartCaptcha и Google reCAPTCHA
Схема работы совпадает: смонтировать каптчу на загрузке страницы, вызвать
execute() на целевом действии, отправить токен на бэкенд.
| Yandex SmartCaptcha / reCAPTCHA | VK ID Captcha |
|---|---|
| render(container, { sitekey, invisible }) | render({ link, container?, invisible? }) |
| execute() | execute() |
| subscribe('success', cb) / callback | on('captcha: success', cb) |
| subscribe('challenge-visible', cb) | on('captcha: challenge visible', cb) |
| subscribe('token-expired', cb) | on('captcha: token expired', cb) |
| destroy() | destroy() |
| Статический sitekey | Живой link из createSession вашего бэкенда |
Главное отличие — вместо постоянного sitekey каптча получает одноразовую
ссылку сессии, которую выдаёт ваш бэкенд. Из этого следует и ограничение,
которого нет у SmartCaptcha: проверка одна на загрузку страницы.
Легаси-путь show()
Метод show() продолжает работать: он показывает каптчу сразу и отдаёт токен
промисом. execute() и публичные события доступны только после render().
Что изменилось на этом пути:
- без ссылки (
linkилиiframeSrc) промис отклоняется кодом'link_required', послеrender()—'already_initialized'; раньше это были исключения; destroy()отклоняет незавершённый промис кодом'destroyed';- повторный
close()ничего не делает:common: closeиonCloseприходят один раз; - в типах параметров
iframeSrcиcaptchaTypeстали необязательными.
Разработка
Демо-страница поднимается одной командой:
pnpm demo:dev # сборка SDK, демо и мока приложения + дев-серверВсе режимы, сценарии проверки и мок приложения описаны в demo/README.md. Бэкенд каптчи для демо не нужен.
Прод-сборка SDK — pnpm sdk:prod, тесты — pnpm tests.
