package com.plugin.auth import android.app.Activity import android.content.Context import android.util.Base64 import androidx.credentials.ClearCredentialStateRequest import androidx.credentials.CredentialManager import androidx.credentials.CustomCredential import androidx.credentials.GetCredentialRequest import androidx.credentials.GetCredentialResponse import androidx.credentials.exceptions.ClearCredentialException import androidx.credentials.exceptions.GetCredentialException import androidx.credentials.exceptions.NoCredentialException import com.google.android.libraries.identity.googleid.GetGoogleIdOption import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException import app.tauri.annotation.Command import app.tauri.annotation.InvokeArg import app.tauri.annotation.TauriPlugin import app.tauri.plugin.Invoke import app.tauri.plugin.JSObject import app.tauri.plugin.Plugin import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.security.SecureRandom @InvokeArg class KirimArgs { var idKlien: String? = null } @TauriPlugin class BluclasPlugin(private val activity: Activity) : Plugin(activity) { private val credentialManager by lazy { CredentialManager.create(activity) } private val scope = CoroutineScope(Dispatchers.Main) @Command fun masuk(invoke: Invoke) { val args = invoke.parseArgs(KirimArgs::class.java) val idKlien = args.idKlien if (idKlien.isNullOrEmpty()) { invoke.reject("ID Klien belum disediakan") return } val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() .setFilterByAuthorizedAccounts(true) .setServerClientId(idKlien) .setNonce(generateSecureRandomNonce()) .build() val request = GetCredentialRequest.Builder() .addCredentialOption(googleIdOption) .build() scope.launch { try { val result = credentialManager.getCredential( request = request, context = activity ) handleSignInResult(result, invoke) } catch (e: NoCredentialException) { signInWithAllAccounts(idKlien, invoke) } catch (e: GetCredentialException) { invoke.reject("Gagal masuk: ${e.message}") } } } @Command fun keluar(invoke: Invoke) { scope.launch { try { credentialManager.clearCredentialState(ClearCredentialStateRequest()) invoke.resolve() } catch (e: ClearCredentialException) { invoke.reject("Sign-out failed: ${e.message}") } } } private suspend fun signInWithAllAccounts(serverClientId: String, invoke: Invoke) { val googleIdOption = GetGoogleIdOption.Builder() .setFilterByAuthorizedAccounts(false) .setServerClientId(serverClientId) .setNonce(generateSecureRandomNonce()) .build() val request = GetCredentialRequest.Builder() .addCredentialOption(googleIdOption) .build() try { val result = credentialManager.getCredential( request = request, context = activity ) handleSignInResult(result, invoke) } catch (e: GetCredentialException) { invoke.reject("Sign-in failed: ${e.message}") } } private fun generateSecureRandomNonce(byteLength: Int = 32): String { val randomBytes = ByteArray(byteLength) SecureRandom().nextBytes(randomBytes) return Base64.encodeToString(randomBytes, Base64.NO_WRAP or Base64.URL_SAFE or Base64.NO_PADDING) } private fun handleSignInResult(result: GetCredentialResponse, invoke: Invoke) { val credential = result.credential when (credential) { is CustomCredential -> { if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) { try { val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data) // NOTE: do not treat the user as signed in on the client // alone — send idToken to your backend and verify it there // before trusting it. val ret = JSObject() ret.put("idToken", googleIdTokenCredential.idToken) ret.put("id", googleIdTokenCredential.id) ret.put("displayName", googleIdTokenCredential.displayName) ret.put("givenName", googleIdTokenCredential.givenName) ret.put("familyName", googleIdTokenCredential.familyName) ret.put( "profilePictureUri", googleIdTokenCredential.profilePictureUri?.toString() ) ret.put("phoneNumber", googleIdTokenCredential.phoneNumber) invoke.resolve(ret) } catch (e: GoogleIdTokenParsingException) { invoke.reject("Invalid Google ID token response: ${e.message}") } } else { invoke.reject("Unexpected credential type: ${credential.type}") } } else -> { invoke.reject("Unexpected credential type") } } } }