diff options
Diffstat (limited to 'packages/merchant-backoffice-ui/src/hooks/backend.ts')
-rw-r--r-- | packages/merchant-backoffice-ui/src/hooks/backend.ts | 139 |
1 files changed, 110 insertions, 29 deletions
diff --git a/packages/merchant-backoffice-ui/src/hooks/backend.ts b/packages/merchant-backoffice-ui/src/hooks/backend.ts index ecd34df6d..fe4155788 100644 --- a/packages/merchant-backoffice-ui/src/hooks/backend.ts +++ b/packages/merchant-backoffice-ui/src/hooks/backend.ts @@ -19,19 +19,21 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { useSWRConfig } from "swr"; -import { MerchantBackend } from "../declaration.js"; -import { useBackendContext } from "../context/backend.js"; -import { useCallback, useEffect, useState } from "preact/hooks"; -import { useInstanceContext } from "../context/instance.js"; +import { AbsoluteTime, HttpStatusCode } from "@gnu-taler/taler-util"; import { ErrorType, + HttpError, HttpResponse, HttpResponseOk, RequestError, RequestOptions, + useApiContext, } from "@gnu-taler/web-util/browser"; -import { useApiContext } from "@gnu-taler/web-util/browser"; +import { useCallback, useEffect, useState } from "preact/hooks"; +import { useSWRConfig } from "swr"; +import { useBackendContext } from "../context/backend.js"; +import { useInstanceContext } from "../context/instance.js"; +import { AccessToken, LoginToken, MerchantBackend, Timestamp } from "../declaration.js"; export function useMatchMutate(): ( @@ -85,6 +87,9 @@ export function useBackendInstancesTestForAdmin(): HttpResponse< return result; } +const CHECK_CONFIG_INTERVAL_OK = 5 * 60 * 1000; +const CHECK_CONFIG_INTERVAL_FAIL = 2 * 1000; + export function useBackendConfig(): HttpResponse< MerchantBackend.VersionResponse, RequestError<MerchantBackend.ErrorDetail> @@ -92,18 +97,33 @@ export function useBackendConfig(): HttpResponse< const { request } = useBackendBaseRequest(); type Type = MerchantBackend.VersionResponse; - - const [result, setResult] = useState< - HttpResponse<Type, RequestError<MerchantBackend.ErrorDetail>> - >({ loading: true }); + type State = { data: HttpResponse<Type, RequestError<MerchantBackend.ErrorDetail>>, timer: number } + const [result, setResult] = useState<State>({ data: { loading: true }, timer: 0 }); useEffect(() => { - request<Type>(`/config`) - .then((data) => setResult(data)) - .catch((error) => setResult(error)); + if (result.timer) { + clearTimeout(result.timer) + } + function tryConfig(): void { + request<Type>(`/config`) + .then((data) => { + const timer: any = setTimeout(() => { + tryConfig() + }, CHECK_CONFIG_INTERVAL_OK) + setResult({ data, timer }) + }) + .catch((error) => { + const timer: any = setTimeout(() => { + tryConfig() + }, CHECK_CONFIG_INTERVAL_FAIL) + const data = error.cause + setResult({ data, timer }) + }); + } + tryConfig() }, [request]); - return result; + return result.data; } interface useBackendInstanceRequestType { @@ -149,32 +169,86 @@ interface useBackendBaseRequestType { } type YesOrNo = "yes" | "no"; +type LoginResult = { + valid: true; + token: string; + expiration: Timestamp; +} | { + valid: false; + cause: HttpError<{}>; +} export function useCredentialsChecker() { const { request } = useApiContext(); //check against instance details endpoint //while merchant backend doesn't have a login endpoint - async function testLogin( - instance: string, - token: string, - ): Promise<{ - valid: boolean; - cause?: ErrorType; - }> { + async function requestNewLoginToken( + baseUrl: string, + token: AccessToken, + ): Promise<LoginResult> { + const data: MerchantBackend.Instances.LoginTokenRequest = { + scope: "write", + duration: { + d_us: "forever" + }, + refreshable: true, + } try { - const response = await request(instance, `/private/`, { + const response = await request<MerchantBackend.Instances.LoginTokenSuccessResponse>(baseUrl, `/private/token`, { + method: "POST", token, + data }); - return { valid: true }; + return { valid: true, token: response.data.token, expiration: response.data.expiration }; } catch (error) { if (error instanceof RequestError) { - return { valid: false, cause: error.cause.type }; + return { valid: false, cause: error.cause }; } - return { valid: false, cause: ErrorType.UNEXPECTED }; + return { + valid: false, cause: { + type: ErrorType.UNEXPECTED, + loading: false, + info: { + hasToken: true, + status: 0, + options: {}, + url: `/private/token`, + payload: {} + }, + exception: error, + message: (error instanceof Error ? error.message : "unpexepected error") + } + }; } }; - return testLogin + + async function refreshLoginToken( + baseUrl: string, + token: LoginToken + ): Promise<LoginResult> { + + if (AbsoluteTime.isExpired(AbsoluteTime.fromProtocolTimestamp(token.expiration))) { + return { + valid: false, cause: { + type: ErrorType.CLIENT, + status: HttpStatusCode.Unauthorized, + message: "login token expired, login again.", + info: { + hasToken: true, + status: 401, + options: {}, + url: `/private/token`, + payload: {} + }, + payload: {} + }, + } + } + + return requestNewLoginToken(baseUrl, token.token as AccessToken) + } + return { requestNewLoginToken, refreshLoginToken } } /** @@ -183,15 +257,20 @@ export function useCredentialsChecker() { * @returns request handler to */ export function useBackendBaseRequest(): useBackendBaseRequestType { - const { url: backend, token } = useBackendContext(); + const { url: backend, token: loginToken } = useBackendContext(); const { request: requestHandler } = useApiContext(); + const token = loginToken?.token; const request = useCallback( function requestImpl<T>( endpoint: string, options: RequestOptions = {}, ): Promise<HttpResponseOk<T>> { - return requestHandler<T>(backend, endpoint, { token, ...options }); + return requestHandler<T>(backend, endpoint, { token, ...options }).then(res => { + return res + }).catch(err => { + throw err + }); }, [backend, token], ); @@ -204,10 +283,12 @@ export function useBackendInstanceRequest(): useBackendInstanceRequestType { const { token: instanceToken, id, admin } = useInstanceContext(); const { request: requestHandler } = useApiContext(); - const { baseUrl, token } = !admin + const { baseUrl, token: loginToken } = !admin ? { baseUrl: rootBackendUrl, token: rootToken } : { baseUrl: `${rootBackendUrl}/instances/${id}`, token: instanceToken }; + const token = loginToken?.token; + const request = useCallback( function requestImpl<T>( endpoint: string, |