ribaunt 0.2.2 → 0.2.4
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/README.md +113 -10
- package/demo/lib/ribaunt-solver.wasm +0 -0
- package/demo/lib/solver-worker.js +174 -0
- package/demo/lib/solver-worker.js.map +1 -0
- package/demo/lib/solver.js +135 -0
- package/demo/lib/solver.js.map +1 -0
- package/demo/lib/wasm-solver.js +296 -0
- package/demo/lib/wasm-solver.js.map +1 -0
- package/demo/lib/widget-browser.js +9 -0
- package/demo/lib/widget-browser.js.map +1 -0
- package/demo/lib/widget.js +942 -0
- package/demo/lib/widget.js.map +1 -0
- package/demo/lib/worker-client.js +128 -0
- package/demo/lib/worker-client.js.map +1 -0
- package/dist/cjs/index.d.ts +48 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +196 -17
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/risk.d.ts +124 -0
- package/dist/cjs/risk.d.ts.map +1 -0
- package/dist/cjs/risk.js +213 -0
- package/dist/cjs/risk.js.map +1 -0
- package/dist/index.d.ts +48 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +193 -16
- package/dist/index.js.map +1 -1
- package/dist/ribaunt-solver.wasm +0 -0
- package/dist/risk.d.ts +124 -0
- package/dist/risk.d.ts.map +1 -0
- package/dist/risk.js +202 -0
- package/dist/risk.js.map +1 -0
- package/dist/solver-worker.d.ts.map +1 -1
- package/dist/solver-worker.js +166 -7
- package/dist/solver-worker.js.map +1 -1
- package/dist/solver.d.ts +1 -0
- package/dist/solver.d.ts.map +1 -1
- package/dist/solver.js +38 -3
- package/dist/solver.js.map +1 -1
- package/dist/wasm-solver.d.ts +29 -0
- package/dist/wasm-solver.d.ts.map +1 -0
- package/dist/wasm-solver.js +296 -0
- package/dist/wasm-solver.js.map +1 -0
- package/dist/widget-browser.d.ts +3 -2
- package/dist/widget-browser.d.ts.map +1 -1
- package/dist/widget-browser.js +1 -1
- package/dist/widget-browser.js.map +1 -1
- package/dist/widget-react.d.ts +4 -1
- package/dist/widget-react.d.ts.map +1 -1
- package/dist/widget-react.js +102 -14
- package/dist/widget-react.js.map +1 -1
- package/dist/widget.d.ts +42 -14
- package/dist/widget.d.ts.map +1 -1
- package/dist/widget.js +254 -12
- package/dist/widget.js.map +1 -1
- package/dist/worker-client.d.ts +7 -1
- package/dist/worker-client.d.ts.map +1 -1
- package/dist/worker-client.js +69 -19
- package/dist/worker-client.js.map +1 -1
- package/package.json +22 -9
- package/context7.json +0 -4
package/README.md
CHANGED
|
@@ -16,10 +16,19 @@ Ribaunt is a stateless proof-of-work CAPTCHA library for Node.js and modern brow
|
|
|
16
16
|
- Browser widget for plain HTML apps
|
|
17
17
|
- React and Next.js-friendly wrapper via `ribaunt/widget-react`
|
|
18
18
|
- Server helpers for creating, solving, and verifying PoW challenges
|
|
19
|
+
- Programmable risk engine (`assess()`) with caller-supplied signals, pluggable scorers, and `allow`/`challenge`/`block` policy
|
|
19
20
|
- Default process-local replay protection with support for remote replay stores
|
|
20
21
|
- CSS custom properties for theming
|
|
21
22
|
- TypeScript types included
|
|
22
23
|
|
|
24
|
+
## What Ribaunt does not do
|
|
25
|
+
|
|
26
|
+
Read this before relying on Ribaunt alone.
|
|
27
|
+
|
|
28
|
+
- **Ribaunt does not rate-limit your endpoints.** Nothing stops an attacker from hammering your challenge or verify endpoints themselves, no solving required. Put a rate limiter (per-IP, per-account, or WAF-level) in front of both. Ribaunt exposes optional rate-limit and telemetry hooks so you can wire your own limiter and metrics in at the point challenges are issued and verified (see `rateLimiter` and `onEvent` below).
|
|
29
|
+
- **Ribaunt does not prove a human is present.** It raises the cost of automated abuse. A sufficiently resourced attacker can solve any proof-of-work challenge.
|
|
30
|
+
- **Ribaunt is one layer, not the whole stack.** Use it behind rate limiting and risk signals, and never as the sole gate for sensitive actions such as password reset, account recovery, or payments.
|
|
31
|
+
|
|
23
32
|
## Install
|
|
24
33
|
|
|
25
34
|
```bash
|
|
@@ -56,8 +65,8 @@ const app = express();
|
|
|
56
65
|
|
|
57
66
|
app.use(express.json());
|
|
58
67
|
|
|
59
|
-
app.get('/api/captcha/challenge', (_req, res) => {
|
|
60
|
-
const challenges = createChallenge(5, 4, 120);
|
|
68
|
+
app.get('/api/captcha/challenge', async (_req, res) => {
|
|
69
|
+
const challenges = await createChallenge(5, 4, 120);
|
|
61
70
|
res.json({ challenges });
|
|
62
71
|
});
|
|
63
72
|
|
|
@@ -129,10 +138,10 @@ For Next.js App Router, keep the widget in a client component. Do not expose `RI
|
|
|
129
138
|
|
|
130
139
|
### `createChallenge(difficulty, amount, ttlSeconds)`
|
|
131
140
|
|
|
132
|
-
Creates signed challenge tokens.
|
|
141
|
+
Creates signed challenge tokens. Since the optional `rateLimiter` hook can be async, `createChallenge()` returns a `Promise`:
|
|
133
142
|
|
|
134
143
|
```ts
|
|
135
|
-
const challenges = createChallenge(5, 4, 120);
|
|
144
|
+
const challenges = await createChallenge(5, 4, 120);
|
|
136
145
|
```
|
|
137
146
|
|
|
138
147
|
| Parameter | Default | Description |
|
|
@@ -143,12 +152,14 @@ const challenges = createChallenge(5, 4, 120);
|
|
|
143
152
|
|
|
144
153
|
Validate user- or config-controlled values before passing them to `createChallenge()`. Invalid values throw.
|
|
145
154
|
|
|
155
|
+
> **API note:** the options object form defaults to a single challenge (`amount: 1`) unless you pass `amount`; only the positional form above defaults to `4`.
|
|
156
|
+
|
|
146
157
|
### `createChallenge({ difficulty: "auto", ... })`
|
|
147
158
|
|
|
148
159
|
Creates adaptive challenge tokens from a server-bounded calibration hint. Calibration is untrusted and raise-only: it can increase work for fast clients, but it never lowers the server baseline.
|
|
149
160
|
|
|
150
161
|
```ts
|
|
151
|
-
const challenges = createChallenge({
|
|
162
|
+
const challenges = await createChallenge({
|
|
152
163
|
difficulty: 'auto',
|
|
153
164
|
calibration: requestBody.calibration,
|
|
154
165
|
targetDurationMs: 750,
|
|
@@ -196,9 +207,41 @@ const workload = selectWorkload({
|
|
|
196
207
|
minDifficulty: 3,
|
|
197
208
|
maxDifficulty: 6,
|
|
198
209
|
});
|
|
199
|
-
// { difficulty:
|
|
210
|
+
// { difficulty: 3, amount: 5, estimatedAttempts: 20480 }
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### `assess(options)` — Risk Engine
|
|
214
|
+
|
|
215
|
+
Stateless, caller-driven risk assessment. All signals are caller-supplied (untrusted). Returns a bounded heuristic risk score (0–100, not a probability) and a policy action.
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import { assess } from 'ribaunt';
|
|
219
|
+
|
|
220
|
+
const assessment = await assess({
|
|
221
|
+
signals: {
|
|
222
|
+
accountAgeSeconds: account.ageSeconds,
|
|
223
|
+
requestVelocity: requestsPerMinute,
|
|
224
|
+
userAgent: req.headers['user-agent'],
|
|
225
|
+
ip: req.ip, // accepted for custom scorers; default scorer ignores it
|
|
226
|
+
},
|
|
227
|
+
// thresholds: { challenge: 40, block: 80 }, // defaults shown
|
|
228
|
+
// scorer: myScorer, // optional pluggable RiskScorer
|
|
229
|
+
// workload: { minDifficulty: 3, maxDifficulty: 6, targetDurationMs: 750, calibration },
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (assessment.action === 'allow') { /* continue */ }
|
|
233
|
+
else if (assessment.action === 'challenge') {
|
|
234
|
+
const workload = assessment.workload!; // { difficulty, amount, estimatedAttempts } from selectWorkload()
|
|
235
|
+
}
|
|
236
|
+
else { /* block — application decides how to reject */ }
|
|
200
237
|
```
|
|
201
238
|
|
|
239
|
+
- `risk < challenge` → `allow` (no workload)
|
|
240
|
+
- `challenge ≤ risk < block` → `challenge` (with workload via existing `selectWorkload()`)
|
|
241
|
+
- `risk ≥ block` → `block` (no workload)
|
|
242
|
+
|
|
243
|
+
Defaults are `DEFAULT_RISK_THRESHOLDS = { challenge: 40, block: 80 }` — tune to your app; they are policy defaults, not fraud probabilities. Custom scorers (`RiskScorer { score(signals): Promise<number> }`) are `async` so you can call a remote model; invalid outputs (`NaN`/`Infinity`/`<0`/`>100`/non-number) are rejected, not clamped, and thrown errors propagate. See `docs/risk-engine.md` for the full default-scorer buckets, trust model, and limitations.
|
|
244
|
+
|
|
202
245
|
### `verifySolution(tokens, solutions, options?)`
|
|
203
246
|
|
|
204
247
|
Verifies submitted solutions and returns a structured result:
|
|
@@ -226,6 +269,59 @@ const result = await verifySolution(tokens, solutions, {
|
|
|
226
269
|
});
|
|
227
270
|
```
|
|
228
271
|
|
|
272
|
+
### `rateLimiter` hook
|
|
273
|
+
|
|
274
|
+
Ribaunt does not rate-limit anything itself — bring your own limiter. Both `createChallenge()` and `verifySolution()` accept an optional `rateLimiter: { check(key?) => Promise<boolean> }` that runs before the operation. If it returns `false`, issuance or verification is blocked with a `RateLimitedError` (`code = 'rate-limited'`) so you can catch it specifically. Errors thrown by your limiter propagate unchanged.
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
import { RateLimitedError } from 'ribaunt';
|
|
278
|
+
|
|
279
|
+
const rateLimiter = {
|
|
280
|
+
check: async (key?: string) => (await redisBucket.hit(key ?? 'unknown')) < 10,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Challenge endpoint: key the limiter on the client IP by closing over the
|
|
284
|
+
// request instead of passing `context`. Passing `context: req.ip` here would
|
|
285
|
+
// also cryptographically bind each challenge to that IP, forcing the verify
|
|
286
|
+
// call to pass the exact same `context` (see `verifySolution` below).
|
|
287
|
+
app.get('/api/captcha/challenge', async (req, res) => {
|
|
288
|
+
try {
|
|
289
|
+
const challenges = await createChallenge({
|
|
290
|
+
difficulty: 5,
|
|
291
|
+
amount: 4,
|
|
292
|
+
rateLimiter,
|
|
293
|
+
});
|
|
294
|
+
res.json({ challenges });
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (error instanceof RateLimitedError) {
|
|
297
|
+
return res.status(429).json({ error: 'Too many challenge requests' });
|
|
298
|
+
}
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
> **Context binding:** if you *do* pass `context` to `createChallenge()`, every challenge token is bound to that context via an HMAC digest. You must then pass the identical `context` to `verifySolution()` or verification fails with `context-mismatch`. Use `context` for binding a challenge to a specific action (e.g. one signup attempt), not for rate limiting.
|
|
305
|
+
|
|
306
|
+
### `onEvent` telemetry hook
|
|
307
|
+
|
|
308
|
+
Both entry points accept an optional synchronous `onEvent` callback for structured lifecycle events. It is fire-and-forget: a throwing consumer can never break challenge issuance or verification.
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
const counts = { issued: 0, success: 0, failure: 0 };
|
|
312
|
+
|
|
313
|
+
const onEvent = (event: RibauntEvent) => {
|
|
314
|
+
if (event.type === 'challenge-issued') counts.issued += 1;
|
|
315
|
+
else if (event.type === 'verify-success') counts.success += 1;
|
|
316
|
+
else if (event.type === 'verify-failure') counts.failure += 1;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
await createChallenge({ difficulty: 5, amount: 4, onEvent });
|
|
320
|
+
const result = await verifySolution(tokens, solutions, { onEvent });
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`verify-failure` events carry the same `reason` values as `VerifySolutionResult` (`invalid-token`, `expired-token`, `invalid-solution`, `context-mismatch`, `replay-detected`, `replay-store-unavailable`, `configuration-error`). Add aggregation over Windows, StatsD, or Prometheus yourself — Ribaunt only emits raw events.
|
|
324
|
+
|
|
229
325
|
### `solveChallenge(token, options?)`
|
|
230
326
|
|
|
231
327
|
Solves challenges synchronously. This is mainly useful for tests and debugging.
|
|
@@ -249,6 +345,8 @@ const solutions = solveChallenge(challenges, {
|
|
|
249
345
|
show-warning="false"
|
|
250
346
|
warning-message="Verification may take longer on this device."
|
|
251
347
|
solve-timeout="15000"
|
|
348
|
+
worker-mode="preferred"
|
|
349
|
+
wasm-mode="preferred"
|
|
252
350
|
disabled="false"
|
|
253
351
|
></ribaunt-widget>
|
|
254
352
|
```
|
|
@@ -262,7 +360,10 @@ const solutions = solveChallenge(challenges, {
|
|
|
262
360
|
| `calibrate` | `calibrate` | Benchmarks the browser and sends `{ calibration }` with POST challenge requests. |
|
|
263
361
|
| `show-warning` | `showWarning` | Shows a warning banner. |
|
|
264
362
|
| `warning-message` | `warningMessage` | Custom warning text. |
|
|
265
|
-
| `
|
|
363
|
+
| `show-progress` | `showProgress` | Set to `"false"` to swap the percentage ring for a plain bars spinner with a static `Loading...` label. |
|
|
364
|
+
| `solve-timeout` | `solveTimeout` | Optional timeout in milliseconds for the whole verification attempt (fetching, solving, and verifying). |
|
|
365
|
+
| `worker-mode` | `workerMode` | Web Worker solving: `preferred` (default; falls back to the main thread), `required` (fail if unavailable), or `disabled`. |
|
|
366
|
+
| `wasm-mode` | `wasmMode` | WASM solver: `preferred` (default; uses WASM batch solver inside worker if available), `disabled` (always use JS solver inside worker). Independent from `worker-mode`. |
|
|
266
367
|
| `disabled` | `disabled` | Blocks user interaction and automatic verification. |
|
|
267
368
|
| | `fallback` | React-only. Custom loading element while widget dynamic import loads. Defaults to a built-in shimmer skeleton. |
|
|
268
369
|
|
|
@@ -295,6 +396,7 @@ See [theming docs](https://github.com/ribaunt/ribaunt/blob/main/docs/theming.md)
|
|
|
295
396
|
|
|
296
397
|
- [Quick start](https://github.com/ribaunt/ribaunt/blob/main/docs/quick-start.md)
|
|
297
398
|
- [Configuration](https://github.com/ribaunt/ribaunt/blob/main/docs/configuration.md)
|
|
399
|
+
- [Risk Engine](https://github.com/ribaunt/ribaunt/blob/main/docs/risk-engine.md)
|
|
298
400
|
- [Events](https://github.com/ribaunt/ribaunt/blob/main/docs/events.md)
|
|
299
401
|
- [HTML integration](https://github.com/ribaunt/ribaunt/blob/main/docs/integrations/html.md)
|
|
300
402
|
- [React integration](https://github.com/ribaunt/ribaunt/blob/main/docs/integrations/react.md)
|
|
@@ -313,9 +415,10 @@ You can also ask documentation-aware tools to use the Context7 library ID:
|
|
|
313
415
|
## Development
|
|
314
416
|
|
|
315
417
|
```bash
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
418
|
+
corepack enable
|
|
419
|
+
pnpm install
|
|
420
|
+
pnpm test
|
|
421
|
+
pnpm run build
|
|
319
422
|
```
|
|
320
423
|
|
|
321
424
|
## License
|
|
Binary file
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/// <reference lib="webworker" />
|
|
2
|
+
import { solveChallenge, decodeChallengeToken } from './solver.js';
|
|
3
|
+
import { ensureWasm, solveBatch, resetWasmHeap } from './wasm-solver.js';
|
|
4
|
+
const workerScope = self;
|
|
5
|
+
const activeControllers = new Map();
|
|
6
|
+
// Cache WASM initialization per worker lifetime (wasm-solver already caches, but keep explicit)
|
|
7
|
+
let wasmBackendState = 'uninitialized';
|
|
8
|
+
const WASM_BATCH_SIZE = 1024;
|
|
9
|
+
const yieldToEventLoop = (() => {
|
|
10
|
+
if (typeof MessageChannel !== 'function') {
|
|
11
|
+
return () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
12
|
+
}
|
|
13
|
+
let channel = null;
|
|
14
|
+
const waiting = new Set();
|
|
15
|
+
return () => {
|
|
16
|
+
if (!channel) {
|
|
17
|
+
channel = new MessageChannel();
|
|
18
|
+
channel.port1.onmessage = () => {
|
|
19
|
+
for (const resolve of waiting)
|
|
20
|
+
resolve();
|
|
21
|
+
waiting.clear();
|
|
22
|
+
};
|
|
23
|
+
channel.port1.unref?.();
|
|
24
|
+
}
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
waiting.add(resolve);
|
|
27
|
+
channel.port2.postMessage(null);
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
})();
|
|
31
|
+
async function solveSingleChallengeWasm(token, signal) {
|
|
32
|
+
const payload = decodeChallengeToken(token);
|
|
33
|
+
if (!payload)
|
|
34
|
+
return undefined;
|
|
35
|
+
const { challenge, difficulty } = payload;
|
|
36
|
+
let startNonce = 0;
|
|
37
|
+
try {
|
|
38
|
+
while (true) {
|
|
39
|
+
if (signal?.aborted) {
|
|
40
|
+
throw new DOMException('Challenge solving aborted', 'AbortError');
|
|
41
|
+
}
|
|
42
|
+
const result = solveBatch(challenge, startNonce, WASM_BATCH_SIZE, difficulty);
|
|
43
|
+
if (result.found && result.nonce && result.hash) {
|
|
44
|
+
// Validate before returning (wasm-solver already validated, but double-check)
|
|
45
|
+
if (typeof result.nonce !== 'string' || typeof result.hash !== 'string') {
|
|
46
|
+
throw new Error('Invalid WASM result shape');
|
|
47
|
+
}
|
|
48
|
+
return { nonce: result.nonce, hash: result.hash };
|
|
49
|
+
}
|
|
50
|
+
startNonce += WASM_BATCH_SIZE;
|
|
51
|
+
// Overflow guard - do not wrap; allow final batch where last nonce is 0x7fffffff
|
|
52
|
+
if (startNonce > 0x7fffffff - WASM_BATCH_SIZE + 1) {
|
|
53
|
+
throw new Error('WASM solver nonce overflow');
|
|
54
|
+
}
|
|
55
|
+
// Yield every 2048 nonces to keep worker responsive (matches JS solver's 2048)
|
|
56
|
+
if (startNonce % 2048 === 0) {
|
|
57
|
+
await yieldToEventLoop();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
// Reset heap to avoid leak between tokens/requests
|
|
63
|
+
try {
|
|
64
|
+
resetWasmHeap();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// ignore
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function solveChallengeWasm(tokens, onProgress, signal) {
|
|
72
|
+
const solutions = [];
|
|
73
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
74
|
+
const token = tokens[i];
|
|
75
|
+
if (!token)
|
|
76
|
+
throw new Error(`Invalid token at index ${i}`);
|
|
77
|
+
if (signal?.aborted)
|
|
78
|
+
throw new DOMException('Challenge solving aborted', 'AbortError');
|
|
79
|
+
const solution = await solveSingleChallengeWasm(token, signal);
|
|
80
|
+
if (!solution)
|
|
81
|
+
throw new Error(`Failed to solve challenge ${i + 1}`);
|
|
82
|
+
solutions.push(solution);
|
|
83
|
+
if (onProgress) {
|
|
84
|
+
const progress = Math.round(((i + 1) / tokens.length) * 100);
|
|
85
|
+
onProgress(progress);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return solutions;
|
|
89
|
+
}
|
|
90
|
+
async function selectBackend(wasmMode) {
|
|
91
|
+
if (wasmMode === 'disabled')
|
|
92
|
+
return 'js';
|
|
93
|
+
// preferred (default)
|
|
94
|
+
if (wasmBackendState === 'wasm-unavailable')
|
|
95
|
+
return 'js';
|
|
96
|
+
if (wasmBackendState === 'wasm-ready')
|
|
97
|
+
return 'wasm';
|
|
98
|
+
// uninitialized -> try to init
|
|
99
|
+
try {
|
|
100
|
+
const ok = await ensureWasm();
|
|
101
|
+
wasmBackendState = ok ? 'wasm-ready' : 'wasm-unavailable';
|
|
102
|
+
return ok ? 'wasm' : 'js';
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
wasmBackendState = 'wasm-unavailable';
|
|
106
|
+
return 'js';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
workerScope.addEventListener('message', (event) => {
|
|
110
|
+
const request = event.data;
|
|
111
|
+
if (!request)
|
|
112
|
+
return;
|
|
113
|
+
if (request.type === 'cancel') {
|
|
114
|
+
const controller = activeControllers.get(request.id);
|
|
115
|
+
if (controller) {
|
|
116
|
+
controller.abort();
|
|
117
|
+
activeControllers.delete(request.id);
|
|
118
|
+
workerScope.postMessage({ type: 'cancelled', id: request.id });
|
|
119
|
+
workerScope.close();
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (request.type !== 'solve')
|
|
124
|
+
return;
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
activeControllers.set(request.id, controller);
|
|
127
|
+
const wasmMode = request.wasmMode === 'disabled' ? 'disabled' : 'preferred';
|
|
128
|
+
(async () => {
|
|
129
|
+
const backend = await selectBackend(wasmMode);
|
|
130
|
+
// Telemetry: report backend selection once per request
|
|
131
|
+
if (!controller.signal.aborted) {
|
|
132
|
+
try {
|
|
133
|
+
workerScope.postMessage({ type: 'backend', id: request.id, backend });
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// telemetry must never break solving
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const solver = backend === 'wasm' ? solveChallengeWasm : solveChallenge;
|
|
140
|
+
// For wasm-unavailable fallback, re-select if wasm fails during solve? We already selected js
|
|
141
|
+
// But if backend is wasm and solve fails with internal error, we surface not fallback
|
|
142
|
+
return solver(request.tokens, (progress) => {
|
|
143
|
+
if (!controller.signal.aborted) {
|
|
144
|
+
workerScope.postMessage({
|
|
145
|
+
type: 'progress',
|
|
146
|
+
id: request.id,
|
|
147
|
+
progress,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}, controller.signal);
|
|
151
|
+
})().then((solutions) => {
|
|
152
|
+
activeControllers.delete(request.id);
|
|
153
|
+
if (controller.signal.aborted)
|
|
154
|
+
return;
|
|
155
|
+
workerScope.postMessage({
|
|
156
|
+
type: 'result',
|
|
157
|
+
id: request.id,
|
|
158
|
+
solutions,
|
|
159
|
+
});
|
|
160
|
+
}, (error) => {
|
|
161
|
+
activeControllers.delete(request.id);
|
|
162
|
+
if (controller.signal.aborted)
|
|
163
|
+
return;
|
|
164
|
+
// If WASM was selected and failed due to unexpected runtime error, surface as error
|
|
165
|
+
// For expected initialization failures we already fell back to JS, so this error is genuine
|
|
166
|
+
workerScope.postMessage({
|
|
167
|
+
type: 'error',
|
|
168
|
+
id: request.id,
|
|
169
|
+
error: error instanceof Error ? error.message : String(error),
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
export {};
|
|
174
|
+
//# sourceMappingURL=solver-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solver-worker.js","sourceRoot":"","sources":["../src/solver-worker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-compatible challenge solver using Web Crypto API
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Decode JWT token (browser-compatible, without verification)
|
|
6
|
+
*/
|
|
7
|
+
function decodeJWT(token) {
|
|
8
|
+
try {
|
|
9
|
+
const parts = token.split('.');
|
|
10
|
+
if (parts.length !== 3 || !parts[1])
|
|
11
|
+
return null;
|
|
12
|
+
const normalizedPayload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
13
|
+
const paddedPayload = normalizedPayload.padEnd(normalizedPayload.length + ((4 - (normalizedPayload.length % 4)) % 4), '=');
|
|
14
|
+
const payload = JSON.parse(atob(paddedPayload));
|
|
15
|
+
return payload;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* SHA-256 hash using Web Crypto API
|
|
23
|
+
*/
|
|
24
|
+
async function sha256(message) {
|
|
25
|
+
if (typeof TextEncoder === 'undefined') {
|
|
26
|
+
throw new Error('TextEncoder is unavailable in this browser environment');
|
|
27
|
+
}
|
|
28
|
+
if (!globalThis.crypto?.subtle) {
|
|
29
|
+
throw new Error('Web Crypto API is unavailable. Use HTTPS or localhost.');
|
|
30
|
+
}
|
|
31
|
+
const msgBuffer = new TextEncoder().encode(message);
|
|
32
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
|
33
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
34
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
35
|
+
return hashHex;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Yield to the event loop without the ~4ms clamping browsers apply to
|
|
39
|
+
* nested setTimeout calls. MessageChannel tasks run as macrotasks with
|
|
40
|
+
* microsecond-level latency, keeping the UI responsive at a fraction of
|
|
41
|
+
* the timer overhead.
|
|
42
|
+
*
|
|
43
|
+
* The channel is created lazily on first yield, and the receiving port is
|
|
44
|
+
* unref'd where supported (Node.js): a port with an active message listener
|
|
45
|
+
* otherwise keeps the host event loop alive and prevents clean exit.
|
|
46
|
+
*/
|
|
47
|
+
const yieldToEventLoop = (() => {
|
|
48
|
+
if (typeof MessageChannel !== 'function') {
|
|
49
|
+
return () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
50
|
+
}
|
|
51
|
+
let channel = null;
|
|
52
|
+
const waiting = new Set();
|
|
53
|
+
return () => {
|
|
54
|
+
if (!channel) {
|
|
55
|
+
channel = new MessageChannel();
|
|
56
|
+
channel.port1.onmessage = () => {
|
|
57
|
+
for (const resolve of waiting)
|
|
58
|
+
resolve();
|
|
59
|
+
waiting.clear();
|
|
60
|
+
};
|
|
61
|
+
channel.port1.unref?.();
|
|
62
|
+
}
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
waiting.add(resolve);
|
|
65
|
+
channel.port2.postMessage(null);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
})();
|
|
69
|
+
export async function calibrateBrowser(iterations = 128) {
|
|
70
|
+
if (!Number.isFinite(iterations) || iterations < 1) {
|
|
71
|
+
throw new Error('Calibration iterations must be at least 1');
|
|
72
|
+
}
|
|
73
|
+
const normalizedIterations = Math.floor(iterations);
|
|
74
|
+
const startedAt = performance.now();
|
|
75
|
+
for (let index = 0; index < normalizedIterations; index++) {
|
|
76
|
+
await sha256(`ribaunt-calibration:${index}`);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
iterations: normalizedIterations,
|
|
80
|
+
durationMs: Math.max(1, Math.round(performance.now() - startedAt)),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export const calibrateClient = calibrateBrowser;
|
|
84
|
+
export function decodeChallengeToken(token) {
|
|
85
|
+
return decodeJWT(token);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Solve a single challenge token (browser-compatible)
|
|
89
|
+
*/
|
|
90
|
+
export async function solveSingleChallenge(token, signal) {
|
|
91
|
+
const payload = decodeJWT(token);
|
|
92
|
+
if (!payload)
|
|
93
|
+
return undefined;
|
|
94
|
+
const { challenge, difficulty } = payload;
|
|
95
|
+
const prefix = '0'.repeat(difficulty);
|
|
96
|
+
let nonce = 0;
|
|
97
|
+
while (true) {
|
|
98
|
+
if (signal?.aborted) {
|
|
99
|
+
throw new DOMException('Challenge solving aborted', 'AbortError');
|
|
100
|
+
}
|
|
101
|
+
const hash = await sha256(`${challenge}${nonce}`);
|
|
102
|
+
if (hash.startsWith(prefix)) {
|
|
103
|
+
return { nonce: String(nonce), hash };
|
|
104
|
+
}
|
|
105
|
+
nonce++;
|
|
106
|
+
// Yield to keep the UI responsive; each batch amortizes the yield cost.
|
|
107
|
+
if (nonce % 2048 === 0) {
|
|
108
|
+
await yieldToEventLoop();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Solve multiple challenge tokens (browser-compatible)
|
|
114
|
+
*/
|
|
115
|
+
export async function solveChallenge(tokens, onProgress, signal) {
|
|
116
|
+
const solutions = [];
|
|
117
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
118
|
+
const token = tokens[i];
|
|
119
|
+
if (!token) {
|
|
120
|
+
throw new Error(`Invalid token at index ${i}`);
|
|
121
|
+
}
|
|
122
|
+
const solution = await solveSingleChallenge(token, signal);
|
|
123
|
+
if (!solution) {
|
|
124
|
+
throw new Error(`Failed to solve challenge ${i + 1}`);
|
|
125
|
+
}
|
|
126
|
+
solutions.push(solution);
|
|
127
|
+
// Report progress
|
|
128
|
+
if (onProgress) {
|
|
129
|
+
const progress = Math.round(((i + 1) / tokens.length) * 100);
|
|
130
|
+
onProgress(progress);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return solutions;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=solver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solver.js","sourceRoot":"","sources":["../src/solver.ts"],"names":[],"mappings":"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"}
|