Node.js

Verbindung mit dem Backend

Integration der Benutzeroberfläche mit den Backend-Endpunkten

Im vorigen Modul haben wir die Benutzeroberfläche und eine kleine UI-Zustandsmaschine (VerifyUiState). Jetzt verbinden wir diese Benutzeroberfläche mit unserem Backend.

Der Grundgedanke ist, dass die Android-App niemals Vonage direkt anruft. Sie wird nur Ihr Backend anrufen:

  • POST /verification um den Prüfablauf zu starten

  • POST /check-code um einen Code zu validieren (vorerst SMS-Code)

  • POST /next den Fallback-Kanal (SMS) sofort zu erzwingen (damit wir nicht ~20 Sekunden warten müssen)

Beginnen wir mit der Umsetzung!

Überprüfung starten (POST /verification)

Wenn der Benutzer tippt Überprüfung starten:

  1. Die App sendet die Rufnummer an /verification

  2. Das Backend gibt zurück:

    • request_id (immer)
    • check_url (optional, wird später für Silent Auth verwendet)
  3. In diesem Abschnitt wird Silent Auth übersprungen und sofort ein Fallback zu SMS durch den Aufruf von /next

  4. Die Benutzeroberfläche wechselt zum SMS-Bildschirm (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)
}

SMS-Code einreichen (POST /check-code)

Wenn der Benutzer tippt Code einreichen:

  1. Die App sendet { request_id, code } zu /check-code
  2. Das Backend antwortet mit { verified: true/false }
  3. UI zeigt Erfolg oder "ungültigen Code" an
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
    )
}

Fallback schnell erzwingen (POST /next)

Aufruf von /next ist aus Sicht der "Korrektheit" optional (Vonage kann automatisch darauf zurückgreifen), aber es ist sehr nützlich für die Benutzerfreundlichkeit:

Wenn wir bereits wissen, dass wir Silent Auth nicht abschließen werden (oder wir es noch nicht implementiert haben), kann der Aufruf von /next vermeidet das Warten auf die Zeitüberschreitung von Silent Auth.

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