Node.js

Connexion avec le backend

Intégration de l'interface utilisateur avec les points de terminaison du backend

Dans le module précédent, nous avons construit l'interface utilisateur et une petite machine à états (VerifyUiState). Nous allons maintenant connecter cette interface utilisateur à notre backend.

L'idée principale est que l'application Android n'appellera jamais Vonage directement. Elle n'appellera que votre backend :

  • POST /verification pour lancer le processus de vérification

  • POST /check-code pour valider un code (code SMS pour l'instant)

  • POST /next pour forcer le canal de repli (SMS) immédiatement (afin de ne pas attendre ~20 secondes)

Commençons par la mise en œuvre !

Démarrer la vérification (POST /verification)

Lorsque l'utilisateur tape sur Début de la vérification:

  1. L'application envoie le numéro de téléphone à /verification

  2. Le backend renvoie :

    • request_id (toujours)
    • check_url (facultatif, utilisé ultérieurement pour l'authentification silencieuse)
  3. Dans cette section, nous allons ignorer l'authentification silencieuse et demander immédiatement le retour aux SMS en appelant /next

  4. L'interface utilisateur passe à l'écran 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)
}

Soumettre le code SMS (POST /check-code)

Lorsque l'utilisateur tape sur Soumettre le code:

  1. L'application envoie { request_id, code } à /check-code
  2. Le backend répond par { verified: true/false }
  3. L'interface utilisateur indique un succès ou un "code invalide".
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
    )
}

Forcer le repli rapidement (POST /next)

Appel /next est facultatif du point de vue de la "correction" (Vonage peut se rabattre automatiquement), mais il est très utile pour l'interface utilisateur :

Si nous savons déjà que nous ne compléterons pas l'authentification silencieuse (ou que nous ne l'avons pas encore mise en œuvre), l'appel de l'option /next évite d'attendre le délai d'expiration de l'authentification silencieuse.

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")
    }
}