Skip to content

Commit 524fa7e

Browse files
author
AJ ONeal
committed
fix: handle fetch (and especially cookies) correctly for node
1 parent 82fc855 commit 524fa7e

File tree

2 files changed

+164
-66
lines changed

2 files changed

+164
-66
lines changed

ws/cookies.js

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
"use strict";
22

3-
/** @type CookieStore */
3+
/** @type {import('../').CookieStore} */
44
let Cookies = module.exports;
55

66
let Cookie = require("tough-cookie");
77
//@ts-ignore TODO
88
//let FileCookieStore = require("@root/file-cookie-store");
99
//let cookies_store = new FileCookieStore("./cookie.txt", { auto_sync: false });
1010
let jar = new Cookie.CookieJar(/*cookies_store*/);
11+
//@ts-ignore
1112
jar.setCookieAsync = require("util").promisify(jar.setCookie);
13+
//@ts-ignore
1214
jar.getCookiesAsync = require("util").promisify(jar.getCookies);
1315
//cookies_store.saveAsync = require("util").promisify(cookies_store.save);
1416

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

30+
if (!Array.isArray(moreCookies)) {
31+
moreCookies = [moreCookies];
32+
}
33+
//@ts-ignore
34+
cookies = moreCookies.map(Cookie.parse);
35+
3036
// let Cookie = //require('set-cookie-parser');
3137
// Cookie.parse(resp, { decodeValues: true });
32-
await Promise.all(
33-
cookies.map(async function (cookie) {
34-
//console.log('DEBUG cookie:', cookie.toJSON());
35-
await jar.setCookieAsync(cookie, url, { now: new Date() });
36-
}),
37-
);
38+
let ps = cookies.map(async function (cookie) {
39+
//console.log('DEBUG cookie:', cookie.toJSON());
40+
let jarOpts = { now: new Date() };
41+
//@ts-ignore
42+
await jar.setCookieAsync(cookie, url, jarOpts);
43+
});
44+
45+
await Promise.all(ps);
3846
//await cookies_store.saveAsync();
3947
};
4048

