taleruri sync with the spec
This commit is contained in:
parent
ce53dd8b85
commit
defdfd7697
@ -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;
|
||||
}
|
||||
if (url.pathname) {
|
||||
path = path + url.pathname;
|
||||
}
|
||||
if (!path.endsWith("/")) {
|
||||
path = path + "/";
|
||||
}
|
||||
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("&");
|
||||
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 `taler://recovery/${key}?${urls}`;
|
||||
}
|
||||
export function parseRecoveryUri(uri: string): BackupRecovery | undefined {
|
||||
const pi = parseProtoInfo(uri, "recovery");
|
||||
if (!pi) {
|
||||
return undefined;
|
||||
}
|
||||
const idx = pi.rest.indexOf("?");
|
||||
if (idx === -1) {
|
||||
return undefined;
|
||||
}
|
||||
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 };
|
||||
return { proto, path, query };
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user