Fix gas issues: add correct estimation for relayer withdrawal, add L1 fee fetching for Optimism, correct gas price estimation

This commit is contained in:
Theo 2023-07-04 20:05:49 -07:00
parent 7243803b21
commit ac0ccbb787
65 changed files with 253 additions and 10198 deletions

@ -0,0 +1,151 @@
[
{
"inputs": [{ "internalType": "address", "name": "_owner", "type": "address" }],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "internalType": "uint256", "name": "", "type": "uint256" }],
"name": "DecimalsUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "internalType": "uint256", "name": "", "type": "uint256" }],
"name": "GasPriceUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "internalType": "uint256", "name": "", "type": "uint256" }],
"name": "L1BaseFeeUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "internalType": "uint256", "name": "", "type": "uint256" }],
"name": "OverheadUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" },
{ "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" }
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "internalType": "uint256", "name": "", "type": "uint256" }],
"name": "ScalarUpdated",
"type": "event"
},
{
"inputs": [],
"name": "decimals",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gasPrice",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "bytes", "name": "_data", "type": "bytes" }],
"name": "getL1Fee",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "bytes", "name": "_data", "type": "bytes" }],
"name": "getL1GasUsed",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "l1BaseFee",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "overhead",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "scalar",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "_decimals", "type": "uint256" }],
"name": "setDecimals",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "_gasPrice", "type": "uint256" }],
"name": "setGasPrice",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "_baseFee", "type": "uint256" }],
"name": "setL1BaseFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "_overhead", "type": "uint256" }],
"name": "setOverhead",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "_scalar", "type": "uint256" }],
"name": "setScalar",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

150
cli.js

