Merge pull request #16 from 0xAyanami/develop

EIP-1559 & Tor Socks5 Proxy & BSC network support
This commit is contained in:
Roman Storm 2022-01-20 14:12:13 -08:00 committed by GitHub
commit 4b13d53cab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 578912 additions and 785 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
node_modules
.env
.idea
backup-tornado-*

500
README.md
View File

@ -1,13 +1,13 @@
# Warning!
Current cli version doesn't support [Anonymity Mining](https://tornado-cash.medium.com/tornado-cash-governance-proposal-a55c5c7d0703)
### Goerli, Mainnet
### Goerli, Mainnet, Binance Smart Chain
1. Add `PRIVATE_KEY` to `.env` file
2. `./cli.js --help`
Example:
```bash
$ ./cli.js deposit ETH 0.1 --rpc https://goerli.infura.io/v3/27a9649f826b4e31a83e07ae09a87448
$ ./cli.js deposit ETH 0.1 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --tor 9050
Your note: tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652
Tornado ETH balance is 8.9
@ -18,7 +18,7 @@ Sender account ETH balance is 1004873.361652048361352542
```
```bash
$ ./cli.js withdraw tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://goerli.infura.io/v3/27a9649f826b4e31a83e07ae09a87448 --relayer https://goerli-frelay.duckdns.org
$ ./cli.js withdraw tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --relayer https://goerli-frelay.duckdns.org --tor 9050
Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754
Getting current state from tornado contract
@ -29,3 +29,497 @@ Transaction submitted through the relay. View transaction on etherscan https://g
Transaction mined in block 17036120
Done
```
### List of public rpc & relayers for withdrawal
Infura API key fetched from https://rpc.info (Same one with Metamask)
```json
{
"netId1":{
"rpcUrls":{
"Infura":{
"name":"Infura",
"url":"https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161"
},
"MyEtherWallet":{
"name":"MyEtherWallet",
"url":"https://nodes.mewapi.io/rpc/eth"
},
"MyCrypto":{
"name":"MyCrypto",
"url":"https://api.mycryptoapi.com/eth"
},
"CloudFlare":{
"name":"CloudFlare",
"url":"https://cloudflare-eth.com"
}
},
"relayers":{
"mainnet-v2.poanet.eth":{
"url":"mainnet-v2.poanet.eth",
"name":"mainnet-v2.poanet.eth",
"cachedUrl":"https://tornado-mainnet-v2.poa.network/"
},
"mainnet-v2.relaymy.eth":{
"url":"mainnet-v2.relaymy.eth",
"name":"mainnet-v2.relaymy.eth",
"cachedUrl":"https://mainnet-v2.relaymy.xyz/"
},
"mainnet-v2.gaasservices.eth":{
"url":"mainnet-v2.gaasservices.eth",
"name":"mainnet-v2.gaasservices.eth",
"cachedUrl":"https://mainnet-v2.gaas.services/"
},
"mainnet-v2.reasoned.eth":{
"url":"mainnet-v2.reasoned.eth",
"name":"mainnet-v2.reasoned.eth",
"cachedUrl":"https://mainnet-v2.solarsis.net/"
},
"v2.mainnet.thewizardseye.eth":{
"url":"v2.mainnet.thewizardseye.eth",
"name":"v2.mainnet.thewizardseye.eth",
"cachedUrl":"https://v2.mainnet.thewizardseye.de/"
},
"v2.odanrot.eth":{
"url":"v2.odanrot.eth",
"name":"v2.odanrot.eth",
"cachedUrl":"https://tcrv2.avado.cloud/"
},
"mainnet-v2.releth.eth":{
"url":"mainnet-v2.releth.eth",
"name":"mainnet-v2.releth.eth",
"cachedUrl":"https://mainnet-v2.reloch.net/"
},
"mainnet.t-relay.eth":{
"url":"mainnet.t-relay.eth",
"name":"mainnet.t-relay.eth",
"cachedUrl":"https://mainnet.t-relay.online/"
},
"mainnet-v2.therelayer.eth":{
"url":"mainnet-v2.therelayer.eth",
"name":"mainnet-v2.therelayer.eth",
"cachedUrl":"https://mainnet-v2.therelayer.xyz/"
},
"mainnet.relayer-service.eth":{
"url":"mainnet.relayer-service.eth",
"name":"mainnet.relayer-service.eth",
"cachedUrl":"https://mainnet-relayer.hertz.zone/"
},
"mainnet-v2.tornadosolutions.eth":{
"url":"mainnet-v2.tornadosolutions.eth",
"name":"mainnet-v2.tornadosolutions.eth",
"cachedUrl":"https://mainnet-v2.tornado.solutions/"
},
"mainnet-v2.torn.eth":{
"url":"mainnet-v2.torn.eth",
"name":"mainnet-v2.torn.eth",
"cachedUrl":"https://mainnet-v2.torn.cash/"
},
"mainnet-v2.defidevotee.eth":{
"url":"mainnet-v2.defidevotee.eth",
"name":"mainnet-v2.defidevotee.eth",
"cachedUrl":"https://mainnet-v2.defidevotee.xyz/"
}
}
},
"netId56":{
"rpcUrls":{
"publicRpc1":{
"name":"BSC Public RPC 1",
"url":"https://bsc-dataseed.binance.org/"
},
"publicRpc2":{
"name":"BSC Public RPC 2",
"url":"https://bsc-dataseed1.defibit.io/"
},
"publicRpc3":{
"name":"BSC Public RPC 3",
"url":"https://bsc-dataseed1.ninicoin.io/"
},
"MyEtherWallet":{
"name":"MyEtherWallet",
"url":"https://nodes.mewapi.io/rpc/bsc/"
}
},
"relayers":{
"bsc.relayer-service.eth":{
"url":"bsc.relayer-service.eth",
"name":"bsc.relayer-service.eth",
"cachedUrl":"https://bsc-relayer.hertz.zone/"
},
"bsc.t-relay.eth":{
"url":"bsc.t-relay.eth",
"name":"bsc.t-relay.eth",
"cachedUrl":"https://bsc.t-relay.online/"
},
"bsc.therelayer.eth":{
"url":"bsc.therelayer.eth",
"name":"bsc.therelayer.eth",
"cachedUrl":"https://bsc.therelayer.xyz/"
},
"bsc-v2.defidevotee.eth":{
"url":"bsc-v2.defidevotee.eth",
"name":"bsc-v2.defidevotee.eth",
"cachedUrl":"https://bsc-v2.defidevotee.xyz/"
},
"v1.bsc.thewizardseye.eth":{
"url":"v1.bsc.thewizardseye.eth",
"name":"v1.bsc.thewizardseye.eth",
"cachedUrl":"https://v1.bsc.thewizardseye.de/"
},
"bsc.relaymy.eth":{
"url":"bsc.relaymy.eth",
"name":"bsc.relaymy.eth",
"cachedUrl":"https://bsc.relaymy.xyz/"
},
"bsc.torn.eth":{
"url":"bsc.torn.eth",
"name":"bsc.torn.eth",
"cachedUrl":"https://bsc.torn.cash/"
},
"bsc.gaasservices.eth":{
"url":"bsc.gaasservices.eth",
"name":"bsc.gaasservices.eth",
"cachedUrl":"https://bsc.gaas.services/"
},
"bsc.tornadosolutions.eth":{
"url":"bsc.tornadosolutions.eth",
"name":"bsc.tornadosolutions.eth",
"cachedUrl":"https://bsc.tornado.solutions/"
},
"bsc.odanrot.eth":{
"url":"bsc.odanrot.eth",
"name":"bsc.odanrot.eth",
"cachedUrl":"https://tcrbsc.avado.cloud/"
},
"bsc.releth.eth":{
"url":"bsc.releth.eth",
"name":"bsc.releth.eth",
"cachedUrl":"https://bsc.reloch.net/"
}
}
},
"netId100":{
"rpcUrls":{
"publicRpc":{
"name":"xDAI Chain RPC",
"url":"https://rpc.xdaichain.com"
},
"publicRpc2":{
"name":"xDAI Chain RPC2",
"url":"https://xdai.poanetwork.dev"
},
"publicRpc3":{
"name":"xDAI Chain RPC3",
"url":"https://dai.poa.network/"
}
},
"relayers":{
"xdai.relayer-service.eth":{
"url":"xdai.relayer-service.eth",
"name":"xdai.relayer-service.eth",
"cachedUrl":"https://xdai-relayer.hertz.zone/"
},
"xdai.releth.eth":{
"url":"xdai.releth.eth",
"name":"xdai.releth.eth",
"cachedUrl":"https://xdai.reloch.net/"
},
"xdai.relaymy.eth":{
"url":"xdai.relaymy.eth",
"name":"xdai.relaymy.eth",
"cachedUrl":"https://xdai.relaymy.xyz/"
},
"xdai.torn.eth":{
"url":"xdai.torn.eth",
"name":"xdai.torn.eth",
"cachedUrl":"https://xdai.torn.cash/"
},
"xdai.t-relay.eth":{
"url":"xdai.t-relay.eth",
"name":"xdai.t-relay.eth",
"cachedUrl":"https://xdai.t-relay.online/"
},
"xdai-v2.poanet.eth":{
"url":"xdai-v2.poanet.eth",
"name":"xdai-v2.poanet.eth",
"cachedUrl":"https://tornado-xdai.poa.network/"
},
"xdai.gaasservices.eth":{
"url":"xdai.gaasservices.eth",
"name":"xdai.gaasservices.eth",
"cachedUrl":"https://xdai.gaas.services/"
},
"xdai.therelayer.eth":{
"url":"xdai.therelayer.eth",
"name":"xdai.therelayer.eth",
"cachedUrl":"https://xdai.therelayer.xyz/"
},
"xdai.tornadosolutions.eth":{
"url":"xdai.tornadosolutions.eth",
"name":"xdai.tornadosolutions.eth",
"cachedUrl":"https://xdai.tornado.solutions/"
},
"xdai.odanrot.eth":{
"url":"xdai.odanrot.eth",
"name":"xdai.odanrot.eth",
"cachedUrl":"https://tcxdai.avado.cloud/"
}
}
},
"netId137":{
"rpcUrls":{
"publicRpc1":{
"name":"publicRpc1",
"url":"https://polygon-rpc.com"
},
"publicRpc2":{
"name":"publicRpc2",
"url":"https://rpc-mainnet.matic.network"
},
"publicRpc3":{
"name":"publicRpc3",
"url":"https://matic-mainnet.chainstacklabs.com"
},
"MyEtherWallet":{
"name":"MyEtherWallet",
"url":"https://nodes.mewapi.io/ws/matic"
}
},
"relayers":{
"polygon.therelayer.eth":{
"url":"polygon.therelayer.eth",
"name":"polygon.therelayer.eth",
"cachedUrl":"https://polygon.therelayer.xyz/"
},
"polygon.odanrot.eth":{
"url":"polygon.odanrot.eth",
"name":"polygon.odanrot.eth",
"cachedUrl":"https://tcrmatic.avado.cloud/"
},
"polygon.t-relay.eth":{
"url":"polygon.t-relay.eth",
"name":"polygon.t-relay.eth",
"cachedUrl":"https://polygon.t-relay.online/"
},
"polygon.relayer-service.eth":{
"url":"polygon.relayer-service.eth",
"name":"polygon.relayer-service.eth",
"cachedUrl":"https://polygon-relayer.hertz.zone/"
},
"poly.releth.eth":{
"url":"poly.releth.eth",
"name":"poly.releth.eth",
"cachedUrl":"https://poly.reloch.net/"
},
"v1.polygon.thewizardseye.eth":{
"url":"v1.polygon.thewizardseye.eth",
"name":"v1.polygon.thewizardseye.eth",
"cachedUrl":"https://v1.polygon.thewizardseye.de/"
},
"polygon.reasoned.eth":{
"url":"polygon.reasoned.eth",
"name":"polygon.reasoned.eth",
"cachedUrl":"https://polygon.solarsis.net/"
},
"polygon.torn.eth":{
"url":"polygon.torn.eth",
"name":"polygon.torn.eth",
"cachedUrl":"https://polygon.torn.cash/"
},
"polygon.relaymy.eth":{
"url":"polygon.relaymy.eth",
"name":"polygon.relaymy.eth",
"cachedUrl":"https://polygon.relaymy.xyz/"
},
"polygon.tornadosolutions.eth":{
"url":"polygon.tornadosolutions.eth",
"name":"polygon.tornadosolutions.eth",
"cachedUrl":"https://polygon.tornado.solutions/"
},
"polygon.gaasservices.eth":{
"url":"polygon.gaasservices.eth",
"name":"polygon.gaasservices.eth",
"cachedUrl":"https://polygon.gaas.services/"
}
}
},
"netId42161":{
"rpcUrls":{
"Arbitrum":{
"name":"Arbitrum Public RPC",
"url":"https://arb1.arbitrum.io/rpc"
}
},
"relayers":{
"arb.releth.eth":{
"url":"arb.releth.eth",
"name":"arb.releth.eth",
"cachedUrl":"https://arbitrum.reloch.net/"
},
"arbitrum.therelayer.eth":{
"url":"arbitrum.therelayer.eth",
"name":"arbitrum.therelayer.eth",
"cachedUrl":"https://arbitrum.therelayer.xyz/"
},
"arbitrum.relayer-service.eth":{
"url":"arbitrum.relayer-service.eth",
"name":"arbitrum.relayer-service.eth",
"cachedUrl":"https://arbitrum-relayer.hertz.zone/"
},
"arbitrum.t-relay.eth":{
"url":"arbitrum.t-relay.eth",
"name":"arbitrum.t-relay.eth",
"cachedUrl":"https://arbitrum.t-relay.online/"
},
"arbitrum.relaymy.eth":{
"url":"arbitrum.relaymy.eth",
"name":"arbitrum.relaymy.eth",
"cachedUrl":"https://arbitrum.relaymy.xyz/"
},
"arbitrum.torn.eth":{
"url":"arbitrum.torn.eth",
"name":"arbitrum.torn.eth",
"cachedUrl":"https://arbitrum.torn.cash/"
}
}
},
"netId43114":{
"rpcUrls":{
"publicRpc":{
"name":"Avalanche RPC",
"url":"https://api.avax.network/ext/bc/C/rpc"
}
},
"relayers":{
"avax.odanrot.eth":{
"url":"avax.odanrot.eth",
"name":"avax.odanrot.eth",
"cachedUrl":"https://tcravalanche.avado.cloud/"
},
"avalanche.therelayer.eth":{
"url":"avalanche.therelayer.eth",
"name":"avalanche.therelayer.eth",
"cachedUrl":"https://avalanche.therelayer.xyz/"
},
"avalanche.t-relay.eth":{
"url":"avalanche.t-relay.eth",
"name":"avalanche.t-relay.eth",
"cachedUrl":"https://avalanche.t-relay.online/"
},
"avalanche.relaymy.eth":{
"url":"avalanche.relaymy.eth",
"name":"avalanche.relaymy.eth",
"cachedUrl":"https://avalanche.relaymy.xyz/"
},
"avalanche.tornadosolutions.eth":{
"url":"avalanche.tornadosolutions.eth",
"name":"avalanche.tornadosolutions.eth",
"cachedUrl":"https://avalanche.tornado.solutions/"
},
"avalanche.gaasservices.eth":{
"url":"avalanche.gaasservices.eth",
"name":"avalanche.gaasservices.eth",
"cachedUrl":"https://avalanche.gaas.services/"
},
"avax.releth.eth":{
"url":"avax.releth.eth",
"name":"avax.releth.eth",
"cachedUrl":"https://avax.reloch.net/"
}
}
},
"netId5":{
"rpcUrls":{
"Infura":{
"name":"Infura",
"url":"https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161"
},
"Mudit":{
"name":"Mudit",
"url":"https://rpc.goerli.mudit.blog"
},
"Slockit":{
"name":"Slockit",
"url":"https://rpc.slock.it/goerli"
},
"Prylabs":{
"name":"Prylabs",
"url":"https://goerli.prylabs.net"
},
"MyEtherWallet":{
"name":"MyEtherWallet",
"url":"https://nodes.mewapi.io/ws/goerli"
}
},
"relayers":{
"goerli-v2.poanet.eth":{
"url":"goerli-v2.poanet.eth",
"name":"goerli-v2.poanet.eth",
"cachedUrl":"https://tornado-goerli-v2.poa.network/"
},
"goerli.v2.odanrot.eth":{
"url":"goerli.v2.odanrot.eth",
"name":"goerli.v2.odanrot.eth",
"cachedUrl":"https://tcrv2goerli.avado.cloud/"
},
"goerli-v2.releth.eth":{
"url":"goerli-v2.releth.eth",
"name":"goerli-v2.releth.eth",
"cachedUrl":"https://goerli-v2.reloch.net/"
},
"goerli-v2.relaymy.eth":{
"url":"goerli-v2.relaymy.eth",
"name":"goerli-v2.relaymy.eth",
"cachedUrl":"https://goerli-v2.relaymy.xyz/"
},
"goerli-v2.gaasservices.eth":{
"url":"goerli-v2.gaasservices.eth",
"name":"goerli-v2.gaasservices.eth",
"cachedUrl":"https://goerli-v2.gaas.services/"
},
"v2.goerli.thewizardseye.eth":{
"url":"v2.goerli.thewizardseye.eth",
"name":"v2.goerli.thewizardseye.eth",
"cachedUrl":"https://digitalocean.v2.goerli.thewizardseye.de/"
},
"goerli-v2.reasoned.eth":{
"url":"goerli-v2.reasoned.eth",
"name":"goerli-v2.reasoned.eth",
"cachedUrl":"https://goerli-v2.fairish.net/"
},
"goerli.t-relay.eth":{
"url":"goerli.t-relay.eth",
"name":"goerli.t-relay.eth",
"cachedUrl":"https://goerli.t-relay.online/"
},
"goerli-v2.therelayer.eth":{
"url":"goerli-v2.therelayer.eth",
"name":"goerli-v2.therelayer.eth",
"cachedUrl":"https://goerli-v2.therelayer.xyz/"
},
"goerli.relayer-service.eth":{
"url":"goerli.relayer-service.eth",
"name":"goerli.relayer-service.eth",
"cachedUrl":"https://goerli-relayer.hertz.zone/"
},
"goerli-v2.tornadosolutions.eth":{
"url":"goerli-v2.tornadosolutions.eth",
"name":"goerli-v2.tornadosolutions.eth",
"cachedUrl":"https://goerli-v2.tornado.solutions/"
},
"goerli-v2.torn.eth":{
"url":"goerli-v2.torn.eth",
"name":"goerli-v2.torn.eth",
"cachedUrl":"https://goerli-v2.torn.cash/"
},
"goerli-v2.defidevotee.eth":{
"url":"goerli-v2.defidevotee.eth",
"name":"goerli-v2.defidevotee.eth",
"cachedUrl":"https://goerli-v2.defidevotee.xyz"
}
}
}
}
```

File diff suppressed because it is too large Load Diff

105590
cache/binancesmartchain/deposits_bnb_1.json vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

515
cli.js
View File

