@sweepconsole/node 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.js +36 -2
- package/package.json +2 -2
package/dist/client.js
CHANGED
|
@@ -72,8 +72,9 @@ export class Sweep {
|
|
|
72
72
|
catch {
|
|
73
73
|
const looksLikeHtml = /^\s*<(!doctype|html)/i.test(text);
|
|
74
74
|
throw new SweepError(looksLikeHtml
|
|
75
|
-
? `Expected JSON from ${res.url} but got an HTML page.
|
|
76
|
-
`
|
|
75
|
+
? `Expected JSON from ${res.url} but got an HTML page. Either baseUrl points at the website ` +
|
|
76
|
+
`rather than the API — api.sweepconsole.xyz, not www — or something between you and the API ` +
|
|
77
|
+
`answered instead of it.`
|
|
77
78
|
: `Expected JSON from ${res.url} but got ${res.headers.get("content-type") ?? "an unknown type"}.`, { code: "invalid_response", status: res.status });
|
|
78
79
|
}
|
|
79
80
|
}
|
|
@@ -105,6 +106,13 @@ export class Sweep {
|
|
|
105
106
|
});
|
|
106
107
|
if (res.ok)
|
|
107
108
|
return (await this.parse(res));
|
|
109
|
+
const blocked = edgeBlock(res);
|
|
110
|
+
if (blocked) {
|
|
111
|
+
throw new SweepError(`Blocked before reaching the Sweep API: ${res.status} from a proxy in front of ` +
|
|
112
|
+
`${res.url}, answering HTML rather than JSON. Server-to-server requests trip bot ` +
|
|
113
|
+
`protection tuned for browsers, so this is infrastructure rather than your code. ` +
|
|
114
|
+
`Quote this trace id to support: ${blocked.trace}`, { code: "blocked_upstream", status: res.status, reference: blocked.trace });
|
|
115
|
+
}
|
|
108
116
|
// 5xx and 429 may succeed on a retry; a 4xx is a decision, not a blip.
|
|
109
117
|
if ((res.status >= 500 || res.status === 429) && attempt < this.maxRetries) {
|
|
110
118
|
lastError = errorFrom(res.status, await safeJson(res));
|
|
@@ -271,6 +279,32 @@ function toCharge(raw) {
|
|
|
271
279
|
function safeJson(res) {
|
|
272
280
|
return res.json().catch(() => null);
|
|
273
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* A proxy in front of the API refused the request before it got there.
|
|
284
|
+
*
|
|
285
|
+
* Server-to-server calls are what this catches. A request from a datacenter
|
|
286
|
+
* carries no browser fingerprint, so bot protection tuned for browsers treats
|
|
287
|
+
* it as exactly the thing it is looking for and answers a challenge page —
|
|
288
|
+
* HTML, with a 403, and a trace id of its own.
|
|
289
|
+
*
|
|
290
|
+
* Worth separating from every other 403, because nothing about the caller's
|
|
291
|
+
* code is wrong and no amount of re-reading their API key will help. The trace
|
|
292
|
+
* id is the one piece of information support actually needs.
|
|
293
|
+
*/
|
|
294
|
+
function edgeBlock(res) {
|
|
295
|
+
if (res.status !== 403 && res.status !== 503)
|
|
296
|
+
return null;
|
|
297
|
+
const trace = res.headers.get("cf-ray") ??
|
|
298
|
+
res.headers.get("x-amz-cf-id") ??
|
|
299
|
+
res.headers.get("x-akamai-request-id");
|
|
300
|
+
if (!trace)
|
|
301
|
+
return null;
|
|
302
|
+
// Our own 403s are JSON. An HTML body means something else answered.
|
|
303
|
+
const type = res.headers.get("content-type") ?? "";
|
|
304
|
+
if (type.includes("json"))
|
|
305
|
+
return null;
|
|
306
|
+
return { trace };
|
|
307
|
+
}
|
|
274
308
|
function sleep(ms) {
|
|
275
309
|
return new Promise((r) => setTimeout(r, ms));
|
|
276
310
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sweepconsole/node",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Node/TypeScript client for the Sweep Console payment rail
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Node/TypeScript client for the Sweep Console payment rail — charge a USDC wallet from your own app",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|