nonecap 0.1.0
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/LICENSE +21 -0
- package/README.md +155 -0
- package/dist/index.cjs +294 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +255 -0
- package/dist/index.d.ts +255 -0
- package/dist/index.js +279 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lunium
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<h1 align="center">nonecap</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://github.com/nonecap/nonecap-js/actions/workflows/ci.yml"><img src="https://github.com/nonecap/nonecap-js/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
5
|
+
<a href="https://www.npmjs.com/package/nonecap"><img src="https://img.shields.io/npm/v/nonecap.svg" alt="npm"></a>
|
|
6
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">Official TypeScript client for the <a href="https://nonecap.com">NoneCap</a> hCaptcha solving API.</p>
|
|
10
|
+
|
|
11
|
+
Submit a captcha, get back a token. The client handles the polling, the timeouts, and the error cases so you do not have to write the fetch loop yourself.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install nonecap
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Works on Node 18+, Bun, Deno, and edge runtimes. No dependencies.
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
Grab an API key from [dashboard.nonecap.com](https://dashboard.nonecap.com), then:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { NoneCap } from "nonecap";
|
|
27
|
+
|
|
28
|
+
const nc = new NoneCap({ apiKey: process.env.NONECAP_KEY! });
|
|
29
|
+
|
|
30
|
+
const solve = await nc.solve({
|
|
31
|
+
type: "hcaptcha",
|
|
32
|
+
sitekey: "10000000-ffff-ffff-ffff-000000000001",
|
|
33
|
+
url: "https://example.com/login",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
console.log(solve.token); // the hCaptcha token, ready to submit
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`solve()` submits the captcha and waits until it is done, using the API's long-poll so you are not hammering it with requests. It returns the solved solve, or throws if the solve fails or your timeout runs out.
|
|
40
|
+
|
|
41
|
+
## Handling failures
|
|
42
|
+
|
|
43
|
+
Every error this library throws extends `NoneCapError`, so you can catch the whole family or pick out the one you care about.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import {
|
|
47
|
+
NoneCap,
|
|
48
|
+
SolveFailedError,
|
|
49
|
+
InsufficientCreditsError,
|
|
50
|
+
RateLimitError,
|
|
51
|
+
} from "nonecap";
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const { token } = await nc.solve({ type: "hcaptcha", sitekey, url });
|
|
55
|
+
// use token
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (err instanceof SolveFailedError) {
|
|
58
|
+
console.error("Could not solve it:", err.solve.error?.code);
|
|
59
|
+
} else if (err instanceof InsufficientCreditsError) {
|
|
60
|
+
console.error("Out of credits. Top up at dashboard.nonecap.com");
|
|
61
|
+
} else if (err instanceof RateLimitError) {
|
|
62
|
+
console.error("Too many in flight, back off and retry");
|
|
63
|
+
} else {
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The error subclasses are `AuthenticationError` (401), `PermissionError` (403), `InsufficientCreditsError` (402), `ValidationError` (422/400, with a `param` naming the bad field), `NotFoundError` (404), `ConflictError` (409), `RateLimitError` (429), `APIError` (5xx), and `ConnectionError` (the request never landed). `SolveFailedError` carries the full `solve` so you can read `solve.error` and the timing fields.
|
|
70
|
+
|
|
71
|
+
## Enterprise captchas
|
|
72
|
+
|
|
73
|
+
For `hcaptcha_enterprise`, `rqdata` is required. The types enforce it, so leaving it out is a compile error, not a runtime surprise.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const { token } = await nc.solve({
|
|
77
|
+
type: "hcaptcha_enterprise",
|
|
78
|
+
sitekey,
|
|
79
|
+
url,
|
|
80
|
+
rqdata: "...", // required for enterprise
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Proxies
|
|
85
|
+
|
|
86
|
+
Pass a proxy as a structured object or a URL string. Solves run through it, and the bytes are metered back on the solve.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
await nc.solve({
|
|
90
|
+
type: "hcaptcha",
|
|
91
|
+
sitekey,
|
|
92
|
+
url,
|
|
93
|
+
proxy: { scheme: "http", host: "1.2.3.4", port: 8080, username: "u", password: "p" },
|
|
94
|
+
// or: proxy: "http://u:p@1.2.3.4:8080"
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Lower-level API
|
|
99
|
+
|
|
100
|
+
`solve()` is the convenient path. When you want control over submission and polling, the raw resource methods map one to one to the REST API.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// Submit without waiting: returns immediately with a pending solve
|
|
104
|
+
const pending = await nc.solves.create({ type: "hcaptcha", sitekey, url });
|
|
105
|
+
|
|
106
|
+
// Submit and hold the connection up to 30s for it to finish
|
|
107
|
+
const maybeDone = await nc.solves.create({ type: "hcaptcha", sitekey, url }, { wait: 30 });
|
|
108
|
+
|
|
109
|
+
// Poll one solve, long-polling up to 30s
|
|
110
|
+
const solve = await nc.solves.retrieve(pending.id, { wait: 30 });
|
|
111
|
+
|
|
112
|
+
// Cancel a pending or in-flight solve
|
|
113
|
+
await nc.solves.cancel(pending.id);
|
|
114
|
+
|
|
115
|
+
// List a page of solves
|
|
116
|
+
const page = await nc.solves.list({ limit: 50, status: "solved" });
|
|
117
|
+
|
|
118
|
+
// Or iterate every solve, newest first
|
|
119
|
+
for await (const s of nc.solves.listAll()) {
|
|
120
|
+
console.log(s.id, s.status);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Your account and credit balance
|
|
124
|
+
const me = await nc.me();
|
|
125
|
+
console.log(me.credits_balance);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Cancelling and timeouts
|
|
129
|
+
|
|
130
|
+
Pass an `AbortSignal` to cancel any call, and a `timeout` to bound how long `solve()` waits.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const ac = new AbortController();
|
|
134
|
+
setTimeout(() => ac.abort(), 60_000);
|
|
135
|
+
|
|
136
|
+
await nc.solve(
|
|
137
|
+
{ type: "hcaptcha", sitekey, url },
|
|
138
|
+
{ timeout: 120_000, signal: ac.signal },
|
|
139
|
+
);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Configuration
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
new NoneCap({
|
|
146
|
+
apiKey: "nc_live_...", // required
|
|
147
|
+
baseURL: "https://api.nonecap.com", // override if you need to
|
|
148
|
+
timeout: 100_000, // per HTTP request, ms
|
|
149
|
+
fetch: customFetch, // inject your own fetch
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT, see [LICENSE](LICENSE).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var NoneCapError = class extends Error {
|
|
5
|
+
/** Machine-readable error code from the API envelope, when there is one. */
|
|
6
|
+
code;
|
|
7
|
+
/** HTTP status, when the error came from a response. */
|
|
8
|
+
status;
|
|
9
|
+
/** The request field that was rejected, for validation errors. */
|
|
10
|
+
param;
|
|
11
|
+
constructor(message, opts = {}) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = new.target.name;
|
|
14
|
+
this.code = opts.code;
|
|
15
|
+
this.status = opts.status;
|
|
16
|
+
this.param = opts.param ?? null;
|
|
17
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var AuthenticationError = class extends NoneCapError {
|
|
21
|
+
};
|
|
22
|
+
var PermissionError = class extends NoneCapError {
|
|
23
|
+
};
|
|
24
|
+
var InsufficientCreditsError = class extends NoneCapError {
|
|
25
|
+
};
|
|
26
|
+
var ValidationError = class extends NoneCapError {
|
|
27
|
+
};
|
|
28
|
+
var NotFoundError = class extends NoneCapError {
|
|
29
|
+
};
|
|
30
|
+
var ConflictError = class extends NoneCapError {
|
|
31
|
+
};
|
|
32
|
+
var RateLimitError = class extends NoneCapError {
|
|
33
|
+
};
|
|
34
|
+
var APIError = class extends NoneCapError {
|
|
35
|
+
};
|
|
36
|
+
var ConnectionError = class extends NoneCapError {
|
|
37
|
+
};
|
|
38
|
+
var SolveFailedError = class extends NoneCapError {
|
|
39
|
+
solve;
|
|
40
|
+
constructor(solve) {
|
|
41
|
+
const detail = solve.error ? `${solve.error.code}: ${solve.error.message}` : solve.status;
|
|
42
|
+
super(`Solve ${solve.id} ${solve.status} (${detail})`, {
|
|
43
|
+
code: void 0,
|
|
44
|
+
status: void 0
|
|
45
|
+
});
|
|
46
|
+
this.solve = solve;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var TimeoutError = class extends NoneCapError {
|
|
50
|
+
};
|
|
51
|
+
function errorFromResponse(status, code, message, param) {
|
|
52
|
+
const opts = { code, status, param };
|
|
53
|
+
switch (code) {
|
|
54
|
+
case "unauthorized":
|
|
55
|
+
return new AuthenticationError(message, opts);
|
|
56
|
+
case "forbidden":
|
|
57
|
+
case "account_locked":
|
|
58
|
+
return new PermissionError(message, opts);
|
|
59
|
+
case "insufficient_credits":
|
|
60
|
+
return new InsufficientCreditsError(message, opts);
|
|
61
|
+
case "invalid_request":
|
|
62
|
+
case "validation_error":
|
|
63
|
+
return new ValidationError(message, opts);
|
|
64
|
+
case "not_found":
|
|
65
|
+
return new NotFoundError(message, opts);
|
|
66
|
+
case "conflict":
|
|
67
|
+
return new ConflictError(message, opts);
|
|
68
|
+
case "rate_limited":
|
|
69
|
+
case "concurrency_limit_exceeded":
|
|
70
|
+
case "ext_daily_limit":
|
|
71
|
+
return new RateLimitError(message, opts);
|
|
72
|
+
default:
|
|
73
|
+
return new APIError(message, opts);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/client.ts
|
|
78
|
+
var DEFAULT_BASE_URL = "https://api.nonecap.com";
|
|
79
|
+
var MAX_WAIT_SECONDS = 90;
|
|
80
|
+
var TERMINAL = /* @__PURE__ */ new Set([
|
|
81
|
+
"solved",
|
|
82
|
+
"failed",
|
|
83
|
+
"cancelled",
|
|
84
|
+
"expired"
|
|
85
|
+
]);
|
|
86
|
+
function isTerminal(status) {
|
|
87
|
+
return TERMINAL.has(status);
|
|
88
|
+
}
|
|
89
|
+
var NoneCap = class {
|
|
90
|
+
#apiKey;
|
|
91
|
+
#baseURL;
|
|
92
|
+
#timeout;
|
|
93
|
+
#fetch;
|
|
94
|
+
constructor(options) {
|
|
95
|
+
if (!options || !options.apiKey) {
|
|
96
|
+
throw new NoneCapError(
|
|
97
|
+
"A NoneCap API key is required. Pass it as `new NoneCap({ apiKey })`."
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
this.#apiKey = options.apiKey;
|
|
101
|
+
this.#baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
102
|
+
this.#timeout = options.timeout ?? 1e5;
|
|
103
|
+
const f = options.fetch ?? globalThis.fetch;
|
|
104
|
+
if (!f) {
|
|
105
|
+
throw new NoneCapError(
|
|
106
|
+
"No `fetch` available. Use Node 18+, or pass a `fetch` in the client options."
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
this.#fetch = f;
|
|
110
|
+
}
|
|
111
|
+
/** Operations on solves. */
|
|
112
|
+
solves = {
|
|
113
|
+
/** Submit a solve. Pass `{ wait }` to hold the connection until it finishes. */
|
|
114
|
+
create: (params, options = {}) => this.#request("POST", "/v1/solves", {
|
|
115
|
+
body: params,
|
|
116
|
+
query: { wait: options.wait },
|
|
117
|
+
signal: options.signal,
|
|
118
|
+
timeout: this.#timeoutForWait(options.wait),
|
|
119
|
+
expect: [202]
|
|
120
|
+
}),
|
|
121
|
+
/** Fetch a solve by id. Pass `{ wait }` to long-poll until it finishes. */
|
|
122
|
+
retrieve: (id, options = {}) => this.#request("GET", `/v1/solves/${encodeURIComponent(id)}`, {
|
|
123
|
+
query: { wait: options.wait },
|
|
124
|
+
signal: options.signal,
|
|
125
|
+
timeout: this.#timeoutForWait(options.wait),
|
|
126
|
+
expect: [202]
|
|
127
|
+
}),
|
|
128
|
+
/** Cancel a pending or in-flight solve. */
|
|
129
|
+
cancel: (id, options = {}) => this.#request("DELETE", `/v1/solves/${encodeURIComponent(id)}`, {
|
|
130
|
+
signal: options.signal
|
|
131
|
+
}),
|
|
132
|
+
/** Fetch one page of solves. */
|
|
133
|
+
list: (params = {}, options = {}) => this.#request("GET", "/v1/solves", {
|
|
134
|
+
query: {
|
|
135
|
+
limit: params.limit,
|
|
136
|
+
starting_after: params.starting_after,
|
|
137
|
+
status: params.status,
|
|
138
|
+
type: params.type
|
|
139
|
+
},
|
|
140
|
+
signal: options.signal
|
|
141
|
+
}),
|
|
142
|
+
/**
|
|
143
|
+
* Iterate every solve across pages, newest first.
|
|
144
|
+
*
|
|
145
|
+
* ```ts
|
|
146
|
+
* for await (const solve of nc.solves.listAll()) { ... }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
listAll: (params = {}, options = {}) => this.#listAll(params, options)
|
|
150
|
+
};
|
|
151
|
+
/** Fetch your account, including the current credit balance. */
|
|
152
|
+
me(options = {}) {
|
|
153
|
+
return this.#request("GET", "/v1/me", { signal: options.signal });
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Submit a solve and wait for it to finish, returning the solved solve.
|
|
157
|
+
*
|
|
158
|
+
* Uses the server's long-poll under the hood and keeps polling until the
|
|
159
|
+
* solve is terminal or `timeout` elapses. Throws {@link SolveFailedError} if
|
|
160
|
+
* the solve fails/expires/is cancelled, or {@link TimeoutError} on timeout.
|
|
161
|
+
*/
|
|
162
|
+
async solve(params, options = {}) {
|
|
163
|
+
const timeout = options.timeout ?? 18e4;
|
|
164
|
+
const deadline = Date.now() + timeout;
|
|
165
|
+
let solve = await this.solves.create(params, {
|
|
166
|
+
wait: waitSeconds(deadline),
|
|
167
|
+
signal: options.signal
|
|
168
|
+
});
|
|
169
|
+
while (!isTerminal(solve.status)) {
|
|
170
|
+
if (Date.now() >= deadline) {
|
|
171
|
+
throw new TimeoutError(
|
|
172
|
+
`Solve ${solve.id} did not finish within ${timeout}ms (last status: ${solve.status}).`,
|
|
173
|
+
{ code: void 0 }
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
solve = await this.solves.retrieve(solve.id, {
|
|
177
|
+
wait: waitSeconds(deadline),
|
|
178
|
+
signal: options.signal
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (solve.status !== "solved") throw new SolveFailedError(solve);
|
|
182
|
+
return solve;
|
|
183
|
+
}
|
|
184
|
+
// -- internals ------------------------------------------------------------
|
|
185
|
+
#timeoutForWait(wait) {
|
|
186
|
+
if (wait === void 0) return void 0;
|
|
187
|
+
return wait * 1e3 + 15e3;
|
|
188
|
+
}
|
|
189
|
+
async *#listAll(params, options) {
|
|
190
|
+
let cursor = params.starting_after;
|
|
191
|
+
for (; ; ) {
|
|
192
|
+
const page = await this.solves.list({ ...params, starting_after: cursor }, options);
|
|
193
|
+
for (const solve of page.data) yield solve;
|
|
194
|
+
if (!page.has_more || page.data.length === 0) return;
|
|
195
|
+
cursor = page.data[page.data.length - 1].id;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async #request(method, path, options = {}) {
|
|
199
|
+
const url = new URL(this.#baseURL + path);
|
|
200
|
+
if (options.query) {
|
|
201
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
202
|
+
if (value !== void 0 && value !== "") url.searchParams.set(key, String(value));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const headers = {
|
|
206
|
+
Authorization: `Bearer ${this.#apiKey}`,
|
|
207
|
+
Accept: "application/json"
|
|
208
|
+
};
|
|
209
|
+
let bodyInit;
|
|
210
|
+
if (options.body !== void 0) {
|
|
211
|
+
headers["Content-Type"] = "application/json";
|
|
212
|
+
bodyInit = JSON.stringify(options.body);
|
|
213
|
+
}
|
|
214
|
+
const controller = new AbortController();
|
|
215
|
+
const timeoutMs = options.timeout ?? this.#timeout;
|
|
216
|
+
const timer = setTimeout(() => controller.abort(new TimeoutError(`Request timed out after ${timeoutMs}ms.`)), timeoutMs);
|
|
217
|
+
const onAbort = () => controller.abort(options.signal?.reason);
|
|
218
|
+
if (options.signal) {
|
|
219
|
+
if (options.signal.aborted) onAbort();
|
|
220
|
+
else options.signal.addEventListener("abort", onAbort, { once: true });
|
|
221
|
+
}
|
|
222
|
+
let res;
|
|
223
|
+
try {
|
|
224
|
+
res = await this.#fetch(url.toString(), {
|
|
225
|
+
method,
|
|
226
|
+
headers,
|
|
227
|
+
body: bodyInit,
|
|
228
|
+
signal: controller.signal
|
|
229
|
+
});
|
|
230
|
+
} catch (err) {
|
|
231
|
+
if (err instanceof TimeoutError) throw err;
|
|
232
|
+
if (isAbort(err)) {
|
|
233
|
+
if (options.signal?.aborted) throw options.signal.reason instanceof Error ? options.signal.reason : new NoneCapError("Request aborted.");
|
|
234
|
+
throw new TimeoutError(`Request timed out after ${timeoutMs}ms.`);
|
|
235
|
+
}
|
|
236
|
+
throw new ConnectionError(
|
|
237
|
+
`Could not reach the NoneCap API at ${this.#baseURL}: ${err.message}`
|
|
238
|
+
);
|
|
239
|
+
} finally {
|
|
240
|
+
clearTimeout(timer);
|
|
241
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
242
|
+
}
|
|
243
|
+
return this.#parse(res, options.expect ?? []);
|
|
244
|
+
}
|
|
245
|
+
async #parse(res, expect) {
|
|
246
|
+
const text = await res.text();
|
|
247
|
+
let json;
|
|
248
|
+
if (text) {
|
|
249
|
+
try {
|
|
250
|
+
json = JSON.parse(text);
|
|
251
|
+
} catch {
|
|
252
|
+
throw new APIError(
|
|
253
|
+
`Unexpected non-JSON response (HTTP ${res.status}): ${text.slice(0, 200)}`,
|
|
254
|
+
{ status: res.status }
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (res.ok || expect.includes(res.status)) {
|
|
259
|
+
return json;
|
|
260
|
+
}
|
|
261
|
+
const envelope = json;
|
|
262
|
+
const apiError = envelope?.error;
|
|
263
|
+
throw errorFromResponse(
|
|
264
|
+
res.status,
|
|
265
|
+
apiError?.code,
|
|
266
|
+
apiError?.message ?? `HTTP ${res.status}`,
|
|
267
|
+
apiError?.param ?? null
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
function waitSeconds(deadline) {
|
|
272
|
+
const remaining = Math.ceil((deadline - Date.now()) / 1e3);
|
|
273
|
+
return Math.max(1, Math.min(MAX_WAIT_SECONDS, remaining));
|
|
274
|
+
}
|
|
275
|
+
function isAbort(err) {
|
|
276
|
+
return err instanceof Error && err.name === "AbortError";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
exports.APIError = APIError;
|
|
280
|
+
exports.AuthenticationError = AuthenticationError;
|
|
281
|
+
exports.ConflictError = ConflictError;
|
|
282
|
+
exports.ConnectionError = ConnectionError;
|
|
283
|
+
exports.InsufficientCreditsError = InsufficientCreditsError;
|
|
284
|
+
exports.NoneCap = NoneCap;
|
|
285
|
+
exports.NoneCapError = NoneCapError;
|
|
286
|
+
exports.NotFoundError = NotFoundError;
|
|
287
|
+
exports.PermissionError = PermissionError;
|
|
288
|
+
exports.RateLimitError = RateLimitError;
|
|
289
|
+
exports.SolveFailedError = SolveFailedError;
|
|
290
|
+
exports.TimeoutError = TimeoutError;
|
|
291
|
+
exports.ValidationError = ValidationError;
|
|
292
|
+
exports.isTerminal = isTerminal;
|
|
293
|
+
//# sourceMappingURL=index.cjs.map
|
|
294
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ErrorCode, Solve } from \"./types.js\";\n\n/**\n * Base class for every error this library throws. Catch `NoneCapError` to\n * handle all of them, or catch a subclass to handle one kind.\n */\nexport class NoneCapError extends Error {\n /** Machine-readable error code from the API envelope, when there is one. */\n readonly code: ErrorCode | undefined;\n /** HTTP status, when the error came from a response. */\n readonly status: number | undefined;\n /** The request field that was rejected, for validation errors. */\n readonly param: string | null;\n\n constructor(\n message: string,\n opts: { code?: ErrorCode; status?: number; param?: string | null } = {},\n ) {\n super(message);\n this.name = new.target.name;\n this.code = opts.code;\n this.status = opts.status;\n this.param = opts.param ?? null;\n // Restore the prototype chain when compiled down to ES5-era targets.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** 401 — the API key is missing, malformed, or revoked. */\nexport class AuthenticationError extends NoneCapError {}\n\n/** 403 — the key is valid but not allowed to do this (scope or locked account). */\nexport class PermissionError extends NoneCapError {}\n\n/** 402 — the account is out of credits. */\nexport class InsufficientCreditsError extends NoneCapError {}\n\n/** 422 / 400 — the request was rejected. `param` names the offending field. */\nexport class ValidationError extends NoneCapError {}\n\n/** 404 — no such resource. */\nexport class NotFoundError extends NoneCapError {}\n\n/** 409 — the solve is already in a terminal state (e.g. cancelling a finished solve). */\nexport class ConflictError extends NoneCapError {}\n\n/** 429 — too many concurrent solves, or rate limited. Back off and retry. */\nexport class RateLimitError extends NoneCapError {}\n\n/** 5xx, or a response that wasn't the expected shape. */\nexport class APIError extends NoneCapError {}\n\n/** The request never reached the API (DNS, TCP, TLS, timeout, offline). */\nexport class ConnectionError extends NoneCapError {}\n\n/**\n * Thrown by {@link NoneCap.solve} when a solve reaches a terminal state without\n * a token: `failed`, `expired`, or `cancelled`. The full solve is attached so\n * you can inspect `solve.error` and timings.\n */\nexport class SolveFailedError extends NoneCapError {\n readonly solve: Solve;\n\n constructor(solve: Solve) {\n const detail = solve.error ? `${solve.error.code}: ${solve.error.message}` : solve.status;\n super(`Solve ${solve.id} ${solve.status} (${detail})`, {\n code: undefined,\n status: undefined,\n });\n this.solve = solve;\n }\n}\n\n/** Thrown by {@link NoneCap.solve} when the client-side timeout elapses first. */\nexport class TimeoutError extends NoneCapError {}\n\n/** Map an API error envelope (plus HTTP status) to the right error subclass. */\nexport function errorFromResponse(\n status: number,\n code: ErrorCode | undefined,\n message: string,\n param: string | null,\n): NoneCapError {\n const opts = { code, status, param };\n switch (code) {\n case \"unauthorized\":\n return new AuthenticationError(message, opts);\n case \"forbidden\":\n case \"account_locked\":\n return new PermissionError(message, opts);\n case \"insufficient_credits\":\n return new InsufficientCreditsError(message, opts);\n case \"invalid_request\":\n case \"validation_error\":\n return new ValidationError(message, opts);\n case \"not_found\":\n return new NotFoundError(message, opts);\n case \"conflict\":\n return new ConflictError(message, opts);\n case \"rate_limited\":\n case \"concurrency_limit_exceeded\":\n case \"ext_daily_limit\":\n return new RateLimitError(message, opts);\n default:\n return new APIError(message, opts);\n }\n}\n","import {\n APIError,\n ConnectionError,\n NoneCapError,\n SolveFailedError,\n TimeoutError,\n errorFromResponse,\n} from \"./errors.js\";\nimport type {\n Account,\n ErrorCode,\n Solve,\n SolveCreateParams,\n SolveList,\n SolveListParams,\n SolveStatus,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.nonecap.com\";\n/** The API caps server-side long-poll at 90 seconds. */\nconst MAX_WAIT_SECONDS = 90;\nconst TERMINAL: ReadonlySet<SolveStatus> = new Set([\n \"solved\",\n \"failed\",\n \"cancelled\",\n \"expired\",\n]);\n\n/** Is this a terminal solve status? */\nexport function isTerminal(status: SolveStatus): boolean {\n return TERMINAL.has(status);\n}\n\n/** A `fetch` implementation. Defaults to the global `fetch`. */\nexport type FetchLike = (\n input: string,\n init?: RequestInit,\n) => Promise<Response>;\n\n/** Options for constructing a {@link NoneCap} client. */\nexport interface NoneCapOptions {\n /** Your NoneCap API key. Find it at https://dashboard.nonecap.com. */\n apiKey: string;\n /** Override the API base URL. Defaults to https://api.nonecap.com. */\n baseURL?: string;\n /**\n * Per-request timeout in milliseconds for a single HTTP call. Defaults to\n * 100000 (just above the 90s server long-poll). Note this bounds one HTTP\n * round trip, not the whole {@link NoneCap.solve} wait — use the `timeout`\n * option on `solve()` for that.\n */\n timeout?: number;\n /** Inject a custom `fetch` (for testing, proxies, or non-standard runtimes). */\n fetch?: FetchLike;\n}\n\n/** Per-call options for the long-polling endpoints. */\nexport interface WaitOptions {\n /**\n * Hold the connection open up to this many seconds (1–90) waiting for the\n * solve to finish before responding. Omit to return immediately.\n */\n wait?: number;\n /** Abort the request. */\n signal?: AbortSignal;\n}\n\n/** Options for {@link NoneCap.solve}. */\nexport interface SolveHelperOptions {\n /**\n * Give up after this many milliseconds and throw {@link TimeoutError}.\n * Defaults to 180000 (3 minutes).\n */\n timeout?: number;\n /** Abort waiting. */\n signal?: AbortSignal;\n}\n\ninterface RequestOptions {\n query?: Record<string, string | number | undefined>;\n body?: unknown;\n signal?: AbortSignal;\n /** Override the per-request timeout for this call (used by long polls). */\n timeout?: number;\n /** HTTP statuses to treat as success in addition to 2xx (e.g. 202). */\n expect?: number[];\n}\n\n/**\n * The NoneCap API client.\n *\n * ```ts\n * const nc = new NoneCap({ apiKey: process.env.NONECAP_KEY! });\n * const { token } = await nc.solve({ type: \"hcaptcha\", sitekey, url });\n * ```\n */\nexport class NoneCap {\n readonly #apiKey: string;\n readonly #baseURL: string;\n readonly #timeout: number;\n readonly #fetch: FetchLike;\n\n constructor(options: NoneCapOptions) {\n if (!options || !options.apiKey) {\n throw new NoneCapError(\n \"A NoneCap API key is required. Pass it as `new NoneCap({ apiKey })`.\",\n );\n }\n this.#apiKey = options.apiKey;\n this.#baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.#timeout = options.timeout ?? 100_000;\n const f = options.fetch ?? (globalThis.fetch as FetchLike | undefined);\n if (!f) {\n throw new NoneCapError(\n \"No `fetch` available. Use Node 18+, or pass a `fetch` in the client options.\",\n );\n }\n this.#fetch = f;\n }\n\n /** Operations on solves. */\n readonly solves = {\n /** Submit a solve. Pass `{ wait }` to hold the connection until it finishes. */\n create: (params: SolveCreateParams, options: WaitOptions = {}): Promise<Solve> =>\n this.#request<Solve>(\"POST\", \"/v1/solves\", {\n body: params,\n query: { wait: options.wait },\n signal: options.signal,\n timeout: this.#timeoutForWait(options.wait),\n expect: [202],\n }),\n\n /** Fetch a solve by id. Pass `{ wait }` to long-poll until it finishes. */\n retrieve: (id: string, options: WaitOptions = {}): Promise<Solve> =>\n this.#request<Solve>(\"GET\", `/v1/solves/${encodeURIComponent(id)}`, {\n query: { wait: options.wait },\n signal: options.signal,\n timeout: this.#timeoutForWait(options.wait),\n expect: [202],\n }),\n\n /** Cancel a pending or in-flight solve. */\n cancel: (id: string, options: { signal?: AbortSignal } = {}): Promise<Solve> =>\n this.#request<Solve>(\"DELETE\", `/v1/solves/${encodeURIComponent(id)}`, {\n signal: options.signal,\n }),\n\n /** Fetch one page of solves. */\n list: (params: SolveListParams = {}, options: { signal?: AbortSignal } = {}): Promise<SolveList> =>\n this.#request<SolveList>(\"GET\", \"/v1/solves\", {\n query: {\n limit: params.limit,\n starting_after: params.starting_after,\n status: params.status,\n type: params.type,\n },\n signal: options.signal,\n }),\n\n /**\n * Iterate every solve across pages, newest first.\n *\n * ```ts\n * for await (const solve of nc.solves.listAll()) { ... }\n * ```\n */\n listAll: (\n params: SolveListParams = {},\n options: { signal?: AbortSignal } = {},\n ): AsyncIterableIterator<Solve> => this.#listAll(params, options),\n };\n\n /** Fetch your account, including the current credit balance. */\n me(options: { signal?: AbortSignal } = {}): Promise<Account> {\n return this.#request<Account>(\"GET\", \"/v1/me\", { signal: options.signal });\n }\n\n /**\n * Submit a solve and wait for it to finish, returning the solved solve.\n *\n * Uses the server's long-poll under the hood and keeps polling until the\n * solve is terminal or `timeout` elapses. Throws {@link SolveFailedError} if\n * the solve fails/expires/is cancelled, or {@link TimeoutError} on timeout.\n */\n async solve(params: SolveCreateParams, options: SolveHelperOptions = {}): Promise<Solve> {\n const timeout = options.timeout ?? 180_000;\n const deadline = Date.now() + timeout;\n\n let solve = await this.solves.create(params, {\n wait: waitSeconds(deadline),\n signal: options.signal,\n });\n\n while (!isTerminal(solve.status)) {\n if (Date.now() >= deadline) {\n throw new TimeoutError(\n `Solve ${solve.id} did not finish within ${timeout}ms (last status: ${solve.status}).`,\n { code: undefined },\n );\n }\n solve = await this.solves.retrieve(solve.id, {\n wait: waitSeconds(deadline),\n signal: options.signal,\n });\n }\n\n if (solve.status !== \"solved\") throw new SolveFailedError(solve);\n return solve;\n }\n\n // -- internals ------------------------------------------------------------\n\n #timeoutForWait(wait?: number): number | undefined {\n if (wait === undefined) return undefined;\n // Give the socket a margin beyond the server's long-poll window.\n return wait * 1000 + 15_000;\n }\n\n async *#listAll(\n params: SolveListParams,\n options: { signal?: AbortSignal },\n ): AsyncIterableIterator<Solve> {\n let cursor = params.starting_after;\n for (;;) {\n const page = await this.solves.list({ ...params, starting_after: cursor }, options);\n for (const solve of page.data) yield solve;\n if (!page.has_more || page.data.length === 0) return;\n cursor = page.data[page.data.length - 1]!.id;\n }\n }\n\n async #request<T>(method: string, path: string, options: RequestOptions = {}): Promise<T> {\n const url = new URL(this.#baseURL + path);\n if (options.query) {\n for (const [key, value] of Object.entries(options.query)) {\n if (value !== undefined && value !== \"\") url.searchParams.set(key, String(value));\n }\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.#apiKey}`,\n Accept: \"application/json\",\n };\n let bodyInit: string | undefined;\n if (options.body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n bodyInit = JSON.stringify(options.body);\n }\n\n const controller = new AbortController();\n const timeoutMs = options.timeout ?? this.#timeout;\n const timer = setTimeout(() => controller.abort(new TimeoutError(`Request timed out after ${timeoutMs}ms.`)), timeoutMs);\n const onAbort = () => controller.abort(options.signal?.reason);\n if (options.signal) {\n if (options.signal.aborted) onAbort();\n else options.signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n\n let res: Response;\n try {\n res = await this.#fetch(url.toString(), {\n method,\n headers,\n body: bodyInit,\n signal: controller.signal,\n });\n } catch (err) {\n if (err instanceof TimeoutError) throw err;\n if (isAbort(err)) {\n // A caller-supplied signal aborting surfaces as the caller's reason.\n if (options.signal?.aborted) throw options.signal.reason instanceof Error\n ? options.signal.reason\n : new NoneCapError(\"Request aborted.\");\n throw new TimeoutError(`Request timed out after ${timeoutMs}ms.`);\n }\n throw new ConnectionError(\n `Could not reach the NoneCap API at ${this.#baseURL}: ${(err as Error).message}`,\n );\n } finally {\n clearTimeout(timer);\n options.signal?.removeEventListener(\"abort\", onAbort);\n }\n\n return this.#parse<T>(res, options.expect ?? []);\n }\n\n async #parse<T>(res: Response, expect: number[]): Promise<T> {\n const text = await res.text();\n let json: unknown;\n if (text) {\n try {\n json = JSON.parse(text);\n } catch {\n throw new APIError(\n `Unexpected non-JSON response (HTTP ${res.status}): ${text.slice(0, 200)}`,\n { status: res.status },\n );\n }\n }\n\n if (res.ok || expect.includes(res.status)) {\n return json as T;\n }\n\n const envelope = json as { error?: { code?: ErrorCode; message?: string; param?: string | null } } | undefined;\n const apiError = envelope?.error;\n throw errorFromResponse(\n res.status,\n apiError?.code,\n apiError?.message ?? `HTTP ${res.status}`,\n apiError?.param ?? null,\n );\n }\n}\n\n/**\n * The `wait` value for the next long-poll: whole seconds until `deadline`,\n * clamped to the server's 1–90 window. The floor of 1 keeps the param valid,\n * so callers must decide whether the deadline has passed with the clock, not\n * with this return value.\n */\nfunction waitSeconds(deadline: number): number {\n const remaining = Math.ceil((deadline - Date.now()) / 1000);\n return Math.max(1, Math.min(MAX_WAIT_SECONDS, remaining));\n}\n\nfunction isAbort(err: unknown): boolean {\n return err instanceof Error && err.name === \"AbortError\";\n}\n"]}
|