@ -12,14 +12,16 @@ const circomlib = require('circomlib')
const bigInt = snarkjs.bigInt
const merkleTree = require('./lib/MerkleTree')
const Web3 = require('web3')
const Web3HttpProvider = require('web3-providers-http');
const buildGroth16 = require('websnark/src/groth16')
const websnarkUtils = require('websnark/src/utils')
const { toWei, fromWei, toBN, BN } = require('web3-utils')
const config = require('./config')
const program = require('commander')
const { GasPriceOracle } = require('gas-price-oracle')
const SocksProxyAgent = require('socks-proxy-agent')
let web3, tornado, tornadoContract, tornadoInstance, circuit, proving_key, groth16, erc20, senderAccount, netId
let web3, tornado, tornadoContract, tornadoInstance, circuit, proving_key, groth16, erc20, senderAccount, netId, netName, netSymbol
let MERKLE_TREE_HEIGHT, ETH_AMOUNT, TOKEN_AMOUNT, PRIVATE_KEY
/** Whether we are in a browser or node.js */
@ -39,15 +41,70 @@ function toHex(number, length = 32) {
}
/** Display ETH account balance */
async function printETHBalance({ address, name }) {
console.log(`${name} ETH balance is`, web3.utils.fromWei(await web3.eth.getBalance(address)))
async function printETHBalance({ address, name, symbol }) {
console.log(`${name} balance is`, web3.utils.fromWei(await web3.eth.getBalance(address)),`${symbol}`)
}
/** Display ERC20 account balance */
async function printERC20Balance({ address, name, tokenAddress }) {
const erc20ContractJson = require('./build/contracts/ERC20Mock.json')
erc20 = tokenAddress ? new web3.eth.Contract(erc20ContractJson.abi, tokenAddress) : erc20
console.log(`${name} Token Balance is`, web3.utils.fromWei(await erc20.methods.balanceOf(address).call()))
balance = await erc20.methods.balanceOf(address).call()
decimals = await erc20.methods.decimals().call()
console.log(`${name}`,(await erc20.methods.name().call()),`Token Balance is`,toDecimals(balance, decimals, (balance.length + decimals)).toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ","),(await erc20.methods.symbol().call()))
}
async function generateTransaction(to, encodedData, value = 0) {
const nonce = await web3.eth.getTransactionCount(senderAccount)
const gasPrice = await fetchGasPrice()
let gasLimit;
let tx = {}
async function estimateGas() {
const fetchedGas = await web3.eth.estimateGas({
from : senderAccount,
to : to,
value : value,
nonce : nonce,
data : encodedData
})
const bumped = Math.floor(fetchedGas * 1.3)
gasLimit = web3.utils.toHex(bumped)
}
await estimateGas();
async function txoptions() {
// Generate EIP-1559 transaction
if (netId == 1 || netId == 5) {
tx = {
to : to,
value : value,
nonce : nonce,
maxFeePerGas : gasPrice,
maxPriorityFeePerGas : web3.utils.toHex(web3.utils.toWei('3', 'gwei')),
gas : gasLimit,
data : encodedData
}
} else {
tx = {
to : to,
value : value,
nonce : nonce,
gasPrice : gasPrice,
gas : gasLimit,
data : encodedData
}
}
}
await txoptions();
const signed = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY);
await web3.eth.sendSignedTransaction(signed.rawTransaction)
.on('transactionHash', function (txHash) {
console.log(`View transaction on block explorer https://${getExplorerLink()}/tx/${txHash}`)
})
.on('error', function (e) {
console.error('on transactionHash error', e.message)
});
}
/**
@ -63,12 +120,22 @@ function createDeposit({ nullifier, secret }) {
return deposit
}
async function backupNote({ currency, amount, netId, note, noteString }) {
try {
await fs.writeFileSync(`./backup-tornado-${currency}-${amount}-${netId}-${note.slice(0, 10)}.txt`, noteString, 'utf8');
console.log("Backed up deposit note as",`./backup-tornado-${currency}-${amount}-${netId}-${note.slice(0, 10)}.txt`)
} catch (e) {
throw new Error('Writing backup note failed:',e)
}
}
/**
* Make a deposit
* @param currency Сurrency
* @param amount Deposit amount
*/
async function deposit({ currency, amount }) {
assert(senderAccount != null, 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you deposit')
const deposit = createDeposit({
nullifier: rbigint(31),
secret: rbigint(31)
@ -76,35 +143,36 @@ async function deposit({ currency, amount }) {
const note = toHex(deposit.preimage, 62)
const noteString = `tornado-${currency}-${amount}-${netId}-${note}`
console.log(`Your note: ${noteString}`)
if (currency === 'eth') {
await printETHBalance({ address: tornado._address, name: 'Tornado' })
await printETHBalance({ address: senderAccount, name: 'Sender account' })
await backupNote({ currency, amount, netId, note, noteString })
if (currency === 'eth' || currency === 'bnb' || currency === 'xdai' || currency === 'matic' || currency === 'avax') {
await printETHBalance({ address: tornadoContract._address, name: 'Tornado contract', symbol: currency.toUpperCase() })
await printETHBalance({ address: senderAccount, name: 'Sender account', symbol: currency.toUpperCase() })
const value = isLocalRPC ? ETH_AMOUNT : fromDecimals({ amount, decimals: 18 })
console.log('Submitting deposit transaction')
await tornado.methods.deposit(tornadoInstance, toHex(deposit.commitment), []).send({ value, from: senderAccount, gas: 2e6 })
await printETHBalance({ address: tornado._address, name: 'Tornado' })
await printETHBalance({ address: senderAccount, name: 'Sender account' })
await generateTransaction(contractAddress, await tornado.methods.deposit(tornadoInstance, toHex(deposit.commitment), []).encodeABI(), value)
await printETHBalance({ address: tornadoContract._address, name: 'Tornado contract', symbol: currency.toUpperCase() })
await printETHBalance({ address: senderAccount, name: 'Sender account', symbol: currency.toUpperCase() })
} else {
// a token
await printERC20Balance({ address: tornado._address, name: 'Tornado' })
await printERC20Balance({ address: tornadoContract._address, name: 'Tornado contract' })
await printERC20Balance({ address: senderAccount, name: 'Sender account' })
const decimals = isLocalRPC ? 18 : config.deployments[`netId${netId}`][currency].decimals
const tokenAmount = isLocalRPC ? TOKEN_AMOUNT : fromDecimals({ amount, decimals })
if (isLocalRPC) {
console.log('Minting some test tokens to deposit')
await erc20.methods.mint(senderAccount, tokenAmount).send({ from: senderAccount, gas: 2e6 })
await generateTransaction(erc20Address, await erc20.methods.mint(senderAccount, tokenAmount).encodeABI())
}
const allowance = await erc20.methods.allowance(senderAccount, tornado._address).call({ from: senderAccount })
console.log('Current allowance is', fromWei(allowance))
if (toBN(allowance).lt(toBN(tokenAmount))) {
console.log('Approving tokens for deposit')
await erc20.methods.approve(tornado._address, tokenAmount).send({ from: senderAccount, gas: 1e6 })
await generateTransaction(erc20Address, await erc20.methods.approve(tornado._address, tokenAmount).encodeABI())
}
console.log('Submitting deposit transaction')
await tornado.methods.deposit(toHex(deposit.commitment)).send({ from: senderAccount, gas: 2e6 })
await printERC20Balance({ address: tornado._address, name: 'Tornado' })
await generateTransaction(contractAddress, await tornado.methods.deposit(toHex(deposit.commitment)).encodeABI())
await printERC20Balance({ address: tornadoContract._address, name: 'Tornado contract' })
await printERC20Balance({ address: senderAccount, name: 'Sender account' })
}
@ -117,34 +185,13 @@ async function deposit({ currency, amount }) {
* in it and generates merkle proof
* @param deposit Deposit object
*/
async function generateMerkleProof(deposit, amount) {
async function generateMerkleProof(deposit, currency, amount) {
let leafIndex = -1
// Get all deposit events from smart contract and assemble merkle tree from them
const cachedEvents = loadCachedEvents({ type: 'Deposit', amount })
const cachedEvents = await fetchEvents({ type: 'deposit', currency, amount })
const startBlock = cachedEvents.lastBlock
let rpcEvents = await tornadoContract.getPastEvents('Deposit', {
fromBlock: startBlock,
toBlock: 'latest'
})
rpcEvents = rpcEvents.map(({ blockNumber, transactionHash, returnValues }) => {
const { commitment, leafIndex, timestamp } = returnValues
return {
blockNumber,
transactionHash,
commitment,
leafIndex: Number(leafIndex),
timestamp
}
})
const events = cachedEvents.events.concat(rpcEvents)
console.log('events', events.length)
const leaves = events
const leaves = cachedEvents
.sort((a, b) => a.leafIndex - b.leafIndex) // Sort events in chronological order
.map((e) => {
const index = toBN(e.leafIndex).toNumber()
@ -176,9 +223,9 @@ async function generateMerkleProof(deposit, amount) {
* @param fee Relayer fee
* @param refund Receive ether for exchanged tokens
*/
async function generateProof({ deposit, amount, recipient, relayerAddress = 0, fee = 0, refund = 0 }) {
async function generateProof({ deposit, currency, amount, recipient, relayerAddress = 0, fee = 0, refund = 0 }) {
// Compute merkle proof of our commitment
const { root, path_elements, path_index } = await generateMerkleProof(deposit, amount)
const { root, path_elements, path_index } = await generateMerkleProof(deposit, currency, amount)
// Prepare circuit input
const input = {
@ -220,7 +267,8 @@ async function generateProof({ deposit, amount, recipient, relayerAddress = 0, f
* @param noteString Note to withdraw
* @param recipient Recipient address
*/
async function withdraw({ deposit, currency, amount, recipient, relayerURL, refund = '0' }) {
async function withdraw({ deposit, currency, amount, recipient, relayerURL, torPort, refund = '0' }) {
let options = {};
if (currency === 'eth' && refund !== '0') {
throw new Error('The ETH purchase is supposted to be 0 for ETH withdrawals')
}
@ -229,11 +277,14 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
if (relayerURL.endsWith('.eth')) {
throw new Error('ENS name resolving is not supported. Please provide DNS name of the relayer. See instuctions in README.md')
}
const relayerStatus = await axios.get(relayerURL + '/status')
if (torPort) {
options = { httpsAgent: new SocksProxyAgent('socks5h://127.0.0.1:'+torPort), headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0' } }
}
const relayerStatus = await axios.get(relayerURL + '/status', options)
const { rewardAccount, netId, ethPrices, tornadoServiceFee } = relayerStatus.data
assert(netId === (await web3.eth.net.getId()) || netId === '*', 'This relay is for different network')
console.log('Relay address: ', rewardAccount)
console.log('Relay address:', rewardAccount)
const gasPrice = await fetchGasPrice()
@ -251,7 +302,7 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
throw new Error('Too high refund')
}
const { proof, args } = await generateProof({ deposit, amount, recipient, relayerAddress: rewardAccount, fee, refund })
const { proof, args } = await generateProof({ deposit, currency, amount, recipient, relayerAddress: rewardAccount, fee, refund })
console.log('Sending withdraw transaction through relay')
try {
@ -259,11 +310,11 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
contract: tornadoInstance,
proof,
args
})
}, options)
const { id } = response.data
const result = await getStatus(id, relayerURL)
const result = await getStatus(id, relayerURL, options)
console.log('STATUS', result)
} catch (e) {
if (e.response) {
@ -274,30 +325,23 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
}
} else {
// using private key
const { proof, args } = await generateProof({ deposit, recipient, refund })
// check if the address of recepient matches with the account of provided private key from environment to prevent accidental use of deposit address for withdrawal transaction.
const { address } = await web3.eth.accounts.privateKeyToAccount('0x' + PRIVATE_KEY)
assert(recipient.toLowerCase() == address.toLowerCase(), 'Withdrawal amount recepient',recipient,'mismatches with the account of provided private key from environment file',address)
const { proof, args } = await generateProof({ deposit, currency, amount, recipient, refund })
console.log('Submitting withdraw transaction')
await tornado.methods
.withdraw(tornadoInstance, proof, ...args)
.send({ from: senderAccount, value: refund.toString(), gas: 1e6 })
.on('transactionHash', function (txHash) {
if (netId === 1 || netId === 42) {
console.log(`View transaction on etherscan https://${getCurrentNetworkName()}etherscan.io/tx/${txHash}`)
} else {
console.log(`The transaction hash is ${txHash}`)
}
})
.on('error', function (e) {
console.error('on transactionHash error', e.message)
})
await generateTransaction(contractAddress, await tornado.methods.withdraw(tornadoInstance, proof, ...args).encodeABI())
}
console.log('Done')
console.log('Done withdrawal from Tornado Cash')
}
function getStatus(id, relayerURL) {
function getStatus(id, relayerURL, options) {
return new Promise((resolve) => {
async function getRelayerStatus() {
const responseStatus = await axios.get(relayerURL + '/v1/jobs/' + id)
const responseStatus = await axios.get(relayerURL + '/v1/jobs/' + id, options)
if (responseStatus.status === 200) {
const { txHash, status, confirmations, failedReason } = responseStatus.data
@ -311,7 +355,7 @@ function getStatus(id, relayerURL) {
if (status === 'CONFIRMED') {
const receipt = await waitForTxReceipt({ txHash })
console.log(
`Transaction submitted through the relay. View transaction on etherscan https://${getCurrentNetworkName()}etherscan.io/tx/${txHash}`
`Transaction submitted through the relay. View transaction on block explorer https://${getExplorerLink()}/tx/${txHash}`
)
console.log('Transaction mined in block', receipt.blockNumber)
resolve(status)
@ -327,6 +371,10 @@ function getStatus(id, relayerURL) {
})
}
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function fromDecimals({ amount, decimals }) {
amount = amount.toString()
let ether = amount.toString()
@ -411,30 +459,95 @@ function toDecimals(value, decimals, fixed) {
return value
}
function getCurrentNetworkName() {
// List fetched from https://github.com/ethereum-lists/chains/blob/master/_data/chains
function getExplorerLink() {
switch (netId) {
case 1:
return ''
case 56:
return 'bscscan.com'
case 100:
return 'blockscout.com/poa/xdai'
case 137:
return 'polygonscan.com'
case 42161:
return 'arbiscan.io'
case 43114:
return 'snowtrace.io'
case 5:
return 'goerli.'
return 'goerli.etherscan.io'
case 42:
return 'kovan.'
return 'kovan.etherscan.io'
default:
return 'etherscan.io'
}
}
function gasPrices(value = 80) {
// List fetched from https://github.com/trustwallet/assets/tree/master/blockchains
function getCurrentNetworkName() {
switch (netId) {
case 1:
return 'Ethereum'
case 56:
return 'BinanceSmartChain'
case 100:
return 'xDai'
case 137:
return 'Polygon'
case 42161:
return 'Arbitrum'
case 43114:
return 'Avalanche'
case 5:
return 'Goerli'
case 42:
return 'Kovan'
default:
return 'localRPC'
}
}
function getCurrentNetworkSymbol() {
switch (netId) {
case 56:
return 'BNB'
case 100:
return 'xDAI'
case 137:
return 'MATIC'
case 43114:
return 'AVAX'
default:
return 'ETH'
}
}
function gasPricesETH(value = 80) {
const tenPercent = (Number(value) * 5) / 100
const max = Math.max(tenPercent, 3)
const bumped = Math.floor(Number(value) + max)
return toHex(toWei(bumped.toString(), 'gwei'))
}
function gasPrices(value = 5) {
return toHex(toWei(value.toString(), 'gwei'))
}
async function fetchGasPrice() {
try {
const oracle = new GasPriceOracle()
const gas = await oracle.gasPrices()
return gasPrices(gas.fast)
const options = {
chainId: netId
}
// Bump fees for Ethereum network
if (netId == 1) {
const oracle = new GasPriceOracle(options)
const gas = await oracle.gasPrices()
return gasPricesETH(gas.instant)
} else if (netId == 5 || isLocalRPC) {
return gasPrices(1)
} else {
const oracle = new GasPriceOracle(options)
const gas = await oracle.gasPrices()
return gasPrices(gas.instant)
}
} catch (err) {
throw new Error(`Method fetchGasPrice has error ${err.message}`)
}
@ -453,6 +566,22 @@ function calculateFee({ currency, gasPrice, amount, refund, ethPrices, relayerSe
desiredFee = expense.add(feePercent)
break
}
case 'bnb': {
desiredFee = expense.add(feePercent)
break
}
case 'xdai': {
desiredFee = expense.add(feePercent)
break
}
case 'matic': {
desiredFee = expense.add(feePercent)
break
}
case 'avax': {
desiredFee = expense.add(feePercent)
break
}
default: {
desiredFee = expense
.add(toBN(refund))
@ -489,16 +618,9 @@ function waitForTxReceipt({ txHash, attempts = 60, delay = 1000 }) {
})
}
function loadCachedEvents({ type, amount }) {
function loadCachedEvents({ type, currency, amount }) {
try {
if (netId !== 1) {
return {
events: [],
lastBlock: 0,
}
}
const module = require(`./cache/${type.toLowerCase()}s_eth_${amount}.json`)
const module = require(`./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`)
if (module) {
const events = module
@ -509,10 +631,99 @@ function loadCachedEvents({ type, amount }) {
}
}
} catch (err) {
throw new Error(`Method loadCachedEvents has error: ${err.message}`)
console.log("Error fetching cached files, syncing from block",deployedBlockNumber)
return {
events: [],
lastBlock: deployedBlockNumber,
}
}
}
async function fetchEvents({ type, currency, amount}) {
let leafIndex = -1
let events = [];
let fetchedEvents, chunks, targetBlock;
if (type === "withdraw") {
type = "withdrawal"
}
const cachedEvents = loadCachedEvents({ type, currency, amount })
const startBlock = cachedEvents.lastBlock + 1
console.log("Fetching",amount,currency.toUpperCase(),type,"events for",netName,"network")
async function fetchLatestEvents() {
targetBlock = await web3.eth.getBlockNumber();
chunks = 1000;
fetchedEvents = [];
for (let i=startBlock; i < targetBlock; i+=chunks) {
await tornadoContract.getPastEvents(capitalizeFirstLetter(type), {
fromBlock: i,
toBlock: i+chunks-1,
}).then(r => { fetchedEvents = fetchedEvents.concat(r); console.log("Fetched",amount,currency.toUpperCase(),type,"events from block:", i) }, err => { console.error(i + " failed fetching",type,"events from node", err) }).catch(console.log);
}
}
await fetchLatestEvents()
async function mapDepositEvents() {
fetchedEvents = fetchedEvents.map(({ blockNumber, transactionHash, returnValues }) => {
const { commitment, leafIndex, timestamp } = returnValues
return {
blockNumber,
transactionHash,
commitment,
leafIndex: Number(leafIndex),
timestamp
}
})
}
async function mapWithdrawEvents() {
fetchedEvents = fetchedEvents.map(({ blockNumber, transactionHash, returnValues }) => {
const { nullifierHash, to, fee } = returnValues
return {
blockNumber,
transactionHash,
nullifierHash,
to,
fee
}
})
}
async function mapLatestEvents() {
console.log("Mapping",amount,currency.toUpperCase(),type,"events, please wait")
if (type === "deposit"){
await mapDepositEvents();
} else {
await mapWithdrawEvents();
}
}
await mapLatestEvents();
console.log("Gathering cached events + collected events from node")
async function concatEvents() {
events = cachedEvents.events.concat(fetchedEvents)
}
await concatEvents();
console.log('Total events:', events.length)
async function updateCache() {
try {
await fs.writeFileSync(`./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`, JSON.stringify(events, null, 2), 'utf8')
console.log("Cache updated for Tornado",type,amount,currency,"instance successfully")
} catch (e) {
throw new Error('Writing cache file failed:',e)
}
}
await updateCache();
return events
}
/**
* Parses Tornado.cash note
* @param noteString the note
@ -538,21 +749,19 @@ function parseNote(noteString) {
}
}
async function loadDepositData({ deposit }) {
async function loadDepositData({ amount, currency, deposit }) {
try {
const eventWhenHappened = await tornadoContract.getPastEvents('Deposit', {
filter: {
commitment: deposit.commitmentHex
},
fromBlock: 0,
toBlock: 'latest'
})
const cachedEvents = await fetchEvents({ type: 'deposit', currency, amount })
const eventWhenHappened = await cachedEvents.filter(function (event) {
return event.commitment === deposit.commitmentHex;
})[0]
if (eventWhenHappened.length === 0) {
throw new Error('There is no related deposit, the note is invalid')
}
const { timestamp } = eventWhenHappened[0].returnValues
const txHash = eventWhenHappened[0].transactionHash
const timestamp = eventWhenHappened.timestamp
const txHash = eventWhenHappened.transactionHash
const isSpent = await tornadoContract.methods.isSpent(deposit.nullifierHex).call()
const receipt = await web3.eth.getTransactionReceipt(txHash)
@ -570,29 +779,9 @@ async function loadDepositData({ deposit }) {
}
async function loadWithdrawalData({ amount, currency, deposit }) {
try {
const cachedEvents = loadCachedEvents({ type: 'Withdrawal', amount })
const cachedEvents = await fetchEvents({ type: 'withdrawal', currency, amount })
const startBlock = cachedEvents.lastBlock
let rpcEvents = await tornadoContract.getPastEvents('Withdrawal', {
fromBlock: startBlock,
toBlock: 'latest'
})
rpcEvents = rpcEvents.map(({ blockNumber, transactionHash, returnValues }) => {
const { nullifierHash, to, fee } = returnValues
return {
blockNumber,
transactionHash,
nullifierHash,
to,
fee
}
})
const events = cachedEvents.events.concat(rpcEvents)
const withdrawEvent = events.filter((event) => {
const withdrawEvent = cachedEvents.filter((event) => {
return event.nullifierHash === deposit.nullifierHex
})[0]
@ -616,7 +805,7 @@ async function loadWithdrawalData({ amount, currency, deposit }) {
/**
* Init web3, contracts, and snark
*/
async function init({ rpc, noteNetId, currency = 'dai', amount = '100' }) {
async function init({ rpc, noteNetId, currency = 'dai', amount = '100', torPort, balanceCheck }) {
let contractJson, instanceJson, erc20ContractJson, erc20tornadoJson, tornadoAddress, tokenAddress
// TODO do we need this? should it work in browser really?
if (inBrowser) {
@ -634,8 +823,22 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100' }) {
TOKEN_AMOUNT = 1e19
senderAccount = (await web3.eth.getAccounts())[0]
} else {
// Initialize from local node
web3 = new Web3(rpc, null, { transactionConfirmationBlocks: 1 })
if (torPort) {
console.log("Using tor network")
web3Options = { agent: { https: new SocksProxyAgent('socks5h://127.0.0.1:'+torPort) }, timeout: 60000 }
// Use forked web3-providers-http from local file to modify user-agent header value which improves privacy.
web3 = new Web3(new Web3HttpProvider(rpc, web3Options), null, { transactionConfirmationBlocks: 1 })
} else if (rpc.includes("ipc")) {
console.log("Using ipc connection")
web3 = new Web3(new Web3.providers.IpcProvider(rpc, net), null, { transactionConfirmationBlocks: 1 })
} else if (rpc.includes("ws") || rpc.includes("wss")) {
console.log("Using websocket connection (Note: Tor is not supported for Websocket providers)")
web3Options = { clientConfig: { keepalive: true, keepaliveInterval: -1 }, reconnect: { auto: true, delay: 1000, maxAttempts: 10, onTimeout: false } }
web3 = new Web3(new Web3.providers.WebsocketProvider(rpc, web3Options), net, { transactionConfirmationBlocks: 1 })
} else {
console.log("Connecting to remote node")
web3 = new Web3(rpc, null, { transactionConfirmationBlocks: 1 })
}
contractJson = require('./build/contracts/TornadoProxy.abi.json')
instanceJson = require('./build/contracts/Instance.abi.json')
circuit = require('./build/circuits/tornado.json')
@ -649,8 +852,6 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100' }) {
web3.eth.accounts.wallet.add('0x' + PRIVATE_KEY)
web3.eth.defaultAccount = account.address
senderAccount = account.address
} else {
console.log('Warning! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you deposit')
}
erc20ContractJson = require('./build/contracts/ERC20Mock.json')
erc20tornadoJson = require('./build/contracts/ERC20Tornado.json')
@ -658,32 +859,45 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100' }) {
// groth16 initialises a lot of Promises that will never be resolved, that's why we need to use process.exit to terminate the CLI
groth16 = await buildGroth16()
netId = await web3.eth.net.getId()
netName = getCurrentNetworkName()
if (noteNetId && Number(noteNetId) !== netId) {
throw new Error('This note is for a different network. Specify the --rpc option explicitly')
}
isLocalRPC = netId > 42
if (netName === "localRPC") {
isLocalRPC = true;
}
if (isLocalRPC) {
tornadoAddress = currency === 'eth' ? contractJson.networks[netId].address : erc20tornadoJson.networks[netId].address
tokenAddress = currency !== 'eth' ? erc20ContractJson.networks[netId].address : null
netSymbol = getCurrentNetworkSymbol()
deployedBlockNumber = 0
senderAccount = (await web3.eth.getAccounts())[0]
} else {
try {
if (balanceCheck) {
netSymbol = getCurrentNetworkSymbol()
currency = netSymbol.toLowerCase()
amount = Object.keys(config.deployments[`netId${netId}`][currency].instanceAddress)[0]
}
tornadoAddress = config.deployments[`netId${netId}`].proxy
tornadoInstance = config.deployments[`netId${netId}`][currency].instanceAddress[amount]
deployedBlockNumber = config.deployments[`netId${netId}`][currency].deployedBlockNumber[amount]
if (!tornadoAddress) {
throw new Error()
}
tokenAddress = config.deployments[`netId${netId}`][currency].tokenAddress
} catch (e) {
console.error('There is no such tornado instance, check the currency and amount you provide')
console.error('There is no such tornado instance, check the currency and amount you provide', e)
process.exit(1)
}
}
tornado = new web3.eth.Contract(contractJson, tornadoAddress)
tornadoContract = new web3.eth.Contract(instanceJson, tornadoInstance)
contractAddress = tornadoAddress
erc20 = currency !== 'eth' ? new web3.eth.Contract(erc20ContractJson.abi, tokenAddress) : {}
erc20Address = tokenAddress
}
async function main() {
@ -703,8 +917,9 @@ async function main() {
}
} else {
program
.option('-r, --rpc <URL>', 'The RPC, CLI should interact with', 'http://localhost:8545')
.option('-r, --rpc <URL>', 'The RPC that CLI should interact with', 'http://localhost:8545')
.option('-R, --relayer <URL>', 'Withdraw via relayer')
.option('-T, --tor <PORT>', 'Optional tor port')
program
.command('deposit <currency> <amount>')
.description(
@ -712,7 +927,7 @@ async function main() {
)
.action(async (currency, amount) => {
currency = currency.toLowerCase()
await init({ rpc: program.rpc, currency, amount })
await init({ rpc: program.rpc, currency, amount, torPort: program.tor })
await deposit({ currency, amount })
})
program
@ -722,24 +937,25 @@ async function main() {
)
.action(async (noteString, recipient, refund) => {
const { currency, amount, netId, deposit } = parseNote(noteString)
await init({ rpc: program.rpc, noteNetId: netId, currency, amount })
await init({ rpc: program.rpc, noteNetId: netId, currency, amount, torPort: program.tor })
await withdraw({
deposit,
currency,
amount,
recipient,
refund,
relayerURL: program.relayer
relayerURL: program.relayer,
torPort: program.tor
})
})
program
.command('balance <address> [token_address]')
.description('Check ETH and ERC20 balance')
.action(async (address, tokenAddress) => {
await init({ rpc: program.rpc })
await printETHBalance({ address, name: '' })
await init({ rpc: program.rpc, torPort: program.tor, balanceCheck: true })
await printETHBalance({ address, name: 'Account', symbol: netSymbol })
if (tokenAddress) {
await printERC20Balance({ address, name: '', tokenAddress })
await printERC20Balance({ address, name: 'Account', tokenAddress })
}
})
program
@ -749,33 +965,44 @@ async function main() {
)
.action(async (noteString) => {
const { currency, amount, netId, deposit } = parseNote(noteString)
await init({ rpc: program.rpc, noteNetId: netId, currency, amount })
const depositInfo = await loadDepositData({ deposit })
await init({ rpc: program.rpc, noteNetId: netId, currency, amount, torPort: program.tor })
const depositInfo = await loadDepositData({ amount, currency, deposit })
const depositDate = new Date(depositInfo.timestamp * 1000)
console.log('\n=============Deposit=================')
console.log('Deposit :', amount, currency)
console.log('Deposit :', amount, currency.toUpperCase())
console.log('Date :', depositDate.toLocaleDateString(), depositDate.toLocaleTimeString())
console.log('From :', `https://${getCurrentNetworkName()}etherscan.io/address/${depositInfo.from}`)
console.log('Transaction :', `https://${getCurrentNetworkName()}etherscan.io/tx/${depositInfo.txHash}`)
console.log('From :', `https://${getExplorerLink()}/address/${depositInfo.from}`)
console.log('Transaction :', `https://${getExplorerLink()}/tx/${depositInfo.txHash}`)
console.log('Commitment :', depositInfo.commitment)
if (!deposit.isSpent) {
console.log('Spent :', depositInfo.isSpent)
if (!depositInfo.isSpent) {
console.log('The note was not spent')
return
}
console.log('=====================================','\n')
const withdrawInfo = await loadWithdrawalData({
amount,
currency,
deposit
})
const withdrawInfo = await loadWithdrawalData({ amount, currency, deposit })
const withdrawalDate = new Date(withdrawInfo.timestamp * 1000)
console.log('\n=============Withdrawal==============')
console.log('Withdrawal :', withdrawInfo.amount, currency)
console.log('Relayer Fee :', withdrawInfo.fee, currency)
console.log('Date :', withdrawalDate.toLocaleDateString(), withdrawalDate.toLocaleTimeString())
console.log('To :', `https://${getCurrentNetworkName()}etherscan.io/address/${withdrawInfo.to}`)
console.log('Transaction :', `https://${getCurrentNetworkName()}etherscan.io/tx/${withdrawInfo.txHash}`)
console.log('To :', `https://${getExplorerLink()}/address/${withdrawInfo.to}`)
console.log('Transaction :', `https://${getExplorerLink()}/tx/${withdrawInfo.txHash}`)
console.log('Nullifier :', withdrawInfo.nullifier)
console.log('=====================================','\n')
})
program
.command('syncEvents <type> <currency> <amount>')
.description(
'Sync the local cache file of deposit / withdrawal events for specific currency.'
)
.action(async (type, currency, amount) => {
console.log("Starting event sync command")
currency = currency.toLowerCase()
await init({ rpc: program.rpc, type, currency, amount, torPort: program.tor })
const cachedEvents = await fetchEvents({ type, currency, amount })
console.log("Synced event for",type,amount,currency.toUpperCase(),netName,"Tornado instance to block",cachedEvents[cachedEvents.length - 1].blockNumber)
})
program
.command('test')

178
config.js
View File

@ -5,10 +5,16 @@ module.exports = {
netId1: {
'eth': {
'instanceAddress': {
'0.1': '0x12D66f87A04A9E220743712cE6d9bB1B5616B8Fc',
'1': '0x47CE0C6eD5B0Ce3d3A51fdb1C52DC66a7c3c2936',
'10': '0x910Cbd523D972eb0a6f4cAe4618aD62622b39DbF',
'100': '0xA160cdAB225685dA1d56aa342Ad8841c3b53f291',
'0.1': '0x12D66f87A04A9E220743712cE6d9bB1B5616B8Fc'
'100': '0xA160cdAB225685dA1d56aa342Ad8841c3b53f291'
},
'deployedBlockNumber': {
'0.1': 9116966,
'1': 9117609,
'10': 9117720,
'100': 9161895
},
'miningEnabled': true,
'symbol': 'ETH',
@ -21,6 +27,12 @@ module.exports = {
'10000': '0x07687e702b410Fa43f4cB4Af7FA097918ffD2730',
'100000': '0x23773E65ed146A459791799d01336DB287f25334'
},
'deployedBlockNumber': {
'100': 9117612,
'1000': 9161917,
'10000': 12066007,
'100000': 12066048
},
'miningEnabled': true,
'tokenAddress': '0x6B175474E89094C44Da98b954EedeAC495271d0F',
'symbol': 'DAI',
@ -34,6 +46,12 @@ module.exports = {
'500000': '0x2717c5e28cf931547B621a5dddb772Ab6A35B701',
'5000000': '0xD21be7248e0197Ee08E0c20D4a96DEBdaC3D20Af'
},
'deployedBlockNumber': {
'5000': 9161938,
'50000': 12069037,
'500000': 12067606,
'5000000': 12066053
},
'miningEnabled': true,
'tokenAddress': '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643',
'symbol': 'cDAI',
@ -47,6 +65,12 @@ module.exports = {
'10000': '',
'100000': ''
},
'deployedBlockNumber': {
'100': 9161958,
'1000': 9161965,
'10000': '',
'100000': ''
},
'miningEnabled': false,
'tokenAddress': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
'symbol': 'USDC',
@ -60,6 +84,12 @@ module.exports = {
'10000': '',
'100000': ''
},
'deployedBlockNumber': {
'100': 9162005,
'1000': 9162012,
'10000': '',
'100000': ''
},
'miningEnabled': false,
'tokenAddress': '0xdAC17F958D2ee523a2206206994597C13D831ec7',
'symbol': 'USDT',
@ -73,6 +103,12 @@ module.exports = {
'10': '0xbB93e510BbCD0B7beb5A853875f9eC60275CF498',
'100': ''
},
'deployedBlockNumber': {
'0.1': 12067529,
'1': 12066652,
'10': 12067591,
'100': ''
},
'miningEnabled': true,
'tokenAddress': '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
'symbol': 'WBTC',
@ -84,10 +120,16 @@ module.exports = {
netId5: {
'eth': {
'instanceAddress': {
'0.1': '0x6Bf694a291DF3FeC1f7e69701E3ab6c592435Ae7',
'1': '0x3aac1cC67c2ec5Db4eA850957b967Ba153aD6279',
'10': '0x723B78e67497E85279CB204544566F4dC5d2acA0',
'100': '0x0E3A09dDA6B20aFbB34aC7cD4A6881493f3E7bf7',
'0.1': '0x6Bf694a291DF3FeC1f7e69701E3ab6c592435Ae7'
'100': '0x0E3A09dDA6B20aFbB34aC7cD4A6881493f3E7bf7'
},
'deployedBlockNumber': {
'0.1': 3782581,
'1': 3782590,
'10': 3782593,
'100': 3782596
},
'miningEnabled': true,
'symbol': 'ETH',
@ -100,6 +142,12 @@ module.exports = {
'10000': '0xD5d6f8D9e784d0e26222ad3834500801a68D027D',
'100000': '0x407CcEeaA7c95d2FE2250Bf9F2c105aA7AAFB512'
},
'deployedBlockNumber': {
'100': 4339088,
'1000': 4367659,
'10000': 4441492,
'100000': 4441488
},
'miningEnabled': true,
'tokenAddress': '0xdc31Ee1784292379Fbb2964b3B9C4124D8F89C60',
'symbol': 'DAI',
@ -113,6 +161,12 @@ module.exports = {
'500000': '0x8281Aa6795aDE17C8973e1aedcA380258Bc124F9',
'5000000': '0x57b2B8c82F065de8Ef5573f9730fC1449B403C9f'
},
'deployedBlockNumber': {
'5000': 4441443,
'50000': 4441489,
'500000': 4441493,
'5000000': 4441489
},
'miningEnabled': true,
'tokenAddress': '0x822397d9a55d0fefd20F5c4bCaB33C5F65bd28Eb',
'symbol': 'cDAI',
@ -126,6 +180,12 @@ module.exports = {
'10000': '',
'100000': ''
},
'deployedBlockNumber': {
'100': 4441426,
'1000': 4441492,
'10000': '',
'100000': ''
},
'miningEnabled': false,
'tokenAddress': '0xD87Ba7A50B2E7E660f678A895E4B72E7CB4CCd9C',
'symbol': 'USDC',
@ -139,6 +199,12 @@ module.exports = {
'10000': '',
'100000': ''
},
'deployedBlockNumber': {
'100': 4441490,
'1000': 4441492,
'10000': '',
'100000': ''
},
'miningEnabled': false,
'tokenAddress': '0xb7FC2023D96AEa94Ba0254AA5Aeb93141e4aad66',
'symbol': 'USDT',
@ -152,6 +218,12 @@ module.exports = {
'10': '0xeDC5d01286f99A066559F60a585406f3878a033e',
'100': ''
},
'deployedBlockNumber': {
'0.1': 4441488,
'1': 4441490,
'10': 4441490,
'100': ''
},
'miningEnabled': true,
'tokenAddress': '0xC04B0d3107736C32e19F1c62b2aF67BE61d63a05',
'symbol': 'WBTC',
@ -160,5 +232,103 @@ module.exports = {
},
proxy: '0x454d870a72e29d5e5697f635128d18077bd04c60',
},
netId56: {
'bnb': {
'instanceAddress': {
'0.1': '0x84443CFd09A48AF6eF360C6976C5392aC5023a1F',
'1': '0xd47438C816c9E7f2E2888E060936a499Af9582b3',
'10': '0x330bdFADE01eE9bF63C209Ee33102DD334618e0a',
'100': '0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD'
},
'deployedBlockNumber': {
'0.1': 8159279,
'1': 8159286,
'10': 8159290,
'100': 8159296
},
'miningEnabled': false,
'symbol': 'BNB',
'decimals': 18
},
proxy: '0x0D5550d52428E7e3175bfc9550207e4ad3859b17',
},
netId100: {
'xdai': {
'instanceAddress': {
'100': '0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD',
'1000': '0xdf231d99Ff8b6c6CBF4E9B9a945CBAcEF9339178',
'10000': '0xaf4c0B70B2Ea9FB7487C7CbB37aDa259579fe040',
'100000': '0xa5C2254e4253490C54cef0a4347fddb8f75A4998'
},
'deployedBlockNumber': {
'100': 17754566,
'1000': 17754568,
'10000': 17754572,
'100000': 17754574
},
'miningEnabled': false,
'symbol': 'xDAI',
'decimals': 18
},
proxy: '0x0D5550d52428E7e3175bfc9550207e4ad3859b17',
},
netId137: {
'matic': {
'instanceAddress': {
'100': '0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD',
'1000': '0xdf231d99Ff8b6c6CBF4E9B9a945CBAcEF9339178',
'10000': '0xaf4c0B70B2Ea9FB7487C7CbB37aDa259579fe040',
'100000': '0xa5C2254e4253490C54cef0a4347fddb8f75A4998'
},
'deployedBlockNumber': {
'100': 16258013,
'1000': 16258032,
'10000': 16258046,
'100000': 16258053
},
'miningEnabled': false,
'symbol': 'MATIC',
'decimals': 18
},
proxy: '0x0D5550d52428E7e3175bfc9550207e4ad3859b17',
},
netId42161: {
'eth': {
'instanceAddress': {
'0.1': '0x84443CFd09A48AF6eF360C6976C5392aC5023a1F',
'1': '0xd47438C816c9E7f2E2888E060936a499Af9582b3',
'10': '0x330bdFADE01eE9bF63C209Ee33102DD334618e0a',
'100': '0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD'
},
'deployedBlockNumber': {
'0.1': 3300000,
'1': 3300000,
'10': 3300000,
'100': 3300000
},
'miningEnabled': false,
'symbol': 'ETH',
'decimals': 18
},
proxy: '0x0D5550d52428E7e3175bfc9550207e4ad3859b17',
},
netId43114: {
'avax': {
'instanceAddress': {
'10': '0x330bdFADE01eE9bF63C209Ee33102DD334618e0a',
'100': '0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD',
'500': '0xaf8d1839c3c67cf571aa74B5c12398d4901147B3'
},
'deployedBlockNumber': {
'10': 4429830,
'100': 4429851,
'500': 4429837
},
'miningEnabled': false,
'symbol': 'AVAX',
'decimals': 18
},
proxy: '0x0D5550d52428E7e3175bfc9550207e4ad3859b17',
},
}
}

View File

@ -0,0 +1,47 @@
# web3-providers-http
[![NPM Package][npm-image]][npm-url] [![Dependency Status][deps-image]][deps-url] [![Dev Dependency Status][deps-dev-image]][deps-dev-url]
Package forked from https://github.com/ChainSafe/web3.js/tree/v1.6.1/packages/web3-providers-http to change http headers
This is a HTTP provider sub-package for [web3.js][repo].
Please read the [documentation][docs] for more.
## Installation
### Node.js
```bash
npm install web3-providers-http
```
## Usage
```js
const http = require('http');
const Web3HttpProvider = require('web3-providers-http');
const options = {
keepAlive: true,
timeout: 20000, // milliseconds,
headers: [{name: 'Access-Control-Allow-Origin', value: '*'},{...}],
withCredentials: false,
agent: {http: http.Agent(...), baseUrl: ''}
};
const provider = new Web3HttpProvider('http://localhost:8545', options);
```
## Types
All the TypeScript typings are placed in the `types` folder.
[docs]: http://web3js.readthedocs.io/en/1.0/
[repo]: https://github.com/ethereum/web3.js
[npm-image]: https://img.shields.io/npm/dm/web3-providers-http.svg
[npm-url]: https://npmjs.org/package/web3-providers-http
[deps-image]: https://david-dm.org/ethereum/web3.js/1.x/status.svg?path=packages/web3-providers-http
[deps-url]: https://david-dm.org/ethereum/web3.js/1.x?path=packages/web3-providers-http
[deps-dev-image]: https://david-dm.org/ethereum/web3.js/1.x/dev-status.svg?path=packages/web3-providers-http
[deps-dev-url]: https://david-dm.org/ethereum/web3.js/1.x?type=dev&path=packages/web3-providers-http

View File

@ -0,0 +1,125 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file httpprovider.js
* @authors:
* Marek Kotewicz <marek@parity.io>
* Marian Oancea
* Fabian Vogelsteller <fabian@ethereum.org>
* @date 2015
*/
var errors = require('web3-core-helpers').errors;
var XHR2 = require('xhr2-cookies').XMLHttpRequest; // jshint ignore: line
var http = require('http');
var https = require('https');
/**
* HttpProvider should be used to send rpc calls over http
*/
var HttpProvider = function HttpProvider(host, options) {
options = options || {};
this.withCredentials = options.withCredentials || false;
this.timeout = options.timeout || 0;
this.headers = options.headers;
this.agent = options.agent;
this.connected = false;
// keepAlive is true unless explicitly set to false
const keepAlive = options.keepAlive !== false;
this.host = host || 'http://localhost:8545';
if (!this.agent) {
if (this.host.substring(0, 5) === "https") {
this.httpsAgent = new https.Agent({ keepAlive });
}
else {
this.httpAgent = new http.Agent({ keepAlive });
}
}
};
HttpProvider.prototype._prepareRequest = function () {
var request;
// the current runtime is a browser
if (typeof XMLHttpRequest !== 'undefined') {
request = new XMLHttpRequest();
}
else {
request = new XHR2();
var agents = { httpsAgent: this.httpsAgent, httpAgent: this.httpAgent, baseUrl: this.baseUrl };
if (this.agent) {
agents.httpsAgent = this.agent.https;
agents.httpAgent = this.agent.http;
agents.baseUrl = this.agent.baseUrl;
}
request.nodejsSet(agents);
}
request.open('POST', this.host, true);
request.setRequestHeader('Content-Type', 'application/json');
request.timeout = this.timeout;
request.withCredentials = this.withCredentials;
if (this.headers) {
this.headers.forEach(function (header) {
request.setRequestHeader(header.name, header.value);
});
}
return request;
};
/**
* Should be used to make async request
*
* @method send
* @param {Object} payload
* @param {Function} callback triggered on end with (err, result)
*/
HttpProvider.prototype.send = function (payload, callback) {
var _this = this;
var request = this._prepareRequest();
request.onreadystatechange = function () {
if (request.readyState === 4 && request.timeout !== 1) {
var result = request.responseText;
var error = null;
try {
result = JSON.parse(result);
}
catch (e) {
error = errors.InvalidResponse(request.responseText);
}
_this.connected = true;
callback(error, result);
}
};
request.ontimeout = function () {
_this.connected = false;
callback(errors.ConnectionTimeout(this.timeout));
};
try {
request.send(JSON.stringify(payload));
}
catch (error) {
this.connected = false;
callback(errors.InvalidConnection(this.host));
}
};
HttpProvider.prototype.disconnect = function () {
//NO OP
};
/**
* Returns the desired boolean.
*
* @method supportsSubscriptions
* @returns {boolean}
*/
HttpProvider.prototype.supportsSubscriptions = function () {
return false;
};
module.exports = HttpProvider;

View File

@ -0,0 +1,6 @@
node_modules
yarn-error.log
.idea
**/*.js
**/*.js.map
!wallaby.js

View File

@ -0,0 +1,4 @@
*
.idea
!dist/*
!package.json

View File

@ -0,0 +1,30 @@
# XMLHttpRequest polyfill for node.js
Based on [https://github.com/pwnall/node-xhr2/tree/bd6d48431ad93c8073811e5d4b77394dd637a85a](https://github.com/pwnall/node-xhr2/tree/bd6d48431ad93c8073811e5d4b77394dd637a85a)
* Adds support for cookies
* Adds in-project TypeScript type definitions
* Switched to TypeScript
### Cookies
* saved in `XMLHttpRequest.cookieJar`
* saved between redirects
* saved between requests
* can be cleared by doing:
```typescript
import * as Cookie from 'cookiejar';
XMLHttpRequest.cookieJar = Cookie.CookieJar();
```
### Aims
* Provide full XMLHttpRequest features to Angular Universal HttpClient &
`node-angular-http-client`
### Changelog
#### `1.1.0`
* added saving of cookies between requests, not just redirects
* bug fixes
* most tests from `xhr2` ported over and passing

View File

@ -0,0 +1,8 @@
export declare class SecurityError extends Error {
}
export declare class InvalidStateError extends Error {
}
export declare class NetworkError extends Error {
}
export declare class SyntaxError extends Error {
}

View File

@ -0,0 +1,2 @@
export * from './xml-http-request';
export { XMLHttpRequestEventTarget } from './xml-http-request-event-target';

View File

@ -0,0 +1,11 @@
import { XMLHttpRequestEventTarget } from './xml-http-request-event-target';
export declare class ProgressEvent {
type: string;
bubbles: boolean;
cancelable: boolean;
target: XMLHttpRequestEventTarget;
loaded: number;
lengthComputable: boolean;
total: number;
constructor(type: string);
}

View File

@ -0,0 +1,19 @@
import { ProgressEvent } from './progress-event';
export declare type ProgressEventListener = (event: ProgressEvent) => void;
export declare type ProgressEventListenerObject = {
handleEvent(event: ProgressEvent): void;
};
export declare type ProgressEventListenerOrEventListenerObject = ProgressEventListener | ProgressEventListenerObject;
export declare class XMLHttpRequestEventTarget {
onloadstart: ProgressEventListener | null;
onprogress: ProgressEventListener | null;
onabort: ProgressEventListener | null;
onerror: ProgressEventListener | null;
onload: ProgressEventListener | null;
ontimeout: ProgressEventListener | null;
onloadend: ProgressEventListener | null;
private listeners;
addEventListener(eventType: string, listener?: ProgressEventListenerOrEventListenerObject): void;
removeEventListener(eventType: string, listener?: ProgressEventListenerOrEventListenerObject): void;
dispatchEvent(event: ProgressEvent): boolean;
}

View File

@ -0,0 +1,12 @@
/// <reference types="node" />
import { XMLHttpRequestEventTarget } from './xml-http-request-event-target';
import { ClientRequest } from 'http';
export declare class XMLHttpRequestUpload extends XMLHttpRequestEventTarget {
private _contentType;
private _body;
constructor();
_reset(): void;
_setData(data?: string | Buffer | ArrayBuffer | ArrayBufferView): void;
_finalizeHeaders(headers: object, loweredHeaders: object): void;
_startUpload(request: ClientRequest): void;
}

View File

@ -0,0 +1,102 @@
/// <reference types="node" />
import { ProgressEvent } from './progress-event';
import { InvalidStateError, NetworkError, SecurityError, SyntaxError } from './errors';
import { ProgressEventListener, XMLHttpRequestEventTarget } from './xml-http-request-event-target';
import { XMLHttpRequestUpload } from './xml-http-request-upload';
import { Url } from 'url';
import { Agent as HttpAgent } from 'http';
import { Agent as HttpsAgent } from 'https';
export interface XMLHttpRequestOptions {
anon?: boolean;
}
export interface XHRUrl extends Url {
method?: string;
}
export declare class XMLHttpRequest extends XMLHttpRequestEventTarget {
static ProgressEvent: typeof ProgressEvent;
static InvalidStateError: typeof InvalidStateError;
static NetworkError: typeof NetworkError;
static SecurityError: typeof SecurityError;
static SyntaxError: typeof SyntaxError;
static XMLHttpRequestUpload: typeof XMLHttpRequestUpload;
static UNSENT: number;
static OPENED: number;
static HEADERS_RECEIVED: number;
static LOADING: number;
static DONE: number;
static cookieJar: any;
UNSENT: number;
OPENED: number;
HEADERS_RECEIVED: number;
LOADING: number;
DONE: number;
onreadystatechange: ProgressEventListener | null;
readyState: number;
response: string | ArrayBuffer | Buffer | object | null;
responseText: string;
responseType: string;
status: number;
statusText: string;
timeout: number;
upload: XMLHttpRequestUpload;
responseUrl: string;
withCredentials: boolean;
nodejsHttpAgent: HttpsAgent;
nodejsHttpsAgent: HttpsAgent;
nodejsBaseUrl: string | null;
private _anonymous;
private _method;
private _url;
private _sync;
private _headers;
private _loweredHeaders;
private _mimeOverride;
private _request;
private _response;
private _responseParts;
private _responseHeaders;
private _aborting;
private _error;
private _loadedBytes;
private _totalBytes;
private _lengthComputable;
private _restrictedMethods;
private _restrictedHeaders;
private _privateHeaders;
private _userAgent;
constructor(options?: XMLHttpRequestOptions);
open(method: string, url: string, async?: boolean, user?: string, password?: string): void;
setRequestHeader(name: string, value: any): void;
send(data?: string | Buffer | ArrayBuffer | ArrayBufferView): void;
abort(): void;
getResponseHeader(name: string): string;
getAllResponseHeaders(): string;
overrideMimeType(mimeType: string): void;
nodejsSet(options: {
httpAgent?: HttpAgent;
httpsAgent?: HttpsAgent;
baseUrl?: string;
}): void;
static nodejsSet(options: {
httpAgent?: HttpAgent;
httpsAgent?: HttpsAgent;
baseUrl?: string;
}): void;
private _setReadyState(readyState);
private _sendFile(data);
private _sendHttp(data?);
private _sendHxxpRequest();
private _finalizeHeaders();
private _onHttpResponse(request, response);
private _onHttpResponseData(response, data);
private _onHttpResponseEnd(response);
private _onHttpResponseClose(response);
private _onHttpTimeout(request);
private _onHttpRequestError(request, error);
private _dispatchProgress(eventType);
private _setError();
private _parseUrl(urlString, user?, password?);
private _parseResponseHeaders(response);
private _parseResponse();
private _parseResponseEncoding();
}

View File

@ -0,0 +1,4 @@
export class SecurityError extends Error {}
export class InvalidStateError extends Error {}
export class NetworkError extends Error {}
export class SyntaxError extends Error {}

View File

@ -0,0 +1,2 @@
export * from './xml-http-request';
export { XMLHttpRequestEventTarget } from './xml-http-request-event-target';

View File

@ -0,0 +1,48 @@
{
"name": "xhr2-cookies",
"version": "1.1.0",
"author": "Ionut Costica <ionut.costica@gmail.com>",
"license": "MIT",
"description": "XMLHttpRequest polyfill for node.js",
"repository": "git://github.com/souldreamer/xhr2-cookies.git",
"keywords": [
"XMLHttpRequest",
"cookies",
"xhr2"
],
"main": "dist/index.js",
"types": "dist/index.d.ts",
"dependencies": {
"cookiejar": "^2.1.1"
},
"devDependencies": {
"@types/body-parser": "^1.16.8",
"@types/cookie-parser": "^1.4.1",
"@types/express": "^4.0.39",
"@types/morgan": "^1.7.35",
"@types/node": "^6",
"ava": "^0.23.0",
"ava-ts": "^0.23.0",
"body-parser": "^1.18.2",
"cookie-parser": "^1.4.3",
"express": "^4.16.2",
"morgan": "^1.9.0",
"ts-loader": "^2.3.4",
"ts-node": "^3.3.0",
"typescript": "^2.5.2",
"webpack": "^3.5.5"
},
"scripts": {
"prepare": "tsc",
"test": "tsc -p ./test && ava-ts -v"
},
"ava": {
"files": [
"test/*.spec.ts"
],
"source": [
"*.ts",
"!dist/**/*"
]
}
}

View File

@ -0,0 +1,12 @@
import { XMLHttpRequestEventTarget } from './xml-http-request-event-target';
export class ProgressEvent {
bubbles = false;
cancelable = false;
target: XMLHttpRequestEventTarget;
loaded = 0;
lengthComputable = false;
total = 0;
constructor (public type: string) {}
}

View File

@ -0,0 +1,59 @@
import * as ava from 'ava';
import { XMLHttpRequest } from '../xml-http-request';
import { HttpServer } from './helpers/server';
import * as Cookie from 'cookiejar';
function contextualize<T>(getContext: () => T): ava.RegisterContextual<T> {
ava.test.beforeEach(t => {
Object.assign(t.context, getContext());
});
return ava.test;
}
const test = contextualize(() => ({
xhr: new XMLHttpRequest()
}));
test.before(async () => {
await HttpServer.serverStarted;
});
test.beforeEach(t => {
t.context.xhr = new XMLHttpRequest();
XMLHttpRequest.cookieJar = Cookie.CookieJar();
});
test('XMLHttpRequest sets cookies and passes them on on redirect', async t => {
const xhr = t.context.xhr;
t.plan(1);
await new Promise(resolve => {
xhr.open('GET', `http://localhost:${HttpServer.port}/_/redirect-cookie/test/works`);
xhr.withCredentials = true;
xhr.onload = () => {
t.is(xhr.responseText, 'works');
resolve();
};
xhr.send();
});
});
test('XMLHttpRequest sets cookies and uses them for subsequent calls', async t => {
let xhr = t.context.xhr;
t.plan(1);
await new Promise(resolve => {
xhr.open('GET', `http://localhost:${HttpServer.port}/_/set-cookie/second-test/works`);
xhr.withCredentials = true;
xhr.onload = resolve;
xhr.send();
});
xhr = new XMLHttpRequest();
await new Promise(resolve => {
xhr.open('GET', `http://localhost:${HttpServer.port}/_/print-cookie/second-test`);
xhr.withCredentials = true;
xhr.onload = () => {
t.is(xhr.responseText, 'works');
resolve();
};
xhr.send();
});
});

View File

@ -0,0 +1,101 @@
import * as ava from 'ava';
import { XMLHttpRequest } from '../xml-http-request';
import { ProgressEvent } from '../progress-event';
function contextualize<T>(getContext: () => T): ava.RegisterContextual<T> {
ava.test.beforeEach(t => {
Object.assign(t.context, getContext());
});
return ava.test;
}
const test = contextualize(() => ({
xhr: new XMLHttpRequest(),
loadEvent: new ProgressEvent('load')
}));
test.beforeEach(t => {
t.context.xhr = new XMLHttpRequest();
t.context.loadEvent = new ProgressEvent('load');
});
test('XMLHttpRequestEventTarget dispatchEvent works with a DOM0 listener', t => {
t.plan(1);
t.context.xhr.onload = () => t.pass();
t.context.xhr.dispatchEvent(t.context.loadEvent);
});
test('XMLHttpRequestEventTarget dispatchEvent works with a DOM2 listener', t => {
t.plan(1);
t.context.xhr.addEventListener('load', () => t.pass());
t.context.xhr.dispatchEvent(t.context.loadEvent);
});
test('XMLHttpRequestEventTarget dispatchEvent executes DOM2 listeners in order', t => {
t.plan(1);
let firstExecuted = false;
t.context.xhr.addEventListener('load', () => firstExecuted = true);
t.context.xhr.addEventListener('load', () => {
if (firstExecuted) { t.pass(); }
});
t.context.xhr.dispatchEvent(t.context.loadEvent);
});
test('XMLHttpRequestEventTarget removes a DOM2 listener correctly', t => {
t.plan(1);
const listener = () => t.pass();
t.context.xhr.addEventListener('load', listener);
t.context.xhr.dispatchEvent(t.context.loadEvent);
t.context.xhr.removeEventListener('load', listener);
t.context.xhr.dispatchEvent(t.context.loadEvent);
});
test('XMLHttpRequestEventTarget binds this correctly in a DOM0 listener', t => {
t.plan(1);
t.context.xhr.onload = function () { if (this === t.context.xhr) { t.pass(); } };
t.context.xhr.dispatchEvent(t.context.loadEvent);
});
test('XMLHttpRequestEventTarget binds this correctly in a DOM2 listener', t => {
t.plan(1);
t.context.xhr.addEventListener('load', function () { if (this === t.context.xhr) { t.pass(); } });
t.context.xhr.dispatchEvent(t.context.loadEvent);
});
test('XMLHttpRequestEventTarget sets target correctly in a DOM0 listener', t => {
t.plan(1);
t.context.xhr.onload = function (event) { if (event.target === t.context.xhr) { t.pass(); } };
t.context.xhr.dispatchEvent(t.context.loadEvent);
});
test('XMLHttpRequestEventTarget sets target correctly in a DOM2 listener', t => {
t.plan(1);
t.context.xhr.addEventListener('load', function (event) { if (event.target === t.context.xhr) { t.pass(); } });
t.context.xhr.dispatchEvent(t.context.loadEvent);
});
test('XMLHttpRequestEventTarget works with a DOM0 and two DOM2 listeners', t => {
t.plan(3);
t.context.xhr.addEventListener('load', () => t.pass());
t.context.xhr.onload = () => t.pass();
t.context.xhr.addEventListener('load', () => t.pass());
t.context.xhr.dispatchEvent(t.context.loadEvent);
});
test('XMLHttpRequestEventTarget does not invoke a DOM0 listener for a different event', t => {
t.plan(0);
['onerror', 'onloadstart', 'onprogress', 'onabort', 'ontimeout', 'onloadend']
.forEach(eventType => t.context.xhr[eventType] = () => t.pass());
t.context.xhr.dispatchEvent(t.context.loadEvent);
});
test('XMLHttpRequestEventTarget does not invoke a DOM2 listener for a different event', t => {
t.plan(0);
['error', 'loadstart', 'progress', 'abort', 'timeout', 'loadend']
.forEach(eventType => t.context.xhr.addEventListener(eventType, () => t.pass()));
t.context.xhr.dispatchEvent(t.context.loadEvent);
});
// TODO:
// * remove event listener from an event that had no listeners
// * remove non-existent event listener from an event that had listeners

View File

@ -0,0 +1,249 @@
import * as ava from 'ava';
import { XMLHttpRequest } from '../xml-http-request';
import { HttpServer } from './helpers/server';
function contextualize<T>(getContext: () => T): ava.RegisterContextual<T> {
ava.test.beforeEach(t => {
Object.assign(t.context, getContext());
});
return ava.test;
}
const test = contextualize(() => ({
xhr: new XMLHttpRequest(),
dripUrl: `http://localhost:${HttpServer.port}/_/drip`,
dripJson: {drips: 3, size: 1000, ms: 50, length: true},
}));
test.before(async () => {
await HttpServer.serverStarted;
XMLHttpRequest.nodejsSet({
baseUrl: HttpServer.testUrl().replace('https://', 'http://')
});
});
test.beforeEach(t => {
t.context.xhr = new XMLHttpRequest();
});
test('level 2 events for a successful fetch with Content-Length set', async t => {
let endFired = false;
let intermediateProgressFired = false;
const xhr = t.context.xhr;
await new Promise(resolve => {
['loadstart', 'progress', 'load', 'loadend', 'error', 'abort'].forEach(addCheckedEvent);
xhr.addEventListener('loadend', () => {
endFired = true;
resolve();
});
xhr.addEventListener('error', () => resolve());
xhr.open('POST', t.context.dripUrl);
xhr.send(JSON.stringify(t.context.dripJson));
});
t.true(intermediateProgressFired, 'at least one intermediate progress event was fired');
function addCheckedEvent(eventType: string) {
xhr.addEventListener(eventType, event => {
t.is(event.type, eventType, `event type is ${eventType}`);
t.is(event.target, xhr, 'event has correct target');
t.false(endFired, 'end is not fired');
t.false(event.bubbles, 'event does not bubble');
t.false(event.cancelable, 'event is not cancelable');
switch (eventType) {
case 'loadstart':
t.is(event.loaded, 0, 'on loadstart loaded = 0');
t.false(event.lengthComputable, 'on loadstart length is not computable');
t.is(event.total, 0, 'on loadstart event total is 0');
break;
case 'load':
case 'loadend':
t.is(event.loaded, 3000, 'on load/loadend loaded = 3000');
t.true(event.lengthComputable, 'on load/loadend length is computable');
t.is(event.total, 3000, 'on load/loadend event total is 0');
break;
case 'progress':
t.true(event.loaded >= 0, 'on progress: loaded >= 0');
t.true(event.loaded <= 3000, 'on progress: loaded <= 3000');
if (event.lengthComputable) {
t.is(event.total, 3000, 'on progress event when length is computable total is 3000');
} else {
t.is(event.total, 0, 'on progress event when length is not computable total is 0');
}
if (event.loaded > 0 && event.loaded < 3000) { intermediateProgressFired = true; }
break;
}
})
}
});
test('level 2 events for a successful fetch without Content-Length set', async t => {
let endFired = false;
let intermediateProgressFired = false;
const xhr = t.context.xhr;
t.context.dripJson = {...t.context.dripJson, length: false};
await new Promise(resolve => {
['loadstart', 'progress', 'load', 'loadend', 'error', 'abort'].forEach(addCheckedEvent);
xhr.addEventListener('loadend', () => {
endFired = true;
resolve();
});
xhr.open('POST', t.context.dripUrl);
xhr.send(JSON.stringify(t.context.dripJson));
});
t.true(intermediateProgressFired, 'at least one intermediate progress event was fired');
function addCheckedEvent(eventType: string) {
xhr.addEventListener(eventType, event => {
t.is(event.type, eventType, `event type is ${eventType}`);
t.is(event.target, xhr, 'event has correct target');
t.false(endFired, 'end is not fired');
t.false(event.bubbles, 'event does not bubble');
t.false(event.cancelable, 'event is not cancelable');
t.false(event.lengthComputable, 'length is not computable');
t.is(event.total, 0, 'when length is not computable total is 0');
switch (eventType) {
case 'loadstart':
t.is(event.loaded, 0, 'on loadstart loaded = 0');
break;
case 'load':
case 'loadend':
t.is(event.loaded, 3000, 'on load/loadend loaded = 3000');
break;
case 'progress':
t.true(event.loaded >= 0, 'on progress: loaded >= 0');
t.true(event.loaded <= 3000, 'on progress: loaded <= 3000');
if (event.loaded > 0 && event.loaded < 3000) { intermediateProgressFired = true; }
break;
}
})
}
});
test('level 2 events for a network error due to bad DNS', async t => {
let errorFired = false;
const xhr = t.context.xhr;
await new Promise(resolve => {
['loadstart', 'progress', 'load', 'loadend', 'error', 'abort'].forEach(addCheckedEvent);
xhr.addEventListener('loadend', () => resolve());
xhr.open('GET', 'https://broken.to.cause.an.xhrnetworkerror.com.a.com');
xhr.send();
});
t.true(errorFired, 'an error event was fired');
function addCheckedEvent(eventType: string) {
xhr.addEventListener(eventType, () => {
switch (eventType) {
case 'load':
case 'progress':
t.fail();
break;
case 'error':
errorFired = true;
break;
}
})
}
});
test('readystatechange for a successful fetch with Content-Length set', async t => {
let doneFired = false;
const xhr = t.context.xhr;
const states = [];
await new Promise(resolve => {
xhr.addEventListener('readystatechange', event => {
t.is(event.type, 'readystatechange', 'event type is correct');
t.false(doneFired, 'no readystatechange events after DONE');
t.is(event.target, xhr, 'event has correct target');
t.false(event.bubbles, 'event does not bubble');
t.false(event.cancelable, 'event is not cancelable');
states.push((event.target as XMLHttpRequest).readyState);
if ((event.target as XMLHttpRequest).readyState === XMLHttpRequest.DONE) {
doneFired = true;
resolve();
}
});
xhr.open('POST', t.context.dripUrl);
xhr.send(JSON.stringify(t.context.dripJson));
});
t.deepEqual(states, [
XMLHttpRequest.OPENED,
XMLHttpRequest.HEADERS_RECEIVED,
XMLHttpRequest.LOADING,
XMLHttpRequest.DONE
], 'right order of ready states');
});
test('readystatechange for a successful fetch without Content-Length set', async t => {
let doneFired = false;
const xhr = t.context.xhr;
const states = [];
t.context.dripJson = {...t.context.dripJson, length: false};
await new Promise(resolve => {
xhr.addEventListener('readystatechange', event => {
t.is(event.type, 'readystatechange', 'event type is correct');
t.false(doneFired, 'no readystatechange events after DONE');
t.is(event.target, xhr, 'event has correct target');
t.false(event.bubbles, 'event does not bubble');
t.false(event.cancelable, 'event is not cancelable');
t.false(event.lengthComputable, 'length is not computable');
t.is(event.total, 0, 'when length is not computable total is 0');
states.push((event.target as XMLHttpRequest).readyState);
if ((event.target as XMLHttpRequest).readyState === XMLHttpRequest.DONE) {
doneFired = true;
resolve();
}
});
xhr.open('POST', t.context.dripUrl);
xhr.send(JSON.stringify(t.context.dripJson));
});
t.deepEqual(states, [
XMLHttpRequest.OPENED,
XMLHttpRequest.HEADERS_RECEIVED,
XMLHttpRequest.LOADING,
XMLHttpRequest.DONE
], 'right order of ready states');
});
test('readystatechange for a network error due to bad DNS', async t => {
const xhr = t.context.xhr;
const states = [];
await new Promise(resolve => {
xhr.addEventListener('readystatechange', event => {
t.is(event.type, 'readystatechange', 'event type is correct');
t.is(event.target, xhr, 'event has correct target');
t.false(event.bubbles, 'event does not bubble');
t.false(event.cancelable, 'event is not cancelable');
states.push((event.target as XMLHttpRequest).readyState);
if ((event.target as XMLHttpRequest).readyState === XMLHttpRequest.DONE) {
resolve();
}
});
xhr.open('GET', 'https://broken.to.cause.an.xhrnetworkerror.com.a.com');
xhr.send();
});
t.deepEqual(states, [
XMLHttpRequest.OPENED,
XMLHttpRequest.DONE
], 'right order of ready states');
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

View File

@ -0,0 +1,189 @@
import * as ava from 'ava';
import { XMLHttpRequest } from '../xml-http-request';
import { HttpServer } from './helpers/server';
function contextualize<T>(getContext: () => T): ava.RegisterContextual<T> {
ava.test.beforeEach(t => {
Object.assign(t.context, getContext());
});
return ava.test;
}
const test = contextualize(() => ({
xhr: new XMLHttpRequest()
}));
test.before(async () => {
await HttpServer.serverStarted;
XMLHttpRequest.nodejsSet({
baseUrl: HttpServer.testUrl().replace('https://', 'http://')
});
});
test.beforeEach(t => {
t.context.xhr = new XMLHttpRequest();
});
test('#setRequestHeader with allowed headers should send the headers', async t => {
const xhr = t.context.xhr;
xhr.open('POST', `http://localhost:${HttpServer.port}/_/headers`);
xhr.responseType = 'text';
xhr.setRequestHeader('Authorization', 'lol');
xhr.setRequestHeader('X-Answer', '42');
xhr.setRequestHeader('X-Header-Name', 'value');
await new Promise(resolve => {
xhr.onload = () => {
t.regex(xhr.responseText, /^\{.*\}$/, 'response text looks like JSON');
const headers = JSON.parse(xhr.responseText);
t.true(headers.hasOwnProperty('authorization'), 'headers have authorization header');
t.is(headers.authorization, 'lol', 'authorization header is correct');
t.true(headers.hasOwnProperty('x-answer'), 'headers have x-answer header');
t.is(headers['x-answer'], '42', 'x-answer header is correct');
t.true(headers.hasOwnProperty('x-header-name'), 'headers have x-header-name header');
t.is(headers['x-header-name'], 'value', 'x-header-name header is correct');
resolve();
};
xhr.send('');
});
});
test('#setRequestHeader with a mix of allowed and forbidden headers should only send the allowed headers', async t => {
const xhr = t.context.xhr;
xhr.open('POST', `http://localhost:${HttpServer.port}/_/headers`);
xhr.responseType = 'text';
xhr.setRequestHeader('Authorization', 'lol');
xhr.setRequestHeader('Proxy-Authorization', 'evil:kitten');
xhr.setRequestHeader('Sec-Breach', 'yes please');
xhr.setRequestHeader('Host', 'www.google.com');
xhr.setRequestHeader('Origin', 'https://www.google.com');
xhr.setRequestHeader('X-Answer', '42');
await new Promise(resolve => {
xhr.onload = () => {
t.regex(xhr.responseText, /^\{.*\}$/, 'response text looks like JSON');
const headers = JSON.parse(xhr.responseText);
t.true(headers.hasOwnProperty('authorization'), 'headers have authorization header');
t.is(headers['authorization'], 'lol', 'authorization header is correct');
t.false(headers.hasOwnProperty('proxy-authorization'), 'headers do not have proxy-authorization header');
t.false(headers.hasOwnProperty('sec-breach'), 'headers do not have sec-breach header');
t.notRegex(headers['origin'] || '', /www\.google\.com/, 'header "origin" should not contain www.google.com');
t.notRegex(headers['host'] || '', /www\.google\.com/, 'header "host" should not contain www.google.com');
t.true(headers.hasOwnProperty('x-answer'), 'headers have x-answer header');
t.is(headers['x-answer'], '42', 'x-answer header is correct');
resolve();
};
xhr.send('');
});
});
test('#setRequestHeader with repeated headers should send all headers', async t => {
const xhr = t.context.xhr;
xhr.open('POST', `http://localhost:${HttpServer.port}/_/headers`);
xhr.responseType = 'text';
xhr.setRequestHeader('Authorization', 'troll');
xhr.setRequestHeader('Authorization', 'lol');
xhr.setRequestHeader('Authorization', 'lol');
xhr.setRequestHeader('X-Answer', '42');
await new Promise(resolve => {
xhr.onload = () => {
t.regex(xhr.responseText, /^\{.*\}$/, 'response text looks like JSON');
const headers = JSON.parse(xhr.responseText);
t.true(headers.hasOwnProperty('authorization'), 'headers have authorization header');
t.is(headers['authorization'], 'troll, lol, lol', 'authorization header is correct');
t.true(headers.hasOwnProperty('x-answer'), 'headers have x-answer header');
t.is(headers['x-answer'], '42', 'x-answer header is correct');
resolve();
};
xhr.send('');
});
});
test('#setRequestHeader with no headers should set the protected headers correctly', async t => {
const xhr = t.context.xhr;
xhr.open('POST', `http://localhost:${HttpServer.port}/_/headers`);
xhr.responseType = 'text';
xhr.setRequestHeader('Authorization', 'troll');
xhr.setRequestHeader('Authorization', 'lol');
xhr.setRequestHeader('Authorization', 'lol');
xhr.setRequestHeader('X-Answer', '42');
await new Promise(resolve => {
xhr.onload = () => {
t.regex(xhr.responseText, /^\{.*\}$/, 'response text looks like JSON');
const headers = JSON.parse(xhr.responseText);
t.true(headers.hasOwnProperty('connection'), 'headers have connection header');
t.is(headers['connection'], 'keep-alive', 'connection header is correct');
t.true(headers.hasOwnProperty('host'), 'headers have host header');
t.is(headers['host'], `localhost:${HttpServer.port}`, 'host header is correct');
t.true(headers.hasOwnProperty('user-agent'), 'headers have user-agent header');
t.regex(headers['user-agent'], /^Mozilla\//, 'user-agent header is correct');
resolve();
};
xhr.send('');
});
});
test('#getResponseHeader returns accessible headers, returns null for private headers, has headers on HEADERS_RECEIVED readyState', async t => {
const xhr = t.context.xhr;
xhr.open('POST', `http://localhost:${HttpServer.port}/_/get-headers`);
const headerJson = `{
"Accept-Ranges": "bytes",
"Content-Type": "application/xhr2; charset=utf-1337",
"Set-Cookie": "UserID=JohnDoe; Max-Age=3600; Version=1",
"X-Header": "one, more, value"
}`;
await new Promise(resolve => {
xhr.onloadend = () => {
t.is(xhr.getResponseHeader('AccEPt-RANgeS'), 'bytes', 'AccEPt-RANgeS works correctly');
t.is(xhr.getResponseHeader('content-Type'), 'application/xhr2; charset=utf-1337', 'content-Type works correctly');
t.is(xhr.getResponseHeader('X-Header'), 'one, more, value', 'X-Header works correctly');
t.is(xhr.getResponseHeader('set-cookie'), null, 'set-cookie works correctly');
resolve();
};
xhr.onreadystatechange = () => {
if (xhr.readyState !== XMLHttpRequest.HEADERS_RECEIVED) { return; }
t.is(xhr.getResponseHeader('AccEPt-RANgeS'), 'bytes', 'AccEPt-RANgeS works correctly when HEADERS_RECEIVED ready state');
};
xhr.send(headerJson);
});
});
test('#getAllResponseHeaders contains accessible headers, does not contain private headers, has headers on HEADERS_RECEIVED readyState', async t => {
const xhr = t.context.xhr;
xhr.open('POST', `http://localhost:${HttpServer.port}/_/get-headers`);
const headerJson = `{
"Accept-Ranges": "bytes",
"Content-Type": "application/xhr2; charset=utf-1337",
"Set-Cookie": "UserID=JohnDoe; Max-Age=3600; Version=1",
"X-Header": "one, more, value"
}`;
await new Promise(resolve => {
xhr.onloadend = () => {
const headers = xhr.getAllResponseHeaders();
t.regex(headers, /(\A|\r\n)accept-ranges: bytes(\r\n|\Z)/mi);
t.regex(headers, /(\A|\r\n)content-type: application\/xhr2; charset=utf-1337(\r\n|\Z)/mi);
t.regex(headers, /(\A|\r\n)X-Header: one, more, value(\r\n|\Z)/mi);
t.notRegex(headers, /(\A|\r\n)set-cookie:/mi);
resolve();
};
xhr.onreadystatechange = () => {
if (xhr.readyState !== XMLHttpRequest.HEADERS_RECEIVED) { return; }
const headers = xhr.getAllResponseHeaders();
t.regex(headers, /(\A|\r\n)accept-ranges: bytes(\r\n|\Z)/mi);
};
xhr.send(headerJson);
});
});
// TODO:
// * set request header after request opened should throw InvalidStateError
// *

View File

@ -0,0 +1,50 @@
export const certificate = `-----BEGIN CERTIFICATE-----
MIIDXjCCAkYCCQCgZ4DViKxZtTANBgkqhkiG9w0BAQsFADBxMQswCQYDVQQGEwJS
TzELMAkGA1UECAwCVE0xCzAJBgNVBAcMAlhYMQwwCgYDVQQKDANYWFgxDTALBgNV
BAsMBFhYWFgxEjAQBgNVBAMMCWxvY2FsaG9zdDEXMBUGCSqGSIb3DQEJARYIWEBY
WC5YWFgwHhcNMTcxMTEzMTQzMTE2WhcNMjAwOTAyMTQzMTE2WjBxMQswCQYDVQQG
EwJSTzELMAkGA1UECAwCVE0xCzAJBgNVBAcMAlhYMQwwCgYDVQQKDANYWFgxDTAL
BgNVBAsMBFhYWFgxEjAQBgNVBAMMCWxvY2FsaG9zdDEXMBUGCSqGSIb3DQEJARYI
WEBYWC5YWFgwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyJ+21siOW
oRkgVSpQMaUAw/R54GG98k9IEMQnGBoD7HlnX4avgz0fNaA/xNdQuKVZqR0oshCx
6ks6mX4z/nYHh4SNmQVmAH7mJnT5aqHVs4OplVU5ZmZNsBx7+7JEFk64G7k011rI
76MVjLrNYJSTlgrtYOcNJle6awCwmI2nsrHSJJeyMVOGUK8H9RDzsPPZIQS0u4wJ
P8mIAoln/mpgP5I2lNTM2FaokmQq4mEYErUsWf+DhSlmnbZFxt5V3r/xHWVrouig
RsjhFxoGRg3p0HoUR79Rc8LqbbMtibh1qSkXcHjue1rBcSYurQNPzdbf3R4WuUyb
lxhui0rfu8fFAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABphKcUJbdEhUpWF4EZE
BBl/uzE4/WXtQZdgz3fGpvpzmXZBRtbkdPR3jxBW1c9asCfb366dXRb8im6/p6ae
sAxZINMKIQ8KCIEb+StVMc4MvxASMm1SSz/kFuTCA2Q8vD5sHJrFcoKk6HKNEOLu
dALKpO8ZDuxjv036sCnjfyDue9psSccsLuAhfr2NLL5Ky9lWrJFi3b35D5UHrlK/
9mb9izRgZSC9+sZgpSyvIK6idKoWB4s9RpCn8itucFHHUDOvv8DdwvsF/5iVyWz7
R5uR4/qA8k7lbHHLosu2ELyx3N6Go0AskjxzsONPOKNlGIDllTx21mkIvTkGlJgs
8/4=
-----END CERTIFICATE-----
`;
export const key = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAsifttbIjlqEZIFUqUDGlAMP0eeBhvfJPSBDEJxgaA+x5Z1+G
r4M9HzWgP8TXULilWakdKLIQsepLOpl+M/52B4eEjZkFZgB+5iZ0+Wqh1bODqZVV
OWZmTbAce/uyRBZOuBu5NNdayO+jFYy6zWCUk5YK7WDnDSZXumsAsJiNp7Kx0iSX
sjFThlCvB/UQ87Dz2SEEtLuMCT/JiAKJZ/5qYD+SNpTUzNhWqJJkKuJhGBK1LFn/
g4UpZp22RcbeVd6/8R1la6LooEbI4RcaBkYN6dB6FEe/UXPC6m2zLYm4dakpF3B4
7ntawXEmLq0DT83W390eFrlMm5cYbotK37vHxQIDAQABAoIBAEUu8EbA2MUj5kgC
Cp59yN/VONkjY5GJyXPo3uN3npKrgDG+jOUXh+LYxlQ9MogsTDnXTHWDQKx2maQ1
+yZhyJ//5l++brQ/uQfTI1XALPx568UtMp1JwKymmUkkYwPBzev9CB0XDDA/rwst
TVV4DfqKJ9Aq807N9v9zkh8B/vCB9Ecvfco7Q2+AgrsLoaUDR9IwbiQXLqrqLA/F
tXh29Okwt7A3cv2C7Yd0rWyZLJi5iyH/lzcu33xGfaIAeN0fHtefKEhPU/yS69VM
9HbdDC44h0/psNyBt0dlrUYx32oYzF8EV4brrqcZTVUJNfCEqA16nTMKSmCJQdR8
nPJCRYECgYEA3U/0MyNDVa/OphEcBGLnXhRBeBGTGIP768gkn0Fw3Fgk1v6eqfKb
JqBujdgJjxbebo32OZeRLw92RBsfNY5IyIwVUKgZbtNkysgf612IhNoeBF7Ljz3r
BbSq3gwOHuUszCjO8/SjQn9bRLxVifrRD04SdHudMN4V2g98yoBBEdUCgYEAzhRZ
BWdOlLG2gAa8waPeuHUkwGU4zKly3zLSnbNvJJJ/wSTbuGmPQhLcWXq27gepHzZf
fvVJbpHrLHksh3fwdPusmygXD/s0gxMQJqJJledk1GEUnPjuuAImKvmeJWyX5lGq
APMh+M5ZB6CBu1dqapAs7nkOLCsSDGatRwc65jECgYBGI2q/MjPK2jbhxpZchYPR
+xVsmhVGNb4HUZzZpAHCs2SphnR+Y9br/PhMl+Ufph3EZ9VbFz/57CqNFxNjA77p
YAv5Te0RhIlzAs2q6C+1+vJ8bBaTRQpQ+psUWDm5bOQvp9c+1Y9QKdChDhcF7amH
8jRDGlINBLVkMHhaLR9yKQKBgQDIBfH+D66zHucPzvpJTYAxM+qvH9CIvfP0doT9
cptvOQ7tbpQho7vcGyhrZXPHCAJ8fC8msHhM7S8B5L924dCwC1QW6UuxRFdM3iTw
Ctc3u/gfN/dlAS3bxqI7VjvNAWFSuXM0JsmTkN3TTFR/fTKaKkSiVzeNYWTMSqDn
bzoZEQKBgFZcAbn2h86jYJ2tcznBriLI8rZBkPL9MFP4QM2Ksz5/8fsJ84EPsatg
700S1yasmDwaOgBLtSKsy7Rju5E3nebaPgLw3x92LiI07F97p2Y5ftSbRgslaih4
fDBm/C82anx0q9s4psw1oNnYj20c+imPIWvM7A0W85kmqcmQvzwZ
-----END RSA PRIVATE KEY-----
`;

View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
rm localhost.key.pem localhost.cert.pem
openssl req -nodes -newkey rsa:2048 -keyout localhost.key2.pem -x509 -days 1024 -out localhost.cert.pem
openssl rsa -in localhost.key2.pem -out localhost.key.pem

View File

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDXjCCAkYCCQCgZ4DViKxZtTANBgkqhkiG9w0BAQsFADBxMQswCQYDVQQGEwJS
TzELMAkGA1UECAwCVE0xCzAJBgNVBAcMAlhYMQwwCgYDVQQKDANYWFgxDTALBgNV
BAsMBFhYWFgxEjAQBgNVBAMMCWxvY2FsaG9zdDEXMBUGCSqGSIb3DQEJARYIWEBY
WC5YWFgwHhcNMTcxMTEzMTQzMTE2WhcNMjAwOTAyMTQzMTE2WjBxMQswCQYDVQQG
EwJSTzELMAkGA1UECAwCVE0xCzAJBgNVBAcMAlhYMQwwCgYDVQQKDANYWFgxDTAL
BgNVBAsMBFhYWFgxEjAQBgNVBAMMCWxvY2FsaG9zdDEXMBUGCSqGSIb3DQEJARYI
WEBYWC5YWFgwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyJ+21siOW
oRkgVSpQMaUAw/R54GG98k9IEMQnGBoD7HlnX4avgz0fNaA/xNdQuKVZqR0oshCx
6ks6mX4z/nYHh4SNmQVmAH7mJnT5aqHVs4OplVU5ZmZNsBx7+7JEFk64G7k011rI
76MVjLrNYJSTlgrtYOcNJle6awCwmI2nsrHSJJeyMVOGUK8H9RDzsPPZIQS0u4wJ
P8mIAoln/mpgP5I2lNTM2FaokmQq4mEYErUsWf+DhSlmnbZFxt5V3r/xHWVrouig
RsjhFxoGRg3p0HoUR79Rc8LqbbMtibh1qSkXcHjue1rBcSYurQNPzdbf3R4WuUyb
lxhui0rfu8fFAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABphKcUJbdEhUpWF4EZE
BBl/uzE4/WXtQZdgz3fGpvpzmXZBRtbkdPR3jxBW1c9asCfb366dXRb8im6/p6ae
sAxZINMKIQ8KCIEb+StVMc4MvxASMm1SSz/kFuTCA2Q8vD5sHJrFcoKk6HKNEOLu
dALKpO8ZDuxjv036sCnjfyDue9psSccsLuAhfr2NLL5Ky9lWrJFi3b35D5UHrlK/
9mb9izRgZSC9+sZgpSyvIK6idKoWB4s9RpCn8itucFHHUDOvv8DdwvsF/5iVyWz7
R5uR4/qA8k7lbHHLosu2ELyx3N6Go0AskjxzsONPOKNlGIDllTx21mkIvTkGlJgs
8/4=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAsifttbIjlqEZIFUqUDGlAMP0eeBhvfJPSBDEJxgaA+x5Z1+G
r4M9HzWgP8TXULilWakdKLIQsepLOpl+M/52B4eEjZkFZgB+5iZ0+Wqh1bODqZVV
OWZmTbAce/uyRBZOuBu5NNdayO+jFYy6zWCUk5YK7WDnDSZXumsAsJiNp7Kx0iSX
sjFThlCvB/UQ87Dz2SEEtLuMCT/JiAKJZ/5qYD+SNpTUzNhWqJJkKuJhGBK1LFn/
g4UpZp22RcbeVd6/8R1la6LooEbI4RcaBkYN6dB6FEe/UXPC6m2zLYm4dakpF3B4
7ntawXEmLq0DT83W390eFrlMm5cYbotK37vHxQIDAQABAoIBAEUu8EbA2MUj5kgC
Cp59yN/VONkjY5GJyXPo3uN3npKrgDG+jOUXh+LYxlQ9MogsTDnXTHWDQKx2maQ1
+yZhyJ//5l++brQ/uQfTI1XALPx568UtMp1JwKymmUkkYwPBzev9CB0XDDA/rwst
TVV4DfqKJ9Aq807N9v9zkh8B/vCB9Ecvfco7Q2+AgrsLoaUDR9IwbiQXLqrqLA/F
tXh29Okwt7A3cv2C7Yd0rWyZLJi5iyH/lzcu33xGfaIAeN0fHtefKEhPU/yS69VM
9HbdDC44h0/psNyBt0dlrUYx32oYzF8EV4brrqcZTVUJNfCEqA16nTMKSmCJQdR8
nPJCRYECgYEA3U/0MyNDVa/OphEcBGLnXhRBeBGTGIP768gkn0Fw3Fgk1v6eqfKb
JqBujdgJjxbebo32OZeRLw92RBsfNY5IyIwVUKgZbtNkysgf612IhNoeBF7Ljz3r
BbSq3gwOHuUszCjO8/SjQn9bRLxVifrRD04SdHudMN4V2g98yoBBEdUCgYEAzhRZ
BWdOlLG2gAa8waPeuHUkwGU4zKly3zLSnbNvJJJ/wSTbuGmPQhLcWXq27gepHzZf
fvVJbpHrLHksh3fwdPusmygXD/s0gxMQJqJJledk1GEUnPjuuAImKvmeJWyX5lGq
APMh+M5ZB6CBu1dqapAs7nkOLCsSDGatRwc65jECgYBGI2q/MjPK2jbhxpZchYPR
+xVsmhVGNb4HUZzZpAHCs2SphnR+Y9br/PhMl+Ufph3EZ9VbFz/57CqNFxNjA77p
YAv5Te0RhIlzAs2q6C+1+vJ8bBaTRQpQ+psUWDm5bOQvp9c+1Y9QKdChDhcF7amH
8jRDGlINBLVkMHhaLR9yKQKBgQDIBfH+D66zHucPzvpJTYAxM+qvH9CIvfP0doT9
cptvOQ7tbpQho7vcGyhrZXPHCAJ8fC8msHhM7S8B5L924dCwC1QW6UuxRFdM3iTw
Ctc3u/gfN/dlAS3bxqI7VjvNAWFSuXM0JsmTkN3TTFR/fTKaKkSiVzeNYWTMSqDn
bzoZEQKBgFZcAbn2h86jYJ2tcznBriLI8rZBkPL9MFP4QM2Ksz5/8fsJ84EPsatg
700S1yasmDwaOgBLtSKsy7Rju5E3nebaPgLw3x92LiI07F97p2Y5ftSbRgslaih4
fDBm/C82anx0q9s4psw1oNnYj20c+imPIWvM7A0W85kmqcmQvzwZ
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCyJ+21siOWoRkg
VSpQMaUAw/R54GG98k9IEMQnGBoD7HlnX4avgz0fNaA/xNdQuKVZqR0oshCx6ks6
mX4z/nYHh4SNmQVmAH7mJnT5aqHVs4OplVU5ZmZNsBx7+7JEFk64G7k011rI76MV
jLrNYJSTlgrtYOcNJle6awCwmI2nsrHSJJeyMVOGUK8H9RDzsPPZIQS0u4wJP8mI
Aoln/mpgP5I2lNTM2FaokmQq4mEYErUsWf+DhSlmnbZFxt5V3r/xHWVrouigRsjh
FxoGRg3p0HoUR79Rc8LqbbMtibh1qSkXcHjue1rBcSYurQNPzdbf3R4WuUyblxhu
i0rfu8fFAgMBAAECggEARS7wRsDYxSPmSAIKnn3I39U42SNjkYnJc+je43eekquA
Mb6M5ReH4tjGVD0yiCxMOddMdYNArHaZpDX7JmHIn//mX75utD+5B9MjVcAs/Hnr
xS0ynUnArKaZSSRjA8HN6/0IHRcMMD+vCy1NVXgN+oon0CrzTs32/3OSHwH+8IH0
Ry99yjtDb4CCuwuhpQNH0jBuJBcuquosD8W1eHb06TC3sDdy/YLth3StbJksmLmL
If+XNy7ffEZ9ogB43R8e158oSE9T/JLr1Uz0dt0MLjiHT+mw3IG3R2WtRjHfahjM
XwRXhuuupxlNVQk18ISoDXqdMwpKYIlB1Hyc8kJFgQKBgQDdT/QzI0NVr86mERwE
YudeFEF4EZMYg/vryCSfQXDcWCTW/p6p8psmoG6N2AmPFt5ujfY5l5EvD3ZEGx81
jkjIjBVQqBlu02TKyB/rXYiE2h4EXsuPPesFtKreDA4e5SzMKM7z9KNCf1tEvFWJ
+tEPThJ0e50w3hXaD3zKgEER1QKBgQDOFFkFZ06UsbaABrzBo964dSTAZTjMqXLf
MtKds28kkn/BJNu4aY9CEtxZerbuB6kfNl9+9UlukesseSyHd/B0+6ybKBcP+zSD
ExAmokmV52TUYRSc+O64AiYq+Z4lbJfmUaoA8yH4zlkHoIG7V2pqkCzueQ4sKxIM
Zq1HBzrmMQKBgEYjar8yM8raNuHGllyFg9H7FWyaFUY1vgdRnNmkAcKzZKmGdH5j
1uv8+EyX5R+mHcRn1VsXP/nsKo0XE2MDvulgC/lN7RGEiXMCzaroL7X68nxsFpNF
ClD6mxRYObls5C+n1z7Vj1Ap0KEOFwXtqYfyNEMaUg0EtWQweFotH3IpAoGBAMgF
8f4PrrMe5w/O+klNgDEz6q8f0Ii98/R2hP1ym285Du1ulCGju9wbKGtlc8cIAnx8
LyaweEztLwHkv3bh0LALVBbpS7FEV0zeJPAK1ze7+B8392UBLdvGojtWO80BYVK5
czQmyZOQ3dNMVH99MpoqRKJXN41hZMxKoOdvOhkRAoGAVlwBufaHzqNgna1zOcGu
IsjytkGQ8v0wU/hAzYqzPn/x+wnzgQ+xq2DvTRLXJqyYPBo6AEu1IqzLtGO7kTed
5to+AvDfH3YuIjTsX3unZjl+1JtGCyVqKHh8MGb8LzZqfHSr2zimzDWg2diPbRz6
KY8ha8zsDRbzmSapyZC/PBk=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,8 @@
import * as fs from 'fs';
import * as path from 'path';
const PNGBuffer = fs.readFileSync(path.join(__dirname, '../fixtures/hello.png'));
const PNGUint8Array = new Uint8Array(PNGBuffer);
const PNGArrayBuffer = PNGUint8Array.buffer as ArrayBuffer;
export { PNGBuffer, PNGArrayBuffer, PNGUint8Array };

View File

@ -0,0 +1,228 @@
import * as express from 'express';
import { Application, NextFunction, Request, Response } from 'express';
import * as http from 'http';
import { Server as HttpServer } from 'http';
import * as https from 'https';
import { Server as HttpsServer } from 'https';
import * as bodyParser from 'body-parser';
import * as cookieParser from 'cookie-parser';
import { certificate, key } from './certificates/certificate';
import * as path from 'path';
export class XhrServer {
app: Application;
server: HttpServer | HttpsServer;
serverStarted: Promise<void>;
private setServerStarted: () => void;
constructor(public port = 8080, private useHttps = false) {
this.serverStarted = new Promise(resolve => this.setServerStarted = resolve);
this.createApp();
}
testUrl() {
return `https://localhost:${this.port}/test/html/browser_test.html`;
}
sslCertificate() {
return this.useHttps ? certificate : null;
}
sslKey() {
return this.useHttps ? key : null;
}
createApp() {
this.app = express();
this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({ extended: true }));
this.app.use(cookieParser());
this.app.use((request: Request, response: Response, next: NextFunction) => {
response.header('Access-Control-Allow-Origin', '*');
response.header('Access-Control-Allow-Methods', 'DELETE,GET,POST,PUT');
response.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Cookie');
next();
});
this.app.get('/test/fixtures/hello.txt', (request: Request, response: Response) => {
const body = 'Hello, world!';
response.header('Content-Type', 'text/plain; charset=utf-8');
response.header('Content-Length', body.length.toString());
response.end(body);
});
this.app.get('/test/fixtures/hello.json', (request: Request, response: Response) => {
const body = '{"hello": "world", "answer": 42}\n';
response.header('Content-Type', 'application/json');
response.header('Content-Length', body.length.toString());
response.end(body);
});
this.app.get('/test/html/browser_test.html', (request: Request, response: Response) => {
const body = '<p>Test</p>';
response.header('Content-Type', 'text/html');
response.header('Content-Length', body.length.toString());
response.end(body);
});
this.app.all('/_/method', (request: Request, response: Response) => {
const body = request.method;
response.header('Content-Type', 'text/plain; charset=utf-8');
response.header('Content-Length', body.length.toString());
response.end(body);
});
// Echoes the request body. Used to test send(data).
this.app.post('/_/echo', (request: Request, response: Response) => {
if (request.headers['content-type']) {
response.header('Content-Type', request.headers['content-type']);
}
if (request.headers['content-length']) {
response.header('Content-Length', request.headers['content-length']);
}
request.on('data', chunk => response.write(chunk));
request.on('end', () => response.end());
});
// Lists the request headers. Used to test setRequestHeader().
this.app.all('/_/headers', (request: Request, response: Response) => {
const body = JSON.stringify(request.headers);
response.header('Content-Type', 'application/json');
response.header('Content-Length', body.length.toString());
response.end(body);
});
// Sets the response headers in the request. Used to test getResponse*().
this.app.post('/_/get-headers', (request: Request, response: Response) => {
let jsonString = '';
request.on('data', chunk => jsonString += chunk);
request.on('end', () => {
const headers = JSON.parse(jsonString);
for (let name in headers) {
if (headers.hasOwnProperty(name)) {
response.header(name, headers[name]);
}
}
response.header('Content-Length', '0');
response.end('');
});
});
// Sets every response detail. Used for error testing.
this.app.post('/_/response', (request: Request, response: Response) => {
let jsonString = '';
request.on('data', chunk => jsonString += chunk);
request.on('end', () => {
const json = JSON.parse(jsonString);
response.writeHead(json.code, json.status, json.headers);
if (json.body) { response.write(json.body); }
response.end();
});
});
// Sends data in small chunks. Used for event testing.
this.app.post('/_/drip', (request: Request, response: Response) => {
request.connection.setNoDelay();
let jsonString = '';
request.on('data', chunk => jsonString += chunk);
request.on('end', () => {
const json = JSON.parse(jsonString);
let sentDrips = 0;
const drip = new Array(json.size + 1).join('.');
response.header('Content-Type', 'text/plain');
if (json.length) { response.header('Content-Length', (json.drips * json.size).toString()); }
(function sendDrip() {
response.write(drip);
sentDrips++;
if (sentDrips >= json.drips) { return response.end(); }
setTimeout(sendDrip, json.ms);
})();
});
});
// Returns a HTTP redirect. Used to test the redirection handling code.
this.app.all('/_/redirect/:status/:next', (request: Request, response: Response) => {
response.statusCode = +request.params.status;
response.header('Location', `${request.protocol}://${request.get('host')}/_/${request.params.next}`);
const body = '<p>This is supposed to have a redirect link</p>';
response.header('Content-Type', 'text/html');
response.header('Content-Length', body.length.toString());
response.header('X-Redirect-Header', 'should not show up');
response.end(body);
});
// Sets a cookie
this.app.all('/_/set-cookie/:name/:value', (request: Request, response: Response) => {
response.cookie(request.params.name, request.params.value);
response.header('Content-Type', 'text/plain');
response.header('Content-Length', '0');
response.end();
});
// Redirects and sets a cookie.
this.app.all('/_/redirect-cookie/:name/:value', (request: Request, response: Response) => {
response.cookie(request.params.name, request.params.value);
response.redirect(301,
`${request.protocol}://${request.get('host')}/_/print-cookie/${request.params.name}`
);
});
// Read cookie + print its value.
this.app.get('/_/print-cookie/:name', (request: Request, response: Response) => {
const cookieValue = request.cookies[request.params.name];
response.header('Content-Type', 'text/plain');
response.header('Content-Length', cookieValue.length.toString());
response.end(cookieValue);
});
// Requested when the browser test suite completes.
this.app.get('/_/end', (request: Request, response: Response) => {
const failed = request.query.hasOwnProperty('failed') ? +request.query.failed : 1;
const total = request.query.hasOwnProperty('total') ? +request.query.total : 0;
const passed = total - failed;
const exitCode = failed ? 1 : 0;
console.log(`${passed} passed, ${failed} failed`);
response.header('Content-Type', 'image/png');
response.header('Content-Length', '0');
response.end('');
if (!process.env.hasOwnProperty('NO_EXIT')) {
this.server.close();
process.exit(exitCode);
}
});
this.app.use(express.static(path.join(__dirname, '../../')));
this.createServer();
}
createServer() {
this.server = this.useHttps
? https.createServer({
cert: this.sslCertificate(),
key: this.sslKey()
}, this.app)
: http.createServer(this.app);
this.server.on('error', (error) => {
if (error.code !== 'EADDRINUSE') { return; }
this.port += 2;
this.createServer();
});
this.server.on('listening', () => {
this.setServerStarted();
});
this.server.listen(this.port);
}
}
const HttpServer = new XhrServer(8900, false);
const HttpsServer = new XhrServer(8901, true);
export { HttpServer, HttpsServer };

View File

@ -0,0 +1,120 @@
import * as ava from 'ava';
import { XMLHttpRequest } from '../xml-http-request';
function contextualize<T>(getContext: () => T): ava.RegisterContextual<T> {
ava.test.beforeEach(t => {
Object.assign(t.context, getContext());
});
return ava.test;
}
const test = contextualize(() => ({
xhr: new XMLHttpRequest(),
customXhr: new XMLHttpRequest()
}));
test.beforeEach(t => {
t.context.xhr = new XMLHttpRequest();
t.context.customXhr = new XMLHttpRequest();
});
test('XMLHttpRequest.nodejsSet with a httpAgent option', t => {
const customAgent = {custom: 'httpAgent'};
const defaultAgent = XMLHttpRequest.prototype.nodejsHttpAgent;
const agent = {mocking: 'httpAgent'};
t.context.customXhr.nodejsHttpAgent = customAgent as any;
XMLHttpRequest.nodejsSet({httpAgent: agent} as any);
t.is(t.context.xhr.nodejsHttpAgent, agent as any, 'sets the default nodejsHttpAgent');
t.is(t.context.customXhr.nodejsHttpAgent, customAgent as any, 'does not interfere with custom nodejsHttpAgent settings');
XMLHttpRequest.nodejsSet({httpAgent: defaultAgent});
});
test('XMLHttpRequest.nodejsSet with a httpsAgent option', t => {
const customAgent = {custom: 'httpsAgent'};
const defaultAgent = XMLHttpRequest.prototype.nodejsHttpsAgent;
const agent = {mocking: 'httpsAgent'};
t.context.customXhr.nodejsHttpsAgent = customAgent as any;
XMLHttpRequest.nodejsSet({httpsAgent: agent} as any);
t.is(t.context.xhr.nodejsHttpsAgent, agent as any, 'sets the default nodejsHttpsAgent');
t.is(t.context.customXhr.nodejsHttpsAgent, customAgent as any, 'does not interfere with custom nodejsHttpsAgent settings');
XMLHttpRequest.nodejsSet({httpsAgent: defaultAgent});
});
test('XMLHttpRequest.nodejsSet with a baseUrl option', t => {
const customBaseUrl = 'http://custom.url/base';
const defaultBaseUrl = XMLHttpRequest.prototype.nodejsBaseUrl;
const baseUrl = 'http://localhost/base';
t.context.customXhr.nodejsBaseUrl = customBaseUrl;
XMLHttpRequest.nodejsSet({baseUrl});
t.is(t.context.xhr.nodejsBaseUrl, baseUrl, 'sets the default nodejsBaseUrl');
t.is(t.context.customXhr.nodejsBaseUrl, customBaseUrl, 'does not interfere with custom nodejsBaseUrl settings');
XMLHttpRequest.nodejsSet({baseUrl: defaultBaseUrl});
});
test('#nodejsSet with a httpAgent option', t => {
const customAgent = {custom: 'httpAgent'};
t.context.customXhr.nodejsSet({httpAgent: customAgent as any});
t.is(t.context.customXhr.nodejsHttpAgent, customAgent as any, 'sets nodejsHttpAgent on the XHR instance');
t.not(t.context.xhr.nodejsHttpAgent, customAgent as any, 'does not interfere with default nodejsHttpAgent settings');
});
test('#nodejsSet with a httpsAgent option', t => {
const customAgent = {custom: 'httpsAgent'};
t.context.customXhr.nodejsSet({httpsAgent: customAgent as any});
t.is(t.context.customXhr.nodejsHttpsAgent, customAgent as any, 'sets nodejsHttpsAgent on the XHR instance');
t.not(t.context.xhr.nodejsHttpsAgent, customAgent as any, 'does not interfere with default nodejsHttpsAgent settings');
});
test('base URL parsing with null baseUrl', t => {
const xhr = t.context.xhr as any;
xhr.nodejsSet({baseUrl: null});
const parsedUrl = xhr._parseUrl('http://www.domain.com/path');
t.truthy(parsedUrl);
t.true(parsedUrl.hasOwnProperty('href'));
t.is(parsedUrl.href, 'http://www.domain.com/path');
});
test('base URL parsing with a (protocol, domain, filePath) baseUrl parses an absolute URL', t => {
const xhr = t.context.xhr as any;
xhr.nodejsSet({baseUrl: 'https://base.url/dir/file.html'});
const parsedUrl = xhr._parseUrl('http://www.domain.com/path');
t.truthy(parsedUrl);
t.true(parsedUrl.hasOwnProperty('href'));
t.is(parsedUrl.href, 'http://www.domain.com/path');
});
test('base URL parsing with a (protocol, domain, filePath) baseUrl parses a path-relative URL', t => {
const xhr = t.context.xhr as any;
xhr.nodejsSet({baseUrl: 'https://base.url/dir/file.html'});
const parsedUrl = xhr._parseUrl('path/to.js');
t.truthy(parsedUrl);
t.true(parsedUrl.hasOwnProperty('href'));
t.is(parsedUrl.href, 'https://base.url/dir/path/to.js');
});
test('base URL parsing with a (protocol, domain, filePath) baseUrl parses a path-relative URL with ..', t => {
const xhr = t.context.xhr as any;
xhr.nodejsSet({baseUrl: 'https://base.url/dir/file.html'});
const parsedUrl = xhr._parseUrl('../path/to.js');
t.truthy(parsedUrl);
t.true(parsedUrl.hasOwnProperty('href'));
t.is(parsedUrl.href, 'https://base.url/path/to.js');
});
test('base URL parsing with a (protocol, domain, filePath) baseUrl parses a host-relative URL', t => {
const xhr = t.context.xhr as any;
xhr.nodejsSet({baseUrl: 'https://base.url/dir/file.html'});
const parsedUrl = xhr._parseUrl('/path/to.js');
t.truthy(parsedUrl);
t.true(parsedUrl.hasOwnProperty('href'));
t.is(parsedUrl.href, 'https://base.url/path/to.js');
});
test('base URL parsing with a (protocol, domain, filePath) baseUrl parses a protocol-relative URL', t => {
const xhr = t.context.xhr as any;
xhr.nodejsSet({baseUrl: 'https://base.url/dir/file.html'});
const parsedUrl = xhr._parseUrl('//domain.com/path/to.js');
t.truthy(parsedUrl);
t.true(parsedUrl.hasOwnProperty('href'));
t.is(parsedUrl.href, 'https://domain.com/path/to.js');
});

View File

@ -0,0 +1,120 @@
import * as ava from 'ava';
import { XMLHttpRequest } from '../xml-http-request';
import { HttpServer } from './helpers/server';
function contextualize<T>(getContext: () => T): ava.RegisterContextual<T> {
ava.test.beforeEach(t => {
Object.assign(t.context, getContext());
});
return ava.test;
}
const test = contextualize(() => ({
xhr: new XMLHttpRequest()
}));
test.before(async () => {
await HttpServer.serverStarted;
});
test.beforeEach(t => {
t.context.xhr = new XMLHttpRequest();
});
test('XMLHttpRequest when redirected issues a GET for the next location', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.open('POST', `http://localhost:${HttpServer.port}/_/redirect/302/method`);
xhr.onload = () => {
t.regex(xhr.responseText, /GET/i);
resolve();
};
xhr.onerror = () => {
t.fail();
resolve();
};
xhr.send('This should be dropped during the redirect');
});
});
test('XMLHttpRequest when redirected does not return the redirect headers', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.open('GET', `http://localhost:${HttpServer.port}/_/redirect/302/method`);
xhr.onload = () => {
t.is(xhr.getResponseHeader('Content-Type'), 'text/plain; charset=utf-8');
t.falsy(xhr.getResponseHeader('X-Redirect-Header'));
resolve();
};
xhr.onerror = () => {
t.fail();
resolve();
};
xhr.send();
});
});
test('XMLHttpRequest when redirected persists custom request headers across redirects', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.open('GET', `http://localhost:${HttpServer.port}/_/redirect/302/headers`);
xhr.setRequestHeader('X-Redirect-Test', 'should be preserved');
xhr.onload = () => {
t.regex(xhr.responseText, /^\{.*\}$/);
const headers = JSON.parse(xhr.responseText);
t.is(headers.connection, 'keep-alive');
t.true(headers.hasOwnProperty('host'));
t.is(headers.host, `localhost:${HttpServer.port}`);
t.true(headers.hasOwnProperty('x-redirect-test'));
t.is(headers['x-redirect-test'], 'should be preserved');
resolve();
};
xhr.onerror = () => {
t.fail();
resolve();
};
xhr.send();
});
});
test('XMLHttpRequest when redirected drops content-related headers across redirects', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.open('GET', `http://localhost:${HttpServer.port}/_/redirect/302/headers`);
xhr.setRequestHeader('X-Redirect-Test', 'should be preserved');
xhr.onload = () => {
t.regex(xhr.responseText, /^\{.*\}$/);
const headers = JSON.parse(xhr.responseText);
t.is(headers.connection, 'keep-alive');
t.true(headers.hasOwnProperty('host'));
t.is(headers.host, `localhost:${HttpServer.port}`);
t.true(headers.hasOwnProperty('x-redirect-test'));
t.is(headers['x-redirect-test'], 'should be preserved');
t.false(headers.hasOwnProperty('content-type'));
t.false(headers.hasOwnProperty('content-length'));
resolve();
};
xhr.onerror = () => {
t.fail();
resolve();
};
xhr.send();
});
});
test('XMLHttpRequest when redirected provides the final responseURL', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.open('GET', `http://localhost:${HttpServer.port}/_/redirect/302/method`);
xhr.setRequestHeader('X-Redirect-Test', 'should be preserved');
xhr.onload = () => {
t.is(xhr.responseUrl, `http://localhost:${HttpServer.port}/_/method`);
resolve();
};
xhr.onerror = () => {
t.fail();
resolve();
};
xhr.send();
});
});

View File

@ -0,0 +1,134 @@
import * as ava from 'ava';
import { XMLHttpRequest } from '../xml-http-request';
import { HttpServer } from './helpers/server';
import { PNGArrayBuffer, PNGBuffer } from './helpers/png';
function contextualize<T>(getContext: () => T): ava.RegisterContextual<T> {
ava.test.beforeEach(t => {
Object.assign(t.context, getContext());
});
return ava.test;
}
const test = contextualize(() => ({
xhr: new XMLHttpRequest(),
jsonUrl: '',
jsonString: '',
imageUrl: ''
}));
test.before(async () => {
await HttpServer.serverStarted;
});
test.beforeEach(t => {
t.context.xhr = new XMLHttpRequest();
t.context.jsonUrl = `http://localhost:${HttpServer.port}/test/fixtures/hello.json`;
t.context.jsonString = '{"hello": "world", "answer": 42}\n';
t.context.imageUrl = `http://localhost:${HttpServer.port}/test/fixtures/hello.png`;
});
test('XMLHttpRequest #responseType text reads a JSON file into a String', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.addEventListener('load', () => {
t.is(xhr.response, t.context.jsonString);
t.is(xhr.responseText, t.context.jsonString);
resolve();
});
xhr.open('GET', t.context.jsonUrl);
xhr.responseType = 'text';
xhr.send();
});
});
test('XMLHttpRequest #responseType json reads a JSON file into a parsed JSON object', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.addEventListener('readystatechange', () => {
if (xhr.readyState !== XMLHttpRequest.DONE) { return; }
t.deepEqual(xhr.response, { hello: 'world', answer: 42 });
resolve();
});
xhr.open('GET', t.context.jsonUrl);
xhr.responseType = 'json';
xhr.send();
});
});
test('XMLHttpRequest #responseType json produces null when reading a non-JSON file', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.addEventListener('loadend', () => {
t.is(xhr.response, null);
resolve();
});
xhr.open('GET', `http://localhost:${HttpServer.port}/test/fixtures/hello.txt`);
xhr.responseType = 'json';
xhr.send();
});
});
test('XMLHttpRequest #responseType arraybuffer reads a JSON file into an ArrayBuffer', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.addEventListener('loadend', () => {
t.true(xhr.response instanceof ArrayBuffer);
if (!(xhr.response instanceof ArrayBuffer)) { return; }
const view = new Uint8Array(xhr.response);
const response = Array.from(view).map(viewElement => String.fromCharCode(viewElement)).join('');
t.is(response, t.context.jsonString);
resolve();
});
xhr.open('GET', t.context.jsonUrl);
xhr.responseType = 'arraybuffer';
xhr.send();
});
});
test('XMLHttpRequest #responseType arraybuffer reads a binary file into an ArrayBuffer', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.addEventListener('loadend', () => {
t.true(xhr.response instanceof ArrayBuffer);
if (!(xhr.response instanceof ArrayBuffer)) { return; }
t.deepEqual(xhr.response, PNGArrayBuffer);
resolve();
});
xhr.open('GET', t.context.imageUrl);
xhr.responseType = 'arraybuffer';
xhr.send();
});
});
test('XMLHttpRequest #responseType buffer reads a JSON file into a node.js Buffer', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.addEventListener('loadend', () => {
t.true(xhr.response instanceof Buffer);
if (!(xhr.response instanceof Buffer)) { return; }
const response = Array.from(xhr.response).map(viewElement => String.fromCharCode(viewElement)).join('');
t.is(response, t.context.jsonString);
resolve();
});
xhr.open('GET', t.context.jsonUrl);
xhr.responseType = 'buffer';
xhr.send();
});
});
test('XMLHttpRequest #responseType buffer reads a binary file into a node.js Buffer', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.addEventListener('loadend', () => {
t.true(xhr.response instanceof Buffer);
if (!(xhr.response instanceof Buffer)) { return; }
t.deepEqual(xhr.response, PNGBuffer);
resolve();
});
xhr.open('GET', t.context.imageUrl);
xhr.responseType = 'buffer';
xhr.send();
});
});

