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/dist/index.js ADDED
@@ -0,0 +1,279 @@
1
+ // src/errors.ts
2
+ var NoneCapError = class extends Error {
3
+ /** Machine-readable error code from the API envelope, when there is one. */
4
+ code;
5
+ /** HTTP status, when the error came from a response. */
6
+ status;
7
+ /** The request field that was rejected, for validation errors. */
8
+ param;
9
+ constructor(message, opts = {}) {
10
+ super(message);
11
+ this.name = new.target.name;
12
+ this.code = opts.code;
13
+ this.status = opts.status;
14
+ this.param = opts.param ?? null;
15
+ Object.setPrototypeOf(this, new.target.prototype);
16
+ }
17
+ };
18
+ var AuthenticationError = class extends NoneCapError {
19
+ };
20
+ var PermissionError = class extends NoneCapError {
21
+ };
22
+ var InsufficientCreditsError = class extends NoneCapError {
23
+ };
24
+ var ValidationError = class extends NoneCapError {
25
+ };
26
+ var NotFoundError = class extends NoneCapError {
27
+ };
28
+ var ConflictError = class extends NoneCapError {
29
+ };
30
+ var RateLimitError = class extends NoneCapError {
31
+ };
32
+ var APIError = class extends NoneCapError {
33
+ };
34
+ var ConnectionError = class extends NoneCapError {
35
+ };
36
+ var SolveFailedError = class extends NoneCapError {
37
+ solve;
38
+ constructor(solve) {
39
+ const detail = solve.error ? `${solve.error.code}: ${solve.error.message}` : solve.status;
40
+ super(`Solve ${solve.id} ${solve.status} (${detail})`, {
41
+ code: void 0,
42
+ status: void 0
43
+ });
44
+ this.solve = solve;
45
+ }
46
+ };
47
+ var TimeoutError = class extends NoneCapError {
48
+ };
49
+ function errorFromResponse(status, code, message, param) {
50
+ const opts = { code, status, param };
51
+ switch (code) {
52
+ case "unauthorized":
53
+ return new AuthenticationError(message, opts);
54
+ case "forbidden":
55
+ case "account_locked":
56
+ return new PermissionError(message, opts);
57
+ case "insufficient_credits":
58
+ return new InsufficientCreditsError(message, opts);
59
+ case "invalid_request":
60
+ case "validation_error":
61
+ return new ValidationError(message, opts);
62
+ case "not_found":
63
+ return new NotFoundError(message, opts);
64
+ case "conflict":
65
+ return new ConflictError(message, opts);
66
+ case "rate_limited":
67
+ case "concurrency_limit_exceeded":
68
+ case "ext_daily_limit":
69
+ return new RateLimitError(message, opts);
70
+ default:
71
+ return new APIError(message, opts);
72
+ }
73
+ }
74
+
75
+ // src/client.ts
76
+ var DEFAULT_BASE_URL = "https://api.nonecap.com";
77
+ var MAX_WAIT_SECONDS = 90;
78
+ var TERMINAL = /* @__PURE__ */ new Set([
79
+ "solved",
80
+ "failed",
81
+ "cancelled",
82
+ "expired"
83
+ ]);
84
+ function isTerminal(status) {
85
+ return TERMINAL.has(status);
86
+ }
87
+ var NoneCap = class {
88
+ #apiKey;
89
+ #baseURL;
90
+ #timeout;
91
+ #fetch;
92
+ constructor(options) {
93
+ if (!options || !options.apiKey) {
94
+ throw new NoneCapError(
95
+ "A NoneCap API key is required. Pass it as `new NoneCap({ apiKey })`."
96
+ );
97
+ }
98
+ this.#apiKey = options.apiKey;
99
+ this.#baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
100
+ this.#timeout = options.timeout ?? 1e5;
101
+ const f = options.fetch ?? globalThis.fetch;
102
+ if (!f) {
103
+ throw new NoneCapError(
104
+ "No `fetch` available. Use Node 18+, or pass a `fetch` in the client options."
105
+ );
106
+ }
107
+ this.#fetch = f;
108
+ }
109
+ /** Operations on solves. */
110
+ solves = {
111
+ /** Submit a solve. Pass `{ wait }` to hold the connection until it finishes. */
112
+ create: (params, options = {}) => this.#request("POST", "/v1/solves", {
113
+ body: params,
114
+ query: { wait: options.wait },
115
+ signal: options.signal,
116
+ timeout: this.#timeoutForWait(options.wait),
117
+ expect: [202]
118
+ }),
119
+ /** Fetch a solve by id. Pass `{ wait }` to long-poll until it finishes. */
120
+ retrieve: (id, options = {}) => this.#request("GET", `/v1/solves/${encodeURIComponent(id)}`, {
121
+ query: { wait: options.wait },
122
+ signal: options.signal,
123
+ timeout: this.#timeoutForWait(options.wait),
124
+ expect: [202]
125
+ }),
126
+ /** Cancel a pending or in-flight solve. */
127
+ cancel: (id, options = {}) => this.#request("DELETE", `/v1/solves/${encodeURIComponent(id)}`, {
128
+ signal: options.signal
129
+ }),
130
+ /** Fetch one page of solves. */
131
+ list: (params = {}, options = {}) => this.#request("GET", "/v1/solves", {
132
+ query: {
133
+ limit: params.limit,
134
+ starting_after: params.starting_after,
135
+ status: params.status,
136
+ type: params.type
137
+ },
138
+ signal: options.signal
139
+ }),
140
+ /**
141
+ * Iterate every solve across pages, newest first.
142
+ *
143
+ * ```ts
144
+ * for await (const solve of nc.solves.listAll()) { ... }
145
+ * ```
146
+ */
147
+ listAll: (params = {}, options = {}) => this.#listAll(params, options)
148
+ };
149
+ /** Fetch your account, including the current credit balance. */
150
+ me(options = {}) {
151
+ return this.#request("GET", "/v1/me", { signal: options.signal });
152
+ }
153
+ /**
154
+ * Submit a solve and wait for it to finish, returning the solved solve.
155
+ *
156
+ * Uses the server's long-poll under the hood and keeps polling until the
157
+ * solve is terminal or `timeout` elapses. Throws {@link SolveFailedError} if
158
+ * the solve fails/expires/is cancelled, or {@link TimeoutError} on timeout.
159
+ */
160
+ async solve(params, options = {}) {
161
+ const timeout = options.timeout ?? 18e4;
162
+ const deadline = Date.now() + timeout;
163
+ let solve = await this.solves.create(params, {
164
+ wait: waitSeconds(deadline),
165
+ signal: options.signal
166
+ });
167
+ while (!isTerminal(solve.status)) {
168
+ if (Date.now() >= deadline) {
169
+ throw new TimeoutError(
170
+ `Solve ${solve.id} did not finish within ${timeout}ms (last status: ${solve.status}).`,
171
+ { code: void 0 }
172
+ );
173
+ }
174
+ solve = await this.solves.retrieve(solve.id, {
175
+ wait: waitSeconds(deadline),
176
+ signal: options.signal
177
+ });
178
+ }
179
+ if (solve.status !== "solved") throw new SolveFailedError(solve);
180
+ return solve;
181
+ }
182
+ // -- internals ------------------------------------------------------------
183
+ #timeoutForWait(wait) {
184
+ if (wait === void 0) return void 0;
185
+ return wait * 1e3 + 15e3;
186
+ }
187
+ async *#listAll(params, options) {
188
+ let cursor = params.starting_after;
189
+ for (; ; ) {
190
+ const page = await this.solves.list({ ...params, starting_after: cursor }, options);
191
+ for (const solve of page.data) yield solve;
192
+ if (!page.has_more || page.data.length === 0) return;
193
+ cursor = page.data[page.data.length - 1].id;
194
+ }
195
+ }
196
+ async #request(method, path, options = {}) {
197
+ const url = new URL(this.#baseURL + path);
198
+ if (options.query) {
199
+ for (const [key, value] of Object.entries(options.query)) {
200
+ if (value !== void 0 && value !== "") url.searchParams.set(key, String(value));
201
+ }
202
+ }
203
+ const headers = {
204
+ Authorization: `Bearer ${this.#apiKey}`,
205
+ Accept: "application/json"
206
+ };
207
+ let bodyInit;
208
+ if (options.body !== void 0) {
209
+ headers["Content-Type"] = "application/json";
210
+ bodyInit = JSON.stringify(options.body);
211
+ }
212
+ const controller = new AbortController();
213
+ const timeoutMs = options.timeout ?? this.#timeout;
214
+ const timer = setTimeout(() => controller.abort(new TimeoutError(`Request timed out after ${timeoutMs}ms.`)), timeoutMs);
215
+ const onAbort = () => controller.abort(options.signal?.reason);
216
+ if (options.signal) {
217
+ if (options.signal.aborted) onAbort();
218
+ else options.signal.addEventListener("abort", onAbort, { once: true });
219
+ }
220
+ let res;
221
+ try {
222
+ res = await this.#fetch(url.toString(), {
223
+ method,
224
+ headers,
225
+ body: bodyInit,
226
+ signal: controller.signal
227
+ });
228
+ } catch (err) {
229
+ if (err instanceof TimeoutError) throw err;
230
+ if (isAbort(err)) {
231
+ if (options.signal?.aborted) throw options.signal.reason instanceof Error ? options.signal.reason : new NoneCapError("Request aborted.");
232
+ throw new TimeoutError(`Request timed out after ${timeoutMs}ms.`);
233
+ }
234
+ throw new ConnectionError(
235
+ `Could not reach the NoneCap API at ${this.#baseURL}: ${err.message}`
236
+ );
237
+ } finally {
238
+ clearTimeout(timer);
239
+ options.signal?.removeEventListener("abort", onAbort);
240
+ }
241
+ return this.#parse(res, options.expect ?? []);
242
+ }
243
+ async #parse(res, expect) {
244
+ const text = await res.text();
245
+ let json;
246
+ if (text) {
247
+ try {
248
+ json = JSON.parse(text);
249
+ } catch {
250
+ throw new APIError(
251
+ `Unexpected non-JSON response (HTTP ${res.status}): ${text.slice(0, 200)}`,
252
+ { status: res.status }
253
+ );
254
+ }
255
+ }
256
+ if (res.ok || expect.includes(res.status)) {
257
+ return json;
258
+ }
259
+ const envelope = json;
260
+ const apiError = envelope?.error;
261
+ throw errorFromResponse(
262
+ res.status,
263
+ apiError?.code,
264
+ apiError?.message ?? `HTTP ${res.status}`,
265
+ apiError?.param ?? null
266
+ );
267
+ }
268
+ };
269
+ function waitSeconds(deadline) {
270
+ const remaining = Math.ceil((deadline - Date.now()) / 1e3);
271
+ return Math.max(1, Math.min(MAX_WAIT_SECONDS, remaining));
272
+ }
273
+ function isAbort(err) {
274
+ return err instanceof Error && err.name === "AbortError";
275
+ }
276
+
277
+ export { APIError, AuthenticationError, ConflictError, ConnectionError, InsufficientCreditsError, NoneCap, NoneCapError, NotFoundError, PermissionError, RateLimitError, SolveFailedError, TimeoutError, ValidationError, isTerminal };
278
+ //# sourceMappingURL=index.js.map
279
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","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"]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "nonecap",
3
+ "version": "0.1.0",
4
+ "description": "Official TypeScript client for the NoneCap hCaptcha solving API.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Lunium",
8
+ "homepage": "https://nonecap.com",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/nonecap/nonecap-js.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/nonecap/nonecap-js/issues"
15
+ },
16
+ "keywords": [
17
+ "nonecap",
18
+ "hcaptcha",
19
+ "captcha",
20
+ "captcha-solver",
21
+ "hcaptcha-solver",
22
+ "api-client",
23
+ "sdk",
24
+ "typescript"
25
+ ],
26
+ "main": "./dist/index.cjs",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js",
33
+ "require": "./dist/index.cjs"
34
+ },
35
+ "./package.json": "./package.json"
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "README.md",
40
+ "LICENSE"
41
+ ],
42
+ "sideEffects": false,
43
+ "engines": {
44
+ "node": ">=18"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup",
48
+ "typecheck": "tsc --noEmit",
49
+ "test": "vitest run",
50
+ "test:watch": "vitest",
51
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build"
52
+ },
53
+ "devDependencies": {
54
+ "tsup": "^8.3.5",
55
+ "typescript": "^5.7.2",
56
+ "vitest": "^2.1.8"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ }
61
+ }