release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-02 23:18:51 +05:30
parent 87507edf59
commit 586a6db8ff
625 changed files with 243608 additions and 11210 deletions
+11
View File
@@ -360,3 +360,14 @@ const engine = createCaptchaEngine({ secret, generators: [wordChallenge] });
```
Applications may also implement `CaptchaStore`, `CaptchaAudioRenderer`, or use `defineCaptchaProvider()` for a completely custom service.
## Helper and block kit
The package exports `captchaTokenFrom`, `captchaHeaders`, `captchaFields`, `verifyCaptcha`, `verifyCaptchaOrThrow`, `captchaResultResponse`, and `captchaContext` for consistent server and client integration.
Enable the CAPTCHA plugin to use the low-level `<Captcha />` challenge plus complete UI-composed blocks:
- `<CaptchaField />`
- `<CaptchaStatus />`
`CaptchaField` composes `Card` from `@wrnexus/ui` and keeps the CAPTCHA-specific size separate from the surrounding UI size.
@@ -0,0 +1,18 @@
component CaptchaField {
props {
title: string = "Security verification"
description: string = "Complete the challenge before submitting."
provider: string = "self-hosted"
type: string = "alphanumeric"
action: string = "form-submit"
color: string = "primary"
size: string = "md"
captchaSize: string = "normal"
class: string = ""
}
view {
<Card {...attrs} title='{title}' description='{description}' color='{color}' size='{size}' class='{class}'>
<Captcha provider='{provider}' type='{type}' action='{action}' color='{color}' size='{captchaSize}' compact='{size == "sm"}' />
</Card>
}
}
@@ -0,0 +1,23 @@
component CaptchaStatus {
props {
success: boolean = false
message: string = ""
successMessage: string = "Security verification completed."
failureMessage: string = "Security verification is required."
color: string = "primary"
size: string = "sm"
class: string = ""
}
view {
<Alert
{...attrs}
title='{success ? "Verified" : "Verification required"}'
description='{message || (success ? successMessage : failureMessage)}'
icon='{success ? "icon-[lucide--shield-check]" : "icon-[lucide--shield-alert]"}'
color='{success ? "success" : color}'
variant="soft"
size='{size}'
class='{class}'
/>
}
}
+4 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/captcha",
"version": "0.7.0",
"version": "0.8.0",
"description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.",
"type": "module",
"sideEffects": false,
@@ -43,10 +43,11 @@
"dependencies": {
"@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*",
"@wrnexus/validation": "workspace:*"
"@wrnexus/validation": "workspace:*",
"@wrnexus/ui": "workspace:*"
},
"devDependencies": {
"@types/bun": "latest",
"@types/bun": "^1.3.14",
"typescript": "^5.9.2",
"@wrnexus/syntax": "workspace:*"
},
+90
View File
@@ -0,0 +1,90 @@
import type { Context } from "@wrnexus/core";
import type {
CaptchaEngine,
CaptchaProvider,
CaptchaVerificationResult,
VerifyCaptchaInput,
} from "./types.ts";
import { selfHostedProvider } from "./providers/self-hosted.ts";
export function captchaTokenFrom(
value: Request | Headers | FormData | URLSearchParams | Record<string, unknown>,
field = "wrn-captcha-response",
): Promise<string | undefined> | string | undefined {
if (value instanceof Request) {
const header = value.headers.get("x-wrn-captcha-token");
if (header) return header;
return value
.clone()
.formData()
.then((form) => {
const token = form.get(field) ?? form.get("captchaToken") ?? form.get("responseToken");
return typeof token === "string" ? token : undefined;
})
.catch(async () => {
try {
const body = (await value.clone().json()) as Record<string, unknown>;
const token = body[field] ?? body.captchaToken ?? body.responseToken;
return token === undefined ? undefined : String(token);
} catch {
return undefined;
}
});
}
if (value instanceof Headers) {
return value.get("x-wrn-captcha-token") ?? value.get("x-captcha-token") ?? undefined;
}
if (value instanceof FormData || value instanceof URLSearchParams) {
const token = value.get(field) ?? value.get("captchaToken") ?? value.get("responseToken");
return typeof token === "string" ? token : undefined;
}
const token = value[field] ?? value.captchaToken ?? value.responseToken;
return token === undefined ? undefined : String(token);
}
export function captchaHeaders(token: string): HeadersInit {
return { "x-wrn-captcha-token": token };
}
export function captchaFields(
token: string,
field = "wrn-captcha-response",
): Record<string, string> {
return { [field]: token };
}
export async function verifyCaptcha(
providerOrEngine: CaptchaProvider | CaptchaEngine,
input: VerifyCaptchaInput,
): Promise<CaptchaVerificationResult> {
const provider: CaptchaProvider =
"client" in providerOrEngine
? (providerOrEngine as CaptchaProvider)
: selfHostedProvider(providerOrEngine as CaptchaEngine);
return provider.verify(input);
}
export async function verifyCaptchaOrThrow(
providerOrEngine: CaptchaProvider | CaptchaEngine,
input: VerifyCaptchaInput,
): Promise<CaptchaVerificationResult> {
const result = await verifyCaptcha(providerOrEngine, input);
if (!result.success) {
throw new Error(
`WRN-CAPTCHA-${String(result.code ?? "FAILED").toUpperCase()}: ${result.message ?? "CAPTCHA verification failed"}`,
);
}
return result;
}
export function captchaResultResponse(result: CaptchaVerificationResult): Response {
return Response.json(result, {
status: result.success ? 200 : 403,
headers: { "cache-control": "no-store", "x-content-type-options": "nosniff" },
});
}
export function captchaContext(ctx: Context): CaptchaVerificationResult | null {
const result = ctx.locals.captcha;
return result && typeof result === "object" ? (result as CaptchaVerificationResult) : null;
}
+1
View File
@@ -12,3 +12,4 @@ export * from "./providers/index.ts";
export * from "./challenges/index.ts";
export * from "./audio/index.ts";
export * from "./crypto.ts";
export * from "./helpers.ts";
+19
View File
@@ -0,0 +1,19 @@
import { describe, expect, test } from "bun:test";
import { captchaFields, captchaHeaders, captchaTokenFrom } from "../src/index.ts";
describe("CAPTCHA helper kit", () => {
test("reads standard response tokens from forms, headers, and objects", async () => {
const form = new FormData();
form.set("wrn-captcha-response", "form-token");
expect(await captchaTokenFrom(form)).toBe("form-token");
expect(await captchaTokenFrom(new Headers({ "x-wrn-captcha-token": "header-token" }))).toBe(
"header-token",
);
expect(await captchaTokenFrom({ "wrn-captcha-response": "object-token" })).toBe("object-token");
});
test("creates consistent fields and headers", () => {
expect(captchaFields("token")).toEqual({ "wrn-captcha-response": "token" });
expect(new Headers(captchaHeaders("token")).get("x-wrn-captcha-token")).toBe("token");
});
});