View File

@ -0,0 +1,46 @@
import * as ava from 'ava';
import { XMLHttpRequest } from '../xml-http-request';
import { HttpServer } from './helpers/server';
function contextualize<T>(getContext: () => T): ava.RegisterContextual<T> {
ava.test.beforeEach(t => {
Object.assign(t.context, getContext());
});
return ava.test;
}
const test = contextualize(() => ({
xhr: new XMLHttpRequest()
}));
test.before(async () => {
await HttpServer.serverStarted;
});
test.beforeEach(t => {
t.context.xhr = new XMLHttpRequest();
});
test('XMLHttpRequest #responseURL provides the URL of the response', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.open('GET', `http://localhost:${HttpServer.port}/_/method`);
xhr.onload = () => {
t.is(xhr.responseUrl, `http://localhost:${HttpServer.port}/_/method`);
resolve();
};
xhr.send();
});
});
test('XMLHttpRequest #responseURL ignores the hash fragment', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.open('GET', `http://localhost:${HttpServer.port}/_/method#foo`);
xhr.onload = () => {
t.is(xhr.responseUrl, `http://localhost:${HttpServer.port}/_/method`);
resolve();
};
xhr.send();
});
});

View File

@ -0,0 +1,137 @@
import * as ava from 'ava';
import { XMLHttpRequest } from '../xml-http-request';
import { HttpServer } from './helpers/server';
import { PNGArrayBuffer, PNGUint8Array } from './helpers/png';
function contextualize<T>(getContext: () => T): ava.RegisterContextual<T> {
ava.test.beforeEach(t => {
Object.assign(t.context, getContext());
});
return ava.test;
}
const test = contextualize(() => ({
xhr: new XMLHttpRequest()
}));
test.before(async () => {
await HttpServer.serverStarted;
});
test.beforeEach(t => {
t.context.xhr = new XMLHttpRequest();
t.context.xhr.open('POST', `http://localhost:${HttpServer.port}/_/echo`);
});
test('XMLHttpRequest #send works with ASCII DOMStrings', async t => {
const xhr = t.context.xhr;
t.plan(2);
await new Promise(resolve => {
xhr.onload = () => {
t.regex(xhr.getResponseHeader('content-type'), /^text\/plain(;\s?charset=UTF-8)?$/);
t.is(xhr.responseText, 'Hello world!');
resolve();
};
xhr.onerror = () => { t.fail(); return resolve(); };
xhr.send('Hello world!');
});
});
test('XMLHttpRequest #send works with UTF-8 DOMStrings', async t => {
const xhr = t.context.xhr;
t.plan(2);
await new Promise(resolve => {
xhr.onload = () => {
t.regex(xhr.getResponseHeader('content-type'), /^text\/plain(;\s?charset=UTF-8)?$/);
t.is(xhr.responseText, '世界你好!');
resolve();
};
xhr.send('世界你好!');
});
});
test('XMLHttpRequest #send works with ArrayBufferViews', async t => {
const xhr = t.context.xhr;
t.plan(2);
await new Promise(resolve => {
xhr.responseType = 'arraybuffer';
xhr.onload = () => {
t.is(xhr.getResponseHeader('content-type'), null);
if (!(xhr.response instanceof ArrayBuffer)) { t.fail(); return resolve(); }
t.deepEqual(new Uint8Array(xhr.response), PNGUint8Array);
resolve();
};
xhr.onerror = () => { t.fail(); return resolve(); };
xhr.send(PNGUint8Array);
});
});
test('XMLHttpRequest #send works with ArrayBufferViews with set index and length', async t => {
const xhr = t.context.xhr;
t.plan(2);
const arrayBufferView10 = new Uint8Array(PNGArrayBuffer, 10, 42);
await new Promise(resolve => {
xhr.responseType = 'arraybuffer';
xhr.onload = () => {
t.is(xhr.getResponseHeader('content-type'), null);
if (!(xhr.response instanceof ArrayBuffer)) { t.fail(); return resolve(); }
t.deepEqual(new Uint8Array(xhr.response), arrayBufferView10);
resolve();
};
xhr.onerror = () => { t.fail(); return resolve(); };
xhr.send(arrayBufferView10);
});
});
test('XMLHttpRequest #send works with ArrayBuffers', async t => {
const xhr = t.context.xhr;
t.plan(2);
await new Promise(resolve => {
xhr.responseType = 'arraybuffer';
xhr.onload = () => {
t.is(xhr.getResponseHeader('content-type'), null);
if (!(xhr.response instanceof ArrayBuffer)) { t.fail(); return resolve(); }
t.deepEqual(xhr.response, PNGArrayBuffer);
resolve();
};
xhr.onerror = () => { t.fail(); return resolve(); };
xhr.send(PNGArrayBuffer);
});
});
test('XMLHttpRequest #send works with node.js Buffers', async t => {
const xhr = t.context.xhr;
const buffer = Buffer.alloc(PNGUint8Array.length);
for (let i = 0; i < PNGUint8Array.length; i++) { buffer.writeUInt8(PNGUint8Array[i], i); }
t.plan(2);
await new Promise(resolve => {
xhr.responseType = 'buffer';
xhr.onload = () => {
t.is(xhr.getResponseHeader('content-type'), null);
if (!(xhr.response instanceof Buffer)) { t.fail(); return resolve(); }
t.deepEqual(new Uint8Array(xhr.response), PNGUint8Array);
resolve();
};
xhr.onerror = () => { t.fail(); return resolve(); };
xhr.send(PNGArrayBuffer);
});
});
test('XMLHttpRequest #send sets POST headers correctly when given null data', async t => {
const xhr = t.context.xhr;
xhr.open('POST', `http://localhost:${HttpServer.port}/_/headers`);
await new Promise(resolve => {
xhr.responseType = 'text';
xhr.onload = () => {
t.regex(xhr.responseText, /^\{.*\}$/);
const headers = JSON.parse(xhr.responseText);
t.true(headers.hasOwnProperty('content-length'));
t.is(headers['content-length'], '0');
t.false(headers.hasOwnProperty('content-type'));
resolve();
};
xhr.onerror = () => { t.fail(); return resolve(); };
xhr.send();
});
});

