support code for NFC tunneling
This commit is contained in:
parent
d76bc2a03d
commit
0032ff9f36
@ -18,9 +18,103 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { Wallet } from "../wallet";
|
import { Wallet } from "../wallet";
|
||||||
import { getDefaultNodeWallet, withdrawTestBalance, DefaultNodeWalletArgs } from "../headless/helpers";
|
import {
|
||||||
import { openPromise } from "../promiseUtils";
|
getDefaultNodeWallet,
|
||||||
|
withdrawTestBalance,
|
||||||
|
DefaultNodeWalletArgs,
|
||||||
|
NodeHttpLib,
|
||||||
|
} from "../headless/helpers";
|
||||||
|
import { openPromise, OpenedPromise } from "../promiseUtils";
|
||||||
import fs = require("fs");
|
import fs = require("fs");
|
||||||
|
import axios from "axios";
|
||||||
|
import { HttpRequestLibrary, HttpResponse } from "../http";
|
||||||
|
import querystring = require("querystring");
|
||||||
|
|
||||||
|
export class AndroidHttpLib implements HttpRequestLibrary {
|
||||||
|
useNfcTunnel: boolean = false;
|
||||||
|
|
||||||
|
private nodeHttpLib: HttpRequestLibrary = new NodeHttpLib();
|
||||||
|
|
||||||
|
private requestId = 1;
|
||||||
|
|
||||||
|
private requestMap: { [id: number]: OpenedPromise<HttpResponse> } = {};
|
||||||
|
|
||||||
|
constructor(private sendMessage: (m: string) => void) {}
|
||||||
|
|
||||||
|
get(url: string): Promise<HttpResponse> {
|
||||||
|
if (this.useNfcTunnel) {
|
||||||
|
const myId = this.requestId++;
|
||||||
|
const p = openPromise<HttpResponse>();
|
||||||
|
this.requestMap[myId] = p;
|
||||||
|
const request = {
|
||||||
|
method: "get",
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
this.sendMessage(
|
||||||
|
JSON.stringify({
|
||||||
|
type: "tunnelHttp",
|
||||||
|
request,
|
||||||
|
id: myId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return p.promise;
|
||||||
|
} else {
|
||||||
|
return this.nodeHttpLib.get(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
postJson(url: string, body: any): Promise<import("../http").HttpResponse> {
|
||||||
|
if (this.useNfcTunnel) {
|
||||||
|
const myId = this.requestId++;
|
||||||
|
const p = openPromise<HttpResponse>();
|
||||||
|
this.requestMap[myId] = p;
|
||||||
|
const request = {
|
||||||
|
method: "postJson",
|
||||||
|
url,
|
||||||
|
body,
|
||||||
|
};
|
||||||
|
this.sendMessage(
|
||||||
|
JSON.stringify({ type: "tunnelHttp", request, id: myId }),
|
||||||
|
);
|
||||||
|
return p.promise;
|
||||||
|
} else {
|
||||||
|
return this.nodeHttpLib.postJson(url, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
postForm(url: string, form: any): Promise<import("../http").HttpResponse> {
|
||||||
|
if (this.useNfcTunnel) {
|
||||||
|
const myId = this.requestId++;
|
||||||
|
const p = openPromise<HttpResponse>();
|
||||||
|
this.requestMap[myId] = p;
|
||||||
|
const request = {
|
||||||
|
method: "postForm",
|
||||||
|
url,
|
||||||
|
form,
|
||||||
|
};
|
||||||
|
this.sendMessage(
|
||||||
|
JSON.stringify({ type: "tunnelHttp", request, id: myId }),
|
||||||
|
);
|
||||||
|
return p.promise;
|
||||||
|
} else {
|
||||||
|
return this.nodeHttpLib.postForm(url, form);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTunnelResponse(msg: any) {
|
||||||
|
const myId = msg.id;
|
||||||
|
const p = this.requestMap[myId];
|
||||||
|
if (!p) {
|
||||||
|
console.error(`no matching request for tunneled HTTP response, id=${myId}`);
|
||||||
|
}
|
||||||
|
if (msg.status == 200) {
|
||||||
|
p.resolve({ responseJson: msg.responseJson, status: msg.status });
|
||||||
|
} else {
|
||||||
|
p.reject(new Error(`unexpected HTTP status code ${msg.status}`));
|
||||||
|
}
|
||||||
|
delete this.requestMap[myId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function installAndroidWalletListener() {
|
export function installAndroidWalletListener() {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -33,6 +127,7 @@ export function installAndroidWalletListener() {
|
|||||||
}
|
}
|
||||||
let maybeWallet: Wallet | undefined;
|
let maybeWallet: Wallet | undefined;
|
||||||
let wp = openPromise<Wallet>();
|
let wp = openPromise<Wallet>();
|
||||||
|
let httpLib = new AndroidHttpLib(sendMessage);
|
||||||
let walletArgs: DefaultNodeWalletArgs | undefined;
|
let walletArgs: DefaultNodeWalletArgs | undefined;
|
||||||
const onMessage = async (msgStr: any) => {
|
const onMessage = async (msgStr: any) => {
|
||||||
if (typeof msgStr !== "string") {
|
if (typeof msgStr !== "string") {
|
||||||
@ -56,6 +151,7 @@ export function installAndroidWalletListener() {
|
|||||||
sendMessage(JSON.stringify({ type: "notification" }));
|
sendMessage(JSON.stringify({ type: "notification" }));
|
||||||
},
|
},
|
||||||
persistentStoragePath: msg.args.persistentStoragePath,
|
persistentStoragePath: msg.args.persistentStoragePath,
|
||||||
|
httpLib: httpLib,
|
||||||
};
|
};
|
||||||
maybeWallet = await getDefaultNodeWallet(walletArgs);
|
maybeWallet = await getDefaultNodeWallet(walletArgs);
|
||||||
wp.resolve(maybeWallet);
|
wp.resolve(maybeWallet);
|
||||||
@ -82,13 +178,25 @@ export function installAndroidWalletListener() {
|
|||||||
result = await wallet.confirmPay(msg.args.proposalId, undefined);
|
result = await wallet.confirmPay(msg.args.proposalId, undefined);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "startTunnel": {
|
||||||
|
httpLib.useNfcTunnel = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "stopTunnel": {
|
||||||
|
httpLib.useNfcTunnel = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "tunnelResponse": {
|
||||||
|
httpLib.handleTunnelResponse(msg.args);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "reset": {
|
case "reset": {
|
||||||
const wallet = await wp.promise;
|
const wallet = await wp.promise;
|
||||||
wallet.stop()
|
wallet.stop();
|
||||||
wp = openPromise<Wallet>();
|
wp = openPromise<Wallet>();
|
||||||
if (walletArgs && walletArgs.persistentStoragePath) {
|
if (walletArgs && walletArgs.persistentStoragePath) {
|
||||||
try {
|
try {
|
||||||
fs.unlinkSync(walletArgs.persistentStoragePath)
|
fs.unlinkSync(walletArgs.persistentStoragePath);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error while deleting the wallet db:", e);
|
console.error("Error while deleting the wallet db:", e);
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,12 @@ export interface DefaultNodeWalletArgs {
|
|||||||
* Handler for asynchronous notifications from the wallet.
|
* Handler for asynchronous notifications from the wallet.
|
||||||
*/
|
*/
|
||||||
notifyHandler?: (reason: string) => void;
|
notifyHandler?: (reason: string) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If specified, use this as HTTP request library instead
|
||||||
|
* of the default one.
|
||||||
|
*/
|
||||||
|
httpLib?: HttpRequestLibrary;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -169,7 +175,12 @@ export async function getDefaultNodeWallet(
|
|||||||
const myBridgeIdbFactory = new BridgeIDBFactory(myBackend);
|
const myBridgeIdbFactory = new BridgeIDBFactory(myBackend);
|
||||||
const myIdbFactory: IDBFactory = (myBridgeIdbFactory as any) as IDBFactory;
|
const myIdbFactory: IDBFactory = (myBridgeIdbFactory as any) as IDBFactory;
|
||||||
|
|
||||||
const myHttpLib = new NodeHttpLib();
|
let myHttpLib;
|
||||||
|
if (args.httpLib) {
|
||||||
|
myHttpLib = args.httpLib;
|
||||||
|
} else {
|
||||||
|
myHttpLib = new NodeHttpLib();
|
||||||
|
}
|
||||||
|
|
||||||
const myVersionChange = () => {
|
const myVersionChange = () => {
|
||||||
console.error("version change requested, should not happen");
|
console.error("version change requested, should not happen");
|
||||||
|
@ -45,8 +45,6 @@ import * as Amounts from "./amounts";
|
|||||||
|
|
||||||
import URI = require("urijs");
|
import URI = require("urijs");
|
||||||
|
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CoinRecord,
|
CoinRecord,
|
||||||
CoinStatus,
|
CoinStatus,
|
||||||
@ -107,6 +105,7 @@ import {
|
|||||||
PreparePayResult,
|
PreparePayResult,
|
||||||
} from "./walletTypes";
|
} from "./walletTypes";
|
||||||
import { openPromise } from "./promiseUtils";
|
import { openPromise } from "./promiseUtils";
|
||||||
|
import Axios from "axios";
|
||||||
|
|
||||||
interface SpeculativePayData {
|
interface SpeculativePayData {
|
||||||
payCoinInfo: PayCoinInfo;
|
payCoinInfo: PayCoinInfo;
|
||||||
@ -787,13 +786,13 @@ export class Wallet {
|
|||||||
console.log("downloading contract from '" + urlWithNonce + "'");
|
console.log("downloading contract from '" + urlWithNonce + "'");
|
||||||
let resp;
|
let resp;
|
||||||
try {
|
try {
|
||||||
resp = await axios.get(urlWithNonce, { validateStatus: s => s === 200 });
|
resp = await this.http.get(urlWithNonce);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("contract download failed", e);
|
console.log("contract download failed", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
const proposal = Proposal.checked(resp.data);
|
const proposal = Proposal.checked(resp.responseJson);
|
||||||
|
|
||||||
const contractTermsHash = await this.hashContract(proposal.contract_terms);
|
const contractTermsHash = await this.hashContract(proposal.contract_terms);
|
||||||
|
|
||||||
@ -853,18 +852,13 @@ export class Wallet {
|
|||||||
const payReq = { ...purchase.payReq, session_id: sessionId };
|
const payReq = { ...purchase.payReq, session_id: sessionId };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const config = {
|
resp = await this.http.postJson(purchase.contractTerms.pay_url, payReq)
|
||||||
headers: { "Content-Type": "application/json;charset=UTF-8" },
|
|
||||||
timeout: 5000 /* 5 seconds */,
|
|
||||||
validateStatus: (s: number) => s === 200,
|
|
||||||
};
|
|
||||||
resp = await axios.post(purchase.contractTerms.pay_url, payReq, config);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Gives the user the option to retry / abort and refresh
|
// Gives the user the option to retry / abort and refresh
|
||||||
console.log("payment failed", e);
|
console.log("payment failed", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
const merchantResp = resp.data;
|
const merchantResp = resp.responseJson;
|
||||||
console.log("got success from pay_url");
|
console.log("got success from pay_url");
|
||||||
|
|
||||||
const merchantPub = purchase.contractTerms.merchant_pub;
|
const merchantPub = purchase.contractTerms.merchant_pub;
|
||||||
@ -2541,6 +2535,10 @@ export class Wallet {
|
|||||||
|
|
||||||
// FIXME: do pagination instead of generating the full history
|
// FIXME: do pagination instead of generating the full history
|
||||||
|
|
||||||
|
// We uniquely identify history rows via their timestamp.
|
||||||
|
// This works as timestamps are guaranteed to be monotonically
|
||||||
|
// increasing even
|
||||||
|
|
||||||
const proposals = await this.q()
|
const proposals = await this.q()
|
||||||
.iter<ProposalDownloadRecord>(Stores.proposals)
|
.iter<ProposalDownloadRecord>(Stores.proposals)
|
||||||
.toArray();
|
.toArray();
|
||||||
@ -3041,16 +3039,13 @@ export class Wallet {
|
|||||||
console.log("processing refund");
|
console.log("processing refund");
|
||||||
let resp;
|
let resp;
|
||||||
try {
|
try {
|
||||||
const config = {
|
resp = await this.http.get(refundUrl);
|
||||||
validateStatus: (s: number) => s === 200,
|
|
||||||
};
|
|
||||||
resp = await axios.get(refundUrl, config);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("error downloading refund permission", e);
|
console.log("error downloading refund permission", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
const refundResponse = MerchantRefundResponse.checked(resp.data);
|
const refundResponse = MerchantRefundResponse.checked(resp.responseJson);
|
||||||
return this.acceptRefundResponse(refundResponse);
|
return this.acceptRefundResponse(refundResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3260,17 +3255,14 @@ export class Wallet {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const config = {
|
|
||||||
validateStatus: (s: number) => s === 200,
|
|
||||||
};
|
|
||||||
const req = { planchets: planchetsDetail, tip_id: tipToken.tip_id };
|
const req = { planchets: planchetsDetail, tip_id: tipToken.tip_id };
|
||||||
merchantResp = await axios.post(tipToken.pickup_url, req, config);
|
merchantResp = await this.http.postJson(tipToken.pickup_url, req);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("tipping failed", e);
|
console.log("tipping failed", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = TipResponse.checked(merchantResp.data);
|
const response = TipResponse.checked(merchantResp.responseJson);
|
||||||
|
|
||||||
if (response.reserve_sigs.length !== tipRecord.planchets.length) {
|
if (response.reserve_sigs.length !== tipRecord.planchets.length) {
|
||||||
throw Error("number of tip responses does not match requested planchets");
|
throw Error("number of tip responses does not match requested planchets");
|
||||||
@ -3389,14 +3381,14 @@ export class Wallet {
|
|||||||
timeout: 5000 /* 5 seconds */,
|
timeout: 5000 /* 5 seconds */,
|
||||||
validateStatus: (s: number) => s === 200,
|
validateStatus: (s: number) => s === 200,
|
||||||
};
|
};
|
||||||
resp = await axios.post(purchase.contractTerms.pay_url, abortReq, config);
|
resp = await this.http.postJson(purchase.contractTerms.pay_url, abortReq);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Gives the user the option to retry / abort and refresh
|
// Gives the user the option to retry / abort and refresh
|
||||||
console.log("aborting payment failed", e);
|
console.log("aborting payment failed", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
const refundResponse = MerchantRefundResponse.checked(resp.data);
|
const refundResponse = MerchantRefundResponse.checked(resp.responseJson);
|
||||||
await this.acceptRefundResponse(refundResponse);
|
await this.acceptRefundResponse(refundResponse);
|
||||||
|
|
||||||
const markAbortDone = (p: PurchaseRecord) => {
|
const markAbortDone = (p: PurchaseRecord) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user