diff options
-rw-r--r-- | src/android/index.ts | 118 | ||||
-rw-r--r-- | src/headless/helpers.ts | 13 | ||||
-rw-r--r-- | src/wallet.ts | 38 |
3 files changed, 140 insertions, 29 deletions
diff --git a/src/android/index.ts b/src/android/index.ts index 6d072a839..7ff9b21d1 100644 --- a/src/android/index.ts +++ b/src/android/index.ts @@ -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); } diff --git a/src/headless/helpers.ts b/src/headless/helpers.ts index 7c4fa8777..9652c630f 100644 --- a/src/headless/helpers.ts +++ b/src/headless/helpers.ts @@ -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"); diff --git a/src/wallet.ts b/src/wallet.ts index 50f3ee7e0..b50f415d4 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -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) => { |