View File

@ -0,0 +1,84 @@
import * as ava from 'ava';
import { XMLHttpRequest } from '../xml-http-request';
import { HttpServer } from './helpers/server';
function contextualize<T>(getContext: () => T): ava.RegisterContextual<T> {
ava.test.beforeEach(t => {
Object.assign(t.context, getContext());
});
return ava.test;
}
const test = contextualize(() => ({
xhr: new XMLHttpRequest(),
okUrl: '',
errorUrl: '',
errorJson: ''
}));
test.before(async () => {
await HttpServer.serverStarted;
});
test.beforeEach(t => {
t.context.xhr = new XMLHttpRequest();
t.context.okUrl = `http://localhost:${HttpServer.port}/test/fixtures/hello.txt`;
t.context.errorUrl = `http://localhost:${HttpServer.port}/_/response`;
t.context.errorJson = JSON.stringify({
code: 401,
status: 'Unauthorized',
body: JSON.stringify({error: 'Credential error'}),
headers: {
'Content-Type': 'application/json',
'Content-Length': '28'
}
});
});
test('XMLHttpRequest #status is 200 for a normal request', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.open('GET', t.context.okUrl);
let done = false;
xhr.addEventListener('readystatechange', () => {
if (done) { return; }
if (xhr.readyState < XMLHttpRequest.HEADERS_RECEIVED) {
t.is(xhr.status, 0);
t.is(xhr.statusText, '');
} else {
t.is(xhr.status, 200);
t.truthy(xhr.statusText);
t.not(xhr.statusText, '');
if (xhr.readyState === XMLHttpRequest.DONE) {
done = true;
resolve();
}
}
});
xhr.send();
});
});
test('XMLHttpRequest #status returns the server-reported status', async t => {
const xhr = t.context.xhr;
await new Promise(resolve => {
xhr.open('POST', t.context.errorUrl);
let done = false;
xhr.addEventListener('readystatechange', () => {
if (done) { return; }
if (xhr.readyState < XMLHttpRequest.HEADERS_RECEIVED) {
t.is(xhr.status, 0);
t.is(xhr.statusText, '');
} else {
t.is(xhr.status, 401);
t.truthy(xhr.statusText);
t.not(xhr.statusText, '');
if (xhr.readyState === XMLHttpRequest.DONE) {
done = true;
resolve();
}
}
});
xhr.send(t.context.errorJson);
});
});

