gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated (ce53dd8b8 -> f788955d4)


From: gnunet
Subject: [taler-wallet-core] branch master updated (ce53dd8b8 -> f788955d4)
Date: Mon, 03 Apr 2023 17:29:35 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a change to branch master
in repository wallet-core.

    from ce53dd8b8 fix #7770
     new defdfd769 taleruri sync with the spec
     new 543795f7f save posConfirmation after payment
     new d9c39f53d fix currency link in history, hide auto-open wallet, fix 
space
     new 1e94777aa fix https on port 8081
     new f788955d4 recovery -> restore

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/taler-util/src/backup-types.ts            |   5 +
 packages/taler-util/src/taler-types.ts             |   2 +
 packages/taler-util/src/taleruri.ts                | 555 ++++++++++++++++-----
 packages/taler-util/src/transactions-types.ts      |   5 +
 packages/taler-util/src/wallet-types.ts            |   8 +-
 packages/taler-wallet-core/src/db.ts               |   2 +
 .../src/operations/backup/export.ts                |   1 +
 .../src/operations/backup/import.ts                |   1 +
 .../src/operations/pay-merchant.ts                 |   9 +-
 .../src/operations/transactions.ts                 |   1 +
 .../src/cta/Recovery/state.ts                      |   4 +-
 .../src/wallet/Application.tsx                     |   3 +-
 .../src/wallet/History.stories.tsx                 |   1 +
 .../src/wallet/Settings.tsx                        |  53 +-
 .../src/wallet/Transaction.stories.tsx             |   1 +
 .../src/wallet/Transaction.tsx                     |   2 +-
 packages/web-util/src/live-reload.ts               |   3 +-
 17 files changed, 501 insertions(+), 155 deletions(-)

