aboutsummaryrefslogtreecommitdiff
path: root/packages/web-util
diff options
context:
space:
mode:
Diffstat (limited to 'packages/web-util')
-rw-r--r--packages/web-util/src/components/Button.tsx36
-rw-r--r--packages/web-util/src/components/CopyButton.tsx5
-rw-r--r--packages/web-util/src/components/ErrorLoading.tsx28
-rw-r--r--packages/web-util/src/components/Header.tsx39
-rw-r--r--packages/web-util/src/components/NotificationBanner.tsx4
-rw-r--r--packages/web-util/src/components/ToastBanner.tsx59
-rw-r--r--packages/web-util/src/hooks/useNotifications.ts68
-rw-r--r--packages/web-util/src/utils/http-impl.sw.ts5
8 files changed, 191 insertions, 53 deletions
diff --git a/packages/web-util/src/components/Button.tsx b/packages/web-util/src/components/Button.tsx
index 26b778eec..ea0ea2f38 100644
--- a/packages/web-util/src/components/Button.tsx
+++ b/packages/web-util/src/components/Button.tsx
@@ -1,8 +1,24 @@
-import { OperationFail, OperationOk, OperationResult, TalerError, TranslatedString } from "@gnu-taler/taler-util";
+/*
+ This file is part of GNU Taler
+ (C) 2022-2024 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 { AbsoluteTime, OperationFail, OperationOk, OperationResult, TalerError, TranslatedString } from "@gnu-taler/taler-util";
// import { NotificationMessage, notifyInfo } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { HTMLAttributes, useEffect, useState, useTransition } from "preact/compat";
-import { NotificationMessage, buildRequestErrorMessage, notifyInfo, useTranslationContext } from "../index.browser.js";
+import { NotificationMessage, buildUnifiedRequestErrorMessage, notifyInfo, useTranslationContext } from "../index.browser.js";
// import { useBankCoreApiContext } from "../context/config.js";
// function errorMap<T extends OperationFail<unknown>>(resp: T, map: (d: T["case"]) => TranslatedString): void {
@@ -10,7 +26,7 @@ import { NotificationMessage, buildRequestErrorMessage, notifyInfo, useTranslati
export interface ButtonHandler<T extends OperationResult<A, B>, A, B> {
onClick: () => Promise<T | undefined>,
onNotification: (n: NotificationMessage) => void;
- onOperationSuccess: ((result:T extends OperationOk<any> ? T :never) => void) | ((result:T extends OperationOk<any> ? T :never) => TranslatedString | undefined),
+ onOperationSuccess: ((result: T extends OperationOk<any> ? T : never) => void) | ((result: T extends OperationOk<any> ? T : never) => TranslatedString | undefined),
onOperationFail: (d: T extends OperationFail<any> ? T : never) => TranslatedString;
onOperationComplete?: () => void;
}
@@ -33,10 +49,10 @@ export function Button<T extends OperationResult<A, B>, A, B>({
handler,
children,
disabled,
- onClick:clickEvent,
+ onClick: clickEvent,
...rest
}: Props<T, A, B>): VNode {
- const {i18n} = useTranslationContext();
+ const { i18n } = useTranslationContext();
const [running, setRunning] = useState(false)
return <button {...rest} disabled={disabled || running} onClick={(e) => {
e.preventDefault();
@@ -62,6 +78,7 @@ export function Button<T extends OperationResult<A, B>, A, B>({
type: "error",
description: error.detail.hint as TranslatedString,
debug: error.detail,
+ when: AbsoluteTime.now(),
})
}
}
@@ -71,17 +88,18 @@ export function Button<T extends OperationResult<A, B>, A, B>({
setRunning(false)
}).catch(error => {
console.error(error)
-
+
if (error instanceof TalerError) {
- handler.onNotification(buildRequestErrorMessage(i18n, error))
+ handler.onNotification(buildUnifiedRequestErrorMessage(i18n, error))
} else {
const description = (error instanceof Error ?
error.message : String(error)) as TranslatedString
-
+
handler.onNotification({
title: i18n.str`Operation failed`,
type: "error",
description,
+ when: AbsoluteTime.now(),
})
}
@@ -95,7 +113,7 @@ export function Button<T extends OperationResult<A, B>, A, B>({
</button>
}
-function Wait():VNode {
+function Wait(): VNode {
return <Fragment>
<style>{`
#l1 { width: 120px;
diff --git a/packages/web-util/src/components/CopyButton.tsx b/packages/web-util/src/components/CopyButton.tsx
index e76447291..fd7f8b3b4 100644
--- a/packages/web-util/src/components/CopyButton.tsx
+++ b/packages/web-util/src/components/CopyButton.tsx
@@ -38,7 +38,10 @@ export function CopyButton({ class: clazz, getContent }: { class: string, getCon
if (!copied) {
return (
- <button class={clazz} onClick={copyText} >
+ <button class={clazz} onClick={e => {
+ e.preventDefault()
+ copyText()
+ }} >
<CopyIcon />
</button>
);
diff --git a/packages/web-util/src/components/ErrorLoading.tsx b/packages/web-util/src/components/ErrorLoading.tsx
index 02f2a3282..7089266b9 100644
--- a/packages/web-util/src/components/ErrorLoading.tsx
+++ b/packages/web-util/src/components/ErrorLoading.tsx
@@ -26,6 +26,34 @@ export function ErrorLoading({ error, showDetail }: { error: TalerError, showDet
//////////////////
// Every error that can be produce in a Http Request
//////////////////
+ case TalerErrorCode.GENERIC_TIMEOUT: {
+ if (error.hasErrorCode(TalerErrorCode.GENERIC_TIMEOUT)) {
+ const { requestMethod, requestUrl, timeoutMs } = error.errorDetail
+ return <Attention type="danger" title={i18n.str`The request reached a timeout, check your connection.`}>
+ {error.message}
+ {showDetail &&
+ <pre class="whitespace-break-spaces ">
+ {JSON.stringify({ requestMethod, requestUrl, timeoutMs }, undefined, 2)}
+ </pre>
+ }
+ </Attention>
+ }
+ assertUnreachable(1 as never)
+ }
+ case TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR: {
+ if (error.hasErrorCode(TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR)) {
+ const { requestMethod, requestUrl, timeoutMs } = error.errorDetail
+ return <Attention type="danger" title={i18n.str`The request was cancelled.`}>
+ {error.message}
+ {showDetail &&
+ <pre class="whitespace-break-spaces ">
+ {JSON.stringify({ requestMethod, requestUrl, timeoutMs }, undefined, 2)}
+ </pre>
+ }
+ </Attention>
+ }
+ assertUnreachable(1 as never)
+ }
case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT: {
if (error.hasErrorCode(TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT)) {
const { requestMethod, requestUrl, timeoutMs } = error.errorDetail
diff --git a/packages/web-util/src/components/Header.tsx b/packages/web-util/src/components/Header.tsx
index fc7716320..29f4a4949 100644
--- a/packages/web-util/src/components/Header.tsx
+++ b/packages/web-util/src/components/Header.tsx
@@ -1,12 +1,24 @@
import { useState } from "preact/hooks";
-import { LangSelector, useTranslationContext } from "../index.browser.js";
+import { LangSelector, useNotifications, useTranslationContext } from "../index.browser.js";
import { ComponentChildren, Fragment, VNode, h } from "preact";
import logo from "../assets/logo-2021.svg";
-export function Header({ title, profileURL, iconLinkURL, sites, onLogout, children }:
- { title: string, iconLinkURL: string, profileURL?: string, children?: ComponentChildren, onLogout: (() => void) | undefined, sites: Array<Array<string>>, supportedLangs: string[] }): VNode {
+interface Props {
+ title: string;
+ iconLinkURL: string;
+ profileURL?: string;
+ notificationURL?: string;
+ children?: ComponentChildren;
+ onLogout: (() => void) | undefined;
+ sites: Array<Array<string>>;
+ supportedLangs: string[]
+}
+
+export function Header({ title, profileURL, notificationURL, iconLinkURL, sites, onLogout, children }: Props): VNode {
const { i18n } = useTranslationContext();
const [open, setOpen] = useState(false)
+ const ns = useNotifications();
+
return <Fragment>
<header class="bg-indigo-600 w-full mx-auto px-2 border-b border-opacity-25 border-indigo-400">
<div class="flex flex-row h-16 items-center ">
@@ -35,6 +47,22 @@ export function Header({ title, profileURL, iconLinkURL, sites, onLogout, childr
</div>
</div>
<div class="flex justify-end">
+ {!notificationURL ? undefined :
+ <a href={notificationURL} name="notifications" class="relative inline-flex items-center justify-center rounded-md bg-indigo-600 p-1 mr-2 text-indigo-200 hover:bg-indigo-500 hover:bg-opacity-75 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-indigo-600" aria-controls="mobile-menu" aria-expanded="false">
+ <span class="absolute -inset-0.5"></span>
+ <span class="sr-only"><i18n.Translate>Show notifications</i18n.Translate></span>
+ {ns.length > 0 ?
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-10 h-10">
+ <path d="M5.85 3.5a.75.75 0 0 0-1.117-1 9.719 9.719 0 0 0-2.348 4.876.75.75 0 0 0 1.479.248A8.219 8.219 0 0 1 5.85 3.5ZM19.267 2.5a.75.75 0 1 0-1.118 1 8.22 8.22 0 0 1 1.987 4.124.75.75 0 0 0 1.48-.248A9.72 9.72 0 0 0 19.266 2.5Z" />
+ <path fill-rule="evenodd" d="M12 2.25A6.75 6.75 0 0 0 5.25 9v.75a8.217 8.217 0 0 1-2.119 5.52.75.75 0 0 0 .298 1.206c1.544.57 3.16.99 4.831 1.243a3.75 3.75 0 1 0 7.48 0 24.583 24.583 0 0 0 4.83-1.244.75.75 0 0 0 .298-1.205 8.217 8.217 0 0 1-2.118-5.52V9A6.75 6.75 0 0 0 12 2.25ZM9.75 18c0-.034 0-.067.002-.1a25.05 25.05 0 0 0 4.496 0l.002.1a2.25 2.25 0 1 1-4.5 0Z" clip-rule="evenodd" />
+ </svg>
+ :
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-10 h-10">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0" />
+ </svg>
+ }
+ </a>
+ }
{!profileURL ? undefined :
<a href={profileURL} name="profile" class="relative inline-flex items-center justify-center rounded-md bg-indigo-600 p-1 mr-2 text-indigo-200 hover:bg-indigo-500 hover:bg-opacity-75 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-indigo-600" aria-controls="mobile-menu" aria-expanded="false">
<span class="absolute -inset-0.5"></span>
@@ -58,7 +86,8 @@ export function Header({ title, profileURL, iconLinkURL, sites, onLogout, childr
</div>
</header>
- {open &&
+ {
+ open &&
<div class="relative z-10" name="sidebar overlay" aria-labelledby="slide-over-title" role="dialog" aria-modal="true"
onClick={() => {
setOpen(false)
@@ -150,5 +179,5 @@ export function Header({ title, profileURL, iconLinkURL, sites, onLogout, childr
</div>
</div>
}
- </Fragment>
+ </Fragment >
}
diff --git a/packages/web-util/src/components/NotificationBanner.tsx b/packages/web-util/src/components/NotificationBanner.tsx
index 62733ab3c..31d5a5d01 100644
--- a/packages/web-util/src/components/NotificationBanner.tsx
+++ b/packages/web-util/src/components/NotificationBanner.tsx
@@ -9,7 +9,7 @@ export function LocalNotificationBanner({ notification, showDebug }: { notificat
return <div class="relative">
<div class="fixed top-0 left-0 right-0 z-20 w-full p-4">
<Attention type="danger" title={notification.message.title} onClose={() => {
- notification.remove()
+ notification.acknowledge()
}}>
{notification.message.description &&
<div class="mt-2 text-sm text-red-700">
@@ -26,7 +26,7 @@ export function LocalNotificationBanner({ notification, showDebug }: { notificat
return <div class="relative">
<div class="fixed top-0 left-0 right-0 z-20 w-full p-4">
<Attention type="success" title={notification.message.title} onClose={() => {
- notification.remove();
+ notification.acknowledge();
}} /></div></div>
}
}
diff --git a/packages/web-util/src/components/ToastBanner.tsx b/packages/web-util/src/components/ToastBanner.tsx
index 2424b17ff..ece26285f 100644
--- a/packages/web-util/src/components/ToastBanner.tsx
+++ b/packages/web-util/src/components/ToastBanner.tsx
@@ -1,5 +1,20 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022-2024 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 { Fragment, VNode, h } from "preact"
-import { Attention, GLOBAL_NOTIFICATION_TIMEOUT as GLOBAL_TOAST_TIMEOUT, useNotifications } from "../index.browser.js"
+import { Attention, GLOBAL_NOTIFICATION_TIMEOUT as GLOBAL_TOAST_TIMEOUT, Notification, useNotifications } from "../index.browser.js"
/**
* Toasts should be considered when displaying these types of information to the user:
@@ -21,24 +36,26 @@ import { Attention, GLOBAL_NOTIFICATION_TIMEOUT as GLOBAL_TOAST_TIMEOUT, useNoti
export function ToastBanner(): VNode {
const notifs = useNotifications()
if (notifs.length === 0) return <Fragment />
- return <Fragment> {
- notifs.map(n => {
- switch (n.message.type) {
- case "error":
- return <Attention type="danger" title={n.message.title} onClose={() => {
- n.remove()
- }} timeout={GLOBAL_TOAST_TIMEOUT}>
- {n.message.description &&
- <div class="mt-2 text-sm text-red-700">
- {n.message.description}
- </div>
- }
- </Attention>
- case "info":
- return <Attention type="success" title={n.message.title} onClose={() => {
- n.remove();
- }} timeout={GLOBAL_TOAST_TIMEOUT} />
- }
- })}
- </Fragment>
+ const show = notifs.filter(e => !e.message.ack && !e.message.timeout)
+ if (show.length === 0) return <Fragment />
+ return <AttentionByType msg={show[0]} />
+}
+
+function AttentionByType({ msg }: { msg: Notification }) {
+ switch (msg.message.type) {
+ case "error":
+ return <Attention type="danger" title={msg.message.title} onClose={() => {
+ msg.acknowledge()
+ }} timeout={GLOBAL_TOAST_TIMEOUT}>
+ {msg.message.description &&
+ <div class="mt-2 text-sm text-red-700">
+ {msg.message.description}
+ </div>
+ }
+ </Attention>
+ case "info":
+ return <Attention type="success" title={msg.message.title} onClose={() => {
+ msg.acknowledge();
+ }} timeout={GLOBAL_TOAST_TIMEOUT} />
+ }
}
diff --git a/packages/web-util/src/hooks/useNotifications.ts b/packages/web-util/src/hooks/useNotifications.ts
index 000abbc94..99f4f2699 100644
--- a/packages/web-util/src/hooks/useNotifications.ts
+++ b/packages/web-util/src/hooks/useNotifications.ts
@@ -1,4 +1,5 @@
import {
+ AbsoluteTime,
Duration,
OperationFail,
OperationOk,
@@ -20,12 +21,18 @@ export type NotificationMessage = ErrorNotification | InfoNotification;
export interface ErrorNotification {
type: "error";
title: TranslatedString;
+ ack?: boolean;
+ timeout?: boolean;
description?: TranslatedString;
debug?: any;
+ when: AbsoluteTime;
}
export interface InfoNotification {
type: "info";
title: TranslatedString;
+ ack?: boolean;
+ timeout?: boolean;
+ when: AbsoluteTime;
}
const storage = memoryMap<Map<string, NotificationMessage>>();
@@ -35,11 +42,11 @@ export const GLOBAL_NOTIFICATION_TIMEOUT = Duration.fromSpec({
seconds: 5,
});
-function removeFromStorage(n: NotificationMessage) {
+function updateInStorage(n: NotificationMessage) {
const h = hash(n);
const mem = storage.get(NOTIFICATION_KEY) ?? new Map();
const newState = new Map(mem);
- newState.delete(h);
+ newState.set(h, n);
storage.set(NOTIFICATION_KEY, newState);
}
@@ -50,7 +57,8 @@ export function notify(notif: NotificationMessage): void {
if (GLOBAL_NOTIFICATION_TIMEOUT.d_ms !== "forever") {
setTimeout(() => {
- removeFromStorage(notif);
+ notif.timeout = true;
+ updateInStorage(notif);
}, GLOBAL_NOTIFICATION_TIMEOUT.d_ms);
}
@@ -66,6 +74,7 @@ export function notifyError(
title,
description,
debug,
+ when: AbsoluteTime.now(),
});
}
export function notifyException(title: TranslatedString, ex: Error) {
@@ -74,34 +83,40 @@ export function notifyException(title: TranslatedString, ex: Error) {
title,
description: ex.message as TranslatedString,
debug: ex.stack,
+ when: AbsoluteTime.now(),
});
}
export function notifyInfo(title: TranslatedString) {
notify({
type: "info" as const,
title,
+ when: AbsoluteTime.now(),
});
}
export type Notification = {
message: NotificationMessage;
- remove: () => void;
+ acknowledge: () => void;
};
export function useNotifications(): Notification[] {
- const [value, setter] = useState<Map<string, NotificationMessage>>(new Map());
+ const [, setLastUpdate] = useState<number>();
+ const value = storage.get(NOTIFICATION_KEY) ?? new Map();
+
useEffect(() => {
return storage.onUpdate(NOTIFICATION_KEY, () => {
- const mem = storage.get(NOTIFICATION_KEY) ?? new Map();
- setter(structuredClone(mem));
+ setLastUpdate(Date.now())
+ // const mem = storage.get(NOTIFICATION_KEY) ?? new Map();
+ // setter(structuredClone(mem));
});
});
return Array.from(value.values()).map((message, idx) => {
return {
message,
- remove: () => {
- removeFromStorage(message);
+ acknowledge: () => {
+ message.ack = true;
+ updateInStorage(message);
},
};
});
@@ -141,6 +156,7 @@ function errorMap<T extends OperationFail<unknown>>(
title: map(resp.case),
description: resp.detail.hint as TranslatedString,
debug: resp.detail,
+ when: AbsoluteTime.now(),
});
}
@@ -165,7 +181,7 @@ export function useLocalNotification(): [
? undefined
: {
message: value,
- remove: () => {
+ acknowledge: () => {
setter(undefined);
},
};
@@ -175,7 +191,7 @@ export function useLocalNotification(): [
return await cb(errorMap);
} catch (error: unknown) {
if (error instanceof TalerError) {
- notify(buildRequestErrorMessage(i18n, error));
+ notify(buildUnifiedRequestErrorMessage(i18n, error));
} else {
notifyError(
i18n.str`Operation failed, please report`,
@@ -212,7 +228,7 @@ export function useLocalNotificationHandler(): [
? undefined
: {
message: value,
- remove: () => {
+ acknowledge: () => {
setter(undefined);
},
};
@@ -241,18 +257,39 @@ export function useLocalNotificationHandler(): [
return [notif, makeHandler, setter];
}
-export function buildRequestErrorMessage(
+export function buildUnifiedRequestErrorMessage(
i18n: InternationalizationAPI,
cause: TalerError,
): ErrorNotification {
let result: ErrorNotification;
switch (cause.errorDetail.code) {
+ case TalerErrorCode.GENERIC_TIMEOUT: {
+ result = {
+ type: "error",
+ title: i18n.str`Request timeout`,
+ description: cause.message as TranslatedString,
+ debug: JSON.stringify(cause.errorDetail, undefined, 2),
+ when: AbsoluteTime.now(),
+ };
+ break;
+ }
+ case TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR: {
+ result = {
+ type: "error",
+ title: i18n.str`Request cancelled`,
+ description: cause.message as TranslatedString,
+ debug: JSON.stringify(cause.errorDetail, undefined, 2),
+ when: AbsoluteTime.now(),
+ };
+ break;
+ }
case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT: {
result = {
type: "error",
title: i18n.str`Request timeout`,
description: cause.message as TranslatedString,
debug: JSON.stringify(cause.errorDetail, undefined, 2),
+ when: AbsoluteTime.now(),
};
break;
}
@@ -262,6 +299,7 @@ export function buildRequestErrorMessage(
title: i18n.str`Request throttled`,
description: cause.message as TranslatedString,
debug: JSON.stringify(cause.errorDetail, undefined, 2),
+ when: AbsoluteTime.now(),
};
break;
}
@@ -271,6 +309,7 @@ export function buildRequestErrorMessage(
title: i18n.str`Malformed response`,
description: cause.message as TranslatedString,
debug: JSON.stringify(cause.errorDetail, undefined, 2),
+ when: AbsoluteTime.now(),
};
break;
}
@@ -280,6 +319,7 @@ export function buildRequestErrorMessage(
title: i18n.str`Network error`,
description: cause.message as TranslatedString,
debug: JSON.stringify(cause.errorDetail, undefined, 2),
+ when: AbsoluteTime.now(),
};
break;
}
@@ -289,6 +329,7 @@ export function buildRequestErrorMessage(
title: i18n.str`Unexpected request error`,
description: cause.message as TranslatedString,
debug: JSON.stringify(cause.errorDetail, undefined, 2),
+ when: AbsoluteTime.now(),
};
break;
}
@@ -298,6 +339,7 @@ export function buildRequestErrorMessage(
title: i18n.str`Unexpected error`,
description: cause.message as TranslatedString,
debug: JSON.stringify(cause.errorDetail, undefined, 2),
+ when: AbsoluteTime.now(),
};
break;
}
diff --git a/packages/web-util/src/utils/http-impl.sw.ts b/packages/web-util/src/utils/http-impl.sw.ts
index 316b75dfd..4d7f3a8a1 100644
--- a/packages/web-util/src/utils/http-impl.sw.ts
+++ b/packages/web-util/src/utils/http-impl.sw.ts
@@ -22,6 +22,7 @@ import {
TalerErrorCode,
TalerError,
Duration,
+ CancellationToken,
} from "@gnu-taler/taler-util";
import {
@@ -137,13 +138,13 @@ export class BrowserFetchHttpLib implements HttpRequestLibrary {
} catch (e) {
if (controller.signal) {
throw TalerError.fromDetail(
- TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT,
+ controller.signal.reason,
{
requestUrl,
requestMethod,
timeoutMs: requestTimeout.d_ms === "forever" ? 0 : requestTimeout.d_ms
},
- `request to ${requestUrl} timed out`,
+ `HTTP request failed.`,
);
}
throw e;