diff options
author | Sebastian <sebasjm@gmail.com> | 2023-10-19 02:44:28 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-10-19 02:56:14 -0300 |
commit | f89e27a4e39412c4863fe26f821988a65ecec1b9 (patch) | |
tree | 2bc1a5c3971a38fa32268593ccce69f0281858e6 | |
parent | c6968c3c21d70bd76ce50f232650d183fa8cfa6e (diff) | |
download | wallet-core-f89e27a4e39412c4863fe26f821988a65ecec1b9.tar.xz |
update api
-rw-r--r-- | packages/taler-util/src/http-client/authentication.ts | 83 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-core.ts | 284 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-integration.ts | 2 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-revenue.ts | 5 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-wire.ts | 8 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/types.ts | 56 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/utils.ts | 110 | ||||
-rw-r--r-- | packages/taler-util/src/http-common.ts | 31 |
8 files changed, 314 insertions, 265 deletions
diff --git a/packages/taler-util/src/http-client/authentication.ts b/packages/taler-util/src/http-client/authentication.ts new file mode 100644 index 000000000..0c59c9308 --- /dev/null +++ b/packages/taler-util/src/http-client/authentication.ts @@ -0,0 +1,83 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { HttpStatusCode } from "../http-status-codes.js"; +import { HttpRequestLibrary, createPlatformHttpLib, makeBasicAuthHeader } from "../http.js"; +import { LibtoolVersion } from "../libtool-version.js"; +import { AccessToken, TalerAuthentication, UserAndPassword, UserAndToken, codecForTokenSuccessResponse } from "./types.js"; +import { makeBearerTokenAuthHeader, opEmptySuccess, opKnownFailure, opSuccess, opUnknownFailure } from "./utils.js"; + +export class TalerAuthenticationHttpClient { + public readonly PROTOCOL_VERSION = "0:0:0"; + + httpLib: HttpRequestLibrary; + + constructor( + readonly baseUrl: string, + readonly username: string, + httpClient?: HttpRequestLibrary, + ) { + this.httpLib = httpClient ?? createPlatformHttpLib(); + } + + isCompatible(version: string): boolean { + const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version) + return compare?.compatible ?? false + } + + /** + * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-token + * + * @returns + */ + async createAccessToken( + password: string, + body: TalerAuthentication.TokenRequest, + ) { + const url = new URL(`token`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + headers: { + Authorization: makeBasicAuthHeader(this.username, password), + }, + body + }); + switch (resp.status) { + case HttpStatusCode.Ok: return opSuccess(resp, codecForTokenSuccessResponse()) + //FIXME: missing in docs + case HttpStatusCode.Unauthorized: return opKnownFailure("wrong-credentials", resp) + case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp) + default: return opUnknownFailure(resp, await resp.text()) + } + } + + async deleteAccessToken(token: AccessToken) { + const url = new URL(`token`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "DELETE", + headers: { + Authorization: makeBearerTokenAuthHeader(token), + } + }); + switch (resp.status) { + case HttpStatusCode.Ok: return opEmptySuccess() + //FIXME: missing in docs + case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp) + default: return opUnknownFailure(resp, await resp.text()) + } + } + +}
\ No newline at end of file diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts index 7b4bb53d4..593daa2c3 100644 --- a/packages/taler-util/src/http-client/bank-core.ts +++ b/packages/taler-util/src/http-client/bank-core.ts @@ -18,63 +18,50 @@ import { AmountJson, Amounts, HttpStatusCode, - Logger + LibtoolVersion } from "@gnu-taler/taler-util"; import { HttpRequestLibrary, - createPlatformHttpLib, - expectSuccessResponseOrThrow, - readSuccessResponseJsonOrThrow + createPlatformHttpLib } from "@gnu-taler/taler-util/http"; import { TalerBankIntegrationHttpClient } from "./bank-integration.js"; import { TalerRevenueHttpClient } from "./bank-revenue.js"; import { TalerWireGatewayHttpClient } from "./bank-wire.js"; -import { AccessToken, TalerAuthentication, TalerCorebankApi, codecForAccountData, codecForBankAccountCreateWithdrawalResponse, codecForBankAccountGetWithdrawalResponse, codecForBankAccountTransactionInfo, codecForBankAccountTransactionsResponse, codecForCashoutConversionResponse, codecForCashoutPending, codecForCashoutStatusResponse, codecForCashouts, codecForConversionRatesResponse, codecForCoreBankConfig, codecForGlobalCashouts, codecForListBankAccountsResponse, codecForMonitorResponse, codecForPublicAccountsResponse, codecForTokenSuccessResponse } from "./types.js"; -import { PaginationParams, UserAndPassword, UserAndToken, addPaginationParams, httpEmptySuccess, httpSuccess, knownFailure, makeBasicAuthHeader, makeBearerTokenAuthHeader, unknownFailure } from "./utils.js"; +import { AccessToken, OperationOk, PaginationParams, TalerCorebankApi, UserAndToken, codecForAccountData, codecForBankAccountCreateWithdrawalResponse, codecForBankAccountGetWithdrawalResponse, codecForBankAccountTransactionInfo, codecForBankAccountTransactionsResponse, codecForCashoutConversionResponse, codecForCashoutPending, codecForCashoutStatusResponse, codecForCashouts, codecForConversionRatesResponse, codecForCoreBankConfig, codecForGlobalCashouts, codecForListBankAccountsResponse, codecForMonitorResponse, codecForPublicAccountsResponse } from "./types.js"; +import { addPaginationParams, opFixedSuccess, opEmptySuccess, opSuccess, opKnownFailure, makeBearerTokenAuthHeader, opUnknownFailure } from "./utils.js"; +import { TalerAuthenticationHttpClient } from "./authentication.js"; -const logger = new Logger("http-client/core-bank.ts"); +type props = keyof TalerCoreBankHttpClient + +export type TalerCoreBankResultByMethod<p extends props> = TalerCoreBankHttpClient[p] extends (...args: any[]) => infer Ret ? + Ret extends Promise<infer Result> ? + Result : + never : //api always use Promises + never; //error cases just for functions + +export type TalerCoreBankErrorsByMethod<p extends props> = Exclude<TalerCoreBankResultByMethod<p>, OperationOk<any>> + +/** + * Protocol version spoken with the bank. + * + * Uses libtool's current:revision:age versioning. + */ export class TalerCoreBankHttpClient { + public readonly PROTOCOL_VERSION = "0:0:0"; + httpLib: HttpRequestLibrary; constructor( - private baseUrl: string, + readonly baseUrl: string, httpClient?: HttpRequestLibrary, ) { this.httpLib = httpClient ?? createPlatformHttpLib(); } - /** - * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-token - * - * @returns - */ - async createAccessToken( - auth: UserAndPassword, - body: TalerAuthentication.TokenRequest, - ): Promise<TalerAuthentication.TokenSuccessResponse> { - const url = new URL(`accounts/${auth.username}/token`, this.baseUrl); - const resp = await this.httpLib.fetch(url.href, { - method: "POST", - headers: { - Authorization: makeBasicAuthHeader(auth.username, auth.password), - }, - body - }); - return readSuccessResponseJsonOrThrow(resp, codecForTokenSuccessResponse()); - } - - async deleteAccessToken( - auth: UserAndToken, - ): Promise<void> { - const url = new URL(`accounts/${auth.username}/token`, this.baseUrl); - const resp = await this.httpLib.fetch(url.href, { - method: "DELETE", - headers: { - Authorization: makeBearerTokenAuthHeader(auth.token), - } - }); - return expectSuccessResponseOrThrow(resp); + isCompatible(version: string): boolean { + const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version) + return compare?.compatible ?? false } /** @@ -88,8 +75,8 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { //FIXME: missing in docs - case HttpStatusCode.Ok: return httpSuccess(resp, codecForCoreBankConfig()) - default: return unknownFailure(url, resp) + case HttpStatusCode.Ok: return opSuccess(resp, codecForCoreBankConfig()) + default: return opUnknownFailure(resp, await resp.text()) } } @@ -111,17 +98,19 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - case HttpStatusCode.NoContent: return httpEmptySuccess() - case HttpStatusCode.BadRequest: return knownFailure("invalid-input", resp); + //FIXME: NOT IN THE DOOOCS + case HttpStatusCode.Created: return opEmptySuccess() + case HttpStatusCode.NoContent: return opEmptySuccess() + case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", resp); case HttpStatusCode.Forbidden: { if (body.username === "bank" || body.username === "admin") { - return knownFailure("unable-to-create", resp); + return opKnownFailure("unable-to-create", resp); } else { - return knownFailure("unauthorized", resp); + return opKnownFailure("unauthorized", resp); } } - case HttpStatusCode.Conflict: return knownFailure("already-exist", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Conflict: return opKnownFailure("already-exist", resp); + default: return opUnknownFailure(resp, await resp.text()) } } /** @@ -137,17 +126,17 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - case HttpStatusCode.NoContent: return httpEmptySuccess() - case HttpStatusCode.NotFound: return knownFailure("not-found", resp); + case HttpStatusCode.NoContent: return opEmptySuccess() + case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); case HttpStatusCode.Forbidden: { if (auth.username === "bank" || auth.username === "admin") { - return knownFailure("unable-to-delete", resp); + return opKnownFailure("unable-to-delete", resp); } else { - return knownFailure("unauthorized", resp); + return opKnownFailure("unauthorized", resp); } } - case HttpStatusCode.PreconditionFailed: return knownFailure("balance-not-zero", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.PreconditionFailed: return opKnownFailure("balance-not-zero", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -165,10 +154,10 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - case HttpStatusCode.NoContent: return httpEmptySuccess() - case HttpStatusCode.NotFound: return knownFailure("not-found", resp); - case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.NoContent: return opEmptySuccess() + case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); + case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -186,12 +175,12 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - case HttpStatusCode.NoContent: return httpEmptySuccess() + case HttpStatusCode.NoContent: return opEmptySuccess() //FIXME: missing in docs - case HttpStatusCode.NotFound: return knownFailure("not-found", resp); + case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); //FIXME: missing in docs - case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -199,8 +188,10 @@ export class TalerCoreBankHttpClient { * https://docs.taler.net/core/get-$BANK_API_BASE_URL-public-accounts * */ - async getPublicAccounts() { + async getPublicAccounts(pagination?: PaginationParams) { const url = new URL(`public-accounts`, this.baseUrl); + //FIXME: missing pagination in docs + addPaginationParams(url, pagination) const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { @@ -208,10 +199,10 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { //FIXME: missing in docs - case HttpStatusCode.Ok: return httpSuccess(resp, codecForPublicAccountsResponse()) + case HttpStatusCode.Ok: return opSuccess(resp, codecForPublicAccountsResponse()) //FIXME: missing in docs - case HttpStatusCode.NoContent: return httpEmptySuccess() - default: return unknownFailure(url, resp) + case HttpStatusCode.NoContent: return opFixedSuccess({ public_accounts: [] }) + default: return opUnknownFailure(resp, await resp.text()) } } @@ -219,8 +210,9 @@ export class TalerCoreBankHttpClient { * https://docs.taler.net/core/api-corebank.html#get--accounts * */ - async getAccounts(auth: AccessToken) { + async getAccounts(auth: AccessToken, pagination?: PaginationParams) { const url = new URL(`accounts`, this.baseUrl); + addPaginationParams(url, pagination) const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { @@ -228,10 +220,10 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - case HttpStatusCode.Ok: return httpSuccess(resp, codecForListBankAccountsResponse()) - case HttpStatusCode.NoContent: return httpEmptySuccess() - case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Ok: return opSuccess(resp, codecForListBankAccountsResponse()) + case HttpStatusCode.NoContent: return opFixedSuccess({ accounts: [] }) + case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -248,12 +240,12 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - case HttpStatusCode.Ok: return httpSuccess(resp, codecForAccountData()) + case HttpStatusCode.Ok: return opSuccess(resp, codecForAccountData()) //FIXME: missing in docs - case HttpStatusCode.NotFound: return knownFailure("not-found", resp); + case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); //FIXME: missing in docs - case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -275,14 +267,14 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - case HttpStatusCode.Ok: return httpSuccess(resp, codecForBankAccountTransactionsResponse()) + case HttpStatusCode.Ok: return opSuccess(resp, codecForBankAccountTransactionsResponse()) //FIXME: missing in docs - case HttpStatusCode.NoContent: return httpEmptySuccess() + case HttpStatusCode.NoContent: return opFixedSuccess({ transactions: [] }) //FIXME: missing in docs - case HttpStatusCode.NotFound: return knownFailure("not-found", resp); + case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); //FIXME: missing in docs - case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -299,12 +291,12 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - case HttpStatusCode.Ok: return httpSuccess(resp, codecForBankAccountTransactionInfo()) + case HttpStatusCode.Ok: return opSuccess(resp, codecForBankAccountTransactionInfo()) //FIXME: missing in docs - case HttpStatusCode.NotFound: return knownFailure("not-found", resp); + case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); //FIXME: missing in docs - case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -323,12 +315,12 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { //FIXME: fix docs... it should be NoContent - case HttpStatusCode.Ok: return httpEmptySuccess() - case HttpStatusCode.NoContent: return httpEmptySuccess() - case HttpStatusCode.BadRequest: return knownFailure("invalid-input", resp); + case HttpStatusCode.Ok: return opEmptySuccess() + case HttpStatusCode.NoContent: return opEmptySuccess() + case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", resp); //FIXME: missing in docs - case HttpStatusCode.Forbidden: return knownFailure("unauthorized", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -351,9 +343,9 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { //FIXME: missing in docs - case HttpStatusCode.Ok: return httpSuccess(resp, codecForBankAccountCreateWithdrawalResponse()) - case HttpStatusCode.Forbidden: return knownFailure("insufficient-funds", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Ok: return opSuccess(resp, codecForBankAccountCreateWithdrawalResponse()) + case HttpStatusCode.Forbidden: return opKnownFailure("insufficient-funds", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -368,8 +360,9 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { //FIXME: missing in docs - case HttpStatusCode.Ok: return httpSuccess(resp, codecForBankAccountGetWithdrawalResponse()) - default: return unknownFailure(url, resp) + case HttpStatusCode.Ok: return opSuccess(resp, codecForBankAccountGetWithdrawalResponse()) + case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp) + default: return opUnknownFailure(resp, await resp.text()) } } @@ -384,10 +377,10 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { //FIXME: fix docs... it should be NoContent - case HttpStatusCode.Ok: return httpEmptySuccess() - case HttpStatusCode.NoContent: return httpEmptySuccess() - case HttpStatusCode.Conflict: return knownFailure("previously-confirmed", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Ok: return opEmptySuccess() + case HttpStatusCode.NoContent: return opEmptySuccess() + case HttpStatusCode.Conflict: return opKnownFailure("previously-confirmed", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -402,11 +395,11 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { //FIXME: fix docs... it should be NoContent - case HttpStatusCode.Ok: return httpEmptySuccess() - case HttpStatusCode.NoContent: return httpEmptySuccess() - case HttpStatusCode.Conflict: return knownFailure("previously-aborted", resp); - case HttpStatusCode.UnprocessableEntity: return knownFailure("no-exchange-or-reserve-selected", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Ok: return opEmptySuccess() + case HttpStatusCode.NoContent: return opEmptySuccess() + case HttpStatusCode.Conflict: return opKnownFailure("previously-aborted", resp); + case HttpStatusCode.UnprocessableEntity: return opKnownFailure("no-exchange-or-reserve-selected", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -428,11 +421,17 @@ export class TalerCoreBankHttpClient { body, }); switch (resp.status) { - case HttpStatusCode.Accepted: return httpSuccess(resp, codecForCashoutPending()) + case HttpStatusCode.Accepted: return opSuccess(resp, codecForCashoutPending()) //FIXME: it should be precondition-failed - case HttpStatusCode.Conflict: return knownFailure("invalid-state", resp); - case HttpStatusCode.ServiceUnavailable: return knownFailure("tan-not-supported", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Conflict: return opKnownFailure("no-contact-info", resp); + //FIXME: missing in the docs + case HttpStatusCode.Forbidden: return opKnownFailure("no-allowed", resp); + //FIXME: missing in the docs + case HttpStatusCode.PreconditionFailed: return opKnownFailure("no-enough-balance", resp); + //FIXME: missing in the docs + case HttpStatusCode.BadRequest: return opKnownFailure("incorrect-exchange-rate", resp); + case HttpStatusCode.ServiceUnavailable: return opKnownFailure("tan-not-supported", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -449,10 +448,10 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - case HttpStatusCode.NoContent: return httpEmptySuccess() - case HttpStatusCode.NotFound: return knownFailure("not-found", resp); - case HttpStatusCode.Conflict: return knownFailure("already-confirmed", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.NoContent: return opEmptySuccess() + case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); + case HttpStatusCode.Conflict: return opKnownFailure("already-confirmed", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -470,11 +469,11 @@ export class TalerCoreBankHttpClient { body, }); switch (resp.status) { - case HttpStatusCode.NoContent: return httpEmptySuccess() - case HttpStatusCode.Forbidden: return knownFailure("wrong-tan-or-credential", resp); - case HttpStatusCode.NotFound: return knownFailure("not-found", resp); - case HttpStatusCode.Conflict: return knownFailure("cashout-address-changed", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.NoContent: return opEmptySuccess() + case HttpStatusCode.Forbidden: return opKnownFailure("wrong-tan-or-credential", resp); + case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); + case HttpStatusCode.Conflict: return opKnownFailure("cashout-address-changed", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -494,10 +493,10 @@ export class TalerCoreBankHttpClient { method: "GET", }); switch (resp.status) { - case HttpStatusCode.Ok: return httpSuccess(resp, codecForCashoutConversionResponse()) - case HttpStatusCode.BadRequest: return knownFailure("wrong-calculation", resp); - case HttpStatusCode.NotFound: return knownFailure("not-supported", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Ok: return opSuccess(resp, codecForCashoutConversionResponse()) + case HttpStatusCode.BadRequest: return opKnownFailure("wrong-calculation", resp); + case HttpStatusCode.NotFound: return opKnownFailure("not-supported", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -514,9 +513,9 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - case HttpStatusCode.Ok: return httpSuccess(resp, codecForCashouts()) - case HttpStatusCode.NoContent: return httpEmptySuccess(); - default: return unknownFailure(url, resp) + case HttpStatusCode.Ok: return opSuccess(resp, codecForCashouts()) + case HttpStatusCode.NoContent: return opFixedSuccess({ cashouts: [] }); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -533,9 +532,9 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - case HttpStatusCode.Ok: return httpSuccess(resp, codecForGlobalCashouts()) - case HttpStatusCode.NoContent: return httpEmptySuccess(); - default: return unknownFailure(url, resp) + case HttpStatusCode.Ok: return opSuccess(resp, codecForGlobalCashouts()) + case HttpStatusCode.NoContent: return opFixedSuccess({ cashouts: [] }); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -553,9 +552,9 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { //FIXME: missing in docs - case HttpStatusCode.Ok: return httpSuccess(resp, codecForCashoutStatusResponse()) - case HttpStatusCode.NotFound: return knownFailure("already-aborted", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Ok: return opSuccess(resp, codecForCashoutStatusResponse()) + case HttpStatusCode.NotFound: return opKnownFailure("already-aborted", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -573,9 +572,9 @@ export class TalerCoreBankHttpClient { method: "GET", }); switch (resp.status) { - case HttpStatusCode.Ok: return httpSuccess(resp, codecForConversionRatesResponse()) - case HttpStatusCode.NotFound: return knownFailure("not-supported", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Ok: return opSuccess(resp, codecForConversionRatesResponse()) + case HttpStatusCode.NotFound: return opKnownFailure("not-supported", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -595,10 +594,10 @@ export class TalerCoreBankHttpClient { method: "GET", }); switch (resp.status) { - case HttpStatusCode.Ok: return httpSuccess(resp, codecForMonitorResponse()) - case HttpStatusCode.NotFound: return knownFailure("not-supported", resp); - case HttpStatusCode.BadRequest: return knownFailure("invalid-input", resp); - default: return unknownFailure(url, resp) + case HttpStatusCode.Ok: return opSuccess(resp, codecForMonitorResponse()) + case HttpStatusCode.NotFound: return opKnownFailure("not-supported", resp); + case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", resp); + default: return opUnknownFailure(resp, await resp.text()) } } @@ -611,7 +610,7 @@ export class TalerCoreBankHttpClient { * */ getIntegrationAPI(): TalerBankIntegrationHttpClient { - const url = new URL(`taler-integration`, this.baseUrl); + const url = new URL(`taler-integration/`, this.baseUrl); return new TalerBankIntegrationHttpClient(url.href, this.httpLib) } @@ -620,7 +619,7 @@ export class TalerCoreBankHttpClient { * */ getWireGatewayAPI(username: string): TalerWireGatewayHttpClient { - const url = new URL(`accounts/${username}/taler-wire-gateway`, this.baseUrl); + const url = new URL(`accounts/${username}/taler-wire-gateway/`, this.baseUrl); return new TalerWireGatewayHttpClient(url.href, username, this.httpLib) } @@ -629,9 +628,16 @@ export class TalerCoreBankHttpClient { * */ getRevenueAPI(username: string): TalerRevenueHttpClient { - const url = new URL(`accounts/${username}/taler-revenue`, this.baseUrl); + const url = new URL(`accounts/${username}/taler-revenue/`, this.baseUrl); return new TalerRevenueHttpClient(url.href, username, this.httpLib,) } + /** + * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-token + * + */ + getAuthenticationAPI(username: string): TalerAuthenticationHttpClient { + const url = new URL(`accounts/${username}/`, this.baseUrl); + return new TalerAuthenticationHttpClient(url.href, username, this.httpLib,) + } } - diff --git a/packages/taler-util/src/http-client/bank-integration.ts b/packages/taler-util/src/http-client/bank-integration.ts index cd6462417..521b6e34c 100644 --- a/packages/taler-util/src/http-client/bank-integration.ts +++ b/packages/taler-util/src/http-client/bank-integration.ts @@ -10,7 +10,7 @@ export class TalerBankIntegrationHttpClient { httpLib: HttpRequestLibrary; constructor( - private baseUrl: string, + readonly baseUrl: string, httpClient?: HttpRequestLibrary, ) { this.httpLib = httpClient ?? createPlatformHttpLib(); diff --git a/packages/taler-util/src/http-client/bank-revenue.ts b/packages/taler-util/src/http-client/bank-revenue.ts index 99ff71457..d594da574 100644 --- a/packages/taler-util/src/http-client/bank-revenue.ts +++ b/packages/taler-util/src/http-client/bank-revenue.ts @@ -1,14 +1,13 @@ import { HttpRequestLibrary, makeBasicAuthHeader, readSuccessResponseJsonOrThrow } from "../http-common.js"; import { createPlatformHttpLib } from "../http.js"; import { TalerRevenueApi, codecForMerchantIncomingHistory } from "./types.js"; -import { UserAndPassword } from "./utils.js"; export class TalerRevenueHttpClient { httpLib: HttpRequestLibrary; constructor( - private baseUrl: string, - private username: string, + readonly baseUrl: string, + readonly username: string, httpClient?: HttpRequestLibrary, ) { this.httpLib = httpClient ?? createPlatformHttpLib(); diff --git a/packages/taler-util/src/http-client/bank-wire.ts b/packages/taler-util/src/http-client/bank-wire.ts index 9f2b859ed..0a032cc61 100644 --- a/packages/taler-util/src/http-client/bank-wire.ts +++ b/packages/taler-util/src/http-client/bank-wire.ts @@ -1,7 +1,7 @@ import { HttpRequestLibrary, makeBasicAuthHeader, readSuccessResponseJsonOrThrow } from "../http-common.js"; import { createPlatformHttpLib } from "../http.js"; -import { TalerWireGatewayApi, codecForAddIncomingResponse, codecForIncomingHistory, codecForOutgoingHistory, codecForTransferResponse } from "./types.js"; -import { PaginationParams, UserAndPassword, addPaginationParams } from "./utils.js"; +import { PaginationParams, TalerWireGatewayApi, codecForAddIncomingResponse, codecForIncomingHistory, codecForOutgoingHistory, codecForTransferResponse } from "./types.js"; +import { addPaginationParams } from "./utils.js"; /** * The API is used by the exchange to trigger transactions and query @@ -14,8 +14,8 @@ export class TalerWireGatewayHttpClient { httpLib: HttpRequestLibrary; constructor( - private baseUrl: string, - private username: string, + readonly baseUrl: string, + readonly username: string, httpClient?: HttpRequestLibrary, ) { this.httpLib = httpClient ?? createPlatformHttpLib(); diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index 66ac39f59..5a76981df 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -1,6 +1,58 @@ import { codecForAmountString } from "../amounts.js"; import { Codec, buildCodecForObject, buildCodecForUnion, codecForBoolean, codecForConstString, codecForEither, codecForList, codecForMap, codecForNumber, codecForString, codecOptional } from "../codec.js"; import { codecForTimestamp } from "../time.js"; +import { TalerErrorDetail } from "../wallet-types.js"; + + +export type UserAndPassword = { + username: string, + password: string, +} + +export type UserAndToken = { + username: string, + token: AccessToken, +} + +export type PaginationParams = { + /** + * row identifier as the starting point of the query + */ + offset?: string, + /** + * max number of element in the result response + * always greater than 0 + */ + limit?: number, + /** + * milliseconds the server should wait for at least one result to be shown + */ + timoutMs?: number, + /** + * order + */ + order: "asc" | "dec" +} + +export type OperationResult<Body, ErrorEnum> = + | OperationOk<Body> + | OperationFail<ErrorEnum>; + +export interface OperationOk<T> { + type: "ok", + body: T; +} +export function isOperationOk<T, E>(c: OperationResult<T, E>): c is OperationOk<T> { + return c.type === "ok" +} +export function isOperationFail<T, E>(c: OperationResult<T, E>): c is OperationFail<E> { + return c.type === "fail" +} +export interface OperationFail<T> { + type: "fail", + case: T, + detail: TalerErrorDetail, +} /// @@ -502,14 +554,14 @@ export const codecForAddIncomingResponse = type EmailAddress = string; type PhoneNumber = string; -type DecimalNumber = string; +type DecimalNumber = number; const codecForURL = codecForString const codecForLibtoolVersion = codecForString const codecForCurrencyName = codecForString const codecForPaytoURI = codecForString const codecForTalerWithdrawalURI = codecForString -const codecForDecimalNumber = codecForString +const codecForDecimalNumber = codecForNumber enum TanChannel { SMS = "sms", diff --git a/packages/taler-util/src/http-client/utils.ts b/packages/taler-util/src/http-client/utils.ts index f4af5ae03..3be5d6e89 100644 --- a/packages/taler-util/src/http-client/utils.ts +++ b/packages/taler-util/src/http-client/utils.ts @@ -1,10 +1,8 @@ import { base64FromArrayBuffer } from "../base64.js"; -import { HttpResponse, readErrorResponse, readSuccessResponseJsonOrThrow, readTalerErrorResponse } from "../http-common.js"; -import { HttpStatusCode } from "../http-status-codes.js"; -import { Codec } from "../index.js"; +import { HttpResponse, readSuccessResponseJsonOrThrow, readTalerErrorResponse } from "../http-common.js"; +import { Codec, TalerError, TalerErrorCode } from "../index.js"; import { stringToBytes } from "../taler-crypto.js"; -import { TalerErrorDetail } from "../wallet-types.js"; -import { AccessToken } from "./types.js"; +import { AccessToken, OperationFail, OperationOk, PaginationParams } from "./types.js"; /** * Helper function to generate the "Authorization" HTTP header. @@ -41,91 +39,33 @@ export function addPaginationParams(url: URL, pagination?: PaginationParams) { url.searchParams.set("delta", String(order * limit)) } -export type UserAndPassword = { - username: string, - password: string, -} - -export type UserAndToken = { - username: string, - token: AccessToken, -} -export type PaginationParams = { - /** - * row identifier as the starting point of the query - */ - offset?: string, - /** - * max number of element in the result response - * always greater than 0 - */ - limit?: number, - /** - * milliseconds the server should wait for at least one result to be shown - */ - timoutMs?: number, - /** - * order - */ - order: "asc" | "dec" -} - -export type HttpResult<Body, ErrorEnum> = - | HttpOk<Body> - | HttpKnownFail<ErrorEnum> - | HttpUnkownFail; - -/** - * 200 < status < 204 - */ -export interface HttpOk<T> { - type: "ok", - body: T; +////// +// Operation Helper Constructors +////// +export async function opSuccess<T>(resp: HttpResponse, codec: Codec<T>): Promise<OperationOk<T>> { + const body = await readSuccessResponseJsonOrThrow(resp, codec) + return { type: "ok" as const, body } } - -/** - * 400 < status < 409 - * and error documented - */ -export interface HttpKnownFail<T> { - type: "fail", - case: T, - detail: TalerErrorDetail, +export function opFixedSuccess<T>(body: T): OperationOk<T> { + return { type: "ok" as const, body } } - -/** - * 400 < status < 599 - * and error NOT documented - * undefined behavior on this responses - */ -export interface HttpUnkownFail { - type: "fail-unknown", - url: URL; - status: HttpStatusCode; - - // read from the body if exist - detail?: TalerErrorDetail; - body?: string; +export function opEmptySuccess(): OperationOk<void> { + return { type: "ok" as const, body: void 0 } } - -export async function knownFailure<T extends string>(s: T, resp: HttpResponse): Promise<HttpKnownFail<T>> { +export async function opKnownFailure<T extends string>(s: T, resp: HttpResponse): Promise<OperationFail<T>> { const detail = await readTalerErrorResponse(resp) return { type: "fail", case: s, detail } } -export async function httpSuccess<T>(resp: HttpResponse, codec: Codec<T>): Promise<HttpOk<T>> { - const body = await readSuccessResponseJsonOrThrow(resp, codec) - return { type: "ok" as const, body } -} -export function httpEmptySuccess(): HttpOk<void> { - return { type: "ok" as const, body: void 0 } -} -export async function unknownFailure(url: URL, resp: HttpResponse): Promise<HttpUnkownFail> { - if (resp.status >= 400 && resp.status < 500) { - const detail = await readTalerErrorResponse(resp) - return { type: "fail-unknown", url, status: resp.status, detail } - } else { - const { detail, body } = await readErrorResponse(resp) - return { type: "fail-unknown", url, status: resp.status, detail, body } - } +export function opUnknownFailure(resp: HttpResponse, text: string): never { + throw TalerError.fromDetail( + TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, + { + requestUrl: resp.requestUrl, + requestMethod: resp.requestMethod, + httpStatusCode: resp.status, + errorResponse: text, + }, + `Unexpected HTTP status ${resp.status} in response`, + ); } diff --git a/packages/taler-util/src/http-common.ts b/packages/taler-util/src/http-common.ts index 817f2367f..da2fbb9da 100644 --- a/packages/taler-util/src/http-common.ts +++ b/packages/taler-util/src/http-common.ts @@ -180,37 +180,6 @@ export async function readTalerErrorResponse( return errJson; } -export async function readErrorResponse( - httpResponse: HttpResponse, -): Promise<{ detail: TalerErrorDetail | undefined, body: string }> { - let errString: string; - try { - errString = await httpResponse.text(); - } catch (e: any) { - throw TalerError.fromDetail( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - { - requestUrl: httpResponse.requestUrl, - requestMethod: httpResponse.requestMethod, - httpStatusCode: httpResponse.status, - validationError: e.toString(), - }, - "Couldn't parse JSON format from error response", - ); - } - let errJson; - try { - errJson = JSON.parse(errString) - } catch (e) { - errJson = undefined - } - - const talerErrorCode = errJson && errJson.code; - if (typeof talerErrorCode === "number") { - return { detail: errJson, body: errString } - } - return { detail: undefined, body: errString }; -} export async function readUnexpectedResponseDetails( httpResponse: HttpResponse, ): Promise<TalerErrorDetail> { |