@ -11,6 +11,7 @@ const bigInt = snarkjs.bigInt;
const merkleTree = require('@tornado/fixed-merkle-tree');
const Web3 = require('web3');
const Web3HttpProvider = require('@tornado/web3-providers-http');
const { serialize } = require('@ethersproject/transactions');
const buildGroth16 = require('@tornado/websnark/src/groth16');
const websnarkUtils = require('@tornado/websnark/src/utils');
const { toWei, fromWei, toBN, BN } = require('web3-utils');
@ -32,7 +33,7 @@ let web3,
tornadoContract,
tornadoInstance,
circuit,
proving_key,
provingKey,
groth16,
erc20,
senderAccount,
@ -40,13 +41,13 @@ let web3,
netName,
netSymbol,
multiCall,
userAction,
subgraph;
let MERKLE_TREE_HEIGHT, ETH_AMOUNT, TOKEN_AMOUNT, PRIVATE_KEY;
/** Command state parameters */
let preferenceSpeed = gasSpeedPreferences[0];
let isTestRPC,
eipGasSupport = false;
let isTestRPC = false;
let shouldPromptConfirmation = true;
let doNotSubmitTx, privateRpc;
/** ----------------------------------------- **/
@ -178,9 +179,43 @@ async function estimateGas(from, to, nonce, encodedData, value = 0) {
return bumped;
}
async function fetchL1Fee({ gasPrice, gasLimit }) {
const DUMMY_NONCE = '0x1111111111111111111111111111111111111111111111111111111111111111';
const DUMMY_WITHDRAW_DATA =
'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111';
if (netId === 10) {
const ovmGasPriceOracleContractAddress = '0x420000000000000000000000000000000000000F';
const optimismL1GasPriceOracleABI = require('./build/contracts/OptimismL1GasPriceOracle.json');
const oracleInstance = new web3.eth.Contract(optimismL1GasPriceOracleABI, ovmGasPriceOracleContractAddress);
try {
const tx = serialize({
type: 0,
gasLimit,
chainId: netId,
nonce: DUMMY_NONCE,
data: DUMMY_WITHDRAW_DATA,
gasPrice,
to: tornadoProxyAddress
});
const l1Fee = await oracleInstance.methods.getL1Fee(tx).call();
return l1Fee;
} catch (err) {
throw new Error('Error while fetching optimism L1 fee: ' + err.message);
}
}
return 0;
}
async function generateTransaction(to, encodedData, value = 0) {
const nonce = await web3.eth.getTransactionCount(senderAccount);
let gasPrice = await fetchGasPrice();
let gasLimit;
if (encodedData) {
@ -192,12 +227,16 @@ async function generateTransaction(to, encodedData, value = 0) {
const isNumRString = typeof value == 'string' || typeof value == 'number';
const valueCost = isNumRString ? toBN(value) : value;
const gasCosts = toBN(gasPrice).mul(toBN(gasLimit));
const additionalFees = userAction === 'withdrawal' ? await fetchL1Fee({ gasPrice, gasLimit }) : 0;
const gasCosts = toBN(gasPrice).mul(toBN(gasLimit)).add(toBN(additionalFees));
const totalCosts = valueCost.add(gasCosts);
/** Transaction details */
console.log('Gas price: ', web3.utils.hexToNumber(gasPrice));
console.log('Gas limit: ', web3.utils.hexToNumber(gasLimit));
if (additionalFees != 0)
console.log('Additional gas fees (like L1 data fee): ', rmDecimalBN(fromWei(additionalFees), 12), `${netSymbol}`);
console.log('Transaction fee: ', rmDecimalBN(fromWei(gasCosts), 12), `${netSymbol}`);
console.log('Transaction cost: ', rmDecimalBN(fromWei(totalCosts), 12), `${netSymbol}`);
/** ----------------------------------------- **/
@ -463,7 +502,7 @@ async function generateProof({ deposit, currency, amount, recipient, relayerAddr
console.log('Generating SNARK proof');
console.time('Proof time');
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key);
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, provingKey);
const { proof } = websnarkUtils.toSolidityInput(proofData);
console.timeEnd('Proof time');
@ -520,7 +559,7 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
const merkleWithdrawalProof = await generateMerkleProof(deposit, currency, amount);
async function calculateDataForRelayer(gasLimit) {
const { desiredFee: totalFee, feePercent: relayerFee } = calculateRelayerWithdrawFee({
const { desiredFee: totalFee, feePercent: relayerFee } = await calculateRelayerWithdrawFee({
currency,
gasPrice,
amount,
@ -550,8 +589,9 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
const { proof, args, totalFee, relayerFee } = await calculateDataForRelayer(realGasLimit);
console.log('Sending withdraw transaction through relay');
const gasCosts = toBN(gasPrice).mul(toBN(realGasLimit));
const l1Fee = await fetchL1Fee({ gasPrice, gasLimit: realGasLimit });
console.log(l1Fee);
const gasCosts = toBN(gasPrice).mul(toBN(realGasLimit)).add(toBN(l1Fee));
/** Relayer fee details **/
console.log('Transaction fee: ', rmDecimalBN(fromWei(gasCosts), 12), `${netSymbol}`);
@ -864,37 +904,31 @@ function getCurrentNetworkSymbol() {
}
}
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 {
/** Gas preferences **/
console.log('Gas speed preference: ', preferenceSpeed);
/** ----------------------------------------------- **/
try {
const isLegacy = !eipGasSupport;
const oracleOptions = { chainId: netId, defaultRpc: web3.currentProvider.host };
const oracle = new GasPriceOracle(oracleOptions);
const gas = await oracle.gasPrices({ isLegacy });
// Extra bump estimated gas price for Polygon and Goerli
const bumpPercent = netId === 137 || netId === 5 ? 30 : 10;
if (netId === 1) {
return gasPricesETH(gas[preferenceSpeed]);
} else {
return gasPrices(gas[preferenceSpeed]);
}
try {
const oracleOptions = {
chainId: netId,
defaultRpc: web3.currentProvider.host,
minPriority: netId === 1 ? 3 : 0.05,
percentile: 5,
blocksCount: 20
};
const oracle = new GasPriceOracle(oracleOptions);
const { maxFeePerGas, gasPrice } = await oracle.getTxGasParams({ legacySpeed: preferenceSpeed, bumpPercent });
return maxFeePerGas || gasPrice;
} catch (e) {
const wei = await web3.eth.getGasPrice();
return wei / web3.utils.unitMap.gwei;
const wei = toBN(await web3.eth.getGasPrice());
const bumped = wei.add(wei.mul(toBN(bumpPercent)).div(toBN(100)));
return toHex(bumped);
}
} catch (err) {
throw new Error(`Method fetchGasPrice has error ${err.message}`);
@ -936,13 +970,26 @@ async function estimateWithdrawGasLimit({ relayer, proof, callArgs }) {
return gasLimit;
}
function calculateRelayerWithdrawFee({ currency, gasPrice, amount, refund, ethPrices, relayerServiceFee, decimals, gasLimit }) {
async function calculateRelayerWithdrawFee({
currency,
gasPrice,
amount,
refund,
ethPrices,
relayerServiceFee,
decimals,
gasLimit
}) {
const decimalsPoint =
Math.floor(relayerServiceFee) === Number(relayerServiceFee) ? 0 : relayerServiceFee.toString().split('.')[1].length;
const roundDecimal = 10 ** decimalsPoint;
const total = toBN(fromDecimals({ amount, decimals }));
const feePercent = total.mul(toBN(relayerServiceFee * roundDecimal)).div(toBN(roundDecimal * 100));
const expense = toBN(gasPrice).mul(toBN(gasLimit || getHardcodedWithdrawGasLimit(netId)));
const l1Fee = await fetchL1Fee({ gasPrice, gasLimit });
const expense = toBN(gasPrice)
.mul(toBN(gasLimit || getHardcodedWithdrawGasLimit(netId)))
.add(toBN(l1Fee));
let desiredFee;
switch (currency) {
case netSymbol.toLowerCase(): {
@ -1010,7 +1057,7 @@ function filterZeroEvents(events) {
function loadCachedEvents({ type, currency, amount }) {
try {
const module = require(`./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`);
const module = require(`./cache/${netName.toLowerCase()}/${type}s_${currency.toLowerCase()}_${amount}.json`);
if (module) {
const events = module;
@ -1042,11 +1089,13 @@ async function fetchEvents({ type, currency, amount, filterEvents }) {
console.log('Fetching', amount, currency.toUpperCase(), type, 'events for', netName, 'network');
async function updateCache(fetchedEvents) {
if (type === 'deposit') fetchedEvents.sort((firstLeaf, secondLeaf) => firstLeaf.leafIndex - secondLeaf.leafIndex);
try {
const fileName = `./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`;
const fileName = `./cache/${netName.toLowerCase()}/${type}s_${currency.toLowerCase()}_${amount}.json`;
const localEvents = await initJson(fileName);
const events = filterZeroEvents(localEvents).concat(fetchedEvents);
await fs.writeFileSync(fileName, JSON.stringify(events, null, 2), 'utf8');
fs.writeFileSync(fileName, JSON.stringify(events, null, 2), 'utf8');
} catch (error) {
throw new Error('Writing cache file failed:', error);
}
@ -1056,7 +1105,7 @@ async function fetchEvents({ type, currency, amount, filterEvents }) {
* Adds an zero (empty) event to the end of the events list
* If tornado transactions on the selected currency/amount are rare and the last one was much earlier than the current block,
* it helps to quickly synchronize the events tree
* @param blockNumber Latest block number on selected chain
* @param {number} blockNumber Latest block number on selected chain
*/
async function addZeroEvent(blockNumber) {
const zeroEvent = { blockNumber, transactionHash: null };
@ -1068,7 +1117,7 @@ async function fetchEvents({ type, currency, amount, filterEvents }) {
async function syncEvents() {
try {
let targetBlock = await web3.eth.getBlockNumber();
let chunks = 1000;
let chunks = 10000;
console.log('Querying latest events from RPC');
for (let i = startBlock; i < targetBlock; i += chunks) {
@ -1138,8 +1187,6 @@ async function fetchEvents({ type, currency, amount, filterEvents }) {
await fetchWeb3Events(i);
await updateCache(fetchedEvents);
}
await addZeroEvent(targetBlock);
} catch (error) {
console.log(error);
throw new Error('Error while updating cache');
@ -1158,9 +1205,10 @@ async function fetchEvents({ type, currency, amount, filterEvents }) {
async function queryLatestTimestamp() {
try {
const variables = {
currency: currency.toString(),
amount: amount.toString()
currency: currency.toString().toLowerCase(),
amount: amount.toString().toLowerCase()
};
console.log(variables);
if (type === 'deposit') {
const query = {
query: `
@ -1201,11 +1249,12 @@ async function fetchEvents({ type, currency, amount, filterEvents }) {
async function queryFromGraph(timestamp) {
try {
const variables = {
currency: currency.toString(),
amount: amount.toString(),
currency: currency.toString().toLowerCase(),
amount: amount.toString().toLowerCase(),
timestamp: timestamp
};
if (type === 'deposit') {
console.log(variables);
const query = {
query: `
query($currency: String, $amount: String, $timestamp: Int){
@ -1228,7 +1277,7 @@ async function fetchEvents({ type, currency, amount, filterEvents }) {
transactionHash,
commitment,
leafIndex: Number(index),
timestamp
timestamp: Number(timestamp)
};
});
return mapResult;
@ -1299,18 +1348,20 @@ async function fetchEvents({ type, currency, amount, filterEvents }) {
}
await fetchGraphEvents();
}
if (!privateRpc && subgraph && !isTestRPC) {
if (!privateRpc && !subgraph && !isTestRPC) {
await syncGraphEvents();
} else {
await syncEvents();
}
await addZeroEvent(await web3.eth.getBlockNumber());
async function loadUpdatedEvents() {
const fileName = `./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`;
// Don't use loadCachedEvents function, because we need to check zero event block (to which block we updated)
const fileName = `./cache/${netName.toLowerCase()}/${type}s_${currency.toLowerCase()}_${amount}.json`;
const updatedEvents = await initJson(fileName);
const updatedBlock = updatedEvents[updatedEvents.length - 1].blockNumber;
console.log('Cache updated for Tornado', type, amount, currency, 'instance to block', updatedBlock, 'successfully');
console.log(`Total ${type}s:`, updatedEvents.length);
console.log(`Total ${type}s:`, updatedEvents.length - 1);
return updatedEvents;
}
const events = await loadUpdatedEvents();
@ -1510,7 +1561,7 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100', balanceC
contractJson = require('./build/contracts/TornadoProxy.abi.json');
instanceJson = require('./build/contracts/Instance.abi.json');
circuit = require('./build/circuits/tornado.json');
proving_key = fs.readFileSync('build/circuits/tornadoProvingKey.bin').buffer;
provingKey = fs.readFileSync('build/circuits/tornadoProvingKey.bin').buffer;
MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT || 20;
ETH_AMOUNT = process.env.ETH_AMOUNT;
TOKEN_AMOUNT = process.env.TOKEN_AMOUNT;
@ -1642,6 +1693,7 @@ async function main() {
statePreferences(program);
const { currency, amount, netId, deposit } = parseNote(noteString);
userAction = 'withdrawal';
await init({
rpc: program.rpc,

@ -1,47 +0,0 @@
# 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

@ -1,125 +0,0 @@
/*
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;

@ -1,4 +0,0 @@
node_modules
yarn-error.log
.idea
!wallaby.js

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

@ -1,30 +0,0 @@
# 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

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

@ -1,45 +0,0 @@
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var SecurityError = /** @class */ (function (_super) {
__extends(SecurityError, _super);
function SecurityError() {
return _super !== null && _super.apply(this, arguments) || this;
}
return SecurityError;
}(Error));
exports.SecurityError = SecurityError;
var InvalidStateError = /** @class */ (function (_super) {
__extends(InvalidStateError, _super);
function InvalidStateError() {
return _super !== null && _super.apply(this, arguments) || this;
}
return InvalidStateError;
}(Error));
exports.InvalidStateError = InvalidStateError;
var NetworkError = /** @class */ (function (_super) {
__extends(NetworkError, _super);
function NetworkError() {
return _super !== null && _super.apply(this, arguments) || this;
}
return NetworkError;
}(Error));
exports.NetworkError = NetworkError;
var SyntaxError = /** @class */ (function (_super) {
__extends(SyntaxError, _super);
function SyntaxError() {
return _super !== null && _super.apply(this, arguments) || this;
}
return SyntaxError;
}(Error));
exports.SyntaxError = SyntaxError;
//# sourceMappingURL=errors.js.map

@ -1 +0,0 @@
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../errors.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA;IAAmC,iCAAK;IAAxC;;IAA0C,CAAC;IAAD,oBAAC;AAAD,CAAC,AAA3C,CAAmC,KAAK,GAAG;AAA9B,sCAAa;AAC1B;IAAuC,qCAAK;IAA5C;;IAA8C,CAAC;IAAD,wBAAC;AAAD,CAAC,AAA/C,CAAuC,KAAK,GAAG;AAAlC,8CAAiB;AAC9B;IAAkC,gCAAK;IAAvC;;IAAyC,CAAC;IAAD,mBAAC;AAAD,CAAC,AAA1C,CAAkC,KAAK,GAAG;AAA7B,oCAAY;AACzB;IAAiC,+BAAK;IAAtC;;IAAwC,CAAC;IAAD,kBAAC;AAAD,CAAC,AAAzC,CAAiC,KAAK,GAAG;AAA5B,kCAAW"}

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

@ -1,9 +0,0 @@
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(require("./xml-http-request"));
var xml_http_request_event_target_1 = require("./xml-http-request-event-target");
exports.XMLHttpRequestEventTarget = xml_http_request_event_target_1.XMLHttpRequestEventTarget;
//# sourceMappingURL=index.js.map

@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;AAAA,wCAAmC;AACnC,iFAA4E;AAAnE,oEAAA,yBAAyB,CAAA"}

@ -1,11 +0,0 @@
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);
}

@ -1,15 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var ProgressEvent = /** @class */ (function () {
function ProgressEvent(type) {
this.type = type;
this.bubbles = false;
this.cancelable = false;
this.loaded = 0;
this.lengthComputable = false;
this.total = 0;
}
return ProgressEvent;
}());
exports.ProgressEvent = ProgressEvent;
//# sourceMappingURL=progress-event.js.map

@ -1 +0,0 @@
{"version":3,"file":"progress-event.js","sourceRoot":"","sources":["../progress-event.ts"],"names":[],"mappings":";;AAEA;IAQC,uBAAoB,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;QAPhC,YAAO,GAAG,KAAK,CAAC;QAChB,eAAU,GAAG,KAAK,CAAC;QAEnB,WAAM,GAAG,CAAC,CAAC;QACX,qBAAgB,GAAG,KAAK,CAAC;QACzB,UAAK,GAAG,CAAC,CAAC;IAEyB,CAAC;IACrC,oBAAC;AAAD,CAAC,AATD,IASC;AATY,sCAAa"}

@ -1,19 +0,0 @@
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;
}

@ -1,41 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var XMLHttpRequestEventTarget = /** @class */ (function () {
function XMLHttpRequestEventTarget() {
this.listeners = {};
}
XMLHttpRequestEventTarget.prototype.addEventListener = function (eventType, listener) {
eventType = eventType.toLowerCase();
this.listeners[eventType] = this.listeners[eventType] || [];
this.listeners[eventType].push(listener.handleEvent || listener);
};
XMLHttpRequestEventTarget.prototype.removeEventListener = function (eventType, listener) {
eventType = eventType.toLowerCase();
if (!this.listeners[eventType]) {
return;
}
var index = this.listeners[eventType].indexOf(listener.handleEvent || listener);
if (index < 0) {
return;
}
this.listeners[eventType].splice(index, 1);
};
XMLHttpRequestEventTarget.prototype.dispatchEvent = function (event) {
var eventType = event.type.toLowerCase();
event.target = this; // TODO: set event.currentTarget?
if (this.listeners[eventType]) {
for (var _i = 0, _a = this.listeners[eventType]; _i < _a.length; _i++) {
var listener_1 = _a[_i];
listener_1.call(this, event);
}
}
var listener = this["on" + eventType];
if (listener) {
listener.call(this, event);
}
return true;
};
return XMLHttpRequestEventTarget;
}());
exports.XMLHttpRequestEventTarget = XMLHttpRequestEventTarget;
//# sourceMappingURL=xml-http-request-event-target.js.map

@ -1 +0,0 @@
{"version":3,"file":"xml-http-request-event-target.js","sourceRoot":"","sources":["../xml-http-request-event-target.ts"],"names":[],"mappings":";;AAMA;IAAA;QASS,cAAS,GAAmD,EAAE,CAAC;IAiCxE,CAAC;IA/BA,oDAAgB,GAAhB,UAAiB,SAAiB,EAAE,QAAqD;QACxF,SAAS,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;QACpC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QAC5D,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,CAAE,QAAwC,CAAC,WAAW,IAAK,QAAkC,CAAC,CAAC;IAC9H,CAAC;IACD,uDAAmB,GAAnB,UAAoB,SAAiB,EAAE,QAAqD;QAC3F,SAAS,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;QACpC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAAC,MAAM,CAAC;QAAC,CAAC;QAE3C,IAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,OAAO,CAAE,QAAwC,CAAC,WAAW,IAAK,QAAkC,CAAC,CAAC;QAC9I,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;YAAC,MAAM,CAAC;QAAC,CAAC;QAE1B,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,iDAAa,GAAb,UAAc,KAAoB;QACjC,IAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAC3C,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,iCAAiC;QAEtD,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAC/B,GAAG,CAAC,CAAiB,UAAyB,EAAzB,KAAA,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,EAAzB,cAAyB,EAAzB,IAAyB;gBAAzC,IAAI,UAAQ,SAAA;gBAChB,UAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;aAC3B;QACF,CAAC;QAED,IAAM,QAAQ,GAAG,IAAI,CAAC,OAAK,SAAW,CAAC,CAAC;QACxC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;YACd,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC5B,CAAC;QAED,MAAM,CAAC,IAAI,CAAC;IACb,CAAC;IACF,gCAAC;AAAD,CAAC,AA1CD,IA0CC;AA1CY,8DAAyB"}

@ -1,12 +0,0 @@
/// <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;
}

@ -1,78 +0,0 @@
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var xml_http_request_event_target_1 = require("./xml-http-request-event-target");
var XMLHttpRequestUpload = /** @class */ (function (_super) {
__extends(XMLHttpRequestUpload, _super);
function XMLHttpRequestUpload() {
var _this = _super.call(this) || this;
_this._contentType = null;
_this._body = null;
_this._reset();
return _this;
}
XMLHttpRequestUpload.prototype._reset = function () {
this._contentType = null;
this._body = null;
};
XMLHttpRequestUpload.prototype._setData = function (data) {
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) {
var body = Buffer.alloc(data.byteLength);
var view = new Uint8Array(data);
for (var i = 0; i < data.byteLength; i++) {
body[i] = view[i];
}
this._body = body;
}
else if (data.buffer && data.buffer instanceof ArrayBuffer) {
var body = Buffer.alloc(data.byteLength);
var offset = data.byteOffset;
var view = new Uint8Array(data.buffer);
for (var i = 0; i < data.byteLength; i++) {
body[i] = view[i + offset];
}
this._body = body;
}
else {
throw new Error("Unsupported send() data " + data);
}
};
XMLHttpRequestUpload.prototype._finalizeHeaders = function (headers, loweredHeaders) {
if (this._contentType && !loweredHeaders['content-type']) {
headers['Content-Type'] = this._contentType;
}
if (this._body) {
headers['Content-Length'] = this._body.length.toString();
}
};
XMLHttpRequestUpload.prototype._startUpload = function (request) {
if (this._body) {
request.write(this._body);
}
request.end();
};
return XMLHttpRequestUpload;
}(xml_http_request_event_target_1.XMLHttpRequestEventTarget));
exports.XMLHttpRequestUpload = XMLHttpRequestUpload;
//# sourceMappingURL=xml-http-request-upload.js.map

@ -1 +0,0 @@
{"version":3,"file":"xml-http-request-upload.js","sourceRoot":"","sources":["../xml-http-request-upload.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,iFAA4E;AAG5E;IAA0C,wCAAyB;IAIlE;QAAA,YACC,iBAAO,SAEP;QANO,kBAAY,GAAkB,IAAI,CAAC;QACnC,WAAK,GAAG,IAAI,CAAC;QAIpB,KAAI,CAAC,MAAM,EAAE,CAAC;;IACf,CAAC;IAED,qCAAM,GAAN;QACC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,uCAAQ,GAAR,UAAS,IAAsD;QAC9D,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC;YAAC,MAAM,CAAC;QAAC,CAAC;QAE7B,EAAE,CAAC,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC;YAC9B,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;gBACvB,IAAI,CAAC,YAAY,GAAG,0BAA0B,CAAC;YAChD,CAAC;YACD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACzC,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACnB,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,YAAY,WAAW,CAAC,CAAC,CAAC;YACxC,IAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC3C,IAAM,IAAI,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;YAClC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;gBAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YAAC,CAAC;YAChE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACnB,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,YAAY,WAAW,CAAC,CAAC,CAAC;YAC9D,IAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC3C,IAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;YAC/B,IAAM,IAAI,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACzC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;gBAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;YAAC,CAAC;YACzE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACnB,CAAC;QAAC,IAAI,CAAC,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,6BAA2B,IAAM,CAAC,CAAC;QACpD,CAAC;IACF,CAAC;IAED,+CAAgB,GAAhB,UAAiB,OAAe,EAAE,cAAsB;QACvD,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;YAC1D,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC;QAC7C,CAAC;QACD,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YAChB,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC1D,CAAC;IACF,CAAC;IAED,2CAAY,GAAZ,UAAa,OAAsB;QAClC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAAC,CAAC;QAC9C,OAAO,CAAC,GAAG,EAAE,CAAC;IACf,CAAC;IACF,2BAAC;AAAD,CAAC,AArDD,CAA0C,yDAAyB,GAqDlE;AArDY,oDAAoB"}

@ -1,102 +0,0 @@
/// <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();
}

@ -1,448 +0,0 @@
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
var http = require("http");
var https = require("https");
var url = require("url");
var progress_event_1 = require("./progress-event");
var errors_1 = require("./errors");
var xml_http_request_event_target_1 = require("./xml-http-request-event-target");
var xml_http_request_upload_1 = require("./xml-http-request-upload");
var Cookie = require("cookiejar");
var XMLHttpRequest = /** @class */ (function (_super) {
__extends(XMLHttpRequest, _super);
function XMLHttpRequest(options) {
if (options === void 0) { options = {}; }
var _this = _super.call(this) || this;
_this.UNSENT = XMLHttpRequest.UNSENT;
_this.OPENED = XMLHttpRequest.OPENED;
_this.HEADERS_RECEIVED = XMLHttpRequest.HEADERS_RECEIVED;
_this.LOADING = XMLHttpRequest.LOADING;
_this.DONE = XMLHttpRequest.DONE;
_this.onreadystatechange = null;
_this.readyState = XMLHttpRequest.UNSENT;
_this.response = null;
_this.responseText = '';
_this.responseType = '';
_this.status = 0; // TODO: UNSENT?
_this.statusText = '';
_this.timeout = 0;
_this.upload = new xml_http_request_upload_1.XMLHttpRequestUpload();
_this.responseUrl = '';
_this.withCredentials = false;
_this._method = null;
_this._url = null;
_this._sync = false;
_this._headers = {};
_this._loweredHeaders = {};
_this._mimeOverride = null; // TODO: is type right?
_this._request = null;
_this._response = null;
_this._responseParts = null;
_this._responseHeaders = null;
_this._aborting = null; // TODO: type?
_this._error = null; // TODO: type?
_this._loadedBytes = 0;
_this._totalBytes = 0;
_this._lengthComputable = false;
_this._restrictedMethods = { CONNECT: true, TRACE: true, TRACK: true };
_this._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
};
_this._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/
_this._userAgent = "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0";
_this._anonymous = options.anon || false;
return _this;
}
XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
if (async === void 0) { async = true; }
method = method.toUpperCase();
if (this._restrictedMethods[method]) {
throw new XMLHttpRequest.SecurityError("HTTP method " + method + " is not allowed in XHR");
}
;
var 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;
};
XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
if (this.readyState !== XMLHttpRequest.OPENED) {
throw new XMLHttpRequest.InvalidStateError('XHR readyState must be OPENED');
}
var 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;
}
};
XMLHttpRequest.prototype.send = function (data) {
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);
}
};
XMLHttpRequest.prototype.abort = function () {
if (this._request == null) {
return;
}
this._request.abort();
this._setError();
this._dispatchProgress('abort');
this._dispatchProgress('loadend');
};
XMLHttpRequest.prototype.getResponseHeader = function (name) {
if (this._responseHeaders == null || name == null) {
return null;
}
var loweredName = name.toLowerCase();
return this._responseHeaders.hasOwnProperty(loweredName)
? this._responseHeaders[name.toLowerCase()]
: null;
};
XMLHttpRequest.prototype.getAllResponseHeaders = function () {
var _this = this;
if (this._responseHeaders == null) {
return '';
}
return Object.keys(this._responseHeaders).map(function (key) { return key + ": " + _this._responseHeaders[key]; }).join('\r\n');
};
XMLHttpRequest.prototype.overrideMimeType = function (mimeType) {
if (this.readyState === XMLHttpRequest.LOADING || this.readyState === XMLHttpRequest.DONE) {
throw new XMLHttpRequest.InvalidStateError('overrideMimeType() not allowed in LOADING or DONE');
}
this._mimeOverride = mimeType.toLowerCase();
};
XMLHttpRequest.prototype.nodejsSet = function (options) {
this.nodejsHttpAgent = options.httpAgent || this.nodejsHttpAgent;
this.nodejsHttpsAgent = options.httpsAgent || this.nodejsHttpsAgent;
if (options.hasOwnProperty('baseUrl')) {
if (options.baseUrl != null) {
var parsedUrl = url.parse(options.baseUrl, false, true);
if (!parsedUrl.protocol) {
throw new XMLHttpRequest.SyntaxError("baseUrl must be an absolute URL");
}
}
this.nodejsBaseUrl = options.baseUrl;
}
};
XMLHttpRequest.nodejsSet = function (options) {
XMLHttpRequest.prototype.nodejsSet(options);
};
XMLHttpRequest.prototype._setReadyState = function (readyState) {
this.readyState = readyState;
this.dispatchEvent(new progress_event_1.ProgressEvent('readystatechange'));
};
XMLHttpRequest.prototype._sendFile = function (data) {
// TODO
throw new Error('Protocol file: not implemented');
};
XMLHttpRequest.prototype._sendHttp = function (data) {
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();
};
XMLHttpRequest.prototype._sendHxxpRequest = function () {
var _this = this;
if (this.withCredentials) {
var cookie = XMLHttpRequest.cookieJar
.getCookies(Cookie.CookieAccessInfo(this._url.hostname, this._url.pathname, this._url.protocol === 'https:')).toValueString();
this._headers.cookie = this._headers.cookie2 = cookie;
}
var _a = this._url.protocol === 'http:' ? [http, this.nodejsHttpAgent] : [https, this.nodejsHttpsAgent], hxxp = _a[0], agent = _a[1];
var requestMethod = hxxp.request.bind(hxxp);
var 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: agent
});
this._request = request;
if (this.timeout) {
request.setTimeout(this.timeout, function () { return _this._onHttpTimeout(request); });
}
request.on('response', function (response) { return _this._onHttpResponse(request, response); });
request.on('error', function (error) { return _this._onHttpRequestError(request, error); });
this.upload._startUpload(request);
if (this._request === request) {
this._dispatchProgress('loadstart');
}
};
XMLHttpRequest.prototype._finalizeHeaders = function () {
this._headers = __assign({}, 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);
};
XMLHttpRequest.prototype._onHttpResponse = function (request, response) {
var _this = this;
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', function (data) { return _this._onHttpResponseData(response, data); });
this._response.on('end', function () { return _this._onHttpResponseEnd(response); });
this._response.on('close', function () { return _this._onHttpResponseClose(response); });
this.responseUrl = this._url.href.split('#')[0];
this.status = response.statusCode;
this.statusText = http.STATUS_CODES[this.status];
this._parseResponseHeaders(response);
var lengthString = this._responseHeaders['content-length'] || '';
this._totalBytes = +lengthString;
this._lengthComputable = !!lengthString;
this._setReadyState(XMLHttpRequest.HEADERS_RECEIVED);
};
XMLHttpRequest.prototype._onHttpResponseData = function (response, data) {
if (this._response !== response) {
return;
}
this._responseParts.push(Buffer.from(data));
this._loadedBytes += data.length;
if (this.readyState !== XMLHttpRequest.LOADING) {
this._setReadyState(XMLHttpRequest.LOADING);
}
this._dispatchProgress('progress');
};
XMLHttpRequest.prototype._onHttpResponseEnd = function (response) {
if (this._response !== response) {
return;
}
this._parseResponse();
this._request = null;
this._response = null;
this._setReadyState(XMLHttpRequest.DONE);
this._dispatchProgress('load');
this._dispatchProgress('loadend');
};
XMLHttpRequest.prototype._onHttpResponseClose = function (response) {
if (this._response !== response) {
return;
}
var request = this._request;
this._setError();
request.abort();
this._setReadyState(XMLHttpRequest.DONE);
this._dispatchProgress('error');
this._dispatchProgress('loadend');
};
XMLHttpRequest.prototype._onHttpTimeout = function (request) {
if (this._request !== request) {
return;
}
this._setError();
request.abort();
this._setReadyState(XMLHttpRequest.DONE);
this._dispatchProgress('timeout');
this._dispatchProgress('loadend');
};
XMLHttpRequest.prototype._onHttpRequestError = function (request, error) {
if (this._request !== request) {
return;
}
this._setError();
request.abort();
this._setReadyState(XMLHttpRequest.DONE);
this._dispatchProgress('error');
this._dispatchProgress('loadend');
};
XMLHttpRequest.prototype._dispatchProgress = function (eventType) {
var event = new XMLHttpRequest.ProgressEvent(eventType);
event.lengthComputable = this._lengthComputable;
event.loaded = this._loadedBytes;
event.total = this._totalBytes;
this.dispatchEvent(event);
};
XMLHttpRequest.prototype._setError = function () {
this._request = null;
this._response = null;
this._responseHeaders = null;
this._responseParts = null;
};
XMLHttpRequest.prototype._parseUrl = function (urlString, user, password) {
var absoluteUrl = this.nodejsBaseUrl == null ? urlString : url.resolve(this.nodejsBaseUrl, urlString);
var xhrUrl = url.parse(absoluteUrl, false, true);
xhrUrl.hash = null;
var _a = (xhrUrl.auth || '').split(':'), xhrUser = _a[0], xhrPassword = _a[1];
if (xhrUser || xhrPassword || user || password) {
xhrUrl.auth = (user || xhrUser || '') + ":" + (password || xhrPassword || '');
}
return xhrUrl;
};
XMLHttpRequest.prototype._parseResponseHeaders = function (response) {
this._responseHeaders = {};
for (var name_1 in response.headers) {
var loweredName = name_1.toLowerCase();
if (this._privateHeaders[loweredName]) {
continue;
}
this._responseHeaders[loweredName] = response.headers[name_1];
}
if (this._mimeOverride != null) {
this._responseHeaders['content-type'] = this._mimeOverride;
}
};
XMLHttpRequest.prototype._parseResponse = function () {
var 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 (_a) {
this.response = null;
}
return;
case 'buffer':
this.responseText = null;
this.response = buffer;
return;
case 'arraybuffer':
this.responseText = null;
var arrayBuffer = new ArrayBuffer(buffer.length);
var view = new Uint8Array(arrayBuffer);
for (var 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 (_b) {
this.responseText = buffer.toString('binary');
}
this.response = this.responseText;
}
};
XMLHttpRequest.prototype._parseResponseEncoding = function () {
return /;\s*charset=(.*)$/.exec(this._responseHeaders['content-type'] || '')[1] || 'utf-8';
};
XMLHttpRequest.ProgressEvent = progress_event_1.ProgressEvent;
XMLHttpRequest.InvalidStateError = errors_1.InvalidStateError;
XMLHttpRequest.NetworkError = errors_1.NetworkError;
XMLHttpRequest.SecurityError = errors_1.SecurityError;
XMLHttpRequest.SyntaxError = errors_1.SyntaxError;
XMLHttpRequest.XMLHttpRequestUpload = xml_http_request_upload_1.XMLHttpRequestUpload;
XMLHttpRequest.UNSENT = 0;
XMLHttpRequest.OPENED = 1;
XMLHttpRequest.HEADERS_RECEIVED = 2;
XMLHttpRequest.LOADING = 3;
XMLHttpRequest.DONE = 4;
XMLHttpRequest.cookieJar = Cookie.CookieJar();
return XMLHttpRequest;
}(xml_http_request_event_target_1.XMLHttpRequestEventTarget));
exports.XMLHttpRequest = XMLHttpRequest;
XMLHttpRequest.prototype.nodejsHttpAgent = http.globalAgent;
XMLHttpRequest.prototype.nodejsHttpsAgent = https.globalAgent;
XMLHttpRequest.prototype.nodejsBaseUrl = null;
//# sourceMappingURL=xml-http-request.js.map

File diff suppressed because one or more lines are too long

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

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

@ -1,48 +0,0 @@
{
"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/**/*"
]
}
}

@ -1,12 +0,0 @@
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) {}
}

@ -1,59 +0,0 @@
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();
});
});

@ -1,101 +0,0 @@
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

@ -1,249 +0,0 @@
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.

Before

Width:  |  Height:  |  Size: 119 B

@ -1,189 +0,0 @@
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
// *

@ -1,50 +0,0 @@
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-----
`;

@ -1,5 +0,0 @@
#!/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

@ -1,21 +0,0 @@
-----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-----

@ -1,27 +0,0 @@
-----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-----

@ -1,28 +0,0 @@
-----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-----

@ -1,8 +0,0 @@
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 };

@ -1,228 +0,0 @@
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 };

@ -1,120 +0,0 @@
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');
});

@ -1,120 +0,0 @@
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();
});
});

@ -1,134 +0,0 @@
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();
});
});

@ -1,46 +0,0 @@
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();
});
});

@ -1,137 +0,0 @@
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();
});
});

@ -1,84 +0,0 @@
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);
});
});

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

@ -1,135 +0,0 @@
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');
});

@ -1,16 +0,0 @@
{
"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"
]
}

@ -1,115 +0,0 @@
{
"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"
]
}
}

@ -1,27 +0,0 @@
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']
})
}
}
};

@ -1,49 +0,0 @@
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;
}
}

@ -1,57 +0,0 @@
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();
}
}

@ -1,471 +0,0 @@
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

@ -1,24 +0,0 @@
{
"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"
}
}

@ -1,142 +0,0 @@
/*
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;

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

@ -1,66 +0,0 @@
/*
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;
}

@ -1,51 +0,0 @@
/*
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();

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

@ -1,10 +0,0 @@
{
"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
}
}

@ -10,6 +10,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"@ethersproject/transactions": "^5.7.0",
"@tornado/circomlib": "0.0.20-p2",
"@tornado/fixed-merkle-tree": "0.6.1-p1",
"@tornado/gas-price-oracle": "0.5.2-p1",