taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit f1daa2460300c3fe1c97ce32efb7b6410702482b
parent 28d808ba98fcb37fa5f041420219e894836aa559
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Wed,  5 Nov 2025 11:23:56 +0100

integrate new mailbox key directory API, some cleanups

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-wallet-contacts-basic.ts | 12+++++++-----
Mpackages/taler-util/src/http-client/mailbox.ts | 40++++++++++++++++++++++++++++++++++++----
Mpackages/taler-util/src/taleruri.test.ts | 15+++++++++++++++
Mpackages/taler-util/src/taleruri.ts | 46++++++++++++++++++++++++++++------------------
Mpackages/taler-util/src/taleruris.test.ts | 5+++--
Mpackages/taler-util/src/types-taler-mailbox.ts | 45+++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-util/src/types-taler-wallet.ts | 27++++++++++++++++++++++-----
Mpackages/taler-wallet-core/src/contacts.ts | 3++-
Mpackages/taler-wallet-core/src/db.ts | 8+++++++-
Mpackages/taler-wallet-core/src/mailbox.ts | 54+++++++++++++++++++++++++++++++++++++++++++++++++++---
Mpackages/taler-wallet-core/src/wallet-api-types.ts | 13+++++++++++++
Mpackages/taler-wallet-core/src/wallet.ts | 13++++++++++++-
Mpackages/taler-wallet-webextension/src/wallet/AddContact/views.tsx | 2+-
Mpackages/taler-wallet-webextension/src/wallet/Application.tsx | 3++-
Mpackages/taler-wallet-webextension/src/wallet/Contacts.tsx | 26+++++++++++++++++++++++---
Mpackages/taler-wallet-webextension/src/wallet/Mailbox.tsx | 2+-
16 files changed, 268 insertions(+), 46 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-wallet-contacts-basic.ts b/packages/taler-harness/src/integrationtests/test-wallet-contacts-basic.ts @@ -17,7 +17,7 @@ /** * Imports. */ -import { j2s } from "@gnu-taler/taler-util"; +import { ContactEntry, j2s } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { GlobalTestState } from "../harness/harness.js"; import { @@ -35,16 +35,18 @@ export async function runWalletContactsBasicTest(t: GlobalTestState) { const { commonDb, merchant, walletClient, bankClient, exchange } = await createSimpleTestkudosEnvironmentV3(t); - const contactBob = { + const contactBob: ContactEntry = { alias: "bobexample.com", aliasType: "email", - mailboxUri: "https://mailbox.example.com/BOBPKEY", + mailboxBaseUri: "https://mailbox.example.com", + mailboxAddress: "BOBPKEY", source: "test" }; - const contactAlice = { + const contactAlice: ContactEntry = { alias: "@alice", aliasType: "social", - mailboxUri: "https://mailbox.example.com/ALICEPKEY", + mailboxBaseUri: "https://mailbox.example.com", + mailboxAddress: "ALICEPKEY", source: "test" }; await walletClient.call(WalletApiOperation.AddContact, { diff --git a/packages/taler-util/src/http-client/mailbox.ts b/packages/taler-util/src/http-client/mailbox.ts @@ -33,6 +33,9 @@ import { opKnownHttpFailure, opUnknownHttpFailure, TalerMailboxConfigResponse, + MailboxMessageKeysResponse, + codecForTalerMailboxMessageKeysResponse, + opSuccessFromHttp, } from "@gnu-taler/taler-util"; import { HttpRequestLibrary, @@ -83,7 +86,7 @@ export class TalerMailboxInstanceHttpClient { */ async getConfig() : Promise< - | OperationOk<TalerMailboxApi.TalerMailboxConfigResponse> + | OperationOk<TalerMailboxApi.TalerMailboxConfigResponse> | OperationFail<HttpStatusCode.NotFound> >{ const url = new URL(`config`, this.baseUrl); @@ -149,14 +152,14 @@ export class TalerMailboxInstanceHttpClient { * https://docs.taler.net/core/api-mailbox.html#get--$H_MAILBOX */ async getMessages(args: { - h_mailbox: string; + hMailbox: string; }): Promise< | OperationOk<Uint8Array> | OperationOk<void> | OperationFail<HttpStatusCode.TooManyRequests> >{ - const { h_mailbox } = args; - const url = new URL(`${h_mailbox}`, this.baseUrl); + const { hMailbox: hMailbox } = args; + const url = new URL(`${hMailbox}`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "GET", @@ -210,4 +213,33 @@ export class TalerMailboxInstanceHttpClient { return opUnknownHttpFailure(resp); } } + + /** + * https://docs.taler.net/core/api-mailbox.html#key-directory + */ + async getKeys(hMailbox: string) : Promise< + | OperationOk<MailboxMessageKeysResponse> + | OperationFail<HttpStatusCode.NotFound> + | OperationFail<HttpStatusCode.TooManyRequests> + >{ + const url = new URL(`keys/${hMailbox}`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + cancellationToken: this.cancellationToken, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: { + return opSuccessFromHttp(resp, + codecForTalerMailboxMessageKeysResponse()); + } + case HttpStatusCode.TooManyRequests: { + return opKnownAlternativeHttpFailure(resp, resp.status, codecForTalerMailboxRateLimitedResponse()); + } + default: + return opUnknownHttpFailure(resp); + } + } + } diff --git a/packages/taler-util/src/taleruri.test.ts b/packages/taler-util/src/taleruri.test.ts @@ -29,6 +29,7 @@ import { parseRestoreUri, parseWithdrawExchangeUri, parseWithdrawUri, + stringifyAddContact, stringifyAddExchange, stringifyDevExperimentUri, stringifyPayPullUri, @@ -604,6 +605,20 @@ import { AmountString } from "./types-taler-common.js"; }); /** + * Add contact + */ + test("taler add contact URI (stringify)", (t) => { + const url = stringifyAddContact({ + alias: "bob@example.com", + aliasType: "email", + sourceBaseUrl: "https://taldir.example.com", + mailboxBaseUri: "https://mailbox.example.com/mb", + mailboxIdentity: "SOMEHASHOFPUBKEY", + }); + t.deepEqual(url, "taler://add-contact/email/bob@example.com/mailbox.example.com/mb/SOMEHASHOFPUBKEY?sourceBaseUrl=https%3A%2F%2Ftaldir.example.com"); + }); + + /** * wrong uris */ test("taler pay url parsing: wrong scheme", (t) => { diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts @@ -27,7 +27,7 @@ import { Codec, Context, DecodingError, renderContext } from "./codec.js"; import { HostPortPath, Paytos } from "./payto.js"; import { Result } from "./result.js"; import { TalerErrorCode } from "./taler-error-codes.js"; -import { AmountString } from "./types-taler-common.js"; +import { AmountString, EddsaPublicKeyString, HashCodeString } from "./types-taler-common.js"; import { URL, URLSearchParams } from "./url.js"; import { opFixedSuccess, @@ -238,13 +238,15 @@ export namespace TalerUris { alias: string, aliasType: string, mailboxUri: string, + mailboxIdentity: string, sourceBaseUrl: string, ): AddContactUri { return { type: TalerUriAction.AddContact, alias: alias, aliasType: aliasType, - mailboxUri: mailboxUri, + mailboxBaseUri: mailboxUri, + mailboxIdentity: mailboxIdentity, sourceBaseUrl: sourceBaseUrl, }; } @@ -371,7 +373,7 @@ export namespace TalerUris { case TalerUriAction.WithdrawalTransferResult: return `/`; case TalerUriAction.AddContact: - return `/${p.aliasType}/${p.alias}/${p.mailboxUri}` + return `/${p.aliasType}/${p.alias}/${asHost(p.mailboxBaseUri as HostPortPath)}/${p.mailboxIdentity}` default: assertUnreachable(p); } @@ -827,30 +829,31 @@ export namespace TalerUris { } case TalerUriAction.AddContact: { // check number of segments - if (cs.length !== 3) { + if (cs.length < 4) { return opKnownFailureWithBody(TalerUriParseError.COMPONENTS_LENGTH, { uriType, }); } - const exchange = Paytos.parseHostPortPath2( - cs[0], - cs.slice(1).join("/"), + const mailboxBaseUri = Paytos.parseHostPortPath2( + cs[2], + cs.slice(2, cs.length - 2).join("/"), scheme, ); - if (!opts.ignoreComponentError && !exchange) { + if (!mailboxBaseUri) { return opKnownFailureWithBody( TalerUriParseError.INVALID_TARGET_PATH, { pos: 0 as const, uriType, - error: exchange, + error: mailboxBaseUri, }, ); } + const mailboxIdentity = cs[cs.length - 1]; return opFixedSuccess<URI>( - createTalerAddContact(cs[0], cs[1], cs[2], params["sourceBaseUrl"]), + createTalerAddContact(cs[0], cs[1], mailboxBaseUri, mailboxIdentity, params["sourceBaseUrl"]), ); } default: { @@ -944,7 +947,8 @@ export interface AddContactUri { type: TalerUriAction.AddContact; alias: string; aliasType: string; - mailboxUri: string; + mailboxBaseUri: string; + mailboxIdentity: HashCodeString; sourceBaseUrl: string; } @@ -1051,17 +1055,21 @@ export function parseAddContactUriWithError(s: string) { } const parts = pi.value.rest.split("/"); - if (parts.length !== 3) { + if (parts.length < 4) { return Result.error(TalerErrorCode.WALLET_TALER_URI_MALFORMED); } - const q = new URLSearchParams(parts[2] ?? ""); + const mailboxBaseUri = parts[2]; + const pathSegments = parts.slice(3, parts.length - 2); + const lastPart = parts[parts.length-1]; + const q = new URLSearchParams(lastPart ?? ""); + const mailboxIdentity = lastPart.split("?")[0]; const sourceBaseUrl = q.get("sourceBaseUrl") ?? ""; - const result: AddContactUri = { type: TalerUriAction.AddContact, aliasType: parts[0], alias: parts[1], - mailboxUri: parts[2], + mailboxBaseUri: [mailboxBaseUri, ...pathSegments].join("/"), + mailboxIdentity: mailboxIdentity, sourceBaseUrl: sourceBaseUrl, }; return Result.of(result); @@ -1690,12 +1698,14 @@ export function stringifyAddExchange({ export function stringifyAddContact({ alias, aliasType, - mailboxUri, + mailboxBaseUri: mailboxBaseUri, + mailboxIdentity: mailboxIdentity, sourceBaseUrl, }: Omit<AddContactUri, "type">): string { - const baseUri = `taler://add-contact/${aliasType}/${alias}/${mailboxUri}`; + const { proto, path } = getUrlInfo(mailboxBaseUri); + const baseUri = `${proto}://add-contact/${aliasType}/${alias}/${path}${mailboxIdentity}`; if (sourceBaseUrl) { - return baseUri + `?sourceBaseUrl=${sourceBaseUrl}`; + return baseUri + `?sourceBaseUrl=${encodeURIComponent(sourceBaseUrl)}`; } else { return baseUri; } diff --git a/packages/taler-util/src/taleruris.test.ts b/packages/taler-util/src/taleruris.test.ts @@ -591,10 +591,11 @@ test("taler-new add contact URI (stringify)", (t) => { type: TalerUriAction.AddContact, alias: "bob@example.com", aliasType: "email", - mailboxUri: "https://mailbox.example.com/ABCDEFG", + mailboxBaseUri: "https://mailbox.example.com/mb", + mailboxIdentity: "SOMEHADDR", sourceBaseUrl: "https://taldir.example.com", }); - t.deepEqual(url, "taler://add-contact/email/bob@example.com/https://mailbox.example.com/ABCDEFG?sourceBaseUrl=https%3A%2F%2Ftaldir.example.com"); + t.deepEqual(url, "taler://add-contact/email/bob@example.com/mailbox.example.com/mb/SOMEHADDR?sourceBaseUrl=https%3A%2F%2Ftaldir.example.com"); }); /** diff --git a/packages/taler-util/src/types-taler-mailbox.ts b/packages/taler-util/src/types-taler-mailbox.ts @@ -28,13 +28,17 @@ import { } from "./index.js"; import { codecForDuration, + codecForTimestamp, } from "./time.js"; import { AmountString, + codecForEddsaPublicKey, + EddsaPublicKeyString, EddsaSignatureString, HashCodeString, Integer, RelativeTime, + Timestamp, } from "./types-taler-common.js"; export const codecForTalerMailboxConfigResponse = @@ -73,6 +77,47 @@ export interface TalerMailboxConfigResponse { delivery_period: RelativeTime; } +export const codecForTalerMailboxMessageKeysResponse = + (): Codec<MailboxMessageKeysResponse> => +buildCodecForObject<MailboxMessageKeysResponse>() + .property("signingKey", codecForEddsaPublicKey()) + .property("signingKeyType", codecForConstString("EdDSA")) + .property("encryptionKey", codecForString()) + .property("encryptionKeyType", codecForConstString("X25519")) + .property("expiration", codecForTimestamp) + .build("TalerMailboxApi.MailboxMessageKeysResponse"); + + +export interface MailboxMessageKeysResponse { + + // The mailbox signing key. + // Note that $H_MAILBOX == H(singingKey). + // Note also how this key cannot be updated + // as it identifies the mailbox. + signingKey: EddsaPublicKeyString; + + // Type of key. + // Optional, as currently only + // EdDSA keys are supported. + signingKeyType?: string; + + // The mailbox encryption key. + // This is an HPKE public key + // in the X25519 format for use + // in a X25519-DHKEM (RFC 9180). + // Base32 crockford-encoded. + encryptionKey: string; + + // Type of key. + // Optional, as currently only + // X25519 keys are supported. + encryptionKeyType?: string; + + // Expiration of this mapping. + expiration: Timestamp; +} + + export const codecForTalerMailboxRateLimitedResponse = (): Codec<MailboxRateLimitedResponse> => buildCodecForObject<MailboxRateLimitedResponse>() diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts @@ -71,6 +71,7 @@ import { EddsaPrivateKeyString, EddsaPublicKeyString, EddsaSignatureString, + HashCodeString, HashCode, Timestamp, codecForEddsaPrivateKey, @@ -1384,6 +1385,10 @@ export const codecForMailboxConfiguration = (): Codec<MailboxConfiguration> => .build("MailboxConfiguration"); +export interface SendTalerUriMailboxMessageRequest { + contact: ContactEntry; + talerUri: string; +} export interface AddMailboxMessageRequest { message: MailboxMessageRecord; @@ -1397,22 +1402,23 @@ export interface MailboxMessagesResponse { messages: MailboxMessageRecord[]; } -export const codecForContactListItem = (): Codec<ContactEntry> => +export const codecForContactEntry = (): Codec<ContactEntry> => buildCodecForObject<ContactEntry>() .property("alias", codecForString()) .property("aliasType", codecForString()) - .property("mailboxUri", codecForString()) + .property("mailboxBaseUri", codecForString()) + .property("mailboxAddress", codecForString()) .property("source", codecForString()) .build("ContactListItem"); export const codecForAddContactRequest = (): Codec<AddContactRequest> => buildCodecForObject<AddContactRequest>() - .property("contact", codecForContactListItem()) + .property("contact", codecForContactEntry()) .build("AddContactRequest"); export const codecForDeleteContactRequest = (): Codec<DeleteContactRequest> => buildCodecForObject<DeleteContactRequest>() - .property("contact", codecForContactListItem()) + .property("contact", codecForContactEntry()) .build("DeleteContactRequest"); export const codecForAddMailboxMessageRequest = (): Codec<AddMailboxMessageRequest> => @@ -1425,6 +1431,12 @@ export const codecForDeleteMailboxMessageRequest = (): Codec<DeleteMailboxMessag .property("message", codecForAny()) .build("DeleteContactRequest"); +export const codecForSendTalerUriMailboxMessageRequest = (): Codec<SendTalerUriMailboxMessageRequest> => + buildCodecForObject<SendTalerUriMailboxMessageRequest>() + .property("contact", codecForContactEntry()) + .property("talerUri", codecForString()) + .build("SendTalerUriMailboxMessageRequest"); + export interface WalletCoreVersion { @@ -1774,7 +1786,12 @@ export interface ContactEntry { /** * mailbox URI */ - mailboxUri: string, + mailboxBaseUri: string, + + /** + * mailbox identity + */ + mailboxAddress: HashCodeString, /** * The source of this contact diff --git a/packages/taler-wallet-core/src/contacts.ts b/packages/taler-wallet-core/src/contacts.ts @@ -58,7 +58,8 @@ async function makeContactListItem( const listItem: ContactEntry = { alias: r.alias, aliasType: r.aliasType, - mailboxUri: r.mailboxUri, + mailboxBaseUri: r.mailboxBaseUri, + mailboxAddress: r.mailboxAddress, source: r.source, }; //switch (listItem.exchangeUpdateStatus) { diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts @@ -2916,7 +2916,13 @@ export interface ContactRecord { /** * The mailbox URI of this contact */ - mailboxUri: string; + mailboxBaseUri: string; + + /** + * The mailbox identity + */ + mailboxAddress: HashCodeString; + /** * The alias of this contact */ diff --git a/packages/taler-wallet-core/src/mailbox.ts b/packages/taler-wallet-core/src/mailbox.ts @@ -27,7 +27,6 @@ import { MailboxMessagesResponse, Logger, NotificationType, - EddsaPrivateKey, createEddsaKeyPair, encodeCrock, MailboxConfiguration, @@ -44,6 +43,8 @@ import { stringToBytes, hpkeOpenOneshot, hpkeCreateSecretKey, + SendTalerUriMailboxMessageRequest, + hpkeSealOneshot, } from "@gnu-taler/taler-util"; import { WalletExecutionContext, @@ -171,7 +172,7 @@ export async function refreshMailbox( const mailboxClient = new TalerMailboxInstanceHttpClient(mailboxConf.mailboxBaseUrl, wex.http); const privKey = decodeCrock(mailboxConf.privateKey); const pubKey = eddsaGetPublic(privKey); - const h_address = encodeCrock(sha512(pubKey)); + const hAddress = encodeCrock(sha512(pubKey)); // Refresh message size var message_size; const resConf = await mailboxClient.getConfig(); @@ -182,7 +183,7 @@ export async function refreshMailbox( default: return []; } - const res = await mailboxClient.getMessages({h_mailbox: h_address}); + const res = await mailboxClient.getMessages({hMailbox: hAddress}); switch (res.case) { case "ok": // FIXME this only parses one message @@ -218,3 +219,50 @@ export async function refreshMailbox( return []; } } + +export async function sendTalerUriMessage( + wex: WalletExecutionContext, + req: SendTalerUriMailboxMessageRequest) : Promise<EmptyObject> { + // FIXME we need to differentiate base URI and account in the contact!! + const mailboxClient = new TalerMailboxInstanceHttpClient(req.contact.mailboxBaseUri, wex.http); + // Get message size + var message_size; + const resConf = await mailboxClient.getConfig(); + switch (resConf.case) { + case "ok": + message_size = resConf.body.message_body_bytes; + break; + default: + return {}; + } + const resKeys = await mailboxClient.getKeys(req.contact.mailboxAddress); + var keys; + switch (resKeys.case) { + case "ok": + keys = resKeys.body; + break; + default: + return {}; + } + const headerBuf = new ArrayBuffer(4); + const paddingLength = message_size - req.talerUri.length - headerBuf.byteLength; + const v = new DataView(headerBuf); + v.setUint16(0, 0); // FIXME message type, derive number from URI type! + const header = new Uint8Array(headerBuf); + const padding = new Uint8Array(paddingLength).fill(0); + const msg = new Uint8Array([...header, ...stringToBytes(req.talerUri), ...padding]); + const encryptedMessage = hpkeSealOneshot(decodeCrock(keys.encryptionKey), + stringToBytes("mailbox-message"), + header, + msg.slice(4)); + const resSend = await mailboxClient.sendMessage({ + h_address: req.contact.mailboxAddress, + body: encryptedMessage, + }); + switch (resSend.case) { + case "ok": + return {}; + default: + throw Error("Failed to send message"); + } +} diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -191,6 +191,7 @@ import { DeleteContactRequest, EddsaPrivateKey, MailboxConfiguration, + SendTalerUriMailboxMessageRequest, } from "@gnu-taler/taler-util"; import { AddBackupProviderRequest, @@ -253,6 +254,7 @@ export enum WalletApiOperation { GetMailboxMessages = "getMailboxMessage", AddMailboxMessage = "addMailboxMessage", DeleteMailboxMessage = "deleteMailboxMessage", + SendTalerUriMailboxMessage = "sendTalerUriMailboxMessage", RetryPendingNow = "retryPendingNow", AbortTransaction = "abortTransaction", FailTransaction = "failTransaction", @@ -518,6 +520,16 @@ export type AddMailboxMessageOp = { response: EmptyObject; }; +/** + * send message. + */ +export type SendTalerUriMailboxMessageOp = { + op: WalletApiOperation.SendTalerUriMailboxMessage; + request: SendTalerUriMailboxMessageRequest; + response: EmptyObject; +}; + + // group: Basic Wallet Information @@ -1701,6 +1713,7 @@ export type WalletOperations = { [WalletApiOperation.DeleteMailboxMessage]: DeleteMailboxMessageOp; [WalletApiOperation.AddMailboxMessage]: AddMailboxMessageOp; [WalletApiOperation.GetMailbox]: GetMailboxOp; + [WalletApiOperation.SendTalerUriMailboxMessage]: SendTalerUriMailboxMessageOp; }; export type WalletCoreRequestType< diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts @@ -222,6 +222,7 @@ import { codecForAddContactRequest, codecForDeleteContactRequest, codecForAddMailboxMessageRequest, + codecForSendTalerUriMailboxMessageRequest, codecForDeleteMailboxMessageRequest, codecForSetWalletDeviceIdRequest, codecForSharePaymentRequest, @@ -318,7 +319,13 @@ import { handleSetDonau, } from "./donau.js"; import { listContacts, addContact, deleteContact } from "./contacts.js"; -import { listMailboxMessages, addMailboxMessage, deleteMailboxMessage, initMailbox } from "./mailbox.js"; +import { + listMailboxMessages, + addMailboxMessage, + deleteMailboxMessage, + initMailbox, + sendTalerUriMessage +} from "./mailbox.js"; import { ReadyExchangeSummary, acceptExchangeTermsOfService, @@ -1927,6 +1934,10 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = { codec: codecForString(), handler: initMailbox, }, + [WalletApiOperation.SendTalerUriMailboxMessage]: { + codec: codecForSendTalerUriMailboxMessageRequest(), + handler: sendTalerUriMessage, + }, [WalletApiOperation.AddMailboxMessage]: { codec: codecForAddMailboxMessageRequest(), handler: addMailboxMessage, diff --git a/packages/taler-wallet-webextension/src/wallet/AddContact/views.tsx b/packages/taler-wallet-webextension/src/wallet/AddContact/views.tsx @@ -182,7 +182,7 @@ export function ConfirmAddContactView({ </div> <div> <i18n.Translate>Mailbox URI</i18n.Translate>: - {contact.mailboxUri} + {contact.mailboxBaseUri} </div> </section> diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -867,7 +867,8 @@ export function Application(): VNode { ? { alias: tUri.alias, aliasType: tUri.aliasType, - mailboxUri: tUri.mailboxUri, + mailboxBaseUri: tUri.mailboxBaseUri, + mailboxAddress: tUri.mailboxIdentity, source: tUri.sourceBaseUrl, } : undefined; diff --git a/packages/taler-wallet-webextension/src/wallet/Contacts.tsx b/packages/taler-wallet-webextension/src/wallet/Contacts.tsx @@ -67,7 +67,7 @@ export function ContactsPage({ const filteredContacts = contacts.filter(c => (!search) ? true : ( c.alias.toLowerCase().includes(search.toLowerCase()) || c.aliasType.toLowerCase().includes(search.toLowerCase()) || - c.mailboxUri.toLowerCase().includes(search.toLowerCase()) || + c.mailboxBaseUri.toLowerCase().includes(search.toLowerCase()) || c.source.toLowerCase().includes(search.toLowerCase())) ); const tx = tid? @@ -109,6 +109,21 @@ export function ContactsPage({ const onDeleteContact = async (c: ContactEntry) => { api.wallet.call(WalletApiOperation.DeleteContact, { contact: c }).then(); }; + const onSendMoneyTransferMessage = async (c: ContactEntry, t?: Transaction) => { + if (!t) { + return; + } + if (t.type != TransactionType.PeerPushDebit) { + return; + } + if (!t.talerUri) { + return; + } + api.wallet.call(WalletApiOperation.SendTalerUriMailboxMessage, { + contact: c, + talerUri: t.talerUri, + }).then(); + }; return ( <ContactsView search={{ @@ -121,6 +136,7 @@ export function ContactsPage({ transaction={state.response.transaction} onAddContact={onAddContact} onDeleteContact={onDeleteContact} + onSendMoneyTransferMessage={onSendMoneyTransferMessage} /> ); } @@ -129,6 +145,7 @@ interface ContactProps { contact: ContactEntry; transaction?: Transaction; onDeleteContact: (c: ContactEntry) => Promise<void>; + onSendMoneyTransferMessage: (c: ContactEntry, t?: Transaction) => Promise<void>; } @@ -145,7 +162,7 @@ function ContactLayout(props: ContactProps): VNode { <i18n.Translate>Source</i18n.Translate>: {props.contact.source} </SmallText> <SmallLightText style={{ marginTop: 5, marginBotton: 5 }}> - <i18n.Translate>Mailbox</i18n.Translate>: {props.contact.mailboxUri} + <i18n.Translate>Mailbox</i18n.Translate>: {props.contact.mailboxBaseUri} </SmallLightText> </p> {!props.transaction && ( @@ -154,7 +171,7 @@ function ContactLayout(props: ContactProps): VNode { </Button> )} {(props.transaction && (props.transaction.type == TransactionType.PeerPushDebit)) && ( - <Button variant="contained" color="warning"> + <Button variant="contained" color="warning" onClick={() => { return props.onSendMoneyTransferMessage(props.contact, props.transaction)}}> <i18n.Translate>Send Cash</i18n.Translate> </Button> )} @@ -173,12 +190,14 @@ export function ContactsView({ transaction, onAddContact, onDeleteContact, + onSendMoneyTransferMessage, }: { search: TextFieldHandler; contacts: ContactEntry[]; transaction?: Transaction; onAddContact: (c: ContactEntry) => Promise<void>; onDeleteContact: (c: ContactEntry) => Promise<void>; + onSendMoneyTransferMessage: (c: ContactEntry, t?: Transaction) => Promise<void>; }): VNode { const { i18n } = useTranslationContext(); return ( @@ -215,6 +234,7 @@ export function ContactsView({ contact={c} transaction={transaction} onDeleteContact={onDeleteContact} + onSendMoneyTransferMessage={onSendMoneyTransferMessage} /> </Grid> )) diff --git a/packages/taler-wallet-webextension/src/wallet/Mailbox.tsx b/packages/taler-wallet-webextension/src/wallet/Mailbox.tsx @@ -117,7 +117,7 @@ export function MailboxPage({ } const httpClient = new BrowserFetchHttpLib(); const mailboxClient = new TalerMailboxInstanceHttpClient(mailboxBaseUrl, httpClient); - const messagesResp = await mailboxClient.getMessages({h_mailbox: h_address}); + const messagesResp = await mailboxClient.getMessages({hMailbox: h_address}); if (messagesResp.type != "ok") { return; }