Node.js

Conecte-se ao backend

Integração da interface do usuário com os pontos de extremidade do backend

No módulo anterior, criamos a interface do usuário e uma pequena máquina de estados para a interface do usuário (VerifyUiState). Agora vamos conectar essa interface do usuário ao nosso backend.

A ideia principal é que o aplicativo para Android nunca entre em contato diretamente com a Vonage. Ele entrará em contato apenas com o seu backend:

  • POST /verification para iniciar o fluxo de verificação

  • POST /check-code para validar um código (código por SMS, por enquanto)

  • POST /next para forçar o uso do canal alternativo (SMS) imediatamente (para não termos que esperar cerca de 20 segundos)

Vamos começar com a implementação!

Iniciar verificação (POST /verification)

Quando o usuário toca em Iniciar a verificação:

  1. O aplicativo envia o número de telefone para /verification

  2. O backend retorna:

    • request_id (sempre)
    • check_url (opcional, usado posteriormente para a autenticação silenciosa)
  3. Nesta seção, vamos ignorar a autenticação silenciosa e solicitar imediatamente o recurso alternativo por SMS chamando /next

  4. A interface do usuário faz a transição para a tela de SMS (EnterSms(requestId))

private suspend fun startVerification(phone: String): Pair<String, String?> = withContext(Dispatchers.IO) {
    val client = OkHttpClient()

    val json = Gson().toJson(mapOf("phone" to phone))
    val requestBody = json.toRequestBody("application/json".toMediaType())

    val request = Request.Builder()
        .url("$BACKEND_URL/verification")
        .post(requestBody)
        .build()

    val response = client.newCall(request).execute()
    if (!response.isSuccessful) {
        val errorBody = response.body?.string() ?: "Unknown error"
        throw IOException("Start verification failed: HTTP ${response.code} - $errorBody")
    }

    val body = response.body?.string() ?: throw IOException("Empty response body")
    val jsonBody = Gson().fromJson(body, JsonObject::class.java)

    val requestId = jsonBody.get("request_id")?.asString ?: throw IOException("Missing request_id")
    val checkUrl = jsonBody.get("check_url")?.asString // may be null

    Pair(requestId, checkUrl)
}

Enviar código por SMS (POST /check-code)

Quando o usuário toca em Enviar código:

  1. O aplicativo envia { request_id, code } para /check-code
  2. O backend responde com { verified: true/false }
  3. A interface do usuário exibe “sucesso” ou “código inválido”
private suspend fun submitCode(requestId: String, code: String): CheckCodeResponse = withContext(Dispatchers.IO) {
    val client = OkHttpClient()

    val json = Gson().toJson(mapOf("request_id" to requestId, "code" to code))
    val requestBody = json.toRequestBody("application/json".toMediaType())

    val request = Request.Builder()
        .url("$BACKEND_URL/check-code")
        .post(requestBody)
        .build()

    val response = client.newCall(request).execute()
    if (!response.isSuccessful) {
        val errorBody = response.body?.string() ?: "Unknown error"
        throw IOException("Check code failed: HTTP ${response.code} - $errorBody")
    }

    val body = response.body?.string() ?: throw IOException("Empty response body")
    val jsonBody = Gson().fromJson(body, JsonObject::class.java)

    CheckCodeResponse(
        verified = jsonBody.get("verified")?.asBoolean ?: false,
        status = jsonBody.get("status")?.asString
    )
}

Forçar o fallback rapidamente (POST /next)

Chamando /next é opcional do ponto de vista da “precisão” (a Vonage pode recorrer automaticamente a uma alternativa), mas é muito útil para a experiência do usuário:

Se já sabemos que não vamos concluir a autenticação silenciosa (ou ainda não a estamos implementando), chamar /next evita a espera pelo tempo limite da autenticação silenciosa.

private suspend fun requestNextWorkflow(requestId: String): Unit = withContext(Dispatchers.IO) {
    val client = OkHttpClient()

    val json = Gson().toJson(mapOf("requestId" to requestId))
    val requestBody = json.toRequestBody("application/json".toMediaType())

    val request = Request.Builder()
        .url("$BACKEND_URL/next")
        .post(requestBody)
        .build()

    val response = client.newCall(request).execute()
    if (!response.isSuccessful) {
        val errorBody = response.body?.string() ?: "Unknown error"
        throw IOException("Next workflow failed: HTTP ${response.code} - $errorBody")
    }
}