@@ -43,5 +51,8 @@ Cookies.set = async function _setCookie(url, resp) {
4351
* @returns {Promise<String>}
4452
*/
4553
Cookies.get = async function _getCookie(url) {
46-
return (await jar.getCookiesAsync(url)).toString();
54+
//@ts-ignore
55+
let cookieObj = await jar.getCookiesAsync(url);
56+
let cookieStr = cookieObj.toString();
57+
return cookieStr;
4758
};

ws/index.js

Lines changed: 139 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ let WSClient = require("ws");
99
/**
1010
* @typedef {Object} WsOpts
1111
* @prop {String} [baseUrl] - (deprecated by dashsocketBaseUrl) ex: https://insight.dash.org
12-
* @prop {CookieStore} cookieStore - only needed for insight APIs hosted behind an AWS load balancer
12+
* @prop {import('../').CookieStore} cookieStore - only needed for insight APIs hosted behind an AWS load balancer
1313
* @prop {Boolean} debug
1414
* @prop {Function} onClose
1515
* @prop {Function} onError
@@ -48,22 +48,19 @@ Ws.create = function ({
4848
let now = Date.now();
4949
let sidUrl = `${dashsocketBaseUrl}/?EIO=3&transport=polling&t=${now}`;
5050

51-
let cookies = await cookieStore.get(sidUrl);
52-
let sidResp = await fetch(sidUrl, {
51+
let cookiesStr = await cookieStore.get(sidUrl);
52+
let sidResp = await Ws.fetch(sidUrl, {
5353
//agent: httpAgent,
5454
//@ts-ignore - request function is not typed correctly
5555
headers: {
56-
Cookie: cookies,
56+
Cookie: cookiesStr,
5757
},
5858
});
59-
if (!sidResp.ok) {
60-
console.error(await sidResp.json());
61-
throw new Error("bad response");
62-
}
59+
6360
await cookieStore.set(sidUrl, sidResp);
6461

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

98-
let cookies = await cookieStore.get(subUrl);
99-
let subResp = await fetch(subUrl, {
95+
let cookiesStr = await cookieStore.get(subUrl);
96+
let subResp = await Ws.fetch(subUrl, {
10097
//agent: httpAgent,
10198
method: "POST",
10299
headers: {
103100
"Content-Type": "text/plain;charset=UTF-8",
104-
Cookie: cookies,
101+
Cookie: cookiesStr,
105102
},
106103
body: body,
107104
});
108-
if (!subResp.ok) {
109-
console.error(await subResp.json());
110-
throw new Error("bad response");
111-
}
105+
112106
await cookieStore.set(subUrl, subResp);
113107

114108
// "ok"
115-
return await subResp.json();
109+
return subResp.body;
116110
};
117111

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

151-
let cookies = await cookieStore.get(`${dashsocketBaseUrl}/`);
146+
let cookiesStr = await cookieStore.get(sidUrl);
152147
let ws = new WSClient(url, {
153148
//agent: httpAgent,
154149
//perMessageDeflate: false,
155150
//@ts-ignore - see above
156151
headers: {
157-
Cookie: cookies,
152+
Cookie: cookiesStr,
158153
},
159154
});
160155

@@ -169,10 +164,25 @@ Ws.create = function ({
169164
ws.once("error", function (err) {
170165
if (onError) {
171166
onError(err);
172-
} else {
173-
console.error("WebSocket Error:");
174-
console.error(err);
167+
return;
175168
}
169+
170+
console.error("WebSocket Error:");
171+
console.error(err);
172+
});
173+
174+
ws.once("unexpected-response", function (req, res) {
175+
let err = new Error("unexpected-response");
176+
//@ts-ignore
177+
err.response = res;
178+
179+
if (onError) {
180+
onError(err);
181+
return;
182+
}
183+
184+
console.error("WebSocket Unexpected Response:");
185+
console.error(err);
176186
});
177187

178188
ws.once("message", function message(data) {
@@ -261,7 +271,7 @@ Ws.create = function ({
261271
return;
262272
}
263273

264-
/** @type {InsightPush} */
274+
/** @type {import('../').InsightPush} */
265275
let [evname, data] = JSON.parse(msg.slice(2));
266276
if (onMessage) {
267277
onMessage(evname, data);
@@ -303,7 +313,7 @@ Ws.create = function ({
303313
/**
304314
* @callback Finder
305315
* @param {String} evname
306-
* @param {InsightSocketEventData} data
316+
* @param {import('../').InsightSocketEventData} data
307317
*/
308318

309319
/**
@@ -359,8 +369,8 @@ Ws.listen = async function (dashsocketBaseUrl, find, opts) {
359369
* @param {String} addr
360370
* @param {Number} [amount]
361371
* @param {Number} [maxTxLockWait]
362-
* @param {WsOpts} [opts]
363-
* @returns {Promise<SocketPayment>}
372+
* @param {Partial<WsOpts>} [opts]
373+
* @returns {Promise<import('../').SocketPayment>}
364374
*/
365375
Ws.waitForVout = async function (
366376
dashsocketBaseUrl,
@@ -374,13 +384,13 @@ Ws.waitForVout = async function (
374384
}
375385

376386
// Listen for Response
377-
/** @type SocketPayment */
387+
/** @type {import('../').SocketPayment} */
378388
let mempoolTx;
379389
return await Ws.listen(dashsocketBaseUrl, findResponse, opts);
380390

381391
/**
382392
* @param {String} evname
383-
* @param {InsightSocketEventData} data
393+
* @param {import('../').InsightSocketEventData} data
384394
*/
385395
function findResponse(evname, data) {
386396
if (!["tx", "txlock"].includes(evname)) {
@@ -397,39 +407,116 @@ Ws.waitForVout = async function (
397407

398408
let result;
399409
// TODO should fetch tx and match hotwallet as vin
400-
data.vout.some(function (vout) {
401-
if (!(addr in vout)) {
402-
return false;
403-
}
404-
405-
let duffs = vout[addr];
406-
if (amount && duffs !== amount) {
407-
return false;
408-
}
410+
data.vout.some(
411+
/** @param {Record<String,Number>} vout */
412+
function (vout) {
413+
if (!(addr in vout)) {
414+
return false;
415+
}
409416

410-
let newTx = {
411-
address: addr,
412-
timestamp: now,
413-
txid: data.txid,
414-
satoshis: duffs,
415-
txlock: data.txlock,
416-
};
417+
let duffs = vout[addr];
418+
if (amount && duffs !== amount) {
419+
return false;
420+
}
417421

418-
if ("txlock" !== evname) {
419-
if (!mempoolTx) {
420-
mempoolTx = newTx;
422+
let newTx = {
423+
address: addr,
424+
timestamp: now,
425+
txid: data.txid,
426+
satoshis: duffs,
427+
txlock: data.txlock,
428+
};
429+
430+
if ("txlock" !== evname) {
431+
if (!mempoolTx) {
432+
mempoolTx = newTx;
433+
}
434+
return false;
421435
}
422-
return false;
423-
}
424436

425-
result = newTx;
426-
return true;
427-
});
437+
result = newTx;
438+
return true;
439+
},
440+
);
428441

429442
return result;
430443
}
431444
};
432445

446+
/** @type {RequestInit} */
447+
let defaultRequest = {
448+
mode: "cors",
449+
credentials: "include",
450+
};
451+
452+
/**
453+
* @param {String | URL | Request} url
454+
* @param {RequestInit} [_opts]
455+
*/
456+
Ws.fetch = async function dashfetch(url, _opts) {
457+
let opts = Object.assign(defaultRequest, _opts);
458+
459+
let resp = await fetch(url, opts);
460+
/** @type {Record<String,String|Array<String>>} */
461+
let headers = {};
462+
463+
// for the Set-Cookie headers through AWS load balancer
464+
let headerEntries = resp.headers.entries();
465+
for (let [key, value] of headerEntries) {
466+
if (!headers[key]) {
467+
headers[key] = value;
468+
continue;
469+
}
470+
471+
let isArray = Array.isArray(headers[key]);
472+
if (!isArray) {
473+
//@ts-ignore
474+
headers[key] = [headers[key]];
475+
}
476+
//@ts-ignore
477+
headers[key].push(value);
478+
}
479+
480+
let body = await resp.text();
481+
482+
let response = {
483+
ok: resp.ok,
484+
statusCode: resp.status, // backwards compat
485+
statusText: resp.statusText,
486+
headers: headers,
487+
body: body,
488+
toJSON: function () {
489+
return {
490+
ok: response.ok,
491+
statusCode: response.statusCode,
492+
statusText: response.statusText,
493+
headers: response.headers,
494+
body: response.body,
495+
};
496+
},
497+
get status() {
498+
console.warn(
499+
"deprecated: please use either 'statusText' or 'statusCode' (node.js and browser both have 'status', but flipped)",
500+
);
501+
return resp.statusText;
502+
},
503+
_request: opts,
504+
_response: resp,
505+
};
506+
507+
if (resp.ok) {
508+
return response;
509+
}
510+
511+
let err = new Error(
512+
`http request was ${resp.status}, not ok. See err.response for details.`,
513+
);
514+
515+
// @ts-ignore
516+
err.response = response;
517+
throw err;
518+
};
519+
433520
/*
434521
async function sleep(ms) {
435522
return await new Promise(function (resolve) {

0 commit comments

Comments
 (0)