View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"sourceMap": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"lib": [ "es5", "es6", "es2016", "es2017", "dom" ]
},
"include": [
"*.spec.ts",
"./png.d.ts"
]
}

View File

@ -0,0 +1,135 @@
import * as ava from 'ava';
import { XMLHttpRequest } from '../xml-http-request';
import { HttpServer, HttpsServer } from './helpers/server';
import * as https from 'https';
const agent = new https.Agent({
rejectUnauthorized: true,
ca: HttpsServer.sslCertificate()
});
XMLHttpRequest.nodejsSet({
httpsAgent: agent
});
function contextualize<T>(getContext: () => T): ava.RegisterContextual<T> {
ava.test.beforeEach(t => {
Object.assign(t.context, getContext());
});
return ava.test;
}
const test = contextualize(() => ({
xhr: new XMLHttpRequest()
}));
test.before(async t => {
await HttpServer.serverStarted;
await HttpsServer.serverStarted;
XMLHttpRequest.nodejsSet({
baseUrl: HttpServer.testUrl().replace('https://', 'http://')
});
});
test.beforeEach(t => {
t.context.xhr = new XMLHttpRequest();
});
test('constructor', t => {
const xhr = t.context.xhr;
t.is(xhr.readyState, XMLHttpRequest.UNSENT, 'sets readyState to UNSENT');
t.is(xhr.timeout, 0, 'sets timeout to 0');
t.is(xhr.responseType, '', 'sets responseType to ""');
t.is(xhr.status, 0, 'sets status to 0');
t.is(xhr.statusText, '', 'sets statusText to ""');
});
test('#open throws SecurityError on CONNECT', t => {
t.throws(() => t.context.xhr.open('CONNECT', `http://localhost:${HttpServer.port}/test`), XMLHttpRequest.SecurityError);
});
test('#open with a GET for a local https request', t => {
const xhr = t.context.xhr;
xhr.open('GET', `https://localhost:${HttpsServer.port}/test/fixtures/hello.txt`);
t.is(xhr.readyState, XMLHttpRequest.OPENED, 'sets readyState to OPENED');
t.is(xhr.status, 0, 'keeps status 0');
t.is(xhr.statusText, '', 'keeps statusText ""');
});
test('#send on a local http GET kicks off the request', async t => {
const xhr = t.context.xhr;
xhr.open('GET', `http://localhost:${HttpServer.port}/test/fixtures/hello.txt`);
t.plan(2);
await new Promise((resolve, reject) => {
xhr.onload = (event) => {
t.is(xhr.status, 200, 'the status is 200');
t.is(xhr.responseText, 'Hello, world!', 'the text is correct');
resolve();
};
xhr.onerror = (event) => {
reject(event);
};
xhr.send();
});
});
test('#send on a local https GET kicks off the request', async t => {
const xhr = t.context.xhr;
xhr.open('GET', `https://localhost:${HttpsServer.port}/test/fixtures/hello.txt`);
t.plan(2);
await new Promise((resolve, reject) => {
xhr.onload = (event) => {
t.is(xhr.status, 200, 'the status is 200');
t.is(xhr.responseText, 'Hello, world!', 'the text is correct');
resolve();
};
xhr.onerror = (event) => {
reject(event);
};
xhr.send();
});
});
test('on a local relative GET it kicks off the request', async t => {
const xhr = t.context.xhr;
xhr.open('GET', '../fixtures/hello.txt');
t.plan(2);
await new Promise((resolve, reject) => {
xhr.onload = (event) => {
t.is(xhr.status, 200, 'the status is 200');
t.is(xhr.responseText, 'Hello, world!', 'the text is correct');
resolve();
};
xhr.onerror = (event) => {
reject(event);
};
xhr.send();
});
});
test('on a local gopher GET #open + #send throws a NetworkError', async t => {
const xhr = t.context.xhr;
t.throws(() => {
xhr.open('GET', `gopher:localhost:${HttpServer.port}`);
xhr.send();
}, XMLHttpRequest.NetworkError);
});
test('readyState constants', t => {
t.is(XMLHttpRequest.UNSENT < XMLHttpRequest.OPENED, true, 'UNSENT < OPENED');
t.is(XMLHttpRequest.OPENED < XMLHttpRequest.HEADERS_RECEIVED, true, 'OPENED < HEADERS_RECEIVED');
t.is(XMLHttpRequest.HEADERS_RECEIVED < XMLHttpRequest.LOADING, true, 'HEADERS_RECEIVED < LOADING');
t.is(XMLHttpRequest.LOADING < XMLHttpRequest.DONE, true, 'LOADING < DONE');
});
test('XMLHttpRequest constants match the instance constants', t => {
const xhr = t.context.xhr;
t.is(XMLHttpRequest.UNSENT, xhr.UNSENT, 'UNSENT');
t.is(XMLHttpRequest.OPENED, xhr.OPENED, 'OPENED');
t.is(XMLHttpRequest.HEADERS_RECEIVED, xhr.HEADERS_RECEIVED, 'HEADERS_RECEIVED');
t.is(XMLHttpRequest.LOADING, xhr.LOADING, 'LOADING');
t.is(XMLHttpRequest.DONE, xhr.DONE, 'DONE');
});

