@nawa-captcha/server 0.0.0-stage → 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/README.md +65 -2
- package/dist/binding.d.ts +22 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +46 -0
- package/dist/binding.js.map +1 -0
- package/dist/catalog.d.ts +12 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +42 -0
- package/dist/catalog.js.map +1 -0
- package/dist/crypto.d.ts +44 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +108 -0
- package/dist/crypto.js.map +1 -0
- package/dist/engine.d.ts +245 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +407 -0
- package/dist/engine.js.map +1 -0
- package/dist/express.d.ts +47 -0
- package/dist/express.d.ts.map +1 -0
- package/dist/express.js +107 -0
- package/dist/express.js.map +1 -0
- package/dist/heuristics.d.ts +33 -0
- package/dist/heuristics.d.ts.map +1 -0
- package/dist/heuristics.js +152 -0
- package/dist/heuristics.js.map +1 -0
- package/dist/http.d.ts +51 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +70 -0
- package/dist/http.js.map +1 -0
- package/dist/i18n.d.ts +237 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +92 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/primitives.browser.d.ts +15 -0
- package/dist/primitives.browser.d.ts.map +1 -0
- package/dist/primitives.browser.js +328 -0
- package/dist/primitives.browser.js.map +1 -0
- package/dist/primitives.d.ts +17 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +58 -0
- package/dist/primitives.js.map +1 -0
- package/dist/puzzles/accessible.d.ts +43 -0
- package/dist/puzzles/accessible.d.ts.map +1 -0
- package/dist/puzzles/accessible.js +317 -0
- package/dist/puzzles/accessible.js.map +1 -0
- package/dist/puzzles/audio.d.ts +42 -0
- package/dist/puzzles/audio.d.ts.map +1 -0
- package/dist/puzzles/audio.js +109 -0
- package/dist/puzzles/audio.js.map +1 -0
- package/dist/puzzles/biggest.d.ts +5 -0
- package/dist/puzzles/biggest.d.ts.map +1 -0
- package/dist/puzzles/biggest.js +40 -0
- package/dist/puzzles/biggest.js.map +1 -0
- package/dist/puzzles/clock.d.ts +8 -0
- package/dist/puzzles/clock.d.ts.map +1 -0
- package/dist/puzzles/clock.js +68 -0
- package/dist/puzzles/clock.js.map +1 -0
- package/dist/puzzles/color-name.d.ts +5 -0
- package/dist/puzzles/color-name.d.ts.map +1 -0
- package/dist/puzzles/color-name.js +73 -0
- package/dist/puzzles/color-name.js.map +1 -0
- package/dist/puzzles/common.d.ts +75 -0
- package/dist/puzzles/common.d.ts.map +1 -0
- package/dist/puzzles/common.js +268 -0
- package/dist/puzzles/common.js.map +1 -0
- package/dist/puzzles/count.d.ts +8 -0
- package/dist/puzzles/count.d.ts.map +1 -0
- package/dist/puzzles/count.js +38 -0
- package/dist/puzzles/count.js.map +1 -0
- package/dist/puzzles/dice.d.ts +8 -0
- package/dist/puzzles/dice.d.ts.map +1 -0
- package/dist/puzzles/dice.js +61 -0
- package/dist/puzzles/dice.js.map +1 -0
- package/dist/puzzles/find-shape.d.ts +8 -0
- package/dist/puzzles/find-shape.d.ts.map +1 -0
- package/dist/puzzles/find-shape.js +45 -0
- package/dist/puzzles/find-shape.js.map +1 -0
- package/dist/puzzles/grid.d.ts +44 -0
- package/dist/puzzles/grid.d.ts.map +1 -0
- package/dist/puzzles/grid.js +33 -0
- package/dist/puzzles/grid.js.map +1 -0
- package/dist/puzzles/hold.d.ts +24 -0
- package/dist/puzzles/hold.d.ts.map +1 -0
- package/dist/puzzles/hold.js +68 -0
- package/dist/puzzles/hold.js.map +1 -0
- package/dist/puzzles/icons-objects.d.ts +3 -0
- package/dist/puzzles/icons-objects.d.ts.map +1 -0
- package/dist/puzzles/icons-objects.js +5 -0
- package/dist/puzzles/icons-objects.js.map +1 -0
- package/dist/puzzles/icons-weather.d.ts +3 -0
- package/dist/puzzles/icons-weather.d.ts.map +1 -0
- package/dist/puzzles/icons-weather.js +5 -0
- package/dist/puzzles/icons-weather.js.map +1 -0
- package/dist/puzzles/icons.d.ts +14 -0
- package/dist/puzzles/icons.d.ts.map +1 -0
- package/dist/puzzles/icons.js +49 -0
- package/dist/puzzles/icons.js.map +1 -0
- package/dist/puzzles/index.d.ts +63 -0
- package/dist/puzzles/index.d.ts.map +1 -0
- package/dist/puzzles/index.js +93 -0
- package/dist/puzzles/index.js.map +1 -0
- package/dist/puzzles/jigsaw.d.ts +22 -0
- package/dist/puzzles/jigsaw.d.ts.map +1 -0
- package/dist/puzzles/jigsaw.js +38 -0
- package/dist/puzzles/jigsaw.js.map +1 -0
- package/dist/puzzles/math-compare.d.ts +8 -0
- package/dist/puzzles/math-compare.d.ts.map +1 -0
- package/dist/puzzles/math-compare.js +47 -0
- package/dist/puzzles/math-compare.js.map +1 -0
- package/dist/puzzles/math.d.ts +16 -0
- package/dist/puzzles/math.d.ts.map +1 -0
- package/dist/puzzles/math.js +106 -0
- package/dist/puzzles/math.js.map +1 -0
- package/dist/puzzles/maze.d.ts +18 -0
- package/dist/puzzles/maze.d.ts.map +1 -0
- package/dist/puzzles/maze.js +138 -0
- package/dist/puzzles/maze.js.map +1 -0
- package/dist/puzzles/odd-one-out.d.ts +5 -0
- package/dist/puzzles/odd-one-out.d.ts.map +1 -0
- package/dist/puzzles/odd-one-out.js +36 -0
- package/dist/puzzles/odd-one-out.js.map +1 -0
- package/dist/puzzles/odd-rotation.d.ts +8 -0
- package/dist/puzzles/odd-rotation.d.ts.map +1 -0
- package/dist/puzzles/odd-rotation.js +48 -0
- package/dist/puzzles/odd-rotation.js.map +1 -0
- package/dist/puzzles/order-numbers.d.ts +5 -0
- package/dist/puzzles/order-numbers.d.ts.map +1 -0
- package/dist/puzzles/order-numbers.js +32 -0
- package/dist/puzzles/order-numbers.js.map +1 -0
- package/dist/puzzles/order-size.d.ts +5 -0
- package/dist/puzzles/order-size.d.ts.map +1 -0
- package/dist/puzzles/order-size.js +30 -0
- package/dist/puzzles/order-size.js.map +1 -0
- package/dist/puzzles/order-time.d.ts +5 -0
- package/dist/puzzles/order-time.d.ts.map +1 -0
- package/dist/puzzles/order-time.js +27 -0
- package/dist/puzzles/order-time.js.map +1 -0
- package/dist/puzzles/order.d.ts +30 -0
- package/dist/puzzles/order.d.ts.map +1 -0
- package/dist/puzzles/order.js +61 -0
- package/dist/puzzles/order.js.map +1 -0
- package/dist/puzzles/pairs.d.ts +15 -0
- package/dist/puzzles/pairs.d.ts.map +1 -0
- package/dist/puzzles/pairs.js +63 -0
- package/dist/puzzles/pairs.js.map +1 -0
- package/dist/puzzles/pattern.d.ts +8 -0
- package/dist/puzzles/pattern.d.ts.map +1 -0
- package/dist/puzzles/pattern.js +68 -0
- package/dist/puzzles/pattern.js.map +1 -0
- package/dist/puzzles/pow.d.ts +23 -0
- package/dist/puzzles/pow.d.ts.map +1 -0
- package/dist/puzzles/pow.js +52 -0
- package/dist/puzzles/pow.js.map +1 -0
- package/dist/puzzles/rotate-match.d.ts +8 -0
- package/dist/puzzles/rotate-match.d.ts.map +1 -0
- package/dist/puzzles/rotate-match.js +34 -0
- package/dist/puzzles/rotate-match.js.map +1 -0
- package/dist/puzzles/rotate-object.d.ts +5 -0
- package/dist/puzzles/rotate-object.d.ts.map +1 -0
- package/dist/puzzles/rotate-object.js +33 -0
- package/dist/puzzles/rotate-object.js.map +1 -0
- package/dist/puzzles/rotate.d.ts +24 -0
- package/dist/puzzles/rotate.d.ts.map +1 -0
- package/dist/puzzles/rotate.js +40 -0
- package/dist/puzzles/rotate.js.map +1 -0
- package/dist/puzzles/shadow-drag.d.ts +20 -0
- package/dist/puzzles/shadow-drag.d.ts.map +1 -0
- package/dist/puzzles/shadow-drag.js +64 -0
- package/dist/puzzles/shadow-drag.js.map +1 -0
- package/dist/puzzles/shadow-pick.d.ts +5 -0
- package/dist/puzzles/shadow-pick.d.ts.map +1 -0
- package/dist/puzzles/shadow-pick.js +38 -0
- package/dist/puzzles/shadow-pick.js.map +1 -0
- package/dist/puzzles/slide-unlock.d.ts +11 -0
- package/dist/puzzles/slide-unlock.d.ts.map +1 -0
- package/dist/puzzles/slide-unlock.js +79 -0
- package/dist/puzzles/slide-unlock.js.map +1 -0
- package/dist/puzzles/slider.d.ts +23 -0
- package/dist/puzzles/slider.d.ts.map +1 -0
- package/dist/puzzles/slider.js +74 -0
- package/dist/puzzles/slider.js.map +1 -0
- package/dist/puzzles/spell.d.ts +8 -0
- package/dist/puzzles/spell.d.ts.map +1 -0
- package/dist/puzzles/spell.js +41 -0
- package/dist/puzzles/spell.js.map +1 -0
- package/dist/puzzles/story.d.ts +5 -0
- package/dist/puzzles/story.d.ts.map +1 -0
- package/dist/puzzles/story.js +101 -0
- package/dist/puzzles/story.js.map +1 -0
- package/dist/puzzles/symmetry.d.ts +8 -0
- package/dist/puzzles/symmetry.d.ts.map +1 -0
- package/dist/puzzles/symmetry.js +49 -0
- package/dist/puzzles/symmetry.js.map +1 -0
- package/dist/puzzles/tap-order.d.ts +20 -0
- package/dist/puzzles/tap-order.d.ts.map +1 -0
- package/dist/puzzles/tap-order.js +37 -0
- package/dist/puzzles/tap-order.js.map +1 -0
- package/dist/puzzles/trace-letters.d.ts +5 -0
- package/dist/puzzles/trace-letters.d.ts.map +1 -0
- package/dist/puzzles/trace-letters.js +19 -0
- package/dist/puzzles/trace-letters.js.map +1 -0
- package/dist/puzzles/trace.d.ts +32 -0
- package/dist/puzzles/trace.d.ts.map +1 -0
- package/dist/puzzles/trace.js +81 -0
- package/dist/puzzles/trace.js.map +1 -0
- package/dist/render/audio.d.ts +53 -0
- package/dist/render/audio.d.ts.map +1 -0
- package/dist/render/audio.js +236 -0
- package/dist/render/audio.js.map +1 -0
- package/dist/render/clock.d.ts +6 -0
- package/dist/render/clock.d.ts.map +1 -0
- package/dist/render/clock.js +23 -0
- package/dist/render/clock.js.map +1 -0
- package/dist/render/glyphs.d.ts +18 -0
- package/dist/render/glyphs.d.ts.map +1 -0
- package/dist/render/glyphs.js +70 -0
- package/dist/render/glyphs.js.map +1 -0
- package/dist/render/pictograms.d.ts +60 -0
- package/dist/render/pictograms.d.ts.map +1 -0
- package/dist/render/pictograms.js +167 -0
- package/dist/render/pictograms.js.map +1 -0
- package/dist/render/png.d.ts +11 -0
- package/dist/render/png.d.ts.map +1 -0
- package/dist/render/png.js +107 -0
- package/dist/render/png.js.map +1 -0
- package/dist/render/raster.d.ts +61 -0
- package/dist/render/raster.d.ts.map +1 -0
- package/dist/render/raster.js +389 -0
- package/dist/render/raster.js.map +1 -0
- package/dist/render/shapes.d.ts +25 -0
- package/dist/render/shapes.d.ts.map +1 -0
- package/dist/render/shapes.js +157 -0
- package/dist/render/shapes.js.map +1 -0
- package/dist/rng.d.ts +25 -0
- package/dist/rng.d.ts.map +1 -0
- package/dist/rng.js +93 -0
- package/dist/rng.js.map +1 -0
- package/dist/selection.d.ts +54 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/selection.js +85 -0
- package/dist/selection.js.map +1 -0
- package/dist/store.d.ts +66 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +100 -0
- package/dist/store.js.map +1 -0
- package/dist/tokens.d.ts +70 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +103 -0
- package/dist/tokens.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +61 -0
- package/dist/types.js.map +1 -0
- package/package.json +68 -4
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Interaction, TracePoint } from './types.js';
|
|
2
|
+
export declare const MAX_TRAIL_POINTS = 800;
|
|
3
|
+
/** Validates an untrusted trail: bounded length, finite numbers, non-decreasing time. */
|
|
4
|
+
export declare function parseTrail(value: unknown, bounds: {
|
|
5
|
+
w: number;
|
|
6
|
+
h: number;
|
|
7
|
+
}): TracePoint[] | undefined;
|
|
8
|
+
export declare function parseInteraction(value: unknown, bounds: {
|
|
9
|
+
w: number;
|
|
10
|
+
h: number;
|
|
11
|
+
}): Interaction | undefined;
|
|
12
|
+
export interface TrajectoryOptions {
|
|
13
|
+
/** Minimum duration of the gesture itself, in ms. */
|
|
14
|
+
readonly minDurationMs?: number;
|
|
15
|
+
/** 'x' for one-axis drags (slider) where off-axis wobble is a human signal. */
|
|
16
|
+
readonly axis?: 'x' | 'free';
|
|
17
|
+
/** Score below which the gesture is rejected (0..1). */
|
|
18
|
+
readonly threshold?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface TrajectoryVerdict {
|
|
21
|
+
readonly human: boolean;
|
|
22
|
+
readonly score: number;
|
|
23
|
+
readonly reasons: readonly string[];
|
|
24
|
+
}
|
|
25
|
+
/** Shannon entropy (bits) of values quantized into `bins` equal-width buckets. */
|
|
26
|
+
export declare function entropy(xs: readonly number[], bins?: number): number;
|
|
27
|
+
/**
|
|
28
|
+
* Scores how human a drag looks. These are cost-raising signals, not proof: a scripted mouse
|
|
29
|
+
* with recorded human jitter will pass. What it reliably rejects is the cheap attack —
|
|
30
|
+
* synthetic events in a perfectly straight line at constant speed, or an instant "teleport".
|
|
31
|
+
*/
|
|
32
|
+
export declare function analyzeTrajectory(interaction: Interaction, opts?: TrajectoryOptions): TrajectoryVerdict;
|
|
33
|
+
//# sourceMappingURL=heuristics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heuristics.d.ts","sourceRoot":"","sources":["../src/heuristics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAErE,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAGpC,yFAAyF;AACzF,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,EAAE,GAAG,SAAS,CAgBrG;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,GAAG,SAAS,CAM1G;AAED,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAWD,kFAAkF;AAClF,wBAAgB,OAAO,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,SAAI,GAAG,MAAM,CAQ/D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,GAAE,iBAAsB,GAAG,iBAAiB,CAqE3G"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
export const MAX_TRAIL_POINTS = 800;
|
|
2
|
+
const INPUT_MODES = ['mouse', 'touch', 'pen', 'keyboard'];
|
|
3
|
+
/** Validates an untrusted trail: bounded length, finite numbers, non-decreasing time. */
|
|
4
|
+
export function parseTrail(value, bounds) {
|
|
5
|
+
if (!Array.isArray(value) || value.length > MAX_TRAIL_POINTS)
|
|
6
|
+
return undefined;
|
|
7
|
+
const out = [];
|
|
8
|
+
let lastT = -Infinity;
|
|
9
|
+
for (const p of value) {
|
|
10
|
+
if (!Array.isArray(p) || p.length !== 3)
|
|
11
|
+
return undefined;
|
|
12
|
+
const [x, y, t] = p;
|
|
13
|
+
if (typeof x !== 'number' || typeof y !== 'number' || typeof t !== 'number')
|
|
14
|
+
return undefined;
|
|
15
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(t))
|
|
16
|
+
return undefined;
|
|
17
|
+
// Generous margin: a finger may legitimately overshoot the image while dragging.
|
|
18
|
+
if (x < -bounds.w || x > bounds.w * 2 || y < -bounds.h || y > bounds.h * 2 || t < 0 || t > 600_000)
|
|
19
|
+
return undefined;
|
|
20
|
+
if (t < lastT)
|
|
21
|
+
return undefined;
|
|
22
|
+
lastT = t;
|
|
23
|
+
out.push([x, y, t]);
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
export function parseInteraction(value, bounds) {
|
|
28
|
+
if (!value || typeof value !== 'object')
|
|
29
|
+
return undefined;
|
|
30
|
+
const v = value;
|
|
31
|
+
if (!INPUT_MODES.includes(v['mode']))
|
|
32
|
+
return undefined;
|
|
33
|
+
const trail = parseTrail(v['trail'], bounds);
|
|
34
|
+
return trail ? { mode: v['mode'], trail } : undefined;
|
|
35
|
+
}
|
|
36
|
+
function mean(xs) {
|
|
37
|
+
return xs.reduce((a, b) => a + b, 0) / (xs.length || 1);
|
|
38
|
+
}
|
|
39
|
+
function std(xs) {
|
|
40
|
+
const m = mean(xs);
|
|
41
|
+
return Math.sqrt(mean(xs.map((x) => (x - m) ** 2)));
|
|
42
|
+
}
|
|
43
|
+
/** Shannon entropy (bits) of values quantized into `bins` equal-width buckets. */
|
|
44
|
+
export function entropy(xs, bins = 8) {
|
|
45
|
+
if (xs.length === 0)
|
|
46
|
+
return 0;
|
|
47
|
+
const min = Math.min(...xs);
|
|
48
|
+
const max = Math.max(...xs);
|
|
49
|
+
if (max - min < 1e-9)
|
|
50
|
+
return 0;
|
|
51
|
+
const counts = new Array(bins).fill(0);
|
|
52
|
+
for (const x of xs)
|
|
53
|
+
counts[Math.min(bins - 1, Math.floor(((x - min) / (max - min)) * bins))] += 1;
|
|
54
|
+
return counts.reduce((h, c) => (c === 0 ? h : h - (c / xs.length) * Math.log2(c / xs.length)), 0);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Scores how human a drag looks. These are cost-raising signals, not proof: a scripted mouse
|
|
58
|
+
* with recorded human jitter will pass. What it reliably rejects is the cheap attack —
|
|
59
|
+
* synthetic events in a perfectly straight line at constant speed, or an instant "teleport".
|
|
60
|
+
*/
|
|
61
|
+
export function analyzeTrajectory(interaction, opts = {}) {
|
|
62
|
+
const { trail, mode } = interaction;
|
|
63
|
+
const minDuration = opts.minDurationMs ?? 180;
|
|
64
|
+
const threshold = opts.threshold ?? 0.5;
|
|
65
|
+
const reasons = [];
|
|
66
|
+
const first = trail[0];
|
|
67
|
+
const last = trail[trail.length - 1];
|
|
68
|
+
if (!first || !last)
|
|
69
|
+
return { human: false, score: 0, reasons: ['no-samples'] };
|
|
70
|
+
const duration = last[2] - first[2];
|
|
71
|
+
if (mode === 'keyboard') {
|
|
72
|
+
// Keyboard users press keys; we only refuse the impossible (zero time, machine-perfect cadence).
|
|
73
|
+
if (duration < Math.min(minDuration, 120) && trail.length > 1)
|
|
74
|
+
reasons.push('too-fast');
|
|
75
|
+
const dts = trail.slice(1).map((p, i) => p[2] - trail[i][2]);
|
|
76
|
+
if (dts.length >= 6 && std(dts) < 0.5)
|
|
77
|
+
reasons.push('uniform-key-timing');
|
|
78
|
+
const score = reasons.length === 0 ? 1 : 0;
|
|
79
|
+
return { human: score >= threshold, score, reasons };
|
|
80
|
+
}
|
|
81
|
+
if (trail.length < 5)
|
|
82
|
+
return { human: false, score: 0, reasons: ['too-few-samples'] };
|
|
83
|
+
if (duration < minDuration)
|
|
84
|
+
return { human: false, score: 0, reasons: ['too-fast'] };
|
|
85
|
+
let score = 1;
|
|
86
|
+
const dts = [];
|
|
87
|
+
const speeds = [];
|
|
88
|
+
for (let i = 1; i < trail.length; i += 1) {
|
|
89
|
+
const a = trail[i - 1];
|
|
90
|
+
const b = trail[i];
|
|
91
|
+
const dt = b[2] - a[2];
|
|
92
|
+
dts.push(dt);
|
|
93
|
+
if (dt > 0)
|
|
94
|
+
speeds.push(Math.hypot(b[0] - a[0], b[1] - a[1]) / dt);
|
|
95
|
+
}
|
|
96
|
+
// Synthetic event loops fire at a perfectly regular cadence.
|
|
97
|
+
if (dts.length >= 8 && std(dts) < 0.5) {
|
|
98
|
+
score -= 0.2;
|
|
99
|
+
reasons.push('uniform-timing');
|
|
100
|
+
}
|
|
101
|
+
// Humans accelerate and decelerate; scripts tend to move at a constant velocity.
|
|
102
|
+
const moving = speeds.filter((s) => s > 0.001);
|
|
103
|
+
const cv = moving.length > 2 ? std(moving) / (mean(moving) || 1) : 0;
|
|
104
|
+
if (cv < 0.12) {
|
|
105
|
+
score -= 0.4;
|
|
106
|
+
reasons.push('constant-velocity');
|
|
107
|
+
}
|
|
108
|
+
if (moving.length > 6 && entropy(moving) < 1) {
|
|
109
|
+
score -= 0.15;
|
|
110
|
+
reasons.push('low-speed-entropy');
|
|
111
|
+
}
|
|
112
|
+
// A one-axis drag by a hand always wanders a little off-axis.
|
|
113
|
+
if ((opts.axis ?? 'free') === 'x') {
|
|
114
|
+
const ys = trail.map((p) => p[1]);
|
|
115
|
+
if (Math.max(...ys) - Math.min(...ys) < 0.5) {
|
|
116
|
+
score -= 0.25;
|
|
117
|
+
reasons.push('perfectly-straight');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// Straight machine segments only change heading at the corners.
|
|
122
|
+
const turns = directionChanges(trail);
|
|
123
|
+
if (trail.length > 10 && turns / (trail.length - 1) < 0.1) {
|
|
124
|
+
score -= 0.25;
|
|
125
|
+
reasons.push('no-direction-noise');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
score = Math.max(0, Math.round(score * 100) / 100);
|
|
129
|
+
return { human: score >= threshold, score, reasons };
|
|
130
|
+
}
|
|
131
|
+
/** Counts small heading changes (> ~3°) between consecutive segments. */
|
|
132
|
+
function directionChanges(trail) {
|
|
133
|
+
let changes = 0;
|
|
134
|
+
let prev;
|
|
135
|
+
for (let i = 1; i < trail.length; i += 1) {
|
|
136
|
+
const a = trail[i - 1];
|
|
137
|
+
const b = trail[i];
|
|
138
|
+
if (Math.hypot(b[0] - a[0], b[1] - a[1]) < 0.5)
|
|
139
|
+
continue;
|
|
140
|
+
const heading = Math.atan2(b[1] - a[1], b[0] - a[0]);
|
|
141
|
+
if (prev !== undefined) {
|
|
142
|
+
let d = Math.abs(heading - prev);
|
|
143
|
+
if (d > Math.PI)
|
|
144
|
+
d = 2 * Math.PI - d;
|
|
145
|
+
if (d > 0.05 && d < 1.2)
|
|
146
|
+
changes += 1;
|
|
147
|
+
}
|
|
148
|
+
prev = heading;
|
|
149
|
+
}
|
|
150
|
+
return changes;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=heuristics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heuristics.js","sourceRoot":"","sources":["../src/heuristics.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ClientInfo } from './binding.js';
|
|
2
|
+
import type { NawaCaptcha, PassTokenResult, VerifyPassTokenOptions } from './engine.js';
|
|
3
|
+
import { verifyPassToken } from './engine.js';
|
|
4
|
+
import type { NawaCaptchaStore } from './store.js';
|
|
5
|
+
/** Framework-agnostic request: what the handlers need, nothing else. */
|
|
6
|
+
export interface GuardHttpRequest {
|
|
7
|
+
/** Parsed JSON body (or undefined). */
|
|
8
|
+
readonly body: unknown;
|
|
9
|
+
readonly client: ClientInfo;
|
|
10
|
+
}
|
|
11
|
+
export interface GuardHttpResponse {
|
|
12
|
+
readonly status: number;
|
|
13
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
14
|
+
readonly body: unknown;
|
|
15
|
+
}
|
|
16
|
+
export declare const SECURITY_HEADERS: Readonly<Record<string, string>>;
|
|
17
|
+
/** Maximum accepted JSON body for the captcha endpoints (trails are the largest part). */
|
|
18
|
+
export declare const MAX_BODY_BYTES: number;
|
|
19
|
+
/** POST /challenge body: { siteKey?, action?, lang?, type?, types? } */
|
|
20
|
+
export declare function handleChallenge(guard: NawaCaptcha, req: GuardHttpRequest): Promise<GuardHttpResponse>;
|
|
21
|
+
/**
|
|
22
|
+
* POST /verify body: { token, answer } → { success, token (pass token), expiresAt }, or for a
|
|
23
|
+
* multi-step policy { success, next: <the next challenge> } until the last step is solved.
|
|
24
|
+
*/
|
|
25
|
+
export declare function handleVerify(guard: NawaCaptcha, req: GuardHttpRequest): Promise<GuardHttpResponse>;
|
|
26
|
+
export interface PassTokenSource {
|
|
27
|
+
readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>;
|
|
28
|
+
readonly body?: unknown;
|
|
29
|
+
}
|
|
30
|
+
export declare const DEFAULT_TOKEN_FIELD = "nawa-captcha-token";
|
|
31
|
+
export declare const DEFAULT_TOKEN_HEADER = "x-nawa-captcha-token";
|
|
32
|
+
/** @deprecated Pre-rename (Nawa Guard) names, still accepted by default for this release. */
|
|
33
|
+
export declare const LEGACY_TOKEN_FIELD = "nawa-guard-token";
|
|
34
|
+
/** @deprecated Pre-rename (Nawa Guard) names, still accepted by default for this release. */
|
|
35
|
+
export declare const LEGACY_TOKEN_HEADER = "x-nawa-guard-token";
|
|
36
|
+
/**
|
|
37
|
+
* Reads a pass token from a header (preferred for JSON APIs) or a form/body field. With the
|
|
38
|
+
* defaults, the deprecated `X-Nawa-Guard-Token` header and `nawa-guard-token` field also work.
|
|
39
|
+
*/
|
|
40
|
+
export declare function readPassToken(src: PassTokenSource, field?: string, header?: string): string | undefined;
|
|
41
|
+
export interface ProtectOptions extends Omit<VerifyPassTokenOptions, 'secret' | 'client' | 'store'> {
|
|
42
|
+
readonly field?: string;
|
|
43
|
+
readonly header?: string;
|
|
44
|
+
readonly store?: NawaCaptchaStore;
|
|
45
|
+
}
|
|
46
|
+
/** One call for protected routes in any framework: read + verify the pass token. */
|
|
47
|
+
export declare function checkPassToken(guard: NawaCaptcha, src: PassTokenSource & {
|
|
48
|
+
client: ClientInfo;
|
|
49
|
+
}, opts: ProtectOptions): Promise<PassTokenResult>;
|
|
50
|
+
export { verifyPassToken };
|
|
51
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAK7D,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,cAAc,QAAY,CAAC;AAQxC,yEAAyE;AACzE,wBAAsB,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAM3G;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAWxG;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,mBAAmB,uBAAuB,CAAC;AACxD,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAC3D,6FAA6F;AAC7F,eAAO,MAAM,kBAAkB,qBAAqB,CAAC;AACrD,6FAA6F;AAC7F,eAAO,MAAM,mBAAmB,uBAAuB,CAAC;AAOxD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,SAAsB,EAAE,MAAM,SAAuB,GAAG,MAAM,GAAG,SAAS,CASlI;AAED,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,sBAAsB,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IACjG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACnC;AAED,oFAAoF;AACpF,wBAAsB,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,eAAe,GAAG;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAGtJ;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { verifyPassToken } from './engine.js';
|
|
2
|
+
export const SECURITY_HEADERS = {
|
|
3
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
4
|
+
// Challenges and pass tokens must never be cached by a browser or proxy.
|
|
5
|
+
'Cache-Control': 'no-store',
|
|
6
|
+
'X-Content-Type-Options': 'nosniff',
|
|
7
|
+
};
|
|
8
|
+
/** Maximum accepted JSON body for the captcha endpoints (trails are the largest part). */
|
|
9
|
+
export const MAX_BODY_BYTES = 32 * 1024;
|
|
10
|
+
const json = (status, body) => ({ status, headers: SECURITY_HEADERS, body });
|
|
11
|
+
function record(body) {
|
|
12
|
+
return body && typeof body === 'object' && !Array.isArray(body) ? body : undefined;
|
|
13
|
+
}
|
|
14
|
+
/** POST /challenge body: { siteKey?, action?, lang?, type?, types? } */
|
|
15
|
+
export async function handleChallenge(guard, req) {
|
|
16
|
+
const body = record(req.body ?? {});
|
|
17
|
+
if (!body)
|
|
18
|
+
return json(400, { success: false, error: 'invalid-input' });
|
|
19
|
+
const result = await guard.createChallenge({ siteKey: body['siteKey'], action: body['action'], lang: body['lang'], type: body['type'], types: body['types'], client: req.client });
|
|
20
|
+
if (!result.ok)
|
|
21
|
+
return json(result.error === 'rate-limited' ? 429 : 400, { success: false, error: result.error });
|
|
22
|
+
return json(200, { success: true, ...result.challenge });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* POST /verify body: { token, answer } → { success, token (pass token), expiresAt }, or for a
|
|
26
|
+
* multi-step policy { success, next: <the next challenge> } until the last step is solved.
|
|
27
|
+
*/
|
|
28
|
+
export async function handleVerify(guard, req) {
|
|
29
|
+
const body = record(req.body);
|
|
30
|
+
if (!body)
|
|
31
|
+
return json(400, { success: false, error: 'invalid-input', retry: false });
|
|
32
|
+
const result = await guard.verify({ token: body['token'], answer: body['answer'], client: req.client });
|
|
33
|
+
if (result.ok && result.next)
|
|
34
|
+
return json(200, { success: true, next: result.next });
|
|
35
|
+
if (result.ok) {
|
|
36
|
+
return json(200, { success: true, token: result.passToken, expiresAt: result.expiresAt, expiresInMs: result.expiresInMs, type: result.type, solvedTypes: result.solvedTypes });
|
|
37
|
+
}
|
|
38
|
+
// `reason`/`detail` stay server-side (onEvent); the browser only learns the coarse outcome.
|
|
39
|
+
const status = result.error === 'rate-limited' ? 429 : result.error === 'invalid-input' ? 400 : 403;
|
|
40
|
+
return json(status, { success: false, error: result.error, retry: result.retry });
|
|
41
|
+
}
|
|
42
|
+
export const DEFAULT_TOKEN_FIELD = 'nawa-captcha-token';
|
|
43
|
+
export const DEFAULT_TOKEN_HEADER = 'x-nawa-captcha-token';
|
|
44
|
+
/** @deprecated Pre-rename (Nawa Guard) names, still accepted by default for this release. */
|
|
45
|
+
export const LEGACY_TOKEN_FIELD = 'nawa-guard-token';
|
|
46
|
+
/** @deprecated Pre-rename (Nawa Guard) names, still accepted by default for this release. */
|
|
47
|
+
export const LEGACY_TOKEN_HEADER = 'x-nawa-guard-token';
|
|
48
|
+
function pick(value) {
|
|
49
|
+
const v = Array.isArray(value) ? value[0] : value;
|
|
50
|
+
return typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Reads a pass token from a header (preferred for JSON APIs) or a form/body field. With the
|
|
54
|
+
* defaults, the deprecated `X-Nawa-Guard-Token` header and `nawa-guard-token` field also work.
|
|
55
|
+
*/
|
|
56
|
+
export function readPassToken(src, field = DEFAULT_TOKEN_FIELD, header = DEFAULT_TOKEN_HEADER) {
|
|
57
|
+
const legacy = field === DEFAULT_TOKEN_FIELD && header === DEFAULT_TOKEN_HEADER;
|
|
58
|
+
const body = record(src.body);
|
|
59
|
+
return (pick(src.headers[header.toLowerCase()]) ??
|
|
60
|
+
(legacy ? pick(src.headers[LEGACY_TOKEN_HEADER]) : undefined) ??
|
|
61
|
+
pick(body?.[field]) ??
|
|
62
|
+
(legacy ? pick(body?.[LEGACY_TOKEN_FIELD]) : undefined));
|
|
63
|
+
}
|
|
64
|
+
/** One call for protected routes in any framework: read + verify the pass token. */
|
|
65
|
+
export async function checkPassToken(guard, src, opts) {
|
|
66
|
+
const token = readPassToken(src, opts.field, opts.header);
|
|
67
|
+
return guard.verifyPassToken(token, { ...opts, client: src.client });
|
|
68
|
+
}
|
|
69
|
+
export { verifyPassToken };
|
|
70
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import type { Lang } from './types.js';
|
|
2
|
+
import type { PictoKind } from './render/pictograms.js';
|
|
3
|
+
declare const PROMPTS: {
|
|
4
|
+
readonly slider: {
|
|
5
|
+
readonly en: "Drag the piece into the matching gap.";
|
|
6
|
+
readonly ar: "اسحب القطعة إلى الفراغ المطابق لها.";
|
|
7
|
+
};
|
|
8
|
+
readonly slideUnlock: {
|
|
9
|
+
readonly en: "Slide the handle until its arrow points at the ring.";
|
|
10
|
+
readonly ar: "اسحب المقبض حتى يشير سهمه إلى الحلقة.";
|
|
11
|
+
};
|
|
12
|
+
readonly shadowDrag: {
|
|
13
|
+
readonly en: "Drag the picture onto its matching shadow.";
|
|
14
|
+
readonly ar: "اسحب الصورة إلى الظل المطابق لها.";
|
|
15
|
+
};
|
|
16
|
+
readonly jigsaw: {
|
|
17
|
+
readonly en: "Swap the pieces until the picture is complete.";
|
|
18
|
+
readonly ar: "بدّل القطع حتى تكتمل الصورة.";
|
|
19
|
+
};
|
|
20
|
+
readonly rotate: {
|
|
21
|
+
readonly en: "Rotate the picture until it is upright.";
|
|
22
|
+
readonly ar: "دوّر الصورة حتى تصبح معتدلة.";
|
|
23
|
+
};
|
|
24
|
+
readonly rotateObject: {
|
|
25
|
+
readonly en: "Rotate the {thing} until it stands upright.";
|
|
26
|
+
readonly ar: "دوّر صورة {thing} حتى تصبح معتدلة.";
|
|
27
|
+
};
|
|
28
|
+
readonly rotateMatch: {
|
|
29
|
+
readonly en: "Turn the inner circle until the picture lines up.";
|
|
30
|
+
readonly ar: "دوّر الدائرة الداخلية حتى تتطابق الصورة.";
|
|
31
|
+
};
|
|
32
|
+
readonly hold: {
|
|
33
|
+
readonly en: "Press and hold, and let go while the ring is in the green zone.";
|
|
34
|
+
readonly ar: "اضغط مطولًا ثم أفلت عندما تكون الحلقة داخل المنطقة الخضراء.";
|
|
35
|
+
};
|
|
36
|
+
readonly icons: {
|
|
37
|
+
readonly en: "Select every {icon}.";
|
|
38
|
+
readonly ar: "اختر كل {icon}.";
|
|
39
|
+
};
|
|
40
|
+
readonly oddOneOut: {
|
|
41
|
+
readonly en: "One picture is different from the others. Select it.";
|
|
42
|
+
readonly ar: "إحدى الصور مختلفة عن البقية. اخترها.";
|
|
43
|
+
};
|
|
44
|
+
readonly oddRotation: {
|
|
45
|
+
readonly en: "One letter is mirrored (backwards). Select it.";
|
|
46
|
+
readonly ar: "أحد الحروف معكوس كالمرآة. اختره.";
|
|
47
|
+
};
|
|
48
|
+
readonly colorName: {
|
|
49
|
+
readonly en: "Select every {color} shape (they have {pattern}).";
|
|
50
|
+
readonly ar: "اختر كل شكل {color} (عليه {pattern}).";
|
|
51
|
+
};
|
|
52
|
+
readonly biggest: {
|
|
53
|
+
readonly en: "Select the largest {icon}.";
|
|
54
|
+
readonly ar: "اختر أكبر {icon}.";
|
|
55
|
+
};
|
|
56
|
+
readonly smallest: {
|
|
57
|
+
readonly en: "Select the smallest {icon}.";
|
|
58
|
+
readonly ar: "اختر أصغر {icon}.";
|
|
59
|
+
};
|
|
60
|
+
readonly findShape: {
|
|
61
|
+
readonly en: "Select every cell that shows the shape in the box.";
|
|
62
|
+
readonly ar: "اختر كل خانة فيها الشكل الموجود في المربع.";
|
|
63
|
+
};
|
|
64
|
+
readonly shadowPick: {
|
|
65
|
+
readonly en: "Select the shadow that matches the picture in the box.";
|
|
66
|
+
readonly ar: "اختر الظل الذي يطابق الصورة في المربع.";
|
|
67
|
+
};
|
|
68
|
+
readonly pattern: {
|
|
69
|
+
readonly en: "Which picture comes next in the row? Select it.";
|
|
70
|
+
readonly ar: "ما الصورة التالية في الصف؟ اخترها.";
|
|
71
|
+
};
|
|
72
|
+
readonly clock: {
|
|
73
|
+
readonly en: "Select the clock that shows {time}.";
|
|
74
|
+
readonly ar: "اختر الساعة التي تشير إلى {time}.";
|
|
75
|
+
};
|
|
76
|
+
readonly mathCompare: {
|
|
77
|
+
readonly en: "Select every card whose answer is more than {n}.";
|
|
78
|
+
readonly ar: "اختر كل بطاقة ناتجها أكبر من {n}.";
|
|
79
|
+
};
|
|
80
|
+
readonly symmetry: {
|
|
81
|
+
readonly en: "Fill the right half so it mirrors the left half.";
|
|
82
|
+
readonly ar: "لوّن النصف الأيمن ليصبح مرآة للنصف الأيسر.";
|
|
83
|
+
};
|
|
84
|
+
readonly pairs: {
|
|
85
|
+
readonly en: "Match the pictures in pairs: select two that are the same.";
|
|
86
|
+
readonly ar: "طابق الصور أزواجًا: اختر صورتين متماثلتين.";
|
|
87
|
+
};
|
|
88
|
+
readonly tapOrder: {
|
|
89
|
+
readonly en: "Tap the numbers from smallest to largest.";
|
|
90
|
+
readonly ar: "انقر الأرقام من الأصغر إلى الأكبر.";
|
|
91
|
+
};
|
|
92
|
+
readonly spell: {
|
|
93
|
+
readonly en: "Tap the letters to spell the word at the top.";
|
|
94
|
+
readonly ar: "انقر الحروف لتكتب الكلمة الظاهرة في الأعلى.";
|
|
95
|
+
};
|
|
96
|
+
readonly orderDots: {
|
|
97
|
+
readonly en: "Order the tiles from fewest dots to most.";
|
|
98
|
+
readonly ar: "رتّب البطاقات من الأقل نقاطًا إلى الأكثر.";
|
|
99
|
+
};
|
|
100
|
+
readonly orderBars: {
|
|
101
|
+
readonly en: "Order the tiles from shortest bar to tallest.";
|
|
102
|
+
readonly ar: "رتّب البطاقات من أقصر عمود إلى أطوله.";
|
|
103
|
+
};
|
|
104
|
+
readonly orderSize: {
|
|
105
|
+
readonly en: "Order the shapes from smallest to largest.";
|
|
106
|
+
readonly ar: "رتّب الأشكال من الأصغر إلى الأكبر.";
|
|
107
|
+
};
|
|
108
|
+
readonly orderNumbersUp: {
|
|
109
|
+
readonly en: "Order the numbers from smallest to largest.";
|
|
110
|
+
readonly ar: "رتّب الأعداد من الأصغر إلى الأكبر.";
|
|
111
|
+
};
|
|
112
|
+
readonly orderNumbersDown: {
|
|
113
|
+
readonly en: "Order the numbers from largest to smallest.";
|
|
114
|
+
readonly ar: "رتّب الأعداد من الأكبر إلى الأصغر.";
|
|
115
|
+
};
|
|
116
|
+
readonly orderTime: {
|
|
117
|
+
readonly en: "Order the clocks from earliest time to latest.";
|
|
118
|
+
readonly ar: "رتّب الساعات من الوقت الأبكر إلى الأحدث.";
|
|
119
|
+
};
|
|
120
|
+
readonly story: {
|
|
121
|
+
readonly en: "Put the pictures in order: {story}.";
|
|
122
|
+
readonly ar: "رتّب الصور: {story}.";
|
|
123
|
+
};
|
|
124
|
+
readonly trace: {
|
|
125
|
+
readonly en: "Connect the dots in order, from 1 to {n}.";
|
|
126
|
+
readonly ar: "صِل النقاط بالترتيب من 1 إلى {n}.";
|
|
127
|
+
};
|
|
128
|
+
readonly traceLetters: {
|
|
129
|
+
readonly en: "Connect the dots in alphabetical order, from A to {last}.";
|
|
130
|
+
readonly ar: "صِل النقاط بالترتيب الأبجدي الإنجليزي من A إلى {last}.";
|
|
131
|
+
};
|
|
132
|
+
readonly maze: {
|
|
133
|
+
readonly en: "Draw a path through the maze from the green dot to the flag.";
|
|
134
|
+
readonly ar: "ارسم طريقًا عبر المتاهة من النقطة الخضراء إلى العلم.";
|
|
135
|
+
};
|
|
136
|
+
readonly math: {
|
|
137
|
+
readonly en: "Solve the sum shown in the picture.";
|
|
138
|
+
readonly ar: "احسب ناتج العملية في الصورة.";
|
|
139
|
+
};
|
|
140
|
+
readonly count: {
|
|
141
|
+
readonly en: "How many {icon} shapes are in the picture?";
|
|
142
|
+
readonly ar: "كم {icon} في الصورة؟";
|
|
143
|
+
};
|
|
144
|
+
readonly dice: {
|
|
145
|
+
readonly en: "Add up the dots on all the dice.";
|
|
146
|
+
readonly ar: "اجمع النقاط الموجودة على كل أحجار النرد.";
|
|
147
|
+
};
|
|
148
|
+
readonly pow: {
|
|
149
|
+
readonly en: "Checking your browser…";
|
|
150
|
+
readonly ar: "جارٍ التحقق من المتصفح…";
|
|
151
|
+
};
|
|
152
|
+
readonly accessible: {
|
|
153
|
+
readonly en: "Answer the question in words or digits.";
|
|
154
|
+
readonly ar: "أجب عن السؤال بالكلمات أو الأرقام.";
|
|
155
|
+
};
|
|
156
|
+
readonly audioDigits: {
|
|
157
|
+
readonly en: "Press Play and type the {n} digits you hear.";
|
|
158
|
+
readonly ar: "اضغط تشغيل واكتب الأرقام الـ{n} التي تسمعها (بالإنجليزية).";
|
|
159
|
+
};
|
|
160
|
+
readonly audioBeeps: {
|
|
161
|
+
readonly en: "Press Play and count the high-pitched beeps. Type how many you heard.";
|
|
162
|
+
readonly ar: "اضغط تشغيل وعُدّ النغمات الحادّة (العالية)، ثم اكتب عددها.";
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
export type PromptKey = keyof typeof PROMPTS;
|
|
166
|
+
export declare function prompt(key: PromptKey, lang: Lang, vars?: Record<string, string | number>): string;
|
|
167
|
+
export declare function pictoName(kind: PictoKind, lang: Lang): string;
|
|
168
|
+
/** @deprecated kept for compatibility; use {@link pictoName}. */
|
|
169
|
+
export declare const iconName: typeof pictoName;
|
|
170
|
+
export declare const COLOR_NAMES: {
|
|
171
|
+
readonly red: {
|
|
172
|
+
readonly en: "red";
|
|
173
|
+
readonly ar: "أحمر";
|
|
174
|
+
readonly pattern: {
|
|
175
|
+
readonly en: "dots";
|
|
176
|
+
readonly ar: "نقاط";
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
readonly blue: {
|
|
180
|
+
readonly en: "blue";
|
|
181
|
+
readonly ar: "أزرق";
|
|
182
|
+
readonly pattern: {
|
|
183
|
+
readonly en: "stripes";
|
|
184
|
+
readonly ar: "خطوط";
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
readonly green: {
|
|
188
|
+
readonly en: "green";
|
|
189
|
+
readonly ar: "أخضر";
|
|
190
|
+
readonly pattern: {
|
|
191
|
+
readonly en: "a cross";
|
|
192
|
+
readonly ar: "علامة زائد";
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
readonly yellow: {
|
|
196
|
+
readonly en: "yellow";
|
|
197
|
+
readonly ar: "أصفر";
|
|
198
|
+
readonly pattern: {
|
|
199
|
+
readonly en: "a ring";
|
|
200
|
+
readonly ar: "حلقة";
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
readonly purple: {
|
|
204
|
+
readonly en: "purple";
|
|
205
|
+
readonly ar: "بنفسجي";
|
|
206
|
+
readonly pattern: {
|
|
207
|
+
readonly en: "a zigzag";
|
|
208
|
+
readonly ar: "خط متعرّج";
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
export type ColorName = keyof typeof COLOR_NAMES;
|
|
213
|
+
export declare const STORIES: {
|
|
214
|
+
readonly plant: {
|
|
215
|
+
readonly en: "a seed grows into a flower";
|
|
216
|
+
readonly ar: "بذرة تنمو حتى تصبح زهرة";
|
|
217
|
+
};
|
|
218
|
+
readonly candle: {
|
|
219
|
+
readonly en: "a candle burns down";
|
|
220
|
+
readonly ar: "شمعة تحترق وتقصر";
|
|
221
|
+
};
|
|
222
|
+
readonly glass: {
|
|
223
|
+
readonly en: "a glass is being filled with water";
|
|
224
|
+
readonly ar: "كوب يُملأ بالماء";
|
|
225
|
+
};
|
|
226
|
+
readonly egg: {
|
|
227
|
+
readonly en: "a chick hatches from an egg";
|
|
228
|
+
readonly ar: "كتكوت يفقس من بيضة";
|
|
229
|
+
};
|
|
230
|
+
readonly battery: {
|
|
231
|
+
readonly en: "a battery charges up";
|
|
232
|
+
readonly ar: "بطارية تُشحن";
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
export type StoryKind = keyof typeof STORIES;
|
|
236
|
+
export {};
|
|
237
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCH,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,OAAO,CAAC;AAE7C,wBAAgB,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAAG,MAAM,CAErG;AA8BD,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAE7D;AAED,iEAAiE;AACjE,eAAO,MAAM,QAAQ,kBAAY,CAAC;AAElC,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAMd,CAAC;AACX,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,WAAW,CAAC;AAEjD,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;CAMV,CAAC;AACX,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,OAAO,CAAC"}
|