support code for NFC tunneling

This commit is contained in:
Florian Dold 2019-08-22 23:36:36 +02:00
parent d76bc2a03d
commit 0032ff9f36
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
3 changed files with 140 additions and 29 deletions

View File

@ -18,9 +18,103 @@
* Imports.
*/
import { Wallet } from "../wallet";
import { getDefaultNodeWallet, withdrawTestBalance, DefaultNodeWalletArgs } from "../headless/helpers";
import { openPromise } from "../promiseUtils";
import {
getDefaultNodeWallet,
withdrawTestBalance,
DefaultNodeWalletArgs,
NodeHttpLib,
} from "../headless/helpers";
import { openPromise, OpenedPromise } from "../promiseUtils";
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() {
// @ts-ignore
@ -33,6 +127,7 @@ export function installAndroidWalletListener() {
}
let maybeWallet: Wallet | undefined;
let wp = openPromise<Wallet>();
let httpLib = new AndroidHttpLib(sendMessage);
let walletArgs: DefaultNodeWalletArgs | undefined;
const onMessage = async (msgStr: any) => {
if (typeof msgStr !== "string") {
@ -55,7 +150,8 @@ export function installAndroidWalletListener() {
notifyHandler: async () => {
sendMessage(JSON.stringify({ type: "notification" }));
},
persistentStoragePath: msg.args.persistentStoragePath,
persistentStoragePath: msg.args.persistentStoragePath,
httpLib: httpLib,
};
maybeWallet = await getDefaultNodeWallet(walletArgs);
wp.resolve(maybeWallet);
@ -82,13 +178,25 @@ export function installAndroidWalletListener() {
result = await wallet.confirmPay(msg.args.proposalId, undefined);
break;
}
case "startTunnel": {
httpLib.useNfcTunnel = true;
break;
}
case "stopTunnel": {
httpLib.useNfcTunnel = false;
break;
}
case "tunnelResponse": {
httpLib.handleTunnelResponse(msg.args);
break;
}
case "reset": {
const wallet = await wp.promise;
wallet.stop()
wallet.stop();
wp = openPromise<Wallet>();
if (walletArgs && walletArgs.persistentStoragePath) {
try {
fs.unlinkSync(walletArgs.persistentStoragePath)
fs.unlinkSync(walletArgs.persistentStoragePath);
} catch (e) {
console.error("Error while deleting the wallet db:", e);
}

View File

@ -120,6 +120,12 @@ export interface DefaultNodeWalletArgs {
* Handler for asynchronous notifications from the wallet.
*/
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 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 = () => {
console.error("version change requested, should not happen");

View File

@ -45,8 +45,6 @@ import * as Amounts from "./amounts";
import URI = require("urijs");
import axios from "axios";
import {
CoinRecord,
CoinStatus,
@ -107,6 +105,7 @@ import {
PreparePayResult,
} from "./walletTypes";
import { openPromise } from "./promiseUtils";
import Axios from "axios";
interface SpeculativePayData {
payCoinInfo: PayCoinInfo;
@ -787,13 +786,13 @@ export class Wallet {
console.log("downloading contract from '" + urlWithNonce + "'");
let resp;
try {
resp = await axios.get(urlWithNonce, { validateStatus: s => s === 200 });
resp = await this.http.get(urlWithNonce);
} catch (e) {
console.log("contract download failed", e);
throw e;
}
const proposal = Proposal.checked(resp.data);
const proposal = Proposal.checked(resp.responseJson);
const contractTermsHash = await this.hashContract(proposal.contract_terms);
@ -853,18 +852,13 @@ export class Wallet {
const payReq = { ...purchase.payReq, session_id: sessionId };
try {
const config = {
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);
resp = await this.http.postJson(purchase.contractTerms.pay_url, payReq)
} catch (e) {
// Gives the user the option to retry / abort and refresh
console.log("payment failed", e);
throw e;
}
const merchantResp = resp.data;
const merchantResp = resp.responseJson;
console.log("got success from pay_url");
const merchantPub = purchase.contractTerms.merchant_pub;
@ -2541,6 +2535,10 @@ export class Wallet {
// 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()
.iter<ProposalDownloadRecord>(Stores.proposals)
.toArray();
@ -3041,16 +3039,13 @@ export class Wallet {
console.log("processing refund");
let resp;
try {
const config = {
validateStatus: (s: number) => s === 200,
};
resp = await axios.get(refundUrl, config);
resp = await this.http.get(refundUrl);
} catch (e) {
console.log("error downloading refund permission", e);
throw e;
}
const refundResponse = MerchantRefundResponse.checked(resp.data);
const refundResponse = MerchantRefundResponse.checked(resp.responseJson);
return this.acceptRefundResponse(refundResponse);
}
@ -3260,17 +3255,14 @@ export class Wallet {
}));
try {
const config = {
validateStatus: (s: number) => s === 200,
};
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) {
console.log("tipping failed", e);
throw e;
}
const response = TipResponse.checked(merchantResp.data);
const response = TipResponse.checked(merchantResp.responseJson);
if (response.reserve_sigs.length !== tipRecord.planchets.length) {
throw Error("number of tip responses does not match requested planchets");
@ -3389,14 +3381,14 @@ export class Wallet {
timeout: 5000 /* 5 seconds */,
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) {
// Gives the user the option to retry / abort and refresh
console.log("aborting payment failed", e);
throw e;
}
const refundResponse = MerchantRefundResponse.checked(resp.data);
const refundResponse = MerchantRefundResponse.checked(resp.responseJson);
await this.acceptRefundResponse(refundResponse);
const markAbortDone = (p: PurchaseRecord) => {