View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"outDir": "dist",
"sourceMap": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"declaration": true,
"declarationDir": "dist",
"lib": [ "es5", "es6", "es2016", "es2017", "dom" ]
},
"files": [
"index.ts"
]
}

View File

@ -0,0 +1,115 @@
{
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs"
],
"import-spacing": true,
"indent": [
true,
"tabs"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"typeof-compare": true,
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}

View File

@ -0,0 +1,27 @@
module.exports = function (wallaby) {
return {
files: ['**/*.ts', '*.ts', '!test/**/*'],
tests: ['test/**/*.ts'],
env: {type: 'node'},
testFramework: 'ava',
recycle: true,
name: 'XMLHttpRequest 2+',
slowTestThreshold: 300,
reportUnhandledPromises: false,
workers: {
// initial: 1,
// regular: 1,
recycle: true
},
compilers: {
'**/*.ts': wallaby.compilers.typeScript({
target: 'es2015',
module: 'commonjs',
sourceMap: true,
experimentalDecorators: true,
emitDecoratorMetadata: true,
lib: ['es5', 'es6', 'es2016', 'es2017', 'dom']
})
}
}
};

View File

@ -0,0 +1,49 @@
import { ProgressEvent } from './progress-event';
export type ProgressEventListener = (event: ProgressEvent) => void;
export type ProgressEventListenerObject = {handleEvent(event: ProgressEvent): void};
export type ProgressEventListenerOrEventListenerObject = ProgressEventListener | ProgressEventListenerObject;
export class XMLHttpRequestEventTarget {
onloadstart: ProgressEventListener | null;
onprogress: ProgressEventListener | null;
onabort: ProgressEventListener | null;
onerror: ProgressEventListener | null;
onload: ProgressEventListener | null;
ontimeout: ProgressEventListener | null;
onloadend: ProgressEventListener | null;
private listeners: {[eventType: string]: ProgressEventListener[]} = {};
addEventListener(eventType: string, listener?: ProgressEventListenerOrEventListenerObject) {
eventType = eventType.toLowerCase();
this.listeners[eventType] = this.listeners[eventType] || [];
this.listeners[eventType].push((listener as ProgressEventListenerObject).handleEvent || (listener as ProgressEventListener));
}
removeEventListener(eventType: string, listener?: ProgressEventListenerOrEventListenerObject) {
eventType = eventType.toLowerCase();
if (!this.listeners[eventType]) { return; }
const index = this.listeners[eventType].indexOf((listener as ProgressEventListenerObject).handleEvent || (listener as ProgressEventListener));
if (index < 0) { return; }
this.listeners[eventType].splice(index, 1);
}
dispatchEvent(event: ProgressEvent) {
const eventType = event.type.toLowerCase();
event.target = this; // TODO: set event.currentTarget?
if (this.listeners[eventType]) {
for (let listener of this.listeners[eventType]) {
listener.call(this, event);
}
}
const listener = this[`on${eventType}`];
if (listener) {
listener.call(this, event);
}
return true;
}
}

