Para configurar políticas de mitigação baseadas em cenários para seu aplicativo no console Bot Management, integre o App Protection SDK do Web Application Firewall (WAF). Este tópico descreve como integrar o App Protection SDK a um aplicativo iOS.
Informações básicas
O App Protection SDK assina as requisições originadas pelo cliente do aplicativo. O servidor do WAF verifica a assinatura da requisição para identificar e bloquear solicitações maliciosas, protegendo seu aplicativo.
Limitações
-
O SDK para iOS está disponível em duas versões: uma com Identifier for Advertising (IDFA) e outra sem IDFA. Os arquivos correspondentes do SDK são:
AliTigerTally_IDFA.framework
AliTigerTally_NOIDFA.framework
Se seu projeto iOS utilizar IDFA, recomendamos a integração da versão AliTigerTally_IDFA do SDK. Caso contrário, utilize a versão AliTigerTally_NOIDFA.
A interface
initpode demandar tempo de processamento. Para garantir proteção ideal, aguarde pelo menos 2 segundos após chamar a interfaceinitantes de invocar a interfacevmpSign. Embora esse atraso não seja obrigatório, um intervalo menor pode impedir que os recursos de segurança funcionem plenamente. Ajuste o intervalo conforme as necessidades do seu negócio.O App Protection SDK exige a versão 9,0 ou superior do iOS.
Pré-requisitos
-
Obtenha o SDK para seu aplicativo iOS.
Para obter o SDK, envie um ticket para entrar em contato com um especialista técnico do produto.
NotaO arquivo do SDK para um aplicativo iOS chama-se
tigertally-X.Y.Z-xxxx-ios.zip, onde X.Y.Z representa o número da versão. O arquivo contém dois arquivos framework e dois arquivos xcframework. -
Obtenha a chave de autenticação do SDK (também conhecida como appkey).
Após ativar o Bot Management, acesse a página . Na lista de aplicativos, clique em Obtain and Copy AppKey para obter a chave de autenticação do SDK. Essa chave é necessária para a inicialização do SDK e deve ser incluída no seu código de integração.
NotaCada conta Alibaba Cloud possui uma appkey exclusiva para todos os nomes de domínio protegidos pelo WAF. Essa appkey é usada na integração do SDK em aplicativos Android, iOS e HarmonyOS.
Exemplo de chave de autenticação:
****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****.
Etapa 1: Crie um projeto
No Xcode, crie um projeto iOS usando o assistente de configuração. A figura a seguir mostra um exemplo de diretório de projeto.
Etapa 2: Integre o framework
Adicione o arquivo do SDK, AliTigerTally_IDFA.framework ou AliTigerTally_NOIDFA.framework, ao seu projeto.
Versão com IDFA

Versão sem IDFA

Adicione o arquivo do módulo Captcha, AliCaptcha.framework, ao seu projeto.

Adicione o arquivo de recurso AliCaptcha.bundle ao seu projeto.

Etapa 3: Adicione bibliotecas de dependência
|
Biblioteca de dependência |
Necessária para IDFA |
Necessária para versão sem IDFA |
|
libc++.tbd |
Sim |
Sim |
|
libresolv.9.tbd |
Sim |
Sim |
|
CoreTelephony.framework |
Sim |
Sim |
|
AdSupport.framework |
Sim |
Não |
|
AppTrackingTransparency.framework |
Sim |
Não |