diff --git a/packages/taler-util/src/backup-types.ts 
b/packages/taler-util/src/backup-types.ts
index 0a355b65f..f7bf5ef30 100644
--- a/packages/taler-util/src/backup-types.ts
+++ b/packages/taler-util/src/backup-types.ts
@@ -947,6 +947,11 @@ export interface BackupPurchase {
    */
   merchant_pay_sig: string | undefined;
 
+  /**
+   * Text to be shown to the point-of-sale staff as a proof of payment.
+   */
+  pos_confirmation: string | undefined;
+
   timestamp_proposed: TalerProtocolTimestamp;
 
   /**
diff --git a/packages/taler-util/src/taler-types.ts 
b/packages/taler-util/src/taler-types.ts
index 6e7df2c04..48eb49d22 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -961,6 +961,7 @@ export class ExchangeWithdrawBatchResponse {
 
 export interface MerchantPayResponse {
   sig: string;
+  pos_confirmation?: string;
 }
 
 export interface ExchangeMeltRequest {
@@ -1490,6 +1491,7 @@ export const codecForWithdrawBatchResponse =
 export const codecForMerchantPayResponse = (): Codec<MerchantPayResponse> =>
   buildCodecForObject<MerchantPayResponse>()
     .property("sig", codecForString())
+    .property("pos_confirmation", codecOptional(codecForString()))
     .build("MerchantPayResponse");
 
 export const codecForExchangeMeltResponse = (): Codec<ExchangeMeltResponse> =>
diff --git a/packages/taler-util/src/taleruri.ts 
b/packages/taler-util/src/taleruri.ts
index 4d55d4c98..e641f1d27 100644
--- a/packages/taler-util/src/taleruri.ts
+++ b/packages/taler-util/src/taleruri.ts
@@ -14,11 +14,24 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { BackupRecovery } from "./backup-types.js";
 import { canonicalizeBaseUrl } from "./helpers.js";
 import { URLSearchParams, URL } from "./url.js";
 
+export type TalerUri =
+  | PayUriResult
+  | PayTemplateUriResult
+  | DevExperimentUri
+  | PayPullUriResult
+  | PayPushUriResult
+  | BackupRestoreUri
+  | RefundUriResult
+  | TipUriResult
+  | WithdrawUriResult
+  | ExchangeUri
+  | AuditorUri;
+
 export interface PayUriResult {
+  type: TalerUriAction.Pay;
   merchantBaseUrl: string;
   orderId: string;
   sessionId: string;
@@ -27,40 +40,68 @@ export interface PayUriResult {
 }
 
 export interface PayTemplateUriResult {
+  type: TalerUriAction.PayTemplate;
   merchantBaseUrl: string;
   templateId: string;
   templateParams: Record<string, string>;
 }
 
 export interface WithdrawUriResult {
+  type: TalerUriAction.Withdraw;
   bankIntegrationApiBaseUrl: string;
   withdrawalOperationId: string;
 }
 
 export interface RefundUriResult {
+  type: TalerUriAction.Refund;
   merchantBaseUrl: string;
   orderId: string;
 }
 
 export interface TipUriResult {
-  merchantTipId: string;
+  type: TalerUriAction.Tip;
   merchantBaseUrl: string;
+  merchantTipId: string;
+}
+
+export interface ExchangeUri {
+  type: TalerUriAction.Exchange;
+  exchangeBaseUrl: string;
+  exchangePub: string;
+}
+
+export interface AuditorUri {
+  type: TalerUriAction.Auditor;
+  auditorBaseUrl: string;
+  auditorPub: string;
 }
 
 export interface PayPushUriResult {
+  type: TalerUriAction.PayPush;
   exchangeBaseUrl: string;
   contractPriv: string;
 }
 
 export interface PayPullUriResult {
+  type: TalerUriAction.PayPull;
   exchangeBaseUrl: string;
   contractPriv: string;
 }
 
 export interface DevExperimentUri {
+  type: TalerUriAction.DevExperiment;
   devExperimentId: string;
 }
 
+export interface BackupRestoreUri {
+  type: TalerUriAction.Restore;
+  walletRootPriv: string;
+  providers: {
+    name: string;
+    url: string;
+  }[];
+}
+
 /**
  * Parse a taler[+http]://withdraw URI.
  * Return undefined if not passed a valid URI.
@@ -89,11 +130,15 @@ export function parseWithdrawUri(s: string): 
WithdrawUriResult | undefined {
   const p = [host, ...pathSegments].join("/");
 
   return {
+    type: TalerUriAction.Withdraw,
     bankIntegrationApiBaseUrl: canonicalizeBaseUrl(`${pi.innerProto}://${p}/`),
     withdrawalOperationId: withdrawId,
   };
 }
 
+/**
+ * @deprecated use TalerUriAction
+ */
 export enum TalerUriType {
   TalerPay = "taler-pay",
   TalerTemplate = "taler-template",
@@ -112,15 +157,30 @@ const talerActionPayPull = "pay-pull";
 const talerActionPayPush = "pay-push";
 const talerActionPayTemplate = "pay-template";
 
+export enum TalerUriAction {
+  Pay = "pay",
+  Withdraw = "withdraw",
+  Refund = "refund",
+  Tip = "tip",
+  PayPull = "pay-pull",
+  PayPush = "pay-push",
+  PayTemplate = "pay-template",
+  Exchange = "exchange",
+  Auditor = "auditor",
+  Restore = "restore",
+  DevExperiment = "dev-experiment",
+}
+
 /**
  * Classify a taler:// URI.
+ * @deprecated use parseTalerUri
  */
 export function classifyTalerUri(s: string): TalerUriType {
   const sl = s.toLowerCase();
-  if (sl.startsWith("taler://recovery/")) {
+  if (sl.startsWith("taler://restore/")) {
     return TalerUriType.TalerRecovery;
   }
-  if (sl.startsWith("taler+http://recovery/";)) {
+  if (sl.startsWith("taler+http://restore/";)) {
     return TalerUriType.TalerRecovery;
   }
   if (sl.startsWith("taler://pay/")) {
@@ -197,6 +257,71 @@ function parseProtoInfo(
   }
 }
 
+type Parser = (s: string) => TalerUri | undefined;
+const parsers: { [A in TalerUriAction]: Parser } = {
+  [TalerUriAction.Pay]: parsePayUri,
+  [TalerUriAction.PayPull]: parsePayPullUri,
+  [TalerUriAction.PayPush]: parsePayPushUri,
+  [TalerUriAction.PayTemplate]: parsePayTemplateUri,
+  [TalerUriAction.Restore]: parseRestoreUri,
+  [TalerUriAction.Refund]: parseRefundUri,
+  [TalerUriAction.Tip]: parseTipUri,
+  [TalerUriAction.Withdraw]: parseWithdrawUri,
+  [TalerUriAction.DevExperiment]: parseDevExperimentUri,
+  [TalerUriAction.Exchange]: parseExchangeUri,
+  [TalerUriAction.Auditor]: parseAuditorUri,
+};
+
+export function parseTalerUri(string: string): TalerUri | undefined {
+  const https = string.startsWith("taler://");
+  const http = string.startsWith("taler+http://";);
+  if (!https && !http) return undefined;
+  const actionStart = https ? 8 : 13;
+  const actionEnd = string.indexOf("/", actionStart + 1);
+  const action = string.substring(actionStart, actionEnd);
+  const found = Object.values(TalerUriAction).find((x) => x === action);
+  if (!found) return undefined;
+  return parsers[found](string);
+}
+
+export function stringifyTalerUri(uri: TalerUri): string {
+  switch (uri.type) {
+    case TalerUriAction.DevExperiment: {
+      return stringifyDevExperimentUri(uri);
+    }
+    case TalerUriAction.Pay: {
+      return stringifyPayUri(uri);
+    }
+    case TalerUriAction.PayPull: {
+      return stringifyPayPullUri(uri);
+    }
+    case TalerUriAction.PayPush: {
+      return stringifyPayPushUri(uri);
+    }
+    case TalerUriAction.PayTemplate: {
+      return stringifyPayTemplateUri(uri);
+    }
+    case TalerUriAction.Restore: {
+      return stringifyRestoreUri(uri);
+    }
+    case TalerUriAction.Refund: {
+      return stringifyRefundUri(uri);
+    }
+    case TalerUriAction.Tip: {
+      return stringifyTipUri(uri);
+    }
+    case TalerUriAction.Withdraw: {
+      return stringifyWithdrawUri(uri);
+    }
+    case TalerUriAction.Exchange: {
+      return stringifyExchangeUri(uri);
+    }
+    case TalerUriAction.Auditor: {
+      return stringifyAuditorUri(uri);
+    }
+  }
+}
+
 /**
  * Parse a taler[+http]://pay URI.
  * Return undefined if not passed a valid URI.
@@ -222,76 +347,51 @@ export function parsePayUri(s: string): PayUriResult | 
undefined {
   const merchantBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
 
   return {
+    type: TalerUriAction.Pay,
     merchantBaseUrl,
     orderId,
-    sessionId: sessionId,
+    sessionId,
     claimToken,
     noncePriv,
   };
 }
 
 export function parsePayTemplateUri(
-  s: string,
+  uriString: string,
 ): PayTemplateUriResult | undefined {
-  const pi = parseProtoInfo(s, talerActionPayTemplate);
+  const pi = parseProtoInfo(uriString, talerActionPayTemplate);
   if (!pi) {
     return undefined;
   }
-  const c = pi?.rest.split("?");
-  const q = new URLSearchParams(c[1] ?? "");
+  const c = pi.rest.split("?");
+
   const parts = c[0].split("/");
   if (parts.length < 2) {
     return undefined;
   }
-  const host = parts[0].toLowerCase();
-  const templateId = parts[parts.length - 1];
-  const pathSegments = parts.slice(1, parts.length - 1);
-  const p = [host, ...pathSegments].join("/");
-  const merchantBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
 
+  const q = new URLSearchParams(c[1] ?? "");
   const params: Record<string, string> = {};
-
   q.forEach((v, k) => {
     params[k] = v;
   });
 
+  const host = parts[0].toLowerCase();
+  const templateId = parts[parts.length - 1];
+  const pathSegments = parts.slice(1, parts.length - 1);
+  const hostAndSegments = [host, ...pathSegments].join("/");
+  const merchantBaseUrl = canonicalizeBaseUrl(
+    `${pi.innerProto}://${hostAndSegments}/`,
+  );
+
   return {
+    type: TalerUriAction.PayTemplate,
     merchantBaseUrl,
     templateId,
     templateParams: params,
   };
 }
 
-export function constructPayUri(
-  merchantBaseUrl: string,
-  orderId: string,
-  sessionId: string,
-  claimToken?: string,
-  noncePriv?: string,
-): string {
-  const base = canonicalizeBaseUrl(merchantBaseUrl);
-  const url = new URL(base);
-  const isHttp = base.startsWith("http://";);
-  let result = isHttp ? `taler+http://pay/` : `taler://pay/`;
-  result += url.hostname;
-  if (url.port != "") {
-    result += `:${url.port}`;
-  }
-  result += `${url.pathname}${orderId}/${sessionId}`;
-  const qp = new URLSearchParams();
-  if (claimToken) {
-    qp.append("c", claimToken);
-  }
-  if (noncePriv) {
-    qp.append("n", noncePriv);
-  }
-  const queryPart = qp.toString();
-  if (queryPart) {
-    result += "?" + queryPart;
-  }
-  return result;
-}
-
 export function parsePayPushUri(s: string): PayPushUriResult | undefined {
   const pi = parseProtoInfo(s, talerActionPayPush);
   if (!pi) {
@@ -305,10 +405,13 @@ export function parsePayPushUri(s: string): 
PayPushUriResult | undefined {
   const host = parts[0].toLowerCase();
   const contractPriv = parts[parts.length - 1];
   const pathSegments = parts.slice(1, parts.length - 1);
-  const p = [host, ...pathSegments].join("/");
-  const exchangeBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
+  const hostAndSegments = [host, ...pathSegments].join("/");
+  const exchangeBaseUrl = canonicalizeBaseUrl(
+    `${pi.innerProto}://${hostAndSegments}/`,
+  );
 
   return {
+    type: TalerUriAction.PayPush,
     exchangeBaseUrl,
     contractPriv,
   };
@@ -327,10 +430,13 @@ export function parsePayPullUri(s: string): 
PayPullUriResult | undefined {
   const host = parts[0].toLowerCase();
   const contractPriv = parts[parts.length - 1];
   const pathSegments = parts.slice(1, parts.length - 1);
-  const p = [host, ...pathSegments].join("/");
-  const exchangeBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
+  const hostAndSegments = [host, ...pathSegments].join("/");
+  const exchangeBaseUrl = canonicalizeBaseUrl(
+    `${pi.innerProto}://${hostAndSegments}/`,
+  );
 
   return {
+    type: TalerUriAction.PayPull,
     exchangeBaseUrl,
     contractPriv,
   };
@@ -353,15 +459,67 @@ export function parseTipUri(s: string): TipUriResult | 
undefined {
   const host = parts[0].toLowerCase();
   const tipId = parts[parts.length - 1];
   const pathSegments = parts.slice(1, parts.length - 1);
-  const p = [host, ...pathSegments].join("/");
-  const merchantBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
+  const hostAndSegments = [host, ...pathSegments].join("/");
+  const merchantBaseUrl = canonicalizeBaseUrl(
+    `${pi.innerProto}://${hostAndSegments}/`,
+  );
 
   return {
+    type: TalerUriAction.Tip,
     merchantBaseUrl,
     merchantTipId: tipId,
   };
 }
 
+export function parseExchangeUri(s: string): ExchangeUri | undefined {
+  const pi = parseProtoInfo(s, "exchange");
+  if (!pi) {
+    return undefined;
+  }
+  const c = pi?.rest.split("?");
+  const parts = c[0].split("/");
+  if (parts.length < 2) {
+    return undefined;
+  }
+  const host = parts[0].toLowerCase();
+  const exchangePub = parts[parts.length - 1];
+  const pathSegments = parts.slice(1, parts.length - 1);
+  const hostAndSegments = [host, ...pathSegments].join("/");
+  const exchangeBaseUrl = canonicalizeBaseUrl(
+    `${pi.innerProto}://${hostAndSegments}/`,
+  );
+
+  return {
+    type: TalerUriAction.Exchange,
+    exchangeBaseUrl,
+    exchangePub,
+  };
+}
+export function parseAuditorUri(s: string): AuditorUri | undefined {
+  const pi = parseProtoInfo(s, "auditor");
+  if (!pi) {
+    return undefined;
+  }
+  const c = pi?.rest.split("?");
+  const parts = c[0].split("/");
+  if (parts.length < 2) {
+    return undefined;
+  }
+  const host = parts[0].toLowerCase();
+  const auditorPub = parts[parts.length - 1];
+  const pathSegments = parts.slice(1, parts.length - 1);
+  const hostAndSegments = [host, ...pathSegments].join("/");
+  const auditorBaseUrl = canonicalizeBaseUrl(
+    `${pi.innerProto}://${hostAndSegments}/`,
+  );
+
+  return {
+    type: TalerUriAction.Auditor,
+    auditorBaseUrl,
+    auditorPub,
+  };
+}
+
 /**
  * Parse a taler[+http]://refund URI.
  * Return undefined if not passed a valid URI.
@@ -377,13 +535,16 @@ export function parseRefundUri(s: string): 
RefundUriResult | undefined {
     return undefined;
   }
   const host = parts[0].toLowerCase();
-  const sessionId = parts[parts.length - 1];
-  const orderId = parts[parts.length - 2];
-  const pathSegments = parts.slice(1, parts.length - 2);
-  const p = [host, ...pathSegments].join("/");
-  const merchantBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
+  // const sessionId = parts[parts.length - 1];
+  const orderId = parts[parts.length - 1];
+  const pathSegments = parts.slice(1, parts.length - 1);
+  const hostAndSegments = [host, ...pathSegments].join("/");
+  const merchantBaseUrl = canonicalizeBaseUrl(
+    `${pi.innerProto}://${hostAndSegments}/`,
+  );
 
   return {
+    type: TalerUriAction.Refund,
     merchantBaseUrl,
     orderId,
   };
@@ -395,89 +556,249 @@ export function parseDevExperimentUri(s: string): 
DevExperimentUri | undefined {
   if (!c) {
     return undefined;
   }
-  // const q = new URLSearchParams(c[1] ?? "");
   const parts = c[0].split("/");
   return {
+    type: TalerUriAction.DevExperiment,
     devExperimentId: parts[0],
   };
 }
 
-export function constructPayPushUri(args: {
-  exchangeBaseUrl: string;
-  contractPriv: string;
-}): string {
-  const url = new URL(args.exchangeBaseUrl);
-  let proto: string;
-  if (url.protocol === "https:") {
-    proto = "taler";
-  } else if (url.protocol === "http:") {
-    proto = "taler+http";
-  } else {
-    throw Error(`Unsupported exchange URL protocol ${args.exchangeBaseUrl}`);
+export function parseRestoreUri(uri: string): BackupRestoreUri | undefined {
+  const pi = parseProtoInfo(uri, "restore");
+  if (!pi) {
+    return undefined;
   }
-  if (!url.pathname.endsWith("/")) {
-    throw Error(
-      `exchange base URL must end with a slash (got 
${args.exchangeBaseUrl}instead)`,
-    );
+  const c = pi.rest.split("?");
+  const parts = c[0].split("/");
+  if (parts.length < 2) {
+    return undefined;
   }
-  return `${proto}://pay-push/${url.host}${url.pathname}${args.contractPriv}`;
+
+  const walletRootPriv = parts[0];
+  if (!walletRootPriv) return undefined;
+  const providers = new Array<{ name: string; url: string }>();
+  parts[1].split(",").map((name) => {
+    const url = canonicalizeBaseUrl(`${pi.innerProto}://${name}/`);
+    providers.push({ name, url });
+  });
+  return {
+    type: TalerUriAction.Restore,
+    walletRootPriv,
+    providers,
+  };
+}
+
+// ================================================
+//  To string functions
+// ================================================
+
+/**
+ * @deprecated use stringifyRecoveryUri
+ */
+export function constructRecoveryUri(args: {
+  walletRootPriv: string;
+  providers: {
+    name: string;
+    url: string;
+  }[];
+}): string {
+  return stringifyRestoreUri(args);
 }
 
+/**
+ * @deprecated stringifyPayPullUri
+ */
 export function constructPayPullUri(args: {
   exchangeBaseUrl: string;
   contractPriv: string;
 }): string {
-  const url = new URL(args.exchangeBaseUrl);
+  return stringifyPayPullUri(args);
+}
+
+/**
+ * @deprecated use stringifyPayPushUri
+ */
+export function constructPayPushUri(args: {
+  exchangeBaseUrl: string;
+  contractPriv: string;
+}): string {
+  return stringifyPayPushUri(args);
+}
+
+/**
+ *
+ * @deprecated use stringifyPayUri
+ */
+export function constructPayUri(
+  merchantBaseUrl: string,
+  orderId: string,
+  sessionId: string,
+  claimToken?: string,
+  noncePriv?: string,
+): string {
+  return stringifyPayUri({
+    merchantBaseUrl,
+    orderId,
+    sessionId,
+    claimToken,
+    noncePriv,
+  });
+}
+
+export function stringifyPayUri({
+  merchantBaseUrl,
+  orderId,
+  sessionId,
+  claimToken,
+  noncePriv,
+}: Omit<PayUriResult, "type">): string {
+  // const base = canonicalizeBaseUrl(merchantBaseUrl);
+  // const url = new URL(base);
+  // const isHttp = base.startsWith("http://";);
+  // let result = isHttp ? `taler+http://pay/` : `taler://pay/`;
+  // result += url.hostname;
+  // if (url.port != "") {
+  //   result += `:${url.port}`;
+  // }
+  // result += `${url.pathname}${orderId}/${sessionId}`;
+  // const qp = new URLSearchParams();
+  // if (claimToken) {
+  //   qp.append("c", claimToken);
+  // }
+  // if (noncePriv) {
+  //   qp.append("n", noncePriv);
+  // }
+  // const queryPart = qp.toString();
+  // if (queryPart) {
+  //   result += "?" + queryPart;
+  // }
+  const { proto, path, query } = getUrlInfo(merchantBaseUrl, {
+    c: claimToken,
+    n: noncePriv,
+  });
+  return `${proto}://pay/${path}${orderId}/${sessionId}${query}`;
+}
+
+export function stringifyPayPullUri({
+  contractPriv,
+  exchangeBaseUrl,
+}: Omit<PayPullUriResult, "type">): string {
+  const { proto, path } = getUrlInfo(exchangeBaseUrl);
+  return `${proto}://pay-pull/${path}${contractPriv}`;
+}
+
+export function stringifyPayPushUri({
+  contractPriv,
+  exchangeBaseUrl,
+}: Omit<PayPushUriResult, "type">): string {
+  const { proto, path } = getUrlInfo(exchangeBaseUrl);
+
+  return `${proto}://pay-push/${path}${contractPriv}`;
+}
+
+export function stringifyRestoreUri({
+  providers,
+  walletRootPriv,
+}: Omit<BackupRestoreUri, "type">): string {
+  const list = providers.map((p) => `${new URL(p.url).hostname}`).join("m");
+  return `taler://restore/${walletRootPriv}/${list}`;
+}
+
+export function stringifyDevExperimentUri({
+  devExperimentId,
+}: Omit<DevExperimentUri, "type">): string {
+  return `taler://dev-experiment/${devExperimentId}`;
+}
+
+export function stringifyPayTemplateUri({
+  merchantBaseUrl,
+  templateId,
+  templateParams,
+}: Omit<PayTemplateUriResult, "type">): string {
+  const { proto, path, query } = getUrlInfo(merchantBaseUrl, templateParams);
+  return `${proto}://pay-template/${path}${templateId}${query}`;
+}
+export function stringifyRefundUri({
+  merchantBaseUrl,
+  orderId,
+}: Omit<RefundUriResult, "type">): string {
+  const { proto, path } = getUrlInfo(merchantBaseUrl);
+  return `${proto}://refund/${path}${orderId}`;
+}
+export function stringifyTipUri({
+  merchantBaseUrl,
+  merchantTipId,
+}: Omit<TipUriResult, "type">): string {
+  const { proto, path } = getUrlInfo(merchantBaseUrl);
+  return `${proto}://tip/${path}${merchantTipId}`;
+}
+
+export function stringifyExchangeUri({
+  exchangeBaseUrl,
+  exchangePub,
+}: Omit<ExchangeUri, "type">): string {
+  const { proto, path } = getUrlInfo(exchangeBaseUrl);
+  return `${proto}://exchange/${path}${exchangePub}`;
+}
+
+export function stringifyAuditorUri({
+  auditorBaseUrl,
+  auditorPub,
+}: Omit<AuditorUri, "type">): string {
+  const { proto, path } = getUrlInfo(auditorBaseUrl);
+  return `${proto}://auditor/${path}${auditorPub}`;
+}
+
+export function stringifyWithdrawUri({
+  bankIntegrationApiBaseUrl,
+  withdrawalOperationId,
+}: Omit<WithdrawUriResult, "type">): string {
+  const { proto, path } = getUrlInfo(bankIntegrationApiBaseUrl);
+  return `${proto}://withdraw/${path}${withdrawalOperationId}`;
+}
+
+/**
+ * Use baseUrl to defined http or https
+ * create path using host+port+pathname
+ * use params to create a query parameter string or empty
+ *
+ * @param baseUrl
+ * @param params
+ * @returns
+ */
+function getUrlInfo(
+  baseUrl: string,
+  params: Record<string, string | undefined> = {},
+): { proto: string; path: string; query: string } {
+  const url = new URL(baseUrl);
   let proto: string;
   if (url.protocol === "https:") {
     proto = "taler";
   } else if (url.protocol === "http:") {
     proto = "taler+http";
   } else {
-    throw Error(`Unsupported exchange URL protocol ${args.exchangeBaseUrl}`);
+    throw Error(`Unsupported URL protocol in ${baseUrl}`);
   }
-  if (!url.pathname.endsWith("/")) {
-    throw Error(
-      `exchange base URL must end with a slash (got 
${args.exchangeBaseUrl}instead)`,
-    );
+  let path = url.hostname;
+  if (url.port) {
+    path = path + ":" + url.port;
   }
-  return `${proto}://pay-pull/${url.host}${url.pathname}${args.contractPriv}`;
-}
-
-export function constructRecoveryUri(args: BackupRecovery): string {
-  const key = args.walletRootPriv;
-  //FIXME: name may contain non valid characters
-  const urls = args.providers
-    .map((p) => `${p.name}=${canonicalizeBaseUrl(p.url)}`)
-    .join("&");
-
-  return `taler://recovery/${key}?${urls}`;
-}
-export function parseRecoveryUri(uri: string): BackupRecovery | undefined {
-  const pi = parseProtoInfo(uri, "recovery");
-  if (!pi) {
-    return undefined;
+  if (url.pathname) {
+    path = path + url.pathname;
   }
-  const idx = pi.rest.indexOf("?");
-  if (idx === -1) {
-    return undefined;
+  if (!path.endsWith("/")) {
+    path = path + "/";
   }
-  const path = pi.rest.slice(0, idx);
-  const params = pi.rest.slice(idx + 1);
-  if (!path || !params) {
-    return undefined;
-  }
-  const parts = path.split("/");
-  const walletRootPriv = parts[0];
-  if (!walletRootPriv) return undefined;
-  const providers = new Array<{ name: string; url: string }>();
-  const args = params.split("&");
-  for (const param in args) {
-    const eq = args[param].indexOf("=");
-    if (eq === -1) return undefined;
-    const name = args[param].slice(0, eq);
-    const url = args[param].slice(eq + 1);
-    providers.push({ name, url });
-  }
-  return { walletRootPriv, providers };
+
+  const qp = new URLSearchParams();
+  let withParams = false;
+  Object.entries(params).forEach(([name, value]) => {
+    if (value) {
+      withParams = true;
+      qp.append(name, value);
+    }
+  });
+  const query = withParams ? "?" + qp.toString() : "";
+
+  return { proto, path, query };
 }
diff --git a/packages/taler-util/src/transactions-types.ts 
b/packages/taler-util/src/transactions-types.ts
index d3b68b114..eec173de6 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -406,6 +406,11 @@ export interface TransactionPayment extends 
TransactionCommon {
    * Is the wallet currently checking for a refund?
    */
   refundQueryActive: boolean;
+
+  /**
+   * Does this purchase has an pos validation
+   */
+  posConfirmation: string | undefined;
 }
 
 export interface OrderShortInfo {
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index de84677ac..940251366 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -51,15 +51,15 @@ import {
   AmountString,
   AuditorDenomSig,
   codecForMerchantContractTerms,
+  codecForPeerContractTerms,
   CoinEnvelope,
-  MerchantContractTerms,
-  PeerContractTerms,
   DenominationPubKey,
   DenomKeyType,
   ExchangeAuditor,
-  UnblindedSignature,
-  codecForPeerContractTerms,
+  MerchantContractTerms,
+  PeerContractTerms,
   TrackTransaction,
+  UnblindedSignature,
 } from "./taler-types.js";
 import {
   AbsoluteTime,
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index 9ee157a2a..0ec2e31c8 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1239,6 +1239,8 @@ export interface PurchaseRecord {
 
   merchantPaySig: string | undefined;
 
+  posConfirmation: string | undefined;
+
   /**
    * When was the purchase record created?
    */
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts 
b/packages/taler-wallet-core/src/operations/backup/export.ts
index 3dd4e0e32..68f8beb93 100644
--- a/packages/taler-wallet-core/src/operations/backup/export.ts
+++ b/packages/taler-wallet-core/src/operations/backup/export.ts
@@ -468,6 +468,7 @@ export async function exportBackup(
           contract_terms_raw: contractTermsRaw,
           auto_refund_deadline: purch.autoRefundDeadline,
           merchant_pay_sig: purch.merchantPaySig,
+          pos_confirmation: purch.posConfirmation,
           pay_info: backupPayInfo,
           proposal_id: purch.proposalId,
           refunds,
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
index c1be1d4d9..32b90e126 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -691,6 +691,7 @@ export async function importBackup(
               backupPurchase.timestamp_first_successful_pay,
             timestampLastRefundStatus: undefined,
             merchantPaySig: backupPurchase.merchant_pay_sig,
+            posConfirmation: backupPurchase.pos_confirmation,
             lastSessionId: undefined,
             download,
             refunds,
diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts 
b/packages/taler-wallet-core/src/operations/pay-merchant.ts
index f8fa1d34d..496641fe7 100644
--- a/packages/taler-wallet-core/src/operations/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts
@@ -57,6 +57,7 @@ import {
   MerchantCoinRefundStatus,
   MerchantCoinRefundSuccessStatus,
   MerchantContractTerms,
+  MerchantPayResponse,
   NotificationType,
   parsePayUri,
   parseRefundUri,
@@ -605,6 +606,7 @@ async function startDownloadProposal(
     timestampFirstSuccessfulPay: undefined,
     timestampLastRefundStatus: undefined,
     pendingRemovedCoinPubs: undefined,
+    posConfirmation: undefined,
   };
 
   await ws.db
@@ -629,7 +631,7 @@ async function storeFirstPaySuccess(
   ws: InternalWalletState,
   proposalId: string,
   sessionId: string | undefined,
-  paySig: string,
+  payResponse: MerchantPayResponse,
 ): Promise<void> {
   const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
   await ws.db
@@ -651,7 +653,8 @@ async function storeFirstPaySuccess(
       }
       purchase.timestampFirstSuccessfulPay = now;
       purchase.lastSessionId = sessionId;
-      purchase.merchantPaySig = paySig;
+      purchase.merchantPaySig = payResponse.sig;
+      purchase.posConfirmation = payResponse.pos_confirmation;
       const dl = purchase.download;
       checkDbInvariant(!!dl);
       const contractTermsRecord = await tx.contractTerms.get(
@@ -1529,7 +1532,7 @@ export async function processPurchasePay(
       throw Error("merchant payment signature invalid");
     }
 
-    await storeFirstPaySuccess(ws, proposalId, sessionId, merchantResp.sig);
+    await storeFirstPaySuccess(ws, proposalId, sessionId, merchantResp);
     await unblockBackup(ws, proposalId);
   } else {
     const payAgainUrl = new URL(
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index c0045aced..34d76c5c2 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -1014,6 +1014,7 @@ async function buildTransactionForPurchase(
     extendedStatus: status,
     pending: purchaseRecord.purchaseStatus === PurchaseStatus.Paying,
     refunds,
+    posConfirmation: purchaseRecord.posConfirmation,
     timestamp,
     transactionId: makeTransactionId(
       TransactionType.Payment,
diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/state.ts 
b/packages/taler-wallet-webextension/src/cta/Recovery/state.ts
index 9731d3f69..fcf5e309f 100644
--- a/packages/taler-wallet-webextension/src/cta/Recovery/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/Recovery/state.ts
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { parseRecoveryUri } from "@gnu-taler/taler-util";
+import { parseRestoreUri } from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { useAlertContext } from "../../context/alert.js";
 import { useBackendContext } from "../../context/backend.js";
@@ -41,7 +41,7 @@ export function useComponentState({
       },
     };
   }
-  const info = parseRecoveryUri(talerRecoveryUri);
+  const info = parseRestoreUri(talerRecoveryUri);
 
   if (!info) {
     return {
diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx 
b/packages/taler-wallet-webextension/src/wallet/Application.tsx
index fc8ddb804..4a5ef30eb 100644
--- a/packages/taler-wallet-webextension/src/wallet/Application.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx
@@ -136,12 +136,13 @@ export function Application(): VNode {
 
             <Route
               path={Pages.balanceHistory.pattern}
-              component={() => (
+              component={({ currency }: { currency?: string }) => (
                 <WalletTemplate
                   path="balance"
                   goToTransaction={redirectToTxInfo}
                 >
                   <HistoryPage
+                    currency={currency}
                     goToWalletDeposit={(currency: string) =>
                       redirectTo(Pages.sendCash({ amount: `${currency}:0` }))
                     }
diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
index e7dcc18f4..5fc03e0a7 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
@@ -75,6 +75,7 @@ const exampleData = {
     ...commonTransaction(),
     amountEffective: "USD:11",
     type: TransactionType.Payment,
+    posConfirmation: undefined,
     info: {
       contractTermsHash: "ASDZXCASD",
       merchant: {
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx 
b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
index d65f3a095..3109e8947 100644
--- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
@@ -115,21 +115,6 @@ export function SettingsView({
   return (
     <Fragment>
       <section>
-        <SubTitle>
-          <i18n.Translate>Navigator</i18n.Translate>
-        </SubTitle>
-        <Checkbox
-          label={i18n.str`Automatically open wallet based on page content`}
-          name="autoOpen"
-          description={
-            <i18n.Translate>
-              Enabling this option below will make using the wallet faster, but
-              requires more permissions from your browser.
-            </i18n.Translate>
-          }
-          enabled={autoOpenToggle.value!}
-          onToggle={autoOpenToggle.button.onClick!}
-        />
         {/* <Checkbox
           label={
             i18n.str`Automatically check clipboard for Taler URI`
@@ -225,17 +210,6 @@ export function SettingsView({
           </LinkPrimary>
         </div>
 
-        <SubTitle>
-          <i18n.Translate>Troubleshooting</i18n.Translate>
-        </SubTitle>
-        <Checkbox
-          label={i18n.str`Developer mode`}
-          name="devMode"
-          description={i18n.str`More options and information useful for 
debugging`}
-          enabled={devModeToggle.value!}
-          onToggle={devModeToggle.button.onClick!}
-        />
-
         <JustInDevMode>
           <SubTitle>
             <i18n.Translate>Display</i18n.Translate>
@@ -289,6 +263,33 @@ export function SettingsView({
             />
           </JustInDevMode>
         )}
+        <SubTitle>
+          <i18n.Translate>Troubleshooting</i18n.Translate>
+        </SubTitle>
+        <Checkbox
+          label={i18n.str`Developer mode`}
+          name="devMode"
+          description={i18n.str`More options and information useful for 
debugging`}
+          enabled={devModeToggle.value!}
+          onToggle={devModeToggle.button.onClick!}
+        />
+        <JustInDevMode>
+          <SubTitle>
+            <i18n.Translate>Navigator</i18n.Translate>
+          </SubTitle>
+          <Checkbox
+            label={i18n.str`Automatically open wallet based on page content`}
+            name="autoOpen"
+            description={
+              <i18n.Translate>
+                Enabling this option below will make using the wallet faster,
+                but requires more permissions from your browser.
+              </i18n.Translate>
+            }
+            enabled={autoOpenToggle.value!}
+            onToggle={autoOpenToggle.button.onClick!}
+          />
+        </JustInDevMode>
       </section>
     </Fragment>
   );
diff --git 
a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
index b2922de22..d338b77f5 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
@@ -89,6 +89,7 @@ const exampleData = {
     ...commonTransaction,
     amountEffective: "KUDOS:12",
     type: TransactionType.Payment,
+    posConfirmation: undefined,
     info: {
       contractTermsHash: "ASDZXCASD",
       merchant: {
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index 58e50e7b6..d34e57f58 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -486,7 +486,7 @@ export function TransactionView({
               <InfoBox>
                 <div style={{ display: "block" }}>
                   <i18n.Translate>
-                    Wire transfer need a confirmation. Go to the
+                    Wire transfer need a confirmation. Go to the{" "}
                     <a
                       href={transaction.withdrawalDetails.bankConfirmationUrl}
                       target="_blank"
diff --git a/packages/web-util/src/live-reload.ts 
b/packages/web-util/src/live-reload.ts
index b65d472c1..8e5e7da71 100644
--- a/packages/web-util/src/live-reload.ts
+++ b/packages/web-util/src/live-reload.ts
@@ -2,7 +2,8 @@
 
 function setupLiveReload(): void {
   const protocol = window.location.protocol === "http:" ? "ws:" : "wss:";
-  const ws = new WebSocket(`${protocol}//localhost:8080/ws`);
+  const port = window.location.protocol === "http:" ? "8080" : "8081";
+  const ws = new WebSocket(`${protocol}//localhost:${port}/ws`);
 
   ws.addEventListener("message", (message) => {
     try {

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]