View File

@ -0,0 +1,57 @@
import { XMLHttpRequestEventTarget } from './xml-http-request-event-target';
import { ClientRequest } from 'http';
export class XMLHttpRequestUpload extends XMLHttpRequestEventTarget {
private _contentType: string | null = null;
private _body = null;
constructor() {
super();
this._reset();
}
_reset() {
this._contentType = null;
this._body = null;
}
_setData(data?: string | Buffer | ArrayBuffer | ArrayBufferView) {
if (data == null) { return; }
if (typeof data === 'string') {
if (data.length !== 0) {
this._contentType = 'text/plain;charset=UTF-8';
}
this._body = Buffer.from(data, 'utf-8');
} else if (Buffer.isBuffer(data)) {
this._body = data;
} else if (data instanceof ArrayBuffer) {
const body = Buffer.alloc(data.byteLength);
const view = new Uint8Array(data);
for (let i = 0; i < data.byteLength; i++) { body[i] = view[i]; }
this._body = body;
} else if (data.buffer && data.buffer instanceof ArrayBuffer) {
const body = Buffer.alloc(data.byteLength);
const offset = data.byteOffset;
const view = new Uint8Array(data.buffer);
for (let i = 0; i < data.byteLength; i++) { body[i] = view[i + offset]; }
this._body = body;
} else {
throw new Error(`Unsupported send() data ${data}`);
}
}
_finalizeHeaders(headers: object, loweredHeaders: object) {
if (this._contentType && !loweredHeaders['content-type']) {
headers['Content-Type'] = this._contentType;
}
if (this._body) {
headers['Content-Length'] = this._body.length.toString();
}
}
_startUpload(request: ClientRequest) {
if (this._body) { request.write(this._body); }
request.end();
}
}

View File

@ -0,0 +1,471 @@
import * as http from 'http';
import * as https from 'https';
import * as os from 'os';
import * as url from 'url';
import { ProgressEvent } from './progress-event';
import { InvalidStateError, NetworkError, SecurityError, SyntaxError } from './errors';
import { ProgressEventListener, XMLHttpRequestEventTarget } from './xml-http-request-event-target';
import { XMLHttpRequestUpload } from './xml-http-request-upload';
import { Url } from 'url';
import { Agent as HttpAgent, ClientRequest, IncomingMessage, RequestOptions as RequestOptionsHttp } from 'http';
import { Agent as HttpsAgent } from 'https';
import * as Cookie from 'cookiejar';
export interface XMLHttpRequestOptions {
anon?: boolean;
}
export interface XHRUrl extends Url {
method?: string;
}
export class XMLHttpRequest extends XMLHttpRequestEventTarget {
static ProgressEvent = ProgressEvent;
static InvalidStateError = InvalidStateError;
static NetworkError = NetworkError;
static SecurityError = SecurityError;
static SyntaxError = SyntaxError;
static XMLHttpRequestUpload = XMLHttpRequestUpload;
static UNSENT = 0;
static OPENED = 1;
static HEADERS_RECEIVED = 2;
static LOADING = 3;
static DONE = 4;
static cookieJar = Cookie.CookieJar();
UNSENT = XMLHttpRequest.UNSENT;
OPENED = XMLHttpRequest.OPENED;
HEADERS_RECEIVED = XMLHttpRequest.HEADERS_RECEIVED;
LOADING = XMLHttpRequest.LOADING;
DONE = XMLHttpRequest.DONE;
onreadystatechange: ProgressEventListener | null = null;
readyState: number = XMLHttpRequest.UNSENT;
response: string | ArrayBuffer | Buffer | object | null = null;
responseText = '';
responseType = '';
status = 0; // TODO: UNSENT?
statusText = '';
timeout = 0;
upload = new XMLHttpRequestUpload();
responseUrl = '';
withCredentials = false;
nodejsHttpAgent: HttpsAgent;
nodejsHttpsAgent: HttpsAgent;
nodejsBaseUrl: string | null;
private _anonymous: boolean;
private _method: string | null = null;
private _url: XHRUrl | null = null;
private _sync = false;
private _headers: {[header: string]: string} = {};
private _loweredHeaders: {[lowercaseHeader: string]: string} = {};
private _mimeOverride: string | null = null; // TODO: is type right?
private _request: ClientRequest | null = null;
private _response: IncomingMessage | null = null;
private _responseParts: Buffer[] | null = null;
private _responseHeaders: {[lowercaseHeader: string]: string} | null = null;
private _aborting = null; // TODO: type?
private _error = null; // TODO: type?
private _loadedBytes = 0;
private _totalBytes = 0;
private _lengthComputable = false;
private _restrictedMethods = {CONNECT: true, TRACE: true, TRACK: true};
private _restrictedHeaders = {
'accept-charset': true,
'accept-encoding': true,
'access-control-request-headers': true,
'access-control-request-method': true,
connection: true,
'content-length': true,
cookie: true,
cookie2: true,
date: true,
dnt: true,
expect: true,
host: true,
'keep-alive': true,
origin: true,
referer: true,
te: true,
trailer: true,
'transfer-encoding': true,
upgrade: true,
'user-agent': true,
via: true
};
private _privateHeaders = {'set-cookie': true, 'set-cookie2': true};
//Redacted private information (${os.type()} ${os.arch()}) node.js/${process.versions.node} v8/${process.versions.v8} from the original version @ github
//Pretend to be tor-browser https://blog.torproject.org/browser-fingerprinting-introduction-and-challenges-ahead/
private _userAgent = `Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0`;
constructor(options: XMLHttpRequestOptions = {}) {
super();
this._anonymous = options.anon || false;
}
open(method: string, url: string, async = true, user?: string, password?: string) {
method = method.toUpperCase();
if (this._restrictedMethods[method]) { throw new XMLHttpRequest.SecurityError(`HTTP method ${method} is not allowed in XHR`)};
const xhrUrl = this._parseUrl(url, user, password);
if (this.readyState === XMLHttpRequest.HEADERS_RECEIVED || this.readyState === XMLHttpRequest.LOADING) {
// TODO(pwnall): terminate abort(), terminate send()
}
this._method = method;
this._url = xhrUrl;
this._sync = !async;
this._headers = {};
this._loweredHeaders = {};
this._mimeOverride = null;
this._setReadyState(XMLHttpRequest.OPENED);
this._request = null;
this._response = null;
this.status = 0;
this.statusText = '';
this._responseParts = [];
this._responseHeaders = null;
this._loadedBytes = 0;
this._totalBytes = 0;
this._lengthComputable = false;
}
setRequestHeader(name: string, value: any) {
if (this.readyState !== XMLHttpRequest.OPENED) { throw new XMLHttpRequest.InvalidStateError('XHR readyState must be OPENED'); }
const loweredName = name.toLowerCase();
if (this._restrictedHeaders[loweredName] || /^sec-/.test(loweredName) || /^proxy-/.test(loweredName)) {
console.warn(`Refused to set unsafe header "${name}"`);
return;
}
value = value.toString();
if (this._loweredHeaders[loweredName] != null) {
name = this._loweredHeaders[loweredName];
this._headers[name] = `${this._headers[name]}, ${value}`;
} else {
this._loweredHeaders[loweredName] = name;
this._headers[name] = value;
}
}
send(data?: string | Buffer | ArrayBuffer | ArrayBufferView) {
if (this.readyState !== XMLHttpRequest.OPENED) { throw new XMLHttpRequest.InvalidStateError('XHR readyState must be OPENED'); }
if (this._request) { throw new XMLHttpRequest.InvalidStateError('send() already called'); }
switch (this._url.protocol) {
case 'file:':
return this._sendFile(data);
case 'http:':
case 'https:':
return this._sendHttp(data);
default:
throw new XMLHttpRequest.NetworkError(`Unsupported protocol ${this._url.protocol}`);
}
}
abort() {
if (this._request == null) { return; }
this._request.abort();
this._setError();
this._dispatchProgress('abort');
this._dispatchProgress('loadend');
}
getResponseHeader(name: string) {
if (this._responseHeaders == null || name == null) { return null; }
const loweredName = name.toLowerCase();
return this._responseHeaders.hasOwnProperty(loweredName)
? this._responseHeaders[name.toLowerCase()]
: null;
}
getAllResponseHeaders() {
if (this._responseHeaders == null) { return ''; }
return Object.keys(this._responseHeaders).map(key => `${key}: ${this._responseHeaders[key]}`).join('\r\n');
}
overrideMimeType(mimeType: string) {
if (this.readyState === XMLHttpRequest.LOADING || this.readyState === XMLHttpRequest.DONE) { throw new XMLHttpRequest.InvalidStateError('overrideMimeType() not allowed in LOADING or DONE'); }
this._mimeOverride = mimeType.toLowerCase();
}
nodejsSet(options: {httpAgent?: HttpAgent, httpsAgent?: HttpsAgent, baseUrl?: string }) {
this.nodejsHttpAgent = options.httpAgent || this.nodejsHttpAgent;
this.nodejsHttpsAgent = options.httpsAgent || this.nodejsHttpsAgent;
if (options.hasOwnProperty('baseUrl')) {
if (options.baseUrl != null) {
const parsedUrl = url.parse(options.baseUrl, false, true);
if (!parsedUrl.protocol) {
throw new XMLHttpRequest.SyntaxError("baseUrl must be an absolute URL")
}
}
this.nodejsBaseUrl = options.baseUrl;
}
}
static nodejsSet(options: {httpAgent?: HttpAgent, httpsAgent?: HttpsAgent, baseUrl?: string }) {
XMLHttpRequest.prototype.nodejsSet(options);
}
private _setReadyState(readyState: number) {
this.readyState = readyState;
this.dispatchEvent(new ProgressEvent('readystatechange'));
}
private _sendFile(data: any) {
// TODO
throw new Error('Protocol file: not implemented');
}
private _sendHttp(data?: string | Buffer | ArrayBuffer | ArrayBufferView) {
if (this._sync) { throw new Error('Synchronous XHR processing not implemented'); }
if (data && (this._method === 'GET' || this._method === 'HEAD')) {
console.warn(`Discarding entity body for ${this._method} requests`);
data = null;
} else {
data = data || '';
}
this.upload._setData(data);
this._finalizeHeaders();
this._sendHxxpRequest();
}
private _sendHxxpRequest() {
if (this.withCredentials) {
const cookie = XMLHttpRequest.cookieJar
.getCookies(
Cookie.CookieAccessInfo(this._url.hostname, this._url.pathname, this._url.protocol === 'https:')
).toValueString();
this._headers.cookie = this._headers.cookie2 = cookie;
}
const [hxxp, agent] = this._url.protocol === 'http:' ? [http, this.nodejsHttpAgent] : [https, this.nodejsHttpsAgent];
const requestMethod: (options: RequestOptionsHttp) => ClientRequest = hxxp.request.bind(hxxp);
const request = requestMethod({
hostname: this._url.hostname,
port: +this._url.port,
path: this._url.path,
auth: this._url.auth,
method: this._method,
headers: this._headers,
agent
});
this._request = request;
if (this.timeout) { request.setTimeout(this.timeout, () => this._onHttpTimeout(request)); }
request.on('response', response => this._onHttpResponse(request, response));
request.on('error', error => this._onHttpRequestError(request, error));
this.upload._startUpload(request);
if (this._request === request) { this._dispatchProgress('loadstart'); }
}
private _finalizeHeaders() {
this._headers = {
...this._headers,
Connection: 'keep-alive',
Host: this._url.host,
'User-Agent': this._userAgent,
...this._anonymous ? {Referer: 'about:blank'} : {}
};
this.upload._finalizeHeaders(this._headers, this._loweredHeaders);
}
private _onHttpResponse(request: ClientRequest, response: IncomingMessage) {
if (this._request !== request) { return; }
if (this.withCredentials && (response.headers['set-cookie'] || response.headers['set-cookie2'])) {
XMLHttpRequest.cookieJar
.setCookies(response.headers['set-cookie'] || response.headers['set-cookie2']);
}
if ([301, 302, 303, 307, 308].indexOf(response.statusCode) >= 0) {
this._url = this._parseUrl(response.headers.location);
this._method = 'GET';
if (this._loweredHeaders['content-type']) {
delete this._headers[this._loweredHeaders['content-type']];
delete this._loweredHeaders['content-type'];
}
if (this._headers['Content-Type'] != null) {
delete this._headers['Content-Type'];
}
delete this._headers['Content-Length'];
this.upload._reset();
this._finalizeHeaders();
this._sendHxxpRequest();
return;
}
this._response = response;
this._response.on('data', data => this._onHttpResponseData(response, data));
this._response.on('end', () => this._onHttpResponseEnd(response));
this._response.on('close', () => this._onHttpResponseClose(response));
this.responseUrl = this._url.href.split('#')[0];
this.status = response.statusCode;
this.statusText = http.STATUS_CODES[this.status];
this._parseResponseHeaders(response);
const lengthString = this._responseHeaders['content-length'] || '';
this._totalBytes = +lengthString;
this._lengthComputable = !!lengthString;
this._setReadyState(XMLHttpRequest.HEADERS_RECEIVED);
}
private _onHttpResponseData(response: IncomingMessage, data: string | Buffer) {
if (this._response !== response) { return; }
this._responseParts.push(Buffer.from(data as any));
this._loadedBytes += data.length;
if (this.readyState !== XMLHttpRequest.LOADING) {
this._setReadyState(XMLHttpRequest.LOADING);
}
this._dispatchProgress('progress');
}
private _onHttpResponseEnd(response: IncomingMessage) {
if (this._response !== response) { return; }
this._parseResponse();
this._request = null;
this._response = null;
this._setReadyState(XMLHttpRequest.DONE);
this._dispatchProgress('load');
this._dispatchProgress('loadend');
}
private _onHttpResponseClose(response: IncomingMessage) {
if (this._response !== response) { return; }
const request = this._request;
this._setError();
request.abort();
this._setReadyState(XMLHttpRequest.DONE);
this._dispatchProgress('error');
this._dispatchProgress('loadend');
}
private _onHttpTimeout(request: ClientRequest) {
if (this._request !== request) { return; }
this._setError();
request.abort();
this._setReadyState(XMLHttpRequest.DONE);
this._dispatchProgress('timeout');
this._dispatchProgress('loadend');
}
private _onHttpRequestError(request: ClientRequest, error: Error) {
if (this._request !== request) { return; }
this._setError();
request.abort();
this._setReadyState(XMLHttpRequest.DONE);
this._dispatchProgress('error');
this._dispatchProgress('loadend');
}
private _dispatchProgress(eventType: string) {
const event = new XMLHttpRequest.ProgressEvent(eventType);
event.lengthComputable = this._lengthComputable;
event.loaded = this._loadedBytes;
event.total = this._totalBytes;
this.dispatchEvent(event);
}
private _setError() {
this._request = null;
this._response = null;
this._responseHeaders = null;
this._responseParts = null;
}
private _parseUrl(urlString: string, user?: string, password?: string) {
const absoluteUrl = this.nodejsBaseUrl == null ? urlString : url.resolve(this.nodejsBaseUrl, urlString);
const xhrUrl: XHRUrl = url.parse(absoluteUrl, false, true);
xhrUrl.hash = null;
const [xhrUser, xhrPassword] = (xhrUrl.auth || '').split(':');
if (xhrUser || xhrPassword || user || password) {
xhrUrl.auth = `${user || xhrUser || ''}:${password || xhrPassword || ''}`;
}
return xhrUrl;
}
private _parseResponseHeaders(response: IncomingMessage) {
this._responseHeaders = {};
for (let name in response.headers) {
const loweredName = name.toLowerCase();
if (this._privateHeaders[loweredName]) { continue; }
this._responseHeaders[loweredName] = response.headers[name];
}
if (this._mimeOverride != null) {
this._responseHeaders['content-type'] = this._mimeOverride;
}
}
private _parseResponse() {
const buffer = Buffer.concat(this._responseParts);
this._responseParts = null;
switch (this.responseType) {
case 'json':
this.responseText = null;
try {
this.response = JSON.parse(buffer.toString('utf-8'));
} catch {
this.response = null;
}
return;
case 'buffer':
this.responseText = null;
this.response = buffer;
return;
case 'arraybuffer':
this.responseText = null;
const arrayBuffer = new ArrayBuffer(buffer.length);
const view = new Uint8Array(arrayBuffer);
for (let i = 0; i < buffer.length; i++) { view[i] = buffer[i]; }
this.response = arrayBuffer;
return;
case 'text':
default:
try {
this.responseText = buffer.toString(this._parseResponseEncoding());
} catch {
this.responseText = buffer.toString('binary');
}
this.response = this.responseText;
}
}
private _parseResponseEncoding() {
return /;\s*charset=(.*)$/.exec(this._responseHeaders['content-type'] || '')[1] || 'utf-8';
}
}
XMLHttpRequest.prototype.nodejsHttpAgent = http.globalAgent;
XMLHttpRequest.prototype.nodejsHttpsAgent = https.globalAgent;
XMLHttpRequest.prototype.nodejsBaseUrl = null;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
{
"name": "web3-providers-http",
"version": "1.6.1",
"description": "Module to handle web3 RPC connections over HTTP.",
"repository": "https://github.com/ethereum/web3.js/tree/1.x/packages/web3-providers-http",
"license": "LGPL-3.0",
"engines": {
"node": ">=8.0.0"
},
"scripts": {
"compile": "tsc -b tsconfig.json",
"dtslint": "dtslint --localTs ../../node_modules/typescript/lib types"
},
"types": "types/index.d.ts",
"main": "lib/index.js",
"dependencies": {
"web3-core-helpers": "1.6.1",
"xhr2-cookies": "file:./local_modules/xhr2-cookies"
},
"devDependencies": {
"dtslint": "^3.4.1",
"typescript": "^3.9.5"
}
}

View File

@ -0,0 +1,142 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file httpprovider.js
* @authors:
* Marek Kotewicz <marek@parity.io>
* Marian Oancea
* Fabian Vogelsteller <fabian@ethereum.org>
* @date 2015
*/
var errors = require('web3-core-helpers').errors;
var XHR2 = require('xhr2-cookies').XMLHttpRequest; // jshint ignore: line
var http = require('http');
var https = require('https');
/**
* HttpProvider should be used to send rpc calls over http
*/
var HttpProvider = function HttpProvider(host, options) {
options = options || {};
this.withCredentials = options.withCredentials || false;
this.timeout = options.timeout || 0;
this.headers = options.headers;
this.agent = options.agent;
this.connected = false;
// keepAlive is true unless explicitly set to false
const keepAlive = options.keepAlive !== false;
this.host = host || 'http://localhost:8545';
if (!this.agent) {
if (this.host.substring(0,5) === "https") {
this.httpsAgent = new https.Agent({ keepAlive });
} else {
this.httpAgent = new http.Agent({ keepAlive });
}
}
};
HttpProvider.prototype._prepareRequest = function(){
var request;
// the current runtime is a browser
if (typeof XMLHttpRequest !== 'undefined') {
request = new XMLHttpRequest();
} else {
request = new XHR2();
var agents = {httpsAgent: this.httpsAgent, httpAgent: this.httpAgent, baseUrl: this.baseUrl};
if (this.agent) {
agents.httpsAgent = this.agent.https;
agents.httpAgent = this.agent.http;
agents.baseUrl = this.agent.baseUrl;
}
request.nodejsSet(agents);
}
request.open('POST', this.host, true);
request.setRequestHeader('Content-Type','application/json');
request.timeout = this.timeout;
request.withCredentials = this.withCredentials;
if(this.headers) {
this.headers.forEach(function(header) {
request.setRequestHeader(header.name, header.value);
});
}
return request;
};
/**
* Should be used to make async request
*
* @method send
* @param {Object} payload
* @param {Function} callback triggered on end with (err, result)
*/
HttpProvider.prototype.send = function (payload, callback) {
var _this = this;
var request = this._prepareRequest();
request.onreadystatechange = function() {
if (request.readyState === 4 && request.timeout !== 1) {
var result = request.responseText;
var error = null;
try {
result = JSON.parse(result);
} catch(e) {
error = errors.InvalidResponse(request.responseText);
}
_this.connected = true;
callback(error, result);
}
};
request.ontimeout = function() {
_this.connected = false;
callback(errors.ConnectionTimeout(this.timeout));
};
try {
request.send(JSON.stringify(payload));
} catch(error) {
this.connected = false;
callback(errors.InvalidConnection(this.host));
}
};
HttpProvider.prototype.disconnect = function () {
//NO OP
};
/**
* Returns the desired boolean.
*
* @method supportsSubscriptions
* @returns {boolean}
*/
HttpProvider.prototype.supportsSubscriptions = function () {
return false;
};
module.exports = HttpProvider;

View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./lib"
},
"include": [
"./src"
]
}

View File

@ -0,0 +1,66 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file index.d.ts
* @author Josh Stevens <joshstevens19@hotmail.co.uk>
* @date 2018
*/
import * as http from 'http';
import * as https from 'https';
import { HttpProviderBase, JsonRpcResponse } from 'web3-core-helpers';
export interface HttpHeader {
name: string;
value: string;
}
export interface HttpProviderAgent {
baseUrl?: string;
http?: http.Agent;
https?: https.Agent;
}
export interface HttpProviderOptions {
withCredentials?: boolean;
timeout?: number;
headers?: HttpHeader[];
agent?: HttpProviderAgent;
keepAlive?: boolean;
}
export class HttpProvider extends HttpProviderBase {
host: string;
withCredentials: boolean;
timeout: number;
headers?: HttpHeader[];
agent?: HttpProviderAgent;
connected: boolean;
constructor(host?: string, options?: HttpProviderOptions);
send(
payload: object,
callback?: (
error: Error | null,
result: JsonRpcResponse | undefined
) => void
): void;
disconnect(): boolean;
supportsSubscriptions(): boolean;
}

View File

@ -0,0 +1,51 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file web3-provider-http-tests.ts
* @author Josh Stevens <joshstevens19@hotmail.co.uk> , Samuel Furter <samuel@ethereum.org>
* @date 2018
*/
import * as http from 'http';
import * as https from 'https';
import { HttpProvider } from 'web3-providers';
import { JsonRpcResponse } from 'web3-core-helpers';
const httpProvider = new HttpProvider('http://localhost:8545', {
timeout: 20000,
headers: [
{
name: 'Access-Control-Allow-Origin',
value: '*'
}
],
withCredentials: false,
agent: {
baseUrl: 'base',
http: new http.Agent({}),
https: new https.Agent({})
}
});
// $ExpectType void
httpProvider.send({}, (error: Error | null) => {});
// $ExpectType void
httpProvider.send({}, (error: Error | null, result: JsonRpcResponse | undefined) => {});
// $ExpectType boolean
httpProvider.disconnect();

View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": ["es6"],
"target": "es6",
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noEmit": true,
"allowSyntheticDefaultImports": false,
"baseUrl": ".",
"paths": {
"web3-providers": ["."]
}
}
}

View File

@ -0,0 +1,10 @@
{
"extends": "dtslint/dtslint.json",
"rules": {
"semicolon": false,
"no-import-default-of-export-equals": false,
"file-name-casing": [true, "kebab-case"],
"whitespace": false,
"no-unnecessary-class": false
}
}