Etapa 4: Adicione uma flag de linker
Na configuração Other Linker Flags, adicione a flag -ObjC.
Etapa 5: Adicione o código de integração
1. Adicione o arquivo de cabeçalho
-
Para a versão com IDFA, adicione a seguinte instrução de importação:
#import <AliTigerTally_IDFA/AliTigerTally.h> -
Para a versão sem IDFA, adicione a seguinte instrução de importação:
#import <AliTigerTally_NOIDFA/AliTigerTally.h>
2. Configure a assinatura de dados
-
Defina um identificador personalizado de usuário final para o seu negócio. Isso permite configurar políticas de proteção do WAF com maior flexibilidade.
/** * Sets the user account. * * @param account The account information. */ - (void)setAccount:(NSString *)account;-
Parâmetros:
account: Tipo: NSString. Uma string que identifica um usuário. Recomendamos o uso de um formato anonimizado.
Valor de retorno: Nenhum.
-
Exemplo:
// For guest users, you can skip setAccount and initialize directly. After a user logs in, call setAccount and re-initialize. [[AliTigerTally sharedInstance] setAccount:@"testAccount"];
-
-
Inicialize o SDK para coletar dados.
Cada inicialização coleta informações do dispositivo uma única vez. Chame a função init novamente para reiniciar a coleta de dados em diferentes cenários de negócio.
A inicialização oferece suporte a três modos de coleta de dados: completo, privacidade personalizada e sem privacidade. O modo sem privacidade exclui campos relacionados à privacidade do usuário final, como IDFA e IDFV.
NotaPara atender aos seus requisitos internos, selecione um modo de coleta de dados adequado que garanta a integridade das informações. Dados mais completos melhoram a detecção de ameaças.
// Initialization callback. Returns the status code of the interface call. typedef void (^TTInitListener)(int); /** * Initializes the SDK. * * @param appkey The key. * @param options Optional parameters. * @param onInitFinish The callback for when initialization is complete. * @return Indicates whether the initialization was successful. */ - (int)init:(NSString *)appkey collectType:(TTCollectType)type options:(NSMutableDictionary *_Nullable)options listener:(TTInitListener _Nullable)onInitFinish;-
Parâmetros:
appkey: Tipo: NSString. Sua chave de autenticação do SDK.
-
collectType: Tipo: TTCollectType. O modo de coleta de dados. Valores válidos:
Campo
Descrição
Exemplo
TT_DEFAULT
Coleta todos os dados.
TT_DEFAULT
TT_NO_BASIC_DATA
Não coleta dados básicos do dispositivo.
Inclui nome do dispositivo, versão do sistema operacional e resolução de tela.
TT_NO_X | TT_NO_Y
(onde X e Y representam os tipos de campos de dados a excluir)
TT_NO_UNIQUE_DATA
Não coleta dados de identificadores exclusivos.
Inclui IDFV e IDFA.
TT_NO_EXTRA_DATA
Não coleta dados estendidos do dispositivo.
Inclui informações de Wi-Fi conectado (SSID, BSSID) e a lista de redes Wi-Fi próximas.
TT_NOT_GRANTED
Não coleta nenhum dos dados relacionados à privacidade mencionados anteriormente.
TT_NOT_GRANTED
-
options: Tipo: NSMutableDictionary. Parâmetros opcionais para coleta de dados. O valor padrão é nil. Estão disponíveis os seguintes parâmetros:
Campo
Descrição
Exemplo
IPv6
Especifica se deve usar um nome de domínio IPv6 para relatar informações do dispositivo.
0 (padrão): Usa um nome de domínio IPv4.
1: Usa um nome de domínio IPv6.
1
Intl
Especifica se deve usar um nome de domínio fora da China continental para relatar informações do dispositivo.
0 (padrão): Relata para a China continental.
1: Relata para regiões fora da China continental.
1
CustomUrl
Define o nome de domínio do servidor de relatório de dados.
https://cloudauth-device.us-west-1.aliyuncs.com
CustomHost
Define o host do servidor de relatório de dados.
cloudauth-device.us-west-1.aliyuncs.com
NotaPara a maioria dos sites internacionais, defina apenas o parâmetro Intl. Para relatar dados a um site específico, configure os parâmetros CustomUrl e CustomHost. Os sites disponíveis incluem:
Se
Intlestiver definido como0, os dados serão relatados ao site padrão na China (Xangai): https://cloudauth-device.cn-shanghai.aliyuncs.com-
Se
Intlestiver definido como1:O site padrão é Singapura: https://cloudauth-device.ap-southeast-1.aliyuncs.com
Indonésia (Jacarta): https://cloudauth-device.ap-southeast-5.aliyuncs.com
EUA (Vale do Silício): https://cloudauth-device.us-west-1.aliyuncs.com
Alemanha (Frankfurt): https://cloudauth-device.eu-central-1.aliyuncs.com
China (Hong Kong): https://cloudauth-device.cn-hongkong.aliyuncs.com
-
listener: Tipo: TTInitListener. A interface de callback de inicialização do SDK. Use este callback para determinar o status da inicialização. O valor padrão é nil.
TTCode
Código
Descrição
TT_SUCCESS
0
Inicialização bem-sucedida.
TT_NOT_INIT
-1
O SDK não foi inicializado.
TT_NOT_PERMISSION
-2
As permissões necessárias do iOS não foram concedidas.
TT_UNKNOWN_ERROR
-3
Ocorreu um erro desconhecido do sistema.
TT_NETWORK_ERROR
-4
Ocorreu um erro de rede.
TT_NETWORK_ERROR_EMPTY
-5
Ocorreu um erro de rede. O conteúdo retornado é uma string vazia.
TT_NETWORK_ERROR_INVALID
-6
O formato da resposta de rede é inválido.
TT_PARSE_SRV_CFG_ERROR
-7
Falha ao analisar a configuração do servidor.
TT_NETWORK_RET_CODE_ERROR
-8
O gateway retornou uma resposta de falha.
TT_APPKEY_EMPTY
-9
A appkey está vazia.
TT_PARAMS_ERROR
-10
Ocorreram outros erros de parâmetro.
TT_FGKEY_ERROR
-11
Ocorreu um erro de cálculo de chave.
TT_APPKEY_ERROR
-12
A versão do SDK não corresponde à versão da appkey.
Valor de retorno: Tipo: int. Um código de erro.
0indica sucesso, e um número negativo indica falha.-
Exemplo:
// The appkey is the authentication key assigned to you on the Alibaba Cloud console. NSString *appKey = @"xxxxxxxxxxxxxxxxxxxxx"; // Optional parameters. You can configure IPv6 and reporting from outside the Chinese mainland. NSMutableDictionary *options = [[NSMutableDictionary alloc] init]; [options setValue:@"0" forKey:@"IPv6"]; // Use IPv4. [options setValue:@"0" forKey:@"Intl"]; // Report data from within the Chinese mainland. // [options setValue:@"1" forKey:@"Intl"]; // Report data from outside the Chinese mainland. // Report data to the US (West) region. // [options setValue:@"https://cloudauth-device.us-west-1.aliyuncs.com" forKey:@"CustomUrl"]; // [options setValue:@"cloudauth-device.us-west-1.aliyuncs.com" forKey:@"CustomHost"]; // An initialization call collects device information once. You can call the init function again to collect information for different business scenarios. // Full data collection. if (0 == [[AliTigerTally sharedInstance] init:appkey collectType:TT_DEFAULT options:options listener:nil]) { NSLog(@"Initialization successful"); } else { NSLog(@"Initialization failed"); } // To collect specific types of private data, use the "|" operator to specify multiple types. TTCollectType collectPrivacy = TT_NO_BASIC_DATA | TT_NO_EXTRA_DATA; int ret = [[AliTigerTally sharedInstance] init:appkey collectType:collectPrivacy options:options listener:nil]; // Do not collect private data. int ret = [[AliTigerTally sharedInstance] init:appkey collectType:TT_NOT_GRANTED options:options listener:nil];
-
-
Hashing de dados.
Esta interface de assinatura personalizada aplica hash aos dados de entrada para gerar uma string
whash, que serve como uma assinatura personalizada. Para requisições POST, PUT e PATCH, use o corpo da requisição como entrada. Para requisições GET e DELETE, use a URL completa. Adicione também a stringwhashgerada ao campo ali_sign_whash no cabeçalho da requisição HTTP.// Request type: typedef NS_ENUM(NSInteger, TTRequestType) { TT_GET=0, TT_POST, TT_PUT, TT_PATCH, TT_DELETE }; /** * Hashes the custom signature data. * @param type The data type. * @param input The data to be signed. * @return The whash string. */ - (NSString *)vmpHash:(TTRequestType)type input:(NSData *)input;-
Parâmetros:
-
type: Tipo: TTTypeRequest. O tipo de dados. Valores válidos:
GET: Dados de requisição GET.
POST: Dados de requisição POST.
PUT: Dados de requisição PUT.
PATCH: Dados de requisição PATCH.
DELETE: Dados de requisição DELETE.
input: Tipo: NSData. Os dados a serem assinados. Passe o corpo ou a URL com base no tipo.
-
Valor de retorno: Tipo: NSString. A string
whash.-
Exemplo:
// GET request NSString *url = @"https://tigertally.aliyun.com/apptest"; NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_GET input:[url dataUsingEncoding:NSUTF8StringEncoding]]; NSLog(@"whash: %@", whash); // POST request NSString *body = @"hello world"; NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_POST input:[body dataUsingEncoding:NSUTF8StringEncoding]]; NSLog(@"whash: %@", whash);
NotaChame esta interface apenas se você tiver ativado a opção de assinatura personalizada no console do WAF. Caso utilize a configuração padrão de assinatura, pule esta etapa.
-
-
Assinatura de dados.
Este método utiliza a tecnologia VMP para assinar os dados de entrada e retorna uma string
wtokenpara autenticação da requisição./** * Signs the data. * @param input The data to be signed. * @return The wtoken string. */ - (NSString *)vmpSign:(NSData *)input;-
Parâmetros:
input: Tipo: NSData. Os dados a serem assinados. Geralmente, trata-se de todo o corpo da requisição ou do
whashproveniente da assinatura personalizada.
Valor de retorno: Tipo: NSString. A string
wtoken.-
Exemplo:
// Default signature configured in the console (custom signing is not selected). NSString *body = @"hello world"; NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[body dataUsingEncoding:NSUTF8StringEncoding]]; NSLog(@"wtoken: %@", wtoken); // Custom signing configured in the console. // Custom signing for a POST request. NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_POST input:[body dataUsingEncoding:NSUTF8StringEncoding]]; NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]]; NSLog(@"whash: %@, wtoken: %@", whash, wtoken); // Custom signing for a GET request. NSString *url = @"https://tigertally.aliyun.com/apptest"; NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_GET input:[url dataUsingEncoding:NSUTF8StringEncoding]]; NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]]; NSLog(@"whash: %@, wtoken: %@", whash, wtoken);NotaAo usar assinatura personalizada, a entrada para a interface vmpSign deve ser a string
whashgerada por vmpHash. Ao configurar uma política baseada em cenário para App Protection, defina o Custom Signing Field comoali_sign_whash.Ao chamar vmpHash para gerar um
whashpara uma requisição GET, garanta que a URL de entrada seja idêntica à URL final da requisição de rede. Preste atenção especial à codificação da URL, pois alguns frameworks codificam caracteres ou parâmetros automaticamente.A entrada para a interface vmpHash não pode ser um array de bytes ou uma string vazia. Se a entrada for uma URL, ela deve incluir um caminho ou um parâmetro.
Ao chamar vmpSign, se o corpo da requisição estiver vazio (por exemplo, em uma requisição GET ou POST sem corpo), passe
nilou a representação NSData de uma string vazia, como[@"" dataUsingEncoding:NSUTF8StringEncoding].-
Se
whashouwtokenretornar uma das strings a seguir, isso indica um erro:you must call init first: A função
initnão foi chamada.you must input correct data: Os dados de entrada são inválidos.
you must input correct type: O tipo de entrada é inválido.
-
3. Verificação secundária
-
Verifique o resultado.
Para determinar se a verificação secundária é necessária, verifique o cookie e o corpo da resposta. Se o cabeçalho contiver vários campos Set-Cookie, combine-os em uma única string de cookie antes de chamar esta interface.
/** * Checks whether to perform secondary verification. * * @param cookie The response cookie. * @param body The response body. * @return 0: Pass, 1: Secondary verification required. */ - (int)cptCheck:(NSString *)cookie body:(NSData *)body;-
Parâmetros:
cookie: Tipo: NSString. Todos os cookies na resposta da requisição.
body: Tipo: NSData. Todo o corpo da resposta da requisição.
Valor de retorno: Tipo: int. O resultado da decisão.
0indica que a requisição foi aprovada, e1indica que a verificação secundária é necessária.-
Exemplo:
NSString *cookie = @"key1=value1;key2=value2;"; NSData *body = xxx; int recheck = [[AliTigerTally sharedInstance] cptCheck:cookie body:body]; NSLog(@"recheck: %d", recheck);
-
-
Crie um slider.
Se o resultado de cptCheck indicar necessidade de verificação, crie um objeto slider. O objeto TTCaptcha fornece os métodos
showedismisspara controlar a janela do slider. Use TTOption para configurar seus parâmetros e TTDelegate para lidar com os callbacks de status. Para personalizar a janela do slider, forneça uma URL para uma página personalizada, que pode ser um arquivo HTML local ou uma página remota./** * Displays the slider for verification. * * @param view The parent component. * @param option The parameters. * @param detegate The callback protocol. */ - (TTCaptcha *)cptCreate:(UIView *)view option:(TTOption *)option delegate:(id<TTDelegate>)detegate; @protocol TTDelegate <NSObject> @required // Slider verification is successful. - (void)success:(TTCaptcha *)captcha data:(NSString *)data; // Slider verification failed. - (void)failed:(TTCaptcha *)captcha code:(NSString *)code; @end @interface TTOption : NSObject // Tap to cancel. @property (nonatomic, assign) BOOL cancelable; // Custom page. @property (nonatomic, strong) NSString *customUri; // Language. @property (nonatomic, strong) NSString *language; @end @interface TTCaptcha : NSObject - (instancetype)init:(UIView *)view option:(TTOption *)option delegate:(id<TTDelegate>)delegate; // Obtains the slider traceId for data analytics. - (NSString *)getTraceId; // Displays the slider. - (void)show; // Dismisses the slider. - (void)dismiss; @end-
Parâmetros:
view: Tipo: View. A visualização da página atual.
option: Tipo: TTOption. Os parâmetros de configuração do slider.
delegate: Tipo: TTDelegate. O callback de status do slider.
Valor de retorno: Tipo: TTCaptcha. O objeto slider.
-
Exemplo:
#pragma mark - TTDelegate - (void)failed:(TTCaptcha *)captcha code:(nonnull NSString *)code { NSLog(@"captcha failed: %@", code); } - (void)success:(TTCaptcha *)captcha data:(nonnull NSString *)data { NSLog(@"captcha success: %@", data); } TTOption *option = [[TTOption alloc] init]; // option.customUri = @"ali-tt-captcha-demo-ios"; option.language = @"cn"; option.cancelable = true; TTCaptcha *captcha = [[AliTigerTally sharedInstance] cptCreate:[self view] option:option delegate:self]; [captcha show];NotaUm erro "Verification failed" indica que uma exceção foi detectada durante ou após a interação do usuário com o slider.
A tabela a seguir descreve os códigos de erro.
1001: Falha no desafio de verificação.
1002: Ocorreu uma exceção do sistema.
1003: Um parâmetro inválido foi especificado.
1005: A verificação foi cancelada.
8001: Falha ao exibir o slider.
8002: Ocorreu uma exceção nos dados de verificação do slider.
8003: Ocorreu uma exceção interna durante a verificação do slider.
8004: Ocorreu um erro de rede.
-
Melhor prática
#import "DemoController.h"
#if __has_include(<AliTigerTally_NOIDFA/AliTigerTally_NOIDFA.h>)
#import <AliTigerTally_NOIDFA/AliTigerTally_NOIDFA.h>
#else
#import <AliTigerTally_IDFA/AliTigerTally_IDFA.h>
#endif
@interface DemoController () <TTDelegate>
@end
static NSString *kAppHost = @"******";
static NSString *kAppUrl = @"******";
static NSString *kAppkey = @"******";
@implementation DemoController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self doTest];
}
- (void)doTest {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
// Initialize the SDK.
NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
// [options setValue:@"1" forKey:@"Intl"]; // Configure for international reporting.
int code = [[AliTigerTally sharedInstance] init:kAppkey collectType:TT_DEFAULT options:options listener:nil];
NSLog(@"tigertally init: %d", code);
// Do not make a synchronous call immediately.
[NSThread sleepForTimeInterval:2.0];
// Generate the signature.
NSString *body = @"hello world";
NSString *whash = nil, *wtoken = nil;
// Custom signing.
whash = [[AliTigerTally sharedInstance] vmpHash:TT_Post input:[body dataUsingEncoding:NSUTF8StringEncoding]];
wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];
NSLog(@"tigertally vmp: %@, %@", whash, wtoken);
// Default signing.
// wtoken = [[AliTigerTally sharedInstance] vmpSign:[body dataUsingEncoding:NSUTF8StringEncoding]];
// NSLog(@"tigertally vmp: %@", wtoken);
[self doPost:kAppUrl host:kAppHost whash:whash wtoken:wtoken body:[body dataUsingEncoding:NSUTF8StringEncoding] callback:^(NSInteger code, NSString * _Nonnull cookie, NSData * _Nonnull body) {
int check = [[AliTigerTally sharedInstance] cptCheck:cookie body:body];
NSLog(@"captcha result:%d", check);
if (check == 0) return;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self doShow];
}];
}];
}];
[thread start];
}
- (void)doShow {
NSLog(@"captcha show");
TTOption *option = [[TTOption alloc] init];
// option.customUri = @"ali-tt-captcha-demo-ios";
option.language = @"cn";
option.cancelable = true;
TTCaptcha *captcha = [[AliTigerTally sharedInstance] cptCreate:[self view] option:option delegate:self];
[captcha show];
}
- (void)doPost:(NSString *)url host:(NSString *)host whash:(NSString *)whash wtoken:(NSString *)wtoken body:(NSData *)body callback:(void(^)(NSInteger code, NSString* cookie, NSData *body))callback {
NSLog(@"start reqeust post");
NSURL* requestUrl = [NSURL URLWithString: url];
NSData* requestBody = body;
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
[request setValue: @"text/x-markdown" forHTTPHeaderField: @"Content-Type"];
[request setValue: host forHTTPHeaderField: @"HOST"];
[request setValue: wtoken forHTTPHeaderField:@"wToken"];
if (whash) {
[request setValue:whash forHTTPHeaderField:@"ali_sign_whash"];
}
request.HTTPMethod = @"post";
request.HTTPBody = requestBody;
NSURLSessionDataTask* dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"tiger tally sign failed: %@", [error description]);
callback(-1, nil, [[error description] dataUsingEncoding:NSUTF8StringEncoding]);
return;
}
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response;
NSInteger code = httpResponse.statusCode;
NSString* body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSMutableString* cookies = [[NSMutableString alloc] init];
for (NSHTTPCookie* cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
if ([url containsString:[cookie domain]]) {
NSLog(@"domain:%@, path: %@, name:%@, value: %@", [cookie domain], [cookie path], [cookie name], [cookie value]);
[cookies appendFormat: @"%@=%@;", [cookie name], [cookie value]];
}
}
NSLog(@"reponse code: %ld", code);
NSLog(@"reponse cookie: %@", cookies);
NSLog(@"reponse body: %@", body ? (body.length > 100 ? [body substringToIndex:100]:body) : @"");
callback(code, cookies, data);
}];
[dataTask resume];
}
#pragma mark - TTDelegate
- (void)failed:(TTCaptcha *)captcha code:(nonnull NSString *)code {
NSLog(@"captcha failed: %@", code);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:code preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:true completion:nil];
}
- (void)success:(TTCaptcha *)captcha data:(nonnull NSString *)data {
NSLog(@"captcha success: %@", data);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:data preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:true completion:nil];
}
@end