Привет! Меня зовут Александр, я iOS-разработчик в IT-компании SimbirSoft. В этой статье я расскажу, как интегрировать Yandex SmartCaptcha в iOS-приложение — от подготовки до решения возможных трудностей.

Настройка Yandex SmartCaptcha на первый взгляд может показаться простой задачей, но на практике она требует внимательности, точной настройки и понимания архитектуры приложения. Я поделюсь личным опытом внедрения этого инструмента, объясню, какие нюансы стоит учесть, а также как избежать ошибок.

Материал предназначен для iOS-разработчиков, у которых уже есть базовые знания мобильной разработки, и кто хочет быстро и корректно внедрить капчу от Яндекса в свое iOS-приложение.

В первой части мы с вами рассмотрим, как настроить визуализацию капчи на web-стороне, а во второй — как интегрировать данный инструмент в ваше iOS-приложение.

Содержание

  1. Настройка визуализации капчи на web-стороне

  2. Интеграция в iOS приложение

Пример реализации

Заключение

Настройка визуализации капчи на web-стороне

Чтобы работать с капчей, для этого нужно в личном кабинете получить ключ. Он выглядит примерно так: ysc1_ADJdjadjkandanjkdakjDHkakdahkdkda

Также нужно создать капчу, подробное описание есть в документации, останавливаться не буду.

Yandex SmartCaptcha состоит из двух блоков:

Первый блок Yandex SmartCaptcha
Первый блок Yandex SmartCaptcha

Механика требует нажатия на галочку для проверки, также есть на выбор другой блок в виде слайдера.

Первый срабатывает в 90% случаев, в 10% открывается второй блок:

Второй блок Yandex SmartCaptcha
Второй блок Yandex SmartCaptcha

Второй блок работает как bottom sheet. Отключить второй блок нельзя, только можно уменьшить сложность, чтобы он реже появлялся у пользователей. Также можно выбрать другой вид, например, слайдер-мозаику.

Можно настроить цвет и вид блоков:

Image

Интеграция в iOS приложение

1. Настройка JS части

Создадим константу и заполним body во viewModel (можно сделать отдельным JSON-файлом):

   let body = """
        <html>
        <head>
            <meta charset=«UTF-8">
// Настраиваем внешний вид капчи для мобильных устройств. Если этого не сделать, капча будет растянута, так как по умолчанию она оптимизирована для веб-версии.
            <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
            <script>
// Подписываемся на готовность капчи к представлению
	function onSmartCaptchaReady(widgetId) {
            if (!window.smartCaptcha) {
            throw new Error("SmartCaptcha is not present");
        }
        
// Подписываемся на показ капчи
        window.smartCaptcha.subscribe(
            widgetId,
            "challenge-visible",
            handleChallengeVisible
        );
        
// Подписываемся на скрытие капчу
        window.smartCaptcha.subscribe(
          widgetId,
          "challenge-hidden",
          handleChallengeHidden
        );


// Подписываемся на успешный event показа капчи
        window.smartCaptcha.subscribe(widgetId, "success", handleSuccess);
        }
       
// У капчи есть жизненный цикл: 
// captchaDidFinish — когда капча успешно завершилась
        function handleSuccess(token) {
            sendIos("captchaDidFinish", token);
        }
        
// challengeDidAppear — когда пользователь смахнул вниз второй блок(bottom sheet)
        function handleChallengeVisible() {
            sendIos("challengeDidAppear");
        } 
        
// challengeDidDisappear — капча находиться в не пройденном состоянии
        function handleChallengeHidden() {
            sendIos("challengeDidDisappear");
        }
        
 // Метод для взаимодействия с IOS-частью
	function sendIos(...args) {
            if (args.length == 0) {
                  return;
            }
        
            const message = {
                method: args[0],
                data: args[1] !== undefined ? args[1] : ""
            };
        
            if (window.webkit) {
                window.webkit.messageHandlers.NativeClient.postMessage(message);
                }
            } 



// Загрузка body капчи
            </script>
              <script src="https://smartcaptcha.yandexcloud.net/captcha.js?render=onload&onload=onloadFunction" defer></script>
        </head>
        <body>
            <div
                id="captcha-container"
                class="smart-captcha"
                data-sitekey=«Ваш ключ"
                data-hl="ru"
                data-callback="callback">
               <script>
        function onloadFunction() {
            if (window.smartCaptcha) {
              const container = document.getElementById('captcha-container');
        
              const widgetId = window.smartCaptcha.render(container, {
                sitekey: «Ваш ключ",
                hl: "ru",
              });
              onSmartCaptchaReady(widgetId)
            }
          }
        </script>
            </div>
        </body>
        </html>
        """
  }