2267
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,9 +14,11 @@
"circomlib": "git+https://github.com/tornadocash/circomlib.git#3b492f9801573eebcfe1b6c584afe8a3beecf2b4",
"commander": "^5.1.0",
"dotenv": "^8.2.0",
"gas-price-oracle": "^0.2.2",
"gas-price-oracle": "^0.4.4",
"socks-proxy-agent": "^6.1.1",
"snarkjs": "git+https://github.com/tornadocash/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5",
"web3": "^1.2.8",
"web3": "^1.6.1",
"web3-providers-http": "file:./local_modules/web3-providers-http",
"websnark": "git+https://github.com/tornadocash/websnark.git#4c0af6a8b65aabea3c09f377f63c44e7a58afa6d"
},
"devDependencies": {

368
yarn.lock
View File

@ -39,6 +39,22 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
"@ethereumjs/common@^2.5.0", "@ethereumjs/common@^2.6.0":
version "2.6.0"
resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.6.0.tgz#feb96fb154da41ee2cc2c5df667621a440f36348"
integrity sha512-Cq2qS0FTu6O2VU1sgg+WyU9Ps0M6j/BEMHN+hRaECXCV/r0aI78u4N6p52QW/BDVhwWZpCdrvG8X7NJdzlpNUA==
dependencies:
crc-32 "^1.2.0"
ethereumjs-util "^7.1.3"
"@ethereumjs/tx@^3.3.2":
version "3.4.0"
resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.4.0.tgz#7eb1947eefa55eb9cf05b3ca116fb7a3dbd0bce7"
integrity sha512-WWUwg1PdjHKZZxPPo274ZuPsJCWV3SqATrEKQP1n2DrVYVP1aZIYpo/mFaA0BDoE0tIQmBeimRCEA0Lgil+yYw==
dependencies:
"@ethereumjs/common" "^2.6.0"
ethereumjs-util "^7.1.3"
"@ethersproject/abi@5.0.7":
version "5.0.7"
resolved "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.0.7.tgz#79e52452bd3ca2956d0e1c964207a58ad1a0ee7b"
@ -232,6 +248,13 @@
dependencies:
"@types/node" "*"
"@types/bn.js@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68"
integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==
dependencies:
"@types/node" "*"
"@types/node@*":
version "14.14.14"
resolved "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae"
@ -279,6 +302,13 @@ acorn@^7.4.0:
resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
agent-base@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
dependencies:
debug "4"
ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.9.1:
version "6.12.6"
resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@ -421,6 +451,13 @@ axios@^0.19.2:
dependencies:
follow-redirects "1.5.10"
axios@^0.21.2:
version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
dependencies:
follow-redirects "^1.14.0"
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@ -511,6 +548,11 @@ bn.js@^5.0.0, bn.js@^5.1.1:
resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b"
integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==
bn.js@^5.1.2, bn.js@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002"
integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==
body-parser@1.19.0, body-parser@^1.16.0:
version "1.19.0"
resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
@ -886,6 +928,14 @@ cors@^2.8.1:
object-assign "^4"
vary "^1"
crc-32@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208"
integrity sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==
dependencies:
exit-on-epipe "~1.0.1"
printj "~1.1.0"
create-ecdh@^4.0.0:
version "4.0.4"
resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
@ -976,6 +1026,13 @@ debug@2.6.9, debug@^2.2.0:
dependencies:
ms "2.0.0"
debug@4, debug@^4.3.1:
version "4.3.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
dependencies:
ms "2.1.2"
debug@=3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
@ -1468,6 +1525,17 @@ ethereumjs-util@^6.0.0:
ethjs-util "0.1.6"
rlp "^2.2.3"
ethereumjs-util@^7.0.10, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.3.tgz#b55d7b64dde3e3e45749e4c41288238edec32d23"
integrity sha512-y+82tEbyASO0K0X1/SRhbJJoAlfcvq8JbrG4a5cjrOks7HS/36efU/0j2flxCPOUM++HFahk33kr/ZxyC4vNuw==
dependencies:
"@types/bn.js" "^5.1.0"
bn.js "^5.1.2"
create-hash "^1.1.2"
ethereum-cryptography "^0.1.3"
rlp "^2.2.4"
ethjs-unit@0.1.6:
version "0.1.6"
resolved "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699"
@ -1510,6 +1578,11 @@ execa@^1.0.0:
signal-exit "^3.0.0"
strip-eof "^1.0.0"
exit-on-epipe@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692"
integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==
express@^4.14.0:
version "4.17.1"
resolved "https://registry.npmjs.org/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
@ -1672,6 +1745,11 @@ follow-redirects@1.5.10:
dependencies:
debug "=3.1.0"
follow-redirects@^1.14.0:
version "1.14.5"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381"
integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==
foreach@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
@ -1732,12 +1810,12 @@ functional-red-black-tree@^1.0.1:
resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
gas-price-oracle@^0.2.2:
version "0.2.2"
resolved "https://registry.npmjs.org/gas-price-oracle/-/gas-price-oracle-0.2.2.tgz#32c57a9aa6bc69152be96812880232efebfecbc6"
integrity sha512-I4+rLbc7C1vgYXV+cYY0MKeqdZVna2hXpNfD2fcIvf/wIgvtIYmG9gsmhiaYGSgOE2RSPUs2xf/W4K2nJOoNuQ==
gas-price-oracle@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/gas-price-oracle/-/gas-price-oracle-0.4.4.tgz#9b7e5583ed7126a68f9d230b9efbd9d75d864bad"
integrity sha512-alAHLiZmPJ+GxKvujZZzEY8NRPqgGGHmDQUPFTa6HAMFB4LR/T6ShTDbqQzsdeWLPSw/j8/Gux0ZSC2AsPK+Hg==
dependencies:
axios "^0.19.2"
axios "^0.21.2"
bignumber.js "^9.0.0"
get-caller-file@^1.0.1:
@ -2059,6 +2137,11 @@ invert-kv@^2.0.0:
resolved "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
ip@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
ipaddr.js@1.9.1:
version "1.9.1"
resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
@ -2883,6 +2966,11 @@ prepend-http@^2.0.0:
resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
printj@~1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222"
integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==
process@^0.11.10:
version "0.11.10"
resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
@ -3089,6 +3177,13 @@ rlp@^2.2.3:
dependencies:
bn.js "^4.11.1"
rlp@^2.2.4:
version "2.2.7"
resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf"
integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==
dependencies:
bn.js "^5.2.0"
run-async@^2.2.0:
version "2.4.1"
resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
@ -3266,6 +3361,11 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
smart-buffer@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
"snarkjs@git+https://github.com/tornadocash/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5":
version "0.1.20"
resolved "git+https://github.com/tornadocash/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5"
@ -3277,6 +3377,23 @@ slice-ansi@^4.0.0:
keccak "^2.0.0"
yargs "^12.0.5"
socks-proxy-agent@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz#e664e8f1aaf4e1fb3df945f09e3d94f911137f87"
integrity sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==
dependencies:
agent-base "^6.0.2"
debug "^4.3.1"
socks "^2.6.1"
socks@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e"
integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==
dependencies:
ip "^1.1.5"
smart-buffer "^4.1.0"
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@ -3714,6 +3831,15 @@ web3-bzz@1.3.1:
swarm-js "^0.1.40"
underscore "1.9.1"
web3-bzz@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.6.1.tgz#8430eb3cbb69baaee4981d190b840748c37a9ec2"
integrity sha512-JbnFNbRlwwHJZPtVuCxo7rC4U4OTg+mPsyhjgPQJJhS0a6Y54OgVWYk9UA/95HqbmTJwTtX329gJoSsseEfrng==
dependencies:
"@types/node" "^12.12.6"
got "9.6.0"
swarm-js "^0.1.40"
web3-core-helpers@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.3.1.tgz#ffd6f47c1b54a8523f00760a8d713f44d0f97e97"
@ -3723,6 +3849,14 @@ web3-core-helpers@1.3.1:
web3-eth-iban "1.3.1"
web3-utils "1.3.1"
web3-core-helpers@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.6.1.tgz#cb21047306871f4cf0fedfece7d47ea2aa96141b"
integrity sha512-om2PZvK1uoWcgMq6JfcSx3241LEIVF6qi2JuHz2SLKiKEW5UsBUaVx0mNCmcZaiuYQCyOsLS3r33q5AdM+v8ng==
dependencies:
web3-eth-iban "1.6.1"
web3-utils "1.6.1"
web3-core-method@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.3.1.tgz#c1d8bf1e2104a8d625c99caf94218ad2dc948c92"
@ -3735,6 +3869,17 @@ web3-core-method@1.3.1:
web3-core-subscriptions "1.3.1"
web3-utils "1.3.1"
web3-core-method@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.6.1.tgz#4ae91c639bf1da85ebfd8b99595da6a2235d7b98"
integrity sha512-szH5KyIWIaULQDBdDvevQUCHV9lsExJ/oV0ePqK+w015D2SdMPMuhii0WB+HCePaksWO+rr/GAypvV9g2T3N+w==
dependencies:
"@ethersproject/transactions" "^5.0.0-beta.135"
web3-core-helpers "1.6.1"
web3-core-promievent "1.6.1"
web3-core-subscriptions "1.6.1"
web3-utils "1.6.1"
web3-core-promievent@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.3.1.tgz#b4da4b34cd9681e22fcda25994d7629280a1e046"
@ -3742,6 +3887,13 @@ web3-core-promievent@1.3.1:
dependencies:
eventemitter3 "4.0.4"
web3-core-promievent@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.6.1.tgz#f650dea9361e2edf02691015b213fcc8ea499992"
integrity sha512-byJ5s2MQxrWdXd27pWFmujfzsTZK4ik8rDgIV1RFDFc+rHZ2nZhq+VWk7t/Nkrj7EaVXncEgTdPEHc18nx+ocQ==
dependencies:
eventemitter3 "4.0.4"
web3-core-requestmanager@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.3.1.tgz#6dd2b5161ba778dfffe68994a4accff2decc54fe"
@ -3754,6 +3906,17 @@ web3-core-requestmanager@1.3.1:
web3-providers-ipc "1.3.1"
web3-providers-ws "1.3.1"
web3-core-requestmanager@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.6.1.tgz#d9c08b0716c9cda546a0c02767b7e08deb04448a"
integrity sha512-4y7etYEUtkfflyYVBfN1oJtCbVFNhNX1omlEYzezhTnPj3/dT7n+dhUXcqvIhx9iKA13unGfpFge80XNFfcB8A==
dependencies:
util "^0.12.0"
web3-core-helpers "1.6.1"
web3-providers-http "1.6.1"
web3-providers-ipc "1.6.1"
web3-providers-ws "1.6.1"
web3-core-subscriptions@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.3.1.tgz#be1103259f91b7fc7f4c6a867aa34dea70a636f7"
@ -3763,6 +3926,14 @@ web3-core-subscriptions@1.3.1:
underscore "1.9.1"
web3-core-helpers "1.3.1"
web3-core-subscriptions@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.6.1.tgz#4dfc1f74137354d4ac9eaa628aa916c5e2cc8741"
integrity sha512-WZwxsYttIojyGQ5RqxuQcKg0IJdDCFpUe4EncS3QKZwxPqWzGmgyLwE0rm7tP+Ux1waJn5CUaaoSCBxWGSun1g==
dependencies:
eventemitter3 "4.0.4"
web3-core-helpers "1.6.1"
web3-core@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-core/-/web3-core-1.3.1.tgz#fb0fc5d952a7f3d580a7e6155d2f28be064e64cb"
@ -3776,6 +3947,19 @@ web3-core@1.3.1:
web3-core-requestmanager "1.3.1"
web3-utils "1.3.1"
web3-core@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.6.1.tgz#b41f08fdc9ea1082d15384a3d6fa93a47c3fc1b4"
integrity sha512-m+b7UfYvU5cQUAh6NRfxRzH/5B3to1AdEQi1HIQt570cDWlObOOmoO9tY6iJnI5w4acxIO19LqjDMqEJGBYyRQ==
dependencies:
"@types/bn.js" "^4.11.5"
"@types/node" "^12.12.6"
bignumber.js "^9.0.0"
web3-core-helpers "1.6.1"
web3-core-method "1.6.1"
web3-core-requestmanager "1.6.1"
web3-utils "1.6.1"
web3-eth-abi@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.3.1.tgz#d60fe5f15c7a3a426c553fdaa4199d07f1ad899c"
@ -3785,6 +3969,14 @@ web3-eth-abi@1.3.1:
underscore "1.9.1"
web3-utils "1.3.1"
web3-eth-abi@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.6.1.tgz#15b937e3188570754d50bbac51a4bb0578600d1d"
integrity sha512-svhYrAlXP9XQtV7poWKydwDJq2CaNLMtmKydNXoOBLcQec6yGMP+v20pgrxF2H6wyTK+Qy0E3/5ciPOqC/VuoQ==
dependencies:
"@ethersproject/abi" "5.0.7"
web3-utils "1.6.1"
web3-eth-accounts@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.3.1.tgz#63b247461f1ae0ae46f9a5d5aa896ea80237143e"
@ -3802,6 +3994,23 @@ web3-eth-accounts@1.3.1:
web3-core-method "1.3.1"
web3-utils "1.3.1"
web3-eth-accounts@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.6.1.tgz#aeb0dfb52c4391773550569732975b471212583f"
integrity sha512-rGn3jwnuOKwaQRu4SiShz0YAQ87aVDBKs4HO43+XTCI1q1Y1jn3NOsG3BW9ZHaOckev4+zEyxze/Bsh2oEk24w==
dependencies:
"@ethereumjs/common" "^2.5.0"
"@ethereumjs/tx" "^3.3.2"
crypto-browserify "3.12.0"
eth-lib "0.2.8"
ethereumjs-util "^7.0.10"
scrypt-js "^3.0.1"
uuid "3.3.2"
web3-core "1.6.1"
web3-core-helpers "1.6.1"
web3-core-method "1.6.1"
web3-utils "1.6.1"
web3-eth-contract@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.3.1.tgz#05cb77bd2a671c5480897d20de487f3bae82e113"
@ -3817,6 +4026,20 @@ web3-eth-contract@1.3.1:
web3-eth-abi "1.3.1"
web3-utils "1.3.1"
web3-eth-contract@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.6.1.tgz#4b0a2c0b37015d70146e54c7cb3f035a58fbeec0"
integrity sha512-GXqTe3mF6kpbOAakiNc7wtJ120/gpuKMTZjuGFKeeY8aobRLfbfgKzM9IpyqVZV2v5RLuGXDuurVN2KPgtu3hQ==
dependencies:
"@types/bn.js" "^4.11.5"
web3-core "1.6.1"
web3-core-helpers "1.6.1"
web3-core-method "1.6.1"
web3-core-promievent "1.6.1"
web3-core-subscriptions "1.6.1"
web3-eth-abi "1.6.1"
web3-utils "1.6.1"
web3-eth-ens@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.3.1.tgz#ccfd621ddc1fecb44096bc8e60689499a9eb4421"
@ -3832,6 +4055,20 @@ web3-eth-ens@1.3.1:
web3-eth-contract "1.3.1"
web3-utils "1.3.1"
web3-eth-ens@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.6.1.tgz#801bd5fb5237377ec2ed8517a9fe4634f2269c7a"
integrity sha512-ngprtbnoRgxg8s1wXt9nXpD3h1P+p7XnKXrp/8GdFI9uDmrbSQPRfzBw86jdZgOmy78hAnWmrHI6pBInmgi2qQ==
dependencies:
content-hash "^2.5.2"
eth-ens-namehash "2.0.8"
web3-core "1.6.1"
web3-core-helpers "1.6.1"
web3-core-promievent "1.6.1"
web3-eth-abi "1.6.1"
web3-eth-contract "1.6.1"
web3-utils "1.6.1"
web3-eth-iban@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.3.1.tgz#4351e1a658efa5f3218357f0a38d6d8cad82481e"
@ -3840,6 +4077,14 @@ web3-eth-iban@1.3.1:
bn.js "^4.11.9"
web3-utils "1.3.1"
web3-eth-iban@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.6.1.tgz#20bbed75723e3e9ff98e624979629d26329462b6"
integrity sha512-91H0jXZnWlOoXmc13O9NuQzcjThnWyAHyDn5Yf7u6mmKOhpJSGF/OHlkbpXt1Y4v2eJdEPaVFa+6i8aRyagE7Q==
dependencies:
bn.js "^4.11.9"
web3-utils "1.6.1"
web3-eth-personal@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.3.1.tgz#cfe8af01588870d195dabf0a8d9e34956fb8856d"
@ -3852,6 +4097,18 @@ web3-eth-personal@1.3.1:
web3-net "1.3.1"
web3-utils "1.3.1"
web3-eth-personal@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.6.1.tgz#9b524fb9f92b51163f46920ee2663d34a4897c8d"
integrity sha512-ItsC89Ln02+irzJjK6ALcLrMZfbVUCqVbmb/ieDKJ+eLW3pNkBNwoUzaydh92d5NzxNZgNxuQWVdlFyYX2hkEw==
dependencies:
"@types/node" "^12.12.6"
web3-core "1.6.1"
web3-core-helpers "1.6.1"
web3-core-method "1.6.1"
web3-net "1.6.1"
web3-utils "1.6.1"
web3-eth@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-eth/-/web3-eth-1.3.1.tgz#60ac4b58e5fd17b8dbbb8378abd63b02e8326727"
@ -3871,6 +4128,24 @@ web3-eth@1.3.1:
web3-net "1.3.1"
web3-utils "1.3.1"
web3-eth@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.6.1.tgz#a25aba1ac213d872ecf3f81c7b4ab8072ecae224"
integrity sha512-kOV1ZgCKypSo5BQyltRArS7ZC3bRpIKAxSgzl7pUFinUb/MxfbM9KGeNxUXoCfTSErcCQJaDjcS6bSre5EMKuQ==
dependencies:
web3-core "1.6.1"
web3-core-helpers "1.6.1"
web3-core-method "1.6.1"
web3-core-subscriptions "1.6.1"
web3-eth-abi "1.6.1"
web3-eth-accounts "1.6.1"
web3-eth-contract "1.6.1"
web3-eth-ens "1.6.1"
web3-eth-iban "1.6.1"
web3-eth-personal "1.6.1"
web3-net "1.6.1"
web3-utils "1.6.1"
web3-net@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-net/-/web3-net-1.3.1.tgz#79374b1df37429b0839b83b0abc4440ac6181568"
@ -3880,6 +4155,15 @@ web3-net@1.3.1:
web3-core-method "1.3.1"
web3-utils "1.3.1"
web3-net@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.6.1.tgz#7a630a804ec9f81908ae52ccbb4ebbb9530b3906"
integrity sha512-gpnqKEIwfUHh5ik7wsQFlCje1DfcmGv+Sk7LCh1hCqn++HEDQxJ/mZCrMo11ZZpZHCH7c87imdxTg96GJnRxDw==
dependencies:
web3-core "1.6.1"
web3-core-method "1.6.1"
web3-utils "1.6.1"
web3-providers-http@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.3.1.tgz#becbea61706b2fa52e15aca6fe519ee108a8fab9"
@ -3888,6 +4172,20 @@ web3-providers-http@1.3.1:
web3-core-helpers "1.3.1"
xhr2-cookies "1.1.0"
web3-providers-http@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.6.1.tgz#b59b14eefef23b98c327806f5f566303a73bd435"
integrity sha512-xBoKOJxu10+kO3ikamXmBfrWZ/xpQOGy0ocdp7Y81B17En5TXELwlmMXt1UlIgWiyYDhjq4OwlH/VODYqHXy3A==
dependencies:
web3-core-helpers "1.6.1"
xhr2-cookies "1.1.0"
"web3-providers-http@file:./local_modules/web3-providers-http":
version "1.6.1"
dependencies:
web3-core-helpers "1.6.1"
xhr2-cookies "file:./../../AppData/Local/Yarn/Cache/v6/npm-web3-providers-http-1.6.1-6879cf8b-ee02-483d-9a46-76314494b334-1638895560210/node_modules/web3-providers-http/local_modules/xhr2-cookies"
web3-providers-ipc@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.3.1.tgz#3cb2572fc5286ab2f3117e0a2dce917816c3dedb"
@ -3897,6 +4195,14 @@ web3-providers-ipc@1.3.1:
underscore "1.9.1"
web3-core-helpers "1.3.1"
web3-providers-ipc@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.6.1.tgz#7ba460589d46896bb3d124288deed1b6a72d517e"
integrity sha512-anyoIZlpMzwEQI4lwylTzDrHsVp20v0QUtSTp2B5jInBinmQtyCE7vnbX20jEQ4j5uPwfJabKNtoJsk6a3O4WQ==
dependencies:
oboe "2.1.5"
web3-core-helpers "1.6.1"
web3-providers-ws@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.3.1.tgz#a70140811d138a1a5cf3f0c39d11887c8e341c83"
@ -3907,6 +4213,15 @@ web3-providers-ws@1.3.1:
web3-core-helpers "1.3.1"
websocket "^1.0.32"
web3-providers-ws@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.6.1.tgz#f7ee71f158971102b865e99ea7911f483e0507e9"
integrity sha512-FWMEFYb4rYFYRgSFBf/O1Ex4p/YKSlN+JydCtdlJwRimd89qm95CTfs4xGjCskwvXMjV2sarH+f1NPwJXicYpg==
dependencies:
eventemitter3 "4.0.4"
web3-core-helpers "1.6.1"
websocket "^1.0.32"
web3-shh@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-shh/-/web3-shh-1.3.1.tgz#42294d684358c22aa48616cb9a3eb2e9c1e6362f"
@ -3917,6 +4232,16 @@ web3-shh@1.3.1:
web3-core-subscriptions "1.3.1"
web3-net "1.3.1"
web3-shh@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.6.1.tgz#eebaab2e5e6be80fe2585c6c094fa10a03349ca7"
integrity sha512-oP00HbAtybLCGlLOZUYXOdeB9xq88k2l0TtStvKBtmFqRt+zVk5TxEeuOnVPRxNhcA2Un8RUw6FtvgZlWStu9A==
dependencies:
web3-core "1.6.1"
web3-core-method "1.6.1"
web3-core-subscriptions "1.6.1"
web3-net "1.6.1"
web3-utils@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/web3-utils/-/web3-utils-1.3.1.tgz#9aa880dd8c9463fe5c099107889f86a085370c2e"
@ -3931,7 +4256,20 @@ web3-utils@1.3.1:
underscore "1.9.1"
utf8 "3.0.0"
web3@^1.2.11, web3@^1.2.8:
web3-utils@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.6.1.tgz#befcb23922b00603ab56d8c5b4158468dc494aca"
integrity sha512-RidGKv5kOkcerI6jQqDFDoTllQQqV+rPhTzZHhmbqtFObbYpU93uc+yG1LHivRTQhA6llIx67iudc/vzisgO+w==
dependencies:
bn.js "^4.11.9"
ethereum-bloom-filters "^1.0.6"
ethereumjs-util "^7.1.0"
ethjs-unit "0.1.6"
number-to-bn "1.7.0"
randombytes "^2.1.0"
utf8 "3.0.0"
web3@^1.2.11:
version "1.3.1"
resolved "https://registry.npmjs.org/web3/-/web3-1.3.1.tgz#f780138c92ae3c42ea45e1a3c6ae8844e0aa5054"
integrity sha512-lDJwOLSRWHYwhPy4h5TNgBRJ/lED7lWXyVOXHCHcEC8ai3coBNdgEXWBu/GGYbZMsS89EoUOJ14j3Ufi4dUkog==
@ -3944,6 +4282,19 @@ web3@^1.2.11, web3@^1.2.8:
web3-shh "1.3.1"
web3-utils "1.3.1"
web3@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/web3/-/web3-1.6.1.tgz#c9e68fe7b3073adddf35393441f950ec69b92735"
integrity sha512-c299lLiyb2/WOcxh7TinwvbATaMmrgNIeAzbLbmOKHI0LcwyfsB1eu2ReOIrfrCYDYRW2KAjYr7J7gHawqDNPQ==
dependencies:
web3-bzz "1.6.1"
web3-core "1.6.1"
web3-eth "1.6.1"
web3-eth-personal "1.6.1"
web3-net "1.6.1"
web3-shh "1.6.1"
web3-utils "1.6.1"
"websnark@git+https://github.com/tornadocash/websnark.git#4c0af6a8b65aabea3c09f377f63c44e7a58afa6d":
version "0.0.4"
resolved "git+https://github.com/tornadocash/websnark.git#4c0af6a8b65aabea3c09f377f63c44e7a58afa6d"
@ -4060,6 +4411,11 @@ xhr2-cookies@1.1.0:
dependencies:
cookiejar "^2.1.1"
"xhr2-cookies@file:./local_modules/web3-providers-http/local_modules/xhr2-cookies":
version "1.1.0"
dependencies:
cookiejar "^2.1.1"
xhr@^2.0.4, xhr@^2.3.3:
version "2.6.0"
resolved "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d"