@nawa-captcha/svelte 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 +42 -2
- package/package.json +64 -4
- package/src/NawaCaptcha.svelte +69 -0
- package/src/elements.d.ts +22 -0
- package/src/index.d.ts +31 -0
- package/src/index.js +3 -0
package/README.md
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nawa-captcha/svelte
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Nawa Captcha** โ a self-hosted, privacy-friendly and accessible puzzle CAPTCHA. A drop-in alternative to reCAPTCHA, hCaptcha and Turnstile with no third-party tracking: your server issues the challenge and verifies the answer.
|
|
4
|
+
|
|
5
|
+
This package: Svelte 5 component.
|
|
6
|
+
|
|
7
|
+
- ๐งฉ 38 challenge types you choose from (one, two or many, per action): slider jigsaws, rotate, drag to the shadow, jigsaw, hold, icon/colour/clock/symmetry grids, match pairs, tap in order, spell, order and story tiles, connect the dots, maze, math, dice, invisible proof-of-work, an accessible text question and an audio challenge โ [see them live](https://oriebalzyuot96.github.io/nawa-captcha/#playground)
|
|
8
|
+
- ๐ Answers are sealed (AES-256-GCM) and never reach the browser; pass tokens are signed and single-use
|
|
9
|
+
- โฟ Keyboard, touch and screen-reader friendly (text question and audio challenge); Arabic and English; RTL
|
|
10
|
+
- ๐จ Premium 3D design, six themes in light and dark plus high contrast (WCAG 2.2 AA), Pelagic UI tokens, optional sound effects
|
|
11
|
+
- ๐ฅ๏ธ Backends: Node.js / Express and ASP.NET Core (`NawaCaptcha.AspNetCore`, coming to NuGet)
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
> **Coming to npm.** `@nawa-captcha/svelte` is published once the `@nawa-captcha` npm organisation exists. Until then, build it from the [repository](https://github.com/oriebalzyuot96/nawa-captcha) (`npm install && npm run build`, then `npm pack -w @nawa-captcha/svelte`).
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i @nawa-captcha/svelte
|
|
19
|
+
# or
|
|
20
|
+
yarn add @nawa-captcha/svelte
|
|
21
|
+
# or
|
|
22
|
+
pnpm add @nawa-captcha/svelte
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```svelte
|
|
28
|
+
<script lang="ts">
|
|
29
|
+
import { NawaCaptcha } from '@nawa-captcha/svelte';
|
|
30
|
+
let token = $state('');
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<NawaCaptcha bind:token siteKey="main" action="login" lang="en" />
|
|
34
|
+
<button disabled={!token}>Sign in</button>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`types` narrows the server's allowed puzzles: `<NawaCaptcha bind:token action="login" types={['slider', 'pairs']} />` (never widens them).
|
|
38
|
+
|
|
39
|
+
Then verify on your backend before trusting the request โ see the [full guide](https://github.com/oriebalzyuot96/nawa-captcha#readme).
|
|
40
|
+
|
|
41
|
+
๐ Docs: https://oriebalzyuot96.github.io/nawa-captcha/ ยท ๐ป Source: https://github.com/oriebalzyuot96/nawa-captcha
|
|
42
|
+
|
|
43
|
+
MIT ยฉ oriebalzyuot96
|
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nawa-captcha/svelte",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Nawa Captcha for Svelte 5: accessible, self-hosted puzzle CAPTCHA component โ a privacy-friendly reCAPTCHA alternative.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"svelte": "./src/index.js",
|
|
8
|
+
"types": "./src/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./src/index.d.ts",
|
|
12
|
+
"svelte": "./src/index.js",
|
|
13
|
+
"default": "./src/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"typecheck": "svelte-check --tsconfig ./tsconfig.json --fail-on-warnings"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@nawa-captcha/widget": "0.1.1"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"svelte": "^5.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"svelte": "^5.40.0",
|
|
31
|
+
"svelte-check": "^4.3.0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"svelte",
|
|
35
|
+
"svelte5",
|
|
36
|
+
"svelte-component",
|
|
37
|
+
"captcha",
|
|
38
|
+
"recaptcha-alternative",
|
|
39
|
+
"hcaptcha-alternative",
|
|
40
|
+
"turnstile-alternative",
|
|
41
|
+
"self-hosted-captcha",
|
|
42
|
+
"accessible-captcha",
|
|
43
|
+
"puzzle-captcha",
|
|
44
|
+
"slider-captcha",
|
|
45
|
+
"privacy",
|
|
46
|
+
"gdpr",
|
|
47
|
+
"bot-protection",
|
|
48
|
+
"anti-spam",
|
|
49
|
+
"proof-of-work",
|
|
50
|
+
"nawa-captcha",
|
|
51
|
+
"pelagic-ui",
|
|
52
|
+
"arabic",
|
|
53
|
+
"rtl"
|
|
54
|
+
],
|
|
55
|
+
"homepage": "https://oriebalzyuot96.github.io/nawa-captcha/",
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "git+https://github.com/oriebalzyuot96/nawa-captcha.git",
|
|
59
|
+
"directory": "packages/svelte"
|
|
60
|
+
},
|
|
61
|
+
"bugs": "https://github.com/oriebalzyuot96/nawa-captcha/issues",
|
|
62
|
+
"author": "oriebalzyuot96",
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Svelte 5 wrapper for <nawa-captcha>.
|
|
3
|
+
<NawaCaptcha bind:token action="login" lang="ar" onverified={(d) => โฆ} />
|
|
4
|
+
`token` is bindable: it holds the pass token ('' until verified). Call `reset()` (via bind:this)
|
|
5
|
+
after the token has been used, because pass tokens are single-use.
|
|
6
|
+
-->
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import '@nawa-captcha/widget';
|
|
9
|
+
import type { NawaCaptchaElement, NawaCaptchaErrorDetail, NawaCaptchaVerifiedDetail } from '@nawa-captcha/widget';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
siteKey?: string;
|
|
13
|
+
endpoint?: string;
|
|
14
|
+
action?: string;
|
|
15
|
+
lang?: 'ar' | 'en';
|
|
16
|
+
name?: string;
|
|
17
|
+
type?: string;
|
|
18
|
+
/** Narrows the server's allowed puzzle types (array or comma-separated). Never widens it. */
|
|
19
|
+
types?: string | readonly string[];
|
|
20
|
+
theme?: string;
|
|
21
|
+
appearance?: 'auto' | 'light' | 'dark' | 'high-contrast';
|
|
22
|
+
sound?: boolean;
|
|
23
|
+
volume?: number;
|
|
24
|
+
muted?: boolean;
|
|
25
|
+
token?: string;
|
|
26
|
+
onverified?: (detail: NawaCaptchaVerifiedDetail) => void;
|
|
27
|
+
onexpired?: () => void;
|
|
28
|
+
onerror?: (detail: NawaCaptchaErrorDetail) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let { siteKey, endpoint, action, lang, name, type, types, theme, appearance, sound, volume, muted, token = $bindable(''), onverified, onexpired, onerror }: Props = $props();
|
|
32
|
+
|
|
33
|
+
let el: NawaCaptchaElement | undefined = $state();
|
|
34
|
+
const typesAttr = $derived(types === undefined ? undefined : typeof types === 'string' ? types : types.join(','));
|
|
35
|
+
|
|
36
|
+
$effect(() => {
|
|
37
|
+
const node = el;
|
|
38
|
+
if (!node) return;
|
|
39
|
+
const verified = (e: Event): void => {
|
|
40
|
+
const detail = (e as CustomEvent<NawaCaptchaVerifiedDetail>).detail;
|
|
41
|
+
token = detail.token;
|
|
42
|
+
onverified?.(detail);
|
|
43
|
+
};
|
|
44
|
+
const expired = (): void => {
|
|
45
|
+
token = '';
|
|
46
|
+
onexpired?.();
|
|
47
|
+
};
|
|
48
|
+
const failed = (e: Event): void => {
|
|
49
|
+
token = '';
|
|
50
|
+
onerror?.((e as CustomEvent<NawaCaptchaErrorDetail>).detail);
|
|
51
|
+
};
|
|
52
|
+
node.addEventListener('nawa-captcha:verified', verified);
|
|
53
|
+
node.addEventListener('nawa-captcha:expired', expired);
|
|
54
|
+
node.addEventListener('nawa-captcha:error', failed);
|
|
55
|
+
return () => {
|
|
56
|
+
node.removeEventListener('nawa-captcha:verified', verified);
|
|
57
|
+
node.removeEventListener('nawa-captcha:expired', expired);
|
|
58
|
+
node.removeEventListener('nawa-captcha:error', failed);
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/** Clears the token and loads a fresh challenge. */
|
|
63
|
+
export function reset(): void {
|
|
64
|
+
token = '';
|
|
65
|
+
el?.reset();
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<nawa-captcha bind:this={el} site-key={siteKey} {endpoint} {action} {lang} {name} {type} types={typesAttr} {theme} {appearance} {sound} {volume} {muted}></nawa-captcha>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
|
|
3
|
+
// Lets Svelte's type checker know the <nawa-captcha> custom element and its attributes.
|
|
4
|
+
declare module 'svelte/elements' {
|
|
5
|
+
interface SvelteHTMLElements {
|
|
6
|
+
'nawa-captcha': HTMLAttributes<HTMLElement> & {
|
|
7
|
+
'site-key'?: string | undefined;
|
|
8
|
+
endpoint?: string | undefined;
|
|
9
|
+
action?: string | undefined;
|
|
10
|
+
name?: string | undefined;
|
|
11
|
+
type?: string | undefined;
|
|
12
|
+
types?: string | undefined;
|
|
13
|
+
theme?: string | undefined;
|
|
14
|
+
appearance?: string | undefined;
|
|
15
|
+
sound?: boolean | string | undefined;
|
|
16
|
+
volume?: number | string | undefined;
|
|
17
|
+
muted?: boolean | string | undefined;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {};
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
import type { NawaCaptchaErrorDetail, NawaCaptchaVerifiedDetail } from '@nawa-captcha/widget';
|
|
3
|
+
|
|
4
|
+
export type { NawaCaptchaErrorDetail, NawaCaptchaVerifiedDetail };
|
|
5
|
+
|
|
6
|
+
export interface NawaCaptchaProps {
|
|
7
|
+
siteKey?: string;
|
|
8
|
+
endpoint?: string;
|
|
9
|
+
action?: string;
|
|
10
|
+
lang?: 'ar' | 'en';
|
|
11
|
+
name?: string;
|
|
12
|
+
type?: string;
|
|
13
|
+
/** Narrows the server's allowed puzzle types. Never widens it. */
|
|
14
|
+
types?: string | readonly string[];
|
|
15
|
+
/** Theme preset or appearance shorthand (see the widget README). */
|
|
16
|
+
theme?: string;
|
|
17
|
+
appearance?: 'auto' | 'light' | 'dark' | 'high-contrast';
|
|
18
|
+
sound?: boolean;
|
|
19
|
+
volume?: number;
|
|
20
|
+
muted?: boolean;
|
|
21
|
+
token?: string;
|
|
22
|
+
onverified?: (detail: NawaCaptchaVerifiedDetail) => void;
|
|
23
|
+
onexpired?: () => void;
|
|
24
|
+
onerror?: (detail: NawaCaptchaErrorDetail) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export declare const NawaCaptcha: Component<NawaCaptchaProps, { reset(): void }, 'token'>;
|
|
28
|
+
/** @deprecated Renamed to {@link NawaCaptcha} (Nawa Guard is now Nawa Captcha). */
|
|
29
|
+
export declare const NawaGuard: typeof NawaCaptcha;
|
|
30
|
+
/** @deprecated Renamed to NawaCaptchaProps. */
|
|
31
|
+
export type NawaGuardProps = NawaCaptchaProps;
|
package/src/index.js
ADDED