2. Настройка нативной части

1. Создаем:

  private lazy var webView: WKWebView = {
        let configuration = WKWebViewConfiguration()

// Регистрируем конфигурацию
        configuration.userContentController.add(self, name: "NativeClient")
        let webView = WKWebView(frame: .zero, configuration: configuration)
        return webView
    }()

2. Расставляем констрейты под наши нужды

3. Cоздаем enum с жизненным циклом капчи:

enum CaptchaState: String {
    case captchaDidFinish
    case challengeDidAppear
    case challengeDidDisappear
}

4.  Загружаем loadCaptchaBody во ViewDidLoad:

// Создаем url во viewModel
 var url = Foundation.URL(string: “Ваша ссылка”)


   private func loadCaptchaBody() {
        if let baseURL = viewModel.url {
// Загружаем наш JS код
            webView.loadHTMLString(viewModel.body, baseURL: baseURL)
        } else {
            print("Invalid urlAuth")
        }
    }

5.  Подписываемся под делегат WKScriptMessageHandler и используем метод userContentController:

extension AuthorizationViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
  guard let jsData = message.body as? [String: String],
            let methodName = jsData["method"] else { return }

        if let captchaState = CaptchaState(rawValue: methodName) {
            switch captchaState {
//  Когда капча завершилась успешно
            case .captchaDidFinish:
// Здесь отправляем токен
                viewModel.validateToken(jsData["data"] ?? «")
// К примеру со взаимодействием UI, можно свернуть клавиатуру, кнопка далее не скрыта
            case .challengeDidAppear:
// Срабатывает, когда сворачивается bottom sheet, капча не закончена и пользователь вернулся к первому шагу
            case .challengeDidDisappear:
  // Срабатывает в первоначальном кейсе, когда капча не пройдена
            }
        }
    }
}

6.  Нужно отправить токен во viewModel. В моем случае я использую RxSwift, наблюдаю за наличием токена, и если он есть, делаю активной кнопку «Далее» (наличие токена, одна из проверок (к примеру, обычно еще есть проверка «маски» номера телефона или email). Метод validateToken вызывается в пункте 5.

  func validateToken(_ text: String) {
        validateTokenCaptchaPhoneRelay.accept(text)
    }

7. В метод, где происходит валидация телефона или email, вставляем проверку на наличие токена.

    func validatePhone(_ text: String) {
        guard !validateTokenCaptchaPhoneRelay.value.isEmpty else {
            print(“Токен не найден”)
            return
        }
    }

8. Очищаем токен и перезапускаем капчу при каждом появлении экрана в viewWillAppear.

    private func resetCaptcha() {
        webView.evaluateJavaScript("window.smartCaptcha.reset()")
        viewModel.clearCaptchaToken()
    }

// Очищаем токен во ViewModel
  func clearCaptchaToken() {
        validateTokenCaptchaPhoneRelay.accept("")
    }

9. Если у вас будет такой же кейс, как у меня — нужно сделать неактивной вторую часть экрана. Придется делать градиентом, потому что подложка у WebView черная и изменить ее нельзя.

Пример реализации

Заключение

В этой статье я описал процесс интеграции Yandex SmartCaptcha в iOS-приложение, показал основные шаги, указал на возможные сложности и ключевые моменты, на которые стоит обратить внимание. Следуя описанным рекомендациям, вы сможете легко внедрить SmartCaptcha и улучшить безопасность вашего приложения.

Спасибо за внимание! Больше авторских материалов для Mobile-разработчиков от моих коллег читайте в соцсетях SimbirSoft – ВКонтакте и Telegram.

Комментарии (0)