Skip to content

Commit

Permalink
fix: handle fetch (and especially cookies) correctly for node
Browse files Browse the repository at this point in the history
  • Loading branch information
AJ ONeal committed Mar 26, 2023
1 parent 82fc855 commit 524fa7e
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 66 deletions.
39 changes: 25 additions & 14 deletions ws/cookies.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
"use strict";

/** @type CookieStore */
/** @type {import('../').CookieStore} */
let Cookies = module.exports;

let Cookie = require("tough-cookie");
//@ts-ignore TODO
//let FileCookieStore = require("@root/file-cookie-store");
//let cookies_store = new FileCookieStore("./cookie.txt", { auto_sync: false });
let jar = new Cookie.CookieJar(/*cookies_store*/);
//@ts-ignore
jar.setCookieAsync = require("util").promisify(jar.setCookie);
//@ts-ignore
jar.getCookiesAsync = require("util").promisify(jar.getCookies);
//cookies_store.saveAsync = require("util").promisify(cookies_store.save);

Expand All @@ -18,23 +20,29 @@ jar.getCookiesAsync = require("util").promisify(jar.getCookies);
* @returns {Promise<void>}
*/
Cookies.set = async function _setCookie(url, resp) {
/** @type {Array<String>} */
let cookies;
if (resp.headers["set-cookie"]) {
if (Array.isArray(resp.headers["set-cookie"])) {
cookies = resp.headers["set-cookie"].map(Cookie.parse);
} else {
cookies = [Cookie.parse(resp.headers["set-cookie"])];
}
let moreCookies = resp.headers["set-cookie"];
if (!moreCookies) {
return;
}

if (!Array.isArray(moreCookies)) {
moreCookies = [moreCookies];
}
//@ts-ignore
cookies = moreCookies.map(Cookie.parse);

// let Cookie = //require('set-cookie-parser');
// Cookie.parse(resp, { decodeValues: true });
await Promise.all(
cookies.map(async function (cookie) {
//console.log('DEBUG cookie:', cookie.toJSON());
await jar.setCookieAsync(cookie, url, { now: new Date() });
}),
);
let ps = cookies.map(async function (cookie) {
//console.log('DEBUG cookie:', cookie.toJSON());
let jarOpts = { now: new Date() };
//@ts-ignore
await jar.setCookieAsync(cookie, url, jarOpts);
});

await Promise.all(ps);
//await cookies_store.saveAsync();
};

Expand All @@ -43,5 +51,8 @@ Cookies.set = async function _setCookie(url, resp) {
* @returns {Promise<String>}
*/
Cookies.get = async function _getCookie(url) {
return (await jar.getCookiesAsync(url)).toString();
//@ts-ignore
let cookieObj = await jar.getCookiesAsync(url);
let cookieStr = cookieObj.toString();
return cookieStr;
};
191 changes: 139 additions & 52 deletions ws/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ let WSClient = require("ws");
/**
* @typedef {Object} WsOpts
* @prop {String} [baseUrl] - (deprecated by dashsocketBaseUrl) ex: https://insight.dash.org
* @prop {CookieStore} cookieStore - only needed for insight APIs hosted behind an AWS load balancer
* @prop {import('../').CookieStore} cookieStore - only needed for insight APIs hosted behind an AWS load balancer
* @prop {Boolean} debug
* @prop {Function} onClose
* @prop {Function} onError
Expand Down Expand Up @@ -48,22 +48,19 @@ Ws.create = function ({
let now = Date.now();
let sidUrl = `${dashsocketBaseUrl}/?EIO=3&transport=polling&t=${now}`;

let cookies = await cookieStore.get(sidUrl);
let sidResp = await fetch(sidUrl, {
let cookiesStr = await cookieStore.get(sidUrl);
let sidResp = await Ws.fetch(sidUrl, {
//agent: httpAgent,
//@ts-ignore - request function is not typed correctly
headers: {
Cookie: cookies,
Cookie: cookiesStr,
},
});
if (!sidResp.ok) {
console.error(await sidResp.json());
throw new Error("bad response");
}

await cookieStore.set(sidUrl, sidResp);

// ex: `97:0{"sid":"xxxx",...}`
let msg = await sidResp.json();
let msg = sidResp.body || "";
let colonIndex = msg.indexOf(":");
// 0 is CONNECT, which will always follow our first message
let start = colonIndex + ":0".length;
Expand Down Expand Up @@ -95,24 +92,21 @@ Ws.create = function ({
let len = msg.length;
let body = `${len}:${msg}`;

let cookies = await cookieStore.get(subUrl);
let subResp = await fetch(subUrl, {
let cookiesStr = await cookieStore.get(subUrl);
let subResp = await Ws.fetch(subUrl, {
//agent: httpAgent,
method: "POST",
headers: {
"Content-Type": "text/plain;charset=UTF-8",
Cookie: cookies,
Cookie: cookiesStr,
},
body: body,
});
if (!subResp.ok) {
console.error(await subResp.json());
throw new Error("bad response");
}

await cookieStore.set(subUrl, subResp);

// "ok"
return await subResp.json();
return subResp.body;
};

/*
Expand Down Expand Up @@ -147,14 +141,15 @@ Ws.create = function ({
Eio3.connectWs = async function (sid) {
let dashsocketBaseUrlPart = dashsocketBaseUrl.slice(4); // trim leading 'http'
let url = `ws${dashsocketBaseUrlPart}/?EIO=3&transport=websocket&sid=${sid}`;
let sidUrl = `${dashsocketBaseUrl}/`;

let cookies = await cookieStore.get(`${dashsocketBaseUrl}/`);
let cookiesStr = await cookieStore.get(sidUrl);
let ws = new WSClient(url, {
//agent: httpAgent,
//perMessageDeflate: false,
//@ts-ignore - see above
headers: {
Cookie: cookies,
Cookie: cookiesStr,
},
});

Expand All @@ -169,10 +164,25 @@ Ws.create = function ({
ws.once("error", function (err) {
if (onError) {
onError(err);
} else {
console.error("WebSocket Error:");
console.error(err);
return;
}

console.error("WebSocket Error:");
console.error(err);
});

ws.once("unexpected-response", function (req, res) {
let err = new Error("unexpected-response");
//@ts-ignore
err.response = res;

if (onError) {
onError(err);
return;
}

console.error("WebSocket Unexpected Response:");
console.error(err);
});

ws.once("message", function message(data) {
Expand Down Expand Up @@ -261,7 +271,7 @@ Ws.create = function ({
return;
}

/** @type {InsightPush} */
/** @type {import('../').InsightPush} */
let [evname, data] = JSON.parse(msg.slice(2));
if (onMessage) {
onMessage(evname, data);
Expand Down Expand Up @@ -303,7 +313,7 @@ Ws.create = function ({
/**
* @callback Finder
* @param {String} evname
* @param {InsightSocketEventData} data
* @param {import('../').InsightSocketEventData} data
*/

/**
Expand Down Expand Up @@ -359,8 +369,8 @@ Ws.listen = async function (dashsocketBaseUrl, find, opts) {
* @param {String} addr
* @param {Number} [amount]
* @param {Number} [maxTxLockWait]
* @param {WsOpts} [opts]
* @returns {Promise<SocketPayment>}
* @param {Partial<WsOpts>} [opts]
* @returns {Promise<import('../').SocketPayment>}
*/
Ws.waitForVout = async function (
dashsocketBaseUrl,
Expand All @@ -374,13 +384,13 @@ Ws.waitForVout = async function (
}

// Listen for Response
/** @type SocketPayment */
/** @type {import('../').SocketPayment} */
let mempoolTx;
return await Ws.listen(dashsocketBaseUrl, findResponse, opts);

/**
* @param {String} evname
* @param {InsightSocketEventData} data
* @param {import('../').InsightSocketEventData} data
*/
function findResponse(evname, data) {
if (!["tx", "txlock"].includes(evname)) {
Expand All @@ -397,39 +407,116 @@ Ws.waitForVout = async function (

let result;
// TODO should fetch tx and match hotwallet as vin
data.vout.some(function (vout) {
if (!(addr in vout)) {
return false;
}

let duffs = vout[addr];
if (amount && duffs !== amount) {
return false;
}
data.vout.some(
/** @param {Record<String,Number>} vout */
function (vout) {
if (!(addr in vout)) {
return false;
}

let newTx = {
address: addr,
timestamp: now,
txid: data.txid,
satoshis: duffs,
txlock: data.txlock,
};
let duffs = vout[addr];
if (amount && duffs !== amount) {
return false;
}

if ("txlock" !== evname) {
if (!mempoolTx) {
mempoolTx = newTx;
let newTx = {
address: addr,
timestamp: now,
txid: data.txid,
satoshis: duffs,
txlock: data.txlock,
};

if ("txlock" !== evname) {
if (!mempoolTx) {
mempoolTx = newTx;
}
return false;
}
return false;
}

result = newTx;
return true;
});
result = newTx;
return true;
},
);

return result;
}
};

/** @type {RequestInit} */
let defaultRequest = {
mode: "cors",
credentials: "include",
};

/**
* @param {String | URL | Request} url
* @param {RequestInit} [_opts]
*/
Ws.fetch = async function dashfetch(url, _opts) {
let opts = Object.assign(defaultRequest, _opts);

let resp = await fetch(url, opts);
/** @type {Record<String,String|Array<String>>} */
let headers = {};

// for the Set-Cookie headers through AWS load balancer
let headerEntries = resp.headers.entries();
for (let [key, value] of headerEntries) {
if (!headers[key]) {
headers[key] = value;
continue;
}

let isArray = Array.isArray(headers[key]);
if (!isArray) {
//@ts-ignore
headers[key] = [headers[key]];
}
//@ts-ignore
headers[key].push(value);
}

let body = await resp.text();

let response = {
ok: resp.ok,
statusCode: resp.status, // backwards compat
statusText: resp.statusText,
headers: headers,
body: body,
toJSON: function () {
return {
ok: response.ok,
statusCode: response.statusCode,
statusText: response.statusText,
headers: response.headers,
body: response.body,
};
},
get status() {
console.warn(
"deprecated: please use either 'statusText' or 'statusCode' (node.js and browser both have 'status', but flipped)",
);
return resp.statusText;
},
_request: opts,
_response: resp,
};

if (resp.ok) {
return response;
}

let err = new Error(
`http request was ${resp.status}, not ok. See err.response for details.`,
);

// @ts-ignore
err.response = response;
throw err;
};

/*
async function sleep(ms) {
return await new Promise(function (resolve) {
Expand Down

0 comments on commit 524fa7e

Please sign in to comment.