From 54c430af2bebf1f78f3e2c2c381e6d03027a8e71 Mon Sep 17 00:00:00 2001 From: dankito Date: Sat, 19 Feb 2022 13:17:02 +0100 Subject: [PATCH] Replaced callbacks with coroutines --- .../banking/fints4k/android/Presenter.kt | 6 +- .../main/kotlin/AccountTransactionsView.kt | 5 +- .../banking/fints/FinTsClientDeprecated.kt | 105 ++-- .../banking/fints/FinTsClientForCustomer.kt | 16 +- .../dankito/banking/fints/FinTsJobExecutor.kt | 474 +++++++++--------- .../dankito/banking/fints/RequestExecutor.kt | 107 ++-- .../fints/callback/FinTsClientCallback.kt | 6 +- .../fints/callback/NoOpFinTsClientCallback.kt | 14 +- .../callback/SimpleFinTsClientCallback.kt | 19 +- .../banking/fints/webclient/IWebClient.kt | 6 +- .../banking/fints/webclient/KtorWebClient.kt | 12 +- .../fints/webclient/ProxyingWebClient.kt | 4 +- fints4k/src/nativeMain/kotlin/Application.kt | 13 +- .../BlockingKtorWebClient.kt | 14 - 14 files changed, 375 insertions(+), 426 deletions(-) delete mode 100644 fints4k/src/nativeMain/kotlin/net.dankito.banking.fints.webclient/BlockingKtorWebClient.kt diff --git a/SampleApplications/AndroidApp/src/main/java/net/codinux/banking/fints4k/android/Presenter.kt b/SampleApplications/AndroidApp/src/main/java/net/codinux/banking/fints4k/android/Presenter.kt index ecb32797..6b70dc43 100644 --- a/SampleApplications/AndroidApp/src/main/java/net/codinux/banking/fints4k/android/Presenter.kt +++ b/SampleApplications/AndroidApp/src/main/java/net/codinux/banking/fints4k/android/Presenter.kt @@ -3,6 +3,7 @@ package net.codinux.banking.fints4k.android import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.datetime.LocalDate import net.dankito.banking.fints.FinTsClientDeprecated import net.dankito.banking.fints.callback.SimpleFinTsClientCallback @@ -27,10 +28,11 @@ class Presenter { fun retrieveAccountData(bankCode: String, customerId: String, pin: String, finTs3ServerAddress: String, retrievedResult: (AddAccountResponse) -> Unit) { - fintsClient.addAccountAsync(AddAccountParameter(bankCode, customerId, pin, finTs3ServerAddress)) { response -> + GlobalScope.launch(Dispatchers.IO) { + val response = fintsClient.addAccountAsync(AddAccountParameter(bankCode, customerId, pin, finTs3ServerAddress)) log.info("Retrieved response from ${response.bank.bankName} for ${response.bank.customerName}") - GlobalScope.launch(Dispatchers.Main) { + withContext(Dispatchers.Main) { retrievedResult(response) } } diff --git a/SampleApplications/WebApp/src/main/kotlin/AccountTransactionsView.kt b/SampleApplications/WebApp/src/main/kotlin/AccountTransactionsView.kt index ff83a74f..fe2e8f05 100644 --- a/SampleApplications/WebApp/src/main/kotlin/AccountTransactionsView.kt +++ b/SampleApplications/WebApp/src/main/kotlin/AccountTransactionsView.kt @@ -1,3 +1,5 @@ +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import net.dankito.banking.fints.FinTsClientDeprecated import net.dankito.banking.fints.model.AccountTransaction import net.dankito.banking.fints.model.AddAccountParameter @@ -23,7 +25,8 @@ class AccountTransactionsView(props: AccountTransactionsViewProps) : RComponent< // due to CORS your bank's servers can not be requested directly from browser -> set a CORS proxy url in main.kt // TODO: set your credentials here - props.client.addAccountAsync(AddAccountParameter("", "", "", "")) { response -> + GlobalScope.launch { + val response = props.client.addAccountAsync(AddAccountParameter("", "", "", "")) if (response.successful) { val balance = response.retrievedData.sumOf { it.balance?.amount?.string?.replace(',', '.')?.toDoubleOrNull() ?: 0.0 } // i know, double is not an appropriate data type for amounts diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClientDeprecated.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClientDeprecated.kt index 06bb739b..bf1c5a62 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClientDeprecated.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClientDeprecated.kt @@ -44,7 +44,7 @@ open class FinTsClientDeprecated( open fun getAnonymousBankInfoAsync(bank: BankData, callback: (FinTsClientResponse) -> Unit) { GlobalScope.launch { - getAnonymousBankInfo(bank, callback) + callback(getAnonymousBankInfo(bank)) } } @@ -54,90 +54,74 @@ open class FinTsClientDeprecated( * * On success [bank] parameter is updated afterwards. */ - open fun getAnonymousBankInfo(bank: BankData, callback: (FinTsClientResponse) -> Unit) { + open suspend fun getAnonymousBankInfo(bank: BankData): FinTsClientResponse { val context = JobContext(JobContextType.AnonymousBankInfo, this.callback, product, bank) - jobExecutor.getAnonymousBankInfo(context) { response -> - callback(FinTsClientResponse(context, response)) - } + val response = jobExecutor.getAnonymousBankInfo(context) + return FinTsClientResponse(context, response) } - open fun addAccountAsync(parameter: AddAccountParameter, callback: (AddAccountResponse) -> Unit) { + open suspend fun addAccountAsync(parameter: AddAccountParameter): AddAccountResponse { val bank = parameter.bank val context = JobContext(JobContextType.AddAccount, this.callback, product, bank) /* First dialog: Get user's basic data like BPD, customer system ID and her TAN methods */ - jobExecutor.retrieveBasicDataLikeUsersTanMethods(context, parameter.preferredTanMethods, parameter.preferredTanMedium) { newUserInfoResponse -> + val newUserInfoResponse = jobExecutor.retrieveBasicDataLikeUsersTanMethods(context, parameter.preferredTanMethods, parameter.preferredTanMedium) - if (newUserInfoResponse.successful == false) { // bank parameter (FinTS server address, ...) already seem to be wrong - callback(AddAccountResponse(context, newUserInfoResponse)) - return@retrieveBasicDataLikeUsersTanMethods - } - - /* Second dialog, executed in retrieveBasicDataLikeUsersTanMethods() if required: some banks require that in order to initialize a dialog with - strong customer authorization TAN media is required */ - - addAccountGetAccountsAndTransactions(context, parameter, callback) + if (newUserInfoResponse.successful == false) { // bank parameter (FinTS server address, ...) already seem to be wrong + return AddAccountResponse(context, newUserInfoResponse) } + + /* Second dialog, executed in retrieveBasicDataLikeUsersTanMethods() if required: some banks require that in order to initialize a dialog with + strong customer authorization TAN media is required */ + + return addAccountGetAccountsAndTransactions(context, parameter) } - protected open fun addAccountGetAccountsAndTransactions(context: JobContext, parameter: AddAccountParameter, - callback: (AddAccountResponse) -> Unit) { + protected open suspend fun addAccountGetAccountsAndTransactions(context: JobContext, parameter: AddAccountParameter): AddAccountResponse { /* Third dialog: Now we can initialize our first dialog with strong customer authorization. Use it to get UPD and customer's accounts */ - jobExecutor.getAccounts(context) { getAccountsResponse -> + val getAccountsResponse = jobExecutor.getAccounts(context) - if (getAccountsResponse.successful == false) { - callback(AddAccountResponse(context, getAccountsResponse)) - return@getAccounts - } + if (getAccountsResponse.successful == false) { + return AddAccountResponse(context, getAccountsResponse) + } - /* Fourth dialog (if requested): Try to retrieve account balances and transactions of last 90 days without TAN */ + /* Fourth dialog (if requested): Try to retrieve account balances and transactions of last 90 days without TAN */ - if (parameter.fetchBalanceAndTransactions) { - addAccountGetAccountBalancesAndTransactions(context, getAccountsResponse, callback) - } - else { - addAccountDone(context, getAccountsResponse, listOf(), callback) - } + if (parameter.fetchBalanceAndTransactions) { + return addAccountGetAccountBalancesAndTransactions(context, getAccountsResponse) + } + else { + return addAccountDone(context, getAccountsResponse, listOf()) } } - protected open fun addAccountGetAccountBalancesAndTransactions(context: JobContext, getAccountsResponse: BankResponse, - callback: (AddAccountResponse) -> Unit) { + protected open suspend fun addAccountGetAccountBalancesAndTransactions(context: JobContext, getAccountsResponse: BankResponse): AddAccountResponse { val bank = context.bank val retrievedTransactionsResponses = mutableListOf() val accountsSupportingRetrievingTransactions = bank.accounts.filter { it.supportsRetrievingBalance || it.supportsRetrievingAccountTransactions } - val countAccountsSupportingRetrievingTransactions = accountsSupportingRetrievingTransactions.size - var countRetrievedAccounts = 0 - if (countAccountsSupportingRetrievingTransactions == 0) { - addAccountDone(context, getAccountsResponse, retrievedTransactionsResponses, callback) - return // not necessary just to make it clearer that code below doesn't get called + if (accountsSupportingRetrievingTransactions.isEmpty()) { + return addAccountDone(context, getAccountsResponse, retrievedTransactionsResponses) } accountsSupportingRetrievingTransactions.forEach { account -> - tryGetAccountTransactionsOfLast90DaysWithoutTan(bank, account) { response -> - retrievedTransactionsResponses.add(response) - - countRetrievedAccounts++ - if (countRetrievedAccounts == countAccountsSupportingRetrievingTransactions) { - addAccountDone(context, getAccountsResponse, retrievedTransactionsResponses, callback) - } - } + retrievedTransactionsResponses.add(tryGetAccountTransactionsOfLast90DaysWithoutTan(bank, account)) } + + return addAccountDone(context, getAccountsResponse, retrievedTransactionsResponses) } protected open fun addAccountDone(context: JobContext, getAccountsResponse: BankResponse, - retrievedTransactionsResponses: List, - callback: (AddAccountResponse) -> Unit) { + retrievedTransactionsResponses: List): AddAccountResponse { - callback(AddAccountResponse(context, getAccountsResponse, retrievedTransactionsResponses)) + return AddAccountResponse(context, getAccountsResponse, retrievedTransactionsResponses) } @@ -147,9 +131,9 @@ open class FinTsClientDeprecated( * * Check if bank supports this. */ - open fun tryGetAccountTransactionsOfLast90DaysWithoutTan(bank: BankData, account: AccountData, callback: (GetAccountTransactionsResponse) -> Unit) { + open suspend fun tryGetAccountTransactionsOfLast90DaysWithoutTan(bank: BankData, account: AccountData): GetAccountTransactionsResponse { - getAccountTransactionsAsync(createGetAccountTransactionsOfLast90DaysParameter(bank, account), callback) + return getAccountTransactionsAsync(createGetAccountTransactionsOfLast90DaysParameter(bank, account)) } protected open fun createGetAccountTransactionsOfLast90DaysParameter(bank: BankData, account: AccountData): GetAccountTransactionsParameter { @@ -159,36 +143,35 @@ open class FinTsClientDeprecated( return GetAccountTransactionsParameter(bank, account, account.supportsRetrievingBalance, ninetyDaysAgo, abortIfTanIsRequired = true) } - open fun getAccountTransactionsAsync(parameter: GetAccountTransactionsParameter, callback: (GetAccountTransactionsResponse) -> Unit) { + open suspend fun getAccountTransactionsAsync(parameter: GetAccountTransactionsParameter): GetAccountTransactionsResponse { val context = JobContext(JobContextType.GetTransactions, this.callback, product, parameter.bank, parameter.account) - jobExecutor.getTransactionsAsync(context, parameter, callback) + return jobExecutor.getTransactionsAsync(context, parameter) } - open fun getTanMediaList(bank: BankData, tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle, - tanMediumClass: TanMediumKlasse = TanMediumKlasse.AlleMedien, callback: (GetTanMediaListResponse) -> Unit) { + open suspend fun getTanMediaList(bank: BankData, tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle, + tanMediumClass: TanMediumKlasse = TanMediumKlasse.AlleMedien): GetTanMediaListResponse { val context = JobContext(JobContextType.GetTanMedia, this.callback, product, bank) - jobExecutor.getTanMediaList(context, tanMediaKind, tanMediumClass, callback) + return jobExecutor.getTanMediaList(context, tanMediaKind, tanMediumClass) } - open fun changeTanMedium(newActiveTanMedium: TanGeneratorTanMedium, bank: BankData, callback: (FinTsClientResponse) -> Unit) { + open suspend fun changeTanMedium(newActiveTanMedium: TanGeneratorTanMedium, bank: BankData): FinTsClientResponse { val context = JobContext(JobContextType.ChangeTanMedium, this.callback, product, bank) - jobExecutor.changeTanMedium(context, newActiveTanMedium) { response -> - callback(FinTsClientResponse(context, response)) - } + val response = jobExecutor.changeTanMedium(context, newActiveTanMedium) + return FinTsClientResponse(context, response) } - open fun doBankTransferAsync(bankTransferData: BankTransferData, bank: BankData, account: AccountData, callback: (FinTsClientResponse) -> Unit) { + open suspend fun doBankTransferAsync(bankTransferData: BankTransferData, bank: BankData, account: AccountData): FinTsClientResponse { val context = JobContext(JobContextType.TransferMoney, this.callback, product, bank, account) - jobExecutor.doBankTransferAsync(context, bankTransferData, callback) + return jobExecutor.doBankTransferAsync(context, bankTransferData) } } \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClientForCustomer.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClientForCustomer.kt index f6febcd8..09e78ee2 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClientForCustomer.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClientForCustomer.kt @@ -39,22 +39,22 @@ open class FinTsClientForCustomer( } - open fun addAccountAsync(callback: (AddAccountResponse) -> Unit) { - addAccountAsync(bank.toAddAccountParameter(), callback) + open suspend fun addAccountAsync(): AddAccountResponse { + return addAccountAsync(bank.toAddAccountParameter()) } - open fun addAccountAsync(parameter: AddAccountParameter, callback: (AddAccountResponse) -> Unit) { - client.addAccountAsync(parameter, callback) + open suspend fun addAccountAsync(parameter: AddAccountParameter): AddAccountResponse { + return client.addAccountAsync(parameter) } - open fun getAccountTransactionsAsync(parameter: GetAccountTransactionsParameter, callback: (GetAccountTransactionsResponse) -> Unit) { - client.getAccountTransactionsAsync(parameter, callback) + open suspend fun getAccountTransactionsAsync(parameter: GetAccountTransactionsParameter): GetAccountTransactionsResponse { + return client.getAccountTransactionsAsync(parameter) } - open fun doBankTransferAsync(bankTransferData: BankTransferData, account: AccountData, callback: (FinTsClientResponse) -> Unit) { - client.doBankTransferAsync(bankTransferData, bank, account, callback) + open suspend fun doBankTransferAsync(bankTransferData: BankTransferData, account: AccountData): FinTsClientResponse { + return client.doBankTransferAsync(bankTransferData, bank, account) } } \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsJobExecutor.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsJobExecutor.kt index 3ea52c0f..63ac8e5f 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsJobExecutor.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsJobExecutor.kt @@ -41,21 +41,21 @@ open class FinTsJobExecutor( } - open fun getAnonymousBankInfo(context: JobContext, callback: (BankResponse) -> Unit) { + open suspend fun getAnonymousBankInfo(context: JobContext): BankResponse { context.startNewDialog() val message = messageBuilder.createAnonymousDialogInitMessage(context) - getAndHandleResponseForMessage(context, message) { response -> - if (response.successful) { - closeAnonymousDialog(context, response) - } + val response = getAndHandleResponseForMessage(context, message) - callback(response) + if (response.successful) { + closeAnonymousDialog(context, response) } + + return response } - protected open fun closeAnonymousDialog(context: JobContext, response: BankResponse) { + protected open suspend fun closeAnonymousDialog(context: JobContext, response: BankResponse) { // bank already closed dialog -> there's no need to send dialog end message if (shouldNotCloseDialog(context)) { @@ -75,8 +75,8 @@ open class FinTsJobExecutor( * * Be aware this method resets BPD, UPD and selected TAN method! */ - open fun retrieveBasicDataLikeUsersTanMethods(context: JobContext, preferredTanMethods: List? = null, preferredTanMedium: String? = null, - closeDialog: Boolean = false, callback: (BankResponse) -> Unit) { + open suspend fun retrieveBasicDataLikeUsersTanMethods(context: JobContext, preferredTanMethods: List? = null, preferredTanMedium: String? = null, + closeDialog: Boolean = false): BankResponse { val bank = context.bank // just to ensure settings are in its initial state and that bank sends us bank parameter (BPD), @@ -96,38 +96,38 @@ open class FinTsJobExecutor( val message = messageBuilder.createInitDialogMessage(context) - getAndHandleResponseForMessage(context, message) { response -> - closeDialog(context) + val response = getAndHandleResponseForMessage(context, message) - handleGetUsersTanMethodsResponse(context, response) { getTanMethodsResponse -> - if (bank.tanMethodsAvailableForUser.isEmpty()) { // could not retrieve supported tan methods for user - callback(getTanMethodsResponse) - } else { - getUsersTanMethod(context, preferredTanMethods) { - if (bank.isTanMethodSelected == false) { - callback(getTanMethodsResponse) - } else if (bank.tanMedia.isEmpty() && isJobSupported(bank, CustomerSegmentId.TanMediaList)) { // tan media not retrieved yet - getTanMediaList(context, TanMedienArtVersion.Alle, TanMediumKlasse.AlleMedien, preferredTanMedium) { - callback(getTanMethodsResponse) // TODO: judge if bank requires selecting TAN media and if though evaluate getTanMediaListResponse - } - } else { - callback(getTanMethodsResponse) - } - } - } + closeDialog(context) + + val getTanMethodsResponse = handleGetUsersTanMethodsResponse(context, response) + + if (bank.tanMethodsAvailableForUser.isEmpty()) { // could not retrieve supported tan methods for user + return getTanMethodsResponse + } else { + getUsersTanMethod(context, preferredTanMethods) + + if (bank.isTanMethodSelected == false) { + return getTanMethodsResponse + } else if (bank.tanMedia.isEmpty() && isJobSupported(bank, CustomerSegmentId.TanMediaList)) { // tan media not retrieved yet + getTanMediaList(context, TanMedienArtVersion.Alle, TanMediumKlasse.AlleMedien, preferredTanMedium) + + return getTanMethodsResponse // TODO: judge if bank requires selecting TAN media and if though evaluate getTanMediaListResponse + } else { + return getTanMethodsResponse } } } - protected open fun handleGetUsersTanMethodsResponse(context: JobContext, response: BankResponse, callback: (BankResponse) -> Unit) { + protected open suspend fun handleGetUsersTanMethodsResponse(context: JobContext, response: BankResponse): BankResponse { val getUsersTanMethodsResponse = GetUserTanMethodsResponse(response) // even though it is required by specification some banks don't support retrieving user's TAN method by setting TAN method to '999' if (bankDoesNotSupportRetrievingUsersTanMethods(getUsersTanMethodsResponse)) { - getBankDataForNewUserViaAnonymousDialog(context, callback) // TODO: should not be necessary anymore + return getBankDataForNewUserViaAnonymousDialog(context) // TODO: should not be necessary anymore } else { - callback(getUsersTanMethodsResponse) + return getUsersTanMethodsResponse } } @@ -138,73 +138,70 @@ open class FinTsJobExecutor( } // TODO: this is only a quick fix. Find a better and general solution - protected open fun getBankDataForNewUserViaAnonymousDialog(context: JobContext, callback: (BankResponse) -> Unit) { - getAnonymousBankInfo(context) { anonymousBankInfoResponse -> - val bank = context.bank + protected open suspend fun getBankDataForNewUserViaAnonymousDialog(context: JobContext): BankResponse { + val anonymousBankInfoResponse = getAnonymousBankInfo(context) - if (anonymousBankInfoResponse.successful == false) { - callback(anonymousBankInfoResponse) - } else if (bank.tanMethodsSupportedByBank.isEmpty()) { // should only be a theoretical error - callback(BankResponse(true, internalError = "Die TAN Verfahren der Bank konnten nicht ermittelt werden")) // TODO: translate + val bank = context.bank + + if (anonymousBankInfoResponse.successful == false) { + return anonymousBankInfoResponse + } else if (bank.tanMethodsSupportedByBank.isEmpty()) { // should only be a theoretical error + return BankResponse(true, internalError = "Die TAN Verfahren der Bank konnten nicht ermittelt werden") // TODO: translate + } else { + bank.tanMethodsAvailableForUser = bank.tanMethodsSupportedByBank + + val didSelectTanMethod = getUsersTanMethod(context) + + if (didSelectTanMethod) { + val initDialogResponse = initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context) + + closeDialog(context) + + return initDialogResponse } else { - bank.tanMethodsAvailableForUser = bank.tanMethodsSupportedByBank - - getUsersTanMethod(context) { didSelectTanMethod -> - if (didSelectTanMethod) { - initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context) { initDialogResponse -> - closeDialog(context) - - callback(initDialogResponse) - } - } else { - callback(createNoTanMethodSelectedResponse(bank)) - } - } + return createNoTanMethodSelectedResponse(bank) } } } - open fun getAccounts(context: JobContext, callback: (BankResponse) -> Unit) { - initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context) { response -> - closeDialog(context) + open suspend fun getAccounts(context: JobContext): BankResponse { + val response = initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context) - callback(response) - } + closeDialog(context) + + return response } - open fun getTransactionsAsync(context: JobContext, parameter: GetAccountTransactionsParameter, callback: (GetAccountTransactionsResponse) -> Unit) { + open suspend fun getTransactionsAsync(context: JobContext, parameter: GetAccountTransactionsParameter): GetAccountTransactionsResponse { val dialogContext = context.startNewDialog() // TODO: initDialogWithStrongCustomerAuthentication() also starts a new dialog in initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks() - initDialogWithStrongCustomerAuthentication(context) { initDialogResponse -> + val initDialogResponse = initDialogWithStrongCustomerAuthentication(context) - if (initDialogResponse.successful == false) { - callback(GetAccountTransactionsResponse(context, initDialogResponse, RetrievedAccountData.unsuccessful(parameter.account))) - } - else { - // we now retrieved the fresh account information from FinTS server, use that one - parameter.account = getUpdatedAccount(context, parameter.account) + if (initDialogResponse.successful == false) { + return GetAccountTransactionsResponse(context, initDialogResponse, RetrievedAccountData.unsuccessful(parameter.account)) + } + else { + // we now retrieved the fresh account information from FinTS server, use that one + parameter.account = getUpdatedAccount(context, parameter.account) - mayGetBalance(context, parameter) { balanceResponse -> - if (dialogContext.didBankCloseDialog) { - callback(GetAccountTransactionsResponse(context, balanceResponse ?: initDialogResponse, RetrievedAccountData.unsuccessful(parameter.account))) - } - else { - getTransactionsAfterInitAndGetBalance(context, parameter, balanceResponse, callback) - } - } + val balanceResponse = mayGetBalance(context, parameter) + if (dialogContext.didBankCloseDialog) { + return GetAccountTransactionsResponse(context, balanceResponse ?: initDialogResponse, RetrievedAccountData.unsuccessful(parameter.account)) + } else { + return getTransactionsAfterInitAndGetBalance(context, parameter, balanceResponse) } } } - private fun getUpdatedAccount(context: JobContext, account: AccountData): AccountData { + protected open fun getUpdatedAccount(context: JobContext, account: AccountData): AccountData { return context.bank.accounts.firstOrNull { it.accountIdentifier == account.accountIdentifier } ?: account } - protected open fun getTransactionsAfterInitAndGetBalance(context: JobContext, parameter: GetAccountTransactionsParameter, - balanceResponse: BankResponse?, callback: (GetAccountTransactionsResponse) -> Unit) { + protected open suspend fun getTransactionsAfterInitAndGetBalance(context: JobContext, parameter: GetAccountTransactionsParameter, + balanceResponse: BankResponse?): GetAccountTransactionsResponse { var balance: Money? = balanceResponse?.getFirstSegmentById(InstituteSegmentId.Balance)?.let { Money(it.balance, it.currency) } @@ -234,29 +231,28 @@ open class FinTsJobExecutor( } } - getAndHandleResponseForMessage(context, message) { response -> - closeDialog(context) + val response = getAndHandleResponseForMessage(context, message) - val successful = response.tanRequiredButWeWereToldToAbortIfSo - || (response.successful && (parameter.alsoRetrieveBalance == false || balance != null)) - val fromDate = parameter.fromDate - ?: parameter.account.countDaysForWhichTransactionsAreKept?.let { LocalDate.todayAtSystemDefaultTimeZone().minusDays(it) } - ?: bookedTransactions.minByOrNull { it.valueDate.millisSinceEpochAtEuropeBerlin }?.valueDate - val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, fromDate, parameter.toDate ?: LocalDate.todayAtEuropeBerlin(), response.internalError) + closeDialog(context) - callback(GetAccountTransactionsResponse(context, response, retrievedData, - if (parameter.maxCountEntries != null) parameter.isSettingMaxCountEntriesAllowedByBank else null)) - } + val successful = response.tanRequiredButWeWereToldToAbortIfSo + || (response.successful && (parameter.alsoRetrieveBalance == false || balance != null)) + val fromDate = parameter.fromDate + ?: parameter.account.countDaysForWhichTransactionsAreKept?.let { LocalDate.todayAtSystemDefaultTimeZone().minusDays(it) } + ?: bookedTransactions.minByOrNull { it.valueDate.millisSinceEpochAtEuropeBerlin }?.valueDate + val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, fromDate, parameter.toDate ?: LocalDate.todayAtEuropeBerlin(), response.internalError) + + return GetAccountTransactionsResponse(context, response, retrievedData, + if (parameter.maxCountEntries != null) parameter.isSettingMaxCountEntriesAllowedByBank else null) } - protected open fun mayGetBalance(context: JobContext, parameter: GetAccountTransactionsParameter, callback: (BankResponse?) -> Unit) { + protected open suspend fun mayGetBalance(context: JobContext, parameter: GetAccountTransactionsParameter): BankResponse? { if (parameter.alsoRetrieveBalance && parameter.account.supportsRetrievingBalance) { val message = messageBuilder.createGetBalanceMessage(context, parameter.account) - getAndHandleResponseForMessage(context, message, callback) - } - else { - callback(null) + return getAndHandleResponseForMessage(context, message) + } else { + return null } } @@ -272,38 +268,37 @@ open class FinTsJobExecutor( * * If you change customer system id during a dialog your messages get rejected by bank institute. */ - protected open fun synchronizeCustomerSystemId(context: JobContext, callback: (FinTsClientResponse) -> Unit) { + protected open suspend fun synchronizeCustomerSystemId(context: JobContext): FinTsClientResponse { context.startNewDialog() val message = messageBuilder.createSynchronizeCustomerSystemIdMessage(context) - getAndHandleResponseForMessage(context, message) { response -> - if (response.successful) { - closeDialog(context) - } + val response = getAndHandleResponseForMessage(context, message) - callback(FinTsClientResponse(context, response)) + if (response.successful) { + closeDialog(context) } + + return FinTsClientResponse(context, response) } - open fun getTanMediaList(context: JobContext, tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle, tanMediumClass: TanMediumKlasse = TanMediumKlasse.AlleMedien, - callback: (GetTanMediaListResponse) -> Unit) { - getTanMediaList(context, tanMediaKind, tanMediumClass, null, callback) + open suspend fun getTanMediaList(context: JobContext, tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle, tanMediumClass: TanMediumKlasse = TanMediumKlasse.AlleMedien): GetTanMediaListResponse { + return getTanMediaList(context, tanMediaKind, tanMediumClass, null) } - protected open fun getTanMediaList(context: JobContext, tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle, tanMediumClass: TanMediumKlasse = TanMediumKlasse.AlleMedien, - preferredTanMedium: String? = null, callback: (GetTanMediaListResponse) -> Unit) { + protected open suspend fun getTanMediaList(context: JobContext, tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle, tanMediumClass: TanMediumKlasse = TanMediumKlasse.AlleMedien, + preferredTanMedium: String? = null): GetTanMediaListResponse { - sendMessageInNewDialogAndHandleResponse(context, CustomerSegmentId.TanMediaList, false, { + val response = sendMessageInNewDialogAndHandleResponse(context, CustomerSegmentId.TanMediaList, false) { messageBuilder.createGetTanMediaListMessage(context, tanMediaKind, tanMediumClass) - }) { response -> - handleGetTanMediaListResponse(context, response, preferredTanMedium, callback) } + + return handleGetTanMediaListResponse(context, response, preferredTanMedium) } - protected open fun handleGetTanMediaListResponse(context: JobContext, response: BankResponse, preferredTanMedium: String? = null, callback: (GetTanMediaListResponse) -> Unit) { + protected open fun handleGetTanMediaListResponse(context: JobContext, response: BankResponse, preferredTanMedium: String? = null): GetTanMediaListResponse { val bank = context.bank // TAN media list (= TAN generator list) is only returned for users with chipTAN TAN methods @@ -318,83 +313,80 @@ open class FinTsJobExecutor( ?: bank.tanMedia.firstOrNull { it.mediumName != null } } - callback(GetTanMediaListResponse(context, response, tanMediaList)) + return GetTanMediaListResponse(context, response, tanMediaList) } - open fun changeTanMedium(context: JobContext, newActiveTanMedium: TanGeneratorTanMedium, callback: (BankResponse) -> Unit) { + open suspend fun changeTanMedium(context: JobContext, newActiveTanMedium: TanGeneratorTanMedium): BankResponse { val bank = context.bank if (bank.changeTanMediumParameters?.enteringAtcAndTanRequired == true) { - context.callback.enterTanGeneratorAtc(bank, newActiveTanMedium) { enteredAtc -> - if (enteredAtc.hasAtcBeenEntered == false) { - val message = "Bank requires to enter ATC and TAN in order to change TAN medium." // TODO: translate - callback(BankResponse(false, internalError = message)) - } - else { - sendChangeTanMediumMessage(context, newActiveTanMedium, enteredAtc, callback) - } + val enteredAtc = context.callback.enterTanGeneratorAtc(bank, newActiveTanMedium) + if (enteredAtc.hasAtcBeenEntered == false) { + val message = "Bank requires to enter ATC and TAN in order to change TAN medium." // TODO: translate + return BankResponse(false, internalError = message) + } else { + return sendChangeTanMediumMessage(context, newActiveTanMedium, enteredAtc) } } else { - sendChangeTanMediumMessage(context, newActiveTanMedium, null, callback) + return sendChangeTanMediumMessage(context, newActiveTanMedium, null) } } - protected open fun sendChangeTanMediumMessage(context: JobContext, newActiveTanMedium: TanGeneratorTanMedium, enteredAtc: EnterTanGeneratorAtcResult?, - callback: (BankResponse) -> Unit) { + protected open suspend fun sendChangeTanMediumMessage(context: JobContext, newActiveTanMedium: TanGeneratorTanMedium, enteredAtc: EnterTanGeneratorAtcResult?): BankResponse { - sendMessageInNewDialogAndHandleResponse(context, null, true, { + return sendMessageInNewDialogAndHandleResponse(context, null, true) { messageBuilder.createChangeTanMediumMessage(context, newActiveTanMedium, enteredAtc?.tan, enteredAtc?.atc) - }, callback) + } } - open fun doBankTransferAsync(context: JobContext, bankTransferData: BankTransferData, callback: (FinTsClientResponse) -> Unit) { + open suspend fun doBankTransferAsync(context: JobContext, bankTransferData: BankTransferData): FinTsClientResponse { - sendMessageInNewDialogAndHandleResponse(context, null, true, { + val response = sendMessageInNewDialogAndHandleResponse(context, null, true) { val updatedAccount = getUpdatedAccount(context, context.account!!) messageBuilder.createBankTransferMessage(context, bankTransferData, updatedAccount) - }) { response -> - callback(FinTsClientResponse(context, response)) } + + return FinTsClientResponse(context, response) } - protected open fun getAndHandleResponseForMessage(context: JobContext, message: MessageBuilderResult, callback: (BankResponse) -> Unit) { - requestExecutor.getAndHandleResponseForMessage(message, context, - { tanResponse, bankResponse, tanRequiredCallback -> + protected open suspend fun getAndHandleResponseForMessage(context: JobContext, message: MessageBuilderResult): BankResponse { + val response = requestExecutor.getAndHandleResponseForMessage(message, context, { tanResponse, bankResponse -> // if we receive a message that tells us a TAN is required below callback doesn't get called for that message -> update data here // for Hypovereinsbank it's absolutely necessary to update bank data (more specific: PinInfo / HIPINS) after first strong authentication dialog init response // as HIPINS differ in anonymous and in authenticated dialog. Anonymous dialog tells us for HKSAL and HKKAZ no TAN is needed updateBankAndCustomerDataIfResponseSuccessful(context, bankResponse) - handleEnteringTanRequired(context, tanResponse, bankResponse, tanRequiredCallback) - }) { response -> - // TODO: really update data only on complete successfully response? as it may contain useful information anyway // TODO: extract method for this code part - updateBankAndCustomerDataIfResponseSuccessful(context, response) - callback(response) - } + handleEnteringTanRequired(context, tanResponse, bankResponse) + }) + + // TODO: really update data only on complete successfully response? as it may contain useful information anyway // TODO: extract method for this code part + updateBankAndCustomerDataIfResponseSuccessful(context, response) + + return response } - protected open fun fireAndForgetMessage(context: JobContext, message: MessageBuilderResult) { + protected open suspend fun fireAndForgetMessage(context: JobContext, message: MessageBuilderResult) { requestExecutor.fireAndForgetMessage(context, message) } - protected open fun handleEnteringTanRequired(context: JobContext, tanResponse: TanResponse, response: BankResponse, callback: (BankResponse) -> Unit) { + protected open suspend fun handleEnteringTanRequired(context: JobContext, tanResponse: TanResponse, response: BankResponse): BankResponse { val bank = context.bank // TODO: copy required data to TanChallenge val tanChallenge = createTanChallenge(tanResponse, bank) val userDidCancelEnteringTan = ObjectReference(false) - context.callback.enterTan(bank, tanChallenge) { enteredTanResult -> - userDidCancelEnteringTan.value = true + val enteredTanResult = context.callback.enterTan(bank, tanChallenge) + userDidCancelEnteringTan.value = true - handleEnterTanResult(context, enteredTanResult, tanResponse, response, callback) - } + return handleEnterTanResult(context, enteredTanResult, tanResponse, response) - mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(context, tanChallenge, tanResponse, userDidCancelEnteringTan) + // TODO: +// mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(context, tanChallenge, tanResponse, userDidCancelEnteringTan) } protected open fun createTanChallenge(tanResponse: TanResponse, bank: BankData): TanChallenge { @@ -431,36 +423,36 @@ open class FinTsJobExecutor( log.info("automaticallyRetrieveIfUserEnteredDecoupledTan() called for $tanChallenge") } - protected open fun handleEnterTanResult(context: JobContext, enteredTanResult: EnterTanResult, tanResponse: TanResponse, - response: BankResponse, callback: (BankResponse) -> Unit) { + protected open suspend fun handleEnterTanResult(context: JobContext, enteredTanResult: EnterTanResult, tanResponse: TanResponse, + response: BankResponse): BankResponse { if (enteredTanResult.changeTanMethodTo != null) { - handleUserAsksToChangeTanMethodAndResendLastMessage(context, enteredTanResult.changeTanMethodTo, callback) + return handleUserAsksToChangeTanMethodAndResendLastMessage(context, enteredTanResult.changeTanMethodTo) } else if (enteredTanResult.changeTanMediumTo is TanGeneratorTanMedium) { - handleUserAsksToChangeTanMediumAndResendLastMessage(context, enteredTanResult.changeTanMediumTo, - enteredTanResult.changeTanMediumResultCallback, callback) + return handleUserAsksToChangeTanMediumAndResendLastMessage(context, enteredTanResult.changeTanMediumTo, + enteredTanResult.changeTanMediumResultCallback) } else if (enteredTanResult.enteredTan == null) { // i tried to send a HKTAN with cancelJob = true but then i saw there are no tan methods that support cancellation (at least not at my bank) // but it's not required anyway, tan times out after some time. Simply don't respond anything and close dialog response.tanRequiredButUserDidNotEnterOne = true - callback(response) + return response } else { - sendTanToBank(context, enteredTanResult.enteredTan, tanResponse, callback) + return sendTanToBank(context, enteredTanResult.enteredTan, tanResponse) } } - protected open fun sendTanToBank(context: JobContext, enteredTan: String, tanResponse: TanResponse, callback: (BankResponse) -> Unit) { + protected open suspend fun sendTanToBank(context: JobContext, enteredTan: String, tanResponse: TanResponse): BankResponse { val message = messageBuilder.createSendEnteredTanMessage(context, enteredTan, tanResponse) - getAndHandleResponseForMessage(context, message, callback) + return getAndHandleResponseForMessage(context, message) } - protected open fun handleUserAsksToChangeTanMethodAndResendLastMessage(context: JobContext, changeTanMethodTo: TanMethod, callback: (BankResponse) -> Unit) { + protected open suspend fun handleUserAsksToChangeTanMethodAndResendLastMessage(context: JobContext, changeTanMethodTo: TanMethod): BankResponse { context.bank.selectedTanMethod = changeTanMethodTo @@ -469,131 +461,125 @@ open class FinTsJobExecutor( lastCreatedMessage?.let { closeDialog(context) } - resendMessageInNewDialog(context, lastCreatedMessage, callback) + return resendMessageInNewDialog(context, lastCreatedMessage) } - protected open fun handleUserAsksToChangeTanMediumAndResendLastMessage(context: JobContext, changeTanMediumTo: TanGeneratorTanMedium, - changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)?, - callback: (BankResponse) -> Unit) { + protected open suspend fun handleUserAsksToChangeTanMediumAndResendLastMessage(context: JobContext, changeTanMediumTo: TanGeneratorTanMedium, + changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)?): BankResponse { val lastCreatedMessage = context.dialog.currentMessage lastCreatedMessage?.let { closeDialog(context) } - changeTanMedium(context, changeTanMediumTo) { changeTanMediumResponse -> - changeTanMediumResultCallback?.invoke(FinTsClientResponse(context, changeTanMediumResponse)) + val changeTanMediumResponse = changeTanMedium(context, changeTanMediumTo) - if (changeTanMediumResponse.successful == false || lastCreatedMessage == null) { - callback(changeTanMediumResponse) - } - else { - resendMessageInNewDialog(context, lastCreatedMessage, callback) - } + changeTanMediumResultCallback?.invoke(FinTsClientResponse(context, changeTanMediumResponse)) + + if (changeTanMediumResponse.successful == false || lastCreatedMessage == null) { + return changeTanMediumResponse + } + else { + return resendMessageInNewDialog(context, lastCreatedMessage) } } - protected open fun resendMessageInNewDialog(context: JobContext, lastCreatedMessage: MessageBuilderResult?, callback: (BankResponse) -> Unit) { + protected open suspend fun resendMessageInNewDialog(context: JobContext, lastCreatedMessage: MessageBuilderResult?): BankResponse { if (lastCreatedMessage != null) { // do not use previousDialogContext.currentMessage as this may is previous dialog's dialog close message context.startNewDialog(chunkedResponseHandler = context.dialog.chunkedResponseHandler) - initDialogWithStrongCustomerAuthentication(context) { initDialogResponse -> - if (initDialogResponse.successful == false) { - callback(initDialogResponse) - } - else { - val newMessage = messageBuilder.rebuildMessage(context, lastCreatedMessage) + val initDialogResponse = initDialogWithStrongCustomerAuthentication(context) - getAndHandleResponseForMessage(context, newMessage) { response -> - closeDialog(context) + if (initDialogResponse.successful == false) { + return initDialogResponse + } else { + val newMessage = messageBuilder.rebuildMessage(context, lastCreatedMessage) - callback(response) - } - } + val response = getAndHandleResponseForMessage(context, newMessage) + + closeDialog(context) + + return response } } else { val errorMessage = "There's no last action (like retrieve account transactions, transfer money, ...) to re-send with new TAN method. Probably an internal programming error." // TODO: translate - callback(BankResponse(false, internalError = errorMessage)) // should never come to this + return BankResponse(false, internalError = errorMessage) // should never come to this } } - protected open fun sendMessageInNewDialogAndHandleResponse(context: JobContext, segmentForNonStrongCustomerAuthenticationTwoStepTanProcess: CustomerSegmentId? = null, - closeDialog: Boolean = true, createMessage: () -> MessageBuilderResult, callback: (BankResponse) -> Unit) { + protected open suspend fun sendMessageInNewDialogAndHandleResponse(context: JobContext, segmentForNonStrongCustomerAuthenticationTwoStepTanProcess: CustomerSegmentId? = null, + closeDialog: Boolean = true, createMessage: () -> MessageBuilderResult): BankResponse { context.startNewDialog(closeDialog) - if (segmentForNonStrongCustomerAuthenticationTwoStepTanProcess == null) { - initDialogWithStrongCustomerAuthentication(context) { initDialogResponse -> - sendMessageAndHandleResponseAfterDialogInitialization(context, initDialogResponse, createMessage, callback) - } - } - else { - initDialogMessageWithoutStrongCustomerAuthenticationAfterSuccessfulChecks(context, segmentForNonStrongCustomerAuthenticationTwoStepTanProcess) { initDialogResponse -> - sendMessageAndHandleResponseAfterDialogInitialization(context, initDialogResponse, createMessage, callback) - } + val initDialogResponse = if (segmentForNonStrongCustomerAuthenticationTwoStepTanProcess == null) { + initDialogWithStrongCustomerAuthentication(context) + } else { + initDialogMessageWithoutStrongCustomerAuthenticationAfterSuccessfulChecks(context, segmentForNonStrongCustomerAuthenticationTwoStepTanProcess) } + + return sendMessageAndHandleResponseAfterDialogInitialization(context, initDialogResponse, createMessage) } - private fun sendMessageAndHandleResponseAfterDialogInitialization(context: JobContext, initDialogResponse: BankResponse, - createMessage: () -> MessageBuilderResult, callback: (BankResponse) -> Unit) { + protected open suspend fun sendMessageAndHandleResponseAfterDialogInitialization(context: JobContext, initDialogResponse: BankResponse, + createMessage: () -> MessageBuilderResult): BankResponse { if (initDialogResponse.successful == false) { - callback(initDialogResponse) + return initDialogResponse } else { val message = createMessage() - getAndHandleResponseForMessage(context, message) { response -> - closeDialog(context) + val response = getAndHandleResponseForMessage(context, message) - callback(response) - } + closeDialog(context) + + return response } } - protected open fun initDialogWithStrongCustomerAuthentication(context: JobContext, callback: (BankResponse) -> Unit) { + protected open suspend fun initDialogWithStrongCustomerAuthentication(context: JobContext): BankResponse { // we first need to retrieve supported tan methods and jobs before we can do anything - ensureBasicBankDataRetrieved(context) { retrieveBasicBankDataResponse -> - if (retrieveBasicBankDataResponse.successful == false) { - callback(retrieveBasicBankDataResponse) + val retrieveBasicBankDataResponse = ensureBasicBankDataRetrieved(context) + + if (retrieveBasicBankDataResponse.successful == false) { + return retrieveBasicBankDataResponse + } + else { + // as in the next step we have to supply user's tan method, ensure user selected his or her + val tanMethodSelectedResponse = ensureTanMethodIsSelected(context) + + if (tanMethodSelectedResponse.successful == false) { + return tanMethodSelectedResponse } else { - // as in the next step we have to supply user's tan method, ensure user selected his or her - ensureTanMethodIsSelected(context) { tanMethodSelectedResponse -> - if (tanMethodSelectedResponse.successful == false) { - callback(tanMethodSelectedResponse) - } - else { - initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context, callback) - } - } + return initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context) } } } - protected open fun initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context: JobContext, callback: (BankResponse) -> Unit) { + protected open suspend fun initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context: JobContext): BankResponse { context.startNewDialog(false) // don't know if it's ok for all invocations of this method to set closeDialog to false (was actually only set in getAccounts()) val message = messageBuilder.createInitDialogMessage(context) - getAndHandleResponseForMessage(context, message, callback) + return getAndHandleResponseForMessage(context, message) } - protected open fun initDialogMessageWithoutStrongCustomerAuthenticationAfterSuccessfulChecks(context: JobContext, segmentIdForTwoStepTanProcess: CustomerSegmentId?, - callback: (BankResponse) -> Unit) { + protected open suspend fun initDialogMessageWithoutStrongCustomerAuthenticationAfterSuccessfulChecks(context: JobContext, segmentIdForTwoStepTanProcess: CustomerSegmentId?): BankResponse { val message = messageBuilder.createInitDialogMessageWithoutStrongCustomerAuthentication(context, segmentIdForTwoStepTanProcess) - getAndHandleResponseForMessage(context, message, callback) + return getAndHandleResponseForMessage(context, message) } - protected open fun closeDialog(context: JobContext) { + protected open suspend fun closeDialog(context: JobContext) { // bank already closed dialog -> there's no need to send dialog end message if (shouldNotCloseDialog(context)) { @@ -605,82 +591,80 @@ open class FinTsJobExecutor( fireAndForgetMessage(context, dialogEndRequestBody) } - private fun shouldNotCloseDialog(context: JobContext): Boolean { + protected open fun shouldNotCloseDialog(context: JobContext): Boolean { return context.dialog.closeDialog == false || context.dialog.didBankCloseDialog } - protected open fun ensureBasicBankDataRetrieved(context: JobContext, callback: (BankResponse) -> Unit) { + protected open suspend fun ensureBasicBankDataRetrieved(context: JobContext): BankResponse { val bank = context.bank if (bank.tanMethodsSupportedByBank.isEmpty() || bank.supportedJobs.isEmpty()) { - retrieveBasicDataLikeUsersTanMethods(context) { getBankInfoResponse -> - if (getBankInfoResponse.successful == false) { - callback(getBankInfoResponse) - } else if (bank.tanMethodsSupportedByBank.isEmpty() || bank.supportedJobs.isEmpty()) { - callback(BankResponse(false, internalError = - "Could not retrieve basic bank data like supported tan methods or supported jobs")) // TODO: translate // TODO: add as messageToShowToUser - } else { - callback(BankResponse(true)) - } + val getBankInfoResponse = retrieveBasicDataLikeUsersTanMethods(context) + + if (getBankInfoResponse.successful == false) { + return getBankInfoResponse + } else if (bank.tanMethodsSupportedByBank.isEmpty() || bank.supportedJobs.isEmpty()) { + return BankResponse(false, internalError = + "Could not retrieve basic bank data like supported tan methods or supported jobs") // TODO: translate // TODO: add as messageToShowToUser + } else { + return BankResponse(true) } } else { - callback(BankResponse(true)) + return BankResponse(true) } } - protected open fun ensureTanMethodIsSelected(context: JobContext, callback: (BankResponse) -> Unit) { + protected open suspend fun ensureTanMethodIsSelected(context: JobContext): BankResponse { val bank = context.bank if (bank.isTanMethodSelected == false) { if (bank.tanMethodsAvailableForUser.isEmpty()) { - retrieveBasicDataLikeUsersTanMethods(context) { retrieveBasicDataResponse -> - callback(retrieveBasicDataResponse) - } + return retrieveBasicDataLikeUsersTanMethods(context) } else { - getUsersTanMethod(context) { - callback(createNoTanMethodSelectedResponse(bank)) - } + getUsersTanMethod(context) + + return createNoTanMethodSelectedResponse(bank) } } else { - callback(createNoTanMethodSelectedResponse(bank)) + return createNoTanMethodSelectedResponse(bank) } } - private fun createNoTanMethodSelectedResponse(bank: BankData): BankResponse { + protected open fun createNoTanMethodSelectedResponse(bank: BankData): BankResponse { val noTanMethodSelected = !!!bank.isTanMethodSelected val errorMessage = if (noTanMethodSelected) "User did not select a TAN method" else null // TODO: translate return BankResponse(true, noTanMethodSelected = noTanMethodSelected, internalError = errorMessage) } - open fun getUsersTanMethod(context: JobContext, preferredTanMethods: List? = null, done: (Boolean) -> Unit) { + open suspend fun getUsersTanMethod(context: JobContext, preferredTanMethods: List? = null): Boolean { val bank = context.bank if (bank.tanMethodsAvailableForUser.size == 1) { // user has only one TAN method -> set it and we're done bank.selectedTanMethod = bank.tanMethodsAvailableForUser.first() - done(true) + return true } else { tanMethodSelector.findPreferredTanMethod(bank.tanMethodsAvailableForUser, preferredTanMethods)?.let { bank.selectedTanMethod = it - done(true) - return + return true } // we know user's supported tan methods, now ask user which one to select val suggestedTanMethod = tanMethodSelector.getSuggestedTanMethod(bank.tanMethodsAvailableForUser) - context.callback.askUserForTanMethod(bank.tanMethodsAvailableForUser, suggestedTanMethod) { selectedTanMethod -> - if (selectedTanMethod != null) { - bank.selectedTanMethod = selectedTanMethod - done(true) - } - else { - done(false) - } + + val selectedTanMethod = context.callback.askUserForTanMethod(bank.tanMethodsAvailableForUser, suggestedTanMethod) + + if (selectedTanMethod != null) { + bank.selectedTanMethod = selectedTanMethod + return true + } + else { + return false } } } diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/RequestExecutor.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/RequestExecutor.kt index dd00b2da..c328aefb 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/RequestExecutor.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/RequestExecutor.kt @@ -21,79 +21,78 @@ open class RequestExecutor( ) { companion object { - private val log = LoggerFactory.getLogger(FinTsJobExecutor::class) + private val log = LoggerFactory.getLogger(RequestExecutor::class) } - open fun getAndHandleResponseForMessage(message: MessageBuilderResult, context: JobContext, - tanRequiredCallback: (TanResponse, BankResponse, callback: (BankResponse) -> Unit) -> Unit, callback: (BankResponse) -> Unit) { + open suspend fun getAndHandleResponseForMessage(message: MessageBuilderResult, context: JobContext, tanRequiredCallback: suspend (TanResponse, BankResponse) -> BankResponse): BankResponse { if (message.createdMessage == null) { log.error("Could not create FinTS message to be sent to bank. isJobAllowed ${message.isJobAllowed}, isJobVersionSupported = ${message.isJobVersionSupported}," + "allowedVersions = ${message.allowedVersions}, supportedVersions = ${message.supportedVersions}.") - callback(BankResponse(false, messageThatCouldNotBeCreated = message, internalError = "Could not create FinTS message to be sent to bank")) // TODO: translate + return BankResponse(false, messageThatCouldNotBeCreated = message, internalError = "Could not create FinTS message to be sent to bank") // TODO: translate } else { - getAndHandleResponseForMessage(context, message.createdMessage) { response -> - handleMayRequiresTan(context, response, tanRequiredCallback) { handledResponse -> - // if there's a Aufsetzpunkt (continuationId) set, then response is not complete yet, there's more information to fetch by sending this Aufsetzpunkt - handledResponse.aufsetzpunkt?.let { continuationId -> - if (handledResponse.followUpResponse == null) { // for re-sent messages followUpResponse is already set and dialog already closed -> would be overwritten with an error response that dialog is closed - if (message.isSendEnteredTanMessage() == false) { // for sending TAN no follow up message can be created -> filter out, otherwise chunkedResponseHandler would get called twice for same response - context.dialog.chunkedResponseHandler?.invoke(handledResponse) - } + val response = getAndHandleResponseForMessage(context, message.createdMessage) - getFollowUpMessageForContinuationId(context, handledResponse, continuationId, message, tanRequiredCallback) { followUpResponse -> - handledResponse.followUpResponse = followUpResponse - handledResponse.hasFollowUpMessageButCouldNotReceiveIt = handledResponse.followUpResponse == null + val handledResponse = handleMayRequiresTan(context, response, tanRequiredCallback) - callback(handledResponse) - } - } - else { - callback(handledResponse) - } + // if there's a Aufsetzpunkt (continuationId) set, then response is not complete yet, there's more information to fetch by sending this Aufsetzpunkt + handledResponse.aufsetzpunkt?.let { continuationId -> + if (handledResponse.followUpResponse == null) { // for re-sent messages followUpResponse is already set and dialog already closed -> would be overwritten with an error response that dialog is closed + if (message.isSendEnteredTanMessage() == false) { // for sending TAN no follow up message can be created -> filter out, otherwise chunkedResponseHandler would get called twice for same response + context.dialog.chunkedResponseHandler?.invoke(handledResponse) } - ?: run { - // e.g. response = enter TAN response, but handledResponse is then response after entering TAN, e.g. account transactions - // -> chunkedResponseHandler would get called for same handledResponse multiple times - if (response == handledResponse) { - context.dialog.chunkedResponseHandler?.invoke(handledResponse) - } - callback(handledResponse) - } + val followUpResponse = getFollowUpMessageForContinuationId(context, handledResponse, continuationId, message, tanRequiredCallback) + + handledResponse.followUpResponse = followUpResponse + handledResponse.hasFollowUpMessageButCouldNotReceiveIt = handledResponse.followUpResponse == null + + return handledResponse + } + else { + return handledResponse } } + ?: run { + // e.g. response = enter TAN response, but handledResponse is then response after entering TAN, e.g. account transactions + // -> chunkedResponseHandler would get called for same handledResponse multiple times + if (response == handledResponse) { + context.dialog.chunkedResponseHandler?.invoke(handledResponse) + } + + return handledResponse + } } } - protected open fun getAndHandleResponseForMessage(context: JobContext, requestBody: String, callback: (BankResponse) -> Unit) { + protected open suspend fun getAndHandleResponseForMessage(context: JobContext, requestBody: String): BankResponse { addMessageLog(context, MessageLogEntryType.Sent, requestBody) - getResponseForMessage(requestBody, context.bank.finTs3ServerAddress) { webResponse -> - val response = handleResponse(context, webResponse) + val webResponse = getResponseForMessage(requestBody, context.bank.finTs3ServerAddress) - val dialog = context.dialog - dialog.response = response + val response = handleResponse(context, webResponse) - response.messageHeader?.let { header -> dialog.dialogId = header.dialogId } - dialog.didBankCloseDialog = response.didBankCloseDialog + val dialog = context.dialog + dialog.response = response - callback(response) - } + response.messageHeader?.let { header -> dialog.dialogId = header.dialogId } + dialog.didBankCloseDialog = response.didBankCloseDialog + + return response } - protected open fun getResponseForMessage(requestBody: String, finTs3ServerAddress: String, callback: (WebClientResponse) -> Unit) { + protected open suspend fun getResponseForMessage(requestBody: String, finTs3ServerAddress: String): WebClientResponse { val encodedRequestBody = base64Service.encode(requestBody) - webClient.post(finTs3ServerAddress, encodedRequestBody, "application/octet-stream", IWebClient.DefaultUserAgent, callback) + return webClient.post(finTs3ServerAddress, encodedRequestBody, "application/octet-stream", IWebClient.DefaultUserAgent) } - open fun fireAndForgetMessage(context: JobContext, message: MessageBuilderResult) { + open suspend fun fireAndForgetMessage(context: JobContext, message: MessageBuilderResult) { message.createdMessage?.let { requestBody -> addMessageLog(context, MessageLogEntryType.Sent, requestBody) - getResponseForMessage(requestBody, context.bank.finTs3ServerAddress) { } + getResponseForMessage(requestBody, context.bank.finTs3ServerAddress) // if really needed add received response to message log here } @@ -129,34 +128,30 @@ open class RequestExecutor( } - protected open fun getFollowUpMessageForContinuationId(context: JobContext, response: BankResponse, continuationId: String, message: MessageBuilderResult, - tanRequiredCallback: (TanResponse, BankResponse, callback: (BankResponse) -> Unit) -> Unit, - callback: (BankResponse?) -> Unit) { + protected open suspend fun getFollowUpMessageForContinuationId(context: JobContext, response: BankResponse, continuationId: String, message: MessageBuilderResult, + tanRequiredCallback: suspend (TanResponse, BankResponse) -> BankResponse): BankResponse? { messageBuilder.rebuildMessageWithContinuationId(context, message, continuationId)?.let { followUpMessage -> - getAndHandleResponseForMessage(followUpMessage, context, tanRequiredCallback, callback) + return getAndHandleResponseForMessage(followUpMessage, context, tanRequiredCallback) } - ?: run { callback(null) } + + return null } - protected open fun handleMayRequiresTan(context: JobContext, response: BankResponse, - tanRequiredCallback: (TanResponse, BankResponse, callback: (BankResponse) -> Unit) -> Unit, - callback: (BankResponse) -> Unit) { // TODO: use response from DialogContext + protected open suspend fun handleMayRequiresTan(context: JobContext, response: BankResponse, + tanRequiredCallback: suspend (TanResponse, BankResponse) -> BankResponse): BankResponse { // TODO: use response from DialogContext if (response.isStrongAuthenticationRequired) { if (context.dialog.abortIfTanIsRequired) { response.tanRequiredButWeWereToldToAbortIfSo = true - callback(response) - return + return response } else if (response.tanResponse != null) { response.tanResponse?.let { tanResponse -> - tanRequiredCallback(tanResponse, response, callback) + return tanRequiredCallback(tanResponse, response) } - - return } } @@ -167,7 +162,7 @@ open class RequestExecutor( // TODO: also check '9931 Sperrung des Kontos nach %1 Fehlversuchen' -> if %1 == 3 synchronize TAN generator // as it's quite unrealistic that user entered TAN wrong three times, in most cases TAN generator is not synchronized - callback(response) + return response } diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/callback/FinTsClientCallback.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/callback/FinTsClientCallback.kt index 715de801..d4e611e9 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/callback/FinTsClientCallback.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/callback/FinTsClientCallback.kt @@ -16,15 +16,15 @@ interface FinTsClientCallback { * If you do not support an enter tan dialog or if your enter tan dialog supports selecting a TAN method, it's * best returning [suggestedTanMethod] and to not show an extra select TAN method dialog. */ - fun askUserForTanMethod(supportedTanMethods: List, suggestedTanMethod: TanMethod?, callback: (TanMethod?) -> Unit) + suspend fun askUserForTanMethod(supportedTanMethods: List, suggestedTanMethod: TanMethod?): TanMethod? - fun enterTan(bank: BankData, tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) + suspend fun enterTan(bank: BankData, tanChallenge: TanChallenge): EnterTanResult /** * This method gets called for chipTan TAN generators when the bank asks the customer to synchronize her/his TAN generator. * * If you do not support entering TAN generator ATC, return [EnterTanGeneratorAtcResult.userDidNotEnterAtc] */ - fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium, callback: (EnterTanGeneratorAtcResult) -> Unit) + suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult } \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/callback/NoOpFinTsClientCallback.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/callback/NoOpFinTsClientCallback.kt index eb1533d7..7b0d8203 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/callback/NoOpFinTsClientCallback.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/callback/NoOpFinTsClientCallback.kt @@ -6,18 +6,16 @@ import net.dankito.banking.fints.model.* open class NoOpFinTsClientCallback : FinTsClientCallback { - override fun askUserForTanMethod(supportedTanMethods: List, - suggestedTanMethod: TanMethod?, callback: (TanMethod?) -> Unit) { - - callback(suggestedTanMethod) + override suspend fun askUserForTanMethod(supportedTanMethods: List, suggestedTanMethod: TanMethod?): TanMethod? { + return suggestedTanMethod } - override fun enterTan(bank: BankData, tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) { - callback(EnterTanResult.userDidNotEnterTan()) + override suspend fun enterTan(bank: BankData, tanChallenge: TanChallenge): EnterTanResult { + return EnterTanResult.userDidNotEnterTan() } - override fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium, callback: (EnterTanGeneratorAtcResult) -> Unit) { - callback(EnterTanGeneratorAtcResult.userDidNotEnterAtc()) + override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult { + return EnterTanGeneratorAtcResult.userDidNotEnterAtc() } } \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/callback/SimpleFinTsClientCallback.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/callback/SimpleFinTsClientCallback.kt index db1b3d74..62441cb5 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/callback/SimpleFinTsClientCallback.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/callback/SimpleFinTsClientCallback.kt @@ -5,9 +5,9 @@ import net.dankito.banking.fints.model.* open class SimpleFinTsClientCallback( - protected val enterTan: ((bank: BankData, tanChallenge: TanChallenge) -> EnterTanResult)? = null, - protected val enterTanGeneratorAtc: ((bank: BankData, tanMedium: TanGeneratorTanMedium) -> EnterTanGeneratorAtcResult)? = null, - protected val askUserForTanMethod: ((supportedTanMethods: List, suggestedTanMethod: TanMethod?) -> TanMethod?)? = null + protected open val enterTan: ((bank: BankData, tanChallenge: TanChallenge) -> EnterTanResult)? = null, + protected open val enterTanGeneratorAtc: ((bank: BankData, tanMedium: TanGeneratorTanMedium) -> EnterTanGeneratorAtcResult)? = null, + protected open val askUserForTanMethod: ((supportedTanMethods: List, suggestedTanMethod: TanMethod?) -> TanMethod?)? = null ) : FinTsClientCallback { constructor() : this(null) // Swift does not support default parameter values -> create constructor overloads @@ -15,18 +15,17 @@ open class SimpleFinTsClientCallback( constructor(enterTan: ((bank: BankData, tanChallenge: TanChallenge) -> EnterTanResult)?) : this(enterTan, null) - override fun askUserForTanMethod(supportedTanMethods: List, - suggestedTanMethod: TanMethod?, callback: (TanMethod?) -> Unit) { + override suspend fun askUserForTanMethod(supportedTanMethods: List, suggestedTanMethod: TanMethod?): TanMethod? { - callback(askUserForTanMethod?.invoke(supportedTanMethods, suggestedTanMethod) ?: suggestedTanMethod) + return askUserForTanMethod?.invoke(supportedTanMethods, suggestedTanMethod) ?: suggestedTanMethod } - override fun enterTan(bank: BankData, tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) { - callback(enterTan?.invoke(bank, tanChallenge) ?: EnterTanResult.userDidNotEnterTan()) + override suspend fun enterTan(bank: BankData, tanChallenge: TanChallenge): EnterTanResult { + return enterTan?.invoke(bank, tanChallenge) ?: EnterTanResult.userDidNotEnterTan() } - override fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium, callback: (EnterTanGeneratorAtcResult) -> Unit) { - callback(enterTanGeneratorAtc?.invoke(bank, tanMedium) ?: EnterTanGeneratorAtcResult.userDidNotEnterAtc()) + override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult { + return enterTanGeneratorAtc?.invoke(bank, tanMedium) ?: EnterTanGeneratorAtcResult.userDidNotEnterAtc() } } \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/webclient/IWebClient.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/webclient/IWebClient.kt index 98b65c64..da312bf9 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/webclient/IWebClient.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/webclient/IWebClient.kt @@ -20,6 +20,10 @@ interface IWebClient { } - fun post(url: String, body: String, contentType: String = "application/octet-stream", userAgent: String = DefaultUserAgent, callback: (WebClientResponse) -> Unit) + suspend fun post(url: String, body: String): WebClientResponse { // some platforms don't support default parameters + return post(url, body) + } + + suspend fun post(url: String, body: String, contentType: String = "application/octet-stream", userAgent: String = DefaultUserAgent): WebClientResponse } \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/webclient/KtorWebClient.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/webclient/KtorWebClient.kt index 6a2ddf70..fb5faa90 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/webclient/KtorWebClient.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/webclient/KtorWebClient.kt @@ -35,13 +35,11 @@ open class KtorWebClient : IWebClient { } - override fun post(url: String, body: String, contentType: String, userAgent: String, callback: (WebClientResponse) -> Unit) { - GlobalScope.async { - postInCoroutine(url, body, contentType, userAgent, callback) - } + override suspend fun post(url: String, body: String, contentType: String, userAgent: String): WebClientResponse { + return postInCoroutine(url, body, contentType, userAgent) } - protected open suspend fun postInCoroutine(url: String, body: String, contentType: String, userAgent: String, callback: (WebClientResponse) -> Unit) { + protected open suspend fun postInCoroutine(url: String, body: String, contentType: String, userAgent: String): WebClientResponse { try { val clientResponse = client.post(url) { contentType(ContentType.Application.OctetStream) @@ -50,11 +48,11 @@ open class KtorWebClient : IWebClient { val responseBody = clientResponse.bodyAsText() - callback(WebClientResponse(clientResponse.status.value == 200, clientResponse.status.value, body = responseBody)) + return WebClientResponse(clientResponse.status.value == 200, clientResponse.status.value, body = responseBody) } catch (e: Exception) { log.error(e) { "Could not send request to url '$url'" } - callback(WebClientResponse(false, error = e)) + return WebClientResponse(false, error = e) } } diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/webclient/ProxyingWebClient.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/webclient/ProxyingWebClient.kt index f7260a76..6c602df2 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/webclient/ProxyingWebClient.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/webclient/ProxyingWebClient.kt @@ -10,8 +10,8 @@ class ProxyingWebClient(proxyUrl: String, private val delegate: IWebClient) : IW private val proxyUrl = if (proxyUrl.endsWith("/")) proxyUrl else proxyUrl + "/" - override fun post(url: String, body: String, contentType: String, userAgent: String, callback: (WebClientResponse) -> Unit) { - delegate.post(proxyUrl + url, body, contentType, userAgent, callback) + override suspend fun post(url: String, body: String, contentType: String, userAgent: String): WebClientResponse { + return delegate.post(proxyUrl + url, body, contentType, userAgent) } } \ No newline at end of file diff --git a/fints4k/src/nativeMain/kotlin/Application.kt b/fints4k/src/nativeMain/kotlin/Application.kt index 7b52e633..14ac98f7 100644 --- a/fints4k/src/nativeMain/kotlin/Application.kt +++ b/fints4k/src/nativeMain/kotlin/Application.kt @@ -1,13 +1,10 @@ import kotlinx.coroutines.runBlocking import kotlinx.datetime.LocalDate import net.dankito.banking.fints.FinTsClientDeprecated -import net.dankito.banking.fints.FinTsJobExecutor -import net.dankito.banking.fints.RequestExecutor import net.dankito.banking.fints.callback.SimpleFinTsClientCallback import net.dankito.banking.fints.model.AddAccountParameter import net.dankito.banking.fints.model.RetrievedAccountData import net.dankito.banking.fints.response.client.AddAccountResponse -import net.dankito.banking.fints.webclient.BlockingKtorWebClient import net.dankito.utils.multiplatform.extensions.* import platform.posix.exit @@ -25,13 +22,13 @@ class Application { fun retrieveAccountData(bankCode: String, customerId: String, pin: String, finTs3ServerAddress: String) { runBlocking { - val client = FinTsClientDeprecated(SimpleFinTsClientCallback(), FinTsJobExecutor(RequestExecutor(webClient = BlockingKtorWebClient()))) + val client = FinTsClientDeprecated(SimpleFinTsClientCallback()) - client.addAccountAsync(AddAccountParameter(bankCode, customerId, pin, finTs3ServerAddress)) { response -> - println("Retrieved response from ${response.bank.bankName} for ${response.bank.customerName}") + val response = client.addAccountAsync(AddAccountParameter(bankCode, customerId, pin, finTs3ServerAddress)) - displayRetrievedAccountData(response) - } + println("Retrieved response from ${response.bank.bankName} for ${response.bank.customerName}") + + displayRetrievedAccountData(response) } } diff --git a/fints4k/src/nativeMain/kotlin/net.dankito.banking.fints.webclient/BlockingKtorWebClient.kt b/fints4k/src/nativeMain/kotlin/net.dankito.banking.fints.webclient/BlockingKtorWebClient.kt deleted file mode 100644 index 479b9f48..00000000 --- a/fints4k/src/nativeMain/kotlin/net.dankito.banking.fints.webclient/BlockingKtorWebClient.kt +++ /dev/null @@ -1,14 +0,0 @@ -package net.dankito.banking.fints.webclient -import kotlinx.coroutines.runBlocking - - -open class BlockingKtorWebClient : KtorWebClient() { - - - override fun post(url: String, body: String, contentType: String, userAgent: String, callback: (WebClientResponse) -> Unit) { - runBlocking { - postInCoroutine(url, body, contentType, userAgent, callback) - } - } - -} \ No newline at end of file